Skip to main content

cosmic/surface/
mod.rs

1// Copyright 2025 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4pub mod action;
5#[cfg(wayland_platform)]
6pub mod corner_radius;
7
8use iced::{Limits, Size, Task};
9use std::any::Any;
10use std::sync::Arc;
11
12type BoxedSetting = Arc<Box<dyn Any + Send + Sync + 'static>>;
13
14/// Produces the content of a surface created from within a widget.
15///
16/// Typed on the message the widget publishes.
17pub type View<M> =
18    Arc<dyn Fn() -> crate::Element<'static, crate::Action<M>> + Send + Sync + 'static>;
19
20/// Ignore this message in your application. It will be intercepted.
21///
22/// `M` is the message type of whoever created the action. The ones prefixed with `App` take the
23/// application itself and are type-erased, the others carry a [`View`] typed on `M`.
24#[derive(Clone)]
25pub enum Action<M> {
26    /// Create a subsurface with a view function accepting the App as a parameter
27    AppSubsurface(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
28    /// Create a subsurface with a view function
29    Subsurface(BoxedSetting, BoxedSetting, Option<View<M>>),
30    /// Destroy a subsurface with a view function
31    DestroySubsurface(iced::window::Id),
32    /// Create a popup with a view function accepting the App as a parameter
33    AppPopup(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
34    /// Create a popup
35    Popup(BoxedSetting, BoxedSetting, Option<View<M>>),
36    /// Destroy a subsurface with a view function
37    DestroyPopup(iced::window::Id),
38    /// Destroys the global tooltip popup subsurface
39    DestroyTooltipPopup,
40
41    /// Create a window with a view function accepting the App as a parameter
42    AppWindow(
43        iced::window::Id,
44        BoxedSetting,
45        BoxedSetting,
46        Option<BoxedSetting>,
47    ),
48    /// Create a window with a view function
49    Window(
50        iced::window::Id,
51        BoxedSetting,
52        BoxedSetting,
53        Option<View<M>>,
54    ),
55    /// Destroy a window
56    DestroyWindow(iced::window::Id),
57
58    /// Create a layer shell surface with a view function accepting the App as a parameter
59    AppLayerShell(BoxedSetting, BoxedSetting, Option<BoxedSetting>),
60
61    /// Create a layer shell surface with a view function
62    LayerShell(BoxedSetting, BoxedSetting, Option<View<M>>),
63
64    /// Destroy a layer shell surface
65    DestroyLayerShell(iced::window::Id),
66
67    #[cfg(wayland_platform)]
68    /// Create a lock shell surface with a view function
69    Lock(
70        iced::window::Id,
71        cctk::wayland_client::protocol::wl_output::WlOutput,
72        BoxedSetting,
73        Option<View<M>>,
74    ),
75
76    /// Destroy a lock surface
77    DestroyLock(iced::window::Id),
78
79    /// Responsive menu bar update
80    ResponsiveMenuBar {
81        /// Id of the menu bar
82        menu_bar: crate::widget::Id,
83        /// Limits of the menu bar
84        limits: Limits,
85        /// Requested Full Size for expanded menu bar
86        size: Size,
87    },
88    Ignore,
89    SyncLiveSettings(iced::window::Id),
90    Task(Arc<dyn Fn() -> Task<Action<M>> + Send + Sync>),
91}
92
93impl<M: 'static> Action<M> {
94    /// Re-type the action for a component whose messages are wrapped by `f`.
95    ///
96    /// Similar to [`iced::Element::map`]. A component that maps a widget's messages must
97    /// map the widget's surface actions too.
98    #[must_use]
99    pub fn map<N: 'static>(self, f: impl Fn(M) -> N + Clone + Send + Sync + 'static) -> Action<N> {
100        self.map_actions(move |action| action.map(f.clone()))
101    }
102
103    fn map_actions<N: 'static>(
104        self,
105        g: impl Fn(crate::Action<M>) -> crate::Action<N> + Clone + Send + Sync + 'static,
106    ) -> Action<N> {
107        let map_view = |view: Option<View<M>>| -> Option<View<N>> {
108            let view = view?;
109            let g = g.clone();
110            Some(Arc::new(move || view().map(g.clone())))
111        };
112        match self {
113            Action::AppSubsurface(a, b, c) => Action::AppSubsurface(a, b, c),
114            Action::Subsurface(a, b, view) => Action::Subsurface(a, b, map_view(view)),
115            Action::DestroySubsurface(id) => Action::DestroySubsurface(id),
116            Action::AppPopup(a, b, c) => Action::AppPopup(a, b, c),
117            Action::Popup(a, b, view) => Action::Popup(a, b, map_view(view)),
118            Action::DestroyPopup(id) => Action::DestroyPopup(id),
119            Action::DestroyTooltipPopup => Action::DestroyTooltipPopup,
120            Action::AppWindow(id, a, b, c) => Action::AppWindow(id, a, b, c),
121            Action::Window(id, a, b, view) => Action::Window(id, a, b, map_view(view)),
122            Action::DestroyWindow(id) => Action::DestroyWindow(id),
123            Action::AppLayerShell(a, b, c) => Action::AppLayerShell(a, b, c),
124            Action::LayerShell(a, b, view) => Action::LayerShell(a, b, map_view(view)),
125            Action::DestroyLayerShell(id) => Action::DestroyLayerShell(id),
126            #[cfg(wayland_platform)]
127            Action::Lock(id, output, a, view) => Action::Lock(id, output, a, map_view(view)),
128            Action::DestroyLock(id) => Action::DestroyLock(id),
129            Action::ResponsiveMenuBar {
130                menu_bar,
131                limits,
132                size,
133            } => Action::ResponsiveMenuBar {
134                menu_bar,
135                limits,
136                size,
137            },
138            Action::Ignore => Action::Ignore,
139            Action::SyncLiveSettings(id) => Action::SyncLiveSettings(id),
140            Action::Task(task) => Action::Task(Arc::new(move || {
141                let g = g.clone();
142                task().map(move |action| action.map_actions(g.clone()))
143            })),
144        }
145    }
146}
147
148impl<M: 'static> Action<crate::Action<M>> {
149    #[must_use]
150    pub fn flatten(self) -> Action<M> {
151        self.map_actions(crate::Action::flatten)
152    }
153}
154
155#[cfg(feature = "winit")]
156pub fn surface_task<M: Send + 'static>(action: Action<M>) -> Task<crate::Action<M>> {
157    crate::task::message(crate::Action::Surface(action))
158}
159
160impl<M> std::fmt::Debug for Action<M> {
161    #[cold]
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        match self {
164            Self::AppSubsurface(arg0, arg1, arg2) => f
165                .debug_tuple("AppSubsurface")
166                .field(arg0)
167                .field(arg1)
168                .field(arg2)
169                .finish(),
170            Self::Subsurface(arg0, arg1, view) => f
171                .debug_tuple("Subsurface")
172                .field(arg0)
173                .field(arg1)
174                .field(&view.as_ref().map(|_| "view"))
175                .finish(),
176            Self::DestroySubsurface(arg0) => {
177                f.debug_tuple("DestroySubsurface").field(arg0).finish()
178            }
179            Self::AppPopup(arg0, arg1, arg2) => f
180                .debug_tuple("AppPopup")
181                .field(arg0)
182                .field(arg1)
183                .field(arg2)
184                .finish(),
185            Self::Popup(arg0, arg1, view) => f
186                .debug_tuple("Popup")
187                .field(arg0)
188                .field(arg1)
189                .field(&view.as_ref().map(|_| "view"))
190                .finish(),
191            Self::DestroyPopup(arg0) => f.debug_tuple("DestroyPopup").field(arg0).finish(),
192            Self::DestroyTooltipPopup => f.debug_tuple("DestroyTooltipPopup").finish(),
193            Self::ResponsiveMenuBar {
194                menu_bar,
195                limits,
196                size,
197            } => f
198                .debug_struct("ResponsiveMenuBar")
199                .field("menu_bar", menu_bar)
200                .field("limits", limits)
201                .field("size", size)
202                .finish(),
203            Self::Ignore => write!(f, "Ignore"),
204            Self::AppWindow(id, arg0, arg1, arg2) => f
205                .debug_tuple("AppWindow")
206                .field(id)
207                .field(arg0)
208                .field(arg1)
209                .field(arg2)
210                .finish(),
211            Self::Window(id, arg0, arg1, view) => f
212                .debug_tuple("Window")
213                .field(id)
214                .field(arg0)
215                .field(arg1)
216                .field(&view.as_ref().map(|_| "view"))
217                .finish(),
218            Self::DestroyWindow(arg0) => f.debug_tuple("DestroyWindow").field(arg0).finish(),
219            Self::Task(_) => f.debug_tuple("Future").finish(),
220            Self::AppLayerShell(arg, arg1, arg2) => f
221                .debug_tuple("AppLayerShell")
222                .field(arg)
223                .field(arg1)
224                .field(arg2)
225                .finish(),
226            Self::LayerShell(arg0, arg1, view) => f
227                .debug_tuple("LayerShell")
228                .field(arg0)
229                .field(arg1)
230                .field(&view.as_ref().map(|_| "view"))
231                .finish(),
232            Self::DestroyLayerShell(arg0) => {
233                f.debug_tuple("DestroyLayerShell").field(arg0).finish()
234            }
235            #[cfg(wayland_platform)]
236            Self::Lock(id, output, arg0, view) => f
237                .debug_tuple("Lock")
238                .field(id)
239                .field(output)
240                .field(arg0)
241                .field(&view.as_ref().map(|_| "view"))
242                .finish(),
243            Self::DestroyLock(arg0) => f.debug_tuple("DestroyLock").field(arg0).finish(),
244            Self::SyncLiveSettings(arg0) => f.debug_tuple("SyncLiveSettings").field(arg0).finish(),
245        }
246    }
247}