From 4fcd09d69088b2658ebb754b10a135759dd6f5f5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 24 Apr 2025 18:24:10 -0400 Subject: [PATCH] wip --- src/widget/context_menu.rs | 51 +- src/widget/menu/flex.rs | 171 +++- src/widget/menu/menu_bar.rs | 219 +++-- src/widget/menu/menu_inner.rs | 1124 ++++++++++++------------- src/widget/menu/menu_tree.rs | 105 ++- src/widget/nav_bar.rs | 2 +- src/widget/segmented_button/widget.rs | 37 +- src/widget/table/widget/compact.rs | 15 +- src/widget/table/widget/standard.rs | 25 +- src/widget/wrapper.rs | 13 + 10 files changed, 999 insertions(+), 763 deletions(-) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index 87122029..79b216a0 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -13,17 +13,19 @@ 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<'a, Message: 'a>( - content: impl Into> + 'a, +pub fn context_menu( + content: impl Into> + 'static, // on_context: Message, - context_menu: Option>>, -) -> ContextMenu<'a, Message> { + context_menu: Option>>, +) -> ContextMenu<'static, Message> { let mut this = ContextMenu { content: content.into(), context_menu: context_menu.map(|menus| { vec![menu::Tree::with_children( - crate::widget::row::<'static, Message>(), + crate::Element::from(crate::widget::row::<'static, Message>()), menus, )] }), @@ -43,10 +45,12 @@ pub struct ContextMenu<'a, Message> { #[setters(skip)] content: crate::Element<'a, Message>, #[setters(skip)] - context_menu: Option>>, + context_menu: Option>>, } -impl Widget for ContextMenu<'_, Message> { +impl Widget + for ContextMenu<'_, Message> +{ fn tag(&self) -> tree::Tag { tree::Tag::of::() } @@ -56,6 +60,7 @@ impl Widget for ContextM tree::State::new(LocalState { context_cursor: Point::default(), fingers_pressed: Default::default(), + menu_state: Default::default(), }) } @@ -75,7 +80,7 @@ impl Widget for ContextM let flat = root .flattern() .iter() - .map(|mt| Tree::new(mt.item.as_widget())) + .map(|mt| Tree::new(mt.item.clone())) .collect(); tree.children = flat; tree @@ -183,10 +188,12 @@ impl Widget for ContextM && (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::(); - menu_state.open = true; - menu_state.view_cursor = cursor; + + menu_state.inner.with_data_mut(|state| { + state.open = true; + state.view_cursor = cursor; + }); return event::Status::Captured; } @@ -212,23 +219,28 @@ impl Widget for ContextM translation: Vector, ) -> Option> { let state = tree.state.downcast_ref::(); + let menu_state = state.menu_state.clone(); let Some(context_menu) = self.context_menu.as_mut() else { return None; }; - if !tree.children[1].state.downcast_ref::().open { + if !tree.children[1] + .state + .downcast_ref::() + .inner + .with_data(|state| state.open) + { return None; } let mut bounds = layout.bounds(); bounds.x = state.context_cursor.x; bounds.y = state.context_cursor.y; - Some( crate::widget::menu::Menu { - tree: &mut tree.children[1], - menu_roots: context_menu, + tree: menu_state, + menu_roots: std::borrow::Cow::Borrowed(context_menu), bounds_expand: 16, menu_overlays_parent: true, close_condition: CloseCondition { @@ -243,7 +255,7 @@ impl Widget for ContextM cross_offset: 0, root_bounds_list: vec![bounds], path_highlight: Some(PathHighlight::MenuActive), - style: &crate::theme::menu_bar::MenuBarStyle::Default, + style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), position: Point::new(translation.x, translation.y), } .overlay(), @@ -263,8 +275,10 @@ impl Widget for ContextM } } -impl<'a, Message: Clone + 'a> From> for crate::Element<'a, Message> { - fn from(widget: ContextMenu<'a, Message>) -> Self { +impl<'a, Message: Clone + 'static> From> + for crate::Element<'static, Message> +{ + fn from(widget: ContextMenu<'static, Message>) -> Self { Self::new(widget) } } @@ -283,4 +297,5 @@ fn touch_lifted(event: &Event) -> bool { pub struct LocalState { context_cursor: Point, fingers_pressed: HashSet, + menu_state: MenuBarState, } diff --git a/src/widget/menu/flex.rs b/src/widget/menu/flex.rs index c093e802..5eaf3d94 100644 --- a/src/widget/menu/flex.rs +++ b/src/widget/menu/flex.rs @@ -1,12 +1,14 @@ // From iced_aw, license MIT -use iced_core::widget::Tree; +use iced_core::{Widget, widget::Tree}; use iced_widget::core::{ Alignment, Element, Padding, Point, Size, layout::{Limits, Node}, renderer, }; +use crate::widget::RcElementWrapper; + /// The main axis of a flex layout. #[derive(Debug)] pub enum Axis { @@ -217,3 +219,170 @@ where Node::with_children(size.expand(padding), nodes) } + +/// Computes the flex layout with the given axis and limits, applying spacing, +/// padding and alignment to the items as needed. +/// +/// It returns a new layout [`Node`]. +pub fn resolve_wrapper<'a, Message>( + axis: &Axis, + renderer: &crate::Renderer, + limits: &Limits, + padding: Padding, + spacing: f32, + align_items: Alignment, + items: &[&RcElementWrapper], + tree: &mut [&mut Tree], +) -> Node { + let limits = limits.shrink(padding); + let total_spacing = spacing * items.len().saturating_sub(1) as f32; + let max_cross = axis.cross(limits.max()); + + let mut fill_sum = 0; + let mut cross = axis.cross(limits.min()).max(axis.cross(Size::INFINITY)); + let mut available = axis.main(limits.max()) - total_spacing; + + let mut nodes: Vec = Vec::with_capacity(items.len()); + nodes.resize(items.len(), Node::default()); + + if align_items == Alignment::Center { + let mut fill_cross = axis.cross(limits.min()); + + for (child, tree) in items.iter().zip(tree.iter_mut()) { + let c_size = child.size(); + let cross_fill_factor = match axis { + Axis::Horizontal => c_size.height, + Axis::Vertical => c_size.width, + } + .fill_factor(); + + if cross_fill_factor == 0 { + let (max_width, max_height) = axis.pack(available, max_cross); + + let child_limits = Limits::new(Size::ZERO, Size::new(max_width, max_height)); + + let layout = child.layout(tree, renderer, &child_limits); + let size = layout.size(); + + fill_cross = fill_cross.max(axis.cross(size)); + } + } + + cross = fill_cross; + } + + for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { + let c_size = child.size(); + let fill_factor = match axis { + Axis::Horizontal => c_size.width, + Axis::Vertical => c_size.height, + } + .fill_factor(); + + if fill_factor == 0 { + let (min_width, min_height) = if align_items == Alignment::Center { + axis.pack(0.0, cross) + } else { + axis.pack(0.0, 0.0) + }; + + let (max_width, max_height) = if align_items == Alignment::Center { + axis.pack(available, cross) + } else { + axis.pack(available, max_cross) + }; + + let child_limits = Limits::new( + Size::new(min_width, min_height), + Size::new(max_width, max_height), + ); + + let layout = child.layout(tree, renderer, &child_limits); + let size = layout.size(); + + available -= axis.main(size); + + if align_items != Alignment::Center { + cross = cross.max(axis.cross(size)); + } + + nodes[i] = layout; + } else { + fill_sum += fill_factor; + } + } + + let remaining = available.max(0.0); + + for (i, (child, tree)) in items.iter().zip(tree.iter_mut()).enumerate() { + let c_size = child.size(); + let fill_factor = match axis { + Axis::Horizontal => c_size.width, + Axis::Vertical => c_size.height, + } + .fill_factor(); + + if fill_factor != 0 { + let max_main = remaining * f32::from(fill_factor) / f32::from(fill_sum); + let min_main = if max_main.is_infinite() { + 0.0 + } else { + max_main + }; + + let (min_width, min_height) = if align_items == Alignment::Center { + axis.pack(min_main, cross) + } else { + axis.pack(min_main, axis.cross(limits.min())) + }; + + let (max_width, max_height) = if align_items == Alignment::Center { + axis.pack(max_main, cross) + } else { + axis.pack(max_main, max_cross) + }; + + let child_limits = Limits::new( + Size::new(min_width, min_height), + Size::new(max_width, max_height), + ); + + let layout = child.layout(tree, renderer, &child_limits); + + if align_items != Alignment::Center { + cross = cross.max(axis.cross(layout.size())); + } + + nodes[i] = layout; + } + } + + let pad = axis.pack(padding.left, padding.top); + let mut main = pad.0; + + for (i, node) in nodes.iter_mut().enumerate() { + if i > 0 { + main += spacing; + } + + let (x, y) = axis.pack(main, pad.1); + + let node_ = node.clone().move_to(Point::new(x, y)); + + let node_ = match axis { + Axis::Horizontal => node_.align(Alignment::Start, align_items, Size::new(0.0, cross)), + Axis::Vertical => node_.align(align_items, Alignment::Start, Size::new(cross, 0.0)), + }; + + let size = node_.bounds().size(); + + *node = node_; + + main += axis.main(size); + } + + let (width, height) = axis.pack(main - pad.0, cross); + let size = limits.resolve(width, height, Size::new(width, height)); + + Node::with_children(size.expand(padding), nodes) +} diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 2584e762..ba4562f5 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -7,26 +7,42 @@ use super::{ }, menu_tree::MenuTree, }; -use crate::style::menu_bar::StyleSheet; +use crate::{ + Renderer, + style::menu_bar::StyleSheet, + widget::{ + RcWrapper, + dropdown::menu::{self, State}, + }, +}; -use iced::{Point, Vector}; +use iced::{Point, Vector, window}; use iced_core::Border; use iced_widget::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, event, layout::{Limits, Node}, mouse::{self, Cursor}, - overlay, renderer, touch, + overlay, + renderer::{self, Renderer as IcedRenderer}, + touch, widget::{Tree, tree}, }; /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. -pub fn menu_bar( - menu_roots: Vec>, -) -> MenuBar { +pub fn menu_bar(menu_roots: Vec>) -> MenuBar +where + Message: Clone + 'static, +{ MenuBar::new(menu_roots) } +#[derive(Clone, Default)] pub(crate) struct MenuBarState { + pub(crate) inner: RcWrapper, +} + +pub(crate) struct MenuBarStateInner { + pub(crate) tree: Tree, pub(crate) pressed: bool, pub(crate) view_cursor: Cursor, pub(crate) open: bool, @@ -35,7 +51,7 @@ pub(crate) struct MenuBarState { pub(crate) vertical_direction: Direction, pub(crate) menu_states: Vec, } -impl MenuBarState { +impl MenuBarStateInner { pub(super) fn get_trimmed_indices(&self) -> impl Iterator + '_ { self.menu_states .iter() @@ -49,9 +65,10 @@ impl MenuBarState { self.menu_states.clear(); } } -impl Default for MenuBarState { +impl Default for MenuBarStateInner { fn default() -> Self { Self { + tree: Tree::empty(), pressed: false, view_cursor: Cursor::Available([-0.5, -0.5].into()), open: false, @@ -63,11 +80,9 @@ impl Default for MenuBarState { } } -pub(crate) fn menu_roots_children( - menu_roots: &Vec>, -) -> Vec +pub(crate) fn menu_roots_children(menu_roots: &Vec>) -> Vec where - Renderer: renderer::Renderer, + Message: Clone + 'static, { /* menu bar @@ -85,7 +100,7 @@ where let flat = root .flattern() .iter() - .map(|mt| Tree::new(mt.item.as_widget())) + .map(|mt| Tree::new(mt.item.clone())) .collect(); tree.children = flat; tree @@ -94,11 +109,9 @@ where } #[allow(invalid_reference_casting)] -pub(crate) fn menu_roots_diff( - menu_roots: &mut Vec>, - tree: &mut Tree, -) where - Renderer: renderer::Renderer, +pub(crate) fn menu_roots_diff(menu_roots: &mut Vec>, tree: &mut Tree) +where + Message: Clone + 'static, { if tree.children.len() > menu_roots.len() { tree.children.truncate(menu_roots.len()); @@ -112,7 +125,7 @@ pub(crate) fn menu_roots_diff( .flattern() .iter() .map(|mt| { - let widget = mt.item.as_widget(); + let widget = &mt.item; let widget_ptr = widget as *const dyn Widget; let widget_ptr_mut = widget_ptr as *mut dyn Widget; @@ -130,7 +143,7 @@ pub(crate) fn menu_roots_diff( let flat = root .flattern() .iter() - .map(|mt| Tree::new(mt.item.as_widget())) + .map(|mt| Tree::new(mt.item.clone())) .collect(); tree.children = flat; tree @@ -141,10 +154,7 @@ pub(crate) fn menu_roots_diff( /// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. #[allow(missing_debug_implementations)] -pub struct MenuBar<'a, Message, Renderer = crate::Renderer> -where - Renderer: renderer::Renderer, -{ +pub struct MenuBar { width: Length, height: Length, spacing: f32, @@ -156,17 +166,20 @@ where item_width: ItemWidth, item_height: ItemHeight, path_highlight: Option, - menu_roots: Vec>, + menu_roots: Vec>, style: ::Style, + window_id: window::Id, + #[cfg(feature = "wayland")] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, } -impl<'a, Message, Renderer> MenuBar<'a, Message, Renderer> +impl MenuBar where - Renderer: renderer::Renderer, + Message: Clone + 'static, { /// Creates a new [`MenuBar`] with the given menu roots #[must_use] - pub fn new(menu_roots: Vec>) -> Self { + pub fn new(menu_roots: Vec>) -> Self { let mut menu_roots = menu_roots; menu_roots.iter_mut().for_each(MenuTree::set_index); @@ -188,6 +201,9 @@ where path_highlight: Some(PathHighlight::MenuActive), menu_roots, style: ::Style::default(), + window_id: window::Id::NONE, + #[cfg(feature = "wayland")] + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), } } @@ -278,10 +294,31 @@ where self.width = width; self } + + #[cfg(feature = "wayland")] + pub fn with_positioner( + mut self, + positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, + ) -> Self { + self.positioner = positioner; + self + } + + pub fn window_id(mut self, id: window::Id) -> Self { + self.window_id = id; + self + } + + pub fn window_id_maybe(mut self, id: Option) -> Self { + if let Some(id) = id { + self.window_id = id; + } + self + } } -impl Widget for MenuBar<'_, Message, Renderer> +impl Widget for MenuBar where - Renderer: renderer::Renderer, + Message: Clone + 'static, { fn size(&self) -> iced_core::Size { iced_core::Size::new(self.width, self.height) @@ -318,7 +355,7 @@ where .iter_mut() .map(|t| &mut t.children[0]) .collect::>(); - flex::resolve( + flex::resolve_wrapper( &flex::Axis::Horizontal, renderer, &limits, @@ -361,12 +398,14 @@ where match event { Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. } | FingerLost { .. }) => { - if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) { - state.view_cursor = view_cursor; - state.open = true; - // #[cfg(feature = "wayland")] - // TODO emit Message to open menu - } + state.inner.with_data_mut(|state| { + if state.menu_states.is_empty() && view_cursor.is_over(layout.bounds()) { + state.view_cursor = view_cursor; + state.open = true; + // #[cfg(feature = "wayland")] + // TODO emit Message to open menu + } + }); } _ => (), } @@ -385,49 +424,51 @@ where ) { let state = tree.state.downcast_ref::(); let cursor_pos = view_cursor.position().unwrap_or_default(); - let position = if state.open && (cursor_pos.x < 0.0 || cursor_pos.y < 0.0) { - state.view_cursor - } else { - view_cursor - }; + state.inner.with_data_mut(|state| { + let position = if state.open && (cursor_pos.x < 0.0 || cursor_pos.y < 0.0) { + state.view_cursor + } else { + view_cursor + }; - // draw path highlight - if self.path_highlight.is_some() { - let styling = theme.appearance(&self.style); - if let Some(active) = state.active_root { - let active_bounds = layout - .children() - .nth(active) - .expect("Active child not found in menu?") - .bounds(); - let path_quad = renderer::Quad { - bounds: active_bounds, - border: Border { - radius: styling.bar_border_radius.into(), - ..Default::default() - }, - shadow: Default::default(), - }; + // draw path highlight + if self.path_highlight.is_some() { + let styling = theme.appearance(&self.style); + if let Some(active) = state.active_root { + let active_bounds = layout + .children() + .nth(active) + .expect("Active child not found in menu?") + .bounds(); + let path_quad = renderer::Quad { + bounds: active_bounds, + border: Border { + radius: styling.bar_border_radius.into(), + ..Default::default() + }, + shadow: Default::default(), + }; - renderer.fill_quad(path_quad, styling.path); + renderer.fill_quad(path_quad, styling.path); + } } - } - self.menu_roots - .iter() - .zip(&tree.children) - .zip(layout.children()) - .for_each(|((root, t), lo)| { - root.item.as_widget().draw( - &t.children[root.index], - renderer, - theme, - style, - lo, - position, - viewport, - ); - }); + self.menu_roots + .iter() + .zip(&tree.children) + .zip(layout.children()) + .for_each(|((root, t), lo)| { + root.item.draw( + &t.children[root.index], + renderer, + theme, + style, + lo, + position, + viewport, + ); + }); + }); } fn overlay<'b>( @@ -441,14 +482,14 @@ where // return None; let state = tree.state.downcast_ref::(); - if !state.open { + if state.inner.with_data_mut(|state| !state.open) { return None; - } + }; Some( Menu { - tree, - menu_roots: &mut self.menu_roots, + tree: state.clone(), + menu_roots: std::borrow::Cow::Borrowed(&mut self.menu_roots), bounds_expand: self.bounds_expand, menu_overlays_parent: false, close_condition: self.close_condition, @@ -459,27 +500,26 @@ where cross_offset: self.cross_offset, root_bounds_list: layout.children().map(|lo| lo.bounds()).collect(), path_highlight: self.path_highlight, - style: &self.style, + style: std::borrow::Cow::Borrowed(&self.style), position: Point::new(translation.x, translation.y), } .overlay(), ) } } -impl<'a, Message, Renderer> From> - for Element<'a, Message, crate::Theme, Renderer> + +impl<'a, Message> From> for Element<'a, Message, crate::Theme, Renderer> where - Message: 'a, - Renderer: 'a + renderer::Renderer, + Message: Clone + 'static, { - fn from(value: MenuBar<'a, Message, Renderer>) -> Self { + fn from(value: MenuBar) -> Self { Self::new(value) } } #[allow(unused_results, clippy::too_many_arguments)] -fn process_root_events( - menu_roots: &mut [MenuTree<'_, Message, Renderer>], +fn process_root_events( + menu_roots: &mut [MenuTree], view_cursor: Cursor, tree: &mut Tree, event: &event::Event, @@ -490,7 +530,6 @@ fn process_root_events( viewport: &Rectangle, ) -> event::Status where - Renderer: renderer::Renderer, { menu_roots .iter_mut() @@ -498,7 +537,7 @@ where .zip(layout.children()) .map(|((root, t), lo)| { // assert!(t.tag == tree::Tag::stateless()); - root.item.as_widget_mut().on_event( + root.item.on_event( &mut t.children[root.index], event.clone(), lo, diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index f8c0471a..21820a97 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -1,10 +1,12 @@ // From iced_aw, license MIT //! Menu tree overlay +use std::borrow::Cow; + use super::{menu_bar::MenuBarState, menu_tree::MenuTree}; use crate::style::menu_bar::StyleSheet; -use iced_core::{Border, Shadow}; +use iced_core::{Border, Renderer as IcedRenderer, Shadow, Widget}; use iced_widget::core::{ Clipboard, Layout, Length, Padding, Point, Rectangle, Shell, Size, Vector, event, layout::{Limits, Node}, @@ -238,9 +240,9 @@ struct MenuBounds { } impl MenuBounds { #[allow(clippy::too_many_arguments)] - fn new( - menu_tree: &MenuTree<'_, Message, Renderer>, - renderer: &Renderer, + fn new( + menu_tree: &MenuTree, + renderer: &crate::Renderer, item_width: ItemWidth, item_height: ItemHeight, viewport_size: Size, @@ -249,10 +251,7 @@ impl MenuBounds { bounds_expand: u16, parent_bounds: Rectangle, tree: &mut [Tree], - ) -> Self - where - Renderer: renderer::Renderer, - { + ) -> Self { let (children_size, child_positions, child_sizes) = get_children_layout(menu_tree, renderer, item_width, item_height, tree); @@ -294,17 +293,14 @@ pub(crate) struct MenuState { menu_bounds: MenuBounds, } impl MenuState { - pub(super) fn layout( + pub(super) fn layout( &self, overlay_offset: Vector, slice: MenuSlice, - renderer: &Renderer, - menu_tree: &MenuTree<'_, Message, Renderer>, + renderer: &crate::Renderer, + menu_tree: &MenuTree, tree: &mut [Tree], - ) -> Node - where - Renderer: renderer::Renderer, - { + ) -> Node { let MenuSlice { start_index, end_index, @@ -339,7 +335,6 @@ impl MenuState { let limits = Limits::new(Size::ZERO, size); mt.item - .as_widget() .layout(&mut tree[mt.index], renderer, &limits) .move_to(Point::new(0.0, position + self.scroll_offset)) }) @@ -348,24 +343,21 @@ impl MenuState { Node::with_children(children_bounds.size(), child_nodes).move_to(children_bounds.position()) } - fn layout_single( + fn layout_single( &self, overlay_offset: Vector, index: usize, - renderer: &Renderer, - menu_tree: &MenuTree<'_, Message, Renderer>, + renderer: &crate::Renderer, + menu_tree: &MenuTree, tree: &mut Tree, - ) -> Node - where - Renderer: renderer::Renderer, - { + ) -> Node { // viewport space children bounds let children_bounds = self.menu_bounds.children_bounds + overlay_offset; let position = self.menu_bounds.child_positions[index]; let limits = Limits::new(Size::ZERO, self.menu_bounds.child_sizes[index]); let parent_offset = children_bounds.position() - Point::ORIGIN; - let node = menu_tree.item.as_widget().layout(tree, renderer, &limits); + let node = menu_tree.item.layout(tree, renderer, &limits); node.clone().move_to(Point::new( parent_offset.x, parent_offset.y + position + self.scroll_offset, @@ -426,12 +418,9 @@ impl MenuState { } } -pub(crate) struct Menu<'a, 'b, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - pub(crate) tree: &'b mut Tree, - pub(crate) menu_roots: &'b mut Vec>, +pub(crate) struct Menu<'b, Message: std::clone::Clone> { + pub(crate) tree: MenuBarState, + pub(crate) menu_roots: Cow<'b, Vec>>, pub(crate) bounds_expand: u16, /// Allows menu overlay items to overlap the parent pub(crate) menu_overlays_parent: bool, @@ -443,61 +432,57 @@ where pub(crate) cross_offset: i32, pub(crate) root_bounds_list: Vec, pub(crate) path_highlight: Option, - pub(crate) style: &'b ::Style, + pub(crate) style: Cow<'b, ::Style>, pub(crate) position: Point, } -impl<'b, Message, Renderer> Menu<'_, 'b, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, Renderer> { +impl<'b, Message: Clone + 'static> Menu<'b, Message> { + pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> { overlay::Element::new(Box::new(self)) } } -impl overlay::Overlay - for Menu<'_, '_, Message, Renderer> -where - Renderer: renderer::Renderer, +impl overlay::Overlay + for Menu<'_, Message> { - fn layout(&mut self, renderer: &Renderer, bounds: Size) -> Node { + fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> Node { // layout children let position = self.position; - let state = self.tree.state.downcast_mut::(); - let overlay_offset = Point::ORIGIN - position; - let tree_children = &mut self.tree.children; - let children = state - .active_root - .map(|active_root| { - let root = &self.menu_roots[active_root]; - let active_tree = &mut tree_children[active_root]; - state.menu_states.iter().enumerate().fold( - (root, Vec::new()), - |(menu_root, mut nodes), (_i, ms)| { - let slice = ms.slice(bounds, overlay_offset, self.item_height); - let _start_index = slice.start_index; - let _end_index = slice.end_index; - let children_node = ms.layout( - overlay_offset, - slice, - renderer, - menu_root, - &mut active_tree.children, - ); - nodes.push(children_node); - // only the last menu can have a None active index - ( - ms.index - .map_or(menu_root, |active| &menu_root.children[active]), - nodes, - ) - }, - ) - }) - .map(|(_, l)| l) - .unwrap_or_default(); + self.tree.inner.with_data_mut(|data| { + let overlay_offset = Point::ORIGIN - position; + let tree_children = &mut data.tree.children; + let children = data + .active_root + .map(|active_root| { + let root = &self.menu_roots[active_root]; + let active_tree = &mut tree_children[active_root]; + data.menu_states.iter().enumerate().fold( + (root, Vec::new()), + |(menu_root, mut nodes), (_i, ms)| { + let slice = ms.slice(bounds, overlay_offset, self.item_height); + let _start_index = slice.start_index; + let _end_index = slice.end_index; + let children_node = ms.layout( + overlay_offset, + slice, + renderer, + menu_root, + &mut active_tree.children, + ); + nodes.push(children_node); + // only the last menu can have a None active index + ( + ms.index + .map_or(menu_root, |active| &menu_root.children[active]), + nodes, + ) + }, + ) + }) + .map(|(_, l)| l) + .unwrap_or_default(); - // overlay space viewport rectangle - Node::with_children(bounds, children).translate(Point::ORIGIN - position) + // overlay space viewport rectangle + Node::with_children(bounds, children).translate(Point::ORIGIN - position) + }) } fn on_event( @@ -505,7 +490,7 @@ where event: event::Event, layout: Layout<'_>, view_cursor: Cursor, - renderer: &Renderer, + renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, ) -> event::Status { @@ -519,7 +504,7 @@ where }; use touch::Event::{FingerLifted, FingerMoved, FingerPressed}; - if !self.tree.state.downcast_ref::().open { + if !self.tree.inner.with_data(|data| data.open) { return Ignored; }; @@ -527,17 +512,22 @@ where let viewport_size = viewport.size(); let overlay_offset = Point::ORIGIN - viewport.position(); let overlay_cursor = view_cursor.position().unwrap_or_default() - overlay_offset; - - let menu_status = process_menu_events( - self.tree, - self.menu_roots, - event.clone(), - view_cursor, - renderer, - clipboard, - shell, - overlay_offset, - ); + let menu_roots = match &mut self.menu_roots { + Cow::Borrowed(_) => panic!(), + Cow::Owned(o) => o.as_mut_slice(), + }; + let menu_status = self.tree.inner.with_data_mut(|data| { + process_menu_events( + &mut data.tree, + menu_roots, + event.clone(), + view_cursor, + renderer, + clipboard, + shell, + overlay_offset, + ) + }); init_root_menu( self, @@ -557,9 +547,10 @@ where } Mouse(ButtonPressed(Left)) | Touch(FingerPressed { .. }) => { - let state = self.tree.state.downcast_mut::(); - state.pressed = true; - state.view_cursor = view_cursor; + self.tree.inner.with_data_mut(|data| { + data.pressed = true; + data.view_cursor = view_cursor; + }); Captured } @@ -579,46 +570,47 @@ where } Mouse(ButtonReleased(_)) | Touch(FingerLifted { .. }) => { - let state = self.tree.state.downcast_mut::(); - state.pressed = false; + self.tree.inner.with_data_mut(|state| { + state.pressed = false; - // process close condition - if state - .view_cursor - .position() - .unwrap_or_default() - .distance(view_cursor.position().unwrap_or_default()) - < 2.0 - { - let is_inside = state - .menu_states - .iter() - .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor)); - - if self.close_condition.click_inside - && is_inside - && matches!( - event, - Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. }) - ) + // process close condition + if state + .view_cursor + .position() + .unwrap_or_default() + .distance(view_cursor.position().unwrap_or_default()) + < 2.0 { - state.reset(); - return Captured; + let is_inside = state + .menu_states + .iter() + .any(|ms| ms.menu_bounds.check_bounds.contains(overlay_cursor)); + + if self.close_condition.click_inside + && is_inside + && matches!( + event, + Mouse(ButtonReleased(Left)) | Touch(FingerLifted { .. }) + ) + { + state.reset(); + return Captured; + } + + if self.close_condition.click_outside && !is_inside { + state.reset(); + return Captured; + } } - if self.close_condition.click_outside && !is_inside { + // close all menus when clicking inside the menu bar + if self.bar_bounds.contains(overlay_cursor) { state.reset(); - return Captured; + Captured + } else { + menu_status } - } - - // close all menus when clicking inside the menu bar - if self.bar_bounds.contains(overlay_cursor) { - state.reset(); - Captured - } else { - menu_status - } + }) } _ => menu_status, @@ -628,115 +620,118 @@ where #[allow(unused_results)] fn draw( &self, - renderer: &mut Renderer, + renderer: &mut crate::Renderer, theme: &crate::Theme, style: &renderer::Style, layout: Layout<'_>, view_cursor: Cursor, ) { - let state = self.tree.state.downcast_ref::(); - let Some(active_root) = state.active_root else { - return; - }; + self.tree.inner.with_data(|data| { + let Some(active_root) = data.active_root else { + return; + }; - let viewport = layout.bounds(); - let viewport_size = viewport.size(); - let overlay_offset = Point::ORIGIN - viewport.position(); - let render_bounds = Rectangle::new(Point::ORIGIN, viewport.size()); + let viewport = layout.bounds(); + let viewport_size = viewport.size(); + let overlay_offset = Point::ORIGIN - viewport.position(); + let render_bounds = Rectangle::new(Point::ORIGIN, viewport.size()); - let styling = theme.appearance(self.style); + let styling = theme.appearance(&self.style); - let tree = &self.tree.children[active_root].children; - let root = &self.menu_roots[active_root]; + let tree = &data.tree.children[active_root].children; + let root = &self.menu_roots[active_root]; - let indices = state.get_trimmed_indices().collect::>(); + let indices = data.get_trimmed_indices().collect::>(); - state - .menu_states - .iter() - .zip(layout.children()) - .enumerate() - .fold(root, |menu_root, (i, (ms, children_layout))| { - let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph { - PathHighlight::Full => true, - PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1, - PathHighlight::MenuActive => i < state.menu_states.len() - 1, - }); + data.menu_states + .iter() + .zip(layout.children()) + .enumerate() + .fold(root, |menu_root, (i, (ms, children_layout))| { + let draw_path = self.path_highlight.as_ref().map_or(false, |ph| match ph { + PathHighlight::Full => true, + PathHighlight::OmitActive => !indices.is_empty() && i < indices.len() - 1, + PathHighlight::MenuActive => i < data.menu_states.len() - 1, + }); - // react only to the last menu - let view_cursor = if i == state.menu_states.len() - 1 { - view_cursor - } else { - Cursor::Available([-1.0; 2].into()) - }; - - let draw_menu = |r: &mut Renderer| { - // calc slice - let slice = ms.slice(viewport_size, overlay_offset, self.item_height); - let start_index = slice.start_index; - let end_index = slice.end_index; - - let children_bounds = children_layout.bounds(); - - // draw menu background - // let bounds = pad_rectangle(children_bounds, styling.background_expand.into()); - // println!("cursor: {:?}", view_cursor); - // println!("bg_bounds: {:?}", bounds); - // println!("color: {:?}\n", styling.background); - let menu_quad = renderer::Quad { - bounds: pad_rectangle(children_bounds, styling.background_expand.into()), - border: Border { - radius: styling.menu_border_radius.into(), - width: styling.border_width, - color: styling.border_color, - }, - shadow: Shadow::default(), + // react only to the last menu + let view_cursor = if i == data.menu_states.len() - 1 { + view_cursor + } else { + Cursor::Available([-1.0; 2].into()) }; - let menu_color = styling.background; - r.fill_quad(menu_quad, menu_color); - // draw path hightlight - if let (true, Some(active)) = (draw_path, ms.index) { - let active_bounds = children_layout - .children() - .nth(active.saturating_sub(start_index)) - .expect("No active children were found in menu?") - .bounds(); - let path_quad = renderer::Quad { - bounds: active_bounds, + let draw_menu = |r: &mut crate::Renderer| { + // calc slice + let slice = ms.slice(viewport_size, overlay_offset, self.item_height); + let start_index = slice.start_index; + let end_index = slice.end_index; + + let children_bounds = children_layout.bounds(); + + // draw menu background + // let bounds = pad_rectangle(children_bounds, styling.background_expand.into()); + // println!("cursor: {:?}", view_cursor); + // println!("bg_bounds: {:?}", bounds); + // println!("color: {:?}\n", styling.background); + let menu_quad = renderer::Quad { + bounds: pad_rectangle( + children_bounds, + styling.background_expand.into(), + ), border: Border { radius: styling.menu_border_radius.into(), - ..Default::default() + width: styling.border_width, + color: styling.border_color, }, shadow: Shadow::default(), }; + let menu_color = styling.background; + r.fill_quad(menu_quad, menu_color); - r.fill_quad(path_quad, styling.path); - } + // draw path hightlight + if let (true, Some(active)) = (draw_path, ms.index) { + let active_bounds = children_layout + .children() + .nth(active.saturating_sub(start_index)) + .expect("No active children were found in menu?") + .bounds(); + let path_quad = renderer::Quad { + bounds: active_bounds, + border: Border { + radius: styling.menu_border_radius.into(), + ..Default::default() + }, + shadow: Shadow::default(), + }; - // draw item - menu_root.children[start_index..=end_index] - .iter() - .zip(children_layout.children()) - .for_each(|(mt, clo)| { - mt.item.as_widget().draw( - &tree[mt.index], - r, - theme, - style, - clo, - view_cursor, - &children_layout.bounds(), - ); - }); - }; + r.fill_quad(path_quad, styling.path); + } - renderer.with_layer(render_bounds, draw_menu); + // draw item + menu_root.children[start_index..=end_index] + .iter() + .zip(children_layout.children()) + .for_each(|(mt, clo)| { + mt.item.draw( + &data.tree.children[mt.index], + r, + theme, + style, + clo, + view_cursor, + &children_layout.bounds(), + ); + }); + }; - // only the last menu can have a None active index - ms.index - .map_or(menu_root, |active| &menu_root.children[active]) - }); + renderer.with_layer(render_bounds, draw_menu); + + // only the last menu can have a None active index + ms.index + .map_or(menu_root, |active| &menu_root.children[active]) + }); + }) } } @@ -749,145 +744,143 @@ fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle { } } -pub(super) fn init_root_menu( - menu: &mut Menu<'_, '_, Message, Renderer>, - renderer: &Renderer, +pub(super) fn init_root_menu( + menu: &mut Menu<'_, Message>, + renderer: &crate::Renderer, shell: &mut Shell<'_, Message>, overlay_cursor: Point, viewport_size: Size, overlay_offset: Vector, bar_bounds: Rectangle, main_offset: f32, -) where - Renderer: renderer::Renderer, -{ - let state = menu.tree.state.downcast_mut::(); - if !(state.menu_states.is_empty() && bar_bounds.contains(overlay_cursor)) { - return; - } - - for (i, (&root_bounds, mt)) in menu - .root_bounds_list - .iter() - .zip(menu.menu_roots.iter()) - .enumerate() - { - if mt.children.is_empty() { - continue; +) { + menu.tree.inner.with_data_mut(|state| { + if !(state.menu_states.is_empty() && bar_bounds.contains(overlay_cursor)) { + return; } - if root_bounds.contains(overlay_cursor) { - let view_center = viewport_size.width * 0.5; - let rb_center = root_bounds.center_x(); + for (i, (&root_bounds, mt)) in menu + .root_bounds_list + .iter() + .zip(menu.menu_roots.iter()) + .enumerate() + { + if mt.children.is_empty() { + continue; + } - state.horizontal_direction = if rb_center > view_center { - Direction::Negative - } else { - Direction::Positive - }; + if root_bounds.contains(overlay_cursor) { + let view_center = viewport_size.width * 0.5; + let rb_center = root_bounds.center_x(); - 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, - }; + state.horizontal_direction = if rb_center > view_center { + Direction::Negative + } else { + Direction::Positive + }; - let menu_bounds = MenuBounds::new( - mt, - renderer, - menu.item_width, - menu.item_height, - viewport_size, - overlay_offset, - &aod, - menu.bounds_expand, - root_bounds, - &mut menu.tree.children[i].children, - ); + 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, + }; - state.active_root = Some(i); - state.menu_states.push(MenuState { - index: None, - scroll_offset: 0.0, - menu_bounds, - }); + let menu_bounds = MenuBounds::new( + mt, + renderer, + menu.item_width, + menu.item_height, + viewport_size, + overlay_offset, + &aod, + menu.bounds_expand, + root_bounds, + &mut state.tree.children[0].children, + ); - // Hack to ensure menu opens properly - shell.invalidate_layout(); + state.active_root = Some(i); + state.menu_states.push(MenuState { + index: None, + scroll_offset: 0.0, + menu_bounds, + }); - break; + // Hack to ensure menu opens properly + shell.invalidate_layout(); + + break; + } } - } + }); } #[allow(clippy::too_many_arguments)] -fn process_menu_events<'b, Message, Renderer>( +fn process_menu_events<'b, Message>( tree: &'b mut Tree, - menu_roots: &'b mut [MenuTree<'_, Message, Renderer>], + menu_roots: &'b mut [MenuTree], event: event::Event, view_cursor: Cursor, - renderer: &Renderer, + renderer: &crate::Renderer, clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, Message>, overlay_offset: Vector, -) -> event::Status -where - Renderer: renderer::Renderer, -{ +) -> event::Status { use event::Status; let state = tree.state.downcast_mut::(); - let Some(active_root) = state.active_root else { - return Status::Ignored; - }; + state.inner.with_data(|state| { + let Some(active_root) = state.active_root else { + return Status::Ignored; + }; - let indices = state.get_trimmed_indices().collect::>(); + let indices = state.get_trimmed_indices().collect::>(); - if indices.is_empty() { - return Status::Ignored; - } + if indices.is_empty() { + return Status::Ignored; + } - // get active item - let mt = indices - .iter() - .fold(&mut menu_roots[active_root], |mt, &i| &mut mt.children[i]); + // get active item + let mt = indices + .iter() + .fold(&mut menu_roots[active_root], |mt, &i| &mut mt.children[i]); - // widget tree - let tree = &mut tree.children[active_root].children[mt.index]; + // widget tree + let tree = &mut tree.children[active_root].children[mt.index]; - // get layout - let last_ms = &state.menu_states[indices.len() - 1]; - let child_node = last_ms.layout_single( - overlay_offset, - last_ms.index.expect("missing index within menu state."), - renderer, - mt, - tree, - ); - let child_layout = Layout::new(&child_node); + // get layout + let last_ms = &state.menu_states[indices.len() - 1]; + let child_node = last_ms.layout_single( + overlay_offset, + last_ms.index.expect("missing index within menu state."), + renderer, + mt, + tree, + ); + let child_layout = Layout::new(&child_node); - // process only the last widget - mt.item.as_widget_mut().on_event( - tree, - event, - child_layout, - view_cursor, - renderer, - clipboard, - shell, - &Rectangle::default(), - ) + // process only the last widget + mt.item.on_event( + tree, + event, + child_layout, + view_cursor, + renderer, + clipboard, + shell, + &Rectangle::default(), + ) + }) } #[allow(unused_results)] -fn process_overlay_events( - menu: &mut Menu<'_, '_, Message, Renderer>, - renderer: &Renderer, +fn process_overlay_events( + menu: &mut Menu, + renderer: &crate::Renderer, viewport_size: Size, overlay_offset: Vector, view_cursor: Cursor, @@ -895,7 +888,7 @@ fn process_overlay_events( cross_offset: f32, ) -> event::Status where - Renderer: renderer::Renderer, + Message: std::clone::Clone, { use event::Status::{Captured, Ignored}; /* @@ -907,263 +900,260 @@ where if active item is a menu: add menu // viewport space */ - - let state = menu.tree.state.downcast_mut::(); - - let Some(active_root) = state.active_root else { - if !menu.bar_bounds.contains(overlay_cursor) { - state.reset(); - } - return Ignored; - }; - - if state.pressed { - return 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( - state.menu_states[..state.menu_states.len().saturating_sub(1)] - .iter() - .map(|ms| ms.menu_bounds.children_bounds), - ) - .collect::>(); - - if menu.close_condition.leave { - 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) - || mb.offset_bounds.contains(overlay_cursor) - || (mb.check_bounds.contains(overlay_cursor) - && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) - { - break; + menu.tree.inner.with_data_mut(|state| { + let Some(active_root) = state.active_root else { + if !menu.bar_bounds.contains(overlay_cursor) { + state.reset(); } - prev_bounds.pop(); - state.menu_states.pop(); - } - } else { - 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.menu_states.pop(); - } - } - - // get indices - let indices = state - .menu_states - .iter() - .map(|ms| ms.index) - .collect::>(); - - // * update active item - let Some(last_menu_state) = state.menu_states.last_mut() else { - // no menus left - state.active_root = None; - - // keep state.open when the cursor is still inside the menu bar - // this allows the overlay to keep drawing when the cursor is - // moving aroung the menu bar - if !menu.bar_bounds.contains(overlay_cursor) { - state.open = false; - } - return Captured; - }; - - let last_menu_bounds = &last_menu_state.menu_bounds; - let last_parent_bounds = last_menu_bounds.parent_bounds; - let last_children_bounds = last_menu_bounds.children_bounds; - - if (!menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor)) - // cursor is in the parent part - || !last_children_bounds.contains(overlay_cursor) - // cursor is outside - { - last_menu_state.index = None; - return Captured; - } - // cursor is in the children part - - // 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_menu_root = &menu.menu_roots[active_root]; - - let active_menu = indices[0..indices.len().saturating_sub(1)] - .iter() - .fold(active_menu_root, |mt, i| { - &mt.children[i.expect("missing active child index in menu")] - }); - - let new_index = match menu.item_height { - ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize, - ItemHeight::Static(_) | ItemHeight::Dynamic(_) => { - let max_index = active_menu.children.len() - 1; - search_bound( - 0, - 0, - max_index, - height_diff, - &last_menu_bounds.child_positions, - &last_menu_bounds.child_sizes, - ) - } - }; - - // set new index - last_menu_state.index = Some(new_index); - - // get new active item - let item = &active_menu.children[new_index]; - - // * add new menu if the new item is a menu - if !item.children.is_empty() { - let item_position = Point::new( - 0.0, - last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset, - ); - let item_size = last_menu_bounds.child_sizes[new_index]; - - // overlay space item bounds - let item_bounds = Rectangle::new(item_position, item_size) - + (last_menu_bounds.children_bounds.position() - Point::ORIGIN); - - let aod = Aod { - horizontal: true, - vertical: true, - horizontal_overlap: false, - vertical_overlap: true, - horizontal_direction: state.horizontal_direction, - vertical_direction: state.vertical_direction, - horizontal_offset: cross_offset, - vertical_offset: 0.0, + return Ignored; }; - state.menu_states.push(MenuState { - index: None, - scroll_offset: 0.0, - menu_bounds: MenuBounds::new( - item, - renderer, - menu.item_width, - menu.item_height, - viewport_size, - overlay_offset, - &aod, - menu.bounds_expand, - item_bounds, - &mut menu.tree.children[active_root].children, - ), - }); - } + if state.pressed { + return Ignored; + } - Captured + /* 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( + state.menu_states[..state.menu_states.len().saturating_sub(1)] + .iter() + .map(|ms| ms.menu_bounds.children_bounds), + ) + .collect::>(); + + if menu.close_condition.leave { + 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) + || mb.offset_bounds.contains(overlay_cursor) + || (mb.check_bounds.contains(overlay_cursor) + && prev_bounds.iter().all(|pvb| !pvb.contains(overlay_cursor))) + { + break; + } + prev_bounds.pop(); + state.menu_states.pop(); + } + } else { + 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.menu_states.pop(); + } + } + + // get indices + let indices = state + .menu_states + .iter() + .map(|ms| ms.index) + .collect::>(); + + // * update active item + let Some(last_menu_state) = state.menu_states.last_mut() else { + // no menus left + state.active_root = None; + + // keep state.open when the cursor is still inside the menu bar + // this allows the overlay to keep drawing when the cursor is + // moving aroung the menu bar + if !menu.bar_bounds.contains(overlay_cursor) { + state.open = false; + } + return Captured; + }; + + let last_menu_bounds = &last_menu_state.menu_bounds; + let last_parent_bounds = last_menu_bounds.parent_bounds; + let last_children_bounds = last_menu_bounds.children_bounds; + + if (!menu.menu_overlays_parent && last_parent_bounds.contains(overlay_cursor)) + // cursor is in the parent part + || !last_children_bounds.contains(overlay_cursor) + // cursor is outside + { + last_menu_state.index = None; + return Captured; + } + // cursor is in the children part + + // 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_menu_root = &menu.menu_roots[active_root]; + + let active_menu = indices[0..indices.len().saturating_sub(1)] + .iter() + .fold(active_menu_root, |mt, i| { + &mt.children[i.expect("missing active child index in menu")] + }); + + let new_index = match menu.item_height { + ItemHeight::Uniform(u) => (height_diff / f32::from(u)).floor() as usize, + ItemHeight::Static(_) | ItemHeight::Dynamic(_) => { + let max_index = active_menu.children.len() - 1; + search_bound( + 0, + 0, + max_index, + height_diff, + &last_menu_bounds.child_positions, + &last_menu_bounds.child_sizes, + ) + } + }; + + // set new index + last_menu_state.index = Some(new_index); + + // get new active item + let item = &active_menu.children[new_index]; + + // * add new menu if the new item is a menu + if !item.children.is_empty() { + let item_position = Point::new( + 0.0, + last_menu_bounds.child_positions[new_index] + last_menu_state.scroll_offset, + ); + let item_size = last_menu_bounds.child_sizes[new_index]; + + // overlay space item bounds + let item_bounds = Rectangle::new(item_position, item_size) + + (last_menu_bounds.children_bounds.position() - Point::ORIGIN); + + let aod = Aod { + horizontal: true, + vertical: true, + horizontal_overlap: false, + vertical_overlap: true, + horizontal_direction: state.horizontal_direction, + vertical_direction: state.vertical_direction, + horizontal_offset: cross_offset, + vertical_offset: 0.0, + }; + + state.menu_states.push(MenuState { + index: None, + scroll_offset: 0.0, + menu_bounds: MenuBounds::new( + item, + renderer, + menu.item_width, + menu.item_height, + viewport_size, + overlay_offset, + &aod, + menu.bounds_expand, + item_bounds, + &mut state.tree.children[active_root].children, + ), + }); + } + Captured + }) } -fn process_scroll_events( - menu: &mut Menu<'_, '_, Message, Renderer>, +fn process_scroll_events( + menu: &mut Menu<'_, Message>, delta: mouse::ScrollDelta, overlay_cursor: Point, viewport_size: Size, overlay_offset: Vector, ) -> event::Status where - Renderer: renderer::Renderer, + Message: Clone, { use event::Status::{Captured, Ignored}; use mouse::ScrollDelta; - let state = menu.tree.state.downcast_mut::(); + menu.tree.inner.with_data_mut(|state| { + let delta_y = match delta { + ScrollDelta::Lines { y, .. } => y * 60.0, + ScrollDelta::Pixels { y, .. } => y, + }; - let delta_y = match delta { - ScrollDelta::Lines { y, .. } => y * 60.0, - ScrollDelta::Pixels { y, .. } => y, - }; + let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) { + // viewport space children bounds + let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset; - let calc_offset_bounds = |menu_state: &MenuState, viewport_size: Size| -> (f32, f32) { - // viewport space children bounds - let children_bounds = menu_state.menu_bounds.children_bounds + overlay_offset; + let max_offset = (0.0 - children_bounds.y).max(0.0); + let min_offset = + (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0); + (max_offset, min_offset) + }; - let max_offset = (0.0 - children_bounds.y).max(0.0); - let min_offset = - (viewport_size.height - (children_bounds.y + children_bounds.height)).min(0.0); - (max_offset, min_offset) - }; + // update + if state.menu_states.is_empty() { + return Ignored; + } else if state.menu_states.len() == 1 { + let last_ms = &mut state.menu_states[0]; - // update - if state.menu_states.is_empty() { - return Ignored; - } else if state.menu_states.len() == 1 { - let last_ms = &mut state.menu_states[0]; - - if last_ms.index.is_none() { - return Captured; - } - - let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size); - last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset); - } else { - // >= 2 - let max_index = state.menu_states.len() - 1; - let last_two = &mut state.menu_states[max_index - 1..=max_index]; - - if last_two[1].index.is_some() { - // scroll the last one - let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size); - last_two[1].scroll_offset = - (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset); - } else { - if !last_two[0] - .menu_bounds - .children_bounds - .contains(overlay_cursor) - { + if last_ms.index.is_none() { return Captured; } - // scroll the second last one - let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size); - let scroll_offset = (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset); - let clamped_delta_y = scroll_offset - last_two[0].scroll_offset; - last_two[0].scroll_offset = scroll_offset; + let (max_offset, min_offset) = calc_offset_bounds(last_ms, viewport_size); + last_ms.scroll_offset = (last_ms.scroll_offset + delta_y).clamp(min_offset, max_offset); + } else { + // >= 2 + let max_index = state.menu_states.len() - 1; + let last_two = &mut state.menu_states[max_index - 1..=max_index]; - // update the bounds of the last one - last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y; - last_two[1].menu_bounds.children_bounds.y += clamped_delta_y; - last_two[1].menu_bounds.check_bounds.y += clamped_delta_y; + if last_two[1].index.is_some() { + // scroll the last one + let (max_offset, min_offset) = calc_offset_bounds(&last_two[1], viewport_size); + last_two[1].scroll_offset = + (last_two[1].scroll_offset + delta_y).clamp(min_offset, max_offset); + } else { + if !last_two[0] + .menu_bounds + .children_bounds + .contains(overlay_cursor) + { + return Captured; + } + + // scroll the second last one + let (max_offset, min_offset) = calc_offset_bounds(&last_two[0], viewport_size); + let scroll_offset = + (last_two[0].scroll_offset + delta_y).clamp(min_offset, max_offset); + let clamped_delta_y = scroll_offset - last_two[0].scroll_offset; + last_two[0].scroll_offset = scroll_offset; + + // update the bounds of the last one + last_two[1].menu_bounds.parent_bounds.y += clamped_delta_y; + last_two[1].menu_bounds.children_bounds.y += clamped_delta_y; + last_two[1].menu_bounds.check_bounds.y += clamped_delta_y; + } } - } - Captured + Captured + }) } #[allow(clippy::pedantic)] /// Returns (children_size, child_positions, child_sizes) -fn get_children_layout( - menu_tree: &MenuTree<'_, Message, Renderer>, - renderer: &Renderer, +fn get_children_layout( + menu_tree: &MenuTree, + renderer: &crate::Renderer, item_width: ItemWidth, item_height: ItemHeight, tree: &mut [Tree], -) -> (Size, Vec, Vec) -where - Renderer: renderer::Renderer, -{ +) -> (Size, Vec, Vec) { let width = match item_width { ItemWidth::Uniform(u) => f32::from(u), ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)), @@ -1183,32 +1173,34 @@ where .children .iter() .map(|mt| { - let w = mt.item.as_widget(); - match w.size().height { - Length::Fixed(f) => Size::new(width, f), - Length::Shrink => { - let l_height = w - .layout( - &mut tree[mt.index], - renderer, - &Limits::new(Size::ZERO, Size::new(width, f32::MAX)), - ) - .size() - .height; + mt.item + .element + .with_data(|w| match w.as_widget().size().height { + Length::Fixed(f) => Size::new(width, f), + Length::Shrink => { + let l_height = w + .as_widget() + .layout( + &mut tree[mt.index], + renderer, + &Limits::new(Size::ZERO, Size::new(width, f32::MAX)), + ) + .size() + .height; - let height = if (f32::MAX - l_height) < 0.001 { - f32::from(d) - } else { - l_height - }; + let height = if (f32::MAX - l_height) < 0.001 { + f32::from(d) + } else { + l_height + }; - Size::new(width, height) - } - _ => mt.height.map_or_else( - || Size::new(width, f32::from(d)), - |h| Size::new(width, f32::from(h)), - ), - } + Size::new(width, height) + } + _ => mt.height.map_or_else( + || Size::new(width, f32::from(d)), + |h| Size::new(width, f32::from(h)), + ), + }) }) .collect(), }; diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 1f3fd4ab..71f93231 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -11,7 +11,7 @@ use iced_widget::core::{Element, renderer}; use crate::iced_core::{Alignment, Length}; use crate::widget::menu::action::MenuAction; use crate::widget::menu::key_bind::KeyBind; -use crate::widget::{Button, icon}; +use crate::widget::{Button, RcElementWrapper, icon}; use crate::{theme, widget}; /// Nested menu is essentially a tree of items, a menu is a collection of items @@ -23,27 +23,25 @@ use crate::{theme, widget}; /// but there's no need to explicitly distinguish them here, if a menu tree /// has children, it's a menu, otherwise it's an item #[allow(missing_debug_implementations)] -pub struct MenuTree<'a, Message, Renderer = crate::Renderer> { +#[derive(Clone)] +pub struct MenuTree { /// The menu tree will be flatten into a vector to build a linear widget tree, /// the `index` field is the index of the item in that vector pub(crate) index: usize, /// The item of the menu tree - pub(crate) item: Element<'a, Message, crate::Theme, Renderer>, + pub(crate) item: RcElementWrapper, /// The children of the menu tree - pub(crate) children: Vec>, + pub(crate) children: Vec>, /// The width of the menu tree pub(crate) width: Option, /// The height of the menu tree pub(crate) height: Option, } -impl<'a, Message, Renderer> MenuTree<'a, Message, Renderer> -where - Renderer: renderer::Renderer, -{ +impl MenuTree { /// Create a new menu tree from a widget - pub fn new(item: impl Into>) -> Self { + pub fn new(item: impl Into>) -> Self { Self { index: 0, item: item.into(), @@ -55,8 +53,8 @@ where /// Create a menu tree from a widget and a vector of sub trees pub fn with_children( - item: impl Into>, - children: Vec>>, + item: impl Into>, + children: Vec>>, ) -> Self { Self { index: 0, @@ -92,7 +90,7 @@ where /// Set the index of each item pub(crate) fn set_index(&mut self) { /// inner counting function. - fn rec(mt: &mut MenuTree<'_, Message, Renderer>, count: &mut usize) { + fn rec(mt: &mut MenuTree, count: &mut usize) { // keep items under the same menu line up mt.children.iter_mut().for_each(|c| { c.index = *count; @@ -109,18 +107,18 @@ where } /// Flatten the menu tree - pub(crate) fn flattern(&'a self) -> Vec<&Self> { + pub(crate) fn flattern(&self) -> Vec<&Self> { /// Inner flattening function - fn rec<'a, Message, Renderer>( - mt: &'a MenuTree<'a, Message, Renderer>, - flat: &mut Vec<&MenuTree<'a, Message, Renderer>>, + fn rec<'a, Message: Clone + 'static>( + mt: &'a MenuTree, + flat: &mut Vec<&'a MenuTree>, ) { mt.children.iter().for_each(|c| { flat.push(c); }); mt.children.iter().for_each(|c| { - rec(c, flat); + rec(&c, flat); }); } @@ -132,13 +130,9 @@ where } } -impl<'a, Message, Renderer> From> - for MenuTree<'a, Message, Renderer> -where - Renderer: renderer::Renderer, -{ - fn from(value: Element<'a, Message, crate::Theme, Renderer>) -> Self { - Self::new(value) +impl From> for MenuTree { + fn from(value: crate::Element<'static, Message>) -> Self { + Self::new(RcElementWrapper::new(value)) } } @@ -160,6 +154,7 @@ where .class(theme::Button::MenuItem) } +#[derive(Clone)] /// Represents a menu item that performs an action when selected or a separator between menu items. /// /// - `Action` - Represents a menu item that performs an action when selected. @@ -216,19 +211,13 @@ where /// # Returns /// - A vector of `MenuTree`. pub fn menu_items< - 'a, A: MenuAction, L: Into> + 'static, - Message, - Renderer: renderer::Renderer + 'a, + Message: 'static + std::clone::Clone, >( key_binds: &HashMap, children: Vec>, -) -> Vec> -where - Element<'a, Message, crate::Theme, Renderer>: From>, - Message: 'a + Clone, -{ +) -> Vec> { fn find_key(action: &A, key_binds: &HashMap) -> String { for (key_bind, key_action) in key_binds { if action == key_action { @@ -263,7 +252,7 @@ where let menu_button = menu_button(items).on_press(action.message()); - trees.push(MenuTree::::new(menu_button)); + trees.push(MenuTree::::from(Element::from(menu_button))); } MenuItem::ButtonDisabled(label, icon, action) => { let key = find_key(&action, key_binds); @@ -281,7 +270,7 @@ where let menu_button = menu_button(items); - trees.push(MenuTree::::new(menu_button)); + trees.push(MenuTree::::from(Element::from(menu_button))); } MenuItem::CheckBox(label, icon, value, action) => { let key = find_key(&action, key_binds); @@ -311,36 +300,40 @@ where items.insert(2, widget::icon::icon(icon).size(14).into()); } - trees.push(MenuTree::new(menu_button(items).on_press(action.message()))); + trees.push(MenuTree::from(Element::from( + menu_button(items).on_press(action.message()), + ))); } MenuItem::Folder(label, children) => { - trees.push(MenuTree::::with_children( - menu_button(vec![ - widget::text(label).into(), - widget::horizontal_space().into(), - widget::icon::from_name("pan-end-symbolic") - .size(16) - .icon() - .into(), - ]) - .class( - // Menu folders have no on_press so they take on the disabled style by default - if children.is_empty() { - // This will make the folder use the disabled style if it has no children - theme::Button::MenuItem - } else { - // This will make the folder use the enabled style if it has children - theme::Button::MenuFolder - }, - ), + trees.push(MenuTree::::with_children( + RcElementWrapper::new(crate::Element::from( + menu_button::<'static, _>(vec![ + widget::text(label).into(), + widget::horizontal_space().into(), + widget::icon::from_name("pan-end-symbolic") + .size(16) + .icon() + .into(), + ]) + .class( + // Menu folders have no on_press so they take on the disabled style by default + if children.is_empty() { + // This will make the folder use the disabled style if it has no children + theme::Button::MenuItem + } else { + // This will make the folder use the enabled style if it has children + theme::Button::MenuFolder + }, + ), + )), menu_items(key_binds, children), )); } MenuItem::Divider => { if i != size - 1 { - trees.push(MenuTree::::new( + trees.push(MenuTree::::from(Element::from( widget::divider::horizontal::light(), - )); + ))); } } } diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index fef3cbe4..6923472a 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -69,7 +69,7 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> { } #[inline] - pub fn context_menu(mut self, context_menu: Option>>) -> Self { + pub fn context_menu(mut self, context_menu: Option>>) -> Self { self.segmented_button = self.segmented_button.context_menu(context_menu); self } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 3cb64e2d..c681262d 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -127,7 +127,7 @@ where pub(super) style: Style, /// The context menu to display when a context is activated #[setters(skip)] - pub(super) context_menu: Option>>, + pub(super) context_menu: Option>>, /// Emits the ID of the item that was activated. #[setters(skip)] pub(super) on_activate: Option Message + 'static>>, @@ -198,13 +198,13 @@ where } } - pub fn context_menu(mut self, context_menu: Option>>) -> Self + pub fn context_menu(mut self, context_menu: Option>>) -> Self where - Message: 'static, + Message: Clone + 'static, { self.context_menu = context_menu.map(|menus| { vec![menu::Tree::with_children( - crate::widget::row::<'static, Message>(), + crate::Element::from(crate::widget::row::<'static, Message>()), menus, )] }); @@ -577,6 +577,7 @@ where fn state(&self) -> tree::State { #[allow(clippy::default_trait_access)] tree::State::new(LocalState { + menu_state: Default::default(), paragraphs: SecondaryMap::new(), text_hashes: SecondaryMap::new(), buttons_visible: Default::default(), @@ -955,8 +956,10 @@ where let menu_state = tree.children[0].state.downcast_mut::(); - menu_state.open = true; - menu_state.view_cursor = cursor_position; + menu_state.inner.with_data_mut(|data| { + data.open = true; + data.view_cursor = cursor_position; + }); shell.publish(on_context(key)); return event::Status::Captured; @@ -1346,7 +1349,11 @@ where let center_y = bounds.center_y(); let menu_open = !tree.children.is_empty() - && tree.children[0].state.downcast_ref::().open; + && tree.children[0] + .state + .downcast_ref::() + .inner + .with_data(|data| data.open); let key_is_active = self.model.is_active(key); let key_is_hovered = self.button_is_hovered(state, key); @@ -1556,6 +1563,7 @@ where translation: Vector, ) -> Option> { let state = tree.state.downcast_ref::(); + let menu_state = state.menu_state.clone(); let Some(entity) = state.show_context else { return None; @@ -1575,7 +1583,12 @@ where return None; }; - if !tree.children[0].state.downcast_ref::().open { + if !tree.children[0] + .state + .downcast_ref::() + .inner + .with_data(|data| data.open) + { return None; } @@ -1584,8 +1597,8 @@ where Some( crate::widget::menu::Menu { - tree: &mut tree.children[0], - menu_roots: context_menu, + tree: menu_state, + menu_roots: std::borrow::Cow::Borrowed(context_menu), bounds_expand: 16, menu_overlays_parent: true, close_condition: CloseCondition { @@ -1600,7 +1613,7 @@ where cross_offset: 0, root_bounds_list: vec![bounds], path_highlight: Some(PathHighlight::MenuActive), - style: &crate::theme::menu_bar::MenuBarStyle::Default, + style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default), position: Point::new(translation.x, translation.y), } .overlay(), @@ -1653,6 +1666,8 @@ where /// State that is maintained by each individual widget. pub struct LocalState { + /// Menu state + pub(crate) menu_state: MenuBarState, /// Defines how many buttons to show at a time. pub(super) buttons_visible: usize, /// Button visibility offset, when collapsed. diff --git a/src/widget/table/widget/compact.rs b/src/widget/table/widget/compact.rs index 43a32de2..2d060ca2 100644 --- a/src/widget/table/widget/compact.rs +++ b/src/widget/table/widget/compact.rs @@ -44,11 +44,12 @@ where #[setters(skip)] pub(super) on_item_mb_right: Option Message + 'static>>, #[setters(skip)] - pub(super) item_context_builder: Box Option>>>, + pub(super) item_context_builder: Box Option>>>, } -impl<'a, SelectionMode, Item, Category, Message> - From> for Element<'a, Message> +impl + From> + for Element<'static, Message> where Category: ItemCategory, Item: ItemInterface, @@ -56,7 +57,7 @@ where SelectionMode: Default, Message: Clone + 'static, { - fn from(val: CompactTableView<'a, SelectionMode, Item, Category, Message>) -> Self { + fn from(val: CompactTableView<'static, SelectionMode, Item, Category, Message>) -> Self { let cosmic_theme::Spacing { space_xxxs, .. } = theme::spacing(); val.model .iter() @@ -97,7 +98,7 @@ where ] }) .flatten() - .collect::>>(); + .collect::>>(); elements.pop(); elements .apply(widget::row::with_children) @@ -171,7 +172,7 @@ where ) .apply(Element::from) }) - .collect::>>() + .collect::>>() .apply(widget::column::with_children) .spacing(val.item_spacing) .padding(val.element_padding) @@ -247,7 +248,7 @@ where pub fn item_context(mut self, context_menu_builder: F) -> Self where - F: Fn(&Item) -> Option>> + 'static, + F: Fn(&Item) -> Option>> + 'static, Message: 'static, { self.item_context_builder = Box::new(context_menu_builder); diff --git a/src/widget/table/widget/standard.rs b/src/widget/table/widget/standard.rs index 01d0ea56..112d673f 100644 --- a/src/widget/table/widget/standard.rs +++ b/src/widget/table/widget/standard.rs @@ -51,7 +51,7 @@ where #[setters(skip)] pub(super) on_item_mb_right: Option Message + 'static>>, #[setters(skip)] - pub(super) item_context_builder: Box Option>>>, + pub(super) item_context_builder: Box Option>>>, // Item DND // === Category Interaction === @@ -64,12 +64,11 @@ where #[setters(skip)] pub(super) on_category_mb_right: Option Message + 'static>>, #[setters(skip)] - pub(super) category_context_builder: - Box Option>>>, + pub(super) category_context_builder: Box Option>>>, } -impl<'a, SelectionMode, Item, Category, Message> - From> for Element<'a, Message> +impl + From> for Element<'static, Message> where Category: ItemCategory, Item: ItemInterface, @@ -77,13 +76,13 @@ where SelectionMode: Default, Message: Clone + 'static, { - fn from(val: TableView<'a, SelectionMode, Item, Category, Message>) -> Self { + fn from(val: TableView<'static, SelectionMode, Item, Category, Message>) -> Self { // Header row let header_row = val .model .categories .iter() - .cloned() + .copied() .map(|category| { let cat_context_tree = (val.category_context_builder)(category); @@ -126,7 +125,7 @@ where .apply(|mouse_area| widget::context_menu(mouse_area, cat_context_tree)) .apply(Element::from) }) - .collect::>>() + .collect::>>() .apply(widget::row::with_children) .apply(Element::from); // Build the items @@ -167,7 +166,7 @@ where .align_y(Alignment::Center) .apply(Element::from) }) - .collect::>>() + .collect::>>() .apply(widget::row::with_children) .apply(container) .padding(val.item_padding) @@ -235,12 +234,12 @@ where ] }) .flatten() - .collect::>>() + .collect::>>() }; vec![vec![header_row], items_full] .into_iter() .flatten() - .collect::>>() + .collect::>>() .apply(widget::column::with_children) .width(val.width) .height(val.height) @@ -328,7 +327,7 @@ where pub fn item_context(mut self, context_menu_builder: F) -> Self where - F: Fn(&Item) -> Option>> + 'static, + F: Fn(&Item) -> Option>> + 'static, Message: 'static, { self.item_context_builder = Box::new(context_menu_builder); @@ -367,7 +366,7 @@ where pub fn category_context(mut self, context_menu_builder: F) -> Self where - F: Fn(Category) -> Option>> + 'static, + F: Fn(Category) -> Option>> + 'static, Message: 'static, { self.category_context_builder = Box::new(context_menu_builder); diff --git a/src/widget/wrapper.rs b/src/widget/wrapper.rs index 0579c4b2..92f26fd4 100644 --- a/src/widget/wrapper.rs +++ b/src/widget/wrapper.rs @@ -1,4 +1,5 @@ use std::{ + borrow::Borrow, cell::RefCell, rc::Rc, thread::{self, ThreadId}, @@ -14,6 +15,12 @@ pub struct RcWrapper { pub(crate) thread_id: ThreadId, } +impl Default for RcWrapper { + fn default() -> Self { + Self::new(T::default()) + } +} + impl Clone for RcWrapper { fn clone(&self) -> Self { Self { @@ -75,6 +82,12 @@ impl RcElementWrapper { } } +impl Borrow> for RcElementWrapper { + fn borrow(&self) -> &(dyn Widget + 'static) { + self + } +} + impl Widget for RcElementWrapper { fn size(&self) -> Size { self.element.with_data(|e| e.as_widget().size())