refactor: more ergonomic dropdown popups

This commit is contained in:
Ashley Wulber 2025-03-10 22:38:02 -04:00
parent 3ee5c0372d
commit c916c89b03
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
6 changed files with 149 additions and 160 deletions

View file

@ -1,29 +1,19 @@
use cosmic::app::{Core, Task}; use cosmic::app::{Core, Task};
use cosmic::cctk::wayland_protocols::xdg::shell::client::xdg_positioner::Gravity;
use cosmic::iced::event::listen_with;
use cosmic::iced::window::Id; use cosmic::iced::window::Id;
use cosmic::iced::{self, Length, Limits}; use cosmic::iced::Length;
use cosmic::iced_runtime::core::window; use cosmic::iced_runtime::core::window;
use cosmic::iced_runtime::platform_specific::wayland::popup::SctkPopupSettings;
use cosmic::iced_runtime::platform_specific::wayland::subsurface;
use cosmic::surface::action::{app_popup, destroy_popup}; use cosmic::surface::action::{app_popup, destroy_popup};
use cosmic::widget::dropdown::DropdownView; use cosmic::widget::{dropdown::popup_dropdown, list_column, settings, toggler};
use cosmic::widget::{autosize, dropdown, layer_container, list_column, settings, toggler}; use cosmic::Element;
use cosmic::{iced_core, Element};
use once_cell::sync::Lazy;
const ID: &str = "com.system76.CosmicAppletExample"; const ID: &str = "com.system76.CosmicAppletExample";
static SUBSURFACE_ID: Lazy<cosmic::widget::Id> =
Lazy::new(|| cosmic::widget::Id::new("subsurface"));
pub struct Window { pub struct Window {
core: Core, core: Core,
popup: Option<Id>, popup: Option<Id>,
example_row: bool, example_row: bool,
selected: Option<usize>, selected: Option<usize>,
subsurface_id: Id,
dropdown_id: Id,
} }
impl Default for Window { impl Default for Window {
@ -33,52 +23,18 @@ impl Default for Window {
popup: None, popup: None,
example_row: false, example_row: false,
selected: None, selected: None,
subsurface_id: Id::unique(),
dropdown_id: Id::unique(),
} }
} }
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
PopupClosed(Id), PopupClosed(Id),
PopupCloseRequested(Id),
ToggleExampleRow(bool), ToggleExampleRow(bool),
Selected(usize), Selected(usize),
Surface(cosmic::surface::Action), Surface(cosmic::surface::Action),
OpenDropdown(SctkPopupSettings, DropdownView<Message>),
} }
impl std::fmt::Debug for Message {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::PopupClosed(arg0) => f.debug_tuple("PopupClosed").field(arg0).finish(),
Self::PopupCloseRequested(arg0) => {
f.debug_tuple("PopupCloseRequested").field(arg0).finish()
}
Self::ToggleExampleRow(arg0) => f.debug_tuple("ToggleExampleRow").field(arg0).finish(),
Self::Selected(arg0) => f.debug_tuple("Selected").field(arg0).finish(),
Self::Surface(arg0) => f.debug_tuple("Surface").field(arg0).finish(),
Self::OpenDropdown(arg0, _) => f.debug_tuple("OpenDropdown").field(arg0).finish(),
}
}
}
// impl From<Message> for MessageWrapper<Message> {
// fn from(value: Message) -> Self {
// match value {
// Message::Surface(s) => MessageWrapper::Surface(s),
// m => MessageWrapper::Message(m),
// }
// }
// }
// impl From<SurfaceMessage> for Message {
// fn from(value: SurfaceMessage) -> Self {
// Message::Surface(value)
// }
// }
impl cosmic::Application for Window { impl cosmic::Application for Window {
type Executor = cosmic::SingleThreadExecutor; type Executor = cosmic::SingleThreadExecutor;
type Flags = (); type Flags = ();
@ -123,25 +79,6 @@ impl cosmic::Application for Window {
} }
Message::Selected(i) => { Message::Selected(i) => {
self.selected = Some(i); self.selected = Some(i);
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(cosmic::surface::action::destroy_popup(
self.dropdown_id,
)),
));
}
Message::OpenDropdown(sctk_popup_settings, view) => {
self.dropdown_id = sctk_popup_settings.id;
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(cosmic::surface::action::app_popup(
move |_: &mut Window| sctk_popup_settings.clone(),
Some(Box::new(move |_: &Window| view().map(cosmic::Action::App))),
)),
));
}
Message::PopupCloseRequested(id) => {
return cosmic::task::message(cosmic::Action::Cosmic(
cosmic::app::Action::Surface(cosmic::surface::action::destroy_popup(id)),
));
} }
}; };
Task::none() Task::none()
@ -178,15 +115,14 @@ impl cosmic::Application for Window {
) )
.height(Length::Fixed(50.)), .height(Length::Fixed(50.)),
)) ))
.add( .add(popup_dropdown(
dropdown( &["1", "asdf", "hello", "test"],
&["1", "asdf", "hello", "test"], state.selected,
state.selected, Message::Selected,
Message::Selected, state.popup.unwrap_or(Id::NONE),
) Message::Surface,
.with_popup(state.popup.unwrap_or(Id::NONE), Message::OpenDropdown) |m| m,
.on_close_popup(Message::PopupCloseRequested), ));
);
Element::from(state.core.applet.popup_container(content_list)) Element::from(state.core.applet.popup_container(content_list))
.map(cosmic::Action::App) .map(cosmic::Action::App)
})), })),

