Message List
Group: Data
Code
pub struct MessageListDemo {
rows: Vec<Vec<MessageRow>>,
scroll_top: Vec<usize>,
last_click: Vec<Option<String>>,
}
impl MessageListDemo {
pub fn new() -> Self {
let plain = (0..30)
.map(|i| {
let fg = if i % 2 == 0 {
Color::rgb(220, 220, 220)
} else {
Color::rgb(140, 200, 255)
};
MessageRow::new(format!("row {i}: quadraui MessageList demo"), fg, 0.0)
})
.collect();
let log = (0..30)
.map(|i| {
let (fg, label) = match i % 3 {
0 => (Color::rgb(220, 220, 220), "INFO"),
1 => (Color::rgb(230, 190, 60), "WARN"),
_ => (Color::rgb(230, 90, 90), "ERROR"),
};
MessageRow::new(format!("[{label}] event #{i} processed"), fg, 0.0)
})
.collect();
Self {
rows: vec![plain, log],
scroll_top: vec![0, 0],
last_click: vec![None, None],
}
}
fn list_rect(area: Rect, backend: &dyn Backend) -> Rect {
let lh = backend.line_height();
Rect::new(area.x, area.y, area.width, (area.height - lh).max(0.0))
}
fn status_rect(area: Rect, backend: &dyn Backend) -> Rect {
let lh = backend.line_height();
Rect::new(area.x, area.y + area.height - lh, area.width, lh)
}
fn list(&self, variant: usize) -> MessageList {
MessageList {
id: WidgetId::new("gallery:message-list"),
rows: self.rows[variant].clone(),
scroll_top: self.scroll_top[variant],
}
}
fn status(&self, variant: usize) -> StatusBar {
let msg = match &self.last_click[variant] {
Some(m) => format!(" {m} "),
None => " click a row — j/k scroll ".into(),
};
StatusBar {
id: WidgetId::new("gallery:message-list:status"),
left_segments: vec![StatusBarSegment {
text: msg,
fg: Color::rgb(255, 255, 255),
bg: Color::rgb(40, 80, 120),
bold: false,
action_id: None,
}],
right_segments: vec![],
}
}
}
impl Default for MessageListDemo {
fn default() -> Self {
Self::new()
}
}
impl Demo for MessageListDemo {
fn name(&self) -> &'static str {
"Message List"
}
fn group(&self) -> &'static str {
"Data"
}
fn variants(&self) -> &'static [&'static str] {
&["Plain rows", "Severity log"]
}
fn render(&self, variant: usize, backend: &mut dyn Backend, area: Rect) {
let rect = Self::list_rect(area, backend);
backend.draw_message_list(rect, &self.list(variant));
let status_rect = Self::status_rect(area, backend);
let _ = backend.draw_status_bar_interactive(
status_rect,
&self.status(variant),
&InteractionState::new(),
);
}
fn handle(
&mut self,
variant: usize,
event: &UiEvent,
backend: &mut dyn Backend,
area: Rect,
) -> Reaction {
match event {
UiEvent::MouseDown {
button: MouseButton::Left,
position,
..
} => {
let rect = Self::list_rect(area, backend);
let measure = MessageListMeasure::from_metrics(&backend.measure());
let list = self.list(variant);
self.last_click[variant] =
Some(match list.hit_test(rect, measure, position.x, position.y) {
MessageListHit::Row(idx) => {
format!("clicked row {idx}: {}", self.rows[variant][idx].text)
}
MessageListHit::Empty => "clicked empty area".into(),
});
Reaction::Redraw
}
UiEvent::KeyPressed {
key: Key::Char('j'),
..
} => {
let max = self.rows[variant].len().saturating_sub(1);
self.scroll_top[variant] = (self.scroll_top[variant] + 1).min(max);
Reaction::Redraw
}
UiEvent::KeyPressed {
key: Key::Char('k'),
..
} => {
self.scroll_top[variant] = self.scroll_top[variant].saturating_sub(1);
Reaction::Redraw
}
_ => Reaction::Continue,
}
}
fn source(&self) -> &'static str {
extract_region(SOURCE)
}
fn data(&self, variant: usize) -> serde_json::Value {
serde_json::json!({
"row_count": self.rows[variant].len(),
"scroll_top": self.scroll_top[variant],
"last_click": self.last_click[variant],
})
}
fn caps_note(&self, _variant: usize, _caps: &BackendCaps) -> Option<String> {
None
}
}
Backends
| Variant | TUI | GTK | macOS | Windows |
|---|---|---|---|---|
| Plain rows | ||||
| Severity log |