This commit is contained in:
Ashley Wulber 2025-04-25 00:28:08 -04:00
parent 4fcd09d690
commit 9e9449d302
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
5 changed files with 649 additions and 191 deletions

View file

@ -8,7 +8,7 @@ use crate::widget::menu::{
}; };
use derive_setters::Setters; use derive_setters::Setters;
use iced::touch::Finger; use iced::touch::Finger;
use iced::{Event, Vector}; use iced::{Event, Vector, window};
use iced_core::widget::{Tree, Widget, tree}; use iced_core::widget::{Tree, Widget, tree};
use iced_core::{Length, Point, Size, event, mouse, touch}; use iced_core::{Length, Point, Size, event, mouse, touch};
use std::collections::HashSet; use std::collections::HashSet;
@ -257,6 +257,9 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
path_highlight: Some(PathHighlight::MenuActive), path_highlight: Some(PathHighlight::MenuActive),
style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
position: Point::new(translation.x, translation.y), position: Point::new(translation.x, translation.y),
is_overlay: true,
window_id: window::Id::NONE,
depth: 0,
} }
.overlay(), .overlay(),
) )

View file

@ -1,6 +1,8 @@
// From iced_aw, license MIT // From iced_aw, license MIT
//! A widget that handles menu trees //! A widget that handles menu trees
use std::collections::HashMap;
use super::{ use super::{
menu_inner::{ menu_inner::{
CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight, CloseCondition, Direction, ItemHeight, ItemWidth, Menu, MenuState, PathHighlight,
@ -43,25 +45,30 @@ pub(crate) struct MenuBarState {
pub(crate) struct MenuBarStateInner { pub(crate) struct MenuBarStateInner {
pub(crate) tree: Tree, pub(crate) tree: Tree,
pub(crate) popup_id: HashMap<window::Id, window::Id>,
pub(crate) pressed: bool, pub(crate) pressed: bool,
pub(crate) bar_pressed: bool,
pub(crate) view_cursor: Cursor, pub(crate) view_cursor: Cursor,
pub(crate) open: bool, pub(crate) open: bool,
pub(crate) active_root: Option<usize>, pub(crate) active_root: HashMap<window::Id, Vec<usize>>,
pub(crate) horizontal_direction: Direction, pub(crate) horizontal_direction: Direction,
pub(crate) vertical_direction: Direction, pub(crate) vertical_direction: Direction,
pub(crate) menu_states: Vec<MenuState>, pub(crate) menu_states: HashMap<window::Id, Vec<MenuState>>,
} }
impl MenuBarStateInner { impl MenuBarStateInner {
pub(super) fn get_trimmed_indices(&self) -> impl Iterator<Item = usize> + '_ { pub(super) fn get_trimmed_indices(&self, id: &window::Id) -> impl Iterator<Item = usize> + '_ {
self.menu_states self.menu_states
.iter() .get(id)
.into_iter()
.map(|v| v.iter())
.flatten()
.take_while(|ms| ms.index.is_some()) .take_while(|ms| ms.index.is_some())
.map(|ms| ms.index.expect("No indices were found in the menu state.")) .map(|ms| ms.index.expect("No indices were found in the menu state."))
} }
pub(super) fn reset(&mut self) { pub(super) fn reset(&mut self) {
self.open = false; self.open = false;
self.active_root = None; self.active_root = HashMap::new();
self.menu_states.clear(); self.menu_states.clear();
} }
} }
@ -72,10 +79,12 @@ impl Default for MenuBarStateInner {
pressed: false, pressed: false,
view_cursor: Cursor::Available([-0.5, -0.5].into()), view_cursor: Cursor::Available([-0.5, -0.5].into()),
open: false, open: false,
active_root: None, active_root: HashMap::new(),
horizontal_direction: Direction::Positive, horizontal_direction: Direction::Positive,
vertical_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<Message> {
window_id: window::Id, window_id: window::Id,
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
pub(crate) on_surface_action: Option<Box<dyn Fn(crate::surface::Action) -> Message>>,
} }
impl<Message> MenuBar<Message> impl<Message> MenuBar<Message>
@ -182,7 +192,13 @@ where
pub fn new(menu_roots: Vec<MenuTree<Message>>) -> Self { pub fn new(menu_roots: Vec<MenuTree<Message>>) -> Self {
let mut menu_roots = menu_roots; let mut menu_roots = menu_roots;
menu_roots.iter_mut().for_each(MenuTree::set_index); 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 { Self {
width: Length::Shrink, width: Length::Shrink,
height: Length::Shrink, height: Length::Shrink,
@ -204,6 +220,7 @@ where
window_id: window::Id::NONE, window_id: window::Id::NONE,
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
on_surface_action: None,
} }
} }
@ -315,6 +332,14 @@ where
} }
self 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<Message> Widget<Message, crate::Theme, Renderer> for MenuBar<Message> impl<Message> Widget<Message, crate::Theme, Renderer> for MenuBar<Message>
where where
@ -325,7 +350,10 @@ where
} }
fn diff(&mut self, tree: &mut Tree) { fn diff(&mut self, tree: &mut Tree) {
menu_roots_diff(&mut self.menu_roots, tree); let state = tree.state.downcast_mut::<MenuBarState>();
state
.inner
.with_data_mut(|inner| menu_roots_diff(&mut self.menu_roots, &mut inner.tree))
} }
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -394,21 +422,38 @@ where
viewport, viewport,
); );
let state = tree.state.downcast_mut::<MenuBarState>(); let my_state = tree.state.downcast_mut::<MenuBarState>();
match event { // XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => { let reset = self.window_id != window::Id::NONE && my_state.inner.with_data(|d| d.open);
state.inner.with_data_mut(|state| {
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()) { if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) {
state.view_cursor = view_cursor; state.view_cursor = view_cursor;
state.open = true; state.open = true;
// #[cfg(feature = "wayland")] // #[cfg(feature = "wayland")]
// TODO emit Message to open menu // TODO emit Message to open menu
} }
}); }
_ => (),
} }
_ => (), });
}
root_status root_status
} }
@ -434,10 +479,14 @@ where
// draw path highlight // draw path highlight
if self.path_highlight.is_some() { if self.path_highlight.is_some() {
let styling = theme.appearance(&self.style); 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 let active_bounds = layout
.children() .children()
.nth(active) .nth(*active)
.expect("Active child not found in menu?") .expect("Active child not found in menu?")
.bounds(); .bounds();
let path_quad = renderer::Quad { let path_quad = renderer::Quad {
@ -489,7 +538,7 @@ where
Some( Some(
Menu { Menu {
tree: state.clone(), 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, bounds_expand: self.bounds_expand,
menu_overlays_parent: false, menu_overlays_parent: false,
close_condition: self.close_condition, close_condition: self.close_condition,
@ -502,6 +551,9 @@ where
path_highlight: self.path_highlight, path_highlight: self.path_highlight,
style: std::borrow::Cow::Borrowed(&self.style), style: std::borrow::Cow::Borrowed(&self.style),
position: Point::new(translation.x, translation.y), position: Point::new(translation.x, translation.y),
is_overlay: false,
window_id: window::Id::NONE,
depth: 0,
} }
.overlay(), .overlay(),
) )

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use apply::Apply; use apply::Apply;
use iced::window;
use crate::{ use crate::{
Core, Element, Core, Element,
@ -80,17 +81,23 @@ impl ResponsiveMenuBar {
menu::bar( menu::bar(
trees trees
.into_iter() .into_iter()
.map(|mt| { .map(
menu::Tree::<_>::with_children( |mt: (
menu::root(mt.0), std::borrow::Cow<'_, str>,
menu::items(key_binds, mt.1.into()), Vec<menu::Item<A, std::borrow::Cow<'_, str>>>,
) )| {
}) menu::Tree::<_>::with_children(
menu::root(mt.0),
menu::items(key_binds, mt.1.into()),
)
},
)
.collect(), .collect(),
) )
.item_width(self.item_width) .item_width(self.item_width)
.item_height(self.item_height) .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}")), crate::widget::Id::new(format!("menu_bar_expanded_{id}")),
), ),
id, id,
@ -123,7 +130,8 @@ impl ResponsiveMenuBar {
)]) )])
.item_height(self.item_height) .item_height(self.item_height)
.item_width(self.collapsed_item_width) .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}")), crate::widget::Id::new(format!("menu_bar_collapsed_{id}")),
), ),
id, id,

View file

@ -17,7 +17,7 @@ use iced::clipboard::mime::AllowedMimeTypes;
use iced::touch::Finger; use iced::touch::Finger;
use iced::{ use iced::{
Alignment, Background, Color, Event, Length, Padding, Rectangle, Size, Task, Vector, alignment, 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::mouse::ScrollDelta;
use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping}; use iced_core::text::{LineHeight, Renderer as TextRenderer, Shaping, Wrapping};
@ -1615,6 +1615,9 @@ where
path_highlight: Some(PathHighlight::MenuActive), path_highlight: Some(PathHighlight::MenuActive),
style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
position: Point::new(translation.x, translation.y), position: Point::new(translation.x, translation.y),
is_overlay: true,
window_id: window::Id::NONE,
depth: 0,
} }
.overlay(), .overlay(),
) )