// Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 //! Application API example use std::collections::HashMap; use std::{env, process}; use cosmic::app::{Core, Settings, Task}; use cosmic::iced::window; use cosmic::iced_core::alignment::{Horizontal, Vertical}; use cosmic::iced_core::keyboard::Key; use cosmic::iced_core::{Length, Size}; use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::Modifier; use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; use cosmic::{executor, Element}; /// Runs application with these settings #[rustfmt::skip] fn main() -> Result<(), Box> { tracing_subscriber::fmt::init(); let _ = tracing_log::LogTracer::init(); let settings = Settings::default() .antialiasing(true) .client_decorations(true) .debug(false) .default_icon_theme("Pop") .default_text_size(16.0) .scale_factor(1.0) .size(Size::new(1024., 768.)); cosmic::app::run::(settings, ())?; Ok(()) } /// Messages that are used specifically by our [`App`]. #[derive(Clone, Debug)] pub enum Message { WindowClose, WindowNew, ToggleHideContent, } /// The [`App`] stores application-specific state. pub struct App { core: Core, config: Config, key_binds: HashMap, } pub struct Config { hide_content: bool, } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { WindowClose, ToggleHideContent, WindowNew, } impl MenuAction for Action { type Message = Message; fn message(&self) -> Self::Message { match self { Action::WindowClose => Message::WindowClose, Action::ToggleHideContent => Message::ToggleHideContent, Action::WindowNew => Message::WindowNew, } } } /// Implement [`cosmic::Application`] to integrate with COSMIC. impl cosmic::Application for App { /// Default async executor to use with the app. type Executor = executor::Default; /// Argument received [`cosmic::Application::new`]. type Flags = (); /// Message type specific to our [`App`]. type Message = Message; /// The unique application ID to supply to the window manager. const APP_ID: &'static str = "org.cosmic.AppDemo"; fn core(&self) -> &Core { &self.core } fn core_mut(&mut self) -> &mut Core { &mut self.core } /// Creates the application, and optionally emits task on initialize. fn init(core: Core, _input: Self::Flags) -> (Self, Task) { let app = App { core, config: Config { hide_content: false, }, key_binds: key_binds(), }; (app, Task::none()) } fn header_start(&self) -> Vec> { vec![menu_bar(&self.config, &self.key_binds)] } /// Handle application events here. fn update(&mut self, message: Self::Message) -> Task { match message { Message::WindowClose => { return window::close(self.core.main_window_id().unwrap()); } Message::WindowNew => match env::current_exe() { Ok(exe) => match process::Command::new(&exe).spawn() { Ok(_child) => {} Err(err) => { eprintln!("failed to execute {:?}: {}", exe, err); } }, Err(err) => { eprintln!("failed to get current executable path: {}", err); } }, Message::ToggleHideContent => self.config.hide_content = !self.config.hide_content, } Task::none() } /// Creates a view after each update. fn view(&self) -> Element { let text = if self.config.hide_content { cosmic::widget::text("") } else { cosmic::widget::text("Menu Example") }; let centered = cosmic::widget::container(text) .width(Length::Fill) .height(Length::Shrink) .align_x(Horizontal::Center) .align_y(Vertical::Center); Element::from(centered) } } pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap) -> Element<'a, Message> { menu::bar(vec![menu::Tree::with_children( menu::root("File"), menu::items( key_binds, vec![ menu::Item::Button( "New window", Some(cosmic::widget::icon::from_name("screenshot-window-symbolic").into()), Action::WindowNew, ), menu::Item::Divider, menu::Item::Folder( "View", vec![menu::Item::CheckBox( "Hide content", Some(cosmic::widget::icon::from_name("view-conceal-symbolic").into()), config.hide_content, Action::ToggleHideContent, )], ), menu::Item::Divider, menu::Item::Button( "Quit", Some(cosmic::widget::icon::from_name("window-close-symbolic").into()), Action::WindowClose, ), ], ), )]) .item_height(ItemHeight::Dynamic(40)) .item_width(ItemWidth::Uniform(240)) .spacing(4.0) .into() } pub fn key_binds() -> HashMap { let mut key_binds = HashMap::new(); macro_rules! bind { ([$($modifier:ident),* $(,)?], $key:expr, $action:ident) => {{ key_binds.insert( KeyBind { modifiers: vec![$(Modifier::$modifier),*], key: $key, }, Action::$action, ); }}; } bind!([Ctrl], Key::Character("w".into()), WindowClose); bind!([Ctrl, Shift], Key::Character("n".into()), WindowNew); key_binds }