Skip to main content

boxmux_lib/components/
choice_content.rs

1use crate::components::renderable_content::{
2    ContentEvent, EventResult, EventType, HoverState, RenderableContent, SensitiveZone,
3};
4use crate::model::choice::Choice;
5use crate::Bounds;
6
7/// ChoiceContent implementation of RenderableContent trait
8/// FIXES the broken choice rendering that renders outside bounds and doesn't trigger horizontal scrollbars
9/// PRESERVES the working wrap mode functionality
10pub struct ChoiceContent<'a> {
11    /// The choices to render
12    choices: &'a [Choice],
13    /// Menu foreground color
14    _menu_fg_color: &'a Option<String>,
15    /// Menu background color
16    _menu_bg_color: &'a Option<String>,
17    /// Selected choice foreground color
18    _selected_menu_fg_color: &'a Option<String>,
19    /// Selected choice background color
20    _selected_menu_bg_color: &'a Option<String>,
21    /// Highlighted choice foreground color (for hover state)
22    _highlighted_menu_fg_color: &'a Option<String>,
23    /// Highlighted choice background color (for hover state)
24    _highlighted_menu_bg_color: &'a Option<String>,
25}
26
27impl<'a> ChoiceContent<'a> {
28    /// Create new ChoiceContent
29    pub fn new(
30        choices: &'a [Choice],
31        menu_fg_color: &'a Option<String>,
32        menu_bg_color: &'a Option<String>,
33        selected_menu_fg_color: &'a Option<String>,
34        selected_menu_bg_color: &'a Option<String>,
35        highlighted_menu_fg_color: &'a Option<String>,
36        highlighted_menu_bg_color: &'a Option<String>,
37    ) -> Self {
38        Self {
39            choices,
40            _menu_fg_color: menu_fg_color,
41            _menu_bg_color: menu_bg_color,
42            _selected_menu_fg_color: selected_menu_fg_color,
43            _selected_menu_bg_color: selected_menu_bg_color,
44            _highlighted_menu_fg_color: highlighted_menu_fg_color,
45            _highlighted_menu_bg_color: highlighted_menu_bg_color,
46        }
47    }
48
49    /// Format choice content (from choice_renderer.rs)
50    fn format_choice_content(&self, choice: &Choice) -> String {
51        if let Some(content) = &choice.content {
52            if choice.waiting {
53                format!("{}...", content)
54            } else {
55                content.clone()
56            }
57        } else {
58            String::new()
59        }
60    }
61
62    /// Calculate maximum choice width for horizontal scrolling
63    fn get_max_choice_width(&self) -> usize {
64        self.choices
65            .iter()
66            .map(|choice| self.format_choice_content(choice).len())
67            .max()
68            .unwrap_or(0)
69    }
70}
71
72impl<'a> RenderableContent for ChoiceContent<'a> {
73    /// Get raw choice dimensions - maximum choice width and total choice count
74    fn get_dimensions(&self) -> (usize, usize) {
75        let max_width = self.get_max_choice_width();
76        let height = self.choices.len();
77        (max_width, height)
78    }
79
80    /// Get raw content string for choices
81    fn get_raw_content(&self) -> String {
82        self.choices
83            .iter()
84            .map(|choice| choice.id.clone())
85            .collect::<Vec<_>>()
86            .join("\n")
87    }
88
89    /// Get box-relative sensitive zones for choices - raw row/col positions
90    fn get_box_relative_sensitive_zones(&self) -> Vec<SensitiveZone> {
91        let mut zones = Vec::new();
92
93        for (idx, choice) in self.choices.iter().enumerate() {
94            zones.push(SensitiveZone {
95                bounds: Bounds::new(0, idx, choice.id.len(), 1), // Raw content: col 0, row idx, width=choice length, height=1
96                content_id: format!("choice_{}", idx),
97                content_type: crate::components::renderable_content::ContentType::Choice,
98                metadata: Default::default(),
99            });
100        }
101
102        zones
103    }
104
105    /// Handle content events on choices (click, hover, keypress, etc.)
106    /// Note: Choice mutation happens at MuxBox level, this validates and processes events
107    fn handle_event(&mut self, event: &ContentEvent) -> EventResult {
108        match event.event_type {
109            EventType::Click => {
110                if let Some(zone_id) = &event.zone_id {
111                    if let Some(idx_str) = zone_id.strip_prefix("choice_") {
112                        if let Ok(choice_idx) = idx_str.parse::<usize>() {
113                            if choice_idx < self.choices.len() {
114                                // Valid choice click - actual mutation happens at MuxBox level
115                                return EventResult::Handled;
116                            }
117                        }
118                    }
119                }
120                EventResult::NotHandled
121            }
122            EventType::Hover => {
123                // Handle hover events for choice highlighting
124                if let Some(hover_info) = event.hover_info() {
125                    match hover_info.state {
126                        HoverState::Enter => {
127                            // Choice gained hover - could trigger visual feedback
128                            EventResult::HandledContinue // Allow tooltips
129                        }
130                        HoverState::Leave => {
131                            // Choice lost hover - remove visual feedback
132                            EventResult::HandledContinue
133                        }
134                        HoverState::Move => {
135                            // Mouse moving within choice - always continue to allow for tooltip handling
136                            EventResult::HandledContinue
137                        }
138                    }
139                } else {
140                    // Basic hover event (no hover info) - still handled for choice highlighting
141                    EventResult::HandledContinue
142                }
143            }
144            EventType::MouseMove => {
145                // Handle mouse movement over choices
146                if let Some(mouse_move) = event.mouse_move_info() {
147                    if mouse_move.is_dragging {
148                        // Dragging over choices - could be selection
149                        EventResult::NotHandled // Let higher level handle drag selection
150                    } else {
151                        // Normal mouse movement - update hover state
152                        EventResult::HandledContinue
153                    }
154                } else {
155                    EventResult::NotHandled
156                }
157            }
158            EventType::KeyPress => {
159                // Handle keyboard navigation within choices
160                if let Some(key_info) = event.key_info() {
161                    match key_info.key.as_str() {
162                        "ArrowUp" | "ArrowDown" | "Enter" | "Space" => {
163                            EventResult::Handled // Navigation keys are handled
164                        }
165                        _ => EventResult::NotHandled,
166                    }
167                } else {
168                    EventResult::NotHandled
169                }
170            }
171            EventType::Focus => {
172                // Choice gained focus
173                EventResult::StateChanged
174            }
175            EventType::Blur => {
176                // Choice lost focus
177                EventResult::StateChanged
178            }
179            _ => EventResult::NotHandled,
180        }
181    }
182}