From 340a029f1d5c0bc5ce5c7c9a15f176901cec8508 Mon Sep 17 00:00:00 2001 From: Aaron Honeycutt Date: Sat, 18 May 2024 10:13:20 -0600 Subject: [PATCH 1/4] Work on adding Settings page --- i18n/en/cosmic_backups.ftl | 11 ++++++++ src/app.rs | 54 ++++++++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/i18n/en/cosmic_backups.ftl b/i18n/en/cosmic_backups.ftl index da6c2ec..5b32d59 100644 --- a/i18n/en/cosmic_backups.ftl +++ b/i18n/en/cosmic_backups.ftl @@ -5,8 +5,19 @@ welcome = Backup and backup often! ✨ # Context Pages ## About +about = About git-description = Git commit {$hash} on {$date} +## Settings +settings = Settings + +### Appearance +appearance = Appearance +theme = Theme +match-desktop = Match desktop +dark = Dark +light = Light + # Menu ## File diff --git a/src/app.rs b/src/app.rs index a84ff60..1fcd1df 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,9 +6,10 @@ use std::{ process, }; use crate::fl; +use crate::config::AppTheme; use cosmic::app::{Command, Core}; use cosmic::{ - cosmic_theme, ApplicationExt, + cosmic_config, cosmic_theme, ApplicationExt, iced::{Alignment, Length}, }; use cosmic::iced::alignment::{Horizontal, Vertical}; @@ -30,6 +31,9 @@ pub struct App { /// This is the core of your application, it is used to communicate with the Cosmic runtime. /// It is used to send messages to your application, and to access the resources of the Cosmic runtime. core: Core, + config_handler: Option, + config: config::CosmicBackupsConfig, + app_themes: Vec, context_page: ContextPage, key_binds: HashMap, } @@ -42,6 +46,8 @@ pub enum Message { // Cut(Option), ToggleContextPage(ContextPage), LaunchUrl(String), + AppTheme(usize), + SystemThemeModeChange(cosmic_theme::ThemeMode), WindowClose, WindowNew, } @@ -49,12 +55,14 @@ pub enum Message { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum ContextPage { About, + Settings, } impl ContextPage { fn title(&self) -> String { match self { - Self::About => String::new(), + Self::About => fl!("about"), + Self::Settings => fl!("settings"), } } } @@ -63,6 +71,7 @@ impl ContextPage { pub enum Action { About, // Cut, + Settings, WindowClose, WindowNew, } @@ -73,6 +82,7 @@ impl MenuAction for Action { match self { Action::About => Message::ToggleContextPage(ContextPage::About), // Action::Cut => Message::Cut(entity_opt), + Action::Settings => Message::ToggleContextPage(ContextPage::Settings), Action::WindowClose => Message::WindowClose, Action::WindowNew => Message::WindowNew, } @@ -116,6 +126,24 @@ impl App { .into() } + fn settings(&self) -> Element { + let app_theme_selected = match self.config.app_theme { + AppTheme::Dark => 1, + AppTheme::Light => 2, + AppTheme::System => 0, + }; + widget::settings::view_column(vec![widget::settings::view_section(fl!("appearance")) + .add( + widget::settings::item::builder(fl!("theme")).control(widget::dropdown( + &self.app_themes, + Some(app_theme_selected), + Message::AppTheme, + )), + ) + .into()]) + .into() + } + } @@ -144,7 +172,10 @@ impl Application for App { fn init(core: Core, _input: Self::Flags) -> (Self, Command) { let app = App { core, - context_page: ContextPage::About, + context_page: ContextPage::Settings, + config_handler: flags.config_handler, + config: flags.config, + app_themes: vec![fl!("match-desktop"), fl!("dark"), fl!("light")], key_binds: key_binds(), }; @@ -158,6 +189,7 @@ impl Application for App { Some(match self.context_page { ContextPage::About => self.about(), + ContextPage::Settings => self.settings(), }) } @@ -197,13 +229,25 @@ impl Application for App { Err(err) => { eprintln!("failed to get current executable path: {}", err); } - }, + } Message::LaunchUrl(url) => match open::that_detached(&url) { Ok(()) => {} Err(err) => { log::warn!("failed to open {:?}: {}", url, err); } - }, + } + Message::AppTheme(index) => { + let app_theme = match index { + 1 => AppTheme::Dark, + 2 => AppTheme::Light, + _ => AppTheme::System, + }; + config_set!(app_theme, app_theme); + return self.update_config(); + } + Message::SystemThemeModeChange(_) => { + return self.update_config(); + } } Command::none() From d5de657715ff1465156e657e81f4b183dc280b3d Mon Sep 17 00:00:00 2001 From: Aaron Honeycutt Date: Sat, 18 May 2024 14:22:09 -0600 Subject: [PATCH 2/4] Working though config and flag errors --- src/app.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/app.rs b/src/app.rs index 1fcd1df..e633bb2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,7 +6,7 @@ use std::{ process, }; use crate::fl; -use crate::config::AppTheme; +use crate::config::{AppTheme, Config}; use cosmic::app::{Command, Core}; use cosmic::{ cosmic_config, cosmic_theme, ApplicationExt, @@ -67,6 +67,11 @@ impl ContextPage { } } +#[derive(Clone, Debug)] +pub struct Flags { + pub config_handler: Option, + pub config: config::CosmicBackupsConfig,} + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { About, From 092f3fadc5be0bd3929a50ca4a8182f099170817 Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Sat, 18 May 2024 14:06:52 -0700 Subject: [PATCH 3/4] chore: add config --- Cargo.toml | 2 + src/app.rs | 119 ++++++++++++++++++++++++++++------------------ src/app/config.rs | 52 ++++++++++++++++++++ src/main.rs | 16 ++++++- 4 files changed, 140 insertions(+), 49 deletions(-) create mode 100644 src/app/config.rs diff --git a/Cargo.toml b/Cargo.toml index e68e242..3d83bd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ once_cell = "1.19.0" rust-embed = "8.3.0" log = "0.4" open = "5.0.2" +serde = { version = "1.0.202", features = ["serde_derive"] } +paste = "1.0" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic.git" diff --git a/src/app.rs b/src/app.rs index e633bb2..7d58749 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,27 +1,25 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{ - collections::{HashMap}, - env, - process, -}; +use crate::app::config::AppTheme; use crate::fl; -use crate::config::{AppTheme, Config}; use cosmic::app::{Command, Core}; -use cosmic::{ - cosmic_config, cosmic_theme, ApplicationExt, - iced::{Alignment, Length}, -}; use cosmic::iced::alignment::{Horizontal, Vertical}; use cosmic::iced::window; use cosmic::iced_core::keyboard::Key; use cosmic::widget::menu::{ - action::{MenuAction}, + action::MenuAction, key_bind::{KeyBind, Modifier}, }; use cosmic::widget::segmented_button::Entity; +use cosmic::{ + cosmic_config, cosmic_theme, + iced::{Alignment, Length}, + ApplicationExt, +}; use cosmic::{widget, Application, Element}; +use std::{collections::HashMap, env, process}; +pub mod config; pub mod menu; /// This is the struct that represents your application. @@ -70,7 +68,8 @@ impl ContextPage { #[derive(Clone, Debug)] pub struct Flags { pub config_handler: Option, - pub config: config::CosmicBackupsConfig,} + pub config: config::CosmicBackupsConfig, +} #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Action { @@ -95,9 +94,9 @@ impl MenuAction for Action { } impl App { -// fn update_config(&mut self) -> Command> { -// cosmic::app::command::set_theme(self.config.app_theme.theme()) -// } + fn update_config(&mut self) -> Command { + cosmic::app::command::set_theme(self.config.app_theme.theme()) + } fn about(&self) -> Element { let cosmic_theme::Spacing { space_xxs, .. } = cosmic::theme::active().cosmic().spacing; @@ -106,31 +105,34 @@ impl App { let short_hash: String = hash.chars().take(7).collect(); let date = env!("VERGEN_GIT_COMMIT_DATE"); widget::column::with_children(vec![ - widget::svg(widget::svg::Handle::from_memory( - &include_bytes!( - "../res/icons/hicolor/128x128/apps/com.example.CosmicAppTemplate.svg" - )[..], - )) + widget::svg(widget::svg::Handle::from_memory( + &include_bytes!( + "../res/icons/hicolor/128x128/apps/com.example.CosmicAppTemplate.svg" + )[..], + )) + .into(), + widget::text::title3(fl!("cosmic-backups")).into(), + widget::button::link(repository) + .on_press(Message::LaunchUrl(repository.to_string())) + .padding(0) .into(), - widget::text::title3(fl!("cosmic-backups")).into(), - widget::button::link(repository) - .on_press(Message::LaunchUrl(repository.to_string())) - .padding(0) - .into(), - widget::button::link(fl!( - "git-description", - hash = short_hash.as_str(), - date = date - )) - .on_press(Message::LaunchUrl(format!("{}/commits/{}", repository, hash))) - .padding(0) - .into(), - ]) + widget::button::link(fl!( + "git-description", + hash = short_hash.as_str(), + date = date + )) + .on_press(Message::LaunchUrl(format!( + "{}/commits/{}", + repository, hash + ))) + .padding(0) + .into(), + ]) .align_items(Alignment::Center) .spacing(space_xxs) .into() } - + fn settings(&self) -> Element { let app_theme_selected = match self.config.app_theme { AppTheme::Dark => 1, @@ -148,14 +150,12 @@ impl App { .into()]) .into() } - } - impl Application for App { type Executor = cosmic::executor::Default; - type Flags = (); + type Flags = Flags; type Message = Message; @@ -174,7 +174,7 @@ impl Application for App { vec![menu::menu_bar(&self.key_binds)] } - fn init(core: Core, _input: Self::Flags) -> (Self, Command) { + fn init(core: Core, flags: Self::Flags) -> (Self, Command) { let app = App { core, context_page: ContextPage::Settings, @@ -206,11 +206,37 @@ impl Application for App { .align_y(Vertical::Center) .into() } - + /// Handle application events here. fn update(&mut self, message: Self::Message) -> Command { - - match message { + // Helper for updating config values efficiently + macro_rules! config_set { + ($name: ident, $value: expr) => { + match &self.config_handler { + Some(config_handler) => { + match paste::paste! { self.config.[](config_handler, $value) } { + Ok(_) => {} + Err(err) => { + log::warn!( + "failed to save config {:?}: {}", + stringify!($name), + err + ); + } + } + } + None => { + self.config.$name = $value; + log::warn!( + "failed to save config {:?}: no config handler", + stringify!($name) + ); + } + } + }; + } + + match message { Message::ToggleContextPage(context_page) => { //TODO: ensure context menus are closed if self.context_page == context_page { @@ -234,13 +260,13 @@ impl Application for App { Err(err) => { eprintln!("failed to get current executable path: {}", err); } - } + }, Message::LaunchUrl(url) => match open::that_detached(&url) { Ok(()) => {} Err(err) => { log::warn!("failed to open {:?}: {}", url, err); } - } + }, Message::AppTheme(index) => { let app_theme = match index { 1 => AppTheme::Dark, @@ -255,9 +281,8 @@ impl Application for App { } } - Command::none() - - } + Command::none() + } } pub fn key_binds() -> HashMap { diff --git a/src/app/config.rs b/src/app/config.rs new file mode 100644 index 0000000..7286a2f --- /dev/null +++ b/src/app/config.rs @@ -0,0 +1,52 @@ +use crate::app::App; +use cosmic::{ + cosmic_config::{self, cosmic_config_derive::CosmicConfigEntry, Config, CosmicConfigEntry}, + theme, Application, +}; +use serde::{Deserialize, Serialize}; + +pub const CONFIG_VERSION: u64 = 1; + +#[derive(Clone, Default, Debug, Eq, PartialEq, Deserialize, Serialize, CosmicConfigEntry)] +pub struct CosmicBackupsConfig { + pub app_theme: AppTheme, +} + +impl CosmicBackupsConfig { + pub fn config_handler() -> Option { + Config::new(App::APP_ID, CONFIG_VERSION).ok() + } + + pub fn config() -> CosmicBackupsConfig { + match Self::config_handler() { + Some(config_handler) => { + let config = CosmicBackupsConfig::get_entry(&config_handler).unwrap_or_else( + |(errs, config)| { + log::info!("errors loading config: {:?}", errs); + config + }, + ); + config + } + None => CosmicBackupsConfig::default(), + } + } +} + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum AppTheme { + Dark, + Light, + #[default] + System, +} + +impl AppTheme { + pub fn theme(&self) -> theme::Theme { + match self { + Self::Dark => theme::Theme::dark(), + Self::Light => theme::Theme::light(), + Self::System => theme::system_preference(), + } + } +} diff --git a/src/main.rs b/src/main.rs index b0f01f5..5416903 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-only use crate::app::App; +use app::{config::CosmicBackupsConfig, Flags}; +use cosmic::app::Settings; + /// The `app` module is used by convention to indicate the main component of our application. mod app; mod core; @@ -11,6 +14,15 @@ mod core; /// - `()` is the flags that your app needs to use before it starts. /// If your app does not need any flags, you can pass in `()`. fn main() -> cosmic::iced::Result { - let settings = cosmic::app::Settings::default(); - cosmic::app::run::(settings, ()) + let (settings, flags) = settings(); + cosmic::app::run::(settings, flags) +} + +fn settings() -> (Settings, Flags) { + let settings = Settings::default(); + let flags = Flags { + config_handler: CosmicBackupsConfig::config_handler(), + config: CosmicBackupsConfig::config(), + }; + (settings, flags) } From dbb440898cb5395f41c1ff4f03f5d90f718836e1 Mon Sep 17 00:00:00 2001 From: Aaron Honeycutt Date: Sat, 18 May 2024 15:37:28 -0600 Subject: [PATCH 4/4] Working Settings for theme change --- i18n/en/cosmic_backups.ftl | 1 + src/app/menu.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/i18n/en/cosmic_backups.ftl b/i18n/en/cosmic_backups.ftl index 5b32d59..e8c5430 100644 --- a/i18n/en/cosmic_backups.ftl +++ b/i18n/en/cosmic_backups.ftl @@ -31,4 +31,5 @@ cut = Cut ## View view = View +menu-settings = Settings... menu-about = About COSMIC Backups... diff --git a/src/app/menu.rs b/src/app/menu.rs index 8d1532f..78b264b 100644 --- a/src/app/menu.rs +++ b/src/app/menu.rs @@ -38,6 +38,8 @@ pub fn menu_bar<'a>(key_binds: &HashMap) -> Element<'a, Message items( key_binds, vec![ + Item::Button(fl!("menu-settings"), Action::Settings), + Item::Divider, Item::Button(fl!("menu-about"), Action::About), ], ),