From c197243c8eafc36be5d40b61e1a3d51932069fa4 Mon Sep 17 00:00:00 2001 From: Frederic Laing Date: Fri, 7 Nov 2025 18:00:20 +0100 Subject: [PATCH 1/3] update current time live in date setting with a 1 second tick interval --- cosmic-settings/src/pages/time/date.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cosmic-settings/src/pages/time/date.rs b/cosmic-settings/src/pages/time/date.rs index 2daab6ea..fe858d3c 100644 --- a/cosmic-settings/src/pages/time/date.rs +++ b/cosmic-settings/src/pages/time/date.rs @@ -6,6 +6,7 @@ use cosmic::{ Apply, Element, Task, app::ContextDrawer, cosmic_config::{self, ConfigGet, ConfigSet}, + iced::{self, Subscription}, iced_core::text::Wrapping, surface, widget::{self, dropdown, settings}, @@ -194,6 +195,11 @@ impl page::Page for Page { None } + + fn subscription(&self, _: &cosmic::Core) -> Subscription { + iced::time::every(std::time::Duration::from_secs(1)) + .map(|_| crate::pages::Message::DateAndTime(Message::Tick)) + } } impl Page { @@ -300,6 +306,10 @@ impl Page { return cosmic::task::message(crate::app::Message::Surface(a)); } + Message::Tick => { + self.update_local_time(); + } + Message::None => (), } @@ -398,6 +408,7 @@ pub enum Message { FirstDayOfWeek(usize), Refresh(Info), ShowDate(bool), + Tick, Timezone(usize), TimezoneContext, TimezoneSearch(String), From d65960ff55010921a6977e56e54e9efd211b7261 Mon Sep 17 00:00:00 2001 From: Frederic Laing Date: Fri, 7 Nov 2025 20:52:13 +0100 Subject: [PATCH 2/3] move time applet settings (show seconds and show date) to desktop>panel page --- .../src/pages/desktop/panel/inner.rs | 73 ++++++++++++++++++- .../src/pages/desktop/panel/mod.rs | 7 +- cosmic-settings/src/pages/time/date.rs | 65 +---------------- i18n/en/cosmic_settings.ftl | 2 + 4 files changed, 80 insertions(+), 67 deletions(-) diff --git a/cosmic-settings/src/pages/desktop/panel/inner.rs b/cosmic-settings/src/pages/desktop/panel/inner.rs index 6e5c3463..16ddca40 100644 --- a/cosmic-settings/src/pages/desktop/panel/inner.rs +++ b/cosmic-settings/src/pages/desktop/panel/inner.rs @@ -1,7 +1,7 @@ use cosmic::{ Element, Task, cctk::sctk::reexports::client::{Proxy, backend::ObjectId, protocol::wl_output::WlOutput}, - cosmic_config::{self, CosmicConfigEntry}, + cosmic_config::{self, CosmicConfigEntry, ConfigGet, ConfigSet}, iced::{Alignment, Length}, surface, theme, widget::{ @@ -10,7 +10,6 @@ use cosmic::{ }; use cosmic::Apply; -use cosmic_config::ConfigSet; use cosmic_panel_config::{ AutoHide, CosmicPanelBackground, CosmicPanelConfig, CosmicPanelContainerConfig, CosmicPanelOuput, PanelAnchor, PanelSize, @@ -18,6 +17,7 @@ use cosmic_panel_config::{ use cosmic_settings_page::{self as page, Section}; use slab::Slab; use std::{collections::HashMap, time::Duration}; +use tracing::error; pub struct PageInner { pub(crate) config_helper: Option, @@ -32,10 +32,23 @@ pub struct PageInner { pub(crate) outputs_map: HashMap, pub(crate) system_default: Option, pub(crate) system_container: Option, + pub(crate) cosmic_applet_config: Option, + pub show_seconds: bool, + pub show_date_in_top_panel: bool, } impl Default for PageInner { fn default() -> Self { + let cosmic_applet_config = cosmic_config::Config::new("com.system76.CosmicAppletTime", 1).ok(); + + let show_seconds = cosmic_applet_config.as_ref() + .and_then(|config| config.get("show_seconds").ok()) + .unwrap_or(false); + + let show_date_in_top_panel = cosmic_applet_config.as_ref() + .and_then(|config| config.get("show_date_in_top_panel").ok()) + .unwrap_or(true); + Self { config_helper: Option::default(), panel_config: Option::default(), @@ -72,6 +85,9 @@ impl Default for PageInner { }, ) .ok(), + cosmic_applet_config, + show_seconds, + show_date_in_top_panel, } } } @@ -264,6 +280,39 @@ pub(crate) fn style< }) } +pub(crate) fn time_applet< + P: page::Page + PanelPage, + T: Fn(Message) -> crate::pages::Message + Copy + Send + Sync + 'static, +>( + msg_map: T, +) -> Section { + let mut descriptions = Slab::new(); + + let show_seconds = descriptions.insert(fl!("time-format", "show-seconds")); + let show_date = descriptions.insert(fl!("time-format", "show-date")); + + Section::default() + .title(fl!("time-applet")) + .descriptions(descriptions) + .view::

(move |_binder, page, section| { + let descriptions = §ion.descriptions; + let inner = page.inner(); + + settings::section() + .title(§ion.title) + .add(settings::item( + &descriptions[show_seconds], + toggler(inner.show_seconds).on_toggle(Message::ShowSeconds), + )) + .add(settings::item( + &descriptions[show_date], + toggler(inner.show_date_in_top_panel).on_toggle(Message::ShowDateInTopPanel), + )) + .apply(Element::from) + .map(msg_map) + }) +} + pub(crate) fn configuration + PanelPage>( p: &P, ) -> Section { @@ -434,6 +483,8 @@ pub enum Message { ResetPanel, FullReset, Surface(surface::Action), + ShowSeconds(bool), + ShowDateInTopPanel(bool), } impl PageInner { @@ -585,6 +636,24 @@ impl PageInner { Message::Surface(_) => { unimplemented!() } + Message::ShowSeconds(enable) => { + self.show_seconds = enable; + if let Some(config) = self.cosmic_applet_config.as_ref() { + if let Err(err) = config.set("show_seconds", enable) { + error!(?err, "Failed to set config 'show_seconds'"); + } + } + return Task::none(); + } + Message::ShowDateInTopPanel(enable) => { + self.show_date_in_top_panel = enable; + if let Some(config) = self.cosmic_applet_config.as_ref() { + if let Err(err) = config.set("show_date_in_top_panel", enable) { + error!(?err, "Failed to set config 'show_date_in_top_panel'"); + } + } + return Task::none(); + } } if panel_config.anchor_gap || !panel_config.expand_to_edges { diff --git a/cosmic-settings/src/pages/desktop/panel/mod.rs b/cosmic-settings/src/pages/desktop/panel/mod.rs index 63a95cf0..5a123cea 100644 --- a/cosmic-settings/src/pages/desktop/panel/mod.rs +++ b/cosmic-settings/src/pages/desktop/panel/mod.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use cosmic::{Task, cosmic_config::CosmicConfigEntry}; +use cosmic::{Task, cosmic_config::{self, CosmicConfigEntry, ConfigGet}}; use cosmic_panel_config::{CosmicPanelConfig, CosmicPanelContainerConfig}; use cosmic_settings_page::{self as page, Section, section}; use slotmap::SlotMap; use crate::pages::desktop::panel::inner::{ - add_panel, behavior_and_position, configuration, reset_button, style, + add_panel, behavior_and_position, configuration, reset_button, style, time_applet, }; use self::inner::{PageInner, PanelPage}; @@ -122,6 +122,9 @@ impl page::Page for Page { sections.insert(style::(self, |m| { crate::pages::Message::Panel(Message(m)) })), + sections.insert(time_applet::(|m| { + crate::pages::Message::Panel(Message(m)) + })), sections.insert(configuration::(self)), sections.insert(reset_button::(|m| { crate::pages::Message::Panel(Message(m)) diff --git a/cosmic-settings/src/pages/time/date.rs b/cosmic-settings/src/pages/time/date.rs index fe858d3c..a3c3d8f6 100644 --- a/cosmic-settings/src/pages/time/date.rs +++ b/cosmic-settings/src/pages/time/date.rs @@ -43,9 +43,7 @@ pub struct Page { cosmic_applet_config: cosmic_config::Config, first_day_of_week: usize, military_time: bool, - show_seconds: bool, ntp_enabled: bool, - show_date_in_top_panel: bool, timezone_context: bool, local_time: Option>, timezone: Option, @@ -71,16 +69,6 @@ impl Default for Page { default }); - let show_seconds = cosmic_applet_config - .get("show_seconds") - .unwrap_or_else(|err| { - if err.is_err() { - error!(?err, "Failed to read config 'show_seconds'"); - } - - false - }); - let first_day_of_week = cosmic_applet_config .get("first_day_of_week") .unwrap_or_else(|err| { @@ -93,16 +81,6 @@ impl Default for Page { default }); - let show_date_in_top_panel = cosmic_applet_config - .get("show_date_in_top_panel") - .unwrap_or_else(|err| { - if err.is_err() { - error!(?err, "Failed to read config 'show_date_in_top_panel'"); - } - - true - }); - Self { entity: page::Entity::null(), cosmic_applet_config, @@ -110,9 +88,7 @@ impl Default for Page { formatted_date: String::new(), local_time: None, military_time, - show_seconds, ntp_enabled: false, - show_date_in_top_panel, timezone: None, timezone_context: false, timezone_list: Vec::new(), @@ -220,15 +196,6 @@ impl Page { } } - Message::ShowSeconds(enable) => { - self.show_seconds = enable; - self.update_local_time(); - - if let Err(err) = self.cosmic_applet_config.set("show_seconds", enable) { - error!(?err, "Failed to set config 'show_seconds'"); - } - } - Message::FirstDayOfWeek(weekday) => { self.first_day_of_week = weekday; @@ -237,17 +204,6 @@ impl Page { } } - Message::ShowDate(enable) => { - self.show_date_in_top_panel = enable; - - if let Err(err) = self - .cosmic_applet_config - .set("show_date_in_top_panel", enable) - { - error!(?err, "Failed to set config 'show_date_in_top_panel'"); - } - } - Message::TimezoneSearch(text) => { self.timezone_search = text; } @@ -393,7 +349,7 @@ impl Page { self.local_time = Some(update_local_time()); self.formatted_date = match self.local_time { - Some(ref time) => format_date(time, self.military_time, self.show_seconds), + Some(ref time) => format_date(time, self.military_time), None => fl!("unknown"), } } @@ -403,11 +359,9 @@ impl Page { pub enum Message { Error(String), MilitaryTime(bool), - ShowSeconds(bool), None, FirstDayOfWeek(usize), Refresh(Info), - ShowDate(bool), Tick, Timezone(usize), TimezoneContext, @@ -443,9 +397,7 @@ fn format() -> Section { let mut descriptions = Slab::new(); let military = descriptions.insert(fl!("time-format", "twenty-four")); - let show_seconds = descriptions.insert(fl!("time-format", "show-seconds")); let first = descriptions.insert(fl!("time-format", "first")); - let show_date = descriptions.insert(fl!("time-format", "show-date")); Section::default() .title(fl!("time-format")) @@ -458,11 +410,6 @@ fn format() -> Section { settings::item::builder(§ion.descriptions[military]) .toggler(page.military_time, Message::MilitaryTime), ) - // Show seconds in time format - .add( - settings::item::builder(§ion.descriptions[show_seconds]) - .toggler(page.show_seconds, Message::ShowSeconds), - ) // First day of week .add( settings::item::builder(§ion.descriptions[first]).control( @@ -492,11 +439,6 @@ fn format() -> Section { ), ), ) - // Date on top panel toggle - .add( - settings::item::builder(§ion.descriptions[show_date]) - .toggler(page.show_date_in_top_panel, Message::ShowDate), - ) .apply(cosmic::Element::from) .map(crate::pages::Message::DateAndTime) }) @@ -552,7 +494,7 @@ fn locale() -> Result> { .map_err(|e| format!("{e:?}").into()) } -fn format_date(date: &DateTime, military: bool, show_seconds: bool) -> String { +fn format_date(date: &DateTime, military: bool) -> String { let Ok(locale) = locale() else { return String::new(); }; @@ -565,9 +507,6 @@ fn format_date(date: &DateTime, military: bool, show_seconds: bool) - }); let mut fs = fieldsets::YMDT::long(); - if !show_seconds { - fs = fs.with_time_precision(TimePrecision::Minute); - } let dtf = DateTimeFormatter::try_new(prefs, fs).unwrap(); diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index eaa153c0..adbf6e79 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -335,6 +335,8 @@ panel-applets = Configuration .dock-desc = Configure dock applets .desc = Configure panel applets +time-applet = Time Applet + panel-missing = Panel configuration is missing .desc = The panel configuration file is missing due to use of a custom configuration or it is corrupted. .fix = Reset to default From 9bbb685a6af605241ab01f14a3bcae2df5027268 Mon Sep 17 00:00:00 2001 From: Frederic Laing Date: Fri, 7 Nov 2025 23:50:16 +0100 Subject: [PATCH 3/3] re-design date & time page and language & region page to be more intuitive and use current date and time values in previews --- cosmic-settings/src/app.rs | 20 +- cosmic-settings/src/main.rs | 2 + .../pages/{time/date.rs => date_time/mod.rs} | 144 ++-------- .../src/pages/desktop/panel/mod.rs | 2 +- .../region.rs => language_region/mod.rs} | 271 ++++++++++++------ cosmic-settings/src/pages/mod.rs | 8 +- cosmic-settings/src/pages/time/mod.rs | 44 --- i18n/en/cosmic_settings.ftl | 6 +- 8 files changed, 231 insertions(+), 266 deletions(-) rename cosmic-settings/src/pages/{time/date.rs => date_time/mod.rs} (74%) rename cosmic-settings/src/pages/{time/region.rs => language_region/mod.rs} (79%) delete mode 100644 cosmic-settings/src/pages/time/mod.rs diff --git a/cosmic-settings/src/app.rs b/cosmic-settings/src/app.rs index 4e15a3b6..02b56cce 100644 --- a/cosmic-settings/src/app.rs +++ b/cosmic-settings/src/app.rs @@ -19,7 +19,7 @@ use crate::pages::networking; use crate::pages::power; #[cfg(feature = "page-sound")] use crate::pages::sound; -use crate::pages::{self, system, time}; +use crate::pages::{self, system, date_time, language_region}; use crate::subscription::desktop_files; use crate::widget::{page_title, search_header}; #[cfg(feature = "wayland")] @@ -87,7 +87,7 @@ impl SettingsApp { #[cfg(feature = "page-bluetooth")] PageCommands::Bluetooth => self.pages.page_id::(), #[cfg(feature = "page-date")] - PageCommands::DateTime => self.pages.page_id::(), + PageCommands::DateTime => self.pages.page_id::(), #[cfg(feature = "page-default-apps")] PageCommands::DefaultApps => self.pages.page_id::(), PageCommands::Desktop => self.pages.page_id::(), @@ -117,12 +117,11 @@ impl SettingsApp { #[cfg(feature = "page-power")] PageCommands::Power => self.pages.page_id::(), #[cfg(feature = "page-region")] - PageCommands::RegionLanguage => self.pages.page_id::(), + PageCommands::LanguageRegion => self.pages.page_id::(), #[cfg(feature = "page-sound")] PageCommands::Sound => self.pages.page_id::(), PageCommands::StartupApps => self.pages.page_id::(), PageCommands::System => self.pages.page_id::(), - PageCommands::Time => self.pages.page_id::(), #[cfg(feature = "page-input")] PageCommands::Touchpad => self.pages.page_id::(), #[cfg(feature = "page-users")] @@ -140,6 +139,7 @@ impl SettingsApp { PageCommands::Wireless => self.pages.page_id::(), #[cfg(feature = "page-workspaces")] PageCommands::Workspaces => self.pages.page_id::(), + &PageCommands::RegionLanguage | &PageCommands::Time => todo!(), } } @@ -223,7 +223,8 @@ impl cosmic::Application for SettingsApp { #[cfg(feature = "page-input")] app.insert_page::(); app.insert_page::(); - app.insert_page::(); + app.insert_page::(); + app.insert_page::(); app.insert_page::(); let active_id = match flags.sub_command { @@ -378,6 +379,7 @@ impl cosmic::Application for SettingsApp { Message::PageMessage(message) => match message { crate::pages::Message::CloseContextDrawer => return self.close_context_drawer(), + crate::pages::Message::None => (), #[cfg(feature = "page-accessibility")] crate::pages::Message::Accessibility(message) => { @@ -416,8 +418,8 @@ impl cosmic::Application for SettingsApp { } #[cfg(feature = "page-date")] - crate::pages::Message::DateAndTime(message) => { - if let Some(page) = self.pages.page_mut::() { + crate::pages::Message::DateTime(message) => { + if let Some(page) = self.pages.page_mut::() { return page.update(message).map(Into::into); } } @@ -537,8 +539,8 @@ impl cosmic::Application for SettingsApp { } #[cfg(feature = "page-region")] - crate::pages::Message::Region(message) => { - if let Some(page) = self.pages.page_mut::() { + crate::pages::Message::LanguageRegion(message) => { + if let Some(page) = self.pages.page_mut::() { return page.update(message).map(Into::into); } } diff --git a/cosmic-settings/src/main.rs b/cosmic-settings/src/main.rs index 05d58c89..b1afec46 100644 --- a/cosmic-settings/src/main.rs +++ b/cosmic-settings/src/main.rs @@ -57,6 +57,8 @@ pub enum PageCommands { /// Date & Time settings page #[cfg(feature = "page-date")] DateTime, + /// Language & Region settings page + LanguageRegion, /// Default application associations #[cfg(feature = "page-default-apps")] DefaultApps, diff --git a/cosmic-settings/src/pages/time/date.rs b/cosmic-settings/src/pages/date_time/mod.rs similarity index 74% rename from cosmic-settings/src/pages/time/date.rs rename to cosmic-settings/src/pages/date_time/mod.rs index a3c3d8f6..6da16e83 100644 --- a/cosmic-settings/src/pages/time/date.rs +++ b/cosmic-settings/src/pages/date_time/mod.rs @@ -6,18 +6,16 @@ use cosmic::{ Apply, Element, Task, app::ContextDrawer, cosmic_config::{self, ConfigGet, ConfigSet}, - iced::{self, Subscription}, iced_core::text::Wrapping, surface, - widget::{self, dropdown, settings}, + widget::{self, settings}, }; use cosmic_settings_page::{self as page, Section, section}; use icu::{ - calendar::{Gregorian, types::Weekday, week}, + calendar::{Gregorian}, datetime::{ DateTimeFormatter, DateTimeFormatterPreferences, fieldsets, input::{Date, DateTime, Time}, - options::TimePrecision, }, locale::{Locale, preferences::extensions::unicode::keywords::HourCycle}, }; @@ -41,7 +39,6 @@ pub struct Info { pub struct Page { entity: page::Entity, cosmic_applet_config: cosmic_config::Config, - first_day_of_week: usize, military_time: bool, ntp_enabled: bool, timezone_context: bool, @@ -69,22 +66,9 @@ impl Default for Page { default }); - let first_day_of_week = cosmic_applet_config - .get("first_day_of_week") - .unwrap_or_else(|err| { - if err.is_err() { - error!(?err, "Failed to read config 'first_day_of_week'"); - } - - let default = get_locale_default_first_day(); - let _ = cosmic_applet_config.set("first_day_of_week", default); - default - }); - Self { entity: page::Entity::null(), cosmic_applet_config, - first_day_of_week, formatted_date: String::new(), local_time: None, military_time, @@ -107,7 +91,6 @@ impl page::Page for Page { sections: &mut SlotMap>, ) -> Option { Some(vec![ - sections.insert(date()), sections.insert(timezone()), sections.insert(format()), ]) @@ -147,7 +130,7 @@ impl page::Page for Page { timezone_list, }) }) - .map(crate::pages::Message::DateAndTime) + .map(crate::pages::Message::DateTime) } fn context_drawer(&self) -> Option> { @@ -156,7 +139,7 @@ impl page::Page for Page { .on_input(Message::TimezoneSearch) .on_clear(Message::TimezoneSearch(String::new())) .apply(Element::from) - .map(crate::pages::Message::DateAndTime); + .map(crate::pages::Message::DateTime); return Some( cosmic::app::context_drawer( @@ -164,18 +147,13 @@ impl page::Page for Page { .map(crate::pages::Message::from), crate::pages::Message::CloseContextDrawer, ) - .title(fl!("time-zone")) - .header(search), + .title(fl!("time-zone")) + .header(search), ); } None } - - fn subscription(&self, _: &cosmic::Core) -> Subscription { - iced::time::every(std::time::Duration::from_secs(1)) - .map(|_| crate::pages::Message::DateAndTime(Message::Tick)) - } } impl Page { @@ -196,14 +174,6 @@ impl Page { } } - Message::FirstDayOfWeek(weekday) => { - self.first_day_of_week = weekday; - - if let Err(err) = self.cosmic_applet_config.set("first_day_of_week", weekday) { - error!(?err, "Failed to set config 'first_day_of_week'"); - } - } - Message::TimezoneSearch(text) => { self.timezone_search = text; } @@ -232,8 +202,8 @@ impl Page { Err(why) => Message::Error(why.to_string()), } }) - .map(crate::pages::Message::DateAndTime) - .map(crate::Message::PageMessage); + .map(crate::pages::Message::DateTime) + .map(crate::Message::PageMessage); } } @@ -262,10 +232,6 @@ impl Page { return cosmic::task::message(crate::app::Message::Surface(a)); } - Message::Tick => { - self.update_local_time(); - } - Message::None => (), } @@ -308,7 +274,7 @@ impl Page { } list.apply(Element::from) - .map(crate::pages::Message::DateAndTime) + .map(crate::pages::Message::DateTime) } fn timezone_context_item<'a>(&self, id: usize, timezone: &'a str) -> Element<'a, Message> { @@ -337,12 +303,12 @@ impl Page { widget::horizontal_space().width(16).into() }, ]) - .apply(widget::container) - .class(cosmic::theme::Container::List) - .apply(widget::button::custom) - .class(cosmic::theme::Button::Transparent) - .on_press(Message::Timezone(id)) - .into() + .apply(widget::container) + .class(cosmic::theme::Container::List) + .apply(widget::button::custom) + .class(cosmic::theme::Button::Transparent) + .on_press(Message::Timezone(id)) + .into() } fn update_local_time(&mut self) { @@ -360,9 +326,7 @@ pub enum Message { Error(String), MilitaryTime(bool), None, - FirstDayOfWeek(usize), Refresh(Info), - Tick, Timezone(usize), TimezoneContext, TimezoneSearch(String), @@ -372,32 +336,10 @@ pub enum Message { impl page::AutoBind for Page {} -fn date() -> Section { - let mut descriptions = Slab::new(); - - let title = descriptions.insert(fl!("time-date")); - - Section::default() - .title(fl!("time-date")) - .descriptions(descriptions) - .view::(move |_binder, page, section| { - settings::section() - .title(§ion.title) - .add( - settings::item::builder(&*section.descriptions[title]) - .description(fl!("time-date", "auto-ntp")) - .control(widget::text::body(&page.formatted_date)), - ) - .apply(cosmic::Element::from) - .map(crate::pages::Message::DateAndTime) - }) -} - fn format() -> Section { let mut descriptions = Slab::new(); let military = descriptions.insert(fl!("time-format", "twenty-four")); - let first = descriptions.insert(fl!("time-format", "first")); Section::default() .title(fl!("time-format")) @@ -410,37 +352,8 @@ fn format() -> Section { settings::item::builder(§ion.descriptions[military]) .toggler(page.military_time, Message::MilitaryTime), ) - // First day of week - .add( - settings::item::builder(§ion.descriptions[first]).control( - dropdown::popup_dropdown( - &*WEEKDAYS, - match page.first_day_of_week { - 4 => Some(0), // friday - 5 => Some(1), // saturday - 0 => Some(3), // monday - _ => Some(2), // sunday - }, - |v| { - match v { - 0 => Message::FirstDayOfWeek(4), // friday - 1 => Message::FirstDayOfWeek(5), // saturday - 3 => Message::FirstDayOfWeek(0), // monday - _ => Message::FirstDayOfWeek(6), // sunday - } - }, - cosmic::iced::window::Id::RESERVED, - Message::Surface, - |a| { - crate::app::Message::PageMessage( - crate::pages::Message::DateAndTime(a), - ) - }, - ), - ), - ) .apply(cosmic::Element::from) - .map(crate::pages::Message::DateAndTime) + .map(crate::pages::Message::DateTime) }) } @@ -461,7 +374,7 @@ fn timezone() -> Section { .map(|id| &*page.timezone_list[id]) .unwrap_or_default(), ) - .wrapping(Wrapping::Word), + .wrapping(Wrapping::Word), ) .push(widget::icon::from_name("go-next-symbolic").size(16).icon()) .apply(widget::container) @@ -478,7 +391,7 @@ fn timezone() -> Section { .control(timezone_context_button), ) .apply(cosmic::Element::from) - .map(crate::pages::Message::DateAndTime) + .map(crate::pages::Message::DateTime) }) } @@ -506,7 +419,7 @@ fn format_date(date: &DateTime, military: bool) -> String { HourCycle::H12 }); - let mut fs = fieldsets::YMDT::long(); + let fs = fieldsets::YMDT::long(); let dtf = DateTimeFormatter::try_new(prefs, fs).unwrap(); @@ -540,21 +453,4 @@ fn get_locale_default_24h() -> bool { // If we see "13" in the output, it's 24-hour format // If we see "1" (but not "13"), it's 12-hour format formatted.contains("13") -} - -fn get_locale_default_first_day() -> usize { - let Ok(locale) = locale() else { return 6 }; - let Ok(week_info) = week::WeekInformation::try_new(week::WeekPreferences::from(&locale)) else { - return 6; - }; - - match week_info.first_weekday { - Weekday::Monday => 0, - Weekday::Tuesday => 1, - Weekday::Wednesday => 2, - Weekday::Thursday => 3, - Weekday::Friday => 4, - Weekday::Saturday => 5, - Weekday::Sunday => 6, - } -} +} \ No newline at end of file diff --git a/cosmic-settings/src/pages/desktop/panel/mod.rs b/cosmic-settings/src/pages/desktop/panel/mod.rs index 5a123cea..60a72db5 100644 --- a/cosmic-settings/src/pages/desktop/panel/mod.rs +++ b/cosmic-settings/src/pages/desktop/panel/mod.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use cosmic::{Task, cosmic_config::{self, CosmicConfigEntry, ConfigGet}}; +use cosmic::{Task, cosmic_config::{self, CosmicConfigEntry}}; use cosmic_panel_config::{CosmicPanelConfig, CosmicPanelContainerConfig}; use cosmic_settings_page::{self as page, Section, section}; use slotmap::SlotMap; diff --git a/cosmic-settings/src/pages/time/region.rs b/cosmic-settings/src/pages/language_region/mod.rs similarity index 79% rename from cosmic-settings/src/pages/time/region.rs rename to cosmic-settings/src/pages/language_region/mod.rs index 4163265e..bb44c3cb 100644 --- a/cosmic-settings/src/pages/time/region.rs +++ b/cosmic-settings/src/pages/language_region/mod.rs @@ -5,11 +5,12 @@ use std::collections::{BTreeMap, BTreeSet}; use std::rc::Rc; use std::sync::Arc; +use chrono::{Datelike, Timelike}; use cosmic::app::{ContextDrawer, context_drawer}; use cosmic::iced::{Alignment, Border, Color, Length}; use cosmic::iced_core::text::Wrapping; use cosmic::widget::{self, button, container}; -use cosmic::{Apply, Element, theme}; +use cosmic::{Apply, Element, theme, surface}; use cosmic_config::{ConfigGet, ConfigSet}; use cosmic_settings_page::Section; use cosmic_settings_page::{self as page, section}; @@ -26,29 +27,36 @@ use icu::{ use locales_rs as locale; use slotmap::{DefaultKey, SlotMap}; +crate::cache_dynamic_lazy! { + static WEEKDAYS: [String; 4] = [fl!("time-format", "friday"), fl!("time-format", "saturday"), fl!("time-format", "sunday"), fl!("time-format", "monday")]; +} + #[derive(Clone, Debug)] pub enum Message { AddLanguage(DefaultKey), AddLanguageContext, AddLanguageSearch(String), ExpandLanguagePopover(Option), + FirstDayOfWeek(usize), InstallAdditionalLanguages, SelectRegion(DefaultKey), SourceContext(SourceContext), Refresh(Arc>), RegionContext, RemoveLanguage(DefaultKey), + Surface(surface::Action), + Tick(cosmic::iced::time::Instant), } impl From for crate::app::Message { fn from(message: Message) -> Self { - crate::pages::Message::Region(message).into() + crate::pages::Message::LanguageRegion(message).into() } } impl From for crate::pages::Message { fn from(message: Message) -> Self { - crate::pages::Message::Region(message) + crate::pages::Message::LanguageRegion(message) } } @@ -117,6 +125,8 @@ pub struct Page { numeric_locale: Option, /// Cached LC_TIME locale in icu locale format. time_locale: Option, + first_day_of_week: usize, + cosmic_applet_config: Option, } impl page::Page for Page { @@ -131,6 +141,7 @@ impl page::Page for Page { Some(vec![ sections.insert(preferred_languages::section()), sections.insert(formatting::section()), + sections.insert(formatting_preview::section()), ]) } @@ -157,6 +168,12 @@ impl page::Page for Page { cosmic::Task::none() } + fn subscription(&self, _core: &cosmic::Core) -> cosmic::iced::Subscription { + cosmic::iced::time::every(std::time::Duration::from_secs(1)) + .map(Message::Tick) + .map(crate::pages::Message::LanguageRegion) + } + fn context_drawer(&self) -> Option> { Some(match self.context.as_ref()? { ContextView::AddLanguage => { @@ -178,9 +195,9 @@ impl page::Page for Page { self.add_language_view().map(crate::pages::Message::from), crate::pages::Message::CloseContextDrawer, ) - .title(fl!("add-language", "context")) - .header(search) - .footer(install_additional_button) + .title(fl!("add-language", "context")) + .header(search) + .footer(install_additional_button) } ContextView::Region => { let search = widget::search_input(fl!("type-to-search"), &self.add_language_search) @@ -193,8 +210,8 @@ impl page::Page for Page { self.region_view().map(crate::pages::Message::from), crate::pages::Message::CloseContextDrawer, ) - .title(fl!("region")) - .header(search) + .title(fl!("region")) + .header(search) } }) } @@ -257,6 +274,16 @@ impl Page { self.expanded_source_popover = id; } + Message::FirstDayOfWeek(weekday) => { + self.first_day_of_week = weekday; + + if let Some(ref cosmic_applet_config) = self.cosmic_applet_config { + if let Err(err) = cosmic_applet_config.set("first_day_of_week", weekday) { + tracing::error!(?err, "Failed to set config 'first_day_of_week'"); + } + } + } + Message::InstallAdditionalLanguages => { return cosmic::task::future(async move { _ = tokio::process::Command::new("gnome-language-selector") @@ -277,6 +304,26 @@ impl Page { self.registry = Some(page_refresh.registry.0); self.numeric_locale = self.icu_locale_from_env("LC_NUMERIC"); self.time_locale = self.icu_locale_from_env("LC_TIME"); + + // Load first_day_of_week from cosmic applet config + if let Ok(cosmic_applet_config) = cosmic_config::Config::new("com.system76.CosmicAppletTime", 1) { + self.cosmic_applet_config = Some(cosmic_applet_config.clone()); + self.first_day_of_week = cosmic_applet_config + .get("first_day_of_week") + .unwrap_or_else(|err| { + if err.is_err() { + tracing::error!(?err, "Failed to read config 'first_day_of_week'"); + } + + // Get default from current locale + let default = self.region + .as_ref() + .map(|r| get_default_first_day(&r.lang_code)) + .unwrap_or(6); // Default to Sunday + let _ = cosmic_applet_config.set("first_day_of_week", default); + default + }); + } } Err(why) => { @@ -315,9 +362,9 @@ impl Page { if let Some(language_code) = locales.first() && let Some(language) = self - .available_languages - .values() - .find(|lang| &lang.lang_code == language_code) + .available_languages + .values() + .find(|lang| &lang.lang_code == language_code) { let language = language.clone(); self.language = Some(language.clone()); @@ -328,11 +375,17 @@ impl Page { language.lang_code.clone(), region.unwrap_or(language).lang_code.clone(), ) - .await; + .await; }); } } } + + Message::Surface(action) => { + return cosmic::task::message(crate::app::Message::Surface(action)); + } + + Message::Tick(_) => {} } cosmic::Task::none() @@ -350,9 +403,9 @@ impl Page { for (id, available_language) in &self.available_languages { if search_input.is_empty() || available_language - .display_name - .to_lowercase() - .contains(search_input) + .display_name + .to_lowercase() + .contains(search_input) { let is_installed = self .config @@ -379,21 +432,21 @@ impl Page { widget::horizontal_space().width(16).into() }, ]) - .apply(widget::container) - .class(cosmic::theme::Container::List) - .apply(widget::button::custom) - .class(cosmic::theme::Button::Transparent) - .on_press(if is_installed { - Message::RemoveLanguage(id) - } else { - Message::AddLanguage(id) - }); + .apply(widget::container) + .class(cosmic::theme::Container::List) + .apply(widget::button::custom) + .class(cosmic::theme::Button::Transparent) + .on_press(if is_installed { + Message::RemoveLanguage(id) + } else { + Message::AddLanguage(id) + }); list = list.add(button) } } - list.apply(Element::from).map(crate::pages::Message::Region) + list.apply(Element::from).map(crate::pages::Message::LanguageRegion) } fn icu_locale_from_env(&self, key: &'static str) -> Option { @@ -417,9 +470,10 @@ impl Page { let prefs = DateTimeFormatterPreferences::from(locale); let dtf = DateTimeFormatter::try_new(prefs, fieldsets::YMD::medium()).unwrap(); + let now = chrono::Local::now(); let datetime = DateTime { - date: Date::try_new_gregorian(1776, 7, 4).unwrap(), - time: Time::try_new(12, 0, 0, 0).unwrap(), + date: Date::try_new_gregorian(now.year(), now.month() as u8, now.day() as u8).unwrap(), + time: Time::try_new(now.hour() as u8, now.minute() as u8, now.second() as u8, 0).unwrap(), }; dtf.format(&datetime).to_string() @@ -433,9 +487,10 @@ impl Page { let prefs = DateTimeFormatterPreferences::from(locale); let dtf = DateTimeFormatter::try_new(prefs, fieldsets::YMDT::long()).unwrap(); + let now = chrono::Local::now(); let datetime = DateTime { - date: Date::try_new_gregorian(1776, 7, 4).unwrap(), - time: Time::try_new(13, 0, 0, 0).unwrap(), + date: Date::try_new_gregorian(now.year(), now.month() as u8, now.day() as u8).unwrap(), + time: Time::try_new(now.hour() as u8, now.minute() as u8, now.second() as u8, 0).unwrap(), }; dtf.format(&datetime).to_string() @@ -449,9 +504,10 @@ impl Page { let prefs = DateTimeFormatterPreferences::from(locale); let dtf = DateTimeFormatter::try_new(prefs, fieldsets::T::medium()).unwrap(); + let now = chrono::Local::now(); let datetime = DateTime { - date: Date::try_new_gregorian(1776, 7, 4).unwrap(), - time: Time::try_new(13, 0, 0, 0).unwrap(), + date: Date::try_new_gregorian(now.year(), now.month() as u8, now.day() as u8).unwrap(), + time: Time::try_new(now.hour() as u8, now.minute() as u8, now.second() as u8, 0).unwrap(), }; dtf.format(&datetime).to_string() @@ -507,28 +563,28 @@ impl Page { widget::horizontal_space().width(16).into() }, ]) - .apply(widget::container) - .class(cosmic::theme::Container::List) - .apply(widget::button::custom) - .class(cosmic::theme::Button::Transparent) - .on_press_maybe(if is_selected { - None - } else { - Some(Message::SelectRegion(id)) - }); + .apply(widget::container) + .class(cosmic::theme::Container::List) + .apply(widget::button::custom) + .class(cosmic::theme::Button::Transparent) + .on_press_maybe(if is_selected { + None + } else { + Some(Message::SelectRegion(id)) + }); list = list.add(button) } } - list.apply(Element::from).map(crate::pages::Message::Region) + list.apply(Element::from).map(crate::pages::Message::LanguageRegion) } } impl page::AutoBind for Page {} mod preferred_languages { - use crate::pages::time::region::localized_iso_codes; + use crate::pages::language_region::localized_iso_codes; use super::Message; use cosmic::{ @@ -590,37 +646,99 @@ mod preferred_languages { } mod formatting { - use super::Message; + use super::{Message, WEEKDAYS}; use cosmic::{Apply, widget}; use cosmic_settings_page::Section; pub fn section() -> Section { crate::slab!(descriptions { formatting_txt = fl!("formatting"); + region_txt = fl!("region"); + first_txt = fl!("time-format", "first"); + }); + + Section::default() + .title(fl!("formatting")) + .descriptions(descriptions) + .view::(move |_binder, page, section| { + let desc = §ion.descriptions; + + let region = page + .region + .as_ref() + .map(|locale| locale.region_name.as_str()) + .unwrap_or(""); + + let select_region = crate::widget::go_next_with_item( + &desc[region_txt], + widget::text::body(region), + Message::RegionContext, + ); + + // First day of week dropdown + let first_day = widget::settings::item::builder(&desc[first_txt]).control( + widget::dropdown::popup_dropdown( + &*WEEKDAYS, + match page.first_day_of_week { + 4 => Some(0), // friday + 5 => Some(1), // saturday + 0 => Some(3), // monday + _ => Some(2), // sunday + }, + |v| { + match v { + 0 => Message::FirstDayOfWeek(4), // friday + 1 => Message::FirstDayOfWeek(5), // saturday + 3 => Message::FirstDayOfWeek(0), // monday + _ => Message::FirstDayOfWeek(6), // sunday + } + }, + cosmic::iced::window::Id::RESERVED, + Message::Surface, + |a| -> crate::app::Message { crate::pages::Message::LanguageRegion(a).into() }, + ), + ); + + widget::settings::section() + .title(&desc[formatting_txt]) + .add(select_region) + .add(first_day) + .apply(cosmic::Element::from) + .map(crate::pages::Message::from) + }) + } +} + +mod formatting_preview { + use cosmic::{Apply, widget}; + use cosmic_settings_page::Section; + + pub fn section() -> Section { + crate::slab!(descriptions { + formatting_preview_txt = fl!("formatting-preview"); dates_txt = [&fl!("formatting", "dates"), ":"].concat(); time_txt = [&fl!("formatting", "time"), ":"].concat(); date_and_time_txt = [&fl!("formatting", "date-and-time"), ":"].concat(); numbers_txt = [&fl!("formatting", "numbers"), ":"].concat(); - region_txt = fl!("region"); }); Section::default() - .title(fl!("formatting")) + .title(fl!("formatting-preview")) .descriptions(descriptions) .view::(move |_binder, page, section| { let desc = §ion.descriptions; - let dates = widget::row::with_capacity(2) + let dates = widget::row::with_capacity::(2) .push(widget::text::body(&desc[dates_txt])) .push(widget::text::body(page.formatted_date()).font(cosmic::font::bold())) .spacing(4); - let time = widget::row::with_capacity(2) + let time = widget::row::with_capacity::(2) .push(widget::text::body(&desc[time_txt])) .push(widget::text::body(page.formatted_time()).font(cosmic::font::bold())) .spacing(4); - let dates_and_times = widget::row::with_capacity(2) + let dates_and_times = widget::row::with_capacity::(2) .push(widget::text::body(&desc[date_and_time_txt])) .push( widget::text::body(page.formatted_dates_and_times()) @@ -628,7 +746,7 @@ mod formatting { ) .spacing(4); - let numbers = widget::row::with_capacity(2) + let numbers = widget::row::with_capacity::(2) .push(widget::text::body(&desc[numbers_txt])) .push(widget::text::body(page.formatted_numbers()).font(cosmic::font::bold())) .spacing(4); @@ -645,7 +763,7 @@ mod formatting { // .push(widget::text::body("").font(cosmic::font::bold())) // .spacing(4); - let formatted_demo = widget::column::with_capacity(6) + let formatted_demo = widget::column::with_capacity::(6) .push(dates) .push(time) .push(dates_and_times) @@ -656,24 +774,11 @@ mod formatting { .padding(5.0) .apply(|column| widget::settings::item_row(vec![column.into()])); - let region = page - .region - .as_ref() - .map(|locale| locale.region_name.as_str()) - .unwrap_or(""); - - let select_region = crate::widget::go_next_with_item( - &desc[region_txt], - widget::text::body(region), - Message::RegionContext, - ); - widget::settings::section() - .title(&desc[formatting_txt]) + .title(&desc[formatting_preview_txt]) .add(formatted_demo) - .add(select_region) .apply(cosmic::Element::from) - .map(Into::into) + .map(crate::pages::Message::from) }) } } @@ -841,24 +946,24 @@ fn popover_menu(id: usize) -> Element<'static, Message> { cosmic::widget::divider::horizontal::default().into(), popover_menu_row(id, fl!("keyboard-sources", "remove"), SourceContext::Remove), ]) - .padding(8) - .width(Length::Shrink) - .height(Length::Shrink) - .apply(cosmic::widget::container) - .class(cosmic::theme::Container::custom(|theme| { - let cosmic = theme.cosmic(); - container::Style { - icon_color: Some(theme.cosmic().background.on.into()), - text_color: Some(theme.cosmic().background.on.into()), - background: Some(Color::from(theme.cosmic().background.base).into()), - border: Border { - radius: cosmic.corner_radii.radius_m.into(), - ..Default::default() - }, - shadow: Default::default(), - } - })) - .into() + .padding(8) + .width(Length::Shrink) + .height(Length::Shrink) + .apply(cosmic::widget::container) + .class(cosmic::theme::Container::custom(|theme| { + let cosmic = theme.cosmic(); + container::Style { + icon_color: Some(theme.cosmic().background.on.into()), + text_color: Some(theme.cosmic().background.on.into()), + background: Some(Color::from(theme.cosmic().background.base).into()), + border: Border { + radius: cosmic.corner_radii.radius_m.into(), + ..Default::default() + }, + shadow: Default::default(), + } + })) + .into() } fn popover_menu_row( diff --git a/cosmic-settings/src/pages/mod.rs b/cosmic-settings/src/pages/mod.rs index a2eab2ed..fe30a804 100644 --- a/cosmic-settings/src/pages/mod.rs +++ b/cosmic-settings/src/pages/mod.rs @@ -20,7 +20,8 @@ pub mod power; #[cfg(feature = "page-sound")] pub mod sound; pub mod system; -pub mod time; +pub mod date_time; +pub mod language_region; pub type Element<'a> = cosmic::Element<'a, Message>; @@ -40,7 +41,7 @@ pub enum Message { #[cfg(feature = "page-input")] CustomShortcuts(input::keyboard::shortcuts::custom::Message), #[cfg(feature = "page-date")] - DateAndTime(time::date::Message), + DateTime(date_time::Message), #[cfg(feature = "page-default-apps")] DefaultApps(applications::default_apps::Message), Desktop(desktop::Message), @@ -81,7 +82,7 @@ pub enum Message { #[cfg(feature = "page-power")] Power(power::Message), #[cfg(feature = "page-region")] - Region(time::region::Message), + LanguageRegion(language_region::Message), #[cfg(feature = "page-sound")] Sound(sound::Message), StartupApps(applications::startup_apps::Message), @@ -102,6 +103,7 @@ pub enum Message { // Common page functionality CloseContextDrawer, + None, } impl From for crate::Message { diff --git a/cosmic-settings/src/pages/time/mod.rs b/cosmic-settings/src/pages/time/mod.rs deleted file mode 100644 index 76a3aee4..00000000 --- a/cosmic-settings/src/pages/time/mod.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use cosmic_settings_page as page; - -#[cfg(feature = "page-date")] -pub mod date; -#[cfg(feature = "page-region")] -pub mod region; - -#[derive(Default)] -pub struct Page { - entity: page::Entity, -} - -impl page::Page for Page { - fn set_id(&mut self, entity: page::Entity) { - self.entity = entity; - } - - fn info(&self) -> page::Info { - page::Info::new("time", "preferences-time-and-language-symbolic") - .title(fl!("time")) - .description(fl!("time", "desc")) - } -} - -impl page::AutoBind for Page { - fn sub_pages( - mut page: page::Insert, - ) -> page::Insert { - #[cfg(feature = "page-date")] - { - page = page.sub_page::(); - } - - #[cfg(feature = "page-region")] - { - page = page.sub_page::(); - } - - page - } -} diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index adbf6e79..f9b26334 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -780,7 +780,7 @@ time-zone = Time zone .auto = Automatic time zone .auto-info = Requires location services and internet access -time-format = Date & time format +time-format = Time format .twenty-four = 24-hour time .show-seconds = Show seconds .first = First day of week @@ -790,7 +790,7 @@ time-format = Date & time format .sunday = Sunday .monday = Monday -time-region = Region & language +time-region = Language & region .desc = Format dates, times, and numbers based on your region formatting = Formatting @@ -801,6 +801,8 @@ formatting = Formatting .measurement = Measurement .paper = Paper +formatting-preview = Formatting preview + preferred-languages = Preferred languages .desc = The order of languages determines which language is used for the user interface. Changes take effect on next login.