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;
}
Message::Surface(a) => {
dbg!("got action...");
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(a),
));
@ -236,8 +235,6 @@ impl cosmic::Application for App {
}
fn header_start(&self) -> Vec<Element<Self::Message>> {
use cosmic::widget::menu::Tree;
vec![cosmic::widget::responsive_menu_bar().into_element(
self.core(),
&self.keybinds,
@ -300,6 +297,10 @@ impl cosmic::Application for App {
vec![
menu::Item::Button("hi 443", None, Action::Hi2),
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),
|| Message::PrevMonth,
|| Message::NextMonth,
chrono::Weekday::Sun,
);
content = content.push(calendar);

View file

@ -11,4 +11,12 @@ tracing-log = "0.2.0"
[dependencies.libcosmic]
path = "../../"
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.
fn view(&self) -> Element<Self::Message> {
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(),
);

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::Modifier;
use cosmic::widget::menu::{self, ItemHeight, ItemWidth};
use cosmic::widget::RcElementWrapper;
use cosmic::{executor, Element};
/// 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> {
menu::bar(vec![menu::Tree::with_children(
menu::root("File"),
RcElementWrapper::new(Element::from(menu::root("File"))),
menu::items(
key_binds,
vec![

View file

@ -209,11 +209,11 @@ impl cosmic::Application for App {
if size.width < 600.0 {
widget::compact_table(&self.table_model)
.on_item_left_click(Message::ItemSelect)
.item_context(|item| {
.item_context(move |item| {
Some(widget::menu::items(
&HashMap::new(),
vec![widget::menu::Item::Button(
format!("Action on {}", item.name),
format!("Action on {}", item.name.to_string()),
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.
use crate::widget::menu::{
self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight, menu_roots_diff,
};
use derive_setters::Setters;
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 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.
pub fn context_menu<Message: 'static + Clone>(
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 {
context_cursor: Point::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.
if let Some(ref context_menu) = self.context_menu {
let mut tree = Tree::empty();
tree.state = tree::State::new(MenuBarState::default());
tree.children = context_menu
.iter()
.map(|root| {
@ -95,6 +92,10 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
fn diff(&mut self, tree: &mut Tree) {
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 {
// 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))
{
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.view_cursor = cursor;
});
@ -219,18 +220,10 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
translation: Vector,
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
let state = tree.state.downcast_ref::<LocalState>();
let menu_state = state.menu_state.clone();
let Some(context_menu) = self.context_menu.as_mut() else {
return None;
};
let context_menu = self.context_menu.as_mut()?;
if !tree.children[1]
.state
.downcast_ref::<MenuBarState>()
.inner
.with_data(|state| state.open)
{
if !state.menu_bar_state.inner.with_data(|state| state.open) {
return None;
}
@ -239,8 +232,8 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
bounds.y = state.context_cursor.y;
Some(
crate::widget::menu::Menu {
tree: menu_state,
menu_roots: std::borrow::Cow::Borrowed(context_menu),
tree: state.menu_bar_state.clone(),
menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
bounds_expand: 16,
menu_overlays_parent: true,
close_condition: CloseCondition {
@ -301,5 +294,5 @@ fn touch_lifted(event: &Event) -> bool {
pub struct LocalState {
context_cursor: Point,
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_widget::core::{
Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event,
@ -429,9 +429,9 @@ where
layout.bounds(),
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
(state
.menu_states
.iter()
.find(|s| s.index.is_none())
@ -452,19 +452,28 @@ where
width: r.width 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 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::BottomLeft,
gravity:cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::BottomRight,
reactive: true,
..Default::default()
};
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::BottomLeft,
gravity,
reactive: true,
..Default::default()
};
let parent = self.window_id;
shell.publish((surface_action)(crate::surface::action::simple_popup(
move || SctkPopupSettings {
@ -496,7 +505,7 @@ where
let state = tree.state.downcast_mut::<MenuBarState>();
state
.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 {
@ -550,10 +559,9 @@ where
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
use event::Event::{Mouse, Touch, Window};
use event::Event::{Mouse, Touch};
use mouse::{Button::Left, Event::ButtonReleased};
use touch::Event::{FingerLifted, FingerLost};
use window::Event::Focused;
let root_status = process_root_events(
&mut self.menu_roots,
@ -656,7 +664,7 @@ where
// draw path highlight
if self.path_highlight.is_some() {
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
.children()
.nth(*active)
@ -668,7 +676,7 @@ where
radius: styling.bar_border_radius.into(),
..Default::default()
},
shadow: Default::default(),
shadow: Shadow::default(),
};
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
Message: Clone + 'static,
{

View file

@ -322,15 +322,13 @@ impl MenuState {
// self.menu_bounds.child_positions.len()
// );
// viewport space children bounds
let children_bounds = self.menu_bounds.children_bounds + overlay_offset;
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[start_index..=end_index].iter())
.enumerate()
.map(|(i, ((cp, size), mt))| {
.map(|((cp, size), mt)| {
let mut position = *cp;
let mut size = *size;
@ -343,7 +341,6 @@ impl MenuState {
}
let limits = Limits::new(size, size);
mt.item
.layout(&mut tree[mt.index], renderer, &limits)
@ -351,8 +348,7 @@ impl MenuState {
})
.collect::<Vec<_>>();
Node::with_children(children_bounds.size(), child_nodes)
.move_to(children_bounds.position())
Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position())
}
fn layout_single<Message>(
@ -468,12 +464,18 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
let empty = Vec::new();
self.tree.inner.with_data_mut(|data| {
if data.active_root.len() < self.depth + 1 || data.menu_states.len() < self.depth + 1 {
return Node::new(Size::ZERO);
if data.active_root.len() < self.depth + 1 || data.menu_states.len() < self.depth + 1 {
return Node::new(limits.min());
}
let overlay_offset = Point::ORIGIN - position;
let tree_children = &mut data.tree.children;
let children = (if self.is_overlay {0} else {self.depth}..=self.depth)
let tree_children: &mut Vec<Tree> = &mut data.tree.children;
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| {
if self.menu_roots.is_empty() {
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()
.filter(|ms| self.is_overlay || ms.0 < 1)
.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();
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
@ -556,9 +559,10 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.with_data(|data| data.open || data.active_root.len() <= self.depth)
{
return (None, Ignored);
};
}
let viewport = layout.bounds();
let viewport_size = viewport.size();
let overlay_offset = Point::ORIGIN - viewport.position();
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())
< 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()
.any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor));
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;
if needs_reset {
#[cfg(all(
feature = "wayland",
feature = "winit",
feature = "surface-message"
))]
if let Some(handler) = self.on_surface_action.as_ref() {
let mut root = self.window_id;
let mut depth = self.depth;
@ -658,7 +671,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
if depth == 0 {
break;
}
root = parent.0.clone();
root = *parent.0;
depth = depth.saturating_sub(1);
}
shell
@ -698,7 +711,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
if !state.open || state.active_root.len() <= self.depth {
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_size = viewport.size();
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 (active_tree, roots) = active_root
.iter()
.skip(1)
.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 roots = active_root.iter().skip(1).fold(
&self.menu_roots[active_root[0]].children,
|mt, next_active_root| (&mt[*next_active_root].children),
);
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()
.zip(layout.children())
.enumerate()
@ -729,7 +740,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.fold(
roots,
|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::OmitActive => {
!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
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
} else {
Cursor::Available([-1.0; 2].into())
@ -796,7 +809,7 @@ impl<'b, Message: Clone + 'static> Menu<'b, Message> {
.zip(children_layout.children())
.for_each(|(mt, clo)| {
mt.item.draw(
&active_tree[mt.index],
&state.tree.children[active_root[0]].children[mt.index],
r,
theme,
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>
for Menu<'a, Message>
impl<Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
for Menu<'_, Message>
{
fn size(&self) -> Size<Length> {
Size {
@ -923,14 +936,7 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
.get(self.depth)
.cloned()
.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
.children()
@ -939,7 +945,6 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
.children()
.map(|lo| lo.bounds())
.collect();
// drop(state);
let mut popup_menu = Menu {
tree: self.tree.clone(),
@ -979,8 +984,10 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
layout.bounds(),
self.main_offset as f32,
);
let anchor_rect = self.tree.inner.with_data_mut(|state| {
state.menu_states.get(self.depth + 1)
let (anchor_rect, gravity) = self.tree.inner.with_data_mut(|state| {
(state
.menu_states
.get(self.depth + 1)
.map(|s| s.menu_bounds.parent_bounds)
.map_or_else(
|| {
@ -998,7 +1005,12 @@ impl<'a, Message: std::clone::Clone + 'static> Widget<Message, crate::Theme, cra
width: r.width 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(
@ -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)),
anchor_rect,
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,
..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>(
menu: &mut Menu<'_, Message>,
renderer: &crate::Renderer,
@ -1074,10 +1087,8 @@ pub(super) fn init_root_menu<Message: Clone>(
main_offset: f32,
) {
menu.tree.inner.with_data_mut(|state| {
if !(state
.menu_states
.get(menu.depth)
.is_none()
if !(state.menu_states.get(menu.depth).is_none()
&& (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
|| menu.depth > 0
|| !state.open
@ -1130,8 +1141,7 @@ pub(super) fn init_root_menu<Message: Clone>(
menu.is_overlay,
);
set = true;
state.active_root.insert(menu.depth, i);
state.active_root.push(i);
let ms = MenuState {
index: None,
scroll_offset: 0.0,
@ -1145,9 +1155,7 @@ pub(super) fn init_root_menu<Message: Clone>(
break;
}
}
if !set {
panic!("huh");
}
debug_assert!(set, "Root not set");
});
}
@ -1165,20 +1173,17 @@ pub(super) fn init_root_popup_menu<Message>(
Message: std::clone::Clone,
{
menu.tree.inner.with_data_mut(|state| {
if !(state
.menu_states
.get(menu.depth)
.is_none()
if !(state.menu_states.get(menu.depth).is_none()
&& (!menu.is_overlay || bar_bounds.contains(overlay_cursor)))
{
return;
}
let active_roots = &state
.active_root[..=menu.depth];
let active_roots = &state.active_root[..=menu.depth];
let mut set = false;
let mt = active_roots.iter()
let mt = active_roots
.iter()
.skip(1)
.fold(&menu.menu_roots[active_roots[0]], |mt, 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
shell.invalidate_layout();
// 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 {
return Status::Ignored;
};
};
let (tree, mt) = state.active_root
.iter()
.skip(1)
.fold(
// 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 = state.active_root.iter().skip(1).fold(
// then use menu states for each open menu
&mut menu_roots[state.active_root[0]],
|mt, next_active_root| &mut mt.children[*next_active_root],
);
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
let child_node = hover.layout_single(
@ -1333,18 +1331,14 @@ where
let mut new_menu_root = None;
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
but I still want Widget::draw() to react to cursor movement */
state.view_cursor = view_cursor;
// * remove invalid menus
let mut prev_bounds = std::iter::once(menu.bar_bounds)
.chain(
if menu.is_overlay {
@ -1355,7 +1349,7 @@ where
.map(|s| s.menu_bounds.children_bounds),
)
.collect::<Vec<_>>();
if menu.is_overlay && menu.close_condition.leave {
for i in (0..state.menu_states.len()).rev() {
let mb = &state.menu_states[i].menu_bounds;
@ -1369,22 +1363,37 @@ where
break;
}
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();
}
}
// * update active item
let menu_states_len = state.menu_states.len();
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 {
menu.depth
}) else {
if menu.is_overlay {
// no menus left
// 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
// 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)
// cursor is outside
{
last_menu_state.index = None;
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
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_tree, roots) = active_root
.iter()
.skip(1)
.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),
);
let active_root = if menu.is_overlay {
&state.active_root
} else {
&state.active_root[..=menu.depth]
};
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 {
ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize,
ItemHeight::Static(_) | ItemHeight::Dynamic(_) => {
@ -1446,12 +1456,10 @@ where
}
};
let remove = !menu.is_overlay
&& last_menu_state
.index
.as_ref()
.is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
let remove = last_menu_state
.index
.as_ref()
.is_some_and(|i| *i != new_index && !active_menu[*i].children.is_empty());
#[cfg(all(feature = "wayland", feature = "winit", feature = "surface-message"))]
{
@ -1465,9 +1473,9 @@ where
}
}
let item = &active_menu[new_index];
// set new index
let old_index = last_menu_state.index.replace(new_index);
// get new active item
// * add new menu if the new item is a menu
if !item.children.is_empty() && old_index.is_none_or(|i| i != new_index) {
@ -1504,18 +1512,22 @@ where
&aod,
menu.bounds_expand,
item_bounds,
active_tree,
tree,
menu.is_overlay,
),
};
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);
} else if remove {
} else if !menu.is_overlay && remove {
state.menu_states.truncate(menu.depth + 1);
}
(new_menu_root, Captured)
})
}
@ -1534,7 +1546,6 @@ where
use mouse::ScrollDelta;
menu.tree.inner.with_data_mut(|state| {
let delta_y = match delta {
ScrollDelta::Lines { y, .. } => y * 60.0,
ScrollDelta::Pixels { y, .. } => y,

View file

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

View file

@ -1,7 +1,6 @@
use std::collections::HashMap;
use apply::Apply;
use iced::window;
use crate::{
Core, Element,
@ -10,6 +9,7 @@ use crate::{
use super::menu::{self, ItemHeight, ItemWidth};
#[must_use]
pub fn responsive_menu_bar() -> ResponsiveMenuBar {
ResponsiveMenuBar::default()
}
@ -34,18 +34,21 @@ impl Default for ResponsiveMenuBar {
impl ResponsiveMenuBar {
/// Set the item width
#[must_use]
pub fn item_width(mut self, item_width: ItemWidth) -> Self {
self.item_width = item_width;
self
}
/// Set the item height
#[must_use]
pub fn item_height(mut self, item_height: ItemHeight) -> Self {
self.item_height = item_height;
self
}
/// Set the spacing
#[must_use]
pub fn spacing(mut self, spacing: f32) -> Self {
self.spacing = spacing;
self
@ -56,7 +59,7 @@ impl ResponsiveMenuBar {
/// Will panic if the menu bar collapses without tracking the size
pub fn into_element<
'a,
Message: std::fmt::Debug + Clone + 'static,
Message: Clone + 'static,
A: menu::Action<Message = Message> + Clone,
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>;
pub type MultiSelectModel<Item, Category> = Model<MultiSelect, Item, Category>;
pub fn table<'a, SelectionMode, Item, Category, Message>(
model: &'a Model<SelectionMode, Item, Category>,
) -> TableView<'a, SelectionMode, Item, Category, Message>
pub fn table<SelectionMode, Item, Category, Message>(
model: &Model<SelectionMode, Item, Category>,
) -> TableView<'_, SelectionMode, Item, Category, Message>
where
Message: Clone,
SelectionMode: Default,
@ -33,9 +33,9 @@ where
TableView::new(model)
}
pub fn compact_table<'a, SelectionMode, Item, Category, Message>(
model: &'a Model<SelectionMode, Item, Category>,
) -> CompactTableView<'a, SelectionMode, Item, Category, Message>
pub fn compact_table<SelectionMode, Item, Category, Message>(
model: &Model<SelectionMode, Item, Category>,
) -> CompactTableView<'_, SelectionMode, Item, Category, Message>
where
Message: Clone,
SelectionMode: Default,

View file

@ -47,9 +47,8 @@ where
pub(super) item_context_builder: Box<dyn Fn(&Item) -> Option<Vec<menu::Tree<Message>>>>,
}
impl<SelectionMode, Item, Category, Message>
From<CompactTableView<'static, SelectionMode, Item, Category, Message>>
for Element<'static, Message>
impl<'a, SelectionMode, Item, Category, Message>
From<CompactTableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
where
Category: ItemCategory,
Item: ItemInterface<Category>,
@ -57,7 +56,7 @@ where
SelectionMode: Default,
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();
val.model
.iter()
@ -172,7 +171,7 @@ where
)
.apply(Element::from)
})
.collect::<Vec<Element<'static, Message>>>()
.collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children)
.spacing(val.item_spacing)
.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>>>>,
}
impl<SelectionMode, Item, Category, Message>
From<TableView<'static, SelectionMode, Item, Category, Message>> for Element<'static, Message>
impl<'a, SelectionMode, Item, Category, Message>
From<TableView<'a, SelectionMode, Item, Category, Message>> for Element<'a, Message>
where
Category: ItemCategory,
Item: ItemInterface<Category>,
@ -76,7 +76,7 @@ where
SelectionMode: Default,
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
let header_row = val
.model
@ -125,7 +125,7 @@ where
.apply(|mouse_area| widget::context_menu(mouse_area, cat_context_tree))
.apply(Element::from)
})
.collect::<Vec<Element<'static, Message>>>()
.collect::<Vec<Element<'a, Message>>>()
.apply(widget::row::with_children)
.apply(Element::from);
// Build the items
@ -234,12 +234,12 @@ where
]
})
.flatten()
.collect::<Vec<Element<'static, Message>>>()
.collect::<Vec<Element<'a, Message>>>()
};
vec![vec![header_row], items_full]
.into_iter()
.flatten()
.collect::<Vec<Element<'static, Message>>>()
.collect::<Vec<Element<'a, Message>>>()
.apply(widget::column::with_children)
.width(val.width)
.height(val.height)