diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 79b216a0..46dc1b88 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -8,7 +8,7 @@ use crate::widget::menu::{ }; use derive_setters::Setters; use iced::touch::Finger; -use iced::{Event, Vector}; +use iced::{Event, Vector, window}; use iced_core::widget::{Tree, Widget, tree}; use iced_core::{Length, Point, Size, event, mouse, touch}; use std::collections::HashSet; @@ -257,6 +257,9 @@ impl Widget path_highlight: Some(PathHighlight::MenuActive), style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), position: Point::new(translation.x, translation.y), + is_overlay: true, + window_id: window::Id::NONE, + depth: 0, } .overlay(), ) diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index ba4562f5..12808ee3 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -1,6 +1,8 @@ // From iced_aw, license MIT //! A widget that handles menu trees +use std::collections::HashMap; + use super::{ menu_inner::{ CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, @@ -43,25 +45,30 @@ pub(crate) struct MenuBarState { pub(crate) struct MenuBarStateInner { pub(crate) tree: Tree, + pub(crate) popup_id: HashMap, pub(crate) pressed: bool, + pub(crate) bar_pressed: bool, pub(crate) view_cursor: Cursor, pub(crate) open: bool, - pub(crate) active_root: Option, + pub(crate) active_root: HashMap>, pub(crate) horizontal_direction: Direction, pub(crate) vertical_direction: Direction, - pub(crate) menu_states: Vec, + pub(crate) menu_states: HashMap>, } impl MenuBarStateInner { - pub(super) fn get_trimmed_indices(&self) -> impl Iterator + '_ { + pub(super) fn get_trimmed_indices(&self, id: &window::Id) -> impl Iterator + '_ { self.menu_states - .iter() + .get(id) + .into_iter() + .map(|v| v.iter()) + .flatten() .take_while(|ms| ms.index.is_some()) .map(|ms| ms.index.expect("No indices were found in the menu state.")) } pub(super) fn reset(&mut self) { self.open = false; - self.active_root = None; + self.active_root = HashMap::new(); self.menu_states.clear(); } } @@ -72,10 +79,12 @@ impl Default for MenuBarStateInner { pressed: false, view_cursor: Cursor::Available([-0.5, -0.5].into()), open: false, - active_root: None, + active_root: HashMap::new(), horizontal_direction: Direction::Positive, vertical_direction: Direction::Positive, - menu_states: Vec::new(), + menu_states: HashMap::new(), + popup_id: HashMap::new(), + bar_pressed: false, } } } @@ -171,6 +180,7 @@ pub struct MenuBar { window_id: window::Id, #[cfg(feature = "wayland")] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + pub(crate) on_surface_action: Option Message>>, } impl MenuBar @@ -182,7 +192,13 @@ where pub fn new(menu_roots: Vec>) -> Self { let mut menu_roots = menu_roots; menu_roots.iter_mut().for_each(MenuTree::set_index); - + // println!("======================================================"); + // for menu_root in &menu_roots { + // dbg!(menu_root.index); + // for inner_root in &menu_root.children { + // dbg!(inner_root.index); + // } + // } Self { width: Length::Shrink, height: Length::Shrink, @@ -204,6 +220,7 @@ where window_id: window::Id::NONE, #[cfg(feature = "wayland")] positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), + on_surface_action: None, } } @@ -315,6 +332,14 @@ where } self } + + pub fn on_surface_action( + mut self, + handler: impl Fn(crate::surface::Action) -> Message + 'static, + ) -> Self { + self.on_surface_action = Some(Box::new(handler)); + self + } } impl Widget for MenuBar where @@ -325,7 +350,10 @@ where } fn diff(&mut self, tree: &mut Tree) { - menu_roots_diff(&mut self.menu_roots, tree); + let state = tree.state.downcast_mut::(); + state + .inner + .with_data_mut(|inner| menu_roots_diff(&mut self.menu_roots, &mut inner.tree)) } fn tag(&self) -> tree::Tag { @@ -394,21 +422,38 @@ where viewport, ); - let state = tree.state.downcast_mut::(); + let my_state = tree.state.downcast_mut::(); - match event { - Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => { - state.inner.with_data_mut(|state| { + // XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open. + let reset = self.window_id != window::Id::NONE && my_state.inner.with_data(|d| d.open); + + my_state.inner.with_data_mut(|state| { + if reset { + if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() { + dbg!("reset destroy"); + // TODO emit message + if let Some(handler) = self.on_surface_action.as_ref() { + shell.publish((handler)(crate::surface::Action::DestroyPopup(popup_id))); + state.reset(); + } + } + } + + let tree = &mut state.tree; + + match event { + Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => { if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) { state.view_cursor = view_cursor; state.open = true; // #[cfg(feature = "wayland")] // TODO emit Message to open menu } - }); + } + _ => (), } - _ => (), - } + }); + root_status } @@ -434,10 +479,14 @@ where // draw path highlight if self.path_highlight.is_some() { let styling = theme.appearance(&self.style); - if let Some(active) = state.active_root { + if let Some(active) = state + .active_root + .get(&self.window_id) + .and_then(|active| active.get(0)) + { let active_bounds = layout .children() - .nth(active) + .nth(*active) .expect("Active child not found in menu?") .bounds(); let path_quad = renderer::Quad { @@ -489,7 +538,7 @@ where Some( Menu { tree: state.clone(), - menu_roots: std::borrow::Cow::Borrowed(&mut self.menu_roots), + menu_roots: std::borrow::Cow::Owned(self.menu_roots.clone()), bounds_expand: self.bounds_expand, menu_overlays_parent: false, close_condition: self.close_condition, @@ -502,6 +551,9 @@ where path_highlight: self.path_highlight, style: std::borrow::Cow::Borrowed(&self.style), position: Point::new(translation.x, translation.y), + is_overlay: false, + window_id: window::Id::NONE, + depth: 0, } .overlay(), ) diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 21820a97..dc6595f2 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -6,6 +6,7 @@ use std::borrow::Cow; use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; use crate::style::menu_bar::StyleSheet; +use iced::window; use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget}; use iced_widget::core::{ Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, @@ -229,6 +230,7 @@ pub(super) struct MenuSlice { pub(super) upper_bound_rel: f32, } +#[derive(Clone)] /// Menu bounds in overlay space struct MenuBounds { child_positions: Vec, @@ -251,6 +253,7 @@ impl MenuBounds { bounds_expand: u16, parent_bounds: Rectangle, tree: &mut [Tree], + is_overlay: bool, ) -> Self { let (children_size, child_positions, child_sizes) = get_children_layout(menu_tree, renderer, item_width, item_height, tree); @@ -261,7 +264,11 @@ impl MenuBounds { // overlay space children position let (children_position, offset_position) = { let (cp, op) = aod.resolve(view_parent_bounds, children_size, viewport_size); - (cp - overlay_offset, op - overlay_offset) + if is_overlay { + (cp - overlay_offset, op - overlay_offset) + } else { + (Point::ORIGIN, op - overlay_offset) + } }; // calc offset bounds @@ -287,6 +294,7 @@ impl MenuBounds { } } +#[derive(Clone)] pub(crate) struct MenuState { pub(super) index: Option, scroll_offset: f32, @@ -298,7 +306,7 @@ impl MenuState { overlay_offset: Vector, slice: MenuSlice, renderer: &crate::Renderer, - menu_tree: &MenuTree, + menu_tree: &[MenuTree], tree: &mut [Tree], ) -> Node { let MenuSlice { @@ -308,21 +316,33 @@ impl MenuState { upper_bound_rel, } = slice; - assert_eq!( - menu_tree.children.len(), - self.menu_bounds.child_positions.len() - ); + // assert_eq!( + // menu_tree.children.len(), + // self.menu_bounds.child_positions.len() + // ); + // dbg!( + // overlay_offset, + // menu_tree.index, + // menu_tree.width, + // menu_tree.height + // ); + // for mt in &menu_tree.children[start_index..=end_index] { + // dbg!(mt.index); + // } + dbg!((start_index, end_index)); // viewport space children bounds let children_bounds = self.menu_bounds.children_bounds + overlay_offset; - + dbg!(children_bounds); let child_nodes = self.menu_bounds.child_positions[start_index..=end_index] .iter() .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter()) - .zip(menu_tree.children[start_index..=end_index].iter()) - .map(|((cp, size), mt)| { + .zip(menu_tree[start_index..=end_index].iter()) + .enumerate() + .map(|(i, ((cp, size), mt))| { let mut position = *cp; let mut size = *size; + dbg!(position, size); if position < lower_bound_rel && (position + size.height) > lower_bound_rel { size.height = position + size.height - lower_bound_rel; @@ -331,8 +351,13 @@ impl MenuState { { size.height = upper_bound_rel - position; } + // dbg!(size); - let limits = Limits::new(Size::ZERO, size); + let limits = Limits::new(size, size); + dbg!(i, mt.index, tree.len()); + for child in &mt.children { + dbg!(child.index); + } mt.item .layout(&mut tree[mt.index], renderer, &limits) @@ -340,6 +365,7 @@ impl MenuState { }) .collect::>(); + // dbg!(children_bounds.size()); Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position()) } @@ -434,54 +460,94 @@ pub(crate) struct Menu<'b, Message: std::clone::Clone> { pub(crate) path_highlight: Option, pub(crate) style: Cow<'b, ::Style>, pub(crate) position: Point, + pub(crate) is_overlay: bool, + /// window id for this popup + pub(crate) window_id: window::Id, + pub(crate) depth: usize, } impl<'b, Message: Clone + 'static> Menu<'b, Message> { pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> { overlay::Element::new(Box::new(self)) } -} -impl overlay::Overlay - for Menu<'_, Message> -{ - fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> Node { + fn layout(&mut self, renderer: &crate::Renderer, limits: Limits) -> Node { // layout children let position = self.position; + let mut intrinsic_size = Size::ZERO; + self.tree.inner.with_data_mut(|data| { let overlay_offset = Point::ORIGIN - position; let tree_children = &mut data.tree.children; let children = data .active_root + .get(&self.window_id) + .cloned() .map(|active_root| { - let root = &self.menu_roots[active_root]; - let active_tree = &mut tree_children[active_root]; - data.menu_states.iter().enumerate().fold( - (root, Vec::new()), - |(menu_root, mut nodes), (_i, ms)| { - let slice = ms.slice(bounds, overlay_offset, self.item_height); + let (active_tree, roots) = if self.depth > 0 { + active_root.iter().skip(1).take(self.depth).fold( + ( + &mut tree_children[active_root[0]].children, + &self.menu_roots[active_root[0]].children, + ), + |(tree, mt), next_active_root| (tree, &mt[*next_active_root].children), + ) + } else { + (tree_children, self.menu_roots.as_ref()) + }; + + dbg!(roots.len()); + dbg!(&active_root); + dbg!(active_tree.len()); + // dbg!( + // self.window_id, + // limits.max(), + // my_ref_mut.menu_states[&self.window_id].len() + // ); + // dbg!(my_ref_mut.active_root.get(&self.window_id)); + // for (i, active_tree) in active_tree.iter().enumerate() { + // dbg!(i); + // for (i2, active_tree) in active_tree.children.iter().enumerate() { + // dbg!(i2); + // for (i3, active_tree) in active_tree.children.iter().enumerate() { + // dbg!(i3); + // } + // } + // } + data.menu_states[&self.window_id] + .iter() + .enumerate() + .filter(|ms| self.is_overlay || ms.0 < active_root.len()) + .fold((roots, Vec::new()), |(menu_root, mut nodes), (_i, ms)| { + dbg!(ms.index); + let slice = ms.slice(limits.max(), overlay_offset, self.item_height); let _start_index = slice.start_index; let _end_index = slice.end_index; - let children_node = ms.layout( - overlay_offset, - slice, - renderer, - menu_root, - &mut active_tree.children, - ); + dbg!(&self.window_id, menu_root.len()); + let children_node = + ms.layout(overlay_offset, slice, renderer, menu_root, active_tree); + let node_size = children_node.size(); + // dbg!(node_size.height); + intrinsic_size.height += node_size.height; + intrinsic_size.width = intrinsic_size.width.max(node_size.width); nodes.push(children_node); + // if popup just use len 1? // only the last menu can have a None active index + // dbg!(ms.index); ( ms.index - .map_or(menu_root, |active| &menu_root.children[active]), + .map_or(menu_root, |active| &menu_root[active].children), nodes, ) - }, - ) + }) }) .map(|(_, l)| l) .unwrap_or_default(); - + // dbg!(intrinsic_size); // overlay space viewport rectangle - Node::with_children(bounds, children).translate(Point::ORIGIN - position) + Node::with_children( + limits.resolve(Length::Shrink, Length::Shrink, intrinsic_size), + children, + ) + .translate(Point::ORIGIN - position) }) } @@ -493,7 +559,7 @@ impl overlay::Overlay renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, - ) -> event::Status { + ) -> (Option<(usize, MenuState)>, event::Status) { use event::{ Event::{Mouse, Touch}, Status::{Captured, Ignored}, @@ -505,7 +571,7 @@ impl overlay::Overlay use touch::Event::{FingerLifted, FingerMoved, FingerPressed}; if !self.tree.inner.with_data(|data| data.open) { - return Ignored; + return (None, Ignored); }; let viewport = layout.bounds(); @@ -516,18 +582,15 @@ impl overlay::Overlay Cow::Borrowed(_) => panic!(), Cow::Owned(o) => o.as_mut_slice(), }; - let menu_status = self.tree.inner.with_data_mut(|data| { - process_menu_events( - &mut data.tree, - menu_roots, - event.clone(), - view_cursor, - renderer, - clipboard, - shell, - overlay_offset, - ) - }); + let menu_status = process_menu_events( + self, + event.clone(), + view_cursor, + renderer, + clipboard, + shell, + overlay_offset, + ); init_root_menu( self, @@ -540,7 +603,7 @@ impl overlay::Overlay self.main_offset as f32, ); - match event { + let ret = match event { Mouse(WheelScrolled { delta }) => { process_scroll_events(self, delta, overlay_cursor, viewport_size, overlay_offset) .merge(menu_status) @@ -557,7 +620,12 @@ impl overlay::Overlay Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => { let view_cursor = Cursor::Available(position); let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; - process_overlay_events( + if !(self.is_overlay || view_cursor.is_over(viewport)) { + return (None, menu_status); + } + // dbg!(view_cursor, viewport, self.window_id); + + let (new_root, status) = process_overlay_events( self, renderer, viewport_size, @@ -565,8 +633,9 @@ impl overlay::Overlay view_cursor, overlay_cursor, self.cross_offset as f32, - ) - .merge(menu_status) + self.is_overlay, + ); + return (new_root, status.merge(menu_status)); } Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => { @@ -581,8 +650,7 @@ impl overlay::Overlay .distance(view_cursor.position().unwrap_or_default()) < 2.0 { - let is_inside = state - .menu_states + let is_inside = state.menu_states[&self.window_id] .iter() .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor)); @@ -614,7 +682,8 @@ impl overlay::Overlay } _ => menu_status, - } + }; + (None, ret) } #[allow(unused_results)] @@ -626,36 +695,63 @@ impl overlay::Overlay layout: Layout<'_>, view_cursor: Cursor, ) { - self.tree.inner.with_data(|data| { - let Some(active_root) = data.active_root else { + self.tree.inner.with_data(|state| { + let Some(active_root) = state.active_root.get(&self.window_id) else { + // dbg!(self.window_id); return; }; - let viewport = layout.bounds(); let viewport_size = viewport.size(); let overlay_offset = Point::ORIGIN - viewport.position(); - let render_bounds = Rectangle::new(Point::ORIGIN, viewport.size()); + + let render_bounds = if self.is_overlay { + Rectangle::new(Point::ORIGIN, viewport.size()) + } else { + Rectangle::new(Point::ORIGIN, Size::INFINITY) + }; + // dbg!(self.window_id, &active_root); let styling = theme.appearance(&self.style); + let (active_tree, roots) = if self.depth > 0 { + active_root.iter().skip(1).take(self.depth).fold( + ( + &state.tree.children[active_root[0]].children, + &self.menu_roots[active_root[0]].children, + ), + |(tree, mt), next_active_root| (tree, &mt[*next_active_root].children), + ) + } else { + (&state.tree.children, self.menu_roots.as_ref()) + }; + // let tree = &state.tree.children[active_root].children; + // let root = &self.menu_roots[active_root]; - let tree = &data.tree.children[active_root].children; - let root = &self.menu_roots[active_root]; - - let indices = data.get_trimmed_indices().collect::>(); - - data.menu_states + let indices = state + .get_trimmed_indices(&self.window_id) + .collect::>(); + // dbg!( + // self.window_id, + // state.menu_states[&self.window_id].len(), + // layout.children().count(), + // root.children.len(), + // layout.bounds() + // ); + state.menu_states[&self.window_id] .iter() .zip(layout.children()) .enumerate() - .fold(root, |menu_root, (i, (ms, children_layout))| { + .filter(|ms: &(usize, (&MenuState, Layout<'_>))| { + self.is_overlay || ms.0 < active_root.len() + }) + .fold(roots, |menu_roots, (i, (ms, children_layout))| { let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph { PathHighlight::Full => true, PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1, - PathHighlight::MenuActive => i < data.menu_states.len() - 1, + PathHighlight::MenuActive => i < state.menu_states.len() - 1, }); // react only to the last menu - let view_cursor = if i == data.menu_states.len() - 1 { + let view_cursor = if i == state.menu_states.len() - 1 { view_cursor } else { Cursor::Available([-1.0; 2].into()) @@ -691,49 +787,89 @@ impl overlay::Overlay // draw path hightlight if let (true, Some(active)) = (draw_path, ms.index) { - let active_bounds = children_layout + if let Some(active_layout) = children_layout .children() .nth(active.saturating_sub(start_index)) - .expect("No active children were found in menu?") - .bounds(); - let path_quad = renderer::Quad { - bounds: active_bounds, - border: Border { - radius: styling.menu_border_radius.into(), - ..Default::default() - }, - shadow: Shadow::default(), - }; + { + let path_quad = renderer::Quad { + bounds: active_layout.bounds(), + border: Border { + radius: styling.menu_border_radius.into(), + ..Default::default() + }, + shadow: Shadow::default(), + }; - r.fill_quad(path_quad, styling.path); + r.fill_quad(path_quad, styling.path); + } + } + if start_index < menu_roots.len() { + // draw item + menu_roots[start_index..=end_index] + .iter() + .zip(children_layout.children()) + .for_each(|(mt, clo)| { + mt.item.draw( + &active_tree[mt.index], + r, + theme, + style, + clo, + view_cursor, + &children_layout.bounds(), + ); + }); } - - // draw item - menu_root.children[start_index..=end_index] - .iter() - .zip(children_layout.children()) - .for_each(|(mt, clo)| { - mt.item.draw( - &data.tree.children[mt.index], - r, - theme, - style, - clo, - view_cursor, - &children_layout.bounds(), - ); - }); }; renderer.with_layer(render_bounds, draw_menu); // only the last menu can have a None active index ms.index - .map_or(menu_root, |active| &menu_root.children[active]) + .map_or(menu_roots, |active| &menu_roots[active].children) }); }) } } +impl overlay::Overlay + for Menu<'_, Message> +{ + fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> iced_core::layout::Node { + Menu::layout( + self, + renderer, + Limits::NONE + .min_width(bounds.width) + .max_width(bounds.width) + .min_height(bounds.height) + .max_height(bounds.height), + ) + } + + fn on_event( + &mut self, + event: iced::Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &crate::Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + self.on_event(event, layout, cursor, renderer, clipboard, shell) + .1 + } + + fn draw( + &self, + renderer: &mut crate::Renderer, + theme: &crate::Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + ) { + self.draw(renderer, theme, style, layout, cursor); + } +} fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle { Rectangle { @@ -755,10 +891,60 @@ pub(super) fn init_root_menu( main_offset: f32, ) { menu.tree.inner.with_data_mut(|state| { - if !(state.menu_states.is_empty() && bar_bounds.contains(overlay_cursor)) { + if !(state + .menu_states + .get(&menu.window_id) + .is_none_or(|s| s.is_empty()) + && (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) + { return; } + println!( + "init root menu {} {:?}", + menu.window_id, menu.root_bounds_list + ); + dbg!(menu.menu_roots.len()); + // dbg!(state + // .menu_states + // .get(&menu.window_id) + // .is_none_or(|s| s.is_empty())); + // dbg!(menu + // .menu_roots + // .as_slice() + // .iter() + // .map(|r| r.index) + // .collect::>()); + + // dbg!(state + // .active_root + // .get(&menu.window_id) + // .into_iter() + // .flatten() + // .count() + // .saturating_sub(1)); + // let root = state + // .active_root + // .get(&menu.window_id) + // .iter() + // .map(|l| l.into_iter()) + // .flatten() + // .take( + // state + // .active_root + // .get(&menu.window_id) + // .into_iter() + // .flatten() + // .count() + // .saturating_sub(1), + // ) + // .fold(menu.menu_roots.as_slice(), |m, i| { + // dbg!(m.len()); + // m[*i].children.as_slice() + // }); + // dbg!(root.len()); + + let mut set = false; for (i, (&root_bounds, mt)) in menu .root_bounds_list .iter() @@ -766,10 +952,140 @@ pub(super) fn init_root_menu( .enumerate() { if mt.children.is_empty() { + // dbg!("skipping menu with no children"); continue; } - + // dbg!(i, root_bounds.contains(overlay_cursor)); if root_bounds.contains(overlay_cursor) { + dbg!(root_bounds); + // dbg!(i, root_bounds, mt.width, mt.height); + let view_center = viewport_size.width * 0.5; + let rb_center = root_bounds.center_x(); + + state.horizontal_direction = if rb_center > view_center { + Direction::Negative + } else { + Direction::Positive + }; + + let aod = Aod { + horizontal: true, + vertical: true, + horizontal_overlap: true, + vertical_overlap: false, + horizontal_direction: state.horizontal_direction, + vertical_direction: state.vertical_direction, + horizontal_offset: 0.0, + vertical_offset: main_offset, + }; + let menu_bounds = MenuBounds::new( + mt, + renderer, + menu.item_width, + menu.item_height, + viewport_size, + overlay_offset, + &aod, + menu.bounds_expand, + root_bounds, + &mut state.tree.children[0].children, + menu.is_overlay, + ); + set = true; + // dbg!(i); + // dbg!("inserting root menu state"); + // dbg!(&menu_bounds.children_bounds); + // dbg!((&menu_bounds.child_positions)); + state.active_root.insert(menu.window_id, vec![i]); + // do we need to insert the rest now too? + let ms = MenuState { + index: None, + scroll_offset: 0.0, + menu_bounds, + }; + let v = state.menu_states.entry(menu.window_id).or_default(); + v.push(ms); + + // Hack to ensure menu opens properly + shell.invalidate_layout(); + + break; + } + } + if !set { + dbg!(overlay_cursor); + } + }); +} + +pub(super) fn init_root_popup_menu( + menu: &mut Menu<'_, Message>, + renderer: &crate::Renderer, + shell: &mut Shell<'_, Message>, + overlay_cursor: Point, + viewport_size: Size, + overlay_offset: Vector, + bar_bounds: Rectangle, + main_offset: f32, +) where + Message: std::clone::Clone, +{ + menu.tree.inner.with_data_mut(|state| { + if !(state + .menu_states + .get(&menu.window_id) + .is_none_or(|s| s.is_empty()) + && (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) + { + return; + } + + println!( + "init root menu {} {:?}", + menu.window_id, menu.root_bounds_list + ); + // dbg!(state + // .menu_states + // .get(&menu.window_id) + // .is_none_or(|s| s.is_empty())); + // dbg!(menu + // .menu_roots + // .as_slice() + // .iter() + // .map(|r| r.index) + // .collect::>()); + + // dbg!(state + // .active_root + // .get(&menu.window_id) + // .into_iter() + // .flatten() + // .count()); + let active_roots = state + .active_root + .get(&menu.window_id) + .cloned() + .unwrap_or_default(); + dbg!(&active_roots); + dbg!(menu.menu_roots.len()); + let root = active_roots + .iter() + .take(active_roots.iter().count().saturating_sub(1)) + .fold(menu.menu_roots.as_slice(), |m, i| { + // dbg!(m.len()); + m[*i].children.as_slice() + }); + // dbg!(root.len()); + + let mut set = false; + for (i, (&root_bounds, mt)) in menu.root_bounds_list.iter().zip(root.iter()).enumerate() { + if mt.children.is_empty() { + // dbg!("skipping menu with no children"); + continue; + } + // dbg!(i, root_bounds.contains(overlay_cursor)); + if root_bounds.contains(overlay_cursor) { + // dbg!(i, root_bounds, mt.width, mt.height); let view_center = viewport_size.width * 0.5; let rb_center = root_bounds.center_x(); @@ -790,6 +1106,11 @@ pub(super) fn init_root_menu( vertical_offset: main_offset, }; + // dbg!( + // state.tree.children.len(), + // state.tree.children[0].children.len(), + // ); + let menu_bounds = MenuBounds::new( mt, renderer, @@ -800,15 +1121,19 @@ pub(super) fn init_root_menu( &aod, menu.bounds_expand, root_bounds, + // TODO how to select the tree for the popup &mut state.tree.children[0].children, + menu.is_overlay, ); + set = true; - state.active_root = Some(i); - state.menu_states.push(MenuState { + let ms = MenuState { index: None, scroll_offset: 0.0, menu_bounds, - }); + }; + let v = state.menu_states.entry(menu.window_id).or_default(); + v.push(ms); // Hack to ensure menu opens properly shell.invalidate_layout(); @@ -816,13 +1141,15 @@ pub(super) fn init_root_menu( break; } } - }); + if !set { + dbg!(overlay_cursor); + } + }) } #[allow(clippy::too_many_arguments)] -fn process_menu_events<'b, Message>( - tree: &'b mut Tree, - menu_roots: &'b mut [MenuTree], +fn process_menu_events<'b, Message: std::clone::Clone>( + menu: &'b mut Menu, event: event::Event, view_cursor: Cursor, renderer: &crate::Renderer, @@ -832,28 +1159,45 @@ fn process_menu_events<'b, Message>( ) -> event::Status { use event::Status; - let state = tree.state.downcast_mut::(); - state.inner.with_data(|state| { - let Some(active_root) = state.active_root else { + let window_id = menu.window_id; + let is_overlay = menu.is_overlay; + let my_state = &mut menu.tree; + let menu_roots = match &mut menu.menu_roots { + Cow::Borrowed(_) => panic!(), + Cow::Owned(o) => o.as_mut_slice(), + }; + my_state.inner.with_data_mut(|state| { + let Some(active_root) = state.active_root.get(&window_id).cloned() else { return Status::Ignored; }; - let indices = state.get_trimmed_indices().collect::>(); + let indices = state.get_trimmed_indices(&window_id); + + let indices = if is_overlay { + indices.collect::>() + } else { + indices.take(1).collect::>() + }; if indices.is_empty() { return Status::Ignored; } // get active item - let mt = indices - .iter() - .fold(&mut menu_roots[active_root], |mt, &i| &mut mt.children[i]); + // let mt = indices.iter().fold(root | mt, &i | &mut mt.children[i]); + let (tree, mt) = active_root.iter().take(menu.depth).skip(1).fold( + ( + &mut state.tree.children[active_root[0]].children, + &mut menu_roots[active_root[0]], + ), + |(tree, mt), next_active_root| (tree, &mut mt.children[*next_active_root]), + ); // widget tree - let tree = &mut tree.children[active_root].children[mt.index]; + let tree = &mut tree[mt.index]; // get layout - let last_ms = &state.menu_states[indices.len() - 1]; + let last_ms = &state.menu_states[&window_id][indices.len() - 1]; let child_node = last_ms.layout_single( overlay_offset, last_ms.index.expect("missing index within menu state."), @@ -886,7 +1230,8 @@ fn process_overlay_events( view_cursor: Cursor, overlay_cursor: Point, cross_offset: f32, -) -> event::Status + is_overlay: bool, +) -> (Option<(usize, MenuState)>, event::Status) where Message: std::clone::Clone, { @@ -900,16 +1245,18 @@ where if active item is a menu: add menu // viewport space */ + let mut new_menu_root = None; + menu.tree.inner.with_data_mut(|state| { - let Some(active_root) = state.active_root else { - if !menu.bar_bounds.contains(overlay_cursor) { + let Some(active_root) = state.active_root.get(&menu.window_id).clone() else { + if is_overlay && !menu.bar_bounds.contains(overlay_cursor) { state.reset(); } - return Ignored; + return (new_menu_root, Ignored); }; if state.pressed { - return Ignored; + return (new_menu_root, Ignored); } /* When overlay is running, cursor_position in any widget method will go negative @@ -919,18 +1266,20 @@ where // * remove invalid menus let mut prev_bounds = std::iter::once(menu.bar_bounds) .chain( - state.menu_states[..state.menu_states.len().saturating_sub(1)] + state.menu_states[&menu.window_id] + [..state.menu_states.len().saturating_sub(1).min(1)] .iter() .map(|ms| ms.menu_bounds.children_bounds), ) .collect::>(); + let menu_states = state.menu_states.get_mut(&menu.window_id).unwrap(); if menu.close_condition.leave { - for i in (0..state.menu_states.len()).rev() { - let mb = &state.menu_states[i].menu_bounds; + for i in (0..menu_states.len()).rev() { + let mb = &menu_states[i].menu_bounds; if mb.parent_bounds.contains(overlay_cursor) - || mb.children_bounds.contains(overlay_cursor) + || is_overlay && mb.children_bounds.contains(overlay_cursor) || mb.offset_bounds.contains(overlay_cursor) || (mb.check_bounds.contains(overlay_cursor) && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) @@ -938,11 +1287,11 @@ where break; } prev_bounds.pop(); - state.menu_states.pop(); + menu_states.pop(); } } else { - for i in (0..state.menu_states.len()).rev() { - let mb = &state.menu_states[i].menu_bounds; + for i in (0..menu_states.len()).rev() { + let mb = &menu_states[i].menu_bounds; if mb.parent_bounds.contains(overlay_cursor) || mb.children_bounds.contains(overlay_cursor) @@ -950,22 +1299,21 @@ where { break; } + // dbg!(menu_states.len()); prev_bounds.pop(); - state.menu_states.pop(); + menu_states.pop(); } } // get indices - let indices = state - .menu_states - .iter() - .map(|ms| ms.index) - .collect::>(); + let indices = menu_states.iter().map(|ms| ms.index).collect::>(); + let should_add = is_overlay || menu_states.len() < 2; // * update active item - let Some(last_menu_state) = state.menu_states.last_mut() else { + let Some(last_menu_state) = menu_states.last_mut() else { // no menus left - state.active_root = None; + // TODO do we want to avoid this for popups? + state.active_root.remove(&menu.window_id); // keep state.open when the cursor is still inside the menu bar // this allows the overlay to keep drawing when the cursor is @@ -973,20 +1321,20 @@ where if !menu.bar_bounds.contains(overlay_cursor) { state.open = false; } - return Captured; + return (new_menu_root, Captured); }; let last_menu_bounds = &last_menu_state.menu_bounds; let last_parent_bounds = last_menu_bounds.parent_bounds; let last_children_bounds = last_menu_bounds.children_bounds; - if (!menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor)) - // cursor is in the parent part - || !last_children_bounds.contains(overlay_cursor) + if (is_overlay && !menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor)) + // cursor is in the parent part + || is_overlay && !last_children_bounds.contains(overlay_cursor) // cursor is outside { last_menu_state.index = None; - return Captured; + return (new_menu_root, Captured); } // cursor is in the children part @@ -994,19 +1342,41 @@ where let height_diff = (overlay_cursor.y - (last_children_bounds.y + last_menu_state.scroll_offset)) .clamp(0.0, last_children_bounds.height - 0.001); - - let active_menu_root = &menu.menu_roots[active_root]; - - let active_menu = indices[0..indices.len().saturating_sub(1)] - .iter() - .fold(active_menu_root, |mt, i| { - &mt.children[i.expect("missing active child index in menu")] - }); - + // dbg!(height_diff); + // let (tree, active_menu_root) = active_root.iter().skip(1).fold( + // ( + // &mut state.tree.children[active_root[0]].children, + // &menu.menu_roots[active_root[0]], + // ), + // |(tree, mr), next_active_root| (tree, &mr.children[*next_active_root]), + // ); + let (active_tree, roots) = if menu.depth > 0 { + active_root.iter().skip(1).take(menu.depth).fold( + ( + &mut state.tree.children[active_root[0]].children, + &menu.menu_roots[active_root[0]].children, + ), + |(tree, mt), next_active_root| (tree, &mt[*next_active_root].children), + ) + } else { + (&mut state.tree.children, menu.menu_roots.as_ref()) + }; + let active_menu = if is_overlay { + indices[0..indices.len().saturating_sub(1)] + .iter() + .fold(roots, |mt, i| { + &mt[i.expect("missing active child index in menu")].children + }) + } else { + // popup does one layer deep + roots + }; + // dbg!(active_menu.children.len()); + // dbg!(menu.window_id); let new_index = match menu.item_height { ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize, ItemHeight::Static(_) | ItemHeight::Dynamic(_) => { - let max_index = active_menu.children.len() - 1; + let max_index = active_menu.len() - 1; search_bound( 0, 0, @@ -1018,14 +1388,26 @@ where } }; + let item = &active_menu[new_index]; + // dbg!(new_index); + // dbg!(item.index); + // if the index changes, get a new root + // if !last_menu_state.index.is_some_and(|old| old == new_index) && !item.children.is_empty() { + // dbg!(&indices); + // dbg!(new_index); + // dbg!(item.children.len()); + // dbg!(menu.window_id); + // new_menu_root = Some(new_index); + // } + // set new index - last_menu_state.index = Some(new_index); + let old_index = last_menu_state.index.replace(new_index); + // dbg!(should_add); // get new active item - let item = &active_menu.children[new_index]; - // * add new menu if the new item is a menu if !item.children.is_empty() { + // dbg!("its a menu"); let item_position = Point::new( 0.0, last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset, @@ -1046,8 +1428,7 @@ where horizontal_offset: cross_offset, vertical_offset: 0.0, }; - - state.menu_states.push(MenuState { + let ms = MenuState { index: None, scroll_offset: 0.0, menu_bounds: MenuBounds::new( @@ -1060,11 +1441,21 @@ where &aod, menu.bounds_expand, item_bounds, - &mut state.tree.children[active_root].children, + active_tree, + menu.is_overlay, ), - }); + }; + if !old_index.is_some_and(|old| old == new_index) { + // dbg!("adding new menu"); + new_menu_root = Some((new_index, ms.clone())); + } + if should_add { + let v = state.menu_states.entry(menu.window_id).or_default(); + v.push(ms); + } } - Captured + + (new_menu_root, Captured) }) } @@ -1096,12 +1487,13 @@ where (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0); (max_offset, min_offset) }; + let menu_states = state.menu_states.get_mut(&menu.window_id).unwrap(); // update - if state.menu_states.is_empty() { + if menu_states.is_empty() { return Ignored; - } else if state.menu_states.len() == 1 { - let last_ms = &mut state.menu_states[0]; + } else if menu_states.len() == 1 { + let last_ms = &mut menu_states[0]; if last_ms.index.is_none() { return Captured; @@ -1111,8 +1503,8 @@ where last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset); } else { // >= 2 - let max_index = state.menu_states.len() - 1; - let last_two = &mut state.menu_states[max_index - 1..=max_index]; + let max_index = menu_states.len() - 1; + let last_two = &mut menu_states[max_index - 1..=max_index]; if last_two[1].index.is_some() { // scroll the last one diff --git a/src/widget/responsive_menu_bar.rs b/src/widget/responsive_menu_bar.rs index 65c5d3eb..b08e86ea 100644 --- a/src/widget/responsive_menu_bar.rs +++ b/src/widget/responsive_menu_bar.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use apply::Apply; +use iced::window; use crate::{ Core, Element, @@ -80,17 +81,23 @@ impl ResponsiveMenuBar { menu::bar( trees .into_iter() - .map(|mt| { - menu::Tree::<_>::with_children( - menu::root(mt.0), - menu::items(key_binds, mt.1.into()), - ) - }) + .map( + |mt: ( + std::borrow::Cow<'_, str>, + Vec>>, + )| { + menu::Tree::<_>::with_children( + menu::root(mt.0), + menu::items(key_binds, mt.1.into()), + ) + }, + ) .collect(), ) .item_width(self.item_width) .item_height(self.item_height) - .spacing(self.spacing), + .spacing(self.spacing) + .window_id_maybe(core.main_window_id()), crate::widget::Id::new(format!("menu_bar_expanded_{id}")), ), id, @@ -123,7 +130,8 @@ impl ResponsiveMenuBar { )]) .item_height(self.item_height) .item_width(self.collapsed_item_width) - .spacing(self.spacing), + .spacing(self.spacing) + .window_id_maybe(core.main_window_id()), crate::widget::Id::new(format!("menu_bar_collapsed_{id}")), ), id, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index c681262d..ba57719d 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -17,7 +17,7 @@ use iced::clipboard::mime::AllowedMimeTypes; use iced::touch::Finger; use iced::{ Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment, - event, keyboard, mouse, touch, + event, keyboard, mouse, touch, window, }; use iced_core::mouse::ScrollDelta; use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; @@ -1615,6 +1615,9 @@ where path_highlight: Some(PathHighlight::MenuActive), style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), position: Point::new(translation.x, translation.y), + is_overlay: true, + window_id: window::Id::NONE, + depth: 0, } .overlay(), )