cosmic/app/
mod.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4//! Build interactive cross-platform COSMIC applications.
5//!
6//! Check out our [application](https://github.com/pop-os/libcosmic/tree/master/examples/application)
7//! example in our repository.
8
9mod action;
10pub use action::Action;
11use cosmic_config::CosmicConfigEntry;
12pub mod context_drawer;
13pub use context_drawer::{ContextDrawer, context_drawer};
14use iced::application::BootFn;
15pub mod cosmic;
16pub mod settings;
17
18pub type Task<M> = iced::Task<crate::Action<M>>;
19
20pub use crate::Core;
21use crate::prelude::*;
22use crate::theme::THEME;
23use crate::widget::{container, id_container, menu, nav_bar, popover, space};
24use apply::Apply;
25use iced::{Length, Subscription};
26use iced::{theme, window};
27pub use settings::Settings;
28use std::borrow::Cow;
29use std::{cell::RefCell, rc::Rc};
30
31#[cold]
32pub(crate) fn iced_settings<App: Application>(
33    settings: Settings,
34    flags: App::Flags,
35) -> (iced::Settings, (Core, App::Flags), iced::window::Settings) {
36    preload_fonts();
37
38    let mut core = Core::default();
39    core.debug = settings.debug;
40    core.icon_theme_override = settings.default_icon_theme.is_some();
41    core.set_scale_factor(settings.scale_factor);
42    core.set_window_width(settings.size.width);
43    core.set_window_height(settings.size.height);
44
45    if let Some(icon_theme) = settings.default_icon_theme {
46        crate::icon_theme::set_default(icon_theme);
47    } else {
48        crate::icon_theme::set_default(crate::config::icon_theme());
49    }
50
51    THEME.lock().unwrap().set_theme(settings.theme.theme_type);
52
53    if settings.no_main_window {
54        core.main_window = Some(iced::window::Id::NONE);
55    }
56
57    let mut iced = iced::Settings::default();
58
59    iced.antialiasing = settings.antialiasing;
60    iced.default_font = settings.default_font;
61    iced.default_text_size = iced::Pixels(settings.default_text_size);
62    let exit_on_close = settings.exit_on_close;
63    iced.is_daemon = false;
64    iced.exit_on_close_request = settings.is_daemon;
65    let mut window_settings = iced::window::Settings::default();
66    window_settings.exit_on_close_request = exit_on_close;
67    iced.id = Some(App::APP_ID.to_owned());
68    #[cfg(target_os = "linux")]
69    {
70        window_settings.platform_specific.application_id = App::APP_ID.to_string();
71    }
72    core.exit_on_main_window_closed = exit_on_close;
73
74    if let Some(border_size) = settings.resizable {
75        window_settings.resize_border = border_size as u32;
76        window_settings.resizable = true;
77    }
78    window_settings.decorations = !settings.client_decorations;
79    window_settings.size = settings.size;
80    let min_size = settings.size_limits.min();
81    if min_size != iced::Size::ZERO {
82        window_settings.min_size = Some(min_size);
83    }
84    let max_size = settings.size_limits.max();
85    if max_size != iced::Size::INFINITE {
86        window_settings.max_size = Some(max_size);
87    }
88
89    window_settings.transparent = settings.transparent;
90    (iced, (core, flags), window_settings)
91}
92
93pub(crate) struct BootDataInner<A: crate::app::Application> {
94    pub flags: A::Flags,
95    pub core: Core,
96    pub settings: window::Settings,
97}
98
99pub(crate) struct BootData<A: crate::app::Application>(pub Rc<RefCell<Option<BootDataInner<A>>>>);
100
101impl<A: crate::app::Application> BootFn<cosmic::Cosmic<A>, crate::Action<A::Message>>
102    for BootData<A>
103{
104    fn boot(&self) -> (cosmic::Cosmic<A>, iced::Task<crate::Action<A::Message>>) {
105        let mut data = self.0.borrow_mut();
106        let mut data = data.take().unwrap();
107        let mut tasks = Vec::new();
108        #[cfg(feature = "multi-window")]
109        if data.core.main_window_id().is_some() {
110            let window_task = iced_runtime::task::oneshot(|channel| {
111                iced_runtime::Action::Window(iced_runtime::window::Action::Open(
112                    window::Id::RESERVED,
113                    data.settings,
114                    channel,
115                ))
116            });
117            data.core.set_main_window_id(Some(window::Id::RESERVED));
118            tasks.push(window_task.discard());
119        }
120        let (a, t) = cosmic::Cosmic::<A>::init((data.core, data.flags));
121        tasks.push(t);
122        (a, Task::batch(tasks))
123    }
124}
125/// Launch a COSMIC application with the given [`Settings`].
126///
127/// # Errors
128///
129/// Returns error on application failure.
130pub fn run<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result {
131    #[cfg(all(target_env = "gnu", not(target_os = "windows")))]
132    if let Some(threshold) = settings.default_mmap_threshold {
133        crate::malloc::limit_mmap_threshold(threshold);
134    }
135
136    let default_font = settings.default_font;
137    let (settings, (mut core, flags), window_settings) = iced_settings::<App>(settings, flags);
138    #[cfg(not(feature = "multi-window"))]
139    {
140        core.main_window = Some(iced::window::Id::RESERVED);
141
142        iced::application(
143            BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
144                flags,
145                core,
146                settings: window_settings.clone(),
147            })))),
148            cosmic::Cosmic::update,
149            cosmic::Cosmic::view,
150        )
151        .subscription(cosmic::Cosmic::subscription)
152        .title(cosmic::Cosmic::title)
153        .style(cosmic::Cosmic::style)
154        .theme(cosmic::Cosmic::theme)
155        .window_size((500.0, 800.0))
156        .settings(settings)
157        .window(window_settings)
158        .run()
159    }
160    #[cfg(feature = "multi-window")]
161    {
162        let no_main_window = core.main_window.is_none();
163        if no_main_window {
164            // app = app.window(window_settings);
165            core.main_window = Some(iced_core::window::Id::RESERVED);
166        }
167        let app = iced::daemon(
168            BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
169                flags,
170                core,
171                settings: window_settings,
172            })))),
173            cosmic::Cosmic::update,
174            cosmic::Cosmic::view,
175        );
176
177        app.subscription(cosmic::Cosmic::subscription)
178            .title(cosmic::Cosmic::title)
179            .style(cosmic::Cosmic::style)
180            .theme(cosmic::Cosmic::theme)
181            .settings(settings)
182            .run()
183    }
184}
185
186#[cfg(feature = "single-instance")]
187/// Launch a COSMIC application with the given [`Settings`].
188/// If the application is already running, the arguments will be passed to the
189/// running instance.
190/// # Errors
191/// Returns error on application failure.
192pub fn run_single_instance<App: Application>(settings: Settings, flags: App::Flags) -> iced::Result
193where
194    App::Flags: CosmicFlags,
195    App::Message: Clone + std::fmt::Debug + Send + 'static,
196{
197    use std::collections::HashMap;
198
199    let activation_token = std::env::var("XDG_ACTIVATION_TOKEN").ok();
200
201    let override_single = std::env::var("COSMIC_SINGLE_INSTANCE")
202        .map(|v| &v.to_lowercase() == "false" || &v == "0")
203        .unwrap_or_default();
204    if override_single {
205        return run::<App>(settings, flags);
206    }
207
208    let path: String = format!("/{}", App::APP_ID.replace('.', "/"));
209
210    let Ok(conn) = zbus::blocking::Connection::session() else {
211        tracing::warn!("Failed to connect to dbus");
212        return run::<App>(settings, flags);
213    };
214
215    if crate::dbus_activation::DbusActivationInterfaceProxyBlocking::builder(&conn)
216        .destination(App::APP_ID)
217        .ok()
218        .and_then(|b| b.path(path).ok())
219        .and_then(|b| b.destination(App::APP_ID).ok())
220        .and_then(|b| b.build().ok())
221        .is_some_and(|mut p| {
222            let res = {
223                let mut platform_data = HashMap::new();
224                if let Some(activation_token) = activation_token {
225                    platform_data.insert("activation-token", activation_token.into());
226                }
227                if let Ok(startup_id) = std::env::var("DESKTOP_STARTUP_ID") {
228                    platform_data.insert("desktop-startup-id", startup_id.into());
229                }
230                if let Some(action) = flags.action() {
231                    let action = action.to_string();
232                    p.activate_action(&action, flags.args(), platform_data)
233                } else {
234                    p.activate(platform_data)
235                }
236            };
237            match res {
238                Ok(()) => {
239                    tracing::info!("Successfully activated another instance");
240                    true
241                }
242                Err(err) => {
243                    tracing::warn!(?err, "Failed to activate another instance");
244                    false
245                }
246            }
247        })
248    {
249        tracing::info!("Another instance is running");
250        Ok(())
251    } else {
252        let (settings, (mut core, flags), window_settings) = iced_settings::<App>(settings, flags);
253        core.single_instance = true;
254
255        #[cfg(not(feature = "multi-window"))]
256        {
257            iced::application(
258                BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
259                    flags,
260                    core,
261                    settings: window_settings.clone(),
262                })))),
263                cosmic::Cosmic::update,
264                cosmic::Cosmic::view,
265            )
266            .subscription(cosmic::Cosmic::subscription)
267            .style(cosmic::Cosmic::style)
268            .theme(cosmic::Cosmic::theme)
269            .window_size((500.0, 800.0))
270            .settings(settings)
271            .window(window_settings)
272            .run()
273        }
274        #[cfg(feature = "multi-window")]
275        {
276            let no_main_window = core.main_window.is_none();
277            if no_main_window {
278                // app = app.window(window_settings);
279                core.main_window = Some(iced_core::window::Id::RESERVED);
280            }
281            let mut app = iced::daemon(
282                BootData(Rc::new(RefCell::new(Some(BootDataInner::<App> {
283                    flags,
284                    core,
285                    settings: window_settings,
286                })))),
287                cosmic::Cosmic::update,
288                cosmic::Cosmic::view,
289            );
290
291            app.subscription(cosmic::Cosmic::subscription)
292                .style(cosmic::Cosmic::style)
293                .title(cosmic::Cosmic::title)
294                .theme(cosmic::Cosmic::theme)
295                .settings(settings)
296                .run()
297        }
298    }
299}
300
301pub trait CosmicFlags {
302    type SubCommand: ToString + std::fmt::Debug + Clone + Send + 'static;
303    type Args: Into<Vec<String>> + std::fmt::Debug + Clone + Send + 'static;
304    #[must_use]
305    fn action(&self) -> Option<&Self::SubCommand> {
306        None
307    }
308
309    #[must_use]
310    fn args(&self) -> Vec<&str> {
311        Vec::new()
312    }
313}
314
315/// An interactive cross-platform COSMIC application.
316#[allow(unused_variables)]
317pub trait Application
318where
319    Self: Sized + 'static,
320{
321    /// Default async executor to use with the app.
322    type Executor: iced_futures::Executor;
323
324    /// Argument received [`Application::new`].
325    type Flags;
326
327    /// Message type specific to our app.
328    type Message: Clone + std::fmt::Debug + Send + 'static;
329
330    /// An ID that uniquely identifies the application.
331    /// The standard is to pick an ID based on a reverse-domain name notation.
332    /// IE: `com.system76.Settings`
333    const APP_ID: &'static str;
334
335    /// Grants access to the COSMIC Core.
336    fn core(&self) -> &Core;
337
338    /// Grants access to the COSMIC Core.
339    fn core_mut(&mut self) -> &mut Core;
340
341    /// Creates the application, and optionally emits task on initialize.
342    fn init(core: Core, flags: Self::Flags) -> (Self, Task<Self::Message>);
343
344    /// Displays a context drawer on the side of the application window when `Some`.
345    /// Use the [`ApplicationExt::set_show_context`] function for this to take effect.
346    fn context_drawer(&self) -> Option<ContextDrawer<'_, Self::Message>> {
347        None
348    }
349
350    /// Displays a dialog in the center of the application window when `Some`.
351    fn dialog(&self) -> Option<Element<'_, Self::Message>> {
352        None
353    }
354
355    /// Displays a footer at the bottom of the application window when `Some`.
356    fn footer(&self) -> Option<Element<'_, Self::Message>> {
357        None
358    }
359
360    /// Attaches elements to the start section of the header.
361    fn header_start(&self) -> Vec<Element<'_, Self::Message>> {
362        Vec::new()
363    }
364
365    /// Attaches elements to the center of the header.
366    fn header_center(&self) -> Vec<Element<'_, Self::Message>> {
367        Vec::new()
368    }
369
370    /// Attaches elements to the end section of the header.
371    fn header_end(&self) -> Vec<Element<'_, Self::Message>> {
372        Vec::new()
373    }
374
375    /// Allows overriding the default nav bar widget.
376    fn nav_bar(&self) -> Option<Element<'_, crate::Action<Self::Message>>> {
377        if !self.core().nav_bar_active() {
378            return None;
379        }
380
381        let nav_model = self.nav_model()?;
382
383        let mut nav =
384            crate::widget::nav_bar(nav_model, |id| crate::Action::Cosmic(Action::NavBar(id)))
385                .on_context(|id| crate::Action::Cosmic(Action::NavBarContext(id)))
386                .context_menu(self.nav_context_menu(self.core().nav_bar_context()))
387                .into_container()
388                .width(iced::Length::Shrink)
389                .height(iced::Length::Fill);
390
391        if !self.core().is_condensed() {
392            nav = nav.max_width(280);
393        }
394
395        Some(Element::from(nav))
396    }
397
398    /// Shows a context menu for the active nav bar item.
399    fn nav_context_menu(
400        &self,
401        id: nav_bar::Id,
402    ) -> Option<Vec<menu::Tree<crate::Action<Self::Message>>>> {
403        None
404    }
405
406    /// Allows COSMIC to integrate with your application's [`nav_bar::Model`].
407    fn nav_model(&self) -> Option<&nav_bar::Model> {
408        None
409    }
410
411    /// Called before closing the application. Returning a message will override closing windows.
412    fn on_app_exit(&mut self) -> Option<Self::Message> {
413        None
414    }
415
416    /// Called when a window requests to be closed.
417    fn on_close_requested(&self, id: window::Id) -> Option<Self::Message> {
418        None
419    }
420
421    // Called when context drawer is toggled
422    fn on_context_drawer(&mut self) -> Task<Self::Message> {
423        Task::none()
424    }
425
426    /// Called when the escape key is pressed.
427    fn on_escape(&mut self) -> Task<Self::Message> {
428        Task::none()
429    }
430
431    /// Called when a navigation item is selected.
432    fn on_nav_select(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
433        Task::none()
434    }
435
436    /// Called when a context menu is requested for a navigation item.
437    fn on_nav_context(&mut self, id: nav_bar::Id) -> Task<Self::Message> {
438        Task::none()
439    }
440
441    /// Called when the search function is requested.
442    fn on_search(&mut self) -> Task<Self::Message> {
443        Task::none()
444    }
445
446    /// Called when a window is resized.
447    fn on_window_resize(&mut self, id: window::Id, width: f32, height: f32) {}
448
449    /// Event sources that are to be listened to.
450    fn subscription(&self) -> Subscription<Self::Message> {
451        Subscription::none()
452    }
453
454    /// Respond to an application-specific message.
455    fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
456        Task::none()
457    }
458
459    /// Respond to a system theme change
460    fn system_theme_update(
461        &mut self,
462        keys: &[&'static str],
463        new_theme: &cosmic_theme::Theme,
464    ) -> Task<Self::Message> {
465        Task::none()
466    }
467
468    /// Respond to a system theme mode change
469    fn system_theme_mode_update(
470        &mut self,
471        keys: &[&'static str],
472        new_theme: &cosmic_theme::ThemeMode,
473    ) -> Task<Self::Message> {
474        Task::none()
475    }
476
477    /// Constructs the view for the main window.
478    fn view(&self) -> Element<'_, Self::Message>;
479
480    /// Constructs views for other windows.
481    fn view_window(&self, id: window::Id) -> Element<'_, Self::Message> {
482        panic!("no view for window {id:?}");
483    }
484
485    /// Overrides the default style for applications
486    fn style(&self) -> Option<theme::Style> {
487        None
488    }
489
490    /// Handles dbus activation messages
491    #[cfg(feature = "single-instance")]
492    fn dbus_activation(&mut self, msg: crate::dbus_activation::Message) -> Task<Self::Message> {
493        Task::none()
494    }
495
496    /// Invoked on connect to dbus session socket used for dbus activation
497    ///
498    /// Can be used to expose custom interfaces on the same owned name.
499    #[cfg(feature = "single-instance")]
500    fn dbus_connection(&mut self, conn: zbus::Connection) -> Task<Self::Message> {
501        Task::none()
502    }
503}
504
505/// Methods automatically derived for all types implementing [`Application`].
506pub trait ApplicationExt: Application {
507    /// Initiates a window drag.
508    fn drag(&mut self) -> Task<Self::Message>;
509
510    /// Maximizes the window.
511    fn maximize(&mut self) -> Task<Self::Message>;
512
513    /// Minimizes the window.
514    fn minimize(&mut self) -> Task<Self::Message>;
515    /// Get the title of the main window.
516
517    #[cfg(not(feature = "multi-window"))]
518    fn title(&self) -> &str;
519
520    #[cfg(feature = "multi-window")]
521    /// Get the title of a window.
522    fn title(&self, id: window::Id) -> &str;
523
524    /// Set the context drawer visibility.
525    fn set_show_context(&mut self, show: bool) {
526        self.core_mut().set_show_context(show);
527    }
528
529    /// Set the header bar title.
530    fn set_header_title(&mut self, title: String) {
531        self.core_mut().set_header_title(title);
532    }
533
534    #[cfg(not(feature = "multi-window"))]
535    /// Set the title of the main window.
536    fn set_window_title(&mut self, title: String) -> Task<Self::Message>;
537
538    #[cfg(feature = "multi-window")]
539    /// Set the title of a window.
540    fn set_window_title(&mut self, title: String, id: window::Id) -> Task<Self::Message>;
541
542    /// View template for the main window.
543    fn view_main(&self) -> Element<'_, crate::Action<Self::Message>>;
544
545    fn watch_config<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone + PartialEq>(
546        &self,
547        id: &'static str,
548    ) -> iced::Subscription<cosmic_config::Update<T>> {
549        self.core().watch_config(id)
550    }
551
552    fn watch_state<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone + PartialEq>(
553        &self,
554        id: &'static str,
555    ) -> iced::Subscription<cosmic_config::Update<T>> {
556        self.core().watch_state(id)
557    }
558}
559
560impl<App: Application> ApplicationExt for App {
561    fn drag(&mut self) -> Task<Self::Message> {
562        self.core().drag(None)
563    }
564
565    fn maximize(&mut self) -> Task<Self::Message> {
566        self.core().maximize(None, true)
567    }
568
569    fn minimize(&mut self) -> Task<Self::Message> {
570        self.core().minimize(None)
571    }
572
573    #[cfg(feature = "multi-window")]
574    fn title(&self, id: window::Id) -> &str {
575        self.core().title.get(&id).map_or("", |s| s.as_str())
576    }
577
578    #[cfg(not(feature = "multi-window"))]
579    fn title(&self) -> &str {
580        self.core()
581            .main_window_id()
582            .and_then(|id| self.core().title.get(&id).map(std::string::String::as_str))
583            .unwrap_or("")
584    }
585
586    #[cfg(feature = "multi-window")]
587    fn set_window_title(&mut self, title: String, id: window::Id) -> Task<Self::Message> {
588        self.core_mut().title.insert(id, title.clone());
589        self.core().set_title(Some(id), title)
590    }
591
592    #[cfg(not(feature = "multi-window"))]
593    fn set_window_title(&mut self, title: String) -> Task<Self::Message> {
594        let Some(id) = self.core().main_window_id() else {
595            return Task::none();
596        };
597
598        self.core_mut().title.insert(id, title.clone());
599        Task::none()
600    }
601
602    #[allow(clippy::too_many_lines)]
603    /// Creates the view for the main window.
604    fn view_main(&self) -> Element<'_, crate::Action<Self::Message>> {
605        let core = self.core();
606        let is_condensed = core.is_condensed();
607        let sharp_corners = core.window.sharp_corners;
608        let maximized = core.window.is_maximized;
609        let content_container = core.window.content_container;
610        let show_context = core.window.show_context;
611        let nav_bar_active = core.nav_bar_active();
612        let focused = core
613            .focus_chain()
614            .iter()
615            .any(|i| Some(*i) == self.core().main_window_id());
616
617        let border_padding = if maximized { 8 } else { 7 };
618
619        let main_content_padding = if !content_container {
620            [0, 0, 0, 0]
621        } else {
622            let right_padding = if show_context { 0 } else { border_padding };
623            let left_padding = if nav_bar_active { 0 } else { border_padding };
624
625            [0, right_padding, 0, left_padding]
626        };
627
628        let content_row = crate::widget::row::with_children({
629            let mut widgets = Vec::with_capacity(3);
630
631            // Insert nav bar onto the left side of the window.
632            let has_nav = if let Some(nav) = self
633                .nav_bar()
634                .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar")))
635            {
636                widgets.push(
637                    container(nav)
638                        .padding([
639                            0,
640                            if is_condensed { border_padding } else { 8 },
641                            border_padding,
642                            border_padding,
643                        ])
644                        .into(),
645                );
646                true
647            } else {
648                false
649            };
650
651            if self.nav_model().is_none() || core.show_content() {
652                let main_content = self.view();
653
654                //TODO: reduce duplication
655                let context_width = core.context_width(has_nav);
656                if core.window.context_is_overlay && show_context {
657                    if let Some(context) = self.context_drawer() {
658                        widgets.push(
659                            crate::widget::context_drawer(
660                                context.title,
661                                context.actions,
662                                context.header,
663                                context.footer,
664                                context.on_close,
665                                main_content,
666                                context.content,
667                                context_width,
668                            )
669                            .apply(|drawer| {
670                                Element::from(id_container(
671                                    drawer,
672                                    iced_core::id::Id::new("COSMIC_context_drawer"),
673                                ))
674                            })
675                            .apply(container)
676                            .padding([0, if content_container { border_padding } else { 0 }, 0, 0])
677                            .apply(Element::from)
678                            .map(crate::Action::App),
679                        );
680                    } else {
681                        widgets.push(
682                            container(main_content.map(crate::Action::App))
683                                .padding(main_content_padding)
684                                .into(),
685                        );
686                    }
687                } else {
688                    //TODO: hide content when out of space
689                    widgets.push(
690                        container(main_content.map(crate::Action::App))
691                            .padding(main_content_padding)
692                            .into(),
693                    );
694                    if let Some(context) = self.context_drawer() {
695                        widgets.push(
696                            crate::widget::ContextDrawer::new_inner(
697                                context.title,
698                                context.actions,
699                                context.header,
700                                context.footer,
701                                context.content,
702                                context.on_close,
703                                context_width,
704                            )
705                            .apply(Element::from)
706                            .map(crate::Action::App)
707                            .apply(container)
708                            .width(context_width)
709                            .apply(|drawer| {
710                                Element::from(id_container(
711                                    drawer,
712                                    iced_core::id::Id::new("COSMIC_context_drawer"),
713                                ))
714                            })
715                            .apply(container)
716                            .padding(if content_container {
717                                [0, border_padding, border_padding, border_padding]
718                            } else {
719                                [0, 0, 0, 0]
720                            })
721                            .into(),
722                        );
723                    } else {
724                        //TODO: this element is added to workaround state issues
725                        widgets.push(space::horizontal().width(Length::Shrink).into());
726                    }
727                }
728            }
729
730            widgets
731        });
732
733        let content_col = crate::widget::column::with_capacity(2)
734            .push(content_row)
735            .push_maybe(self.footer().map(|footer| {
736                container(footer.map(crate::Action::App)).padding([
737                    0,
738                    border_padding,
739                    border_padding,
740                    border_padding,
741                ])
742            }));
743        let content: Element<_> = if content_container {
744            content_col
745                .width(iced::Length::Fill)
746                .height(iced::Length::Fill)
747                .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container")))
748                .into()
749        } else {
750            content_col.into()
751        };
752
753        // Ensures visually aligned radii for content and window corners
754        let window_corner_radius = if sharp_corners {
755            crate::theme::active().cosmic().radius_0()
756        } else {
757            crate::theme::active()
758                .cosmic()
759                .radius_s()
760                .map(|x| if x < 4.0 { x } else { x + 4.0 })
761        };
762
763        let view_column = crate::widget::column::with_capacity(2)
764            .push_maybe(if core.window.show_headerbar {
765                Some({
766                    let mut header = crate::widget::header_bar()
767                        .focused(focused)
768                        .maximized(maximized)
769                        .sharp_corners(sharp_corners)
770                        .transparent(content_container)
771                        .title(&core.window.header_title)
772                        .on_drag(crate::Action::Cosmic(Action::Drag))
773                        .on_right_click(crate::Action::Cosmic(Action::ShowWindowMenu))
774                        .on_double_click(crate::Action::Cosmic(Action::Maximize));
775
776                    if self.nav_model().is_some() {
777                        let toggle = crate::widget::nav_bar_toggle()
778                            .active(core.nav_bar_active())
779                            .selected(focused)
780                            .on_toggle(if is_condensed {
781                                crate::Action::Cosmic(Action::ToggleNavBarCondensed)
782                            } else {
783                                crate::Action::Cosmic(Action::ToggleNavBar)
784                            });
785
786                        header = header.start(toggle);
787                    }
788
789                    if core.window.show_close {
790                        header = header.on_close(crate::Action::Cosmic(Action::Close));
791                    }
792
793                    if core.window.show_maximize && crate::config::show_maximize() {
794                        header = header.on_maximize(crate::Action::Cosmic(Action::Maximize));
795                    }
796
797                    if core.window.show_minimize && crate::config::show_minimize() {
798                        header = header.on_minimize(crate::Action::Cosmic(Action::Minimize));
799                    }
800
801                    for element in self.header_start() {
802                        header = header.start(element.map(crate::Action::App));
803                    }
804
805                    for element in self.header_center() {
806                        header = header.center(element.map(crate::Action::App));
807                    }
808
809                    for element in self.header_end() {
810                        header = header.end(element.map(crate::Action::App));
811                    }
812
813                    if content_container {
814                        header.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header")))
815                    } else {
816                        // Needed to avoid header bar corner gaps for apps without a content container
817                        header
818                            .apply(container)
819                            .class(crate::theme::Container::custom(move |theme| {
820                                let cosmic = theme.cosmic();
821                                container::Style {
822                                    background: Some(iced::Background::Color(
823                                        cosmic.background.base.into(),
824                                    )),
825                                    border: iced::Border {
826                                        radius: [
827                                            (window_corner_radius[0] - 1.0).max(0.0),
828                                            (window_corner_radius[1] - 1.0).max(0.0),
829                                            cosmic.radius_0()[2],
830                                            cosmic.radius_0()[3],
831                                        ]
832                                        .into(),
833                                        ..Default::default()
834                                    },
835                                    ..Default::default()
836                                }
837                            }))
838                            .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header")))
839                    }
840                })
841            } else {
842                None
843            })
844            // The content element contains every element beneath the header.
845            .push(content)
846            .apply(container)
847            .padding(if maximized { 0 } else { 1 })
848            .class(crate::theme::Container::custom(move |theme| {
849                container::Style {
850                    background: if content_container {
851                        Some(iced::Background::Color(
852                            theme.cosmic().background.base.into(),
853                        ))
854                    } else {
855                        None
856                    },
857                    border: iced::Border {
858                        color: theme.cosmic().bg_divider().into(),
859                        width: if maximized { 0.0 } else { 1.0 },
860                        radius: window_corner_radius.into(),
861                    },
862                    ..Default::default()
863                }
864            }));
865
866        // Show any current dialog on top and centered over the view content
867        // We have to use a popover even without a dialog to keep the tree from changing
868        let mut popover = popover(view_column).modal(true);
869        if let Some(dialog) = self
870            .dialog()
871            .map(|w| Element::from(id_container(w, iced_core::id::Id::new("COSMIC_dialog"))))
872        {
873            popover = popover.popup(dialog.map(crate::Action::App));
874        }
875
876        let view_element: Element<_> = popover.into();
877        view_element.debug(core.debug)
878    }
879}
880
881const EMBEDDED_FONTS: &[&[u8]] = &[
882    include_bytes!("../../res/open-sans/OpenSans-Light.ttf"),
883    include_bytes!("../../res/open-sans/OpenSans-Regular.ttf"),
884    include_bytes!("../../res/open-sans/OpenSans-Semibold.ttf"),
885    include_bytes!("../../res/open-sans/OpenSans-Bold.ttf"),
886    include_bytes!("../../res/open-sans/OpenSans-ExtraBold.ttf"),
887    include_bytes!("../../res/noto/NotoSansMono-Regular.ttf"),
888    include_bytes!("../../res/noto/NotoSansMono-Bold.ttf"),
889];
890
891#[cold]
892fn preload_fonts() {
893    let mut font_system = iced::advanced::graphics::text::font_system()
894        .write()
895        .unwrap();
896
897    EMBEDDED_FONTS
898        .iter()
899        .for_each(move |font| font_system.load_font(Cow::Borrowed(font)));
900}