1use iced_core::{
2 Padding, Rectangle, Shell, Size,
3 layout::{Layout, Node},
4 mouse, renderer,
5 widget::Tree,
6};
7
8use super::menu_bar::{GlobalState, MenuBarTask};
9use super::menu_tree::{Item, MenuState};
10use crate::style::menu_bar::Catalog;
11
12#[derive(Debug, Clone, Copy)]
34pub enum DrawPath {
35 FakeHovering,
37 Backdrop,
39}
40
41#[derive(Debug, Clone, Copy)]
43pub(super) enum Direction {
44 Positive,
45 Negative,
46}
47impl Direction {
48 pub(super) fn flip(self) -> Self {
49 match self {
50 Self::Positive => Self::Negative,
51 Self::Negative => Self::Positive,
52 }
53 }
54}
55
56#[allow(missing_docs)]
58#[derive(Debug, Clone, Copy)]
59pub(super) enum Axis {
60 Horizontal,
61 Vertical,
62}
63
64pub(super) type Index = Option<usize>;
65
66#[derive(Debug, Clone, Copy)]
86pub(super) enum RecEvent {
87 Event,
88 Close,
89 None,
90}
91
92#[derive(Debug, Clone, Copy)]
93pub struct ScrollSpeed {
95 pub line: f32,
97 pub pixel: f32,
99}
100
101pub fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
102 Rectangle {
103 x: rect.x - padding.left,
104 y: rect.y - padding.top,
105 width: rect.width + padding.x(),
106 height: rect.height + padding.y(),
107 }
108}
109
110#[derive(Debug, Clone, Copy)]
111pub(super) struct MenuSlice {
112 pub(super) start_index: usize,
113 pub(super) end_index: usize,
114 pub(super) lower_bound_rel: f32,
115 pub(super) upper_bound_rel: f32,
116}
117impl MenuSlice {
118 pub(super) fn from_bounds_rel(
119 lower_bound_rel: f32,
120 upper_bound_rel: f32,
121 items_node: &Node,
122 get_position: fn(&Node) -> f32,
123 ) -> Self {
124 let max_index = items_node.children().len().saturating_sub(1);
125 let nodes = items_node.children();
126 let start_index = search_bound(0, max_index, lower_bound_rel, nodes, get_position);
127 let end_index = search_bound(start_index, max_index, upper_bound_rel, nodes, get_position);
128
129 Self {
130 start_index,
131 end_index,
132 lower_bound_rel,
133 upper_bound_rel,
134 }
135 }
136}
137
138pub(super) fn search_bound(
154 default_left: usize,
155 default_right: usize,
156 bound: f32,
157 list: &[Node],
158 get_position: fn(&Node) -> f32,
159) -> usize {
160 let mut left = default_left;
162 let mut right = default_right;
163
164 while left != right {
165 let m = usize::midpoint(left, right) + 1;
166 if get_position(&list[m]) > bound {
167 right = m - 1;
168 } else {
169 left = m;
170 }
171 }
172 left
173}
174
175pub(super) fn clip_node_y(node: &Node, height: f32, offset: f32) -> Node {
176 let node_bounds = node.bounds();
177 Node::with_children(
178 Size::new(node_bounds.width, height),
179 node.children()
180 .iter()
181 .map(|n| n.clone().translate([0.0, -offset]))
182 .collect(),
183 )
184 .move_to(node_bounds.position())
185 .translate([0.0, offset])
186}
187
188pub(super) fn clip_node_x(node: &Node, width: f32, offset: f32) -> Node {
189 let node_bounds = node.bounds();
190 Node::with_children(
191 Size::new(width, node_bounds.height),
192 node.children()
193 .iter()
194 .map(|n| n.clone().translate([-offset, 0.0]))
195 .collect(),
196 )
197 .move_to(node_bounds.position())
198 .translate([offset, 0.0])
199}
200
201pub(super) struct GlobalParameters<'a, Theme: crate::style::menu_bar::Catalog> {
203 pub(super) safe_bounds_margin: f32,
204 pub(super) draw_path: DrawPath,
205 pub(super) scroll_speed: ScrollSpeed,
206 pub(super) close_on_item_click: bool,
207 pub(super) close_on_background_click: bool,
208 pub(super) class: Theme::Class<'a>,
209}
210
211pub(super) fn try_open_menu<'a, 'b, Message, Theme: Catalog, Renderer: renderer::Renderer>(
213 items: &mut [Item<'a, Message, Theme, Renderer>],
214 menu_state: &mut MenuState,
215 item_trees: &mut [Tree],
216 item_layouts: impl Iterator<Item = Layout<'b>>,
217 cursor: mouse::Cursor,
218 shell: &mut Shell<'_, Message>,
219) {
220 let old_active = menu_state.active;
221 let slice = menu_state.slice;
222
223 for (i, ((item, tree), layout)) in
224 itl_iter_slice_enum!(slice, items;iter_mut, item_trees;iter_mut, item_layouts)
225 {
226 if cursor.is_over(layout.bounds()) {
227 if item.menu.is_some() {
228 menu_state.open_new_menu(i, item, tree);
229 }
230 break;
231 }
232 }
233
234 if menu_state.active != old_active {
235 shell.invalidate_layout();
236 shell.request_redraw();
237 }
238}
239
240#[allow(clippy::too_many_arguments)]
245pub(super) fn schedule_close_on_click<
246 'a,
247 'b,
248 Message,
249 Theme: Catalog,
250 Renderer: renderer::Renderer,
251>(
252 global_state: &mut GlobalState,
253 global_parameters: &GlobalParameters<'_, Theme>,
254 slice: MenuSlice,
255 items: &mut [Item<'a, Message, Theme, Renderer>],
256 slice_layout: impl Iterator<Item = Layout<'b>>,
257 cursor: mouse::Cursor,
258 menu_coic: Option<bool>,
259 menu_cobc: Option<bool>,
260) {
261 global_state.clear_task();
262
263 let mut coc_handled = false;
264
265 for (item, layout) in items[slice.start_index..=slice.end_index] .iter_mut()
267 .zip(slice_layout)
268 {
269 if cursor.is_over(layout.bounds()) {
270 if let Some(coc) = item.close_on_click {
271 coc_handled = true;
272 if coc {
273 global_state.schedule(MenuBarTask::CloseOnClick);
274 }
275 }
276 for cocfb in [menu_coic, Some(global_parameters.close_on_item_click)] {
277 if let (false, Some(coc)) = (coc_handled, cocfb) {
278 coc_handled = true;
279 if coc {
280 global_state.schedule(MenuBarTask::CloseOnClick);
281 }
282 }
283 }
284 break;
285 }
286 }
287
288 for cocfb in [menu_cobc, Some(global_parameters.close_on_background_click)] {
289 if let (false, Some(coc)) = (coc_handled, cocfb) {
290 coc_handled = true;
291 if coc {
292 global_state.schedule(MenuBarTask::CloseOnClick);
293 }
294 }
295 }
296}
297
298macro_rules! itl_iter_slice {
299 ($slice:expr, $items:expr;$iter_0:ident, $item_trees:expr;$iter_1:ident, $slice_layout:expr) => {
300 $items[$slice.start_index..=$slice.end_index]
301 .$iter_0()
302 .zip($item_trees[$slice.start_index..=$slice.end_index].$iter_1())
303 .zip($slice_layout)
304 };
305}
306pub(super) use itl_iter_slice;
307
308macro_rules! itl_iter_slice_enum {
309 ($slice:expr, $items:expr;$iter_0:ident, $item_trees:expr;$iter_1:ident, $slice_layout:expr) => {
310 itl_iter_slice!($slice, $items;$iter_0, $item_trees;$iter_1, $slice_layout)
311 .enumerate()
312 .map(move |(i, ((item, tree), layout))| (i + $slice.start_index, ((item, tree), layout)))
313 };
314}
315pub(super) use itl_iter_slice_enum;