2
iced

@ -1 +1 @@
Subproject commit fd229ebf90e0873f0f40a3906f6d92bceae56ba2 Subproject commit ea5570fdd756f599637a60c7fed4b8a4f03f049e

View file

@ -43,7 +43,6 @@ pub async fn spawn(mut command: Command) -> Option<u32> {
return None; return None;
}; };
match unsafe { libc::fork() } { match unsafe { libc::fork() } {
// Parent process // Parent process
1.. => { 1.. => {

View file

@ -8,6 +8,7 @@ use std::sync::{Arc, Mutex};
pub use appearance::{Appearance, StyleSheet}; pub use appearance::{Appearance, StyleSheet};
use crate::surface;
use crate::widget::{icon, Container, RcWrapper}; use crate::widget::{icon, Container, RcWrapper};
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::layout::{self, Layout}; use iced_core::layout::{self, Layout};
@ -32,6 +33,7 @@ where
hovered_option: Arc<Mutex<Option<usize>>>, hovered_option: Arc<Mutex<Option<usize>>>,
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: Box<dyn FnMut(usize) -> Message + 'a>, on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
close_on_selected: Option<Message>,
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>, on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
width: f32, width: f32,
padding: Padding, padding: Padding,
@ -54,6 +56,7 @@ where
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: impl FnMut(usize) -> Message + 'a, on_selected: impl FnMut(usize) -> Message + 'a,
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>, on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
close_on_selected: Option<Message>,
) -> Self { ) -> Self {
Menu { Menu {
state, state,
@ -68,6 +71,7 @@ where
text_size: None, text_size: None,
text_line_height: text::LineHeight::default(), text_line_height: text::LineHeight::default(),
style: Default::default(), style: Default::default(),
close_on_selected,
} }
} }
@ -153,7 +157,7 @@ struct Overlay<'a, Message> {
position: Point, position: Point,
} }
impl<'a, Message: 'a> Overlay<'a, Message> { impl<'a, Message: Clone + 'a> Overlay<'a, Message> {
pub fn new<S: AsRef<str>>( pub fn new<S: AsRef<str>>(
menu: Menu<'a, S, Message>, menu: Menu<'a, S, Message>,
target_height: f32, target_height: f32,
@ -175,6 +179,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
text_size, text_size,
text_line_height, text_line_height,
style, style,
close_on_selected,
} = menu; } = menu;
let mut container = Container::new(Scrollable::new( let mut container = Container::new(Scrollable::new(
@ -184,6 +189,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
hovered_option, hovered_option,
selected_option, selected_option,
on_selected, on_selected,
close_on_selected,
on_option_hovered, on_option_hovered,
text_size, text_size,
text_line_height, text_line_height,
@ -297,7 +303,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
} }
} }
impl<'a, Message: 'a> iced_core::Overlay<Message, crate::Theme, crate::Renderer> impl<'a, Message: Clone + 'a> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
for Overlay<'a, Message> for Overlay<'a, Message>
{ {
fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node { fn layout(&mut self, renderer: &crate::Renderer, bounds: Size) -> layout::Node {
@ -338,7 +344,7 @@ impl<'a, Message: 'a> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
} }
} }
impl<'a, Message: 'a> crate::widget::Widget<Message, crate::Theme, crate::Renderer> impl<'a, Message: Clone + 'a> crate::widget::Widget<Message, crate::Theme, crate::Renderer>
for Overlay<'a, Message> for Overlay<'a, Message>
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
@ -411,6 +417,7 @@ where
hovered_option: Arc<Mutex<Option<usize>>>, hovered_option: Arc<Mutex<Option<usize>>>,
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: Box<dyn FnMut(usize) -> Message + 'a>, on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
close_on_selected: Option<Message>,
on_option_hovered: Option<&'a dyn Fn(usize) -> Message>, on_option_hovered: Option<&'a dyn Fn(usize) -> Message>,
padding: Padding, padding: Padding,
text_size: Option<f32>, text_size: Option<f32>,
@ -420,6 +427,7 @@ where
impl<S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer> for List<'_, S, Message> impl<S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer> for List<'_, S, Message>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
Message: Clone,
{ {
fn size(&self) -> Size<Length> { fn size(&self) -> Size<Length> {
Size::new(Length::Fill, Length::Shrink) Size::new(Length::Fill, Length::Shrink)
@ -469,6 +477,9 @@ where
if cursor.is_over(layout.bounds()) { if cursor.is_over(layout.bounds()) {
if let Some(index) = *hovered_guard { if let Some(index) = *hovered_guard {
shell.publish((self.on_selected)(index)); shell.publish((self.on_selected)(index));
if let Some(close_on_selected) = self.close_on_selected.clone() {
shell.publish(close_on_selected);
}
return event::Status::Captured; return event::Status::Captured;
} }
} }
@ -510,6 +521,9 @@ where
if let Some(index) = *hovered_guard { if let Some(index) = *hovered_guard {
shell.publish((self.on_selected)(index)); shell.publish((self.on_selected)(index));
if let Some(close_on_selected) = self.close_on_selected.clone() {
shell.publish(close_on_selected);
}
return event::Status::Captured; return event::Status::Captured;
} }
} }
@ -680,6 +694,7 @@ impl<'a, S: AsRef<str>, Message: 'a> From<List<'a, S, Message>>
for Element<'a, Message, crate::Theme, crate::Renderer> for Element<'a, Message, crate::Theme, crate::Renderer>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
Message: Clone,
{ {
fn from(list: List<'a, S, Message>) -> Self { fn from(list: List<'a, S, Message>) -> Self {
Element::new(list) Element::new(list)

View file

@ -5,6 +5,7 @@
//! Displays a list of options in a popover menu on select. //! Displays a list of options in a popover menu on select.
pub mod menu; pub mod menu;
use iced_core::window;
pub use menu::Menu; pub use menu::Menu;
pub mod multi; pub mod multi;
@ -12,6 +13,8 @@ pub mod multi;
mod widget; mod widget;
pub use widget::*; pub use widget::*;
use crate::surface;
/// Displays a list of options in a popover menu on select. /// Displays a list of options in a popover menu on select.
pub fn dropdown< pub fn dropdown<
S: AsRef<str> + std::clone::Clone + Send + Sync + 'static, S: AsRef<str> + std::clone::Clone + Send + Sync + 'static,
@ -20,6 +23,29 @@ pub fn dropdown<
selections: &[S], selections: &[S],
selected: Option<usize>, selected: Option<usize>,
on_selected: impl Fn(usize) -> Message + Send + Sync + 'static, on_selected: impl Fn(usize) -> Message + Send + Sync + 'static,
) -> Dropdown<'_, S, Message> { ) -> Dropdown<'_, S, Message, Message> {
Dropdown::new(selections, selected, on_selected) Dropdown::new(selections, selected, on_selected)
} }
#[cfg(all(feature = "winit", feature = "wayland"))]
/// Displays a list of options in a popover menu on select.
/// AppMessage must be the App's toplevel message.
pub fn popup_dropdown<
'a,
S: AsRef<str> + std::clone::Clone + Send + Sync + 'static,
Message: 'static + Clone,
AppMessage: 'static + Clone,
>(
selections: &'a [S],
selected: Option<usize>,
on_selected: impl Fn(usize) -> Message + Send + Sync + 'static,
parent_id: window::Id,
on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static,
map_action: impl Fn(Message) -> AppMessage + Send + Sync + 'static,
) -> Dropdown<'a, S, Message, AppMessage> {
Dropdown::new(selections, selected, on_selected).with_popup(
parent_id,
on_surface_action,
map_action,
)
}

View file

@ -4,7 +4,7 @@
use super::menu::{self, Menu}; use super::menu::{self, Menu};
use crate::widget::icon::{self, Handle}; use crate::widget::icon::{self, Handle};
use crate::Element; use crate::{surface, Element};
use derive_setters::Setters; use derive_setters::Setters;
use iced::window; use iced::window;
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
@ -18,6 +18,7 @@ use iced_widget::pick_list::{self, Catalog};
use std::borrow::Cow; use std::borrow::Cow;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
use std::marker::PhantomData;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, LazyLock, Mutex}; use std::sync::{Arc, LazyLock, Mutex};
@ -26,7 +27,7 @@ static AUTOSIZE_ID: LazyLock<crate::widget::Id> =
LazyLock::new(|| crate::widget::Id::new("cosmic-applet-autosize")); LazyLock::new(|| crate::widget::Id::new("cosmic-applet-autosize"));
/// A widget for selecting a single value from a list of selections. /// A widget for selecting a single value from a list of selections.
#[derive(Setters)] #[derive(Setters)]
pub struct Dropdown<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message> pub struct Dropdown<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message, AppMessage>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
{ {
@ -48,26 +49,18 @@ where
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
#[setters(strip_option)] #[setters(strip_option)]
font: Option<crate::font::Font>, font: Option<crate::font::Font>,
#[cfg(feature = "wayland")]
#[setters(skip)] #[setters(skip)]
on_open: Option< on_surface_action: Option<Arc<dyn Fn(surface::Action) -> Message + Send + Sync + 'static>>,
Arc<
dyn Fn(
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings,
DropdownView<Message>,
) -> Message
+ 'a,
>,
>,
#[setters(skip)] #[setters(skip)]
on_close_popup: Option<Box<dyn Fn(window::Id) -> Message + 'a>>, action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>,
#[setters(strip_option)] #[setters(strip_option)]
window_id: Option<window::Id>, window_id: Option<window::Id>,
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
} }
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static> Dropdown<'a, S, Message> impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static, AppMessage: 'static>
Dropdown<'a, S, Message, AppMessage>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
{ {
@ -95,33 +88,30 @@ where
text_size: None, text_size: None,
text_line_height: text::LineHeight::Relative(1.2), text_line_height: text::LineHeight::Relative(1.2),
font: None, font: None,
#[cfg(feature = "wayland")]
on_open: None,
window_id: None, window_id: None,
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(), positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner::default(),
on_close_popup: None, on_surface_action: None,
action_map: None,
} }
} }
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
/// Handle dropdown requests for popup creation. /// Handle dropdown requests for popup creation.
/// Intended to be used with [`crate::app::message::get_popup`] /// Intended to be used with [`crate::app::message::get_popup`]
pub fn with_popup( pub fn with_popup(
mut self, mut self,
parent_id: window::Id, parent_id: window::Id,
on_open: impl Fn( on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static,
iced_runtime::platform_specific::wayland::popup::SctkPopupSettings, action_map: impl Fn(Message) -> AppMessage + Send + Sync + 'static,
DropdownView<Message>,
) -> Message
+ 'a,
) -> Self { ) -> Self {
self.window_id = Some(parent_id); self.window_id = Some(parent_id);
self.on_open = Some(Arc::new(on_open)); self.on_surface_action = Some(Arc::new(on_surface_action));
self.action_map = Some(Arc::new(action_map));
self self
} }
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
pub fn with_positioner( pub fn with_positioner(
mut self, mut self,
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
@ -129,18 +119,13 @@ where
self.positioner = positioner; self.positioner = positioner;
self 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<S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static + std::clone::Clone> impl<
Widget<Message, crate::Theme, crate::Renderer> for Dropdown<'_, S, Message> S: AsRef<str> + Send + Sync + Clone + 'static,
Message: 'static + Clone,
AppMessage: 'static + Clone,
> Widget<Message, crate::Theme, crate::Renderer> for Dropdown<'_, S, Message, AppMessage>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
{ {
@ -225,21 +210,20 @@ where
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
_viewport: &Rectangle, _viewport: &Rectangle,
) -> event::Status { ) -> event::Status {
update( update::<S, Message, AppMessage>(
&event, &event,
layout, layout,
cursor, cursor,
shell, shell,
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
self.on_open.clone(),
#[cfg(feature = "wayland")]
self.positioner.clone(), self.positioner.clone(),
self.on_selected.clone(), self.on_selected.clone(),
self.selected, self.selected,
self.selections, self.selections,
|| tree.state.downcast_mut::<State>(), || tree.state.downcast_mut::<State>(),
self.window_id, self.window_id,
self.on_close_popup.as_deref(), self.on_surface_action.clone(),
self.action_map.clone(),
self.icons, self.icons,
self.gap, self.gap,
self.padding, self.padding,
@ -295,8 +279,8 @@ where
renderer: &crate::Renderer, renderer: &crate::Renderer,
translation: Vector, translation: Vector,
) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> { ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
if self.on_open.is_some() || self.window_id.is_some() || self.on_close_popup.is_some() { if self.window_id.is_some() || self.on_surface_action.is_some() {
return None; return None;
} }
@ -316,6 +300,7 @@ where
self.selected, self.selected,
self.on_selected.as_ref(), self.on_selected.as_ref(),
translation, translation,
None,
) )
} }
@ -331,12 +316,16 @@ where
// } // }
} }
impl<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: 'static + std::clone::Clone> impl<
From<Dropdown<'a, S, Message>> for crate::Element<'a, Message> 'a,
S: AsRef<str> + Send + Sync + Clone + 'static,
Message: 'static + std::clone::Clone,
AppMessage: 'static + std::clone::Clone,
> From<Dropdown<'a, S, Message, AppMessage>> for crate::Element<'a, Message>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
{ {
fn from(pick_list: Dropdown<'a, S, Message>) -> Self { fn from(pick_list: Dropdown<'a, S, Message, AppMessage>) -> Self {
Self::new(pick_list) Self::new(pick_list)
} }
} }
@ -441,33 +430,26 @@ pub fn layout(
/// Processes an [`Event`] and updates the [`State`] of a [`Dropdown`] /// Processes an [`Event`] and updates the [`State`] of a [`Dropdown`]
/// accordingly. /// accordingly.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn update< pub fn update<
'a, 'a,
S: AsRef<str> + Send + Sync + std::clone::Clone + 'static, S: AsRef<str> + Send + Sync + Clone + 'static,
Message: Clone + 'static, Message: Clone + 'static,
AppMessage: Clone + 'static,
>( >(
event: &Event, event: &Event,
layout: Layout<'_>, layout: Layout<'_>,
cursor: mouse::Cursor, cursor: mouse::Cursor,
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
#[cfg(feature = "wayland")] on_open: Option< #[cfg(all(feature = "winit", feature = "wayland"))]
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, positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>, on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
selected: Option<usize>, selected: Option<usize>,
selections: &[S], selections: &[S],
state: impl FnOnce() -> &'a mut State, state: impl FnOnce() -> &'a mut State,
window_id: Option<window::Id>, _window_id: Option<window::Id>,
on_close: Option<&'a dyn Fn(window::Id) -> Message>, on_surface_action: Option<Arc<dyn Fn(surface::Action) -> Message + Send + Sync + 'static>>,
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + Send + Sync + 'static>>,
icons: &[icon::Handle], icons: &[icon::Handle],
gap: f32, gap: f32,
padding: Padding, padding: Padding,
@ -484,8 +466,9 @@ pub fn update<
// Event wasn't processed by overlay, so cursor was clicked either outside it's // 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. // bounds or on the drop-down, either way we close the overlay.
state.is_open.store(false, Ordering::Relaxed); state.is_open.store(false, Ordering::Relaxed);
if let Some(on_close) = on_close { #[cfg(all(feature = "winit", feature = "wayland"))]
shell.publish(on_close(state.popup_id)); if let Some(on_close) = on_surface_action {
shell.publish(on_close(surface::action::destroy_popup(state.popup_id)));
} }
event::Status::Captured event::Status::Captured
} else if cursor.is_over(layout.bounds()) { } else if cursor.is_over(layout.bounds()) {
@ -494,8 +477,10 @@ pub fn update<
*hovered_guard = selected; *hovered_guard = selected;
let id = window::Id::unique(); let id = window::Id::unique();
state.popup_id = id; state.popup_id = id;
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
if let Some((on_open, parent)) = on_open.zip(window_id) { if let Some(((on_surface_action, parent), action_map)) =
on_surface_action.zip(_window_id).zip(action_map)
{
use iced_runtime::platform_specific::wayland::popup::{ use iced_runtime::platform_specific::wayland::popup::{
SctkPopupSettings, SctkPositioner, SctkPopupSettings, SctkPositioner,
}; };
@ -521,9 +506,19 @@ pub fn update<
let icons: Cow<'static, [Handle]> = Cow::Owned(icons.to_vec()); let icons: Cow<'static, [Handle]> = Cow::Owned(icons.to_vec());
let selections: Cow<'static, [S]> = Cow::Owned(selections.to_vec()); let selections: Cow<'static, [S]> = Cow::Owned(selections.to_vec());
let state = state.clone(); let state = state.clone();
let on_close = surface::action::destroy_popup(id);
shell.publish(on_open( let on_surface_action_clone = on_surface_action.clone();
SctkPopupSettings { let get_popup_action = surface::action::simple_popup::<
AppMessage,
Box<
dyn Fn() -> Element<'static, crate::Action<AppMessage>>
+ Send
+ Sync
+ 'static,
>,
>(
move || {
SctkPopupSettings {
parent, parent,
id, id,
input_zone: None, input_zone: None,
@ -538,15 +533,29 @@ pub fn update<
parent_size: None, parent_size: None,
grab: true, grab: true,
close_with_children: true, close_with_children: true,
}
}, },
Arc::new(move || { Some(Box::new(move || {
Element::from( let action_map = action_map.clone();
menu_widget( let on_selected = on_selected.clone();
bounds, &state, gap, padding,text_size.unwrap_or(14.0), selections.clone(), icons.clone(), selected_option, on_selected.clone() let e: Element<'static, crate::Action<AppMessage>> =
) Element::from(menu_widget(
) bounds,
}), &state,
)); gap,
padding,
text_size.unwrap_or(14.0),
selections.clone(),
icons.clone(),
selected_option,
Arc::new(move |i| on_selected.clone()(i)),
Some(on_surface_action_clone(on_close.clone())),
))
.map(move |m| crate::Action::App(action_map.clone()(m)));
e
})),
);
shell.publish(on_surface_action(get_popup_action));
} }
event::Status::Captured event::Status::Captured
} else { } else {
@ -595,7 +604,7 @@ pub fn mouse_interaction(layout: Layout<'_>, cursor: mouse::Cursor) -> mouse::In
} }
} }
#[cfg(feature = "wayland")] #[cfg(all(feature = "winit", feature = "wayland"))]
/// Returns the current menu widget of a [`Dropdown`]. /// Returns the current menu widget of a [`Dropdown`].
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn menu_widget< pub fn menu_widget<
@ -611,6 +620,7 @@ pub fn menu_widget<
icons: Cow<'static, [icon::Handle]>, icons: Cow<'static, [icon::Handle]>,
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>, on_selected: Arc<dyn Fn(usize) -> Message + Send + Sync + 'static>,
close_on_selected: Option<Message>,
) -> crate::Element<'static, Message> ) -> crate::Element<'static, Message>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
@ -640,6 +650,7 @@ where
(on_selected)(option) (on_selected)(option)
}, },
None, None,
close_on_selected,
) )
.width(width) .width(width)
.padding(padding) .padding(padding)
@ -672,6 +683,7 @@ pub fn overlay<'a, S: AsRef<str> + Send + Sync + Clone + 'static, Message: std::
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: &'a dyn Fn(usize) -> Message, on_selected: &'a dyn Fn(usize) -> Message,
translation: Vector, translation: Vector,
close_on_selected: Option<Message>,
) -> Option<overlay::Element<'a, Message, crate::Theme, crate::Renderer>> ) -> Option<overlay::Element<'a, Message, crate::Theme, crate::Renderer>>
where where
[S]: std::borrow::ToOwned, [S]: std::borrow::ToOwned,
@ -691,6 +703,7 @@ where
(on_selected)(option) (on_selected)(option)
}, },
None, None,
close_on_selected,
) )
.width({ .width({
let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 { let measure = |_label: &str, selection_paragraph: &crate::Paragraph| -> f32 {