Skip to main content

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