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