merge: upstream/master vers epoch-1.8.0
# Conflicts: # iced # src/surface/action.rs # src/surface/mod.rs # src/widget/text_input/input.rs # src/widget/toaster/widget.rs
This commit is contained in:
commit
c60495c66c
32 changed files with 773 additions and 419 deletions
|
|
@ -176,6 +176,7 @@ zbus = { workspace = true, optional = true }
|
|||
float-cmp = "0.10.0"
|
||||
ron = { workspace = true, optional = true }
|
||||
enumflags2 = "0.7.12"
|
||||
roxmltree = "0.20"
|
||||
|
||||
# Enable DBus feature on Linux targets
|
||||
[target.'cfg(all(unix, not(any(target_os = "redox", target_family = "wasm", target_os = "android", target_vendor = "apple"))))'.dependencies]
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 5252095787cc96e2aed64604158f94e450703455
|
||||
Subproject commit 343c007f37cd71716e68f01c43ecf2764b7f7c47
|
||||
|
|
@ -35,7 +35,7 @@ pub enum Message {
|
|||
PopupClosed(Id),
|
||||
ToggleExampleRow(bool),
|
||||
Selected(usize),
|
||||
Surface(cosmic::surface::Action),
|
||||
Surface(cosmic::surface::Action<Message>),
|
||||
Toggle(bool),
|
||||
}
|
||||
|
||||
|
|
@ -76,9 +76,7 @@ impl cosmic::Application for Window {
|
|||
self.example_row = toggled;
|
||||
}
|
||||
Message::Surface(a) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
));
|
||||
return cosmic::task::message(cosmic::Action::Surface(a));
|
||||
}
|
||||
Message::Selected(i) => {
|
||||
self.selected = Some(i);
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ pub enum Message {
|
|||
Input2(String),
|
||||
Ignore,
|
||||
ToggleHide,
|
||||
Surface(cosmic::surface::Action),
|
||||
Surface(cosmic::surface::Action<Message>),
|
||||
Hi,
|
||||
Hi2,
|
||||
Hi3,
|
||||
|
|
@ -255,9 +255,7 @@ impl cosmic::Application for App {
|
|||
self.hidden = !self.hidden;
|
||||
}
|
||||
Message::Surface(a) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(a),
|
||||
));
|
||||
return cosmic::task::message(cosmic::Action::Surface(a));
|
||||
}
|
||||
Message::Hi => {
|
||||
dbg!("hi");
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
pub enum Message {
|
||||
Clicked,
|
||||
WindowClose,
|
||||
Surface(cosmic::surface::Action),
|
||||
Surface(cosmic::surface::Action<Message>),
|
||||
ToggleHideContent,
|
||||
ToggleSomeAction,
|
||||
WindowNew,
|
||||
|
|
@ -89,9 +89,7 @@ impl cosmic::Application for App {
|
|||
self.button_label = format!("Clicked {message:?}");
|
||||
}
|
||||
Message::Surface(action) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(action),
|
||||
));
|
||||
return cosmic::task::message(cosmic::Action::Surface(action));
|
||||
}
|
||||
Message::WindowClose
|
||||
| Message::ToggleHideContent
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ pub enum Message {
|
|||
OpenError(Arc<file_chooser::Error>),
|
||||
OpenFile,
|
||||
Selected(Url),
|
||||
Surface(cosmic::surface::Action),
|
||||
Surface(cosmic::surface::Action<Message>),
|
||||
}
|
||||
|
||||
/// The [`App`] stores application-specific state.
|
||||
|
|
@ -187,9 +187,7 @@ impl cosmic::Application for App {
|
|||
self.error_status = None;
|
||||
}
|
||||
Message::Surface(action) => {
|
||||
return cosmic::task::message(cosmic::Action::Cosmic(
|
||||
cosmic::app::Action::Surface(action),
|
||||
));
|
||||
return cosmic::task::message(cosmic::Action::Surface(action));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
2
iced
2
iced
|
|
@ -1 +1 @@
|
|||
Subproject commit e891f34b3224b25fc4bdf286b3f9dba26c8abe5b
|
||||
Subproject commit d12bb8e056712ab87efbdd4296261331772ec413
|
||||
|
|
@ -16,6 +16,11 @@ pub const fn none<M>() -> Action<M> {
|
|||
Action::None
|
||||
}
|
||||
|
||||
/// Wrap a surface action, typically produced by a widget, to be handled by libcosmic.
|
||||
pub const fn surface<M>(action: crate::surface::Action<M>) -> Action<M> {
|
||||
Action::Surface(action)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[must_use]
|
||||
pub enum Action<M> {
|
||||
|
|
@ -26,10 +31,45 @@ pub enum Action<M> {
|
|||
#[cfg(feature = "single-instance")]
|
||||
/// Dbus activation messages
|
||||
DbusActivation(dbus_activation::Message),
|
||||
/// Surface (popup, subsurface, window, layer shell) requests, handled by libcosmic.
|
||||
Surface(crate::surface::Action<M>),
|
||||
/// Do nothing
|
||||
None,
|
||||
}
|
||||
|
||||
impl<M: 'static> Action<M> {
|
||||
/// Map the application message inside, leaving libcosmic's own variants untouched.
|
||||
#[must_use]
|
||||
pub fn map<N: 'static>(self, f: impl Fn(M) -> N + Clone + Send + Sync + 'static) -> Action<N> {
|
||||
match self {
|
||||
Action::App(message) => Action::App(f(message)),
|
||||
#[cfg(feature = "winit")]
|
||||
Action::Cosmic(action) => Action::Cosmic(action),
|
||||
#[cfg(feature = "single-instance")]
|
||||
Action::DbusActivation(message) => Action::DbusActivation(message),
|
||||
Action::Surface(action) => Action::Surface(action.map(f)),
|
||||
Action::None => Action::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: 'static> Action<Action<M>> {
|
||||
/// Collapse a doubly wrapped action, as produced by widgets whose message type is already
|
||||
/// an [`Action`], into a single one.
|
||||
#[must_use]
|
||||
pub fn flatten(self) -> Action<M> {
|
||||
match self {
|
||||
Action::App(action) => action,
|
||||
#[cfg(feature = "winit")]
|
||||
Action::Cosmic(action) => Action::Cosmic(action),
|
||||
#[cfg(feature = "single-instance")]
|
||||
Action::DbusActivation(message) => Action::DbusActivation(message),
|
||||
Action::Surface(action) => Action::Surface(action.flatten()),
|
||||
Action::None => Action::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> From<M> for Action<M> {
|
||||
fn from(value: M) -> Self {
|
||||
Self::App(value)
|
||||
|
|
|
|||
|
|
@ -45,8 +45,6 @@ pub enum Action {
|
|||
/// Tracks updates to window suggested size.
|
||||
#[cfg(feature = "applet")]
|
||||
SuggestedBounds(Option<iced::Size>),
|
||||
/// Internal surface message
|
||||
Surface(surface::Action),
|
||||
/// Notifies that a surface was closed.
|
||||
/// Any data relating to the surface should be cleaned up.
|
||||
SurfaceClosed(iced::window::Id),
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ where
|
|||
#[allow(clippy::too_many_lines)]
|
||||
pub fn surface_update(
|
||||
&mut self,
|
||||
_surface_message: crate::surface::Action,
|
||||
_surface_message: crate::surface::Action<T::Message>,
|
||||
) -> iced::Task<crate::Action<T::Message>> {
|
||||
#[cfg(feature = "surface-message")]
|
||||
match _surface_message {
|
||||
|
|
@ -194,18 +194,7 @@ where
|
|||
};
|
||||
let settings = settings();
|
||||
|
||||
if let Some(view) = view.and_then(|view| {
|
||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
||||
>>() {
|
||||
Ok(v) => Some(v),
|
||||
Err(err) => {
|
||||
tracing::error!("Invalid view for subsurface view: {err:?}");
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}) {
|
||||
if let Some(view) = view {
|
||||
self.get_subsurface(settings, Some(Box::new(move |_| view())))
|
||||
} else {
|
||||
self.get_subsurface(settings, None)
|
||||
|
|
@ -301,17 +290,7 @@ where
|
|||
let settings = settings();
|
||||
let live_settings = Box::new(move |_: &T| live_settings());
|
||||
|
||||
if let Some(view) = view.and_then(|view| {
|
||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
||||
>>() {
|
||||
Ok(v) => Some(v),
|
||||
Err(err) => {
|
||||
tracing::error!("Invalid view for subsurface view: {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}) {
|
||||
if let Some(view) = view {
|
||||
self.get_popup(settings, live_settings, Some(Box::new(move |_| view())))
|
||||
} else {
|
||||
self.get_popup(settings, live_settings, None)
|
||||
|
|
@ -377,17 +356,7 @@ where
|
|||
return Task::none();
|
||||
};
|
||||
|
||||
if let Some(view) = view.and_then(|view| {
|
||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
||||
>>() {
|
||||
Ok(v) => Some(v),
|
||||
Err(err) => {
|
||||
tracing::error!("Invalid view for Window: {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}) {
|
||||
if let Some(view) = view {
|
||||
let settings = settings();
|
||||
|
||||
self.get_window(
|
||||
|
|
@ -409,9 +378,7 @@ where
|
|||
}
|
||||
|
||||
crate::surface::Action::Ignore => iced::Task::none(),
|
||||
crate::surface::Action::Task(f) => {
|
||||
f().map(|sm| crate::Action::Cosmic(Action::Surface(sm)))
|
||||
}
|
||||
crate::surface::Action::Task(f) => f().map(crate::Action::Surface),
|
||||
#[cfg(wayland_platform)]
|
||||
crate::surface::Action::AppLayerShell(settings, live_settings, view) => {
|
||||
let Some(settings) = std::sync::Arc::try_unwrap(settings)
|
||||
|
|
@ -474,17 +441,7 @@ where
|
|||
let live_settings = live_settings();
|
||||
let live_settings = Box::new(move |_app: &T| live_settings);
|
||||
|
||||
if let Some(view) = view.and_then(|view| {
|
||||
match std::sync::Arc::try_unwrap(view).ok()?.downcast::<Box<
|
||||
dyn Fn() -> Element<'static, crate::Action<T::Message>> + Send + Sync,
|
||||
>>() {
|
||||
Ok(v) => Some(v),
|
||||
Err(err) => {
|
||||
tracing::error!("Invalid view for layer surface: {err:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}) {
|
||||
if let Some(view) = view {
|
||||
self.get_layer_shell(settings, live_settings, Some(Box::new(move |_| view())))
|
||||
} else {
|
||||
self.get_layer_shell(settings, live_settings, None)
|
||||
|
|
@ -515,6 +472,7 @@ where
|
|||
let mut task = match message {
|
||||
crate::Action::App(message) => self.app.update(message),
|
||||
crate::Action::Cosmic(message) => self.cosmic_update(message),
|
||||
crate::Action::Surface(action) => self.surface_update(action),
|
||||
crate::Action::None => iced::Task::none(),
|
||||
#[cfg(feature = "single-instance")]
|
||||
crate::Action::DbusActivation(message) => {
|
||||
|
|
@ -604,9 +562,13 @@ where
|
|||
#[cfg(wayland_platform)]
|
||||
iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(event)) => {
|
||||
match event {
|
||||
wayland::Event::Popup(wayland::PopupEvent::Done, _, id)
|
||||
| wayland::Event::Layer(wayland::LayerEvent::Done, _, id) => {
|
||||
return Some(Action::SurfaceClosed(id));
|
||||
wayland::Event::Popup(wayland::PopupEvent::Done, _, popup) => {
|
||||
if popup == id {
|
||||
return Some(Action::SurfaceClosed(popup));
|
||||
}
|
||||
}
|
||||
wayland::Event::Layer(wayland::LayerEvent::Done, _, layer) => {
|
||||
return Some(Action::SurfaceClosed(layer));
|
||||
}
|
||||
#[cfg(feature = "applet")]
|
||||
wayland::Event::Window(
|
||||
|
|
@ -1227,8 +1189,6 @@ impl<T: Application> Cosmic<T> {
|
|||
}
|
||||
}
|
||||
|
||||
Action::Surface(action) => return self.surface_update(action),
|
||||
|
||||
Action::SurfaceClosed(id) => {
|
||||
if self.opened_surfaces.get_mut(&id).is_some_and(|v| {
|
||||
*v = v.saturating_sub(1);
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ where
|
|||
{
|
||||
nav = nav
|
||||
.window_id_maybe(self.core().main_window_id())
|
||||
.on_surface_action(|m| crate::Action::Cosmic(crate::app::Action::Surface(m)))
|
||||
.on_surface_action(|action| crate::Action::Surface(action.flatten()))
|
||||
}
|
||||
let mut nav = nav
|
||||
.into_container()
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ impl Context {
|
|||
content: impl Into<Element<'a, Message>>,
|
||||
tooltip: impl Into<Cow<'static, str>>,
|
||||
has_popup: bool,
|
||||
on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
||||
on_surface_action: impl Fn(crate::surface::Action<Message>) -> Message + 'static,
|
||||
parent_id: Option<window::Id>,
|
||||
) -> crate::widget::wayland::tooltip::widget::Tooltip<'a, Message, Message> {
|
||||
let window_id = *TOOLTIP_WINDOW_ID;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
// Copyright 2025 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::Action;
|
||||
use super::{Action, View};
|
||||
#[cfg(feature = "winit")]
|
||||
use crate::Application;
|
||||
|
||||
use iced::{Rectangle, window};
|
||||
|
|
@ -10,30 +11,31 @@ use iced_runtime::platform_specific::wayland::CornerRadius;
|
|||
#[cfg(wayland_platform)]
|
||||
use iced_runtime::platform_specific::wayland::layer_surface::IcedMargin;
|
||||
use std::any::Any;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Used to produce a destroy popup message from within a widget.
|
||||
#[cfg(wayland_platform)]
|
||||
#[must_use]
|
||||
pub fn destroy_popup(id: iced_core::window::Id) -> Action {
|
||||
pub fn destroy_popup<M>(id: iced_core::window::Id) -> Action<M> {
|
||||
Action::DestroyPopup(id)
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
#[must_use]
|
||||
pub fn destroy_subsurface(id: iced_core::window::Id) -> Action {
|
||||
pub fn destroy_subsurface<M>(id: iced_core::window::Id) -> Action<M> {
|
||||
Action::DestroySubsurface(id)
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
#[must_use]
|
||||
pub fn destroy_window(id: iced_core::window::Id) -> Action {
|
||||
pub fn destroy_window<M>(id: iced_core::window::Id) -> Action<M> {
|
||||
Action::DestroyWindow(id)
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
#[must_use]
|
||||
pub fn destroy_layer_shell(id: iced_core::window::Id) -> Action {
|
||||
pub fn destroy_layer_shell<M>(id: iced_core::window::Id) -> Action<M> {
|
||||
Action::DestroyLayerShell(id)
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +67,7 @@ pub fn app_window<App: Application>(
|
|||
live_settings: impl Fn(&App) -> LiveSettings + Send + Sync + 'static,
|
||||
settings: impl Fn(&mut App) -> window::Settings + Send + Sync + 'static,
|
||||
view: BoxedView<App>,
|
||||
) -> (window::Id, Action) {
|
||||
) -> (window::Id, Action<App::Message>) {
|
||||
let id = window::Id::unique();
|
||||
|
||||
let boxed: Box<dyn Fn(&mut App) -> window::Settings + Send + Sync + 'static> =
|
||||
|
|
@ -99,7 +101,7 @@ pub fn simple_window<Message: 'static>(
|
|||
view: Option<
|
||||
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||
>,
|
||||
) -> (window::Id, Action) {
|
||||
) -> (window::Id, Action<Message>) {
|
||||
let id = window::Id::unique();
|
||||
|
||||
let boxed: Box<dyn Fn() -> window::Settings + Send + Sync + 'static> = Box::new(settings);
|
||||
|
|
@ -114,16 +116,7 @@ pub fn simple_window<Message: 'static>(
|
|||
id,
|
||||
Arc::new(boxed),
|
||||
Arc::new(boxed_live),
|
||||
view.map(|view| {
|
||||
let boxed: Box<
|
||||
dyn Fn() -> crate::Element<'static, crate::Action<Message>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
> = Box::new(view);
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
||||
Arc::new(boxed)
|
||||
}),
|
||||
view.map(|view| Arc::new(view) as View<Message>),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -137,7 +130,7 @@ pub fn app_popup<App: Application>(
|
|||
+ Sync
|
||||
+ 'static,
|
||||
view: BoxedView<App>,
|
||||
) -> Action {
|
||||
) -> Action<App::Message> {
|
||||
let boxed: Box<
|
||||
dyn Fn(&mut App) -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
||||
+ Send
|
||||
|
|
@ -171,7 +164,7 @@ pub fn simple_subsurface<Message: 'static>(
|
|||
view: Option<
|
||||
Box<dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static>,
|
||||
>,
|
||||
) -> Action {
|
||||
) -> Action<Message> {
|
||||
let boxed: Box<
|
||||
dyn Fn() -> iced_runtime::platform_specific::wayland::subsurface::SctkSubsurfaceSettings
|
||||
+ Send
|
||||
|
|
@ -183,10 +176,7 @@ pub fn simple_subsurface<Message: 'static>(
|
|||
Action::Subsurface(
|
||||
Arc::new(boxed),
|
||||
Arc::new(Box::new(LiveSettings::default)),
|
||||
view.map(|view| {
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(view);
|
||||
Arc::new(boxed)
|
||||
}),
|
||||
view.map(|view| Arc::from(view) as View<Message>),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +192,7 @@ pub fn simple_popup<Message: 'static>(
|
|||
view: Option<
|
||||
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||
>,
|
||||
) -> Action {
|
||||
) -> Action<Message> {
|
||||
let boxed: Box<
|
||||
dyn Fn() -> iced_runtime::platform_specific::wayland::popup::SctkPopupSettings
|
||||
+ Send
|
||||
|
|
@ -217,13 +207,7 @@ pub fn simple_popup<Message: 'static>(
|
|||
Action::Popup(
|
||||
Arc::new(boxed),
|
||||
Arc::new(boxed_live),
|
||||
view.map(|view| {
|
||||
let boxed: Box<
|
||||
dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||
> = Box::new(view);
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
||||
Arc::new(boxed)
|
||||
}),
|
||||
view.map(|view| Arc::new(view) as View<Message>),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +223,7 @@ pub fn subsurface<App: Application>(
|
|||
+ 'static,
|
||||
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
|
||||
view: BoxedView<App>,
|
||||
) -> Action {
|
||||
) -> Action<App::Message> {
|
||||
let boxed: Box<
|
||||
dyn Fn(
|
||||
&mut App,
|
||||
|
|
@ -273,7 +257,7 @@ pub fn simple_layer_shell<Message: 'static>(
|
|||
view: Option<
|
||||
impl Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||
>,
|
||||
) -> Action {
|
||||
) -> Action<Message> {
|
||||
let boxed: Box<
|
||||
dyn Fn()
|
||||
-> iced_runtime::platform_specific::wayland::layer_surface::SctkLayerSurfaceSettings
|
||||
|
|
@ -287,13 +271,7 @@ pub fn simple_layer_shell<Message: 'static>(
|
|||
Action::LayerShell(
|
||||
Arc::new(boxed),
|
||||
Arc::new(boxed_live),
|
||||
view.map(|view| {
|
||||
let boxed: Box<
|
||||
dyn Fn() -> crate::Element<'static, crate::Action<Message>> + Send + Sync + 'static,
|
||||
> = Box::new(view);
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
||||
Arc::new(boxed)
|
||||
}),
|
||||
view.map(|view| Arc::new(view) as View<Message>),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -310,7 +288,7 @@ pub fn app_layer_shell<App: Application>(
|
|||
+ 'static,
|
||||
// XXX Boxed trait object is required for less cumbersome type inference, but we box it anyways.
|
||||
view: BoxedView<App>,
|
||||
) -> Action {
|
||||
) -> Action<App::Message> {
|
||||
let boxed: Box<
|
||||
dyn Fn(
|
||||
&mut App,
|
||||
|
|
|
|||
|
|
@ -11,19 +11,28 @@ use std::sync::Arc;
|
|||
|
||||
type BoxedSetting = Arc<Box<dyn Any + Send + Sync + 'static>>;
|
||||
|
||||
/// Produces the content of a surface created from within a widget.
|
||||
///
|
||||
/// Typed on the message the widget publishes.
|
||||
pub type View<M> =
|
||||
Arc<dyn Fn() -> crate::Element<'static, crate::Action<M>> + Send + Sync + 'static>;
|
||||
|
||||
/// Ignore this message in your application. It will be intercepted.
|
||||
///
|
||||
/// `M` is the message type of whoever created the action. The ones prefixed with `App` take the
|
||||
/// application itself and are type-erased, the others carry a [`View`] typed on `M`.
|
||||
#[derive(Clone)]
|
||||
pub enum Action {
|
||||
pub enum Action<M> {
|
||||
/// Create a subsurface with a view function accepting the App as a parameter
|
||||
AppSubsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
/// Create a subsurface with a view function
|
||||
Subsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
Subsurface(BoxedSetting, BoxedSetting, Option<View<M>>),
|
||||
/// Destroy a subsurface with a view function
|
||||
DestroySubsurface(iced::window::Id),
|
||||
/// Create a popup with a view function accepting the App as a parameter
|
||||
AppPopup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
/// Create a popup
|
||||
Popup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
Popup(BoxedSetting, BoxedSetting, Option<View<M>>),
|
||||
/// Destroy a subsurface with a view function
|
||||
DestroyPopup(iced::window::Id),
|
||||
/// Destroys the global tooltip popup subsurface
|
||||
|
|
@ -41,7 +50,7 @@ pub enum Action {
|
|||
iced::window::Id,
|
||||
BoxedSetting,
|
||||
BoxedSetting,
|
||||
Option<BoxedSetting>,
|
||||
Option<View<M>>,
|
||||
),
|
||||
/// Destroy a window
|
||||
DestroyWindow(iced::window::Id),
|
||||
|
|
@ -50,7 +59,7 @@ pub enum Action {
|
|||
AppLayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
|
||||
/// Create a layer shell surface with a view function
|
||||
LayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
|
||||
LayerShell(BoxedSetting, BoxedSetting, Option<View<M>>),
|
||||
|
||||
/// Destroy a layer shell surface
|
||||
DestroyLayerShell(iced::window::Id),
|
||||
|
|
@ -66,14 +75,74 @@ pub enum Action {
|
|||
},
|
||||
Ignore,
|
||||
SyncLiveSettings(iced::window::Id),
|
||||
Task(Arc<dyn Fn() -> Task<Action> + Send + Sync>),
|
||||
Task(Arc<dyn Fn() -> Task<Action<M>> + Send + Sync>),
|
||||
}
|
||||
|
||||
pub fn surface_task<M: Send + 'static>(action: Action) -> Task<crate::Action<M>> {
|
||||
crate::task::message(crate::Action::Cosmic(crate::app::Action::Surface(action)))
|
||||
impl<M: 'static> Action<M> {
|
||||
/// Re-type the action for a component whose messages are wrapped by `f`.
|
||||
///
|
||||
/// Similar to [`iced::Element::map`]. A component that maps a widget's messages must
|
||||
/// map the widget's surface actions too.
|
||||
#[must_use]
|
||||
pub fn map<N: 'static>(self, f: impl Fn(M) -> N + Clone + Send + Sync + 'static) -> Action<N> {
|
||||
self.map_actions(move |action| action.map(f.clone()))
|
||||
}
|
||||
|
||||
fn map_actions<N: 'static>(
|
||||
self,
|
||||
g: impl Fn(crate::Action<M>) -> crate::Action<N> + Clone + Send + Sync + 'static,
|
||||
) -> Action<N> {
|
||||
let map_view = |view: Option<View<M>>| -> Option<View<N>> {
|
||||
let view = view?;
|
||||
let g = g.clone();
|
||||
Some(Arc::new(move || view().map(g.clone())))
|
||||
};
|
||||
match self {
|
||||
Action::AppSubsurface(a, b, c) => Action::AppSubsurface(a, b, c),
|
||||
Action::Subsurface(a, b, view) => Action::Subsurface(a, b, map_view(view)),
|
||||
Action::DestroySubsurface(id) => Action::DestroySubsurface(id),
|
||||
Action::AppPopup(a, b, c) => Action::AppPopup(a, b, c),
|
||||
Action::Popup(a, b, view) => Action::Popup(a, b, map_view(view)),
|
||||
Action::DestroyPopup(id) => Action::DestroyPopup(id),
|
||||
Action::DestroyTooltipPopup => Action::DestroyTooltipPopup,
|
||||
Action::AppWindow(id, a, b, c) => Action::AppWindow(id, a, b, c),
|
||||
Action::Window(id, a, b, view) => Action::Window(id, a, b, map_view(view)),
|
||||
Action::DestroyWindow(id) => Action::DestroyWindow(id),
|
||||
Action::AppLayerShell(a, b, c) => Action::AppLayerShell(a, b, c),
|
||||
Action::LayerShell(a, b, view) => Action::LayerShell(a, b, map_view(view)),
|
||||
Action::DestroyLayerShell(id) => Action::DestroyLayerShell(id),
|
||||
Action::ResponsiveMenuBar {
|
||||
menu_bar,
|
||||
limits,
|
||||
size,
|
||||
} => Action::ResponsiveMenuBar {
|
||||
menu_bar,
|
||||
limits,
|
||||
size,
|
||||
},
|
||||
Action::Ignore => Action::Ignore,
|
||||
Action::SyncLiveSettings(id) => Action::SyncLiveSettings(id),
|
||||
Action::Task(task) => Action::Task(Arc::new(move || {
|
||||
let g = g.clone();
|
||||
task().map(move |action| action.map_actions(g.clone()))
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Action {
|
||||
impl<M: 'static> Action<crate::Action<M>> {
|
||||
#[must_use]
|
||||
pub fn flatten(self) -> Action<M> {
|
||||
self.map_actions(crate::Action::flatten)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
pub fn surface_task<M: Send + 'static>(action: Action<M>) -> Task<crate::Action<M>> {
|
||||
crate::task::message(crate::Action::Surface(action))
|
||||
}
|
||||
|
||||
impl<M> std::fmt::Debug for Action<M> {
|
||||
#[cold]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
|
|
@ -83,11 +152,11 @@ impl std::fmt::Debug for Action {
|
|||
.field(arg1)
|
||||
.field(arg2)
|
||||
.finish(),
|
||||
Self::Subsurface(arg0, arg1, arg2) => f
|
||||
Self::Subsurface(arg0, arg1, view) => f
|
||||
.debug_tuple("Subsurface")
|
||||
.field(arg0)
|
||||
.field(arg1)
|
||||
.field(arg2)
|
||||
.field(&view.as_ref().map(|_| "view"))
|
||||
.finish(),
|
||||
Self::DestroySubsurface(arg0) => {
|
||||
f.debug_tuple("DestroySubsurface").field(arg0).finish()
|
||||
|
|
@ -98,11 +167,11 @@ impl std::fmt::Debug for Action {
|
|||
.field(arg1)
|
||||
.field(arg2)
|
||||
.finish(),
|
||||
Self::Popup(arg0, arg1, arg2) => f
|
||||
Self::Popup(arg0, arg1, view) => f
|
||||
.debug_tuple("Popup")
|
||||
.field(arg0)
|
||||
.field(arg1)
|
||||
.field(arg2)
|
||||
.field(&view.as_ref().map(|_| "view"))
|
||||
.finish(),
|
||||
Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
|
||||
Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
|
||||
|
|
@ -124,12 +193,12 @@ impl std::fmt::Debug for Action {
|
|||
.field(arg1)
|
||||
.field(arg2)
|
||||
.finish(),
|
||||
Self::Window(id, arg0, arg1, arg2) => f
|
||||
Self::Window(id, arg0, arg1, view) => f
|
||||
.debug_tuple("Window")
|
||||
.field(id)
|
||||
.field(arg0)
|
||||
.field(arg1)
|
||||
.field(arg2)
|
||||
.field(&view.as_ref().map(|_| "view"))
|
||||
.finish(),
|
||||
Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(),
|
||||
Self::Task(_) => f.debug_tuple("Future").finish(),
|
||||
|
|
@ -139,11 +208,11 @@ impl std::fmt::Debug for Action {
|
|||
.field(arg1)
|
||||
.field(arg2)
|
||||
.finish(),
|
||||
Self::LayerShell(arg, arg1, arg2) => f
|
||||
Self::LayerShell(arg0, arg1, view) => f
|
||||
.debug_tuple("LayerShell")
|
||||
.field(arg)
|
||||
.field(arg0)
|
||||
.field(arg1)
|
||||
.field(arg2)
|
||||
.field(&view.as_ref().map(|_| "view"))
|
||||
.finish(),
|
||||
Self::DestroyLayerShell(arg0) => {
|
||||
f.debug_tuple("DestroyLayerShell").field(arg0).finish()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ pub enum Button {
|
|||
IconVertical,
|
||||
Image,
|
||||
Link,
|
||||
LinkActive,
|
||||
ListItem([f32; 4]),
|
||||
MenuFolder,
|
||||
MenuItem,
|
||||
|
|
@ -121,6 +122,13 @@ pub fn appearance(
|
|||
corner_radii = &cosmic.corner_radii.radius_0;
|
||||
}
|
||||
|
||||
Button::LinkActive => {
|
||||
appearance.background = Some(Background::Color(cosmic.text_button.hover.into()));
|
||||
appearance.icon_color = Some(cosmic.accent_text_color().into());
|
||||
appearance.text_color = Some(cosmic.accent_text_color().into());
|
||||
corner_radii = &cosmic.corner_radii.radius_xs;
|
||||
}
|
||||
|
||||
Button::Custom { .. } => (),
|
||||
Button::AppletMenu => {
|
||||
let (background, _, _) = color(&cosmic.text_button);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ pub fn context_menu<'a, Message: 'static + Clone>(
|
|||
}),
|
||||
close_on_escape: true,
|
||||
window_id: window::Id::RESERVED,
|
||||
item_width: ItemWidth::Uniform(240),
|
||||
on_open: None,
|
||||
on_close: None,
|
||||
on_surface_action: None,
|
||||
};
|
||||
|
||||
|
|
@ -53,12 +56,33 @@ pub struct ContextMenu<'a, Message> {
|
|||
context_menu: Option<Vec<menu::Tree<Message>>>,
|
||||
pub window_id: window::Id,
|
||||
pub close_on_escape: bool,
|
||||
/// Width of each menu item, and therefore of the menu.
|
||||
pub item_width: ItemWidth,
|
||||
/// Emitted when the menu opens, so the application can mark what was right-clicked.
|
||||
#[setters(strip_option)]
|
||||
pub on_open: Option<Message>,
|
||||
/// Emitted when the menu closes by any path, including the compositor dismissing it.
|
||||
#[setters(strip_option)]
|
||||
pub on_close: Option<Message>,
|
||||
#[setters(skip)]
|
||||
pub(crate) on_surface_action:
|
||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
||||
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||
}
|
||||
|
||||
impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
||||
/// Publish `on_open`/`on_close` when the open state changed since the last report.
|
||||
fn report_open_state(&self, state: &mut LocalState, shell: &mut iced_core::Shell<'_, Message>) {
|
||||
let open = state.menu_bar_state.inner.with_data(|d| d.open);
|
||||
if open == state.reported_open {
|
||||
return;
|
||||
}
|
||||
state.reported_open = open;
|
||||
let message = if open { &self.on_open } else { &self.on_close };
|
||||
if let Some(message) = message.clone() {
|
||||
shell.publish(message);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn create_popup(
|
||||
|
|
@ -91,16 +115,12 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
|||
|
||||
shell.publish(self.on_surface_action.as_ref().unwrap()(destroy_popup(id)));
|
||||
state.view_cursor = view_cursor;
|
||||
(
|
||||
id,
|
||||
layout.children().map(|lo| lo.bounds()).collect::<Vec<_>>(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
window::Id::unique(),
|
||||
layout.children().map(|lo| lo.bounds()).collect(),
|
||||
)
|
||||
}
|
||||
// A fresh id per popup, so the old popup's Done cannot be mistaken for the new one's
|
||||
(
|
||||
window::Id::unique(),
|
||||
layout.children().map(|lo| lo.bounds()).collect::<Vec<_>>(),
|
||||
)
|
||||
});
|
||||
let Some(context_menu) = self.context_menu.as_mut() else {
|
||||
return;
|
||||
|
|
@ -116,7 +136,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
|||
click_outside: true,
|
||||
click_inside: true,
|
||||
},
|
||||
item_width: ItemWidth::Uniform(240),
|
||||
item_width: self.item_width,
|
||||
item_height: ItemHeight::Dynamic(40),
|
||||
bar_bounds: bounds,
|
||||
main_offset: -(bounds.height as i32),
|
||||
|
|
@ -215,7 +235,7 @@ impl<Message: Clone + 'static> ContextMenu<'_, Message> {
|
|||
|
||||
pub fn on_surface_action(
|
||||
mut self,
|
||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
||||
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
self.on_surface_action = Some(Arc::new(handler));
|
||||
self
|
||||
|
|
@ -235,6 +255,7 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
context_cursor: Point::default(),
|
||||
fingers_pressed: Default::default(),
|
||||
menu_bar_state: Default::default(),
|
||||
reported_open: false,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -269,9 +290,11 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
fn diff(&mut self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_mut(&mut self.content));
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
state.menu_bar_state.inner.with_data_mut(|inner| {
|
||||
menu_roots_diff(self.context_menu.as_mut().unwrap(), &mut inner.tree);
|
||||
});
|
||||
if let Some(context_menu) = self.context_menu.as_mut() {
|
||||
state.menu_bar_state.inner.with_data_mut(|inner| {
|
||||
menu_roots_diff(context_menu, &mut inner.tree);
|
||||
});
|
||||
}
|
||||
|
||||
// if let Some(ref mut context_menus) = self.context_menu {
|
||||
// for (menu, tree) in context_menus
|
||||
|
|
@ -319,6 +342,38 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
cursor: iced_core::mouse::Cursor,
|
||||
viewport: &iced::Rectangle,
|
||||
renderer: &crate::Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
renderer: &crate::Renderer,
|
||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||
) {
|
||||
self.content.as_widget().drag_destinations(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
dnd_rectangles,
|
||||
);
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
|
|
@ -346,6 +401,20 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
// The compositor dismissed our popup: nothing else tells this state about it.
|
||||
#[cfg(wayland_platform)]
|
||||
if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
|
||||
iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup),
|
||||
)) = event
|
||||
{
|
||||
state.menu_bar_state.inner.with_data_mut(|d| {
|
||||
if d.popup_id.get(&self.window_id) == Some(popup) {
|
||||
d.popup_id.remove(&self.window_id);
|
||||
d.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
|
||||
let reset = self.window_id != window::Id::NONE
|
||||
&& state
|
||||
|
|
@ -425,7 +494,9 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
self.create_popup(layout, cursor, renderer, shell, viewport, state);
|
||||
}
|
||||
|
||||
shell.request_redraw();
|
||||
shell.capture_event();
|
||||
self.report_open_state(tree.state.downcast_mut::<LocalState>(), shell);
|
||||
return;
|
||||
} else if !was_open && right_button_released(event)
|
||||
|| (touch_lifted(event))
|
||||
|
|
@ -461,62 +532,76 @@ impl<Message: 'static + Clone> Widget<Message, crate::Theme, crate::Renderer>
|
|||
shell,
|
||||
viewport,
|
||||
);
|
||||
self.report_open_state(tree.state.downcast_mut::<LocalState>(), shell);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: iced_core::Layout<'_>,
|
||||
_renderer: &crate::Renderer,
|
||||
_viewport: &iced::Rectangle,
|
||||
layout: iced_core::Layout<'b>,
|
||||
renderer: &crate::Renderer,
|
||||
viewport: &iced::Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<iced_core::overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
|
||||
// The wrapped content's overlays (tooltips, dropdowns, ...) always pass through
|
||||
let content = self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
);
|
||||
|
||||
#[cfg(wayland_platform)]
|
||||
if matches!(WINDOWING_SYSTEM.get(), Some(WindowingSystem::Wayland))
|
||||
&& self.window_id != window::Id::NONE
|
||||
&& self.on_surface_action.is_some()
|
||||
{
|
||||
return None;
|
||||
return content;
|
||||
}
|
||||
|
||||
let state = tree.state.downcast_ref::<LocalState>();
|
||||
|
||||
let context_menu = self.context_menu.as_mut()?;
|
||||
|
||||
let Some(context_menu) = self.context_menu.as_mut() else {
|
||||
return content;
|
||||
};
|
||||
if !state.menu_bar_state.inner.with_data(|state| state.open) {
|
||||
return None;
|
||||
return content;
|
||||
}
|
||||
|
||||
let mut bounds = layout.bounds();
|
||||
bounds.x = state.context_cursor.x;
|
||||
bounds.y = state.context_cursor.y;
|
||||
Some(
|
||||
crate::widget::menu::Menu {
|
||||
tree: state.menu_bar_state.clone(),
|
||||
menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
|
||||
bounds_expand: 16,
|
||||
menu_overlays_parent: true,
|
||||
close_condition: CloseCondition {
|
||||
leave: false,
|
||||
click_outside: true,
|
||||
click_inside: true,
|
||||
},
|
||||
item_width: ItemWidth::Uniform(240),
|
||||
item_height: ItemHeight::Dynamic(40),
|
||||
bar_bounds: bounds,
|
||||
main_offset: -(bounds.height as i32),
|
||||
cross_offset: 0,
|
||||
root_bounds_list: vec![bounds],
|
||||
path_highlight: Some(PathHighlight::MenuActive),
|
||||
style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
|
||||
position: Point::new(translation.x, translation.y),
|
||||
is_overlay: true,
|
||||
window_id: window::Id::NONE,
|
||||
depth: 0,
|
||||
on_surface_action: None,
|
||||
// Anchor the menu to a 1x1 rectangle at the click, like the popup path does
|
||||
let bounds = iced::Rectangle::new(state.context_cursor, Size::new(1.0, 1.0));
|
||||
let menu = crate::widget::menu::Menu {
|
||||
tree: state.menu_bar_state.clone(),
|
||||
menu_roots: std::borrow::Cow::Owned(context_menu.clone()),
|
||||
bounds_expand: 16,
|
||||
menu_overlays_parent: true,
|
||||
close_condition: CloseCondition {
|
||||
leave: false,
|
||||
click_outside: true,
|
||||
click_inside: true,
|
||||
},
|
||||
item_width: self.item_width,
|
||||
item_height: ItemHeight::Dynamic(40),
|
||||
bar_bounds: bounds,
|
||||
main_offset: 0,
|
||||
cross_offset: 0,
|
||||
root_bounds_list: vec![bounds],
|
||||
path_highlight: Some(PathHighlight::MenuActive),
|
||||
style: std::borrow::Cow::Borrowed(&crate::theme::menu_bar::MenuBarStyle::Default),
|
||||
position: Point::new(translation.x, translation.y),
|
||||
is_overlay: true,
|
||||
window_id: window::Id::NONE,
|
||||
depth: 0,
|
||||
on_surface_action: None,
|
||||
}
|
||||
.overlay();
|
||||
|
||||
Some(match content {
|
||||
Some(content) => {
|
||||
iced_core::overlay::Group::with_children(vec![content, menu]).overlay()
|
||||
}
|
||||
.overlay(),
|
||||
)
|
||||
None => menu,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "a11y")]
|
||||
|
|
@ -560,4 +645,5 @@ pub struct LocalState {
|
|||
context_cursor: Point,
|
||||
fingers_pressed: HashSet<Finger>,
|
||||
menu_bar_state: MenuBarState,
|
||||
reported_open: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ pub fn popup_dropdown<
|
|||
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,
|
||||
_on_surface_action: impl Fn(surface::Action<AppMessage>) -> Message + Send + Sync + 'static,
|
||||
_map_action: impl Fn(Message) -> AppMessage + Send + Sync + 'static,
|
||||
) -> Dropdown<'a, S, Message, AppMessage> {
|
||||
let dropdown: Dropdown<'_, S, Message, AppMessage> =
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ where
|
|||
#[setters(strip_option)]
|
||||
font: Option<crate::font::Font>,
|
||||
#[setters(skip)]
|
||||
on_surface_action: Option<Arc<dyn Fn(surface::Action) -> Message + Send + Sync + 'static>>,
|
||||
on_surface_action:
|
||||
Option<Arc<dyn Fn(surface::Action<AppMessage>) -> Message + Send + Sync + 'static>>,
|
||||
#[setters(skip)]
|
||||
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + 'static + Send + Sync>>,
|
||||
#[setters(strip_option)]
|
||||
|
|
@ -108,7 +109,7 @@ where
|
|||
pub fn with_popup<NewAppMessage>(
|
||||
self,
|
||||
parent_id: window::Id,
|
||||
on_surface_action: impl Fn(surface::Action) -> Message + Send + Sync + 'static,
|
||||
on_surface_action: impl Fn(surface::Action<NewAppMessage>) -> Message + Send + Sync + 'static,
|
||||
action_map: impl Fn(Message) -> NewAppMessage + Send + Sync + 'static,
|
||||
) -> Dropdown<'a, S, Message, NewAppMessage> {
|
||||
let Self {
|
||||
|
|
@ -551,7 +552,9 @@ pub fn update<
|
|||
selections: &[S],
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
_window_id: Option<window::Id>,
|
||||
on_surface_action: Option<Arc<dyn Fn(surface::Action) -> Message + Send + Sync + 'static>>,
|
||||
on_surface_action: Option<
|
||||
Arc<dyn Fn(surface::Action<AppMessage>) -> Message + Send + Sync + 'static>,
|
||||
>,
|
||||
action_map: Option<Arc<dyn Fn(Message) -> AppMessage + Send + Sync + 'static>>,
|
||||
icons: &[icon::Handle],
|
||||
gap: f32,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ use super::Icon;
|
|||
use crate::widget::{image, svg};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::hash::Hash;
|
||||
use std::path::PathBuf;
|
||||
use std::io::Read;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[must_use]
|
||||
#[derive(Clone, Debug, Hash, derive_setters::Setters)]
|
||||
|
|
@ -31,6 +33,52 @@ pub enum Data {
|
|||
Svg(svg::Handle),
|
||||
}
|
||||
|
||||
enum SvgSource {
|
||||
Path,
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
|
||||
fn svg_source(path: &Path) -> Option<SvgSource> {
|
||||
if path
|
||||
.extension()
|
||||
.and_then(OsStr::to_str)
|
||||
.is_some_and(|extension| extension.eq_ignore_ascii_case("svg"))
|
||||
{
|
||||
return Some(SvgSource::Path);
|
||||
}
|
||||
|
||||
let Ok(mut file) = File::open(path) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Ok(metadata) = file.metadata() else {
|
||||
return None;
|
||||
};
|
||||
const MAX_SVG_SIZE: u64 = 16 * 1024 * 1024;
|
||||
if !metadata.file_type().is_file() || metadata.len() > MAX_SVG_SIZE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut prefix = [0; 32];
|
||||
let Ok(length) = file.read(&mut prefix) else {
|
||||
return None;
|
||||
};
|
||||
let prefix = &prefix[..length];
|
||||
|
||||
if ::image::guess_format(prefix).is_ok() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut bytes = Vec::with_capacity(metadata.len() as usize);
|
||||
bytes.extend_from_slice(prefix);
|
||||
if file.read_to_end(&mut bytes).is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let document = roxmltree::Document::parse(std::str::from_utf8(&bytes).ok()?).ok()?;
|
||||
(document.root_element().tag_name().name() == "svg").then_some(SvgSource::Bytes(bytes))
|
||||
}
|
||||
|
||||
/// Create an icon handle from its path.
|
||||
pub fn from_path(path: PathBuf) -> Handle {
|
||||
Handle {
|
||||
|
|
@ -38,10 +86,10 @@ pub fn from_path(path: PathBuf) -> Handle {
|
|||
.file_stem()
|
||||
.and_then(OsStr::to_str)
|
||||
.is_some_and(|name| name.ends_with("-symbolic")),
|
||||
data: if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
|
||||
Data::Svg(svg::Handle::from_path(path))
|
||||
} else {
|
||||
Data::Image(image::Handle::from_path(path))
|
||||
data: match svg_source(&path) {
|
||||
Some(SvgSource::Path) => Data::Svg(svg::Handle::from_path(path)),
|
||||
Some(SvgSource::Bytes(bytes)) => Data::Svg(svg::Handle::from_memory(bytes)),
|
||||
None => Data::Image(image::Handle::from_path(path)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
use super::{Handle, Icon};
|
||||
use std::borrow::Cow;
|
||||
use std::ffi::OsStr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -138,11 +137,7 @@ impl Named {
|
|||
Handle {
|
||||
symbolic: self.symbolic,
|
||||
data: if let Some(path) = self.path() {
|
||||
if path.extension().is_some_and(|ext| ext == OsStr::new("svg")) {
|
||||
super::Data::Svg(iced_core::svg::Handle::from_path(path))
|
||||
} else {
|
||||
super::Data::Image(iced_core::image::Handle::from_path(path))
|
||||
}
|
||||
super::from_path(path).data
|
||||
} else {
|
||||
super::bundle::get(&name).unwrap_or_else(|| {
|
||||
let bytes: &'static [u8] = &[];
|
||||
|
|
|
|||
|
|
@ -71,8 +71,8 @@ pub mod menu_column;
|
|||
mod menu_inner;
|
||||
mod menu_tree;
|
||||
pub use menu_tree::{
|
||||
MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items, menu_root as root,
|
||||
nav_context,
|
||||
Entry, IconSlot, MenuItem as Item, MenuTree as Tree, menu_button, menu_items as items,
|
||||
menu_root as root, nav_context,
|
||||
};
|
||||
|
||||
pub use crate::style::menu_bar::{Appearance, StyleSheet};
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ pub struct MenuBar<Message> {
|
|||
#[cfg(wayland_platform)]
|
||||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||
pub(crate) on_surface_action:
|
||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
||||
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||
}
|
||||
|
||||
impl<Message> MenuBar<Message>
|
||||
|
|
@ -328,7 +328,7 @@ where
|
|||
#[must_use]
|
||||
pub fn on_surface_action(
|
||||
mut self,
|
||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
||||
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
self.on_surface_action = Some(Arc::new(handler));
|
||||
self
|
||||
|
|
@ -377,13 +377,12 @@ where
|
|||
state.active_root.clear();
|
||||
shell.publish(surface_action(destroy_popup(id)));
|
||||
state.view_cursor = view_cursor;
|
||||
(id, layout.children().map(|lo| lo.bounds()).collect())
|
||||
} else {
|
||||
(
|
||||
window::Id::unique(),
|
||||
layout.children().map(|lo| lo.bounds()).collect(),
|
||||
)
|
||||
}
|
||||
// A fresh id per popup, so the old popup's Done cannot be mistaken for the new one's
|
||||
(
|
||||
window::Id::unique(),
|
||||
layout.children().map(|lo| lo.bounds()).collect(),
|
||||
)
|
||||
});
|
||||
|
||||
let mut popup_menu: Menu<'static, _> = Menu {
|
||||
|
|
@ -581,6 +580,21 @@ where
|
|||
|
||||
let my_state = tree.state.downcast_mut::<MenuBarState>();
|
||||
|
||||
// The compositor dismissed our popup: nothing else tells this state about it.
|
||||
#[cfg(wayland_platform)]
|
||||
if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
|
||||
iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup),
|
||||
)) = event
|
||||
{
|
||||
my_state.inner.with_data_mut(|d| {
|
||||
if d.popup_id.get(&self.window_id) == Some(popup) {
|
||||
// submenus were dismissed with it
|
||||
d.popup_id.clear();
|
||||
d.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// XXX this should reset the state if there are no other copies of the state, which implies no dropdown menus open.
|
||||
let reset = self.window_id != window::Id::NONE
|
||||
&& my_state
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ pub(crate) struct Menu<'b, Message: std::clone::Clone> {
|
|||
pub(crate) window_id: window::Id,
|
||||
pub(crate) depth: usize,
|
||||
pub(crate) on_surface_action:
|
||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
||||
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||
}
|
||||
impl<'b, Message: Clone + 'static> Menu<'b, Message> {
|
||||
pub(crate) fn overlay(self) -> overlay::Element<'b, Message, crate::Theme, crate::Renderer> {
|
||||
|
|
|
|||
|
|
@ -180,6 +180,84 @@ pub enum MenuItem<A: MenuAction, L: Into<Cow<'static, str>>> {
|
|||
Folder(L, Vec<MenuItem<A, L>>),
|
||||
/// Represents a divider between menu items.
|
||||
Divider,
|
||||
/// A menu entry with every option available; see [`Entry`].
|
||||
Entry(Entry<A, L>),
|
||||
}
|
||||
|
||||
impl<A: MenuAction, L: Into<Cow<'static, str>>> MenuItem<A, L> {
|
||||
/// Create an [`Entry`] menu item, configure it with the builder methods on [`Entry`].
|
||||
pub fn entry(label: L, action: A) -> Self {
|
||||
MenuItem::Entry(Entry::new(label, action))
|
||||
}
|
||||
}
|
||||
|
||||
/// The leading icon column of a menu entry.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum IconSlot {
|
||||
/// No icon and no space reserved for one.
|
||||
#[default]
|
||||
None,
|
||||
/// No icon, but the space for an icon is reserved (indented entry)
|
||||
Reserved,
|
||||
/// An icon.
|
||||
Icon(icon::Handle),
|
||||
}
|
||||
|
||||
impl From<Option<icon::Handle>> for IconSlot {
|
||||
fn from(icon: Option<icon::Handle>) -> Self {
|
||||
icon.map_or(IconSlot::None, IconSlot::Icon)
|
||||
}
|
||||
}
|
||||
|
||||
/// A menu entry: label, optional leading icon, optional check column, enabled state and action.
|
||||
#[derive(Clone)]
|
||||
pub struct Entry<A, L> {
|
||||
label: L,
|
||||
icon: IconSlot,
|
||||
/// `Some` draws the check column
|
||||
checked: Option<bool>,
|
||||
enabled: bool,
|
||||
action: A,
|
||||
}
|
||||
|
||||
impl<A, L> Entry<A, L> {
|
||||
pub fn new(label: L, action: A) -> Self {
|
||||
Self {
|
||||
label,
|
||||
icon: IconSlot::None,
|
||||
checked: None,
|
||||
enabled: true,
|
||||
action,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a leading icon
|
||||
#[must_use]
|
||||
pub fn icon(mut self, icon: icon::Handle) -> Self {
|
||||
self.icon = IconSlot::Icon(icon);
|
||||
self
|
||||
}
|
||||
|
||||
/// Draw no icon, but resever the space
|
||||
#[must_use]
|
||||
pub fn reserve_icon(mut self) -> Self {
|
||||
self.icon = IconSlot::Reserved;
|
||||
self
|
||||
}
|
||||
|
||||
/// Show a check column, ticked when `checked` is true, empty sapce when false
|
||||
#[must_use]
|
||||
pub fn checked(mut self, checked: bool) -> Self {
|
||||
self.checked = Some(checked);
|
||||
self
|
||||
}
|
||||
|
||||
/// Disabled entries are drawn dimmed and do not react to presses
|
||||
#[must_use]
|
||||
pub fn enabled(mut self, enabled: bool) -> Self {
|
||||
self.enabled = enabled;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a root menu item.
|
||||
|
|
@ -201,6 +279,84 @@ where
|
|||
.class(theme::Button::MenuRoot)
|
||||
}
|
||||
|
||||
fn entry_tree<
|
||||
A: MenuAction<Message = Message>,
|
||||
L: Into<Cow<'static, str>>,
|
||||
Message: Clone + 'static,
|
||||
>(
|
||||
entry: Entry<A, L>,
|
||||
key_binds: &HashMap<KeyBind, A>,
|
||||
key_class: theme::Text,
|
||||
) -> MenuTree<Message> {
|
||||
let Entry {
|
||||
label,
|
||||
icon,
|
||||
checked,
|
||||
enabled,
|
||||
action,
|
||||
} = entry;
|
||||
let spacing = crate::theme::spacing();
|
||||
let key = key_binds
|
||||
.iter()
|
||||
.find(|(_, a)| **a == action)
|
||||
.map_or_else(String::new, |(k, _)| k.to_string());
|
||||
|
||||
let mut items: Vec<crate::Element<'static, Message>> = Vec::with_capacity(7);
|
||||
|
||||
if let Some(checked) = checked {
|
||||
items.push(if checked {
|
||||
widget::icon::from_name("object-select-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.class(theme::Svg::Custom(Rc::new(|theme| {
|
||||
iced_widget::svg::Style {
|
||||
color: Some(theme.cosmic().accent_text_color().into()),
|
||||
}
|
||||
})))
|
||||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
} else {
|
||||
widget::space::horizontal()
|
||||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
});
|
||||
items.push(widget::space::horizontal().width(spacing.space_xxs).into());
|
||||
}
|
||||
|
||||
match icon {
|
||||
IconSlot::Icon(icon) => {
|
||||
items.push(widget::icon::icon(icon).size(14).into());
|
||||
items.push(widget::space::horizontal().width(spacing.space_xxs).into());
|
||||
}
|
||||
IconSlot::Reserved => {
|
||||
items.push(
|
||||
widget::space::horizontal()
|
||||
.width(Length::Fixed(14.0))
|
||||
.into(),
|
||||
);
|
||||
items.push(widget::space::horizontal().width(spacing.space_xxs).into());
|
||||
}
|
||||
IconSlot::None => {}
|
||||
}
|
||||
|
||||
let ellipsize =
|
||||
iced_core::text::Ellipsize::Middle(iced_core::text::EllipsizeHeightLimit::Lines(1));
|
||||
items.push(widget::text(label.into()).ellipsize(ellipsize).into());
|
||||
items.push(widget::space::horizontal().into());
|
||||
items.push(
|
||||
widget::text(key)
|
||||
.class(key_class)
|
||||
.ellipsize(ellipsize)
|
||||
.into(),
|
||||
);
|
||||
|
||||
let mut button = menu_button(items);
|
||||
if enabled {
|
||||
button = button.on_press(action.message());
|
||||
}
|
||||
MenuTree::from(Element::from(button))
|
||||
}
|
||||
|
||||
/// Create a list of menu items from a vector of `MenuItem`.
|
||||
///
|
||||
/// The `MenuItem` can be either an action or a separator.
|
||||
|
|
@ -220,15 +376,6 @@ pub fn menu_items<
|
|||
key_binds: &HashMap<KeyBind, A>,
|
||||
children: Vec<MenuItem<A, L>>,
|
||||
) -> 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 {
|
||||
return key_bind.to_string();
|
||||
}
|
||||
}
|
||||
String::new()
|
||||
}
|
||||
|
||||
fn key_style(theme: &crate::Theme) -> TextStyle {
|
||||
let mut color = theme.cosmic().background(theme.transparent).component.on;
|
||||
color.alpha *= 0.75;
|
||||
|
|
@ -246,117 +393,25 @@ pub fn menu_items<
|
|||
.enumerate()
|
||||
.flat_map(|(i, item)| {
|
||||
let mut trees = vec![];
|
||||
let spacing = crate::theme::spacing();
|
||||
|
||||
match item {
|
||||
MenuItem::Button(label, icon, action) => {
|
||||
let l: Cow<'static, str> = label.into();
|
||||
let key = find_key(&action, key_binds);
|
||||
let mut items = vec![
|
||||
widget::text(l)
|
||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::text(key)
|
||||
.class(key_class)
|
||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.into(),
|
||||
];
|
||||
|
||||
if let Some(icon) = icon {
|
||||
items.insert(0, widget::icon::icon(icon).size(14).into());
|
||||
items.insert(
|
||||
1,
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
);
|
||||
}
|
||||
|
||||
let menu_button = menu_button(items).on_press(action.message());
|
||||
|
||||
trees.push(MenuTree::<Message>::from(Element::from(menu_button)));
|
||||
let mut entry = Entry::new(label, action);
|
||||
entry.icon = icon.into();
|
||||
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||
}
|
||||
MenuItem::ButtonDisabled(label, icon, action) => {
|
||||
let l: Cow<'static, str> = label.into();
|
||||
|
||||
let key = find_key(&action, key_binds);
|
||||
|
||||
let mut items = vec![
|
||||
widget::text(l)
|
||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::text(key)
|
||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.class(key_class)
|
||||
.into(),
|
||||
];
|
||||
|
||||
if let Some(icon) = icon {
|
||||
items.insert(0, widget::icon::icon(icon).size(14).into());
|
||||
items.insert(
|
||||
1,
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
);
|
||||
}
|
||||
|
||||
let menu_button = menu_button(items);
|
||||
|
||||
trees.push(MenuTree::<Message>::from(Element::from(menu_button)));
|
||||
let mut entry = Entry::new(label, action).enabled(false);
|
||||
entry.icon = icon.into();
|
||||
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||
}
|
||||
MenuItem::CheckBox(label, icon, value, action) => {
|
||||
let key = find_key(&action, key_binds);
|
||||
let mut items = vec![
|
||||
if value {
|
||||
widget::icon::from_name("object-select-symbolic")
|
||||
.size(16)
|
||||
.icon()
|
||||
.class(theme::Svg::Custom(Rc::new(|theme| {
|
||||
iced_widget::svg::Style {
|
||||
color: Some(theme.cosmic().accent_text_color().into()),
|
||||
}
|
||||
})))
|
||||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
} else {
|
||||
widget::space::horizontal()
|
||||
.width(Length::Fixed(16.0))
|
||||
.into()
|
||||
},
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
widget::text(label)
|
||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.align_x(iced::Alignment::Start)
|
||||
.into(),
|
||||
widget::space::horizontal().into(),
|
||||
widget::text(key)
|
||||
.class(key_class)
|
||||
.ellipsize(iced_core::text::Ellipsize::Middle(
|
||||
iced_core::text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.into(),
|
||||
];
|
||||
|
||||
if let Some(icon) = icon {
|
||||
items.insert(
|
||||
1,
|
||||
widget::space::horizontal().width(spacing.space_xxs).into(),
|
||||
);
|
||||
items.insert(2, widget::icon::icon(icon).size(14).into());
|
||||
}
|
||||
|
||||
trees.push(MenuTree::from(Element::from(
|
||||
menu_button(items).on_press(action.message()),
|
||||
)));
|
||||
let mut entry = Entry::new(label, action).checked(value);
|
||||
entry.icon = icon.into();
|
||||
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||
}
|
||||
MenuItem::Entry(entry) => {
|
||||
trees.push(entry_tree(entry, key_binds, key_class.clone()));
|
||||
}
|
||||
MenuItem::Folder(label, children) => {
|
||||
let l: Cow<'static, str> = label.into();
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ impl<'a, Message: Clone + 'static> NavBar<'a, Message> {
|
|||
#[must_use]
|
||||
pub fn on_surface_action(
|
||||
mut self,
|
||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
||||
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
self.segmented_button = self.segmented_button.on_surface_action(handler);
|
||||
self
|
||||
|
|
|
|||
|
|
@ -157,9 +157,17 @@ where
|
|||
) {
|
||||
if self.popup.is_some() {
|
||||
if self.modal {
|
||||
if matches!(event, Event::Mouse(_) | Event::Touch(_)) {
|
||||
shell.capture_event();
|
||||
return;
|
||||
match event {
|
||||
Event::Mouse(_) | Event::Touch(_) => {
|
||||
shell.capture_event();
|
||||
return;
|
||||
}
|
||||
// app-level keyboard navigation (Tab focus cycling, Escape)
|
||||
// still receives keyboard events
|
||||
Event::Keyboard(_) => {
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if let Some(on_close) = self.on_close.as_ref()
|
||||
&& matches!(
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use iced_core::{
|
|||
pub(crate) fn responsive_container<'a, Message: 'static, Theme, E>(
|
||||
content: E,
|
||||
id: Id,
|
||||
on_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
||||
on_action: impl Fn(crate::surface::Action<Message>) -> Message + 'static,
|
||||
) -> ResponsiveContainer<'a, Message, Theme, crate::Renderer>
|
||||
where
|
||||
E: Into<Element<'a, Message, Theme, crate::Renderer>>,
|
||||
|
|
@ -32,7 +32,7 @@ where
|
|||
content: Element<'a, Message, Theme, Renderer>,
|
||||
id: Id,
|
||||
size: Option<Size>,
|
||||
on_action: Box<dyn Fn(crate::surface::Action) -> Message>,
|
||||
on_action: Box<dyn Fn(crate::surface::Action<Message>) -> Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Theme, Renderer> ResponsiveContainer<'a, Message, Theme, Renderer>
|
||||
|
|
@ -43,7 +43,7 @@ where
|
|||
pub(crate) fn new<T>(
|
||||
content: T,
|
||||
id: Id,
|
||||
on_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
||||
on_action: impl Fn(crate::surface::Action<Message>) -> Message + 'static,
|
||||
) -> Self
|
||||
where
|
||||
T: Into<Element<'a, Message, Theme, Renderer>>,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,11 @@ impl ResponsiveMenuBar {
|
|||
core: &Core,
|
||||
key_binds: &HashMap<menu::KeyBind, A>,
|
||||
id: crate::widget::Id,
|
||||
action_message: impl Fn(crate::surface::Action) -> Message + Send + Sync + Clone + 'static,
|
||||
action_message: impl Fn(crate::surface::Action<Message>) -> Message
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ 'static,
|
||||
trees: Vec<(S, Vec<menu::Item<A, S>>)>,
|
||||
) -> Element<'a, Message> {
|
||||
use crate::widget::id_container;
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ where
|
|||
positioner: iced_runtime::platform_specific::wayland::popup::SctkPositioner,
|
||||
#[setters(skip)]
|
||||
pub(crate) on_surface_action:
|
||||
Option<Arc<dyn Fn(crate::surface::Action) -> Message + Send + Sync + 'static>>,
|
||||
Option<Arc<dyn Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static>>,
|
||||
|
||||
/// Defines the implementation of this struct
|
||||
variant: PhantomData<Variant>,
|
||||
|
|
@ -914,7 +914,7 @@ where
|
|||
#[must_use]
|
||||
pub fn on_surface_action(
|
||||
mut self,
|
||||
handler: impl Fn(crate::surface::Action) -> Message + Send + Sync + 'static,
|
||||
handler: impl Fn(crate::surface::Action<Message>) -> Message + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
self.on_surface_action = Some(Arc::new(handler));
|
||||
self
|
||||
|
|
@ -953,10 +953,9 @@ where
|
|||
state.active_root.clear();
|
||||
shell.publish(surface_action(destroy_popup(id)));
|
||||
state.view_cursor = view_cursor;
|
||||
id
|
||||
} else {
|
||||
window::Id::unique()
|
||||
}
|
||||
// A fresh id per popup, so the old popup's Done cannot be mistaken for the new one's
|
||||
window::Id::unique()
|
||||
});
|
||||
let Some(entity) = state.show_context else {
|
||||
return;
|
||||
|
|
@ -1088,7 +1087,7 @@ where
|
|||
+ Sync
|
||||
+ 'static,
|
||||
view: Option<impl Fn() -> crate::Element<'static, Message> + Send + Sync + 'static>,
|
||||
) -> crate::surface::Action {
|
||||
) -> crate::surface::Action<Message> {
|
||||
use std::any::Any;
|
||||
|
||||
let boxed: Box<
|
||||
|
|
@ -1107,11 +1106,8 @@ where
|
|||
Arc::new(boxed),
|
||||
Arc::new(boxed_live),
|
||||
view.map(|view| {
|
||||
let boxed: Box<
|
||||
dyn Fn() -> crate::Element<'static, Message> + Send + Sync + 'static,
|
||||
> = Box::new(view);
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> = Box::new(boxed);
|
||||
Arc::new(boxed)
|
||||
Arc::new(move || view().map(crate::Action::App))
|
||||
as crate::surface::View<Message>
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
|
@ -1260,6 +1256,29 @@ where
|
|||
let my_bounds = layout.bounds();
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
|
||||
// The compositor dismissed our context menu popup: nothing else tells this state about it.
|
||||
#[cfg(wayland_platform)]
|
||||
if let iced::Event::PlatformSpecific(iced::event::PlatformSpecific::Wayland(
|
||||
iced::event::wayland::Event::Popup(iced::event::wayland::PopupEvent::Done, _, popup),
|
||||
)) = &event
|
||||
{
|
||||
let dismissed = state.menu_state.inner.with_data_mut(|data| {
|
||||
if data.popup_id.get(&self.window_id) == Some(popup) {
|
||||
data.popup_id.clear();
|
||||
data.reset();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if dismissed {
|
||||
state.show_context = None;
|
||||
for key in self.model.order.iter().copied() {
|
||||
self.update_entity_paragraph(state, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let hovered_before = state.hovered;
|
||||
|
||||
let my_id = self.get_drag_id();
|
||||
|
|
@ -2240,12 +2259,7 @@ where
|
|||
|
||||
let menu_open = || {
|
||||
state.show_context == Some(key)
|
||||
&& !tree.children.is_empty()
|
||||
&& tree.children[0]
|
||||
.state
|
||||
.downcast_ref::<MenuBarState>()
|
||||
.inner
|
||||
.with_data(|data| data.open)
|
||||
&& state.menu_state.inner.with_data(|data| data.open)
|
||||
};
|
||||
|
||||
let key_is_active = self.model.is_active(key);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
//! A [`TextInput`] has some local [`State`].
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Cell, LazyCell};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use crate::ext::ColorExt;
|
||||
use crate::theme::THEME;
|
||||
|
|
@ -2539,6 +2540,63 @@ fn input_method<'b>(
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_graphemes(
|
||||
value: &Value,
|
||||
state: &State,
|
||||
left: usize,
|
||||
right: usize,
|
||||
text: &str,
|
||||
text_color: Color,
|
||||
bounds: Rectangle,
|
||||
size: f32,
|
||||
renderer: &mut crate::Renderer,
|
||||
font: iced_core::Font,
|
||||
) {
|
||||
let lo_byte = value.byte_index_at_grapheme(left);
|
||||
let hi_byte = value.byte_index_at_grapheme(right);
|
||||
|
||||
let rects = state.value.raw().highlight(
|
||||
0,
|
||||
(lo_byte, text::Affinity::After),
|
||||
(hi_byte, text::Affinity::Before),
|
||||
);
|
||||
|
||||
if !rects.is_empty() {
|
||||
let grapheme_range = text[lo_byte..hi_byte].to_string();
|
||||
let origin = bounds.position();
|
||||
|
||||
let start_pos = origin + (rects.first().unwrap().position() - Point::ORIGIN);
|
||||
|
||||
for rect in rects {
|
||||
let absolute_rect = Rectangle {
|
||||
x: rect.x + origin.x,
|
||||
y: rect.y + origin.y,
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
};
|
||||
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: grapheme_range.clone(),
|
||||
font,
|
||||
bounds: bounds.size(),
|
||||
size: iced::Pixels(size),
|
||||
align_x: text::Alignment::Default,
|
||||
align_y: alignment::Vertical::Center,
|
||||
line_height: text::LineHeight::default(),
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
ellipsize: text::Ellipsize::None,
|
||||
},
|
||||
start_pos,
|
||||
text_color,
|
||||
absolute_rect,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||
/// [`Value`] if provided.
|
||||
///
|
||||
|
|
@ -2747,7 +2805,7 @@ pub fn draw<'a, Message>(
|
|||
let handling_dnd_offer = !matches!(state.dnd_offer, DndOfferState::None);
|
||||
#[cfg(not(wayland_platform))]
|
||||
let handling_dnd_offer = false;
|
||||
let (cursors, offset, _is_selecting) = if let Some(focus) =
|
||||
let (cursors, offset, _) = if let Some(focus) =
|
||||
state.is_focused.filter(|f| f.focused).or_else(|| {
|
||||
let now = Instant::now();
|
||||
handling_dnd_offer.then_some(Focus {
|
||||
|
|
@ -2821,7 +2879,6 @@ pub fn draw<'a, Message>(
|
|||
(lo_byte, text::Affinity::After),
|
||||
(hi_byte, text::Affinity::Before),
|
||||
);
|
||||
|
||||
let cursors: Vec<(renderer::Quad, Color)> = rects
|
||||
.into_iter()
|
||||
.map(|r| {
|
||||
|
|
@ -2875,7 +2932,6 @@ pub fn draw<'a, Message>(
|
|||
state.value.raw().min_width(),
|
||||
effective_alignment(state.value.raw()),
|
||||
);
|
||||
|
||||
if cursors.is_empty() {
|
||||
renderer.with_translation(Vector::ZERO, |_| {});
|
||||
} else {
|
||||
|
|
@ -2892,33 +2948,77 @@ pub fn draw<'a, Message>(
|
|||
width: actual_width,
|
||||
..text_bounds
|
||||
};
|
||||
let color = if text.is_empty() {
|
||||
let tcolor = if text.is_empty() {
|
||||
appearance.placeholder_color
|
||||
} else {
|
||||
text_color
|
||||
};
|
||||
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: if text.is_empty() {
|
||||
placeholder.to_string()
|
||||
} else {
|
||||
text.clone()
|
||||
if let cursor::State::Selection { start, end } = state.cursor.state(value)
|
||||
&& state.is_focused()
|
||||
{
|
||||
let left = start.min(end);
|
||||
let right = end.max(start);
|
||||
let grapheme_len = text.graphemes(true).count();
|
||||
|
||||
if left > 0 {
|
||||
render_graphemes(
|
||||
value, state, 0, left, &text, tcolor, bounds, size, renderer, font,
|
||||
);
|
||||
}
|
||||
|
||||
if grapheme_len >= right {
|
||||
render_graphemes(
|
||||
value,
|
||||
state,
|
||||
left,
|
||||
right,
|
||||
&text,
|
||||
appearance.selected_text_color,
|
||||
bounds,
|
||||
size,
|
||||
renderer,
|
||||
font,
|
||||
);
|
||||
}
|
||||
|
||||
if left < grapheme_len {
|
||||
render_graphemes(
|
||||
value,
|
||||
state,
|
||||
right,
|
||||
grapheme_len,
|
||||
&text,
|
||||
tcolor,
|
||||
bounds,
|
||||
size,
|
||||
renderer,
|
||||
font,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
renderer.fill_text(
|
||||
Text {
|
||||
content: if text.is_empty() {
|
||||
placeholder.to_string()
|
||||
} else {
|
||||
text.clone()
|
||||
},
|
||||
font,
|
||||
bounds: bounds.size(),
|
||||
size: iced::Pixels(size),
|
||||
align_x: text::Alignment::Default,
|
||||
align_y: alignment::Vertical::Center,
|
||||
line_height: text::LineHeight::default(),
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
ellipsize: text::Ellipsize::None,
|
||||
},
|
||||
font,
|
||||
bounds: bounds.size(),
|
||||
size: iced::Pixels(size),
|
||||
align_x: text::Alignment::Default,
|
||||
align_y: alignment::Vertical::Center,
|
||||
line_height: text::LineHeight::default(),
|
||||
shaping: text::Shaping::Advanced,
|
||||
wrapping: text::Wrapping::None,
|
||||
ellipsize: text::Ellipsize::None,
|
||||
},
|
||||
bounds.position(),
|
||||
color,
|
||||
text_bounds,
|
||||
);
|
||||
bounds.position(),
|
||||
tcolor,
|
||||
text_bounds,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: we always must clip with a layer because of what appears to be a tiny-skia text clipping issue.
|
||||
|
|
|
|||
|
|
@ -142,23 +142,26 @@ where
|
|||
viewport: &Rectangle,
|
||||
translation: Vector,
|
||||
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
|
||||
//TODO: this hides the overlay of the content during the toast
|
||||
let (content_state, toasts_state) = state.children.split_at_mut(1);
|
||||
let content = self.content.as_widget_mut().overlay(
|
||||
&mut content_state[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
);
|
||||
if self.is_empty {
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut state.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
} else {
|
||||
let _bounds = layout.bounds();
|
||||
|
||||
Some(overlay::Element::new(Box::new(ToasterOverlay::new(
|
||||
&mut state.children[1],
|
||||
&mut self.toasts,
|
||||
))))
|
||||
return content;
|
||||
}
|
||||
// keep the content's overlays (e.g. popovers, menus) visible while a toast is shown
|
||||
let toaster = overlay::Element::new(Box::new(ToasterOverlay::new(
|
||||
&mut toasts_state[0],
|
||||
&mut self.toasts,
|
||||
)));
|
||||
Some(match content {
|
||||
Some(content) => overlay::Group::with_children(vec![content, toaster]).overlay(),
|
||||
None => toaster,
|
||||
})
|
||||
}
|
||||
|
||||
fn drag_destinations(
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ pub struct Tooltip<'a, Message, TopLevelMessage> {
|
|||
label: Option<Vec<iced_accessibility::accesskit::NodeId>>,
|
||||
content: crate::Element<'a, Message>,
|
||||
on_leave: Message,
|
||||
on_surface_action: Box<dyn Fn(crate::surface::Action) -> Message>,
|
||||
on_surface_action: Box<dyn Fn(crate::surface::Action<TopLevelMessage>) -> Message>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
padding: Padding,
|
||||
|
|
@ -75,7 +75,7 @@ impl<'a, Message, TopLevelMessage> Tooltip<'a, Message, TopLevelMessage> {
|
|||
+ Sync
|
||||
+ 'static,
|
||||
on_leave: Message,
|
||||
on_surface_action: impl Fn(crate::surface::Action) -> Message + 'static,
|
||||
on_surface_action: impl Fn(crate::surface::Action<TopLevelMessage>) -> Message + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: Id::unique(),
|
||||
|
|
@ -440,7 +440,7 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
|||
>,
|
||||
delay: Option<Duration>,
|
||||
on_leave: &Message,
|
||||
on_surface_action: &dyn Fn(crate::surface::Action) -> Message,
|
||||
on_surface_action: &dyn Fn(crate::surface::Action<TopLevelMessage>) -> Message,
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
) {
|
||||
match event {
|
||||
|
|
@ -534,19 +534,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
|||
crate::surface::Action::Popup(
|
||||
Arc::new(boxed),
|
||||
Arc::new(boxed_live),
|
||||
Some({
|
||||
let boxed: Box<
|
||||
dyn Fn() -> crate::Element<
|
||||
'static,
|
||||
crate::Action<TopLevelMessage>,
|
||||
> + Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
> = Box::new(move || view());
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> =
|
||||
Box::new(boxed);
|
||||
Arc::new(boxed)
|
||||
}),
|
||||
Some(Arc::new(move || view())
|
||||
as crate::surface::View<TopLevelMessage>),
|
||||
)
|
||||
})
|
||||
}));
|
||||
|
|
@ -583,19 +572,8 @@ pub fn update<'a, Message: Clone + 'static, TopLevelMessage: Clone + 'static>(
|
|||
let sm = crate::surface::Action::Popup(
|
||||
Arc::new(boxed),
|
||||
Arc::new(boxed_live),
|
||||
Some({
|
||||
let boxed: Box<
|
||||
dyn Fn() -> crate::Element<
|
||||
'static,
|
||||
crate::Action<TopLevelMessage>,
|
||||
> + Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
> = Box::new(move || view());
|
||||
let boxed: Box<dyn Any + Send + Sync + 'static> =
|
||||
Box::new(boxed);
|
||||
Arc::new(boxed)
|
||||
}),
|
||||
Some(Arc::new(move || view())
|
||||
as crate::surface::View<TopLevelMessage>),
|
||||
);
|
||||
shell.publish((on_surface_action)(sm));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue