ratatui_toolkit/primitives/menu_bar/methods/
mod.rs1mod apply_theme;
2mod with_theme;
3
4use crate::primitives::menu_bar::functions::display_width;
5use crate::primitives::menu_bar::MenuBar;
6use ratatui::layout::{Constraint, Direction, Layout, Rect};
7use ratatui::widgets::{Block, BorderType, Borders, Paragraph};
8use ratatui::Frame;
9
10impl MenuBar {
11 pub fn update_hover(&mut self, column: u16, row: u16) {
12 for item in &mut self.items {
13 item.hovered = if let Some(area) = item.area {
14 column >= area.x
15 && column < area.x + area.width
16 && row >= area.y
17 && row < area.y + area.height
18 } else {
19 false
20 };
21 }
22 }
23
24 pub fn handle_click(&mut self, column: u16, row: u16) -> Option<usize> {
25 let clicked_index = self.items.iter().enumerate().find_map(|(i, item)| {
26 if let Some(area) = item.area {
27 if column >= area.x
28 && column < area.x + area.width
29 && row >= area.y
30 && row < area.y + area.height
31 {
32 return Some(i);
33 }
34 }
35 None
36 });
37
38 if let Some(clicked) = clicked_index {
39 for (i, item) in self.items.iter_mut().enumerate() {
40 item.selected = i == clicked;
41 }
42 }
43
44 clicked_index
45 }
46
47 pub fn selected(&self) -> Option<usize> {
48 self.items.iter().position(|item| item.selected)
49 }
50
51 pub fn render(&mut self, frame: &mut Frame, area: Rect) {
53 self.render_with_offset(frame, area, 0);
54 }
55
56 pub fn render_with_offset(&mut self, frame: &mut Frame, area: Rect, left_offset: u16) {
58 if self.items.is_empty() {
59 return;
60 }
61
62 let total_label_width: usize = self
65 .items
66 .iter()
67 .map(|item| display_width(&item.display_label()))
68 .sum();
69 let separators = (self.items.len() - 1) * 3; let needed_width = (total_label_width + separators + 4) as u16; let available_width = area.width.saturating_sub(left_offset);
74
75 let button_group_area = Rect {
77 x: area.x + left_offset,
78 y: area.y,
79 width: needed_width.min(available_width),
80 height: area.height,
81 };
82
83 self.area = Some(button_group_area);
84
85 let block = Block::default()
87 .borders(Borders::ALL)
88 .border_type(BorderType::Rounded);
89
90 let inner_area = block.inner(button_group_area);
91 frame.render_widget(block, button_group_area);
92
93 let mut x_offset = inner_area.x + 1;
95 let button_count = self.items.len();
96
97 for (i, item) in self.items.iter_mut().enumerate() {
98 let label = item.display_label();
100 let item_width = display_width(&label) as u16;
101
102 let available_width = (inner_area.x + inner_area.width).saturating_sub(x_offset);
104 if available_width == 0 {
105 break; }
107
108 let actual_item_width = item_width.min(available_width);
110
111 let item_area = Rect {
112 x: x_offset,
113 y: inner_area.y,
114 width: actual_item_width,
115 height: inner_area.height,
116 };
117
118 item.area = Some(item_area);
119
120 let style = match (item.selected, item.hovered) {
122 (true, true) => self.selected_hover_style,
123 (true, false) => self.selected_style,
124 (false, true) => self.hover_style,
125 (false, false) => self.normal_style,
126 };
127
128 let display_label = if actual_item_width < item_width {
131 label
133 .chars()
134 .take(actual_item_width as usize)
135 .collect::<String>()
136 } else {
137 label
138 };
139 let paragraph = Paragraph::new(display_label).style(style);
140 frame.render_widget(paragraph, item_area);
141
142 x_offset += actual_item_width;
143
144 if i < button_count - 1 && x_offset + 3 <= inner_area.x + inner_area.width {
147 let separator_area = Rect {
148 x: x_offset,
149 y: inner_area.y,
150 width: 3, height: inner_area.height,
152 };
153 let separator = Paragraph::new(" │ ");
154 frame.render_widget(separator, separator_area);
155 x_offset += 3;
156 }
157 }
158 }
159
160 pub fn render_centered(&mut self, frame: &mut Frame, area: Rect) {
162 let total_chars: usize = self
164 .items
165 .iter()
166 .map(|item| display_width(&item.display_label()) + 4)
167 .sum(); let needed_width = total_chars as u16;
169
170 let chunks = Layout::default()
172 .direction(Direction::Horizontal)
173 .constraints([
174 Constraint::Length((area.width.saturating_sub(needed_width)) / 2),
175 Constraint::Length(needed_width.min(area.width)),
176 Constraint::Min(0),
177 ])
178 .split(area);
179
180 self.render(frame, chunks[1]);
181 }
182}