use alloc::{string::String, vec::Vec};
use rlvgl_core::draw::draw_widget_bg;
use rlvgl_core::event::Event;
use rlvgl_core::font::{FontMetrics, WidgetFont, shape_text_ltr};
use rlvgl_core::renderer::Renderer;
use rlvgl_core::style::Style;
use rlvgl_core::widget::{Color, Rect, Widget};
pub struct List {
bounds: Rect,
pub style: Style,
pub text_color: Color,
items: Vec<String>,
selected: Option<usize>,
font: WidgetFont,
}
impl List {
pub fn new(bounds: Rect) -> Self {
Self {
bounds,
style: Style::default(),
text_color: Color(0, 0, 0, 255),
items: Vec::new(),
selected: None,
font: WidgetFont::new(),
}
}
pub fn add_item(&mut self, text: impl Into<String>) {
self.items.push(text.into());
}
pub fn set_items(&mut self, items: &[impl AsRef<str>]) {
self.items.clear();
self.items
.extend(items.iter().map(|item| String::from(item.as_ref())));
self.selected = None;
}
pub fn items(&self) -> &[String] {
&self.items
}
pub fn selected(&self) -> Option<usize> {
self.selected
}
pub fn set_font(&mut self, font: &'static dyn FontMetrics) {
self.font.set(font);
}
fn index_at(&self, y: i32) -> Option<usize> {
let row_height = 16;
if y < self.bounds.y || y >= self.bounds.y + self.bounds.height {
return None;
}
let idx = (y - self.bounds.y) / row_height;
if idx < 0 {
return None;
}
let idx = idx as usize;
if idx < self.items.len() {
Some(idx)
} else {
None
}
}
}
impl Widget for List {
fn bounds(&self) -> Rect {
self.bounds
}
fn widget_font_mut(&mut self) -> Option<&mut WidgetFont> {
Some(&mut self.font)
}
fn draw(&self, renderer: &mut dyn Renderer) {
let a = self.style.alpha;
draw_widget_bg(renderer, self.bounds, &self.style);
let font = self.font.resolve();
let row_height = 16;
for (i, item) in self.items.iter().enumerate() {
let y = self.bounds.y + (i as i32 * row_height);
let pos = (self.bounds.x + 2, y + row_height);
let color = if self.selected == Some(i) {
self.style.border_color
} else {
self.text_color
};
let shaped = shape_text_ltr(font, item, pos, 0);
renderer.draw_text_shaped(&shaped, (0, 0), color.with_alpha(a));
}
}
fn handle_event(&mut self, event: &Event) -> bool {
let Event::PressRelease { x, y } = event else {
return false;
};
if *x < self.bounds.x || *x >= self.bounds.x + self.bounds.width {
return false;
}
let Some(idx) = self.index_at(*y) else {
return false;
};
self.selected = Some(idx);
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_items_replaces_values_and_clears_selection() {
let mut list = List::new(Rect {
x: 0,
y: 0,
width: 100,
height: 48,
});
list.add_item("A");
list.add_item("B");
assert!(list.handle_event(&Event::PressRelease { x: 5, y: 20 }));
assert_eq!(list.selected(), Some(1));
list.set_items(&["C"]);
assert_eq!(list.items(), &["C"]);
assert_eq!(list.selected(), None);
}
}