wip: menus
This commit is contained in:
parent
b316d8901b
commit
04b51bc1ba
25 changed files with 1358 additions and 243 deletions
|
|
@ -3,17 +3,24 @@
|
|||
|
||||
use std::{cell::OnceCell, collections::HashMap};
|
||||
|
||||
use crate::widget::nav_bar;
|
||||
use crate::{
|
||||
theme::menu_bar,
|
||||
widget::{
|
||||
button, icon,
|
||||
menu::{self, Tree},
|
||||
nav_bar, responsive_container,
|
||||
},
|
||||
};
|
||||
use cosmic_config::CosmicConfigEntry;
|
||||
use cosmic_theme::ThemeMode;
|
||||
use iced::window;
|
||||
use iced_core::window::Id;
|
||||
use iced::{window, Limits, Size};
|
||||
use iced_core::{window::Id, Element};
|
||||
use palette::Srgba;
|
||||
use slotmap::Key;
|
||||
|
||||
use crate::Theme;
|
||||
|
||||
use super::{Element, Task};
|
||||
use super::{message::SurfaceMessage, Renderer, Task};
|
||||
|
||||
/// Status of the nav bar and its panels.
|
||||
#[derive(Clone)]
|
||||
|
|
@ -97,6 +104,8 @@ pub struct Core {
|
|||
pub(crate) main_window: Option<window::Id>,
|
||||
|
||||
pub(crate) exit_on_main_window_closed: bool,
|
||||
|
||||
pub(crate) menu_bars: HashMap<crate::widget::Id, (Limits, Size)>,
|
||||
}
|
||||
|
||||
impl Default for Core {
|
||||
|
|
@ -153,6 +162,7 @@ impl Default for Core {
|
|||
portal_is_high_contrast: None,
|
||||
main_window: None,
|
||||
exit_on_main_window_closed: true,
|
||||
menu_bars: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -376,4 +386,35 @@ impl Core {
|
|||
std::mem::swap(&mut self.main_window, &mut id);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn responsive_menu_bar<'a, Message: Clone + From<SurfaceMessage> + 'static>(
|
||||
&self,
|
||||
id: crate::widget::Id,
|
||||
roots: Vec<Tree<'a, Message, Renderer>>,
|
||||
) -> crate::Element<'a, Message> {
|
||||
let menu_bar_size = self.menu_bars.get(&id);
|
||||
if !menu_bar_size.is_some_and(|(limits, size)| {
|
||||
let max_size = limits.max();
|
||||
|
||||
max_size.width < size.width
|
||||
}) {
|
||||
crate::Element::from(responsive_container::responsive_container(
|
||||
menu::bar(roots),
|
||||
id,
|
||||
))
|
||||
} else {
|
||||
crate::Element::from(
|
||||
responsive_container::responsive_container(
|
||||
menu::bar(vec![menu::Tree::<'a, _, _>::with_children(
|
||||
button::icon(icon::from_name("open-menu-symbolic"))
|
||||
.padding([4, 12])
|
||||
.class(crate::theme::Button::MenuRoot),
|
||||
roots,
|
||||
)]),
|
||||
id,
|
||||
)
|
||||
.size(menu_bar_size.unwrap().1),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,8 +124,7 @@ where
|
|||
let message = match message {
|
||||
super::Message::App(message) => {
|
||||
match super::message::SurfaceMessageHandler::to_surface_message(message) {
|
||||
super::message::MessageWrapper::Surface(surface_message) =>
|
||||
{
|
||||
super::message::MessageWrapper::Surface(surface_message) => {
|
||||
#[cfg(feature = "wayland")]
|
||||
match surface_message {
|
||||
super::message::SurfaceMessage::Subsurface(settings, view) => {
|
||||
|
|
@ -160,7 +159,6 @@ where
|
|||
))
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
super::message::SurfaceMessage::Popup(settings, view) => {
|
||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||
.ok()
|
||||
|
|
@ -200,7 +198,18 @@ where
|
|||
super::message::SurfaceMessage::DestroySubsurface(id) => {
|
||||
iced_winit::commands::subsurface::destroy_subsurface(id)
|
||||
}
|
||||
super::message::SurfaceMessage::ResponsiveMenuBar {
|
||||
menu_bar,
|
||||
limits,
|
||||
size,
|
||||
} => {
|
||||
let core = self.app.core_mut();
|
||||
core.menu_bars.insert(menu_bar, (limits, size));
|
||||
iced::Task::none()
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "wayland"))]
|
||||
iced::Task::none()
|
||||
}
|
||||
super::message::MessageWrapper::Message(message) => self.app.update(message),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ pub(crate) mod multi_window;
|
|||
pub mod settings;
|
||||
|
||||
pub mod message {
|
||||
use iced::{Limits, Size};
|
||||
|
||||
pub enum MessageWrapper<M> {
|
||||
Surface(SurfaceMessage),
|
||||
Message(M),
|
||||
|
|
@ -31,6 +33,7 @@ pub mod message {
|
|||
}
|
||||
}
|
||||
|
||||
/// Ignore this message in your application. It will be intercepted.
|
||||
#[derive(Clone)]
|
||||
pub enum SurfaceMessage {
|
||||
/// Create a subsurface with a view function
|
||||
|
|
@ -47,20 +50,39 @@ pub mod message {
|
|||
),
|
||||
/// Destroy a subsurface with a view function
|
||||
DestroyPopup(iced::window::Id),
|
||||
/// Responsive menu bar update
|
||||
ResponsiveMenuBar {
|
||||
/// Id of the menu bar
|
||||
menu_bar: crate::widget::Id,
|
||||
/// Limits of the menu bar
|
||||
limits: Limits,
|
||||
/// Requested Full Size for expanded menu bar
|
||||
size: Size,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
impl std::fmt::Debug for SurfaceMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SurfaceMessage::Subsurface(any, any1) => f.debug_tuple("Subsurface").finish(),
|
||||
SurfaceMessage::DestroySubsurface(id) => {
|
||||
f.debug_tuple("DestroySubsurface").field(id).finish()
|
||||
Self::Subsurface(arg0, arg1) => {
|
||||
f.debug_tuple("Subsurface").field(arg0).field(arg1).finish()
|
||||
}
|
||||
SurfaceMessage::Popup(any, any1) => f.debug_tuple("Popup").finish(),
|
||||
SurfaceMessage::DestroyPopup(id) => {
|
||||
f.debug_tuple("DestroyPopup").field(id).finish()
|
||||
Self::DestroySubsurface(arg0) => {
|
||||
f.debug_tuple("DestroySubsurface").field(arg0).finish()
|
||||
}
|
||||
Self::Popup(arg0, arg1) => f.debug_tuple("Popup").field(arg0).field(arg1).finish(),
|
||||
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
||||
Self::ResponsiveMenuBar {
|
||||
menu_bar,
|
||||
limits,
|
||||
size,
|
||||
} => f
|
||||
.debug_struct("ResponsiveMenuBar")
|
||||
.field("menu_bar", menu_bar)
|
||||
.field("limits", limits)
|
||||
.field("size", size)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,6 +171,20 @@ pub mod message {
|
|||
))
|
||||
}
|
||||
|
||||
// TODO could this somehow be used by widgets? It is the wrong message type though.
|
||||
// #[cfg(feature = "wayland")]
|
||||
// pub fn get_simple_popup<App: super::Application>(
|
||||
// settings: iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||
// view: Option<
|
||||
// impl Fn() -> crate::Element<'static, super::Message<App::Message>> + Send + Sync + 'static,
|
||||
// >,
|
||||
// ) -> App::Message
|
||||
// where
|
||||
// App::Message: SurfaceMessageHandler + From<SurfaceMessage>,
|
||||
// {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn get_subsurface<App: super::Application>(
|
||||
settings: impl Fn(&mut App) -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings + Send + Sync + 'static,
|
||||
|
|
@ -477,7 +513,7 @@ impl DbusActivation {
|
|||
pub fn run_single_instance<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result
|
||||
where
|
||||
App::Flags: CosmicFlags,
|
||||
App::Message: Clone + std::fmt::Debug + Send + 'static,
|
||||
App::Message: Clone + std::fmt::Debug + Send + SurfaceMessageHandler + 'static,
|
||||
{
|
||||
let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
|
||||
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@ impl Context {
|
|||
},
|
||||
parent_size: None,
|
||||
grab: true,
|
||||
close_with_children: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// From iced_aw, license MIT
|
||||
|
||||
//! Change the appearance of menu bars and their menus.
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Theme;
|
||||
use iced_widget::core::Color;
|
||||
|
||||
|
|
@ -33,19 +35,19 @@ pub trait StyleSheet {
|
|||
}
|
||||
|
||||
/// The style of a menu bar and its menus
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub enum MenuBarStyle {
|
||||
/// The default style.
|
||||
#[default]
|
||||
Default,
|
||||
/// A [`Theme`] that uses a `Custom` palette.
|
||||
Custom(Box<dyn StyleSheet<Style = Theme>>),
|
||||
Custom(Arc<dyn StyleSheet<Style = Theme>>),
|
||||
}
|
||||
|
||||
impl From<fn(&Theme) -> Appearance> for MenuBarStyle {
|
||||
fn from(f: fn(&Theme) -> Appearance) -> Self {
|
||||
Self::Custom(Box::new(f))
|
||||
Self::Custom(Arc::new(f))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,5 +32,7 @@ mod text_input;
|
|||
#[doc(inline)]
|
||||
pub use self::text_input::TextInput;
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod tooltip;
|
||||
#[cfg(feature = "wayland")]
|
||||
pub use tooltip::Tooltip;
|
||||
|
|
|
|||
|
|
@ -6,24 +6,30 @@
|
|||
use crate::widget::menu::{
|
||||
self, CloseCondition, ItemHeight, ItemWidth, MenuBarState, PathHighlight,
|
||||
};
|
||||
use crate::widget::ArcElementWrapper;
|
||||
use derive_setters::Setters;
|
||||
use iced::touch::Finger;
|
||||
use iced::{Event, Vector};
|
||||
use iced_core::widget::{tree, Tree, Widget};
|
||||
use iced_core::{event, mouse, touch, Length, Point, Size};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::dropdown;
|
||||
|
||||
/// 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<crate::Element<'a, Message>> + 'a,
|
||||
pub fn context_menu<Message: Clone + 'static>(
|
||||
content: impl Into<crate::Element<'static, Message>> + 'static,
|
||||
// on_context: Message,
|
||||
context_menu: Option<Vec<menu::Tree<'a, Message>>>,
|
||||
) -> ContextMenu<'a, Message> {
|
||||
context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||
) -> 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>(),
|
||||
ArcElementWrapper(Arc::new(Mutex::new(crate::Element::from(
|
||||
crate::widget::row::<'static, Message>(),
|
||||
)))),
|
||||
menus,
|
||||
)]
|
||||
}),
|
||||
|
|
@ -43,10 +49,10 @@ pub struct ContextMenu<'a, Message> {
|
|||
#[setters(skip)]
|
||||
content: crate::Element<'a, Message>,
|
||||
#[setters(skip)]
|
||||
context_menu: Option<Vec<menu::Tree<'a, Message>>>,
|
||||
context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
||||
impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
|
||||
for ContextMenu<'a, Message>
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
|
|
@ -58,6 +64,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
tree::State::new(LocalState {
|
||||
context_cursor: Point::default(),
|
||||
fingers_pressed: Default::default(),
|
||||
menu_state: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -75,9 +82,12 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
.map(|root| {
|
||||
let mut tree = Tree::empty();
|
||||
let flat = root
|
||||
.flattern()
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|mt| Tree::new(mt.item.as_widget()))
|
||||
.map(|mt| {
|
||||
let guard = mt.item.0.lock().unwrap();
|
||||
Tree::new(guard.as_widget())
|
||||
})
|
||||
.collect();
|
||||
tree.children = flat;
|
||||
tree
|
||||
|
|
@ -214,7 +224,7 @@ impl<'a, Message: 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;
|
||||
};
|
||||
|
|
@ -229,8 +239,8 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
|
||||
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 {
|
||||
|
|
@ -245,7 +255,7 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
cross_offset: 0,
|
||||
root_bounds_list: vec![bounds],
|
||||
path_highlight: Some(PathHighlight::MenuActive),
|
||||
style: &crate::theme::menu_bar::MenuBarStyle::Default,
|
||||
style: std::borrow::Cow::Owned(crate::theme::menu_bar::MenuBarStyle::Default),
|
||||
position: Point::new(translation.x, translation.y),
|
||||
}
|
||||
.overlay(),
|
||||
|
|
@ -265,8 +275,10 @@ impl<'a, Message: Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'a> From<ContextMenu<'a, Message>> for crate::Element<'a, Message> {
|
||||
fn from(widget: ContextMenu<'a, Message>) -> Self {
|
||||
impl<Message: Clone + 'static> From<ContextMenu<'static, Message>>
|
||||
for crate::Element<'static, Message>
|
||||
{
|
||||
fn from(widget: ContextMenu<'static, Message>) -> Self {
|
||||
Self::new(widget)
|
||||
}
|
||||
}
|
||||
|
|
@ -285,4 +297,5 @@ fn touch_lifted(event: &Event) -> bool {
|
|||
pub struct LocalState {
|
||||
context_cursor: Point,
|
||||
fingers_pressed: HashSet<Finger>,
|
||||
menu_state: dropdown::menu::State,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@
|
|||
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
||||
|
||||
mod appearance;
|
||||
use std::borrow::Cow;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
pub use appearance::{Appearance, StyleSheet};
|
||||
use iced::advanced::widget;
|
||||
|
||||
use crate::widget::{icon, Container};
|
||||
use iced_core::event::{self, Event};
|
||||
|
|
@ -21,11 +25,12 @@ use iced_widget::scrollable::Scrollable;
|
|||
pub struct Menu<'a, S, Message>
|
||||
where
|
||||
S: AsRef<str>,
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
state: &'a mut State,
|
||||
options: &'a [S],
|
||||
icons: &'a [icon::Handle],
|
||||
hovered_option: &'a mut Option<usize>,
|
||||
state: State,
|
||||
options: Cow<'a, [S]>,
|
||||
icons: Cow<'a, [icon::Handle]>,
|
||||
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||
selected_option: Option<usize>,
|
||||
on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
|
||||
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||
|
|
@ -36,14 +41,17 @@ where
|
|||
style: (),
|
||||
}
|
||||
|
||||
impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
|
||||
impl<'a, S: AsRef<str>, Message: 'a + std::clone::Clone> Menu<'a, S, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
/// Creates a new [`Menu`] with the given [`State`], a list of options, and
|
||||
/// the message to produced when an option is selected.
|
||||
pub fn new(
|
||||
state: &'a mut State,
|
||||
options: &'a [S],
|
||||
icons: &'a [icon::Handle],
|
||||
hovered_option: &'a mut Option<usize>,
|
||||
state: State,
|
||||
options: Cow<'a, [S]>,
|
||||
icons: Cow<'a, [icon::Handle]>,
|
||||
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||
selected_option: Option<usize>,
|
||||
on_selected: impl FnMut(usize) -> Message + 'a,
|
||||
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||
|
|
@ -102,20 +110,35 @@ impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
|
|||
) -> overlay::Element<'a, Message, crate::Theme, crate::Renderer> {
|
||||
overlay::Element::new(Box::new(Overlay::new(self, target_height, position)))
|
||||
}
|
||||
|
||||
/// Turns the [`Menu`] into a popup [`Element`] at the given target
|
||||
/// position.
|
||||
///
|
||||
/// The `target_height` will be used to display the menu either on top
|
||||
/// of the target or under it, depending on the screen position and the
|
||||
/// dimensions of the [`Menu`].
|
||||
#[must_use]
|
||||
pub fn popup(self, position: Point, target_height: f32) -> crate::Element<'a, Message> {
|
||||
Overlay::new(self, target_height, position).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Menu`].
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State {
|
||||
tree: Tree,
|
||||
pub(crate) tree: Arc<Mutex<Tree>>,
|
||||
}
|
||||
|
||||
// XXX The tree is only used on one thread, but this is needed to make a message
|
||||
unsafe impl Send for State {}
|
||||
unsafe impl Sync for State {}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`] for a [`Menu`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
tree: Tree::empty(),
|
||||
tree: Arc::new(Mutex::new(Tree::empty())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,7 +150,7 @@ impl Default for State {
|
|||
}
|
||||
|
||||
struct Overlay<'a, Message> {
|
||||
state: &'a mut Tree,
|
||||
state: Arc<Mutex<Tree>>,
|
||||
container: Container<'a, Message, crate::Theme, crate::Renderer>,
|
||||
width: f32,
|
||||
target_height: f32,
|
||||
|
|
@ -140,7 +163,10 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
menu: Menu<'a, S, Message>,
|
||||
target_height: f32,
|
||||
position: Point,
|
||||
) -> Self {
|
||||
) -> Self
|
||||
where
|
||||
[S]: ToOwned,
|
||||
{
|
||||
let Menu {
|
||||
state,
|
||||
options,
|
||||
|
|
@ -172,10 +198,11 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
))
|
||||
.class(crate::style::Container::Dropdown);
|
||||
|
||||
state.tree.diff(&mut container as &mut dyn Widget<_, _, _>);
|
||||
|
||||
let mut tree_guard = state.tree.lock().unwrap();
|
||||
tree_guard.diff(&mut container as &mut dyn Widget<_, _, _>);
|
||||
drop(tree_guard);
|
||||
Self {
|
||||
state: &mut state.tree,
|
||||
state: state.tree.clone(),
|
||||
container,
|
||||
width,
|
||||
target_height,
|
||||
|
|
@ -183,20 +210,17 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
|
|||
position,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||
for Overlay<'a, Message>
|
||||
{
|
||||
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
let position = self.position;
|
||||
let space_below = bounds.height - (position.y + self.target_height);
|
||||
let space_above = position.y;
|
||||
fn _layout(&self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
|
||||
let space_below = bounds.height - (self.position.y + self.target_height);
|
||||
let space_above = self.position.y;
|
||||
|
||||
let limits = layout::Limits::new(
|
||||
Size::ZERO,
|
||||
Size::new(
|
||||
bounds.width - position.x,
|
||||
bounds.width - self.position.x,
|
||||
if space_below > space_above {
|
||||
space_below
|
||||
} else {
|
||||
|
|
@ -206,16 +230,16 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
|||
)
|
||||
.width(self.width);
|
||||
|
||||
let node = self.container.layout(self.state, renderer, &limits);
|
||||
let node = self.container.layout(&mut guard, renderer, &limits);
|
||||
|
||||
node.clone().move_to(if space_below > space_above {
|
||||
position + Vector::new(0.0, self.target_height)
|
||||
self.position + Vector::new(0.0, self.target_height)
|
||||
} else {
|
||||
position - Vector::new(0.0, node.size().height)
|
||||
self.position - Vector::new(0.0, node.size().height)
|
||||
})
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
fn _on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
|
|
@ -225,24 +249,28 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
|||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let bounds = layout.bounds();
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
|
||||
let state = &mut *guard;
|
||||
self.container.on_event(
|
||||
self.state, event, layout, cursor, renderer, clipboard, shell, &bounds,
|
||||
state, event, layout, cursor, renderer, clipboard, shell, &bounds,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
fn _mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let guard = self.state.lock().unwrap();
|
||||
|
||||
self.container
|
||||
.mouse_interaction(self.state, layout, cursor, viewport, renderer)
|
||||
.mouse_interaction(&guard, layout, cursor, viewport, renderer)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
fn _draw(
|
||||
&self,
|
||||
renderer: &mut crate::Renderer,
|
||||
theme: &crate::Theme,
|
||||
|
|
@ -250,6 +278,8 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
|||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
let state = self.state.lock().unwrap();
|
||||
|
||||
let appearance = theme.appearance(&self.style);
|
||||
let bounds = layout.bounds();
|
||||
|
||||
|
|
@ -267,14 +297,127 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
|||
);
|
||||
|
||||
self.container
|
||||
.draw(self.state, renderer, theme, style, layout, cursor, &bounds);
|
||||
.draw(&*state, renderer, theme, style, layout, cursor, &bounds);
|
||||
}
|
||||
}
|
||||
|
||||
struct List<'a, S: AsRef<str>, Message> {
|
||||
options: &'a [S],
|
||||
icons: &'a [icon::Handle],
|
||||
hovered_option: &'a mut Option<usize>,
|
||||
impl<'a, Message: 'a> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
|
||||
for Overlay<'a, Message>
|
||||
{
|
||||
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
|
||||
self._layout(renderer, bounds)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self._on_event(event, layout, cursor, renderer, clipboard, shell)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self._mouse_interaction(layout, cursor, viewport, renderer)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut crate::Renderer,
|
||||
theme: &crate::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
) {
|
||||
let guard = self.state.lock().unwrap();
|
||||
|
||||
self._draw(renderer, theme, style, layout, cursor);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: 'a> crate::widget::Widget<Message, crate::Theme, crate::Renderer>
|
||||
for Overlay<'a, Message>
|
||||
{
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(Length::Fixed(self.width), Length::Shrink)
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_tree: &mut iced_core::widget::Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &iced::Limits,
|
||||
) -> layout::Node {
|
||||
let mut guard = self.state.lock().unwrap();
|
||||
|
||||
let limits = limits.width(self.width);
|
||||
|
||||
self.container.layout(&mut guard, renderer, &limits)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self._mouse_interaction(layout, cursor, viewport, renderer)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
renderer: &crate::Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self._on_event(event, layout, cursor, renderer, clipboard, shell)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut crate::Renderer,
|
||||
theme: &crate::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
self._draw(renderer, theme, style, layout, cursor);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'a> From<Overlay<'a, Message>> for crate::Element<'a, Message> {
|
||||
fn from(widget: Overlay<'a, Message>) -> Self {
|
||||
Element::new(widget)
|
||||
}
|
||||
}
|
||||
|
||||
struct List<'a, S: AsRef<str>, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
options: Cow<'a, [S]>,
|
||||
icons: Cow<'a, [icon::Handle]>,
|
||||
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||
selected_option: Option<usize>,
|
||||
on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
|
||||
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
|
||||
|
|
@ -285,6 +428,8 @@ struct List<'a, S: AsRef<str>, Message> {
|
|||
|
||||
impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
||||
for List<'a, S, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
fn size(&self) -> Size<Length> {
|
||||
Size::new(Length::Fill, Length::Shrink)
|
||||
|
|
@ -330,8 +475,9 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
|||
) -> event::Status {
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
|
||||
let hovered_guard = self.hovered_option.lock().unwrap();
|
||||
if cursor.is_over(layout.bounds()) {
|
||||
if let Some(index) = *self.hovered_option {
|
||||
if let Some(index) = *hovered_guard {
|
||||
shell.publish((self.on_selected)(index));
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
|
@ -348,14 +494,15 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
|||
+ self.padding.vertical();
|
||||
|
||||
let new_hovered_option = (cursor_position.y / option_height) as usize;
|
||||
let mut hovered_guard = self.hovered_option.lock().unwrap();
|
||||
|
||||
if let Some(on_option_hovered) = self.on_option_hovered {
|
||||
if *self.hovered_option != Some(new_hovered_option) {
|
||||
if *hovered_guard != Some(new_hovered_option) {
|
||||
shell.publish(on_option_hovered(new_hovered_option));
|
||||
}
|
||||
}
|
||||
|
||||
*self.hovered_option = Some(new_hovered_option);
|
||||
*hovered_guard = Some(new_hovered_option);
|
||||
}
|
||||
}
|
||||
Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
|
|
@ -367,10 +514,11 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
|||
let option_height =
|
||||
f32::from(self.text_line_height.to_absolute(Pixels(text_size)))
|
||||
+ self.padding.vertical();
|
||||
let mut hovered_guard = self.hovered_option.lock().unwrap();
|
||||
|
||||
*self.hovered_option = Some((cursor_position.y / option_height) as usize);
|
||||
*hovered_guard = Some((cursor_position.y / option_height) as usize);
|
||||
|
||||
if let Some(index) = *self.hovered_option {
|
||||
if let Some(index) = *hovered_guard {
|
||||
shell.publish((self.on_selected)(index));
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
|
@ -434,6 +582,8 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
|||
height: option_height,
|
||||
};
|
||||
|
||||
let hovered_guard = self.hovered_option.lock().unwrap();
|
||||
|
||||
let (color, font) = if self.selected_option == Some(i) {
|
||||
let item_x = bounds.x + appearance.border_width;
|
||||
let item_width = appearance.border_width.mul_add(-2.0, bounds.width);
|
||||
|
|
@ -471,7 +621,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
|||
);
|
||||
|
||||
(appearance.selected_text_color, crate::font::semibold())
|
||||
} else if *self.hovered_option == Some(i) {
|
||||
} else if *hovered_guard == Some(i) {
|
||||
let item_x = bounds.x + appearance.border_width;
|
||||
let item_width = appearance.border_width.mul_add(-2.0, bounds.width);
|
||||
|
||||
|
|
@ -538,6 +688,8 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
|
|||
|
||||
impl<'a, S: AsRef<str>, Message: 'a> From<List<'a, S, Message>>
|
||||
for Element<'a, Message, crate::Theme, crate::Renderer>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
fn from(list: List<'a, S, Message>) -> Self {
|
||||
Element::new(list)
|
||||
|
|
|
|||
|
|
@ -13,10 +13,14 @@ mod widget;
|
|||
pub use widget::*;
|
||||
|
||||
/// Displays a list of options in a popover menu on select.
|
||||
pub fn dropdown<'a, S: AsRef<str>, Message: 'a>(
|
||||
pub fn dropdown<
|
||||
'a,
|
||||
S: AsRef<str> + std::clone::Clone + Send + Sync + 'static,
|
||||
Message: 'static + Clone,
|
||||
>(
|
||||
selections: &'a [S],
|
||||
selected: Option<usize>,
|
||||
on_selected: impl Fn(usize) -> Message + 'a,
|
||||
on_selected: impl Fn(usize) -> Message + Send + Sync + 'static,
|
||||
) -> Dropdown<'a, S, Message> {
|
||||
Dropdown::new(selections, selected, on_selected)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@
|
|||
// SPDX-License-Identifier: MPL-2.0 AND MIT
|
||||
|
||||
use super::menu::{self, Menu};
|
||||
use crate::widget::icon;
|
||||
use crate::widget::icon::{self, Handle};
|
||||
use crate::Element;
|
||||
use derive_setters::Setters;
|
||||
use iced::Radians;
|
||||
use iced::{window, Radians};
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::text::{self, Paragraph, Text};
|
||||
use iced_core::widget::tree::{self, Tree};
|
||||
|
|
@ -14,14 +15,23 @@ use iced_core::{
|
|||
Clipboard, Layout, Length, Padding, Pixels, Rectangle, Shell, Size, Vector, Widget,
|
||||
};
|
||||
use iced_widget::pick_list::{self, Catalog};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, LazyLock, Mutex};
|
||||
|
||||
pub type DropdownView<Message> = Arc<dyn Fn() -> Element<'static, Message> + Send + Sync>;
|
||||
static AUTOSIZE_ID: LazyLock<crate::widget::Id> =
|
||||
LazyLock::new(|| crate::widget::Id::new("cosmic-applet-autosize"));
|
||||
/// A widget for selecting a single value from a list of selections.
|
||||
#[derive(Setters)]
|
||||
pub struct Dropdown<'a, S: AsRef<str>, Message> {
|
||||
pub struct Dropdown<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
#[setters(skip)]
|
||||
on_selected: Box<dyn Fn(usize) -> Message + 'a>,
|
||||
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync>,
|
||||
#[setters(skip)]
|
||||
selections: &'a [S],
|
||||
#[setters]
|
||||
|
|
@ -38,9 +48,29 @@ pub struct Dropdown<'a, S: AsRef<str>, Message> {
|
|||
text_line_height: text::LineHeight,
|
||||
#[setters(strip_option)]
|
||||
font: Option<crate::font::Font>,
|
||||
#[cfg(feature = "wayland")]
|
||||
#[setters(skip)]
|
||||
on_open: Option<
|
||||
Arc<
|
||||
dyn Fn(
|
||||
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||
DropdownView<Message>,
|
||||
) -> Message
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
#[setters(skip)]
|
||||
on_close_popup: Option<Box<dyn Fn(window::Id) -> Message + 'a>>,
|
||||
#[setters(strip_option)]
|
||||
window_id: Option<window::Id>,
|
||||
#[cfg(feature = "wayland")]
|
||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||
}
|
||||
|
||||
impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
|
||||
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static> Dropdown<'a, S, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
/// The default gap.
|
||||
pub const DEFAULT_GAP: f32 = 4.0;
|
||||
|
||||
|
|
@ -52,10 +82,10 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
|
|||
pub fn new(
|
||||
selections: &'a [S],
|
||||
selected: Option<usize>,
|
||||
on_selected: impl Fn(usize) -> Message + 'a,
|
||||
on_selected: impl Fn(usize) -> Message + 'static + Send + Sync,
|
||||
) -> Self {
|
||||
Self {
|
||||
on_selected: Box::new(on_selected),
|
||||
on_selected: Arc::new(on_selected),
|
||||
selections,
|
||||
icons: &[],
|
||||
selected,
|
||||
|
|
@ -65,12 +95,54 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
|
|||
text_size: None,
|
||||
text_line_height: text::LineHeight::Relative(1.2),
|
||||
font: None,
|
||||
#[cfg(feature = "wayland")]
|
||||
on_open: None,
|
||||
window_id: None,
|
||||
#[cfg(feature = "wayland")]
|
||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
|
||||
on_close_popup: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
/// Handle dropdown requests for popup creation.
|
||||
/// Intended to be used with [`crate::app::message::get_popup`]
|
||||
pub fn with_popup(
|
||||
mut self,
|
||||
parent_id: window::Id,
|
||||
on_open: impl Fn(
|
||||
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||
DropdownView<Message>,
|
||||
) -> Message
|
||||
+ 'a,
|
||||
) -> Self {
|
||||
self.window_id = Some(parent_id);
|
||||
self.on_open = Some(Arc::new(on_open));
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
pub fn with_positioner(
|
||||
mut self,
|
||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||
) -> Self {
|
||||
self.positioner = positioner;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
/// Handle dropdown requests for popup removal.
|
||||
/// Intended to be used with [`crate::app::message::destroy_popup`]
|
||||
pub fn on_close_popup(mut self, on_close: impl Fn(window::Id) -> Message + 'a) -> Self {
|
||||
self.on_close_popup = Some(Box::new(on_close));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Renderer>
|
||||
for Dropdown<'a, S, Message>
|
||||
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static + std::clone::Clone>
|
||||
Widget<Message, crate::Theme, crate::Renderer> for Dropdown<'a, S, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
|
|
@ -158,10 +230,22 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
|||
layout,
|
||||
cursor,
|
||||
shell,
|
||||
self.on_selected.as_ref(),
|
||||
#[cfg(feature = "wayland")]
|
||||
self.on_open.clone(),
|
||||
#[cfg(feature = "wayland")]
|
||||
self.positioner.clone(),
|
||||
self.on_selected.clone(),
|
||||
self.selected,
|
||||
self.selections,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
self.window_id,
|
||||
self.on_close_popup.as_deref(),
|
||||
self.icons,
|
||||
self.gap,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
self.font,
|
||||
self.selected,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -211,6 +295,9 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
|||
renderer: &crate::Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
#[cfg(feature = "wayland")]
|
||||
return None;
|
||||
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
overlay(
|
||||
|
|
@ -225,7 +312,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
|||
self.selections,
|
||||
self.icons,
|
||||
self.selected,
|
||||
&self.on_selected,
|
||||
self.on_selected.as_ref(),
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
|
@ -242,8 +329,10 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
|
|||
// }
|
||||
}
|
||||
|
||||
impl<'a, S: AsRef<str>, Message: 'a> From<Dropdown<'a, S, Message>>
|
||||
for crate::Element<'a, Message>
|
||||
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static + std::clone::Clone>
|
||||
From<Dropdown<'a, S, Message>> for crate::Element<'a, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
fn from(pick_list: Dropdown<'a, S, Message>) -> Self {
|
||||
Self::new(pick_list)
|
||||
|
|
@ -251,15 +340,16 @@ impl<'a, S: AsRef<str>, Message: 'a> From<Dropdown<'a, S, Message>>
|
|||
}
|
||||
|
||||
/// The local state of a [`Dropdown`].
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State {
|
||||
icon: Option<svg::Handle>,
|
||||
menu: menu::State,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
is_open: bool,
|
||||
hovered_option: Option<usize>,
|
||||
is_open: Arc<AtomicBool>,
|
||||
hovered_option: Arc<Mutex<Option<usize>>>,
|
||||
hashes: Vec<u64>,
|
||||
selections: Vec<crate::Plain>,
|
||||
popup_id: window::Id,
|
||||
}
|
||||
|
||||
impl State {
|
||||
|
|
@ -276,10 +366,11 @@ impl State {
|
|||
},
|
||||
menu: menu::State::default(),
|
||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
is_open: false,
|
||||
hovered_option: None,
|
||||
is_open: Arc::new(AtomicBool::new(false)),
|
||||
hovered_option: Arc::new(Mutex::new(None)),
|
||||
selections: Vec::new(),
|
||||
hashes: Vec::new(),
|
||||
popup_id: window::Id::unique(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -349,31 +440,111 @@ pub fn layout(
|
|||
/// Processes an [`Event`] and updates the [`State`] of a [`Dropdown`]
|
||||
/// accordingly.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn update<'a, S: AsRef<str>, Message>(
|
||||
pub fn update<
|
||||
'a,
|
||||
S: AsRef<str> + Send + Sync + std::clone::Clone + 'static,
|
||||
Message: Clone + 'static,
|
||||
>(
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor: mouse::Cursor,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
on_selected: &dyn Fn(usize) -> Message,
|
||||
#[cfg(feature = "wayland")] on_open: Option<
|
||||
Arc<
|
||||
dyn Fn(
|
||||
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
|
||||
DropdownView<Message>,
|
||||
) -> Message
|
||||
+ 'a,
|
||||
>,
|
||||
>,
|
||||
#[cfg(feature = "wayland")]
|
||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
|
||||
selected: Option<usize>,
|
||||
selections: &[S],
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
window_id: Option<window::Id>,
|
||||
on_close: Option<&'a dyn Fn(window::Id) -> Message>,
|
||||
icons: &[icon::Handle],
|
||||
gap: f32,
|
||||
padding: Padding,
|
||||
text_size: Option<f32>,
|
||||
font: Option<crate::font::Font>,
|
||||
selected_option: Option<usize>,
|
||||
) -> event::Status {
|
||||
match event {
|
||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
let state = state();
|
||||
|
||||
if state.is_open {
|
||||
let is_open = state.is_open.load(Ordering::Relaxed);
|
||||
if is_open {
|
||||
// Event wasn't processed by overlay, so cursor was clicked either outside it's
|
||||
// bounds or on the drop-down, either way we close the overlay.
|
||||
state.is_open = false;
|
||||
|
||||
state.is_open.store(false, Ordering::Relaxed);
|
||||
if let Some(on_close) = on_close {
|
||||
shell.publish(on_close(state.popup_id));
|
||||
}
|
||||
event::Status::Captured
|
||||
} else if cursor.is_over(layout.bounds()) {
|
||||
state.is_open = true;
|
||||
state.hovered_option = selected;
|
||||
state.is_open.store(true, Ordering::Relaxed);
|
||||
let mut hovered_guard = state.hovered_option.lock().unwrap();
|
||||
*hovered_guard = selected;
|
||||
let id = window::Id::unique();
|
||||
state.popup_id = id;
|
||||
#[cfg(feature = "wayland")]
|
||||
if let Some((on_open, parent)) = on_open.zip(window_id) {
|
||||
use iced_runtime::platform_specific::wayland::popup::{
|
||||
SctkPopupSettings, SctkPositioner,
|
||||
};
|
||||
let bounds = layout.bounds();
|
||||
let anchor_rect = Rectangle {
|
||||
x: bounds.x as i32,
|
||||
y: bounds.y as i32,
|
||||
width: bounds.width as i32,
|
||||
height: bounds.height as i32,
|
||||
};
|
||||
let icon_width = if icons.is_empty() { 0.0 } else { 24.0 };
|
||||
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
|
||||
selection_paragraph.min_width().round()
|
||||
};
|
||||
let pad_width = padding.horizontal().mul_add(2.0, 16.0);
|
||||
|
||||
let selections_width = selections
|
||||
.iter()
|
||||
.zip(state.selections.iter_mut())
|
||||
.map(|(label, selection)| measure(label.as_ref(), selection.raw()))
|
||||
.fold(0.0, |next, current| current.max(next));
|
||||
|
||||
let icons: Cow<'static, [Handle]> = Cow::Owned(icons.to_vec());
|
||||
let selections: Cow<'static, [S]> = Cow::Owned(selections.to_vec());
|
||||
let state = state.clone();
|
||||
|
||||
shell.publish(on_open(
|
||||
SctkPopupSettings {
|
||||
parent,
|
||||
id,
|
||||
positioner: SctkPositioner {
|
||||
size: Some((selections_width as u32 + gap as u32 + pad_width as u32 + icon_width as u32, 10)),
|
||||
anchor_rect,
|
||||
anchor: cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Anchor::Top,
|
||||
gravity:cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity::Bottom,
|
||||
reactive: true,
|
||||
..Default::default()
|
||||
},
|
||||
parent_size: None,
|
||||
grab: true,
|
||||
close_with_children: true
|
||||
},
|
||||
Arc::new(move || {
|
||||
Element::from(
|
||||
menu_widget(
|
||||
bounds, &state, gap, padding,text_size.unwrap_or(14.0), selections.clone(), icons.clone(), selected_option, on_selected.clone()
|
||||
)
|
||||
)
|
||||
}),
|
||||
));
|
||||
}
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
|
|
@ -383,11 +554,9 @@ pub fn update<'a, S: AsRef<str>, Message>(
|
|||
delta: mouse::ScrollDelta::Lines { .. },
|
||||
}) => {
|
||||
let state = state();
|
||||
let is_open = state.is_open.load(Ordering::Relaxed);
|
||||
|
||||
if state.keyboard_modifiers.command()
|
||||
&& cursor.is_over(layout.bounds())
|
||||
&& !state.is_open
|
||||
{
|
||||
if state.keyboard_modifiers.command() && cursor.is_over(layout.bounds()) && !is_open {
|
||||
let next_index = selected.map(|index| index + 1).unwrap_or_default();
|
||||
|
||||
if selections.len() < next_index {
|
||||
|
|
@ -423,9 +592,70 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
/// Returns the current menu widget of a [`Dropdown`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn menu_widget<
|
||||
S: AsRef<str> + Send + Sync + Clone + 'static,
|
||||
Message: 'static + std::clone::Clone,
|
||||
>(
|
||||
bounds: Rectangle,
|
||||
state: &State,
|
||||
gap: f32,
|
||||
padding: Padding,
|
||||
text_size: f32,
|
||||
selections: Cow<'static, [S]>,
|
||||
icons: Cow<'static, [icon::Handle]>,
|
||||
selected_option: Option<usize>,
|
||||
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
|
||||
) -> crate::Element<'static, Message>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
let icon_width = if icons.is_empty() { 0.0 } else { 24.0 };
|
||||
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {
|
||||
selection_paragraph.min_width().round()
|
||||
};
|
||||
let selections_width = selections
|
||||
.iter()
|
||||
.zip(state.selections.iter())
|
||||
.map(|(label, selection)| measure(label.as_ref(), selection.raw()))
|
||||
.fold(0.0, |next, current| current.max(next));
|
||||
let pad_width = padding.horizontal().mul_add(2.0, 16.0);
|
||||
|
||||
let width = selections_width + gap + pad_width + icon_width;
|
||||
let is_open = state.is_open.clone();
|
||||
let menu: Menu<'static, S, Message> = Menu::new(
|
||||
state.menu.clone(),
|
||||
selections,
|
||||
icons,
|
||||
state.hovered_option.clone(),
|
||||
selected_option,
|
||||
move |option| {
|
||||
is_open.store(false, Ordering::Relaxed);
|
||||
|
||||
(on_selected)(option)
|
||||
},
|
||||
None,
|
||||
)
|
||||
.width(width)
|
||||
.padding(padding)
|
||||
.text_size(text_size);
|
||||
|
||||
crate::widget::autosize::autosize(
|
||||
menu.popup(iced::Point::new(0., 0.), bounds.height),
|
||||
AUTOSIZE_ID.clone(),
|
||||
)
|
||||
.auto_height(true)
|
||||
.auto_width(true)
|
||||
.min_height(1.)
|
||||
.min_width(width)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Returns the current overlay of a [`Dropdown`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
|
||||
pub fn overlay<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: std::clone::Clone + 'a>(
|
||||
layout: Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
state: &'a mut State,
|
||||
|
|
@ -439,18 +669,21 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
|
|||
selected_option: Option<usize>,
|
||||
on_selected: &'a dyn Fn(usize) -> Message,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'a, Message, crate::Theme, crate::Renderer>> {
|
||||
if state.is_open {
|
||||
) -> Option<overlay::Element<'a, Message, crate::Theme, crate::Renderer>>
|
||||
where
|
||||
[S]: std::borrow::ToOwned,
|
||||
{
|
||||
if state.is_open.load(Ordering::Relaxed) {
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let menu = Menu::new(
|
||||
&mut state.menu,
|
||||
selections,
|
||||
icons,
|
||||
&mut state.hovered_option,
|
||||
state.menu.clone(),
|
||||
Cow::Borrowed(selections),
|
||||
Cow::Borrowed(icons),
|
||||
state.hovered_option.clone(),
|
||||
selected_option,
|
||||
|option| {
|
||||
state.is_open = false;
|
||||
state.is_open.store(false, Ordering::Relaxed);
|
||||
|
||||
(on_selected)(option)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -306,7 +306,10 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
Density::Spacious => 48.0,
|
||||
Density::Standard => 48.0,
|
||||
};
|
||||
|
||||
let portion = ((start.len().max(end.len()) as f32 / center.len().max(1) as f32).round()
|
||||
as u16)
|
||||
.max(1);
|
||||
let center_empty = center.is_empty() && self.title.is_empty();
|
||||
// Creates the headerbar widget.
|
||||
let mut widget = widget::row::with_capacity(3)
|
||||
// If elements exist in the start region, append them here.
|
||||
|
|
@ -316,7 +319,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
.align_y(iced::Alignment::Center)
|
||||
.apply(widget::container)
|
||||
.align_x(iced::Alignment::Start)
|
||||
.width(Length::Shrink),
|
||||
.width(Length::FillPortion(portion)),
|
||||
)
|
||||
// If elements exist in the center region, use them here.
|
||||
// This will otherwise use the title as a widget if a title was defined.
|
||||
|
|
@ -338,7 +341,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
.align_y(iced::Alignment::Center)
|
||||
.apply(widget::container)
|
||||
.align_x(iced::Alignment::End)
|
||||
.width(Length::Shrink),
|
||||
.width(if center_empty {
|
||||
Length::Fill
|
||||
} else {
|
||||
Length::FillPortion(portion)
|
||||
}),
|
||||
)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.height(Length::Fixed(height))
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
//!
|
||||
|
||||
pub mod action;
|
||||
|
||||
pub use action::MenuAction as Action;
|
||||
|
||||
mod flex;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ where
|
|||
.map(|root| {
|
||||
let mut tree = Tree::empty();
|
||||
let flat = root
|
||||
.flattern()
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|mt| Tree::new(mt.item.as_widget()))
|
||||
.collect();
|
||||
|
|
@ -110,7 +110,7 @@ pub(crate) fn menu_roots_diff<'a, Message, Renderer>(
|
|||
.zip(menu_roots.iter())
|
||||
.for_each(|(t, root)| {
|
||||
let mut flat = root
|
||||
.flattern()
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|mt| {
|
||||
let widget = mt.item.as_widget();
|
||||
|
|
@ -129,7 +129,7 @@ pub(crate) fn menu_roots_diff<'a, Message, Renderer>(
|
|||
let extended = menu_roots[tree.children.len()..].iter().map(|root| {
|
||||
let mut tree = Tree::empty();
|
||||
let flat = root
|
||||
.flattern()
|
||||
.flatten()
|
||||
.iter()
|
||||
.map(|mt| Tree::new(mt.item.as_widget()))
|
||||
.collect();
|
||||
|
|
@ -366,6 +366,8 @@ where
|
|||
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
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
|
@ -437,6 +439,9 @@ where
|
|||
_renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
// #[cfg(feature = "wayland")]
|
||||
// return None;
|
||||
|
||||
let state = tree.state.downcast_ref::<MenuBarState>();
|
||||
if !state.open {
|
||||
return None;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
// From iced_aw, license MIT
|
||||
|
||||
//! Menu tree overlay
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use super::{menu_bar::MenuBarState, menu_tree::MenuTree};
|
||||
use crate::style::menu_bar::StyleSheet;
|
||||
use crate::{style::menu_bar::StyleSheet, widget::dropdown};
|
||||
|
||||
use iced_core::{Border, Shadow};
|
||||
use iced_widget::core::{
|
||||
|
|
@ -240,7 +245,7 @@ struct MenuBounds {
|
|||
impl MenuBounds {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new<Message, Renderer>(
|
||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
||||
menu_tree: &MenuTree<Message>,
|
||||
renderer: &Renderer,
|
||||
item_width: ItemWidth,
|
||||
item_height: ItemHeight,
|
||||
|
|
@ -300,7 +305,7 @@ impl MenuState {
|
|||
overlay_offset: Vector,
|
||||
slice: MenuSlice,
|
||||
renderer: &Renderer,
|
||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
||||
menu_tree: &MenuTree<Message>,
|
||||
tree: &mut [Tree],
|
||||
) -> Node
|
||||
where
|
||||
|
|
@ -354,7 +359,7 @@ impl MenuState {
|
|||
overlay_offset: Vector,
|
||||
index: usize,
|
||||
renderer: &Renderer,
|
||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
||||
menu_tree: &MenuTree<Message>,
|
||||
tree: &mut Tree,
|
||||
) -> Node
|
||||
where
|
||||
|
|
@ -427,12 +432,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<MenuTree<'a, Message, Renderer>>,
|
||||
pub(crate) struct Menu<'b, Message: std::clone::Clone> {
|
||||
pub(crate) tree: dropdown::menu::State,
|
||||
pub(crate) menu_roots: Cow<'b, Vec<MenuTree<Message>>>,
|
||||
pub(crate) bounds_expand: u16,
|
||||
/// Allows menu overlay items to overlap the parent
|
||||
pub(crate) menu_overlays_parent: bool,
|
||||
|
|
@ -444,10 +446,10 @@ where
|
|||
pub(crate) cross_offset: i32,
|
||||
pub(crate) root_bounds_list: Vec<Rectangle>,
|
||||
pub(crate) path_highlight: Option<PathHighlight>,
|
||||
pub(crate) style: &'b <crate::Theme as StyleSheet>::Style,
|
||||
pub(crate) style: Cow<'b, <crate::Theme as StyleSheet>::Style>,
|
||||
pub(crate) position: Point,
|
||||
}
|
||||
impl<'a, 'b, Message, Renderer> Menu<'a, 'b, Message, Renderer>
|
||||
impl<'b, Message: Clone + 'static, Renderer> Menu<'b, Message>
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
|
|
@ -751,7 +753,7 @@ fn pad_rectangle(rect: Rectangle, padding: Padding) -> Rectangle {
|
|||
}
|
||||
|
||||
pub(super) fn init_root_menu<Message, Renderer>(
|
||||
menu: &mut Menu<'_, '_, Message, Renderer>,
|
||||
menu: &mut Menu<'_, Message>,
|
||||
renderer: &Renderer,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
overlay_cursor: Point,
|
||||
|
|
@ -829,7 +831,7 @@ pub(super) fn init_root_menu<Message, Renderer>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn process_menu_events<'b, Message, Renderer>(
|
||||
tree: &'b mut Tree,
|
||||
menu_roots: &'b mut [MenuTree<'_, Message, Renderer>],
|
||||
menu_roots: &'b mut [MenuTree<Message>],
|
||||
event: event::Event,
|
||||
view_cursor: Cursor,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -887,7 +889,7 @@ where
|
|||
|
||||
#[allow(unused_results)]
|
||||
fn process_overlay_events<Message, Renderer>(
|
||||
menu: &mut Menu<'_, '_, Message, Renderer>,
|
||||
menu: &mut Menu<'_, Message>,
|
||||
renderer: &Renderer,
|
||||
viewport_size: Size,
|
||||
overlay_offset: Vector,
|
||||
|
|
@ -897,6 +899,7 @@ fn process_overlay_events<Message, Renderer>(
|
|||
) -> event::Status
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
Message: std::clone::Clone,
|
||||
{
|
||||
use event::Status::{Captured, Ignored};
|
||||
/*
|
||||
|
|
@ -1077,16 +1080,13 @@ where
|
|||
Captured
|
||||
}
|
||||
|
||||
fn process_scroll_events<Message, Renderer>(
|
||||
menu: &mut Menu<'_, '_, Message, Renderer>,
|
||||
fn process_scroll_events<Message: Clone>(
|
||||
menu: &mut Menu<'_, Message>,
|
||||
delta: mouse::ScrollDelta,
|
||||
overlay_cursor: Point,
|
||||
viewport_size: Size,
|
||||
overlay_offset: Vector,
|
||||
) -> event::Status
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
) -> event::Status {
|
||||
use event::Status::{Captured, Ignored};
|
||||
use mouse::ScrollDelta;
|
||||
|
||||
|
|
@ -1155,16 +1155,13 @@ where
|
|||
|
||||
#[allow(clippy::pedantic)]
|
||||
/// Returns (children_size, child_positions, child_sizes)
|
||||
fn get_children_layout<Message, Renderer>(
|
||||
menu_tree: &MenuTree<'_, Message, Renderer>,
|
||||
renderer: &Renderer,
|
||||
fn get_children_layout<Message>(
|
||||
menu_tree: &MenuTree<Message>,
|
||||
renderer: &crate::Renderer,
|
||||
item_width: ItemWidth,
|
||||
item_height: ItemHeight,
|
||||
tree: &mut [Tree],
|
||||
) -> (Size, Vec<f32>, Vec<Size>)
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
) -> (Size, Vec<f32>, Vec<Size>) {
|
||||
let width = match item_width {
|
||||
ItemWidth::Uniform(u) => f32::from(u),
|
||||
ItemWidth::Static(s) => f32::from(menu_tree.width.unwrap_or(s)),
|
||||
|
|
@ -1184,7 +1181,8 @@ where
|
|||
.children
|
||||
.iter()
|
||||
.map(|mt| {
|
||||
let w = mt.item.as_widget();
|
||||
let guard = mt.item.0.lock().unwrap();
|
||||
let w = guard.as_widget();
|
||||
match w.size().height {
|
||||
Length::Fixed(f) => Size::new(width, f),
|
||||
Length::Shrink => {
|
||||
|
|
|
|||
|
|
@ -5,13 +5,14 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use iced_widget::core::{renderer, Element};
|
||||
|
||||
use crate::iced_core::{Alignment, Length};
|
||||
use crate::widget::icon;
|
||||
use crate::widget::menu::action::MenuAction;
|
||||
use crate::widget::menu::key_bind::KeyBind;
|
||||
use crate::widget::{icon, ArcElementWrapper};
|
||||
use crate::{theme, widget};
|
||||
|
||||
/// Nested menu is essentially a tree of items, a menu is a collection of items
|
||||
|
|
@ -23,27 +24,27 @@ 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<Message> {
|
||||
/// 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,
|
||||
|
||||
// TODO needs to be wrapped in Arc<Mutex kinda like in cosmic-files. Check the widget there for details
|
||||
/// The item of the menu tree
|
||||
pub(crate) item: Element<'a, Message, crate::Theme, Renderer>,
|
||||
pub(crate) item: ArcElementWrapper<Message>,
|
||||
/// The children of the menu tree
|
||||
pub(crate) children: Vec<MenuTree<'a, Message, Renderer>>,
|
||||
pub(crate) children: Vec<MenuTree<Message>>,
|
||||
/// The width of the menu tree
|
||||
pub(crate) width: Option<u16>,
|
||||
/// The height of the menu tree
|
||||
pub(crate) height: Option<u16>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> MenuTree<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
impl<Message: Clone + 'static> MenuTree<Message> {
|
||||
/// Create a new menu tree from a widget
|
||||
pub fn new(item: impl Into<Element<'a, Message, crate::Theme, Renderer>>) -> Self {
|
||||
pub fn new(item: impl Into<ArcElementWrapper<Message>>) -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
item: item.into(),
|
||||
|
|
@ -55,8 +56,8 @@ where
|
|||
|
||||
/// Create a menu tree from a widget and a vector of sub trees
|
||||
pub fn with_children(
|
||||
item: impl Into<Element<'a, Message, crate::Theme, Renderer>>,
|
||||
children: Vec<impl Into<MenuTree<'a, Message, Renderer>>>,
|
||||
item: impl Into<ArcElementWrapper<Message>>,
|
||||
children: Vec<impl Into<MenuTree<Message>>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
index: 0,
|
||||
|
|
@ -92,7 +93,7 @@ where
|
|||
/// Set the index of each item
|
||||
pub(crate) fn set_index(&mut self) {
|
||||
/// inner counting function.
|
||||
fn rec<Message, Renderer>(mt: &mut MenuTree<'_, Message, Renderer>, count: &mut usize) {
|
||||
fn rec<Message: Clone + 'static>(mt: &mut MenuTree<Message>, count: &mut usize) {
|
||||
// keep items under the same menu line up
|
||||
mt.children.iter_mut().for_each(|c| {
|
||||
c.index = *count;
|
||||
|
|
@ -109,36 +110,32 @@ where
|
|||
}
|
||||
|
||||
/// Flatten the menu tree
|
||||
pub(crate) fn flattern(&'a self) -> Vec<&Self> {
|
||||
pub(crate) fn flatten(&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<Message: Clone + 'static>(
|
||||
mt: &MenuTree<Message>,
|
||||
flat: &mut Vec<MenuTree<Message>>,
|
||||
) {
|
||||
mt.children.iter().for_each(|c| {
|
||||
mt.children.clone().into_iter().for_each(|c| {
|
||||
flat.push(c);
|
||||
});
|
||||
|
||||
mt.children.iter().for_each(|c| {
|
||||
rec(c, flat);
|
||||
rec(&c, flat);
|
||||
});
|
||||
}
|
||||
|
||||
let mut flat = Vec::new();
|
||||
flat.push(self);
|
||||
flat.push(self.clone());
|
||||
rec(self, &mut flat);
|
||||
|
||||
flat
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Element<'a, Message, crate::Theme, Renderer>>
|
||||
for MenuTree<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn from(value: Element<'a, Message, crate::Theme, Renderer>) -> Self {
|
||||
Self::new(value)
|
||||
impl<Message: Clone + 'static> From<crate::Element<'static, Message>> for MenuTree<Message> {
|
||||
fn from(value: crate::Element<'static, Message>) -> Self {
|
||||
Self::new(ArcElementWrapper(Arc::new(Mutex::new(value))))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,6 +154,7 @@ pub fn menu_button<'a, Message: 'a>(
|
|||
.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.
|
||||
|
|
@ -213,18 +211,13 @@ where
|
|||
/// # Returns
|
||||
/// - A vector of `MenuTree`.
|
||||
pub fn menu_items<
|
||||
'a,
|
||||
A: MenuAction<Message = Message>,
|
||||
L: Into<Cow<'static, str>> + 'static,
|
||||
Message: 'a,
|
||||
Renderer: renderer::Renderer + 'a,
|
||||
Message: 'static + std::clone::Clone,
|
||||
>(
|
||||
key_binds: &HashMap<KeyBind, A>,
|
||||
children: Vec<MenuItem<A, L>>,
|
||||
) -> Vec<MenuTree<'a, Message, Renderer>>
|
||||
where
|
||||
Element<'a, Message, crate::Theme, Renderer>: From<widget::button::Button<'a, Message>>,
|
||||
{
|
||||
) -> Vec<MenuTree<Message>> {
|
||||
fn find_key<A: MenuAction>(action: &A, key_binds: &HashMap<KeyBind, A>) -> String {
|
||||
for (key_bind, key_action) in key_binds {
|
||||
if action == key_action {
|
||||
|
|
@ -259,7 +252,7 @@ where
|
|||
|
||||
let menu_button = menu_button(items).on_press(action.message());
|
||||
|
||||
trees.push(MenuTree::<Message, Renderer>::new(menu_button));
|
||||
trees.push(MenuTree::<Message>::from(Element::from(menu_button)));
|
||||
}
|
||||
MenuItem::ButtonDisabled(label, icon, action) => {
|
||||
let key = find_key(&action, key_binds);
|
||||
|
|
@ -277,7 +270,7 @@ where
|
|||
|
||||
let menu_button = menu_button(items);
|
||||
|
||||
trees.push(MenuTree::<Message, Renderer>::new(menu_button));
|
||||
trees.push(MenuTree::<Message>::from(Element::from(menu_button)));
|
||||
}
|
||||
MenuItem::CheckBox(label, icon, value, action) => {
|
||||
let key = find_key(&action, key_binds);
|
||||
|
|
@ -307,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::<Message, Renderer>::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::<Message>::with_children(
|
||||
ArcElementWrapper(Arc::new(Mutex::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::<Message, Renderer>::new(
|
||||
trees.push(MenuTree::<Message>::from(Element::from(
|
||||
widget::divider::horizontal::light(),
|
||||
));
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ pub mod aspect_ratio;
|
|||
#[cfg(feature = "autosize")]
|
||||
pub mod autosize;
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
pub(crate) mod responsive_container;
|
||||
|
||||
pub mod button;
|
||||
#[doc(inline)]
|
||||
pub use button::{Button, IconButton, LinkButton, TextButton};
|
||||
|
|
@ -366,6 +369,10 @@ pub mod warning;
|
|||
#[doc(inline)]
|
||||
pub use warning::*;
|
||||
|
||||
pub mod wrapper;
|
||||
#[doc(inline)]
|
||||
pub use wrapper::*;
|
||||
|
||||
#[cfg(feature = "markdown")]
|
||||
#[doc(inline)]
|
||||
pub use iced::widget::markdown;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self {
|
||||
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<Message>>>) -> Self {
|
||||
self.segmented_button = self.segmented_button.context_menu(context_menu);
|
||||
self
|
||||
}
|
||||
|
|
|
|||
293
src/widget/responsive_container.rs
Normal file
293
src/widget/responsive_container.rs
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
//! Autosize Container, which will resize the window to its contents.
|
||||
|
||||
use iced::{Limits, Size};
|
||||
use iced_core::event::{self, Event};
|
||||
use iced_core::layout;
|
||||
use iced_core::mouse;
|
||||
use iced_core::overlay;
|
||||
use iced_core::renderer;
|
||||
use iced_core::widget::{tree, Id, Tree};
|
||||
use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Vector, Widget};
|
||||
|
||||
use crate::app::message::SurfaceMessage;
|
||||
|
||||
pub(crate) fn responsive_container<'a, Message: 'static + From<SurfaceMessage>, Theme, E>(
|
||||
content: E,
|
||||
id: Id,
|
||||
) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer>
|
||||
where
|
||||
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
|
||||
Theme: iced_widget::container::Catalog,
|
||||
<Theme as iced_widget::container::Catalog>::Class<'a>: From<crate::theme::Container<'a>>,
|
||||
{
|
||||
ResponsiveContainer::new(content, id)
|
||||
}
|
||||
|
||||
/// An element decorating some content.
|
||||
///
|
||||
/// It is normally used for alignment purposes.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer,
|
||||
{
|
||||
content: Element<'a, Message, Theme, Renderer>,
|
||||
id: Id,
|
||||
size: Option<Size>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer,
|
||||
{
|
||||
/// Creates an empty [`IdContainer`].
|
||||
pub(crate) fn new<T>(content: T, id: Id) -> Self
|
||||
where
|
||||
T: Into<Element<'a, Message, Theme, Renderer>>,
|
||||
{
|
||||
ResponsiveContainer {
|
||||
content: content.into(),
|
||||
id,
|
||||
size: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn size(mut self, size: Size) -> Self {
|
||||
self.size = Some(size);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
|
||||
for ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Renderer: iced_core::Renderer,
|
||||
Message: From<SurfaceMessage>,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.children[0].diff(&mut self.content);
|
||||
}
|
||||
|
||||
fn size(&self) -> iced_core::Size<Length> {
|
||||
self.content.as_widget().size()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let unrestricted_size = self.size.unwrap_or_else(|| {
|
||||
let node =
|
||||
self.content
|
||||
.as_widget()
|
||||
.layout(&mut tree.children[0], renderer, &Limits::NONE);
|
||||
node.size()
|
||||
});
|
||||
|
||||
let max_size = limits.max();
|
||||
let old_max = state.limits.max();
|
||||
state.needs_update = (unrestricted_size.width > max_size.width)
|
||||
^ (state.size.width > old_max.width)
|
||||
|| (unrestricted_size.height > max_size.height) ^ (state.size.height > old_max.height);
|
||||
if state.needs_update {
|
||||
state.limits = *limits;
|
||||
state.size = unrestricted_size;
|
||||
}
|
||||
|
||||
let node = self
|
||||
.content
|
||||
.as_widget()
|
||||
.layout(&mut tree.children[0], renderer, limits);
|
||||
let size = node.size();
|
||||
layout::Node::with_children(size, vec![node])
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn iced_core::widget::Operation<()>,
|
||||
) {
|
||||
operation.container(Some(&self.id), layout.bounds(), &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
operation,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if state.needs_update {
|
||||
shell.publish(Message::from(SurfaceMessage::ResponsiveMenuBar {
|
||||
menu_bar: self.id.clone(),
|
||||
limits: state.limits,
|
||||
size: state.size,
|
||||
}));
|
||||
state.needs_update = false;
|
||||
}
|
||||
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
content_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Theme,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
renderer_style,
|
||||
content_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
&self,
|
||||
state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
self.content.as_widget().drag_destinations(
|
||||
&state.children[0],
|
||||
content_layout,
|
||||
renderer,
|
||||
dnd_rectangles,
|
||||
);
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<crate::widget::Id> {
|
||||
Some(self.id.clone())
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: crate::widget::Id) {
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
/// get the a11y nodes for the widget
|
||||
fn a11y_nodes(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
state: &Tree,
|
||||
p: mouse::Cursor,
|
||||
) -> iced_accessibility::A11yTree {
|
||||
let c_layout = layout.children().next().unwrap();
|
||||
let c_state = &state.children[0];
|
||||
self.content.as_widget().a11y_nodes(c_layout, c_state, p)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> From<ResponsiveContainer<'a, Message, Theme, Renderer>>
|
||||
for Element<'a, Message, Theme, Renderer>
|
||||
where
|
||||
Message: 'a + From<SurfaceMessage>,
|
||||
Renderer: 'a + iced_core::Renderer,
|
||||
Theme: 'a,
|
||||
{
|
||||
fn from(
|
||||
c: ResponsiveContainer<'a, Message, Theme, Renderer>,
|
||||
) -> Element<'a, Message, Theme, Renderer> {
|
||||
Element::new(c)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct State {
|
||||
limits: Limits,
|
||||
size: Size,
|
||||
needs_update: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
limits: Limits::NONE,
|
||||
size: Size::new(0., 0.),
|
||||
needs_update: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ use crate::widget::menu::{
|
|||
self, menu_roots_children, menu_roots_diff, CloseCondition, ItemHeight, ItemWidth,
|
||||
MenuBarState, PathHighlight,
|
||||
};
|
||||
use crate::widget::{icon, Icon};
|
||||
use crate::widget::{dropdown, icon, Icon};
|
||||
use crate::{Element, Renderer};
|
||||
use derive_setters::Setters;
|
||||
use iced::clipboard::dnd::{self, DndAction, DndDestinationRectangle, DndEvent, OfferEvent};
|
||||
|
|
@ -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<Vec<menu::Tree<'a, Message, crate::Renderer>>>,
|
||||
pub(super) context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||
/// Emits the ID of the item that was activated.
|
||||
#[setters(skip)]
|
||||
pub(super) on_activate: Option<Box<dyn Fn(Entity) -> Message + 'static>>,
|
||||
|
|
@ -197,13 +197,13 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<'a, Message>>>) -> Self
|
||||
pub fn context_menu(mut self, context_menu: Option<Vec<menu::Tree<Message>>>) -> 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,
|
||||
)]
|
||||
});
|
||||
|
|
@ -592,6 +592,7 @@ where
|
|||
wheel_timestamp: Default::default(),
|
||||
dnd_state: Default::default(),
|
||||
fingers_pressed: Default::default(),
|
||||
menu_state: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1537,7 +1538,7 @@ where
|
|||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, Renderer>> {
|
||||
let state = tree.state.downcast_ref::<LocalState>();
|
||||
|
||||
let menu_state = state.menu_state.clone();
|
||||
let Some(entity) = state.show_context else {
|
||||
return None;
|
||||
};
|
||||
|
|
@ -1565,8 +1566,8 @@ where
|
|||
|
||||
Some(
|
||||
crate::widget::menu::Menu {
|
||||
tree: &mut tree.children[0],
|
||||
menu_roots: context_menu,
|
||||
tree: menu_state,
|
||||
menu_roots: Cow::Borrowed(context_menu),
|
||||
bounds_expand: 16,
|
||||
menu_overlays_parent: true,
|
||||
close_condition: CloseCondition {
|
||||
|
|
@ -1581,7 +1582,7 @@ where
|
|||
cross_offset: 0,
|
||||
root_bounds_list: vec![bounds],
|
||||
path_highlight: Some(PathHighlight::MenuActive),
|
||||
style: &crate::theme::menu_bar::MenuBarStyle::Default,
|
||||
style: Cow::Owned(crate::theme::menu_bar::MenuBarStyle::Default),
|
||||
position: Point::new(translation.x, translation.y),
|
||||
}
|
||||
.overlay(),
|
||||
|
|
@ -1666,6 +1667,8 @@ pub struct LocalState {
|
|||
pub dnd_state: crate::widget::dnd_destination::State<Option<Entity>>,
|
||||
/// Tracks multi-touch events
|
||||
fingers_pressed: HashSet<Finger>,
|
||||
/// Menu state
|
||||
pub(crate) menu_state: dropdown::menu::State,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
|
|
|
|||
0
src/widget/wayland/tooltip/button_popup.rs
Normal file
0
src/widget/wayland/tooltip/button_popup.rs
Normal file
0
src/widget/wayland/tooltip/dropdown.rs
Normal file
0
src/widget/wayland/tooltip/dropdown.rs
Normal file
155
src/widget/wrapper.rs
Normal file
155
src/widget/wrapper.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::Element;
|
||||
use iced::{event, Length, Rectangle, Size};
|
||||
use iced_core::{id::Id, widget, widget::tree, Widget};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ArcElementWrapper<M>(pub Arc<Mutex<Element<'static, M>>>);
|
||||
|
||||
impl<M> Widget<M, crate::Theme, crate::Renderer> for ArcElementWrapper<M> {
|
||||
fn size(&self) -> Size<Length> {
|
||||
self.0.lock().unwrap().as_widget().size()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> Size<Length> {
|
||||
self.0.lock().unwrap().as_widget().size_hint()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
tree: &mut tree::Tree,
|
||||
renderer: &crate::Renderer,
|
||||
limits: &crate::iced_core::layout::Limits,
|
||||
) -> crate::iced_core::layout::Node {
|
||||
self.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_widget_mut()
|
||||
.layout(tree, renderer, limits)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &tree::Tree,
|
||||
renderer: &mut crate::Renderer,
|
||||
theme: &crate::Theme,
|
||||
style: &crate::iced_core::renderer::Style,
|
||||
layout: crate::iced_core::Layout<'_>,
|
||||
cursor: crate::iced_core::mouse::Cursor,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_widget()
|
||||
.draw(tree, renderer, theme, style, layout, cursor, viewport)
|
||||
}
|
||||
|
||||
fn tag(&self) -> tree::Tag {
|
||||
self.0.lock().unwrap().as_widget().tag()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
self.0.lock().unwrap().as_widget().state()
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<tree::Tree> {
|
||||
self.0.lock().unwrap().as_widget().children()
|
||||
}
|
||||
|
||||
fn diff(&mut self, tree: &mut tree::Tree) {
|
||||
self.0.lock().unwrap().as_widget_mut().diff(tree)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
state: &mut tree::Tree,
|
||||
layout: crate::iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
operation: &mut dyn widget::Operation,
|
||||
) {
|
||||
self.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_widget()
|
||||
.operate(state, layout, renderer, operation)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_state: &mut tree::Tree,
|
||||
_event: crate::iced::Event,
|
||||
_layout: crate::iced_core::Layout<'_>,
|
||||
_cursor: crate::iced_core::mouse::Cursor,
|
||||
_renderer: &crate::Renderer,
|
||||
_clipboard: &mut dyn crate::iced_core::Clipboard,
|
||||
_shell: &mut crate::iced_core::Shell<'_, M>,
|
||||
_viewport: &Rectangle,
|
||||
) -> event::Status {
|
||||
self.0.lock().unwrap().as_widget_mut().on_event(
|
||||
_state, _event, _layout, _cursor, _renderer, _clipboard, _shell, _viewport,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &tree::Tree,
|
||||
_layout: crate::iced_core::Layout<'_>,
|
||||
_cursor: crate::iced_core::mouse::Cursor,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &crate::Renderer,
|
||||
) -> crate::iced_core::mouse::Interaction {
|
||||
self.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_widget()
|
||||
.mouse_interaction(_state, _layout, _cursor, _viewport, _renderer)
|
||||
}
|
||||
|
||||
fn overlay<'a>(
|
||||
&'a mut self,
|
||||
_state: &'a mut tree::Tree,
|
||||
_layout: crate::iced_core::Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
_translation: crate::iced_core::Vector,
|
||||
) -> Option<crate::iced_core::overlay::Element<'a, M, crate::Theme, crate::Renderer>> {
|
||||
// TODO
|
||||
None
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<Id> {
|
||||
self.0.lock().unwrap().as_widget().id()
|
||||
}
|
||||
|
||||
fn set_id(&mut self, _id: Id) {
|
||||
self.0.lock().unwrap().as_widget_mut().set_id(_id)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
&self,
|
||||
_state: &tree::Tree,
|
||||
_layout: crate::iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
_dnd_rectangles: &mut crate::iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.0.lock().unwrap().as_widget().drag_destinations(
|
||||
_state,
|
||||
_layout,
|
||||
renderer,
|
||||
_dnd_rectangles,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message: 'static> From<ArcElementWrapper<Message>> for Element<'static, Message> {
|
||||
fn from(wrapper: ArcElementWrapper<Message>) -> Self {
|
||||
Element::new(wrapper)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Message: 'static> From<Element<'static, Message>> for ArcElementWrapper<Message> {
|
||||
fn from(e: Element<'static, Message>) -> Self {
|
||||
ArcElementWrapper(Arc::new(Mutex::new(e)))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue