use crate::core::geometry::Rect;
use crate::core::event::Event;
use crate::core::state::StateFlags;
use crate::core::history::HistoryManager;
use crate::terminal::Terminal;
use super::view::View;
use super::list_viewer::{ListViewer, ListViewerState};
pub struct HistoryViewer {
bounds: Rect,
history_id: u16,
items: Vec<String>,
list_state: ListViewerState,
state: StateFlags,
palette_chain: Option<crate::core::palette_chain::PaletteChainNode>,
}
impl HistoryViewer {
pub fn new(bounds: Rect, history_id: u16) -> Self {
let items = HistoryManager::get_list(history_id);
let mut list_state = ListViewerState::new();
list_state.set_range(items.len());
Self {
bounds,
history_id,
items,
list_state,
state: 0,
palette_chain: None,
}
}
pub fn refresh(&mut self) {
self.items = HistoryManager::get_list(self.history_id);
self.list_state.set_range(self.items.len());
}
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 item_count(&self) -> usize {
self.items.len()
}
}
impl View for HistoryViewer {
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, bounds: Rect) {
self.bounds = bounds;
}
fn draw(&mut self, terminal: &mut Terminal) {
use crate::core::draw::DrawBuffer;
use super::view::write_line_to_terminal;
let width = self.bounds.width_clamped() as usize;
let height = self.bounds.height_clamped() as usize;
use crate::core::palette::colors::{LISTBOX_FOCUSED, LISTBOX_NORMAL, LISTBOX_SELECTED_FOCUSED, LISTBOX_SELECTED};
let color_normal = if self.is_focused() {
LISTBOX_FOCUSED
} else {
LISTBOX_NORMAL
};
let color_selected = if self.is_focused() {
LISTBOX_SELECTED_FOCUSED
} else {
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) {
self.handle_list_event(event);
}
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) {
if index < self.items.len() {
let visible_rows = self.bounds.height_clamped() as usize;
self.list_state.focus_item(index, visible_rows);
}
}
fn get_list_selection(&self) -> usize {
self.list_state.focused.unwrap_or(0)
}
fn set_palette_chain(&mut self, node: Option<crate::core::palette_chain::PaletteChainNode>) {
self.palette_chain = node;
}
fn get_palette_chain(&self) -> Option<&crate::core::palette_chain::PaletteChainNode> {
self.palette_chain.as_ref()
}
fn get_palette(&self) -> Option<crate::core::palette::Palette> {
use crate::core::palette::{palettes, Palette};
Some(Palette::from_slice(palettes::CP_HISTORY_VIEWER))
}
}
impl ListViewer for HistoryViewer {
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_history_viewer_creation() {
HistoryManager::clear_all();
HistoryManager::add(1, "test1".to_string());
HistoryManager::add(1, "test2".to_string());
let viewer = HistoryViewer::new(Rect::new(0, 0, 20, 10), 1);
assert_eq!(viewer.item_count(), 2);
assert_eq!(viewer.get_selected_item(), Some("test2")); }
#[test]
fn test_history_viewer_refresh() {
HistoryManager::clear_all();
HistoryManager::add(2, "item1".to_string());
let mut viewer = HistoryViewer::new(Rect::new(0, 0, 20, 10), 2);
assert_eq!(viewer.item_count(), 1);
HistoryManager::add(2, "item2".to_string());
HistoryManager::add(2, "item3".to_string());
viewer.refresh();
assert_eq!(viewer.item_count(), 3);
}
#[test]
fn test_history_viewer_empty() {
HistoryManager::clear_all();
let viewer = HistoryViewer::new(Rect::new(0, 0, 20, 10), 99);
assert_eq!(viewer.item_count(), 0);
assert_eq!(viewer.get_selected_item(), None);
}
}
pub struct HistoryViewerBuilder {
bounds: Option<Rect>,
history_id: Option<u16>,
}
impl HistoryViewerBuilder {
pub fn new() -> Self {
Self { bounds: None, history_id: None }
}
#[must_use]
pub fn bounds(mut self, bounds: Rect) -> Self {
self.bounds = Some(bounds);
self
}
#[must_use]
pub fn history_id(mut self, history_id: u16) -> Self {
self.history_id = Some(history_id);
self
}
pub fn build(self) -> HistoryViewer {
let bounds = self.bounds.expect("HistoryViewer bounds must be set");
let history_id = self.history_id.expect("HistoryViewer history_id must be set");
HistoryViewer::new(bounds, history_id)
}
pub fn build_boxed(self) -> Box<HistoryViewer> {
Box::new(self.build())
}
}
impl Default for HistoryViewerBuilder {
fn default() -> Self {
Self::new()
}
}