wip: hover display working for nested popups

This commit is contained in:
Ashley Wulber 2025-06-04 00:29:24 -04:00
parent 4c03483e25
commit c5c327482b
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
4 changed files with 379 additions and 348 deletions

View file

@ -7,11 +7,11 @@ use std::collections::HashMap;
use std::sync::LazyLock; use std::sync::LazyLock;
use cosmic::app::{Core, Settings, Task}; use cosmic::app::{Core, Settings, Task};
use cosmic::iced::Length;
use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::alignment::{Horizontal, Vertical};
use cosmic::iced::widget::column; use cosmic::iced::widget::column;
use cosmic::iced::Length;
use cosmic::iced_core::Size; use cosmic::iced_core::Size;
use cosmic::widget::icon::{Handle, from_name}; use cosmic::widget::icon::{from_name, Handle};
use cosmic::widget::menu::KeyBind; use cosmic::widget::menu::KeyBind;
use cosmic::widget::{button, text}; use cosmic::widget::{button, text};
use cosmic::widget::{ use cosmic::widget::{
@ -20,7 +20,7 @@ use cosmic::widget::{
menu::{self, action::MenuAction}, menu::{self, action::MenuAction},
nav_bar, responsive, nav_bar, responsive,
}; };
use cosmic::{ApplicationExt, Element, executor, iced}; use cosmic::{executor, iced, ApplicationExt, Element};
static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id")); static MENU_ID: LazyLock<iced::id::Id> = LazyLock::new(|| iced::id::Id::new("menu_id"));
@ -177,6 +177,7 @@ impl cosmic::Application for App {
self.hidden = !self.hidden; self.hidden = !self.hidden;
} }
Message::Surface(a) => { Message::Surface(a) => {
dbg!("got action...");
return cosmic::task::message(cosmic::Action::Cosmic( return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(a), cosmic::app::Action::Surface(a),
)); ));
@ -255,6 +256,21 @@ impl cosmic::Application for App {
vec![ vec![
menu::Item::Button("hi 21", None, Action::Hi), menu::Item::Button("hi 21", None, Action::Hi),
menu::Item::Button("hi 22", None, Action::Hi2), menu::Item::Button("hi 22", None, Action::Hi2),
menu::Item::Folder(
"nest 3 2".into(),
vec![
menu::Item::Button("31", None, Action::Hi),
menu::Item::Button("342", None, Action::Hi2),
menu::Item::Button("3443", None, Action::Hi3),
menu::Item::Folder(
"nest 4 2".into(),
vec![
menu::Item::Button("343", None, Action::Hi2),
menu::Item::Button("3444", None, Action::Hi),
],
),
],
),
], ],
), ),
( (

View file

@ -460,7 +460,6 @@ impl<'a, Message: 'a + Clone> Widget<Message, crate::Theme, crate::Renderer>
if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) { if !self.selected && matches!(self.style, crate::theme::Button::HeaderBar) {
headerbar_alpha = Some(0.8); headerbar_alpha = Some(0.8);
} }
theme.hovered(state.is_focused, self.selected, &self.style) theme.hovered(state.is_focused, self.selected, &self.style)
} }
} else { } else {

View file

@ -461,8 +461,6 @@ where
} }
}); });
// let tree: &mut _ = &mut state.tree;
match event { match event {
Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => { Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => {
let create_popup = my_state.inner.with_data_mut(|state| { let create_popup = my_state.inner.with_data_mut(|state| {
@ -483,16 +481,11 @@ where
} }
create_popup create_popup
}); });
dbg!(create_popup);
if !create_popup { if !create_popup {
return root_status; return root_status;
} }
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
dbg!(
self.window_id != window::Id::NONE,
self.on_surface_action.is_some()
);
// TODO emit Message to open menu // TODO emit Message to open menu
if self.window_id != window::Id::NONE && self.on_surface_action.is_some() { if self.window_id != window::Id::NONE && self.on_surface_action.is_some() {
use crate::surface::action::destroy_popup; use crate::surface::action::destroy_popup;
@ -514,14 +507,6 @@ where
let hovered_root = layout let hovered_root = layout
.children() .children()
.position(|lo| view_cursor.is_over(lo.bounds())); .position(|lo| view_cursor.is_over(lo.bounds()));
dbg!(old_active_root);
// TODO why exit here?
// if hovered_root
// .zip(old_active_root.as_ref())
// .is_none_or(|r| r.0 != *r.1)
// {
// panic!();
// }
let (id, root_list) = my_state.inner.with_data_mut(|state| { let (id, root_list) = my_state.inner.with_data_mut(|state| {
if let Some(id) = state.popup_id.get(&self.window_id).copied() { if let Some(id) = state.popup_id.get(&self.window_id).copied() {
@ -629,7 +614,6 @@ where
// Window(Focused) => { // Window(Focused) => {
// my_state.inner.with_data_mut(|state| { // my_state.inner.with_data_mut(|state| {
// if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() { // if let Some(popup_id) = state.popup_id.get(&self.window_id).copied() {
// dbg!("window focused");
// if let Some(handler) = self.on_surface_action.as_ref() { // if let Some(handler) = self.on_surface_action.as_ref() {
// shell.publish((handler)(destroy_popup(popup_id))); // shell.publish((handler)(destroy_popup(popup_id)));
// state.reset(); // state.reset();
@ -713,7 +697,7 @@ where
_renderer: &Renderer, _renderer: &Renderer,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
// #[cfg(all(feature = "wayland", feature = "winit"))] #[cfg(all(feature = "wayland", feature = "winit"))]
return None; return None;
let state = tree.state.downcast_ref::<MenuBarState>(); let state = tree.state.downcast_ref::<MenuBarState>();

View file

@ -325,11 +325,9 @@ impl MenuState {
// for mt in &menu_tree.children[start_index..=end_index] { // for mt in &menu_tree.children[start_index..=end_index] {
// dbg!(mt.index); // dbg!(mt.index);
// } // }
dbg!((start_index, end_index, menu_tree.len()));
// viewport space children bounds // viewport space children bounds
let children_bounds = self.menu_bounds.children_bounds + overlay_offset; 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] let child_nodes = self.menu_bounds.child_positions[start_index..=end_index]
.iter() .iter()
.zip(self.menu_bounds.child_sizes[start_index..=end_index].iter()) .zip(self.menu_bounds.child_sizes[start_index..=end_index].iter())
@ -338,7 +336,7 @@ impl MenuState {
.map(|(i, ((cp, size), mt))| { .map(|(i, ((cp, size), mt))| {
let mut position = *cp; let mut position = *cp;
let mut size = *size; let mut size = *size;
dbg!(position, size); // dbg!(position, size);
if position < lower_bound_rel && (position + size.height) > lower_bound_rel { if position < lower_bound_rel && (position + size.height) > lower_bound_rel {
size.height = position + size.height - lower_bound_rel; size.height = position + size.height - lower_bound_rel;
@ -350,10 +348,10 @@ impl MenuState {
// dbg!(size); // dbg!(size);
let limits = Limits::new(size, size); let limits = Limits::new(size, size);
dbg!(i, mt.index, tree.len()); // dbg!(i, mt.index, tree.len());
for child in &mt.children { // for child in &mt.children {
dbg!(child.index); // dbg!(child.index);
} // }
mt.item mt.item
.layout(&mut tree[mt.index], renderer, &limits) .layout(&mut tree[mt.index], renderer, &limits)
@ -475,19 +473,14 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
} }
pub(crate) fn layout(&self, renderer: &crate::Renderer, limits: Limits) -> Node { pub(crate) fn layout(&self, renderer: &crate::Renderer, limits: Limits) -> Node {
dbg!("layout");
// layout children; // layout children;
let position = self.position; let position = self.position;
let mut intrinsic_size = Size::ZERO; let mut intrinsic_size = Size::ZERO;
dbg!(self.depth);
let empty = Vec::new(); let empty = Vec::new();
self.tree.inner.with_data_mut(|data| { self.tree.inner.with_data_mut(|data| {
dbg!(data.active_root.len());
let overlay_offset = Point::ORIGIN - position; let overlay_offset = Point::ORIGIN - position;
let tree_children = &mut data.tree.children; let tree_children = &mut data.tree.children;
dbg!(&data.active_root,);
let children = data let children = data
.active_root .active_root
.get(self.depth) .get(self.depth)
@ -496,43 +489,26 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
if active_root.is_empty() || self.menu_roots.is_empty() { if active_root.is_empty() || self.menu_roots.is_empty() {
return (&empty, vec![]); return (&empty, vec![]);
} }
let (active_tree, roots) = active_root.iter().take(self.depth).fold( let (active_tree, roots) = active_root
( .iter()
&mut tree_children[active_root[0]].children, .skip(if self.is_overlay { 0 } else { 1 })
&self.menu_roots[active_root[0]].children, .fold(
), (
|(tree, mt), next_active_root| (tree, &mt[*next_active_root].children), &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),
);
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);
// }
// }
// }
if let Some(ms) = data.menu_states.get(self.depth) { if let Some(ms) = data.menu_states.get(self.depth) {
ms.iter() ms.iter()
.enumerate() .enumerate()
.filter(|ms| self.is_overlay || ms.0 < active_root.len()) .filter(|ms| self.is_overlay || ms.0 < active_root.len())
.fold((roots, Vec::new()), |(menu_root, mut nodes), (_i, ms)| { .fold((roots, Vec::new()), |(menu_root, mut nodes), (_i, ms)| {
dbg!(ms.index);
let slice = let slice =
ms.slice(limits.max(), overlay_offset, self.item_height); ms.slice(limits.max(), overlay_offset, self.item_height);
let _start_index = slice.start_index; let _start_index = slice.start_index;
let _end_index = slice.end_index; let _end_index = slice.end_index;
dbg!(&self.depth, &self.window_id, menu_root.len());
let children_node = ms.layout( let children_node = ms.layout(
overlay_offset, overlay_offset,
slice, slice,
@ -541,13 +517,11 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
active_tree, active_tree,
); );
let node_size = children_node.size(); let node_size = children_node.size();
// dbg!(node_size.height);
intrinsic_size.height += node_size.height; intrinsic_size.height += node_size.height;
intrinsic_size.width = intrinsic_size.width.max(node_size.width); intrinsic_size.width = intrinsic_size.width.max(node_size.width);
nodes.push(children_node); nodes.push(children_node);
// if popup just use len 1? // if popup just use len 1?
// only the last menu can have a None active index // only the last menu can have a None active index
// dbg!(ms.index);
( (
ms.index ms.index
.map_or(menu_root, |active| &menu_root[active].children), .map_or(menu_root, |active| &menu_root[active].children),
@ -580,7 +554,6 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
) -> (Option<(usize, MenuState)>, event::Status) { ) -> (Option<(usize, MenuState)>, event::Status) {
// dbg!("on event");
use event::{ use event::{
Event::{Mouse, Touch}, Event::{Mouse, Touch},
Status::{Captured, Ignored}, Status::{Captured, Ignored},
@ -639,9 +612,11 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
} }
Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => { Mouse(CursorMoved { position }) | Touch(FingerMoved { position, .. }) => {
dbg!("moved", self.window_id);
let view_cursor = Cursor::Available(position); let view_cursor = Cursor::Available(position);
let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
if !(self.is_overlay || view_cursor.is_over(viewport)) { if !(self.is_overlay || view_cursor.is_over(viewport)) {
dbg!("exit early", view_cursor, viewport);
return (None, menu_status); return (None, menu_status);
} }
// dbg!(view_cursor, viewport, self.window_id); // dbg!(view_cursor, viewport, self.window_id);
@ -655,8 +630,9 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
overlay_cursor, overlay_cursor,
self.cross_offset as f32, self.cross_offset as f32,
self.is_overlay, self.is_overlay,
shell,
); );
dbg!(new_root.as_ref().map(|r| r.0)); dbg!(new_root.as_ref().map(|new_root| new_root.0));
return (new_root, status.merge(menu_status)); return (new_root, status.merge(menu_status));
} }
@ -730,6 +706,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let Some(active_root) = state.active_root.get(self.depth) else { let Some(active_root) = state.active_root.get(self.depth) else {
return; return;
}; };
dbg!(self.depth, &active_root);
let viewport = layout.bounds(); let viewport = layout.bounds();
let viewport_size = viewport.size(); let viewport_size = viewport.size();
let overlay_offset = Point::ORIGIN - viewport.position(); let overlay_offset = Point::ORIGIN - viewport.position();
@ -741,16 +718,18 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
}; };
let styling = theme.appearance(&self.style); let styling = theme.appearance(&self.style);
let (active_tree, roots) = active_root.iter().take(self.depth).fold( let (active_tree, roots) = active_root
( .iter()
&state.tree.children[active_root[0]].children, .skip(if self.is_overlay { 0 } else { 1 })
&self.menu_roots[active_root[0]].children, .fold(
), (
|(tree, mt), next_active_root| (tree, &mt[*next_active_root].children), &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),
);
let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>(); let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>();
state.menu_states[self.depth] state.menu_states[self.depth]
.iter() .iter()
.zip(layout.children()) .zip(layout.children())
@ -762,15 +741,16 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph { let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph {
PathHighlight::Full => true, PathHighlight::Full => true,
PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1, PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1,
PathHighlight::MenuActive => i < state.menu_states.len() - 1, PathHighlight::MenuActive => self.depth == state.active_root.len() - 1,
}); });
// react only to the last menu // react only to the last menu
let view_cursor = if i == state.menu_states.len() - 1 { if self.depth == state.active_root.len() - 1 {
view_cursor view_cursor
} else { } else {
Cursor::Available([-1.0; 2].into()) Cursor::Available([-1.0; 2].into())
}; };
dbg!(view_cursor);
let draw_menu = |r: &mut crate::Renderer| { let draw_menu = |r: &mut crate::Renderer| {
// calc slice // calc slice
@ -799,7 +779,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
}; };
let menu_color = styling.background; let menu_color = styling.background;
r.fill_quad(menu_quad, menu_color); r.fill_quad(menu_quad, menu_color);
dbg!(ms.index, children_layout.children().count(), start_index);
// draw path hightlight // draw path hightlight
if let (true, Some(active)) = (draw_path, ms.index) { if let (true, Some(active)) = (draw_path, ms.index) {
if let Some(active_layout) = children_layout if let Some(active_layout) = children_layout
@ -825,6 +805,8 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.iter() .iter()
.zip(children_layout.children()) .zip(children_layout.children())
.for_each(|(mt, clo)| { .for_each(|(mt, clo)| {
dbg!(self.depth, view_cursor, clo.bounds());
mt.item.draw( mt.item.draw(
&active_tree[mt.index], &active_tree[mt.index],
r, r,
@ -920,175 +902,198 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
Menu::draw(self, renderer, theme, style, layout, cursor); Menu::draw(self, renderer, theme, style, layout, cursor);
} }
#[allow(clippy::too_many_lines)]
fn on_event( fn on_event(
&mut self, &mut self,
_tree: &mut Tree, tree: &mut Tree,
event: iced::Event, event: iced::Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
renderer: &crate::Renderer, renderer: &crate::Renderer,
clipboard: &mut dyn Clipboard, clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) -> event::Status {
let prev_hover = self.is_overlay.then(|| {
self.tree.inner.with_data(|d| {
let menu_states = d.menu_states.get(self.depth).unwrap();
menu_states.get(0).and_then(|ms| ms.index)
})
});
let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell); let (new_root, status) = self.on_event(event, layout, cursor, renderer, clipboard, shell);
dbg!(new_root.as_ref().map(|r| r.0)); // dbg!(new_root.as_ref().map(|r| r.0));
// #[cfg(all(feature = "wayland", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
// if let Some((new_root, new_ms)) = new_root { if let Some((new_root, new_ms)) = new_root {
// use iced_runtime::platform_specific::wayland::popup::{ use iced_runtime::platform_specific::wayland::popup::{
// SctkPopupSettings, SctkPositioner, SctkPopupSettings, SctkPositioner,
// }; };
// dbg!(new_root); let overlay_offset = Point::ORIGIN - viewport.position();
// let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| { dbg!(overlay_offset);
// let popup_id = *state
// .popup_id
// .entry(self.window_id)
// .or_insert_with(window::Id::unique);
// let active_roots = state
// .active_root
// .get(self.depth)
// .cloned()
// .unwrap_or_default(); // dbg!(active_roots);
// // let root_bounds_list = active_roots
// // .into_iter()
// // .fold(layout, |l, active_root| {
// // // dbg!(active_root);
// // l.children().nth(active_root).unwrap()
// // })
// // .children()
// // .map(|c| c.bounds())
// // .collect();
// let root_bounds_list = layout.children().map(|lo| lo.bounds()).collect();
// // drop(state);
// // dbg!(&root_bounds_list); let overlay_cursor = cursor.position().unwrap_or_default() - overlay_offset;
// // dbg!(self.menu_roots.clone().len());
// // dbg!(self
// // .menu_roots
// // .clone()
// // .iter()
// // .map(|r| r.index)
// // .collect::<Vec<_>>());
// let mut popup_menu = Menu {
// tree: self.tree.clone(),
// menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
// bounds_expand: self.bounds_expand,
// menu_overlays_parent: false,
// close_condition: self.close_condition,
// item_width: self.item_width,
// item_height: self.item_height,
// bar_bounds: layout.bounds(),
// main_offset: self.main_offset,
// cross_offset: self.cross_offset,
// root_bounds_list,
// path_highlight: self.path_highlight,
// style: Cow::Owned(Cow::into_owned(self.style.clone())),
// position: Point::new(0., 0.),
// is_overlay: false,
// window_id: popup_id,
// depth: self.depth + 1,
// on_surface_action: self.on_surface_action.clone(),
// };
// // let mut state = self.tree.inner.lock().unwrap();
// // dbg!(state.menu_states.keys());
// // let Some(parent_root) = state.active_root.get(&self.window_id) else {
// // // TODO log warning
// // return status;
// // };
// let Some(parent_root) = state.active_root.get(self.depth).cloned() else {
// return None;
// };
// let mut roots = parent_root.clone();
// roots.push(new_root);
// // dbg!(&roots);
// state.active_root.push(roots);
// // _ = state.menu_states.remove(&popup_id);
// // drop(state);
// Some((popup_menu, popup_id))
// }) else {
// return status;
// };
// init_root_popup_menu(
// &mut menu,
// renderer,
// shell,
// cursor.position().unwrap(),
// layout.bounds().size(),
// Vector::new(0., 0.),
// layout.bounds(),
// self.main_offset as f32,
// );
// let anchor_rect = self.tree.inner.with_data_mut(|state| {
// // let mut state = self.tree.inner.lock().unwrap();
// state.menu_states.get_mut(self.depth).unwrap().push(new_ms);
// state.menu_states[self.depth] let Some((mut menu, popup_id)) = self.tree.inner.with_data_mut(|state| {
// .iter() let popup_id = *state
// .find(|s| s.index.is_none()) .popup_id
// .map(|s| s.menu_bounds.parent_bounds) .entry(self.window_id)
// .map_or_else( .or_insert_with(window::Id::unique);
// || { let active_roots = state
// let bounds = layout.bounds(); .active_root
// Rectangle { .get(self.depth)
// x: bounds.x as i32, .cloned()
// y: bounds.y as i32, .unwrap_or_default(); // dbg!(active_roots);
// width: bounds.width as i32, // let root_bounds_list = active_roots
// height: bounds.height as i32, // .into_iter()
// } // .fold(layout, |l, active_root| {
// }, // dbg!(active_root, l.children().count());
// |r| Rectangle { // l.children().nth(active_root).unwrap()
// x: r.x as i32, // })
// y: r.y as i32, // .children()
// width: r.width as i32, // .map(|c| c.bounds())
// height: r.height as i32, // .collect();
// },
// )
// });
// // dbg!(&anchor_rect); let root_bounds_list = layout
.children()
.next()
.unwrap()
.children()
.map(|lo| lo.bounds())
.collect();
// drop(state);
// // drop(state); // dbg!(&root_bounds_list);
// let menu_node = Widget::layout( // dbg!(self.menu_roots.clone().len());
// &menu, // dbg!(self
// &mut Tree::empty(), // .menu_roots
// renderer, // .clone()
// &Limits::NONE.min_width(1.).min_height(1.), // .iter()
// ); // .map(|r| r.index)
// // dbg!(menu_node.size()); // .collect::<Vec<_>>());
let mut popup_menu = Menu {
tree: self.tree.clone(),
menu_roots: Cow::Owned(Cow::into_owned(self.menu_roots.clone())),
bounds_expand: self.bounds_expand,
menu_overlays_parent: false,
close_condition: self.close_condition,
item_width: self.item_width,
item_height: self.item_height,
bar_bounds: layout.bounds(),
main_offset: self.main_offset,
cross_offset: self.cross_offset,
root_bounds_list,
path_highlight: self.path_highlight,
style: Cow::Owned(Cow::into_owned(self.style.clone())),
position: Point::new(0., 0.),
is_overlay: false,
window_id: popup_id,
depth: self.depth + 1,
on_surface_action: self.on_surface_action.clone(),
};
// let mut state = self.tree.inner.lock().unwrap();
// dbg!(state.menu_states.keys());
// let Some(parent_root) = state.active_root.get(&self.window_id) else {
// // TODO log warning
// return status;
// };
let Some(parent_root) = state.active_root.get(self.depth).cloned() else {
dbg!("NO ROOT?");
return None;
};
let mut roots = parent_root.clone();
roots.push(new_root);
// dbg!(&roots);
state.active_root.push(roots);
// _ = state.menu_states.remove(&popup_id);
// drop(state);
Some((popup_menu, popup_id))
}) else {
return status;
};
// XXX we push a new active root manually instead
init_root_popup_menu(
&mut menu,
renderer,
shell,
cursor.position().unwrap(),
layout.bounds().size(),
Vector::new(0., 0.),
layout.bounds(),
self.main_offset as f32,
);
let anchor_rect = self.tree.inner.with_data_mut(|state| {
// let mut state = self.tree.inner.lock().unwrap();
state.menu_states.get_mut(self.depth).unwrap().push(new_ms);
// // dbg!(&menu_node); state.menu_states[self.depth]
.iter()
.find(|s| s.index.is_none())
.map(|s| s.menu_bounds.parent_bounds)
.map_or_else(
|| {
let bounds = layout.bounds();
Rectangle {
x: bounds.x as i32,
y: bounds.y as i32,
width: bounds.width as i32,
height: bounds.height as i32,
}
},
|r| Rectangle {
x: r.x as i32,
y: r.y as i32,
width: r.width as i32,
height: r.height as i32,
},
)
});
// let popup_size = menu_node.size(); // dbg!(&anchor_rect);
// let positioner = SctkPositioner {
// size: Some((popup_size.width.ceil() as u32 + 2, popup_size.height.ceil() as u32 + 2)),
// anchor_rect,
// anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
// gravity:cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
// reactive: true,
// ..Default::default()
// };
// let parent = self.window_id;
// // dbg!(&positioner);
// (self.on_surface_action.as_ref().unwrap())(crate::surface::action::simple_popup(
// move || SctkPopupSettings {
// parent,
// id: popup_id,
// positioner: positioner.clone(),
// parent_size: None,
// grab: true,
// close_with_children: true,
// input_zone: todo!(),
// },
// Some(move || {
// crate::Element::from(
// crate::widget::container(menu.clone()).center(Length::Fill),
// )
// .map(crate::action::app)
// }),
// ));
// return status; // drop(state);
// } let menu_node = Widget::layout(
&menu,
&mut Tree::empty(),
renderer,
&Limits::NONE.min_width(1.).min_height(1.),
);
// dbg!(menu_node.size());
// dbg!(&menu_node);
let popup_size = menu_node.size();
let positioner = SctkPositioner {
size: Some((popup_size.width.ceil() as u32 + 2, popup_size.height.ceil() as u32 + 2)),
anchor_rect,
anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
gravity:cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
reactive: true,
..Default::default()
};
let parent = self.window_id;
dbg!(&positioner);
shell.publish((self.on_surface_action.as_ref().unwrap())(
crate::surface::action::simple_popup(
move || SctkPopupSettings {
parent,
id: popup_id,
positioner: positioner.clone(),
parent_size: None,
grab: true,
close_with_children: false,
input_zone: None,
},
Some(move || {
crate::Element::from(
crate::widget::container(menu.clone()).center(Length::Fill),
)
.map(crate::action::app)
}),
),
));
return status;
}
status status
} }
} }
@ -1129,12 +1134,15 @@ pub(super) fn init_root_menu<Message: Clone>(
.is_none_or(|s| s.is_empty()) .is_none_or(|s| s.is_empty())
&& (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
{ {
// dbg!("exiting from root menu init early...", menu.depth);
return; return;
} }
println!( dbg!(
"init root menu {} {} {:?}", menu.depth,
menu.depth, menu.window_id, &menu.root_bounds_list menu.window_id,
&menu.root_bounds_list,
state.menu_states.get(menu.depth).is_none()
); );
dbg!(menu.menu_roots.len()); dbg!(menu.menu_roots.len());
dbg!( dbg!(
@ -1160,26 +1168,27 @@ pub(super) fn init_root_menu<Message: Clone>(
.count() .count()
.saturating_sub(1) .saturating_sub(1)
); );
let root = state // let root = state
.active_root // .active_root
.get(menu.depth) // .get(menu.depth)
.iter() // .iter()
.map(|l| l.into_iter()) // .map(|l| l.into_iter())
.flatten() // .flatten()
.take( // .take(
state // state
.active_root // .active_root
.get(menu.depth) // .get(menu.depth)
.into_iter() // .into_iter()
.flatten() // .flatten()
.count() // .count()
.saturating_sub(1), // .saturating_sub(1),
) // )
.fold(menu.menu_roots.as_slice(), |m, i| { // .fold(menu.menu_roots.as_slice(), |m, i| {
dbg!(m.len()); // dbg!(m.len());
m[*i].children.as_slice() // m[*i].children.as_slice()
}); // });
dbg!(root.len()); // dbg!(root.len());
dbg!(menu.depth);
let mut set = false; let mut set = false;
for (i, (&root_bounds, mt)) in menu for (i, (&root_bounds, mt)) in menu
@ -1235,6 +1244,7 @@ pub(super) fn init_root_menu<Message: Clone>(
dbg!("inserting root menu state"); dbg!("inserting root menu state");
dbg!(&menu_bounds.children_bounds); dbg!(&menu_bounds.children_bounds);
dbg!((&menu_bounds.child_positions)); dbg!((&menu_bounds.child_positions));
dbg!(&state.active_root);
state.active_root.insert(menu.depth, vec![i]); state.active_root.insert(menu.depth, vec![i]);
// do we need to insert the rest now too? // do we need to insert the rest now too?
let ms = MenuState { let ms = MenuState {
@ -1279,6 +1289,7 @@ pub(super) fn init_root_popup_menu<Message>(
.is_none_or(|s| s.is_empty()) .is_none_or(|s| s.is_empty())
&& (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
{ {
dbg!("exiting init early", menu.depth);
return; return;
} }
@ -1309,82 +1320,79 @@ pub(super) fn init_root_popup_menu<Message>(
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
dbg!(&active_roots); dbg!(&active_roots);
dbg!(menu.menu_roots.len()); dbg!(menu.menu_roots.len(), menu.root_bounds_list.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; let mut set = false;
for (i, (&root_bounds, mt)) in menu.root_bounds_list.iter().zip(root.iter()).enumerate() { let mt = active_roots
if mt.children.is_empty() { .iter()
// dbg!("skipping menu with no children"); .skip(if menu.is_overlay { 0 } else { 1 })
continue; .fold(&menu.menu_roots[active_roots[0]], |mt, next_active_root| {
} &mt.children[*next_active_root]
// dbg!(i, root_bounds.contains(overlay_cursor)); });
if root_bounds.contains(overlay_cursor) { let i = active_roots.last().unwrap();
// dbg!(i, root_bounds, mt.width, mt.height); let root_bounds = menu.root_bounds_list[*i];
let view_center = viewport_size.width * 0.5;
let rb_center = root_bounds.center_x();
state.horizontal_direction = if rb_center > view_center { if mt.children.is_empty() {
Direction::Negative panic!("skipping menu with no children");
} 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,
};
// dbg!(
// state.tree.children.len(),
// state.tree.children[0].children.len(),
// );
let menu_bounds = MenuBounds::new(
mt,
renderer,
menu.item_width,
menu.item_height,
viewport_size,
overlay_offset,
&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;
let ms = MenuState {
index: None,
scroll_offset: 0.0,
menu_bounds,
};
let v = super::menu_bar::get_mut_or_default(&mut state.menu_states, menu.depth);
v.push(ms);
// Hack to ensure menu opens properly
shell.invalidate_layout();
break;
}
} }
dbg!(root_bounds, overlay_cursor);
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,
// TODO how to select the tree for the popup
&mut state.tree.children[0].children,
menu.is_overlay,
);
dbg!(mt.children.len(), root_bounds, overlay_cursor, i,);
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
};
// dbg!(
// state.tree.children.len(),
// state.tree.children[0].children.len(),
// );
set = true;
let ms = MenuState {
index: None,
scroll_offset: 0.0,
menu_bounds,
};
let v = super::menu_bar::get_mut_or_default(&mut state.menu_states, menu.depth);
v.push(ms);
dbg!(v.len(), menu.depth);
dbg!(i);
// Hack to ensure menu opens properly
shell.invalidate_layout();
if !set { if !set {
dbg!(overlay_cursor); panic!("oops");
} }
}) })
} }
@ -1482,6 +1490,7 @@ fn process_overlay_events<Message>(
overlay_cursor: Point, overlay_cursor: Point,
cross_offset: f32, cross_offset: f32,
is_overlay: bool, is_overlay: bool,
shell: &mut Shell<'_, Message>,
) -> (Option<(usize, MenuState)>, event::Status) ) -> (Option<(usize, MenuState)>, event::Status)
where where
Message: std::clone::Clone, Message: std::clone::Clone,
@ -1500,15 +1509,14 @@ where
menu.tree.inner.with_data_mut(|state| { menu.tree.inner.with_data_mut(|state| {
let Some(active_root) = state.active_root.get(menu.depth).clone() else { let Some(active_root) = state.active_root.get(menu.depth).clone() else {
panic!();
if is_overlay && !menu.bar_bounds.contains(overlay_cursor) { if is_overlay && !menu.bar_bounds.contains(overlay_cursor) {
dbg!("overlay");
state.reset(); state.reset();
} }
return (new_menu_root, Ignored); return (new_menu_root, Ignored);
}; };
if state.pressed { if state.pressed {
dbg!("pressed");
return (new_menu_root, Ignored); return (new_menu_root, Ignored);
} }
@ -1559,20 +1567,26 @@ where
// get indices // get indices
let indices = menu_states.iter().map(|ms| ms.index).collect::<Vec<_>>(); let indices = menu_states.iter().map(|ms| ms.index).collect::<Vec<_>>();
let should_add = is_overlay || menu_states.len() < 2; let should_add = is_overlay || menu_states.len() < 2;
dbg!(&indices);
let menu_state_len = menu_states.len();
// dbg!(menu_states.iter().map(|m| m.index).collect::<Vec<_>>());
// * update active item // * update active item
let Some(last_menu_state) = menu_states.last_mut() else { let Some(last_menu_state) = menu_states.get_mut(0) else {
// no menus left if menu.is_overlay {
// TODO do we want to avoid this for popups? // no menus left
state.active_root.remove(menu.depth); // TODO do we want to avoid this for popups?
state.active_root.remove(menu.depth);
// keep state.open when the cursor is still inside the menu bar // keep state.open when the cursor is still inside the menu bar
// this allows the overlay to keep drawing when the cursor is // this allows the overlay to keep drawing when the cursor is
// moving aroung the menu bar // moving aroung the menu bar
dbg!("oops menu bar shouldn't disable popup"); if !menu.bar_bounds.contains(overlay_cursor) {
if !menu.bar_bounds.contains(overlay_cursor) { state.open = false;
state.open = false; }
} }
dbg!("no last menu state", menu.depth, menu_states.len());
return (new_menu_root, Captured); return (new_menu_root, Captured);
}; };
@ -1586,6 +1600,7 @@ where
// cursor is outside // cursor is outside
{ {
last_menu_state.index = None; last_menu_state.index = None;
dbg!("cursor is outside...");
return (new_menu_root, Captured); return (new_menu_root, Captured);
} }
// cursor is in the children part // cursor is in the children part
@ -1596,19 +1611,23 @@ where
.clamp(0.0, last_children_bounds.height - 0.001); .clamp(0.0, last_children_bounds.height - 0.001);
// dbg!(height_diff); // dbg!(height_diff);
let (active_tree, roots) = active_root.iter().take(menu.depth).fold( let (active_tree, roots) = active_root
( .iter()
&mut state.tree.children[active_root[0]].children, .skip(if menu.is_overlay { 0 } else { 1 })
&menu.menu_roots[active_root[0]].children, .fold(
), (
|(tree, mt), next_active_root| (tree, &mt[*next_active_root].children), &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),
);
let active_menu = if is_overlay { let active_menu = if is_overlay {
indices[0..indices.len().saturating_sub(1)] indices[0..indices.len().saturating_sub(1)].iter().fold(
.iter() roots,
.fold(roots, |mt, i| { |mt: &Vec<MenuTree<Message>>, i| {
&mt[i.expect("missing active child index in menu")].children &mt[i.expect("missing active child index in menu")].children
}) },
)
} else { } else {
// popup does one layer deep // popup does one layer deep
roots roots
@ -1637,7 +1656,20 @@ where
// dbg!("skipping duplicate", last_menu_state.index); // dbg!("skipping duplicate", last_menu_state.index);
// return (None, Captured); // return (None, Captured);
// } // }
dbg!(&last_menu_state.index, new_index); if last_menu_state
.index
.as_ref()
.is_some_and(|i| *i != new_index)
{
shell.publish((menu.on_surface_action.as_ref().unwrap())(
crate::surface::action::destroy_popup(
*state
.popup_id
.entry(menu.window_id)
.or_insert_with(window::Id::unique),
),
));
}
let item = &active_menu[new_index]; let item = &active_menu[new_index];
// dbg!(new_index); // dbg!(new_index);
@ -1654,7 +1686,7 @@ where
// set new index // set new index
let old_index = last_menu_state.index.replace(new_index); let old_index = last_menu_state.index.replace(new_index);
// dbg!(should_add); // dbg!(should_add);
dbg!(old_index, new_index);
// get new active item // get new active item
// * add new menu if the new item is a menu // * add new menu if the new item is a menu
if !item.children.is_empty() { if !item.children.is_empty() {
@ -1740,7 +1772,7 @@ where
(viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0); (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0);
(max_offset, min_offset) (max_offset, min_offset)
}; };
let menu_states = state.menu_states.get_mut(menu.depth).unwrap(); let menu_states = state.menu_states.get_mut(0).unwrap();
// update // update
if menu_states.is_empty() { if menu_states.is_empty() {