1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// (C) 2025 - Enzo Lombardi
//! History view - dropdown button control for accessing input line history.
// History - Dropdown button for InputLine history
//
// Matches Borland: THistory (history dropdown button)
//
// A small button (shows '▼') attached to the right side of an InputLine.
// When clicked, displays a HistoryWindow with previous entries.
//
// Usage:
// let history = History::new(Point::new(x, y), history_id);
// // Position it to the right of the InputLine
use crate::core::geometry::{Point, Rect};
use crate::core::event::{Event, EventType, MB_LEFT_BUTTON};
use crate::core::palette::colors;
use crate::core::draw::DrawBuffer;
use crate::core::state::StateFlags;
use crate::core::history::HistoryManager;
use crate::terminal::Terminal;
use super::view::{View, write_line_to_terminal};
use super::history_window::HistoryWindow;
/// History - Dropdown button for accessing input history
///
/// Matches Borland: THistory
pub struct History {
bounds: Rect,
history_id: u16,
state: StateFlags,
pub selected_item: Option<String>, // Public so InputLine can read it
}
impl History {
/// Create a new history button
///
/// The button is 2 characters wide (shows '▼' or similar).
pub fn new(pos: Point, history_id: u16) -> Self {
Self {
bounds: Rect::new(pos.x, pos.y, pos.x + 2, pos.y + 1),
history_id,
state: 0,
selected_item: None,
}
}
/// Check if this history list has any items
pub fn has_items(&self) -> bool {
HistoryManager::has_history(self.history_id)
}
/// Show the history window and let user select an item
fn show_history(&mut self, terminal: &mut Terminal) {
if !self.has_items() {
return;
}
// Create history window slightly below and to the left of the button
let window_pos = Point::new(
(self.bounds.a.x - 20).max(0),
self.bounds.a.y + 1,
);
let mut window = HistoryWindow::new(window_pos, self.history_id, 30);
if let Some(selected) = window.execute(terminal) {
self.selected_item = Some(selected);
}
}
}
impl View for History {
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, bounds: Rect) {
self.bounds = bounds;
}
fn draw(&mut self, terminal: &mut Terminal) {
let mut buf = DrawBuffer::new(2);
// Draw down arrow: ▼ (or use 'v' for ASCII-only)
let arrow = if self.has_items() { "▼" } else { " " };
let color = if self.is_focused() {
colors::BUTTON_SELECTED
} else {
colors::BUTTON_NORMAL
};
buf.move_str(0, arrow, color);
write_line_to_terminal(terminal, self.bounds.a.x, self.bounds.a.y, &buf);
}
fn handle_event(&mut self, event: &mut Event) {
match event.what {
EventType::MouseDown => {
if self.bounds.contains(event.mouse.pos) && event.mouse.buttons & MB_LEFT_BUTTON != 0 {
// Button clicked - show history window
// We need terminal access, which will be handled by parent
event.clear();
}
}
_ => {}
}
}
fn can_focus(&self) -> bool {
false // History button doesn't take focus
}
fn state(&self) -> StateFlags {
self.state
}
fn set_state(&mut self, state: StateFlags) {
self.state = state;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_history_button_creation() {
HistoryManager::clear_all();
let button = History::new(Point::new(20, 5), 1);
assert!(!button.has_items());
assert_eq!(button.bounds.width(), 2);
}
#[test]
fn test_history_button_with_items() {
HistoryManager::clear_all();
HistoryManager::add(2, "test".to_string());
let button = History::new(Point::new(20, 5), 2);
assert!(button.has_items());
}
}