Text Display
Group: Content
Code
/// Distinctive word placed at the very end of the long line in the
/// word-wrap variant. Never painted anywhere on screen if `TextDisplay`
/// truncates instead of wrapping.
const TAIL_MARKER: &str = "TAILMARKER";
fn long_line_text() -> String {
let mut s = String::from(
"This one line of prose is deliberately far wider than the viewport \
so that it cannot possibly fit on a single row no matter how the \
backend measures character width, which is exactly the scenario \
a pane that silently truncated long lines at its right edge \
instead of wrapping them onto continuation rows the reader could \
actually scroll to and read, ",
);
s.push_str(TAIL_MARKER);
s
}
fn log_lines() -> Vec<TextDisplayLine> {
vec![
TextDisplayLine {
spans: vec![StyledSpan::plain("server started on :8080")],
decoration: Decoration::Normal,
timestamp: Some("12:00:00".into()),
},
TextDisplayLine {
spans: vec![StyledSpan::plain("accepted connection from 10.0.0.4")],
decoration: Decoration::Normal,
timestamp: Some("12:00:02".into()),
},
TextDisplayLine {
spans: vec![StyledSpan::plain("slow query: 840ms")],
decoration: Decoration::Warning,
timestamp: Some("12:00:05".into()),
},
TextDisplayLine {
spans: vec![StyledSpan::plain("connection reset by peer")],
decoration: Decoration::Error,
timestamp: Some("12:00:06".into()),
},
TextDisplayLine {
spans: vec![StyledSpan::plain("retry succeeded")],
decoration: Decoration::Muted,
timestamp: Some("12:00:07".into()),
},
]
}
fn wrap_lines() -> Vec<TextDisplayLine> {
vec![
TextDisplayLine {
spans: vec![StyledSpan::plain(long_line_text())],
decoration: Decoration::Normal,
timestamp: None,
},
TextDisplayLine {
spans: vec![StyledSpan::plain("A short line follows.")],
decoration: Decoration::Normal,
timestamp: None,
},
]
}
pub struct TextDisplayDemo {
scroll_offset: usize,
auto_scroll: bool,
}
impl TextDisplayDemo {
pub fn new() -> Self {
Self {
scroll_offset: 0,
auto_scroll: true,
}
}
fn build(&self, variant: usize) -> TextDisplay {
let lines = if variant == 1 {
wrap_lines()
} else {
log_lines()
};
let mut display = TextDisplay::new(WidgetId::new("gallery:text-display"));
display.lines = lines;
display.scroll_offset = self.scroll_offset;
display.auto_scroll = self.auto_scroll;
display.show_scrollbar = true;
display
}
}
impl Default for TextDisplayDemo {
fn default() -> Self {
Self::new()
}
}
impl Demo for TextDisplayDemo {
fn name(&self) -> &'static str {
"Text Display"
}
fn group(&self) -> &'static str {
"Content"
}
fn variants(&self) -> &'static [&'static str] {
&["Log tail (decorations)", "Word-wrap (long line)"]
}
fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
let lh = backend.line_height();
let body = Rect::new(area.x, area.y, area.width, (area.height - lh).max(0.0));
let display = self.build(variant);
backend.draw_text_display(body, &display);
let status_rect = Rect::new(area.x, area.y + body.height, area.width, lh);
let text = format!(
" auto_scroll: {} scroll_offset: {} (k/j or \u{2191}/\u{2193} scroll, G return to live) ",
self.auto_scroll, self.scroll_offset
);
let bar = StatusBar {
id: WidgetId::new("gallery:text-display:status"),
left_segments: vec![StatusBarSegment {
text,
fg: Color::rgb(255, 255, 255),
bg: Color::rgb(40, 80, 120),
bold: false,
action_id: None,
}],
right_segments: vec![],
};
let _ = backend.draw_status_bar_interactive(status_rect, &bar, &InteractionState::new());
}
fn handle(
&mut self,
_variant: usize,
event: &UiEvent,
_backend: &mut dyn Backend,
_area: Rect,
) -> Reaction {
match event {
UiEvent::KeyPressed { key, .. } => match key {
Key::Char('j') | Key::Named(NamedKey::Down) => {
self.scroll_offset = self.scroll_offset.saturating_add(1);
self.auto_scroll = false;
Reaction::Redraw
}
Key::Char('k') | Key::Named(NamedKey::Up) => {
self.scroll_offset = self.scroll_offset.saturating_sub(1);
self.auto_scroll = false;
Reaction::Redraw
}
Key::Char('G') | Key::Named(NamedKey::End) => {
self.auto_scroll = true;
Reaction::Redraw
}
_ => Reaction::Continue,
},
_ => Reaction::Continue,
}
}
fn source(&self) -> &'static str {
extract_region(SOURCE)
}
fn data(&self, variant: usize) -> serde_json::Value {
serde_json::json!({
"variant": variant,
"scroll_offset": self.scroll_offset,
"auto_scroll": self.auto_scroll,
"line_count": self.build(variant).lines.len(),
})
}
}
Backends
| Variant | TUI | GTK | macOS | Windows |
|---|---|---|---|---|
| Log tail (decorations) | ||||
| Word-wrap (long line) |