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
use crate::components::renderable_content::{
ContentEvent, EventResult, EventType, HoverState, RenderableContent, SensitiveZone,
};
use crate::model::choice::Choice;
use crate::Bounds;
/// ChoiceContent implementation of RenderableContent trait
/// FIXES the broken choice rendering that renders outside bounds and doesn't trigger horizontal scrollbars
/// PRESERVES the working wrap mode functionality
pub struct ChoiceContent<'a> {
/// The choices to render
choices: &'a [Choice],
/// Menu foreground color
_menu_fg_color: &'a Option<String>,
/// Menu background color
_menu_bg_color: &'a Option<String>,
/// Selected choice foreground color
_selected_menu_fg_color: &'a Option<String>,
/// Selected choice background color
_selected_menu_bg_color: &'a Option<String>,
/// Highlighted choice foreground color (for hover state)
_highlighted_menu_fg_color: &'a Option<String>,
/// Highlighted choice background color (for hover state)
_highlighted_menu_bg_color: &'a Option<String>,
}
impl<'a> ChoiceContent<'a> {
/// Create new ChoiceContent
pub fn new(
choices: &'a [Choice],
menu_fg_color: &'a Option<String>,
menu_bg_color: &'a Option<String>,
selected_menu_fg_color: &'a Option<String>,
selected_menu_bg_color: &'a Option<String>,
highlighted_menu_fg_color: &'a Option<String>,
highlighted_menu_bg_color: &'a Option<String>,
) -> Self {
Self {
choices,
_menu_fg_color: menu_fg_color,
_menu_bg_color: menu_bg_color,
_selected_menu_fg_color: selected_menu_fg_color,
_selected_menu_bg_color: selected_menu_bg_color,
_highlighted_menu_fg_color: highlighted_menu_fg_color,
_highlighted_menu_bg_color: highlighted_menu_bg_color,
}
}
/// Format choice content (from choice_renderer.rs)
fn format_choice_content(&self, choice: &Choice) -> String {
if let Some(content) = &choice.content {
if choice.waiting {
format!("{}...", content)
} else {
content.clone()
}
} else {
String::new()
}
}
/// Calculate maximum choice width for horizontal scrolling
fn get_max_choice_width(&self) -> usize {
self.choices
.iter()
.map(|choice| self.format_choice_content(choice).len())
.max()
.unwrap_or(0)
}
}
impl<'a> RenderableContent for ChoiceContent<'a> {
/// Get raw choice dimensions - maximum choice width and total choice count
fn get_dimensions(&self) -> (usize, usize) {
let max_width = self.get_max_choice_width();
let height = self.choices.len();
(max_width, height)
}
/// Get raw content string for choices
fn get_raw_content(&self) -> String {
self.choices
.iter()
.map(|choice| choice.id.clone())
.collect::<Vec<_>>()
.join("\n")
}
/// Get box-relative sensitive zones for choices - raw row/col positions
fn get_box_relative_sensitive_zones(&self) -> Vec<SensitiveZone> {
let mut zones = Vec::new();
for (idx, choice) in self.choices.iter().enumerate() {
zones.push(SensitiveZone {
bounds: Bounds::new(0, idx, choice.id.len(), 1), // Raw content: col 0, row idx, width=choice length, height=1
content_id: format!("choice_{}", idx),
content_type: crate::components::renderable_content::ContentType::Choice,
metadata: Default::default(),
});
}
zones
}
/// Handle content events on choices (click, hover, keypress, etc.)
/// Note: Choice mutation happens at MuxBox level, this validates and processes events
fn handle_event(&mut self, event: &ContentEvent) -> EventResult {
match event.event_type {
EventType::Click => {
if let Some(zone_id) = &event.zone_id {
if let Some(idx_str) = zone_id.strip_prefix("choice_") {
if let Ok(choice_idx) = idx_str.parse::<usize>() {
if choice_idx < self.choices.len() {
// Valid choice click - actual mutation happens at MuxBox level
return EventResult::Handled;
}
}
}
}
EventResult::NotHandled
}
EventType::Hover => {
// Handle hover events for choice highlighting
if let Some(hover_info) = event.hover_info() {
match hover_info.state {
HoverState::Enter => {
// Choice gained hover - could trigger visual feedback
EventResult::HandledContinue // Allow tooltips
}
HoverState::Leave => {
// Choice lost hover - remove visual feedback
EventResult::HandledContinue
}
HoverState::Move => {
// Mouse moving within choice - always continue to allow for tooltip handling
EventResult::HandledContinue
}
}
} else {
// Basic hover event (no hover info) - still handled for choice highlighting
EventResult::HandledContinue
}
}
EventType::MouseMove => {
// Handle mouse movement over choices
if let Some(mouse_move) = event.mouse_move_info() {
if mouse_move.is_dragging {
// Dragging over choices - could be selection
EventResult::NotHandled // Let higher level handle drag selection
} else {
// Normal mouse movement - update hover state
EventResult::HandledContinue
}
} else {
EventResult::NotHandled
}
}
EventType::KeyPress => {
// Handle keyboard navigation within choices
if let Some(key_info) = event.key_info() {
match key_info.key.as_str() {
"ArrowUp" | "ArrowDown" | "Enter" | "Space" => {
EventResult::Handled // Navigation keys are handled
}
_ => EventResult::NotHandled,
}
} else {
EventResult::NotHandled
}
}
EventType::Focus => {
// Choice gained focus
EventResult::StateChanged
}
EventType::Blur => {
// Choice lost focus
EventResult::StateChanged
}
_ => EventResult::NotHandled,
}
}
}