1use super::{Menu, SubMenu, SubMenuBuilder};
9use kas::event::{FocusSource, TimerHandle};
10use kas::layout::{self, RowPositionSolver, RowSetter, RowSolver, RulesSetter, RulesSolver};
11use kas::prelude::*;
12use kas::theme::FrameStyle;
13
14const TIMER_SHOW: TimerHandle = TimerHandle::new(0, false);
15
16#[impl_self]
17mod MenuBar {
18 #[widget]
23 pub struct MenuBar<Data, D: Directional = kas::dir::Right> {
24 core: widget_core!(),
25 direction: D,
26 widgets: Vec<SubMenu<true, Data>>,
27 layout_store: layout::DynRowStorage,
28 delayed_open: Option<Id>,
29 }
30
31 impl Self
32 where
33 D: Default,
34 {
35 pub fn new(menus: Vec<SubMenu<true, Data>>) -> Self {
37 MenuBar::new_dir(menus, Default::default())
38 }
39
40 pub fn builder() -> MenuBuilder<Data, D> {
42 MenuBuilder {
43 menus: vec![],
44 direction: D::default(),
45 }
46 }
47 }
48 impl<Data> MenuBar<Data, kas::dir::Right> {
49 pub fn right(menus: Vec<SubMenu<true, Data>>) -> Self {
51 MenuBar::new(menus)
52 }
53 }
54
55 impl Self {
56 pub fn new_dir(menus: Vec<SubMenu<true, Data>>, direction: D) -> Self {
58 MenuBar {
59 core: Default::default(),
60 direction,
61 widgets: menus,
62 layout_store: Default::default(),
63 delayed_open: None,
64 }
65 }
66 }
67
68 impl Layout for Self {
69 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
70 let len = self.widgets.len();
74 let dim = (self.direction, len + 1);
75 let mut solver = RowSolver::new(axis, dim, &mut self.layout_store);
76 let frame_rules = sizer.frame(FrameStyle::MenuEntry, axis);
77 for (n, child) in self.widgets.iter_mut().enumerate() {
78 solver.for_child(&mut self.layout_store, n, |axis| {
79 let rules = child.size_rules(sizer.re(), axis);
80 frame_rules.surround(rules).0
81 });
82 }
83 solver.for_child(&mut self.layout_store, len, |axis| {
84 let mut rules = SizeRules::EMPTY;
85 if axis.is_horizontal() {
86 rules.set_stretch(Stretch::Maximize);
87 }
88 rules
89 });
90 solver.finish(&mut self.layout_store)
91 }
92
93 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, _: AlignHints) {
94 widget_set_rect!(rect);
95 let dim = (self.direction, self.widgets.len() + 1);
96 let mut setter = RowSetter::<D, Vec<i32>, _>::new(rect, dim, &mut self.layout_store);
97 let hints = AlignHints::CENTER;
98
99 for (n, child) in self.widgets.iter_mut().enumerate() {
100 child.set_rect(cx, setter.child_rect(&mut self.layout_store, n), hints);
101 }
102 }
103
104 fn draw(&self, mut draw: DrawCx) {
105 let solver = RowPositionSolver::new(self.direction);
106 let rect = self.rect();
107 solver.for_children(&self.widgets, rect, |w| w.draw(draw.re()));
108 }
109 }
110
111 impl Tile for Self {
112 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
113 Role::MenuBar
114 }
115
116 #[inline]
117 fn child_indices(&self) -> ChildIndices {
118 (0..self.widgets.len()).into()
119 }
120 fn get_child(&self, index: usize) -> Option<&dyn Tile> {
121 self.widgets.get(index).map(|w| w.as_tile())
122 }
123
124 fn probe(&self, coord: Coord) -> Id {
125 let solver = RowPositionSolver::new(self.direction);
126 solver
127 .find_child(&self.widgets, coord)
128 .and_then(|child| child.try_probe(coord))
129 .unwrap_or_else(|| self.id())
130 }
131 }
132
133 impl Events for Self {
134 fn handle_event(&mut self, cx: &mut EventCx, data: &Data, event: Event) -> IsUsed {
135 match event {
136 Event::Timer(TIMER_SHOW) => {
137 if let Some(id) = self.delayed_open.clone() {
138 self.set_menu_path(cx, data, Some(&id), false);
139 }
140 Used
141 }
142 Event::PressStart(press) => {
143 if press
144 .id
145 .as_ref()
146 .map(|id| self.is_ancestor_of(id))
147 .unwrap_or(false)
148 {
149 if press.is_primary() {
150 let any_menu_open = self.widgets.iter().any(|w| w.menu_is_open());
151 let press_in_the_bar = self.rect().contains(press.coord());
152
153 if !press_in_the_bar || !any_menu_open {
154 press.grab_move(self.id()).complete(cx);
155 }
156 cx.set_grab_depress(*press, press.id.clone());
157 if press_in_the_bar {
158 if self
159 .widgets
160 .iter()
161 .any(|w| w.eq_id(&press.id) && !w.menu_is_open())
162 {
163 self.set_menu_path(cx, data, press.id.as_ref(), false);
164 } else {
165 self.set_menu_path(cx, data, None, false);
166 }
167 }
168 }
169 Used
170 } else {
171 self.delayed_open = None;
174 self.set_menu_path(cx, data, None, false);
175 Unused
176 }
177 }
178 Event::CursorMove { press } | Event::PressMove { press, .. } => {
179 cx.set_grab_depress(*press, press.id.clone());
180
181 let id = match press.id {
182 Some(x) => x,
183 None => return Used,
184 };
185
186 if self.is_strict_ancestor_of(&id) {
187 if self.rect().contains(press.coord) {
190 cx.clear_nav_focus();
191 self.delayed_open = None;
192 self.set_menu_path(cx, data, Some(&id), false);
193 } else if id != self.delayed_open {
194 cx.request_nav_focus(id.clone(), FocusSource::Pointer);
195 let delay = cx.config().event().menu_delay();
196 cx.request_timer(self.id(), TIMER_SHOW, delay);
197 self.delayed_open = Some(id);
198 }
199 } else {
200 self.delayed_open = None;
201 }
202 Used
203 }
204 Event::PressEnd { press, success, .. } if success => {
205 let id = match press.id {
206 Some(x) => x,
207 None => return Used,
208 };
209
210 if !self.rect().contains(press.coord) {
211 self.delayed_open = None;
213 cx.send(id, Command::Activate);
214 }
215 Used
216 }
217 Event::Command(cmd, _) => {
218 use Command::{Left, Up};
221 let is_vert = self.direction.is_vertical();
222 let reverse = self.direction.is_reversed() ^ matches!(cmd, Left | Up);
223 match cmd.as_direction().map(|d| d.is_vertical()) {
224 Some(v) if v == is_vert => {
225 for i in 0..self.widgets.len() {
226 if self.widgets[i].menu_is_open() {
227 let mut j = isize::conv(i);
228 j = if reverse { j - 1 } else { j + 1 };
229 j = j.rem_euclid(self.widgets.len().cast());
230 self.widgets[i].set_menu_path(cx, data, None, true);
231 let w = &mut self.widgets[usize::conv(j)];
232 w.set_menu_path(cx, data, Some(&w.id()), true);
233 break;
234 }
235 }
236 Used
237 }
238 Some(_) => {
239 cx.next_nav_focus(self.id(), reverse, FocusSource::Key);
240 Used
241 }
242 None => Unused,
243 }
244 }
245 _ => Unused,
246 }
247 }
248 }
249
250 impl Widget for Self {
251 type Data = Data;
252
253 fn child_node<'n>(&'n mut self, data: &'n Data, index: usize) -> Option<Node<'n>> {
254 self.widgets.get_mut(index).map(|w| w.as_node(data))
255 }
256 }
257
258 impl Self {
259 fn set_menu_path(
260 &mut self,
261 cx: &mut EventCx,
262 data: &Data,
263 target: Option<&Id>,
264 set_focus: bool,
265 ) {
266 log::trace!(
267 "set_menu_path: self={}, target={target:?}, set_focus={set_focus}",
268 self.identify()
269 );
270 self.delayed_open = None;
271 for i in 0..self.widgets.len() {
272 self.widgets[i].set_menu_path(cx, data, target, set_focus);
273 }
274 }
275 }
276}
277
278pub struct MenuBuilder<Data, D: Directional> {
282 menus: Vec<SubMenu<true, Data>>,
283 direction: D,
284}
285
286impl<Data, D: Directional> MenuBuilder<Data, D> {
287 pub fn menu<F>(mut self, label: impl Into<AccessString>, f: F) -> Self
291 where
292 F: FnOnce(SubMenuBuilder<Data>),
293 {
294 let mut menu = Vec::new();
295 f(SubMenuBuilder { menu: &mut menu });
296 let dir = self.direction.as_direction().flipped();
297 self.menus.push(SubMenu::new(label, menu, dir));
298 self
299 }
300
301 pub fn build(self) -> MenuBar<Data, D> {
303 MenuBar::new_dir(self.menus, self.direction)
304 }
305}