Properly format day delimiter
This commit is contained in:
@ -39,6 +39,7 @@ color-eyre = "0.6.2"
|
|||||||
human-panic = "1.2.0"
|
human-panic = "1.2.0"
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
|
enum_dispatch = "0.3.12"
|
||||||
inventory = "0.3"
|
inventory = "0.3"
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
@ -60,6 +61,7 @@ matrix-sdk-ui = { git = "https://github.com/matrix-org/matrix-rust-sdk.git", rev
|
|||||||
|
|
||||||
# UI
|
# UI
|
||||||
ansi-to-tui = "3.1.0"
|
ansi-to-tui = "3.1.0"
|
||||||
|
chrono = "0.4.31"
|
||||||
crossterm = { version = "0.27.0", features = ["serde", "event-stream"] }
|
crossterm = { version = "0.27.0", features = ["serde", "event-stream"] }
|
||||||
ratatui = { version = "0.24.0", features = ["serde", "macros"] }
|
ratatui = { version = "0.24.0", features = ["serde", "macros"] }
|
||||||
strip-ansi-escapes = "0.2.0"
|
strip-ansi-escapes = "0.2.0"
|
||||||
|
@ -24,7 +24,7 @@ use tokio::sync::mpsc::UnboundedReceiver;
|
|||||||
use tracing_error::ErrorLayer;
|
use tracing_error::ErrorLayer;
|
||||||
use tracing_subscriber::prelude::*;
|
use tracing_subscriber::prelude::*;
|
||||||
|
|
||||||
use super::{Buffer, BufferItem};
|
use super::{Buffer, BufferItem, BufferItemContent};
|
||||||
use crate::widgets::Prerender;
|
use crate::widgets::Prerender;
|
||||||
|
|
||||||
/// Maximum number of log lines to be stored in memory
|
/// Maximum number of log lines to be stored in memory
|
||||||
@ -71,10 +71,10 @@ impl Buffer for LogBuffer {
|
|||||||
.chain(slice2.into_iter())
|
.chain(slice2.into_iter())
|
||||||
.rev()
|
.rev()
|
||||||
.map(|(line, prerender)| BufferItem {
|
.map(|(line, prerender)| BufferItem {
|
||||||
text: line.clone().into_text().unwrap_or_else(|e| {
|
content: BufferItemContent::Text(line.clone().into_text().unwrap_or_else(|e| {
|
||||||
tracing::error!("Could not convert line from ANSI codes to ratatui: {}", e);
|
tracing::error!("Could not convert line from ANSI codes to ratatui: {}", e);
|
||||||
Text::raw(line)
|
Text::raw(line)
|
||||||
}),
|
})),
|
||||||
prerender,
|
prerender,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -26,8 +26,15 @@ pub use log::LogBuffer;
|
|||||||
mod room;
|
mod room;
|
||||||
pub use room::RoomBuffer;
|
pub use room::RoomBuffer;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum BufferItemContent<'buf> {
|
||||||
|
Text(Text<'buf>),
|
||||||
|
Divider(Text<'buf>),
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BufferItem<'buf> {
|
pub struct BufferItem<'buf> {
|
||||||
pub text: Text<'buf>,
|
pub content: BufferItemContent<'buf>,
|
||||||
pub prerender: &'buf Prerender,
|
pub prerender: &'buf Prerender,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
use std::sync::atomic::{AtomicU16, Ordering};
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
|
use chrono::{offset::Local, DateTime};
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use color_eyre::eyre::{eyre, Result};
|
||||||
use eyeball_im::VectorDiff;
|
use eyeball_im::VectorDiff;
|
||||||
use futures::{FutureExt, Stream, StreamExt};
|
use futures::{FutureExt, Stream, StreamExt};
|
||||||
@ -32,13 +33,28 @@ use matrix_sdk_ui::timeline::{
|
|||||||
use ratatui::text::Text;
|
use ratatui::text::Text;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use super::{Buffer, BufferItem};
|
use super::{Buffer, BufferItem, BufferItemContent};
|
||||||
use crate::widgets::Prerender;
|
use crate::widgets::Prerender;
|
||||||
|
|
||||||
|
/// Like [`BufferItemContent`] but owned.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum OwnedBufferItemContent {
|
||||||
|
Text(String),
|
||||||
|
Divider(String),
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Like `format!()` but returns OwnedBufferItemContent::Text
|
||||||
|
macro_rules! text {
|
||||||
|
($($tokens:tt)*) => {
|
||||||
|
OwnedBufferItemContent::Text(format!($($tokens)*))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SingleClientRoomBuffer {
|
pub struct SingleClientRoomBuffer {
|
||||||
room_id: OwnedRoomId,
|
room_id: OwnedRoomId,
|
||||||
client: Client,
|
client: Client,
|
||||||
items: imbl::vector::Vector<(String, Prerender)>,
|
items: imbl::vector::Vector<(OwnedBufferItemContent, Prerender)>,
|
||||||
// TODO: get rid of this trait object, we know it's matrix_sdk_ui::timeline::TimelineStream
|
// TODO: get rid of this trait object, we know it's matrix_sdk_ui::timeline::TimelineStream
|
||||||
stream: Box<dyn Stream<Item = Vec<VectorDiff<Arc<TimelineItem>>>> + Send + Sync + Unpin>,
|
stream: Box<dyn Stream<Item = Vec<VectorDiff<Arc<TimelineItem>>>> + Send + Sync + Unpin>,
|
||||||
timeline: Arc<Timeline>,
|
timeline: Arc<Timeline>,
|
||||||
@ -61,7 +77,7 @@ impl SingleClientRoomBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_timeline_item(&self, tl_item: impl AsRef<TimelineItem>) -> String {
|
fn format_timeline_item(&self, tl_item: impl AsRef<TimelineItem>) -> OwnedBufferItemContent {
|
||||||
match tl_item.as_ref().kind() {
|
match tl_item.as_ref().kind() {
|
||||||
TimelineItemKind::Event(event) => {
|
TimelineItemKind::Event(event) => {
|
||||||
use matrix_sdk_ui::timeline::TimelineItemContent::*;
|
use matrix_sdk_ui::timeline::TimelineItemContent::*;
|
||||||
@ -71,54 +87,54 @@ impl SingleClientRoomBuffer {
|
|||||||
.strip_prefix("@")
|
.strip_prefix("@")
|
||||||
.expect("missing @ prefix");
|
.expect("missing @ prefix");
|
||||||
match event.content() {
|
match event.content() {
|
||||||
Message(message) => format!(" <{}> {}", sender, message.body().replace("\n", "\n ")),
|
Message(message) => text!(" <{}> {}", sender, message.body().replace("\n", "\n ")),
|
||||||
RedactedMessage => format!("xx <{}> [redacted]", sender),
|
RedactedMessage => text!("xx <{}> [redacted]", sender),
|
||||||
Sticker(sticker) => format!("st <{}> {}", sender, sticker.content().body),
|
Sticker(sticker) => text!("st <{}> {}", sender, sticker.content().body),
|
||||||
UnableToDecrypt(_) => format!("xx <{}> [unable to decrypt]", sender),
|
UnableToDecrypt(_) => text!("xx <{}> [unable to decrypt]", sender),
|
||||||
MembershipChange(change) => {
|
MembershipChange(change) => {
|
||||||
use matrix_sdk_ui::timeline::MembershipChange::*;
|
use matrix_sdk_ui::timeline::MembershipChange::*;
|
||||||
if change.user_id() == event.sender() {
|
if change.user_id() == event.sender() {
|
||||||
let Some(change_kind) = change.change() else {
|
let Some(change_kind) = change.change() else {
|
||||||
return format!("--- {} made incomprehensible changes to themselves", sender);
|
return text!("--- {} made incomprehensible changes to themselves", sender);
|
||||||
};
|
};
|
||||||
match change_kind {
|
match change_kind {
|
||||||
None => format!("--- {} made no discernable changes to themselves", sender),
|
None => text!("--- {} made no discernable changes to themselves", sender),
|
||||||
Error => format!(
|
Error => text!(
|
||||||
"xxx {} made a change to themselves that made matrix-sdk-ui error",
|
"xxx {} made a change to themselves that made matrix-sdk-ui error",
|
||||||
sender
|
sender
|
||||||
),
|
),
|
||||||
Joined => format!("--> {} joined", sender),
|
Joined => text!("--> {} joined", sender),
|
||||||
Left => format!("<-- {} left", sender),
|
Left => text!("<-- {} left", sender),
|
||||||
Banned => format!("-x- {} banned themselves", sender),
|
Banned => text!("-x- {} banned themselves", sender),
|
||||||
Unbanned => format!("-x- {} unbanned themselves", sender),
|
Unbanned => text!("-x- {} unbanned themselves", sender),
|
||||||
Kicked => format!("<!- {} kicked themselves", sender),
|
Kicked => text!("<!- {} kicked themselves", sender),
|
||||||
Invited => format!("-o- {} invited themselves", sender),
|
Invited => text!("-o- {} invited themselves", sender),
|
||||||
KickedAndBanned => format!("<!x {} kicked and banned themselves", sender),
|
KickedAndBanned => text!("<!x {} kicked and banned themselves", sender),
|
||||||
InvitationAccepted => format!("-o> {} accepted an invite", sender),
|
InvitationAccepted => text!("-o> {} accepted an invite", sender),
|
||||||
InvitationRejected => format!("-ox {} rejected an invite", sender),
|
InvitationRejected => text!("-ox {} rejected an invite", sender),
|
||||||
InvitationRevoked => format!("--x {} revoked an invite", sender),
|
InvitationRevoked => text!("--x {} revoked an invite", sender),
|
||||||
Knocked => format!("-?> {} knocked", sender),
|
Knocked => text!("-?> {} knocked", sender),
|
||||||
KnockAccepted => format!("-?o {} accepted a knock", sender),
|
KnockAccepted => text!("-?o {} accepted a knock", sender),
|
||||||
KnockRetracted => format!("-?x {} retracted a knock", sender),
|
KnockRetracted => text!("-?x {} retracted a knock", sender),
|
||||||
KnockDenied => format!("-?x {} denied a knock", sender),
|
KnockDenied => text!("-?x {} denied a knock", sender),
|
||||||
NotImplemented => format!(
|
NotImplemented => text!(
|
||||||
"xxx {} made a change matrix-sdk-ui does not support yet",
|
"xxx {} made a change matrix-sdk-ui does not support yet",
|
||||||
sender
|
sender
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
} else if change.user_id() == "" {
|
} else if change.user_id() == "" {
|
||||||
let Some(change_kind) = change.change() else {
|
let Some(change_kind) = change.change() else {
|
||||||
return format!("--- {} made incomprehensible changes", sender);
|
return text!("--- {} made incomprehensible changes", sender);
|
||||||
};
|
};
|
||||||
match change_kind {
|
match change_kind {
|
||||||
None => format!("--- {} made no discernable changes", sender),
|
None => text!("--- {} made no discernable changes", sender),
|
||||||
Error => format!("xxx {} made a change that made matrix-sdk-ui error", sender),
|
Error => text!("xxx {} made a change that made matrix-sdk-ui error", sender),
|
||||||
Joined | Left | Banned | Unbanned | Kicked | Invited | KickedAndBanned
|
Joined | Left | Banned | Unbanned | Kicked | Invited | KickedAndBanned
|
||||||
| InvitationAccepted | InvitationRejected | InvitationRevoked | Knocked
|
| InvitationAccepted | InvitationRejected | InvitationRevoked | Knocked
|
||||||
| KnockAccepted | KnockRetracted | KnockDenied => {
|
| KnockAccepted | KnockRetracted | KnockDenied => {
|
||||||
format!("--> {} made a non-sensical change: {:?}", sender, change)
|
text!("--> {} made a non-sensical change: {:?}", sender, change)
|
||||||
},
|
},
|
||||||
NotImplemented => format!(
|
NotImplemented => text!(
|
||||||
"xxx {} made a change matrix-sdk-ui does not support yet",
|
"xxx {} made a change matrix-sdk-ui does not support yet",
|
||||||
sender
|
sender
|
||||||
),
|
),
|
||||||
@ -130,44 +146,48 @@ impl SingleClientRoomBuffer {
|
|||||||
.strip_prefix("@")
|
.strip_prefix("@")
|
||||||
.expect("missing @ prefix");
|
.expect("missing @ prefix");
|
||||||
let Some(change_kind) = change.change() else {
|
let Some(change_kind) = change.change() else {
|
||||||
return format!("--- {} made incomprehensible changes to {}", sender, target);
|
return text!("--- {} made incomprehensible changes to {}", sender, target);
|
||||||
};
|
};
|
||||||
match change_kind {
|
match change_kind {
|
||||||
None => format!("--- {} made no discernable changes to {}", sender, target),
|
None => text!("--- {} made no discernable changes to {}", sender, target),
|
||||||
Error => format!(
|
Error => text!(
|
||||||
"xxx {} made a change to {} that made matrix-sdk-ui error",
|
"xxx {} made a change to {} that made matrix-sdk-ui error",
|
||||||
sender, target
|
sender,
|
||||||
|
target
|
||||||
),
|
),
|
||||||
Joined | Left => format!(
|
Joined | Left => text!(
|
||||||
"--> {} made a non-sensical change to {}: {:?}",
|
"--> {} made a non-sensical change to {}: {:?}",
|
||||||
sender, target, change
|
sender,
|
||||||
|
target,
|
||||||
|
change
|
||||||
),
|
),
|
||||||
Banned => format!("-x- {} banned {}", sender, target),
|
Banned => text!("-x- {} banned {}", sender, target),
|
||||||
Unbanned => format!("-x- {} unbanned {}", sender, target),
|
Unbanned => text!("-x- {} unbanned {}", sender, target),
|
||||||
Kicked => format!("<!- {} kicked {}", sender, target),
|
Kicked => text!("<!- {} kicked {}", sender, target),
|
||||||
Invited => format!("-o- {} invited {}", sender, target),
|
Invited => text!("-o- {} invited {}", sender, target),
|
||||||
KickedAndBanned => format!("<!x {} kicked and banned {}", sender, target),
|
KickedAndBanned => text!("<!x {} kicked and banned {}", sender, target),
|
||||||
InvitationAccepted => format!("-o> {} accepted an invite to {}", sender, target),
|
InvitationAccepted => text!("-o> {} accepted an invite to {}", sender, target),
|
||||||
InvitationRejected => format!("-ox {} rejected an invite to {}", sender, target),
|
InvitationRejected => text!("-ox {} rejected an invite to {}", sender, target),
|
||||||
InvitationRevoked => format!("--x {} revoked an invite to {}", sender, target),
|
InvitationRevoked => text!("--x {} revoked an invite to {}", sender, target),
|
||||||
Knocked => format!("-?> {} made {} knock", sender, target),
|
Knocked => text!("-?> {} made {} knock", sender, target),
|
||||||
KnockAccepted => format!("-?o {} accepted {}'s knock", sender, target),
|
KnockAccepted => text!("-?o {} accepted {}'s knock", sender, target),
|
||||||
KnockRetracted => format!("-?x {} retracted {}'s knock", sender, target),
|
KnockRetracted => text!("-?x {} retracted {}'s knock", sender, target),
|
||||||
KnockDenied => format!("-?x {} denied {}'s knock", sender, target),
|
KnockDenied => text!("-?x {} denied {}'s knock", sender, target),
|
||||||
NotImplemented => format!(
|
NotImplemented => text!(
|
||||||
"xxx {} made a change to {} that matrix-sdk-ui does not support yet",
|
"xxx {} made a change to {} that matrix-sdk-ui does not support yet",
|
||||||
sender, target
|
sender,
|
||||||
|
target
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
ProfileChange(_) => format!("--- {} updated their profile", sender),
|
ProfileChange(_) => text!("--- {} updated their profile", sender),
|
||||||
OtherState(state) => {
|
OtherState(state) => {
|
||||||
if state.state_key() == "" {
|
if state.state_key() == "" {
|
||||||
format!("--- {} changed the room: {:?}", sender, state.content())
|
text!("--- {} changed the room: {:?}", sender, state.content())
|
||||||
} else {
|
} else {
|
||||||
format!(
|
text!(
|
||||||
"--- {} changed {}: {:?}",
|
"--- {} changed {}: {:?}",
|
||||||
sender,
|
sender,
|
||||||
state.state_key(),
|
state.state_key(),
|
||||||
@ -175,28 +195,42 @@ impl SingleClientRoomBuffer {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
FailedToParseMessageLike { event_type, error } => format!(
|
FailedToParseMessageLike { event_type, error } => text!(
|
||||||
"xxx {} sent a {} message that made matrix-sdk-ui error: {:?}",
|
"xxx {} sent a {} message that made matrix-sdk-ui error: {:?}",
|
||||||
sender, event_type, error
|
sender,
|
||||||
|
event_type,
|
||||||
|
error
|
||||||
),
|
),
|
||||||
FailedToParseState {
|
FailedToParseState {
|
||||||
event_type,
|
event_type,
|
||||||
state_key,
|
state_key,
|
||||||
error,
|
error,
|
||||||
} => {
|
} => {
|
||||||
format!(
|
text!(
|
||||||
"xxx {} made a {} change to {} that made matrix-sdk-ui error: {:?}",
|
"xxx {} made a {} change to {} that made matrix-sdk-ui error: {:?}",
|
||||||
sender, event_type, state_key, error
|
sender,
|
||||||
|
event_type,
|
||||||
|
state_key,
|
||||||
|
error
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Poll(_) => format!("-?- {} acted on a poll", sender),
|
Poll(_) => text!("-?- {} acted on a poll", sender),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker) => {
|
TimelineItemKind::Virtual(VirtualTimelineItem::ReadMarker) => {
|
||||||
format!("---- read marker ----")
|
OwnedBufferItemContent::Divider(format!("read marker"))
|
||||||
},
|
},
|
||||||
TimelineItemKind::Virtual(VirtualTimelineItem::DayDivider(day_divider)) => {
|
TimelineItemKind::Virtual(VirtualTimelineItem::DayDivider(day_divider)) => {
|
||||||
format!("---- day divider: {:?} ----", day_divider)
|
match day_divider.to_system_time() {
|
||||||
|
Some(system_time) => {
|
||||||
|
let datetime: DateTime<Local> = system_time.into();
|
||||||
|
OwnedBufferItemContent::Divider(format!("{}", datetime.format("%Y-%m-%d")))
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
tracing::warn!("Could not convert {:?} to system time", day_divider);
|
||||||
|
OwnedBufferItemContent::Empty
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -279,7 +313,7 @@ impl RoomBuffer {
|
|||||||
timeline: Arc::new(timeline),
|
timeline: Arc::new(timeline),
|
||||||
items: items // FIXME: it's always empty. why?
|
items: items // FIXME: it's always empty. why?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|item| (format!("Initial item: {:#?}", item), Prerender::new()))
|
.map(|item| (text!("Initial item: {:#?}", item), Prerender::new()))
|
||||||
.collect(),
|
.collect(),
|
||||||
stream: Box::new(stream),
|
stream: Box::new(stream),
|
||||||
back_pagination_request: AtomicU16::new(0),
|
back_pagination_request: AtomicU16::new(0),
|
||||||
@ -326,7 +360,11 @@ impl Buffer for RoomBuffer {
|
|||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
.map(|(line, prerender)| BufferItem {
|
.map(|(line, prerender)| BufferItem {
|
||||||
text: Text::raw(line),
|
content: match line {
|
||||||
|
OwnedBufferItemContent::Text(text) => BufferItemContent::Text(Text::raw(text)),
|
||||||
|
OwnedBufferItemContent::Divider(text) => BufferItemContent::Divider(Text::raw(text)),
|
||||||
|
OwnedBufferItemContent::Empty => BufferItemContent::Empty,
|
||||||
|
},
|
||||||
prerender,
|
prerender,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -18,14 +18,17 @@ use std::ops::DerefMut;
|
|||||||
|
|
||||||
use color_eyre::eyre::{Result, WrapErr};
|
use color_eyre::eyre::{Result, WrapErr};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
use ratatui::{prelude::*, widgets::*};
|
use ratatui::{prelude::*, widgets::*};
|
||||||
|
|
||||||
use crate::components::Action;
|
use crate::components::Action;
|
||||||
use crate::widgets::prerender::{PrerenderInner, PrerenderValue};
|
use crate::widgets::prerender::{PrerenderInner, PrerenderValue};
|
||||||
use crate::widgets::{BottomAlignedParagraph, OverlappableWidget};
|
use crate::widgets::{
|
||||||
|
BacklogItemWidget, BottomAlignedParagraph, Divider, EmptyWidget, OverlappableWidget,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Component;
|
use super::Component;
|
||||||
use crate::buffers::BufferItem;
|
use crate::buffers::{BufferItem, BufferItemContent};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Backlog {
|
pub struct Backlog {
|
||||||
@ -83,6 +86,21 @@ impl Backlog {
|
|||||||
pub fn scroll_down(&mut self, lines: u64) {
|
pub fn scroll_down(&mut self, lines: u64) {
|
||||||
self.scroll = self.scroll.saturating_sub(20);
|
self.scroll = self.scroll.saturating_sub(20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_widget<'a>(&self, content: BufferItemContent<'a>, scroll: u64) -> BacklogItemWidget<'a> {
|
||||||
|
match content {
|
||||||
|
BufferItemContent::Text(text) => BottomAlignedParagraph::new(text).scroll(scroll).into(),
|
||||||
|
BufferItemContent::Divider(text) => {
|
||||||
|
if scroll == 0 {
|
||||||
|
Divider::new(Paragraph::new(text).alignment(Alignment::Center)).into()
|
||||||
|
} else {
|
||||||
|
EmptyWidget.into()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BufferItemContent::Empty => EmptyWidget.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns how many lines are unused at the top of the area.
|
/// Returns how many lines are unused at the top of the area.
|
||||||
pub fn draw_items<'a>(
|
pub fn draw_items<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -111,7 +129,7 @@ impl Backlog {
|
|||||||
value: PrerenderValue::NotRendered(height),
|
value: PrerenderValue::NotRendered(height),
|
||||||
}) if *key == text_area.width => *height,
|
}) if *key == text_area.width => *height,
|
||||||
prerender => {
|
prerender => {
|
||||||
let widget = BottomAlignedParagraph::new(item.text.clone());
|
let widget = self.build_widget(item.content.clone(), 0);
|
||||||
let expected_height = widget.height(text_area.width);
|
let expected_height = widget.height(text_area.width);
|
||||||
*prerender = Some(PrerenderInner {
|
*prerender = Some(PrerenderInner {
|
||||||
key: text_area.width,
|
key: text_area.width,
|
||||||
@ -128,7 +146,7 @@ impl Backlog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: cache this
|
// TODO: cache this
|
||||||
let widget = BottomAlignedParagraph::new(item.text).scroll(scroll);
|
let widget = self.build_widget(item.content, scroll);
|
||||||
let (_, height) = widget.render_overlap(text_area, frame_buffer);
|
let (_, height) = widget.render_overlap(text_area, frame_buffer);
|
||||||
text_area.height = text_area.height.saturating_sub(height);
|
text_area.height = text_area.height.saturating_sub(height);
|
||||||
|
|
||||||
@ -173,7 +191,7 @@ impl Backlog {
|
|||||||
buf.area().height
|
buf.area().height
|
||||||
},
|
},
|
||||||
prerender => {
|
prerender => {
|
||||||
let widget = BottomAlignedParagraph::new(item.text);
|
let widget = self.build_widget(item.content, 0);
|
||||||
let (drawn_width, height) = widget.render_overlap(text_area, frame_buffer);
|
let (drawn_width, height) = widget.render_overlap(text_area, frame_buffer);
|
||||||
assert!(drawn_width <= text_area.width);
|
assert!(drawn_width <= text_area.width);
|
||||||
|
|
||||||
|
@ -75,14 +75,13 @@ impl<'a> BottomAlignedParagraph<'a> {
|
|||||||
.take(u16::MAX as usize) // Avoid overflows in ratatui for excessively long messages
|
.take(u16::MAX as usize) // Avoid overflows in ratatui for excessively long messages
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns how many lines it would actuall draw if rendered with the given height
|
|
||||||
pub fn height(&self, width: u16) -> u64 {
|
|
||||||
self.wrap_lines(width).len() as u64
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> OverlappableWidget for BottomAlignedParagraph<'a> {
|
impl<'a> OverlappableWidget for BottomAlignedParagraph<'a> {
|
||||||
|
fn height(&self, width: u16) -> u64 {
|
||||||
|
self.wrap_lines(width).len() as u64
|
||||||
|
}
|
||||||
|
|
||||||
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> (u16, u16) {
|
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> (u16, u16) {
|
||||||
// Inspired by https://github.com/ratatui-org/ratatui/blob/9f371000968044e09545d66068c4ed4ea4b35d8a/src/widgets/paragraph.rs#L214-L275
|
// Inspired by https://github.com/ratatui-org/ratatui/blob/9f371000968044e09545d66068c4ed4ea4b35d8a/src/widgets/paragraph.rs#L214-L275
|
||||||
let lines = self.wrap_lines(area.width);
|
let lines = self.wrap_lines(area.width);
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use enum_dispatch::enum_dispatch;
|
||||||
use ratatui::prelude::*;
|
use ratatui::prelude::*;
|
||||||
use ratatui::widgets::Widget;
|
use ratatui::widgets::Widget;
|
||||||
|
|
||||||
@ -23,15 +24,45 @@ pub use bottom_aligned_paragraph::BottomAlignedParagraph;
|
|||||||
pub(crate) mod prerender;
|
pub(crate) mod prerender;
|
||||||
pub use prerender::Prerender;
|
pub use prerender::Prerender;
|
||||||
|
|
||||||
|
mod divider;
|
||||||
|
pub use divider::Divider;
|
||||||
|
|
||||||
#[rustfmt::skip] // reflow is vendored from ratatui, let's avoid changes
|
#[rustfmt::skip] // reflow is vendored from ratatui, let's avoid changes
|
||||||
mod reflow;
|
mod reflow;
|
||||||
|
|
||||||
/// A [`Widget`] that returns how many columns and lines it needs to draw everything
|
/// A [`Widget`] that returns how many columns and lines it needs to draw everything
|
||||||
/// (which is the number of lines it actually drew if it fits on screen)
|
/// (which is the number of lines it actually drew if it fits on screen)
|
||||||
|
#[enum_dispatch]
|
||||||
pub trait OverlappableWidget {
|
pub trait OverlappableWidget {
|
||||||
|
/// Returns how many lines, from the bottom of the area, this widget would actually draw
|
||||||
|
/// if rendered with the given width
|
||||||
|
fn height(&self, width: u16) -> u64;
|
||||||
|
|
||||||
|
/// Render the widget from the bottom, and return the width and height of the area
|
||||||
|
/// actually drawn.
|
||||||
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> (u16, u16);
|
fn render_overlap(self, area: Rect, buf: &mut Buffer) -> (u16, u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Enum of [`OverlappableWidget`] implementors
|
||||||
|
#[enum_dispatch(OverlappableWidget)]
|
||||||
|
pub enum BacklogItemWidget<'a> {
|
||||||
|
Paragraph(BottomAlignedParagraph<'a>),
|
||||||
|
Divider(Divider<'a>),
|
||||||
|
Empty(EmptyWidget),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct EmptyWidget;
|
||||||
|
|
||||||
|
impl OverlappableWidget for EmptyWidget {
|
||||||
|
fn height(&self, _width: u16) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn render_overlap(self, _area: Rect, _buf: &mut Buffer) -> (u16, u16) {
|
||||||
|
(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
impl<W: OverlappableWidget> Widget for W {
|
impl<W: OverlappableWidget> Widget for W {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
Reference in New Issue
Block a user