This commit is contained in:
Ashley Wulber 2025-06-10 11:05:29 -04:00
parent 3fd6c4f6bc
commit 3d84fb2a89
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
14 changed files with 203 additions and 177 deletions

View file

@ -177,7 +177,6 @@ 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),
)); ));
@ -236,8 +235,6 @@ impl cosmic::Application for App {
} }
fn header_start(&self) -> Vec<Element<Self::Message>> { fn header_start(&self) -> Vec<Element<Self::Message>> {
use cosmic::widget::menu::Tree;
vec![cosmic::widget::responsive_menu_bar().into_element( vec![cosmic::widget::responsive_menu_bar().into_element(
self.core(), self.core(),
&self.keybinds, &self.keybinds,
@ -300,6 +297,10 @@ impl cosmic::Application for App {
vec![ vec![
menu::Item::Button("hi 443", None, Action::Hi2), menu::Item::Button("hi 443", None, Action::Hi2),
menu::Item::Button("hi 4444", None, Action::Hi), menu::Item::Button("hi 4444", None, Action::Hi),
menu::Item::Button("hi 44444", None, Action::Hi3),
menu::Item::Button("hi 444445", None, Action::Hi3),
menu::Item::Button("hi 4444446", None, Action::Hi3),
menu::Item::Button("hi 44444447", None, Action::Hi3),
], ],
), ),
], ],

View file

@ -92,6 +92,7 @@ impl cosmic::Application for App {
|date| Message::DateSelected(date), |date| Message::DateSelected(date),
|| Message::PrevMonth, || Message::PrevMonth,
|| Message::NextMonth, || Message::NextMonth,
chrono::Weekday::Sun,
); );
content = content.push(calendar); content = content.push(calendar);

View file

@ -11,4 +11,12 @@ tracing-log = "0.2.0"
[dependencies.libcosmic] [dependencies.libcosmic]
path = "../../" path = "../../"
default-features = false default-features = false
features = ["debug", "winit", "tokio", "xdg-portal", "multi-window"] features = [
"debug",
"winit",
"tokio",
"xdg-portal",
"multi-window",
"surface-message",
"wayland",
]

View file

@ -93,7 +93,7 @@ impl cosmic::Application for App {
/// Creates a view after each update. /// Creates a view after each update.
fn view(&self) -> Element<Self::Message> { fn view(&self) -> Element<Self::Message> {
let widget = cosmic::widget::context_menu( let widget = cosmic::widget::context_menu(
cosmic::widget::button::text(&self.button_label).on_press(Message::Clicked), cosmic::widget::button::text(self.button_label.to_string()).on_press(Message::Clicked),
self.context_menu(), self.context_menu(),
); );

View file

@ -15,6 +15,7 @@ use cosmic::widget::menu::action::MenuAction;
use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::KeyBind;
use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::key_bind::Modifier;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
use cosmic::widget::RcElementWrapper;
use cosmic::{executor, Element}; use cosmic::{executor, Element};
/// Runs application with these settings /// Runs application with these settings
@ -155,7 +156,7 @@ impl cosmic::Application for App {
pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> { pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap<KeyBind, Action>) -> Element<'a, Message> {
menu::bar(vec![menu::Tree::with_children( menu::bar(vec![menu::Tree::with_children(
menu::root("File"), RcElementWrapper::new(Element::from(menu::root("File"))),
menu::items( menu::items(
key_binds, key_binds,
vec![ vec![

View file

@ -209,11 +209,11 @@ impl cosmic::Application for App {
if size.width < 600.0 { if size.width < 600.0 {
widget::compact_table(&self.table_model) widget::compact_table(&self.table_model)
.on_item_left_click(Message::ItemSelect) .on_item_left_click(Message::ItemSelect)
.item_context(|item| { .item_context(move |item| {
Some(widget::menu::items( Some(widget::menu::items(
&HashMap::new(), &HashMap::new(),
vec![widget::menu::Item::Button( vec![widget::menu::Item::Button(
format!("Action on {}", item.name), format!("Action on {}", item.name.to_string()),
None, None,
Action::None, Action::None,
)], )],

View file

@ -4,7 +4,7 @@
//! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. //! A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
use crate::widget::menu::{ use crate::widget::menu::{
self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_diff,
}; };
use derive_setters::Setters; use derive_setters::Setters;
use iced::touch::Finger; use iced::touch::Finger;
@ -13,8 +13,6 @@ 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;
use super::dropdown::menu::State;
/// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation. /// A context menu is a menu in a graphical user interface that appears upon user interaction, such as a right-click mouse operation.
pub fn context_menu<Message: 'static + Clone>( pub fn context_menu<Message: 'static + Clone>(
content: impl Into<crate::Element<'static, Message>> + 'static, content: impl Into<crate::Element<'static, Message>> + 'static,
@ -60,7 +58,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
tree::State::new(LocalState { tree::State::new(LocalState {
context_cursor: Point::default(), context_cursor: Point::default(),
fingers_pressed: Default::default(), fingers_pressed: Default::default(),
menu_state: Default::default(), menu_bar_state: Default::default(),
}) })
} }
@ -72,7 +70,6 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
// Assign the context menu's elements as this widget's children. // Assign the context menu's elements as this widget's children.
if let Some(ref context_menu) = self.context_menu { if let Some(ref context_menu) = self.context_menu {
let mut tree = Tree::empty(); let mut tree = Tree::empty();
tree.state = tree::State::new(MenuBarState::default());
tree.children = context_menu tree.children = context_menu
.iter() .iter()
.map(|root| { .map(|root| {
@ -95,6 +92,10 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
fn diff(&mut self, tree: &mut Tree) { fn diff(&mut self, tree: &mut Tree) {
tree.children[0].diff(self.content.as_widget_mut()); tree.children[0].diff(self.content.as_widget_mut());
let state = tree.state.downcast_mut::<LocalState>();
state.menu_bar_state.inner.with_data_mut(|inner| {
menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree);
});
// if let Some(ref mut context_menus) = self.context_menu { // if let Some(ref mut context_menus) = self.context_menu {
// for (menu, tree) in context_menus // for (menu, tree) in context_menus
@ -188,9 +189,9 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
&& (right_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 2)) && (right_button_released(&event) || (touch_lifted(&event) && fingers_pressed == 2))
{ {
state.context_cursor = cursor.position().unwrap_or_default(); state.context_cursor = cursor.position().unwrap_or_default();
let menu_state = tree.children[1].state.downcast_mut::<MenuBarState>(); let state = tree.state.downcast_mut::<LocalState>();
menu_state.inner.with_data_mut(|state| { state.menu_bar_state.inner.with_data_mut(|state| {
state.open = true; state.open = true;
state.view_cursor = cursor; state.view_cursor = cursor;
}); });
@ -219,18 +220,10 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
translation: Vector, translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_ref::<LocalState>(); let state = tree.state.downcast_ref::<LocalState>();
let menu_state = state.menu_state.clone();
let Some(context_menu) = self.context_menu.as_mut() else { let context_menu = self.context_menu.as_mut()?;
return None;
};
if !tree.children[1] if !state.menu_bar_state.inner.with_data(|state| state.open) {
.state
.downcast_ref::<MenuBarState>()
.inner
.with_data(|state| state.open)
{
return None; return None;
} }
@ -239,8 +232,8 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
bounds.y = state.context_cursor.y; bounds.y = state.context_cursor.y;
Some( Some(
crate::widget::menu::Menu { crate::widget::menu::Menu {
tree: menu_state, tree: state.menu_bar_state.clone(),
menu_roots: std::borrow::Cow::Borrowed(context_menu), menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
bounds_expand: 16, bounds_expand: 16,
menu_overlays_parent: true, menu_overlays_parent: true,
close_condition: CloseCondition { close_condition: CloseCondition {
@ -301,5 +294,5 @@ fn touch_lifted(event: &Event) -> bool {
pub struct LocalState { pub struct LocalState {
context_cursor: Point, context_cursor: Point,
fingers_pressed: HashSet<Finger>, fingers_pressed: HashSet<Finger>,
menu_state: MenuBarState, menu_bar_state: MenuBarState,
} }

View file

@ -19,7 +19,7 @@ use crate::{
}, },
}; };
use iced::{Point, Vector, window}; use iced::{Point, Shadow, Vector, window};
use iced_core::Border; use iced_core::Border;
use iced_widget::core::{ use iced_widget::core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
@ -429,9 +429,9 @@ where
layout.bounds(), layout.bounds(),
self.main_offset as f32, self.main_offset as f32,
); );
let anchor_rect = my_state.inner.with_data_mut(|state| { let (anchor_rect, gravity) = my_state.inner.with_data_mut(|state| {
state.popup_id.insert(self.window_id, id); state.popup_id.insert(self.window_id, id);
state (state
.menu_states .menu_states
.iter() .iter()
.find(|s| s.index.is_none()) .find(|s| s.index.is_none())
@ -452,19 +452,28 @@ where
width: r.width as i32, width: r.width as i32,
height: r.height as i32, height: r.height as i32,
}, },
) ), match (state.horizontal_direction, state.vertical_direction) {
(Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
(Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight,
(Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft,
(Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft,
})
}); });
let menu_node = popup_menu.layout(renderer, Limits::NONE.min_width(1.).min_height(1.)); let menu_node = popup_menu.layout(renderer, Limits::NONE.min_width(1.).min_height(1.));
let popup_size = menu_node.size(); let popup_size = menu_node.size();
let positioner = SctkPositioner { let positioner = SctkPositioner {
size: Some((popup_size.width.ceil() as u32 + 2, popup_size.height.ceil() as u32 + 2)), size: Some((
anchor_rect, popup_size.width.ceil() as u32 + 2,
anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft, popup_size.height.ceil() as u32 + 2,
gravity:cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, )),
reactive: true, anchor_rect,
..Default::default() anchor:
}; cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::BottomLeft,
gravity,
reactive: true,
..Default::default()
};
let parent = self.window_id; let parent = self.window_id;
shell.publish((surface_action)(crate::surface::action::simple_popup( shell.publish((surface_action)(crate::surface::action::simple_popup(
move || SctkPopupSettings { move || SctkPopupSettings {
@ -496,7 +505,7 @@ where
let state = tree.state.downcast_mut::<MenuBarState>(); let state = tree.state.downcast_mut::<MenuBarState>();
state state
.inner .inner
.with_data_mut(|inner| menu_roots_diff(&mut self.menu_roots, &mut inner.tree)) .with_data_mut(|inner| menu_roots_diff(&mut self.menu_roots, &mut inner.tree));
} }
fn tag(&self) -> tree::Tag { fn tag(&self) -> tree::Tag {
@ -550,10 +559,9 @@ where
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) -> event::Status {
use event::Event::{Mouse, Touch, Window}; use event::Event::{Mouse, Touch};
use mouse::{Button::Left, Event::ButtonReleased}; use mouse::{Button::Left, Event::ButtonReleased};
use touch::Event::{FingerLifted, FingerLost}; use touch::Event::{FingerLifted, FingerLost};
use window::Event::Focused;
let root_status = process_root_events( let root_status = process_root_events(
&mut self.menu_roots, &mut self.menu_roots,
@ -656,7 +664,7 @@ 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.get(0) { if let Some(active) = state.active_root.first() {
let active_bounds = layout let active_bounds = layout
.children() .children()
.nth(*active) .nth(*active)
@ -668,7 +676,7 @@ where
radius: styling.bar_border_radius.into(), radius: styling.bar_border_radius.into(),
..Default::default() ..Default::default()
}, },
shadow: Default::default(), shadow: Shadow::default(),
}; };
renderer.fill_quad(path_quad, styling.path); renderer.fill_quad(path_quad, styling.path);
@ -734,7 +742,7 @@ where
} }
} }
impl<'a, Message> From<MenuBar<Message>> for Element<'a, Message, crate::Theme, Renderer> impl<Message> From<MenuBar<Message>> for Element<'_, Message, crate::Theme, Renderer>
where where
Message: Clone + 'static, Message: Clone + 'static,
{ {

View file

@ -322,15 +322,13 @@ impl MenuState {
// self.menu_bounds.child_positions.len() // self.menu_bounds.child_positions.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;
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())
.zip(menu_tree[start_index..=end_index].iter()) .zip(menu_tree[start_index..=end_index].iter())
.enumerate() .map(|((cp, size), mt)| {
.map(|(i, ((cp, size), mt))| {
let mut position = *cp; let mut position = *cp;
let mut size = *size; let mut size = *size;
@ -343,7 +341,6 @@ impl MenuState {
} }
let limits = Limits::new(size, size); let limits = Limits::new(size, size);
mt.item mt.item
.layout(&mut tree[mt.index], renderer, &limits) .layout(&mut tree[mt.index], renderer, &limits)
@ -351,8 +348,7 @@ impl MenuState {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Node::with_children(children_bounds.size(), child_nodes) Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position())
.move_to(children_bounds.position())
} }
fn layout_single<Message>( fn layout_single<Message>(
@ -468,12 +464,18 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let empty = Vec::new(); let empty = Vec::new();
self.tree.inner.with_data_mut(|data| { self.tree.inner.with_data_mut(|data| {
if data.active_root.len() < self.depth + 1 || data.menu_states.len() < self.depth + 1 { if data.active_root.len() < self.depth + 1 || data.menu_states.len() < self.depth + 1 {
return Node::new(Size::ZERO); return Node::new(limits.min());
} }
let overlay_offset = Point::ORIGIN - position; let overlay_offset = Point::ORIGIN - position;
let tree_children = &mut data.tree.children; let tree_children: &mut Vec<Tree> = &mut data.tree.children;
let children = (if self.is_overlay {0} else {self.depth}..=self.depth)
let children = (if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {
data.active_root.len() - 1
} else {
self.depth
})
.map(|active_root| { .map(|active_root| {
if self.menu_roots.is_empty() { if self.menu_roots.is_empty() {
return (&empty, vec![]); return (&empty, vec![]);
@ -491,7 +493,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
}, },
); );
data.menu_states[if self.is_overlay {0} else {self.depth}..=self.depth].iter() data.menu_states[if self.is_overlay {0} else {self.depth}..=if self.is_overlay{data.active_root.len() - 1} else {self.depth}].iter()
.enumerate() .enumerate()
.filter(|ms| self.is_overlay || ms.0 < 1) .filter(|ms| self.is_overlay || ms.0 < 1)
.fold((roots, Vec::new()), |(menu_root, mut nodes), (_i, ms)| { .fold((roots, Vec::new()), |(menu_root, mut nodes), (_i, ms)| {
@ -509,6 +511,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let node_size = children_node.size(); let node_size = children_node.size();
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
@ -556,9 +559,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.with_data(|data| data.open || data.active_root.len() <= self.depth) .with_data(|data| data.open || data.active_root.len() <= self.depth)
{ {
return (None, Ignored); return (None, Ignored);
}; }
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();
let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset;
@ -634,7 +638,11 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.distance(view_cursor.position().unwrap_or_default()) .distance(view_cursor.position().unwrap_or_default())
< 2.0 < 2.0
{ {
let is_inside = state.menu_states[..=self.depth] let is_inside = state.menu_states[..=if self.is_overlay {
state.active_root.len().saturating_sub(1)
} else {
self.depth
}]
.iter() .iter()
.any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor)); .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor));
let mut needs_reset = false; let mut needs_reset = false;
@ -648,6 +656,11 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
needs_reset |= self.close_condition.click_outside && !is_inside; needs_reset |= self.close_condition.click_outside && !is_inside;
if needs_reset { if needs_reset {
#[cfg(all(
feature = "wayland",
feature = "winit",
feature = "surface-message"
))]
if let Some(handler) = self.on_surface_action.as_ref() { if let Some(handler) = self.on_surface_action.as_ref() {
let mut root = self.window_id; let mut root = self.window_id;
let mut depth = self.depth; let mut depth = self.depth;
@ -658,7 +671,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
if depth == 0 { if depth == 0 {
break; break;
} }
root = parent.0.clone(); root = *parent.0;
depth = depth.saturating_sub(1); depth = depth.saturating_sub(1);
} }
shell shell
@ -698,7 +711,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
if !state.open || state.active_root.len() <= self.depth { if !state.open || state.active_root.len() <= self.depth {
return; return;
} }
let active_root = &state.active_root[..=self.depth]; let active_root = &state.active_root[..=if self.is_overlay { 0 } else { self.depth }];
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();
@ -710,18 +723,16 @@ 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 let roots = active_root.iter().skip(1).fold(
.iter() &self.menu_roots[active_root[0]].children,
.skip(1) |mt, next_active_root| (&mt[*next_active_root].children),
.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),
);
let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>(); let indices = state.get_trimmed_indices(self.depth).collect::<Vec<_>>();
state.menu_states[if self.is_overlay {0} else {self.depth}..=self.depth] state.menu_states[if self.is_overlay { 0 } else { self.depth }..=if self.is_overlay {
state.menu_states.len() - 1
} else {
self.depth
}]
.iter() .iter()
.zip(layout.children()) .zip(layout.children())
.enumerate() .enumerate()
@ -729,7 +740,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.fold( .fold(
roots, roots,
|menu_roots: &Vec<MenuTree<Message>>, (i, (ms, children_layout))| { |menu_roots: &Vec<MenuTree<Message>>, (i, (ms, children_layout))| {
let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph { let draw_path = self.path_highlight.as_ref().is_some_and(|ph| match ph {
PathHighlight::Full => true, PathHighlight::Full => true,
PathHighlight::OmitActive => { PathHighlight::OmitActive => {
!indices.is_empty() && i < indices.len() - 1 !indices.is_empty() && i < indices.len() - 1
@ -738,7 +749,9 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
}); });
// react only to the last menu // react only to the last menu
if self.depth == state.active_root.len() - 1 { let view_cursor = if self.depth == state.active_root.len() - 1
|| i == state.menu_states.len() - 1
{
view_cursor view_cursor
} else { } else {
Cursor::Available([-1.0; 2].into()) Cursor::Available([-1.0; 2].into())
@ -796,7 +809,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.zip(children_layout.children()) .zip(children_layout.children())
.for_each(|(mt, clo)| { .for_each(|(mt, clo)| {
mt.item.draw( mt.item.draw(
&active_tree[mt.index], &state.tree.children[active_root[0]].children[mt.index],
r, r,
theme, theme,
style, style,
@ -858,8 +871,8 @@ impl<Message: Clone + 'static> overlay::Overlay<Message, crate::Theme, crate::Re
} }
} }
impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer> impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
for Menu<'a, Message> for Menu<'_, Message>
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
Size { Size {
@ -923,14 +936,7 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
.get(self.depth) .get(self.depth)
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
// let root_bounds_list = active_roots
// .into_iter()
// .fold(layout, |l, active_root| {
// l.children().nth(active_root).unwrap()
// })
// .children()
// .map(|c| c.bounds())
// .collect();
let root_bounds_list = layout let root_bounds_list = layout
.children() .children()
@ -939,7 +945,6 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
.children() .children()
.map(|lo| lo.bounds()) .map(|lo| lo.bounds())
.collect(); .collect();
// drop(state);
let mut popup_menu = Menu { let mut popup_menu = Menu {
tree: self.tree.clone(), tree: self.tree.clone(),
@ -979,8 +984,10 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
layout.bounds(), layout.bounds(),
self.main_offset as f32, self.main_offset as f32,
); );
let anchor_rect = self.tree.inner.with_data_mut(|state| { let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
state.menu_states.get(self.depth + 1) (state
.menu_states
.get(self.depth + 1)
.map(|s| s.menu_bounds.parent_bounds) .map(|s| s.menu_bounds.parent_bounds)
.map_or_else( .map_or_else(
|| { || {
@ -998,7 +1005,12 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
width: r.width as i32, width: r.width as i32,
height: r.height as i32, height: r.height as i32,
}, },
) ), match (state.horizontal_direction, state.vertical_direction) {
(Direction::Positive, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
(Direction::Positive, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopRight,
(Direction::Negative, Direction::Positive) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomLeft,
(Direction::Negative, Direction::Negative) => cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::TopLeft,
})
}); });
let menu_node = Widget::layout( let menu_node = Widget::layout(
@ -1013,7 +1025,7 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
size: Some((popup_size.width.ceil() as u32 + 2, popup_size.height.ceil() as u32 + 2)), size: Some((popup_size.width.ceil() as u32 + 2, popup_size.height.ceil() as u32 + 2)),
anchor_rect, anchor_rect,
anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight, anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::TopRight,
gravity:cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight, gravity,
reactive: true, reactive: true,
..Default::default() ..Default::default()
}; };
@ -1063,6 +1075,7 @@ fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
} }
} }
#[allow(clippy::too_many_arguments)]
pub(super) fn init_root_menu<Message: Clone>( pub(super) fn init_root_menu<Message: Clone>(
menu: &mut Menu<'_, Message>, menu: &mut Menu<'_, Message>,
renderer: &crate::Renderer, renderer: &crate::Renderer,
@ -1074,10 +1087,8 @@ pub(super) fn init_root_menu<Message: Clone>(
main_offset: f32, main_offset: f32,
) { ) {
menu.tree.inner.with_data_mut(|state| { menu.tree.inner.with_data_mut(|state| {
if !(state
.menu_states if !(state.menu_states.get(menu.depth).is_none()
.get(menu.depth)
.is_none()
&& (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
|| menu.depth > 0 || menu.depth > 0
|| !state.open || !state.open
@ -1130,8 +1141,7 @@ pub(super) fn init_root_menu<Message: Clone>(
menu.is_overlay, menu.is_overlay,
); );
set = true; set = true;
state.active_root.push(i);
state.active_root.insert(menu.depth, i);
let ms = MenuState { let ms = MenuState {
index: None, index: None,
scroll_offset: 0.0, scroll_offset: 0.0,
@ -1145,9 +1155,7 @@ pub(super) fn init_root_menu<Message: Clone>(
break; break;
} }
} }
if !set { debug_assert!(set, "Root not set");
panic!("huh");
}
}); });
} }
@ -1165,20 +1173,17 @@ pub(super) fn init_root_popup_menu<Message>(
Message: std::clone::Clone, Message: std::clone::Clone,
{ {
menu.tree.inner.with_data_mut(|state| { menu.tree.inner.with_data_mut(|state| {
if !(state if !(state.menu_states.get(menu.depth).is_none()
.menu_states
.get(menu.depth)
.is_none()
&& (!menu.is_overlay || bar_bounds.contains(overlay_cursor))) && (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
{ {
return; return;
} }
let active_roots = &state let active_roots = &state.active_root[..=menu.depth];
.active_root[..=menu.depth];
let mut set = false; let mut set = false;
let mt = active_roots.iter() let mt = active_roots
.iter()
.skip(1) .skip(1)
.fold(&menu.menu_roots[active_roots[0]], |mt, next_active_root| { .fold(&menu.menu_roots[active_roots[0]], |mt, next_active_root| {
&mt.children[*next_active_root] &mt.children[*next_active_root]
@ -1232,7 +1237,7 @@ pub(super) fn init_root_popup_menu<Message>(
// Hack to ensure menu opens properly // Hack to ensure menu opens properly
shell.invalidate_layout(); shell.invalidate_layout();
// non tree buttons arent active? // non tree buttons arent active?
assert!(set, "oops"); debug_assert!(set, "Root popup menu state was not set.");
}); });
} }
@ -1264,23 +1269,16 @@ fn process_menu_events<Message: std::clone::Clone>(
let Some(hover_index) = hover.index else { let Some(hover_index) = hover.index else {
return Status::Ignored; return Status::Ignored;
}; };
let (tree, mt) = state.active_root let mt = state.active_root.iter().skip(1).fold(
.iter() // then use menu states for each open menu
.skip(1) &mut menu_roots[state.active_root[0]],
.fold( |mt, next_active_root| &mut mt.children[*next_active_root],
// then use menu states for each open menu );
(
&mut state.tree.children[state.active_root[0]].children,
&mut menu_roots[state.active_root[0]],
),
|(tree, mt), next_active_root| (tree, &mut mt.children[*next_active_root]),
);
let mt = &mut mt.children[hover_index]; let mt = &mut mt.children[hover_index];
let tree = &mut tree[mt.index]; let tree = &mut state.tree.children[state.active_root[0]].children[mt.index];
// get layout // get layout
let child_node = hover.layout_single( let child_node = hover.layout_single(
@ -1333,18 +1331,14 @@ where
let mut new_menu_root = None; let mut new_menu_root = None;
menu.tree.inner.with_data_mut(|state| { menu.tree.inner.with_data_mut(|state| {
let active_root = &state.active_root[..=menu.depth];
if state.pressed {
return (new_menu_root, Ignored);
}
/* When overlay is running, cursor_position in any widget method will go negative /* When overlay is running, cursor_position in any widget method will go negative
but I still want Widget::draw() to react to cursor movement */ but I still want Widget::draw() to react to cursor movement */
state.view_cursor = view_cursor; state.view_cursor = view_cursor;
// * remove invalid menus // * remove invalid menus
let mut prev_bounds = std::iter::once(menu.bar_bounds) let mut prev_bounds = std::iter::once(menu.bar_bounds)
.chain( .chain(
if menu.is_overlay { if menu.is_overlay {
@ -1355,7 +1349,7 @@ where
.map(|s| s.menu_bounds.children_bounds), .map(|s| s.menu_bounds.children_bounds),
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if menu.is_overlay && menu.close_condition.leave { if menu.is_overlay && menu.close_condition.leave {
for i in (0..state.menu_states.len()).rev() { for i in (0..state.menu_states.len()).rev() {
let mb = &state.menu_states[i].menu_bounds; let mb = &state.menu_states[i].menu_bounds;
@ -1369,22 +1363,37 @@ where
break; break;
} }
prev_bounds.pop(); prev_bounds.pop();
state.active_root.pop();
state.menu_states.pop();
}
} else if menu.is_overlay {
for i in (0..state.menu_states.len()).rev() {
let mb = &state.menu_states[i].menu_bounds;
if mb.parent_bounds.contains(overlay_cursor)
|| mb.children_bounds.contains(overlay_cursor)
|| prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))
{
break;
}
prev_bounds.pop();
state.active_root.pop();
state.menu_states.pop(); state.menu_states.pop();
} }
} }
// * update active item // * update active item
let menu_states_len = state.menu_states.len(); let menu_states_len = state.menu_states.len();
let Some(last_menu_state) = state.menu_states.get_mut(if menu.is_overlay { let Some(last_menu_state) = state.menu_states.get_mut(if menu.is_overlay {
menu_states_len - 1 menu_states_len.saturating_sub(1)
} else { } else {
menu.depth menu.depth
}) else { }) else {
if menu.is_overlay { if menu.is_overlay {
// no menus left // no menus left
// TODO do we want to avoid this for popups? // TODO do we want to avoid this for popups?
state.active_root.remove(menu.depth); // 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
@ -1406,31 +1415,32 @@ where
|| menu.is_overlay && !last_children_bounds.contains(overlay_cursor) || menu.is_overlay && !last_children_bounds.contains(overlay_cursor)
// cursor is outside // cursor is outside
{ {
last_menu_state.index = None; last_menu_state.index = None;
return (new_menu_root, Captured); return (new_menu_root, Captured);
} }
// cursor is in the children part
// TODO set active root here even when not a tree.
// ensure that the
// calc new index // calc new index
let height_diff = (overlay_cursor.y let height_diff = (overlay_cursor.y
- (last_children_bounds.y + last_menu_state.scroll_offset)) - (last_children_bounds.y + last_menu_state.scroll_offset))
.clamp(0.0, last_children_bounds.height - 0.001); .clamp(0.0, last_children_bounds.height - 0.001);
let (active_tree, roots) = active_root let active_root = if menu.is_overlay {
.iter() &state.active_root
.skip(1) } else {
.fold( &state.active_root[..=menu.depth]
( };
&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 = roots; if state.pressed {
return (new_menu_root, Ignored);
}
let roots = active_root.iter().skip(1).fold(
&menu.menu_roots[active_root[0]].children,
|mt, next_active_root| &mt[*next_active_root].children,
);
let tree = &mut state.tree.children[active_root[0]].children;
let active_menu: &Vec<MenuTree<Message>> = roots;
let new_index = match menu.item_height { let new_index = match menu.item_height {
ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize, ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize,
ItemHeight::Static(_) | ItemHeight::Dynamic(_) => { ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
@ -1446,12 +1456,10 @@ where
} }
}; };
let remove = last_menu_state
let remove = !menu.is_overlay .index
&& last_menu_state .as_ref()
.index .is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
.as_ref()
.is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))] #[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
{ {
@ -1465,9 +1473,9 @@ where
} }
} }
let item = &active_menu[new_index]; let item = &active_menu[new_index];
// 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);
// 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() && old_index.is_none_or(|i| i != new_index) { if !item.children.is_empty() && old_index.is_none_or(|i| i != new_index) {
@ -1504,18 +1512,22 @@ where
&aod, &aod,
menu.bounds_expand, menu.bounds_expand,
item_bounds, item_bounds,
active_tree, tree,
menu.is_overlay, menu.is_overlay,
), ),
}; };
new_menu_root = Some((new_index, ms.clone())); new_menu_root = Some((new_index, ms.clone()));
state.menu_states.truncate(menu.depth + 1); if menu.is_overlay {
state.active_root.push(new_index);
} else {
state.menu_states.truncate(menu.depth + 1);
}
state.menu_states.push(ms); state.menu_states.push(ms);
} else if remove { } else if !menu.is_overlay && remove {
state.menu_states.truncate(menu.depth + 1); state.menu_states.truncate(menu.depth + 1);
} }
(new_menu_root, Captured) (new_menu_root, Captured)
}) })
} }
@ -1534,7 +1546,6 @@ where
use mouse::ScrollDelta; use mouse::ScrollDelta;
menu.tree.inner.with_data_mut(|state| { menu.tree.inner.with_data_mut(|state| {
let delta_y = match delta { let delta_y = match delta {
ScrollDelta::Lines { y, .. } => y * 60.0, ScrollDelta::Lines { y, .. } => y * 60.0,
ScrollDelta::Pixels { y, .. } => y, ScrollDelta::Pixels { y, .. } => y,

View file

@ -210,10 +210,11 @@ where
/// ///
/// # Returns /// # Returns
/// - A vector of `MenuTree`. /// - A vector of `MenuTree`.
#[must_use]
pub fn menu_items< pub fn menu_items<
A: MenuAction<Message = Message>, A: MenuAction<Message = Message>,
L: Into<Cow<'static, str>> + 'static, L: Into<Cow<'static, str>> + 'static,
Message: 'static + std::clone::Clone + std::fmt::Debug, Message: 'static + std::clone::Clone,
>( >(
key_binds: &HashMap<KeyBind, A>, key_binds: &HashMap<KeyBind, A>,
children: Vec<MenuItem<A, L>>, children: Vec<MenuItem<A, L>>,

View file

@ -1,7 +1,6 @@
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,
@ -10,6 +9,7 @@ use crate::{
use super::menu::{self, ItemHeight, ItemWidth}; use super::menu::{self, ItemHeight, ItemWidth};
#[must_use]
pub fn responsive_menu_bar() -> ResponsiveMenuBar { pub fn responsive_menu_bar() -> ResponsiveMenuBar {
ResponsiveMenuBar::default() ResponsiveMenuBar::default()
} }
@ -34,18 +34,21 @@ impl Default for ResponsiveMenuBar {
impl ResponsiveMenuBar { impl ResponsiveMenuBar {
/// Set the item width /// Set the item width
#[must_use]
pub fn item_width(mut self, item_width: ItemWidth) -> Self { pub fn item_width(mut self, item_width: ItemWidth) -> Self {
self.item_width = item_width; self.item_width = item_width;
self self
} }
/// Set the item height /// Set the item height
#[must_use]
pub fn item_height(mut self, item_height: ItemHeight) -> Self { pub fn item_height(mut self, item_height: ItemHeight) -> Self {
self.item_height = item_height; self.item_height = item_height;
self self
} }
/// Set the spacing /// Set the spacing
#[must_use]
pub fn spacing(mut self, spacing: f32) -> Self { pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing; self.spacing = spacing;
self self
@ -56,7 +59,7 @@ impl ResponsiveMenuBar {
/// Will panic if the menu bar collapses without tracking the size /// Will panic if the menu bar collapses without tracking the size
pub fn into_element< pub fn into_element<
'a, 'a,
Message: std::fmt::Debug + Clone + 'static, Message: Clone + 'static,
A: menu::Action<Message = Message> + Clone, A: menu::Action<Message = Message> + Clone,
S: Into<std::borrow::Cow<'static, str>> + 'static, S: Into<std::borrow::Cow<'static, str>> + 'static,
>( >(

View file

@ -20,9 +20,9 @@ pub type MultiSelectTableView<'a, Item, Category, Message> =
TableView<'a, MultiSelect, Item, Category, Message>; TableView<'a, MultiSelect, Item, Category, Message>;
pub type MultiSelectModel<Item, Category> = Model<MultiSelect, Item, Category>; pub type MultiSelectModel<Item, Category> = Model<MultiSelect, Item, Category>;
pub fn table<'a, SelectionMode, Item, Category, Message>( pub fn table<SelectionMode, Item, Category, Message>(
model: &'a Model<SelectionMode, Item, Category>, model: &Model<SelectionMode, Item, Category>,
) -> TableView<'a, SelectionMode, Item, Category, Message> ) -> TableView<'_, SelectionMode, Item, Category, Message>
where where
Message: Clone, Message: Clone,
SelectionMode: Default, SelectionMode: Default,
@ -33,9 +33,9 @@ where
TableView::new(model) TableView::new(model)
} }
pub fn compact_table<'a, SelectionMode, Item, Category, Message>( pub fn compact_table<SelectionMode, Item, Category, Message>(
model: &'a Model<SelectionMode, Item, Category>, model: &Model<SelectionMode, Item, Category>,
) -> CompactTableView<'a, SelectionMode, Item, Category, Message> ) -> CompactTableView<'_, SelectionMode, Item, Category, Message>
where where
Message: Clone, Message: Clone,
SelectionMode: Default, SelectionMode: Default,

View file

@ -47,9 +47,8 @@ where
pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<Message>>>>, pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<Message>>>>,
} }
impl<SelectionMode, Item, Category, Message> impl<'a, SelectionMode, Item, Category, Message>
From<CompactTableView<'static, SelectionMode, Item, Category, Message>> From<CompactTableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
for Element<'static, Message>
where where
Category: ItemCategory, Category: ItemCategory,
Item: ItemInterface<Category>, Item: ItemInterface<Category>,
@ -57,7 +56,7 @@ where
SelectionMode: Default, SelectionMode: Default,
Message: Clone + 'static, Message: Clone + 'static,
{ {
fn from(val: CompactTableView<'static, SelectionMode, Item, Category, Message>) -> Self { fn from(val: CompactTableView<'a, SelectionMode, Item, Category, Message>) -> Self {
let cosmic_theme::Spacing { space_xxxs, .. } = theme::spacing(); let cosmic_theme::Spacing { space_xxxs, .. } = theme::spacing();
val.model val.model
.iter() .iter()
@ -172,7 +171,7 @@ where
) )
.apply(Element::from) .apply(Element::from)
}) })
.collect::<Vec<Element<'static, Message>>>() .collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children) .apply(widget::column::with_children)
.spacing(val.item_spacing) .spacing(val.item_spacing)
.padding(val.element_padding) .padding(val.element_padding)

View file

@ -67,8 +67,8 @@ where
pub(super) category_context_builder: Box<dyn Fn(Category) -> Option<Vec<menu::Tree<Message>>>>, pub(super) category_context_builder: Box<dyn Fn(Category) -> Option<Vec<menu::Tree<Message>>>>,
} }
impl<SelectionMode, Item, Category, Message> impl<'a, SelectionMode, Item, Category, Message>
From<TableView<'static, SelectionMode, Item, Category, Message>> for Element<'static, Message> From<TableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
where where
Category: ItemCategory, Category: ItemCategory,
Item: ItemInterface<Category>, Item: ItemInterface<Category>,
@ -76,7 +76,7 @@ where
SelectionMode: Default, SelectionMode: Default,
Message: Clone + 'static, Message: Clone + 'static,
{ {
fn from(val: TableView<'static, SelectionMode, Item, Category, Message>) -> Self { fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self {
// Header row // Header row
let header_row = val let header_row = val
.model .model
@ -125,7 +125,7 @@ where
.apply(|mouse_area| widget::context_menu(mouse_area, cat_context_tree)) .apply(|mouse_area| widget::context_menu(mouse_area, cat_context_tree))
.apply(Element::from) .apply(Element::from)
}) })
.collect::<Vec<Element<'static, Message>>>() .collect::<Vec<Element<'a, Message>>>()
.apply(widget::row::with_children) .apply(widget::row::with_children)
.apply(Element::from); .apply(Element::from);
// Build the items // Build the items
@ -234,12 +234,12 @@ where
] ]
}) })
.flatten() .flatten()
.collect::<Vec<Element<'static, Message>>>() .collect::<Vec<Element<'a, Message>>>()
}; };
vec![vec![header_row], items_full] vec![vec![header_row], items_full]
.into_iter() .into_iter()
.flatten() .flatten()
.collect::<Vec<Element<'static, Message>>>() .collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children) .apply(widget::column::with_children)
.width(val.width) .width(val.width)
.height(val.height) .height(val.height)