use crate::core::geometry::Rect;
use crate::core::event::{Event, EventType, KB_ENTER, MB_LEFT_BUTTON};
use crate::core::palette::colors;
use crate::core::draw::DrawBuffer;
use crate::core::state::StateFlags;
use crate::terminal::Terminal;
use crate::core::command::CommandId;
use super::view::{View, write_line_to_terminal};
use super::list_viewer::{ListViewer, ListViewerState};
pub struct ListBox {
bounds: Rect,
items: Vec<String>,
list_state: ListViewerState, state: StateFlags,
on_select_command: CommandId,
}
impl ListBox {
pub fn new(bounds: Rect, on_select_command: CommandId) -> Self {
Self {
bounds,
items: Vec::new(),
list_state: ListViewerState::new(),
state: 0,
on_select_command,
}
}
pub fn set_items(&mut self, items: Vec<String>) {
self.items = items;
self.list_state.set_range(self.items.len());
}
pub fn add_item(&mut self, item: String) {
self.items.push(item);
self.list_state.set_range(self.items.len());
}
pub fn clear(&mut self) {
self.items.clear();
self.list_state.set_range(0);
}
pub fn get_selection(&self) -> Option<usize> {
self.list_state.focused
}
pub fn get_selected_item(&self) -> Option<&str> {
self.list_state.focused.and_then(|idx| self.items.get(idx).map(|s| s.as_str()))
}
pub fn set_selection(&mut self, index: usize) {
if index < self.items.len() {
let visible_rows = self.bounds.height() as usize;
self.list_state.focus_item(index, visible_rows);
}
}
pub fn item_count(&self) -> usize {
self.items.len()
}
pub fn select_prev(&mut self) {
let visible_rows = self.bounds.height() as usize;
self.list_state.focus_prev(visible_rows);
}
pub fn select_next(&mut self) {
let visible_rows = self.bounds.height() as usize;
self.list_state.focus_next(visible_rows);
}
pub fn select_first(&mut self) {
let visible_rows = self.bounds.height() as usize;
self.list_state.focus_first(visible_rows);
}
pub fn select_last(&mut self) {
let visible_rows = self.bounds.height() as usize;
self.list_state.focus_last(visible_rows);
}
pub fn page_up(&mut self) {
let visible_rows = self.bounds.height() as usize;
self.list_state.focus_page_up(visible_rows);
}
pub fn page_down(&mut self) {
let visible_rows = self.bounds.height() as usize;
self.list_state.focus_page_down(visible_rows);
}
}
impl View for ListBox {
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, bounds: Rect) {
self.bounds = bounds;
}
fn draw(&mut self, terminal: &mut Terminal) {
let width = self.bounds.width() as usize;
let height = self.bounds.height() as usize;
let color_normal = if self.is_focused() {
colors::LISTBOX_FOCUSED
} else {
colors::LISTBOX_NORMAL
};
let color_selected = if self.is_focused() {
colors::LISTBOX_SELECTED_FOCUSED
} else {
colors::LISTBOX_SELECTED
};
for i in 0..height {
let mut buf = DrawBuffer::new(width);
let item_idx = self.list_state.top_item + i;
if item_idx < self.items.len() {
let is_selected = Some(item_idx) == self.list_state.focused;
let color = if is_selected { color_selected } else { color_normal };
let text = &self.items[item_idx];
buf.move_str(0, text, color);
let text_len = text.len();
if text_len < width {
buf.move_char(text_len, ' ', color, width - text_len);
}
} else {
buf.move_char(0, ' ', color_normal, width);
}
write_line_to_terminal(terminal, self.bounds.a.x, self.bounds.a.y + i as i16, &buf);
}
}
fn handle_event(&mut self, event: &mut Event) {
if self.handle_list_event(event) {
return;
}
match event.what {
EventType::Keyboard => {
if event.key_code == KB_ENTER {
*event = Event::command(self.on_select_command);
}
}
EventType::MouseDown => {
let mouse_pos = event.mouse.pos;
if self.bounds.contains(mouse_pos) && event.mouse.buttons & MB_LEFT_BUTTON != 0 {
if event.mouse.double_click {
*event = Event::command(self.on_select_command);
}
}
}
EventType::MouseWheelUp => {
let mouse_pos = event.mouse.pos;
if self.bounds.contains(mouse_pos) {
self.select_prev();
event.clear();
}
}
EventType::MouseWheelDown => {
let mouse_pos = event.mouse.pos;
if self.bounds.contains(mouse_pos) {
self.select_next();
event.clear();
}
}
_ => {}
}
}
fn can_focus(&self) -> bool {
true
}
fn state(&self) -> StateFlags {
self.state
}
fn set_state(&mut self, state: StateFlags) {
self.state = state;
}
fn set_list_selection(&mut self, index: usize) {
self.set_selection(index);
}
fn get_list_selection(&self) -> usize {
self.list_state.focused.unwrap_or(0)
}
}
impl ListViewer for ListBox {
fn list_state(&self) -> &ListViewerState {
&self.list_state
}
fn list_state_mut(&mut self) -> &mut ListViewerState {
&mut self.list_state
}
fn get_text(&self, item: usize, _max_len: usize) -> String {
self.items.get(item).cloned().unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_listbox_creation() {
let listbox = ListBox::new(Rect::new(0, 0, 20, 10), 1000);
assert_eq!(listbox.item_count(), 0);
assert_eq!(listbox.get_selection(), None);
}
#[test]
fn test_listbox_add_items() {
let mut listbox = ListBox::new(Rect::new(0, 0, 20, 10), 1000);
listbox.add_item("Item 1".to_string());
listbox.add_item("Item 2".to_string());
listbox.add_item("Item 3".to_string());
assert_eq!(listbox.item_count(), 3);
assert_eq!(listbox.get_selection(), Some(0));
assert_eq!(listbox.get_selected_item(), Some("Item 1"));
}
#[test]
fn test_listbox_set_items() {
let mut listbox = ListBox::new(Rect::new(0, 0, 20, 10), 1000);
let items = vec![
"Alpha".to_string(),
"Beta".to_string(),
"Gamma".to_string(),
];
listbox.set_items(items);
assert_eq!(listbox.item_count(), 3);
assert_eq!(listbox.get_selection(), Some(0));
}
#[test]
fn test_listbox_navigation() {
let mut listbox = ListBox::new(Rect::new(0, 0, 20, 10), 1000);
listbox.set_items(vec![
"Item 1".to_string(),
"Item 2".to_string(),
"Item 3".to_string(),
]);
assert_eq!(listbox.get_selection(), Some(0));
listbox.select_next();
assert_eq!(listbox.get_selection(), Some(1));
listbox.select_next();
assert_eq!(listbox.get_selection(), Some(2));
listbox.select_next(); assert_eq!(listbox.get_selection(), Some(2));
listbox.select_prev();
assert_eq!(listbox.get_selection(), Some(1));
listbox.select_first();
assert_eq!(listbox.get_selection(), Some(0));
listbox.select_last();
assert_eq!(listbox.get_selection(), Some(2));
}
#[test]
fn test_listbox_set_selection() {
let mut listbox = ListBox::new(Rect::new(0, 0, 20, 10), 1000);
listbox.set_items(vec![
"A".to_string(),
"B".to_string(),
"C".to_string(),
"D".to_string(),
]);
listbox.set_selection(2);
assert_eq!(listbox.get_selection(), Some(2));
assert_eq!(listbox.get_selected_item(), Some("C"));
listbox.set_selection(10); assert_eq!(listbox.get_selection(), Some(2)); }
#[test]
fn test_listbox_clear() {
let mut listbox = ListBox::new(Rect::new(0, 0, 20, 10), 1000);
listbox.set_items(vec!["Item 1".to_string(), "Item 2".to_string()]);
assert_eq!(listbox.item_count(), 2);
listbox.clear();
assert_eq!(listbox.item_count(), 0);
assert_eq!(listbox.get_selection(), None);
}
}