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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// (C) 2025 - Enzo Lombardi
//! StatusLine view - bottom status bar with keyboard shortcuts and context help.
use crate::core::geometry::Rect;
use crate::core::event::{Event, EventType, KeyCode, MB_LEFT_BUTTON};
use crate::core::draw::DrawBuffer;
use crate::core::palette::colors;
use crate::core::command::CommandId;
use crate::terminal::Terminal;
use super::view::{View, write_line_to_terminal};
pub struct StatusItem {
pub text: String,
pub key_code: KeyCode,
pub command: CommandId,
}
impl StatusItem {
pub fn new(text: &str, key_code: KeyCode, command: CommandId) -> Self {
Self {
text: text.to_string(),
key_code,
command,
}
}
}
pub struct StatusLine {
bounds: Rect,
items: Vec<StatusItem>,
item_positions: Vec<(i16, i16)>, // (start_x, end_x) for each item
selected_item: Option<usize>, // Currently hovered/selected item
hint_text: Option<String>, // Context-sensitive help text
options: u16,
}
impl StatusLine {
pub fn new(bounds: Rect, items: Vec<StatusItem>) -> Self {
use crate::core::state::OF_PRE_PROCESS;
Self {
bounds,
items,
item_positions: Vec::new(),
selected_item: None,
hint_text: None,
options: OF_PRE_PROCESS, // Status line processes in pre-process phase (matches Borland)
}
}
/// Set the hint text to display on the right side of the status line
pub fn set_hint(&mut self, hint: Option<String>) {
self.hint_text = hint;
}
/// Draw the status line with optional selected item highlighting
fn draw_select(&mut self, terminal: &mut Terminal, selected: Option<usize>) {
let width = self.bounds.width() as usize;
let mut buf = DrawBuffer::new(width);
buf.move_char(0, ' ', colors::STATUS_NORMAL, width);
// Clear previous item positions
self.item_positions.clear();
let mut x = 0; // Start at position 0 (Borland starts at i=0)
for (idx, item) in self.items.iter().enumerate() {
if x + item.text.len() + 4 < width { // Need space for: space + text + space + separator
// Hit area starts at the leading space (matches Borland tstatusl.cc:204)
let start_x = x as i16;
// Determine color based on selection
let is_selected = selected == Some(idx);
let normal_color = if is_selected {
colors::STATUS_SELECTED
} else {
colors::STATUS_NORMAL
};
let shortcut_color = if is_selected {
colors::STATUS_SELECTED_SHORTCUT
} else {
colors::STATUS_SHORTCUT
};
// Draw leading space (Borland: b.moveChar(i, ' ', color, 1))
buf.put_char(x, ' ', normal_color);
x += 1;
// Parse ~X~ for highlighting - everything between tildes is highlighted
let mut chars = item.text.chars();
while let Some(ch) = chars.next() {
if ch == '~' {
// Read all characters until closing ~ in highlight color
while let Some(shortcut_ch) = chars.next() {
if shortcut_ch == '~' {
break; // Found closing tilde
}
buf.put_char(x, shortcut_ch, shortcut_color);
x += 1;
}
} else {
buf.put_char(x, ch, normal_color);
x += 1;
}
}
// Draw trailing space (Borland: b.moveChar(i+l+1, ' ', color, 1))
buf.put_char(x, ' ', normal_color);
x += 1;
// Hit area ends after the trailing space (matches Borland inc=2 spacing)
let end_x = x as i16;
self.item_positions.push((start_x, end_x));
// Separator is always drawn in normal color, never highlighted
buf.move_str(x, "│ ", colors::STATUS_NORMAL);
x += 2;
}
}
// Display hint text if available and there's space
if let Some(ref hint) = self.hint_text {
if x + hint.len() + 2 < width {
buf.move_str(x, "- ", colors::STATUS_NORMAL);
x += 2;
let hint_len = (width - x).min(hint.len());
for (i, ch) in hint.chars().take(hint_len).enumerate() {
buf.put_char(x + i, ch, colors::STATUS_NORMAL);
}
}
}
write_line_to_terminal(terminal, self.bounds.a.x, self.bounds.a.y, &buf);
}
/// Find which item the mouse is currently over
fn item_mouse_is_in(&self, mouse_x: i16) -> Option<usize> {
for (i, &(start_x, end_x)) in self.item_positions.iter().enumerate() {
if i < self.items.len() {
let absolute_start = self.bounds.a.x + start_x;
let absolute_end = self.bounds.a.x + end_x;
if mouse_x >= absolute_start && mouse_x < absolute_end {
return Some(i);
}
}
}
None
}
}
impl View for StatusLine {
fn bounds(&self) -> Rect {
self.bounds
}
fn set_bounds(&mut self, bounds: Rect) {
self.bounds = bounds;
}
fn draw(&mut self, terminal: &mut Terminal) {
// Draw with current selection (if any)
self.draw_select(terminal, self.selected_item);
}
fn handle_event(&mut self, event: &mut Event) {
// Handle mouse clicks on status items with tracking (like Borland)
if event.what == EventType::MouseDown {
let mouse_pos = event.mouse.pos;
if event.mouse.buttons & MB_LEFT_BUTTON != 0 && mouse_pos.y == self.bounds.a.y {
// Track mouse movement while button is held down
// Initial selection
let selected_item = self.item_mouse_is_in(mouse_pos.x);
if selected_item.is_some() {
self.selected_item = selected_item;
// Note: In full implementation, we'd redraw here with selection
// For now, we'll skip the redraw to avoid terminal borrow issues
}
// Clear the event since we're handling it
event.clear();
// If an item was selected, generate command
if let Some(idx) = selected_item {
if idx < self.items.len() {
let item = &self.items[idx];
if item.command != 0 {
*event = Event::command(item.command);
}
}
}
// Reset selection
self.selected_item = None;
return;
}
}
// Handle mouse move to show hover effect
if event.what == EventType::MouseMove {
let mouse_pos = event.mouse.pos;
if mouse_pos.y == self.bounds.a.y {
let hovered_item = self.item_mouse_is_in(mouse_pos.x);
if hovered_item != self.selected_item {
self.selected_item = hovered_item;
// Note: Ideally we'd redraw here to show hover effect
// But without access to terminal in handle_event, we defer to next draw cycle
}
} else if self.selected_item.is_some() {
self.selected_item = None;
}
}
// Handle keyboard shortcuts
if event.what == EventType::Keyboard {
for item in &self.items {
if event.key_code == item.key_code {
*event = Event::command(item.command);
return;
}
}
}
}
fn options(&self) -> u16 {
self.options
}
fn set_options(&mut self, options: u16) {
self.options = options;
}
}