fjordgard

A desktop clock application
Log | Files | Refs | README | LICENSE

commit b4acad021c0651512479ca419719e80a1b863a7c
parent 8b2f9707c57443169146c1b5a8a4ede10a812f7b
Author: Sylvia Ivory <git@sivory.net>
Date:   Wed, 18 Jun 2025 16:09:19 -0700

Add setting time format

Diffstat:
MCargo.lock | 1+
MCargo.toml | 1+
Msrc/config.rs | 35++++++++++++++++++++++++++++++++---
Msrc/main.rs | 185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
4 files changed, 191 insertions(+), 31 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -1137,6 +1137,7 @@ dependencies = [ "chrono", "fjordgard-weather", "iced", + "strum", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml @@ -13,4 +13,5 @@ edition = "2024" chrono = "0.4.41" fjordgard-weather = { version = "0.1.0", path = "crates/weather" } iced = { version = "0.13.1", features = ["tokio", "canvas", "image", "svg"] } +strum = { version = "0.27.1", features = ["derive"] } tokio = { version = "1.45.1", features = ["full"] } diff --git a/src/config.rs b/src/config.rs @@ -1,3 +1,31 @@ +use std::fmt; + +#[derive(Debug, Clone, PartialEq, strum::Display, strum::VariantArray)] +pub enum BackgroundMode { + Unsplash, + Solid, + Local, +} + +impl BackgroundMode { + pub fn default_background(&self) -> &'static str { + match self { + // https://unsplash.com/collections/1053828/tabliss-official + Self::Unsplash => "1053828", + Self::Solid => "#ffffff", + Self::Local => "", + } + } + + pub fn edit_text(&self) -> &'static str { + match self { + Self::Unsplash => "Unsplash collection", + Self::Solid => "Color (#rrggbb)", + Self::Local => "File path", + } + } +} + pub struct Location { pub longitude: f64, pub latitude: f64, @@ -7,7 +35,8 @@ pub struct Location { pub struct Config { pub timezone: String, pub time_format: String, - pub collection: String, + pub background_mode: BackgroundMode, + pub background: String, pub location: Option<Location>, } @@ -16,8 +45,8 @@ impl Default for Config { Self { timezone: String::from("Etc/UTC"), time_format: String::from("%-I:%M:%S"), - // https://unsplash.com/collections/1053828/tabliss-official - collection: String::from("1053828"), + background_mode: BackgroundMode::Unsplash, + background: BackgroundMode::Unsplash.default_background().to_string(), location: None, } } diff --git a/src/main.rs b/src/main.rs @@ -1,16 +1,21 @@ use chrono::{DateTime, Local}; use iced::{ - Color, Element, Font, Length, Subscription, + Color, Element, Font, Length, Size, Subscription, Task, font::Weight, time, - widget::{center, column, container, horizontal_space, row, stack, text}, + widget::{ + button, center, column, combo_box, container, horizontal_space, row, stack, text, + text_input, + }, + window, }; -use config::Config; - use background::{BackgroundKind, background}; +use config::Config; +use icon::{icon, icon_button}; +use strum::VariantArray; -use crate::icon::{icon, icon_button}; +use crate::config::BackgroundMode; mod background; mod config; @@ -20,6 +25,10 @@ struct Fjordgard { config: Config, time: DateTime<Local>, background: BackgroundKind, + backgrounds: combo_box::State<BackgroundMode>, + + settings_window: Option<window::Id>, + main_window: window::Id, } #[derive(Debug, Clone, Copy)] @@ -29,27 +38,102 @@ enum MediaControl { Next, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] enum Message { Tick(DateTime<Local>), Media(MediaControl), - Settings, + OpenSettings, + + SettingsOpened(window::Id), + MainWindowOpened, + WindowClosed(window::Id), + + ConfigBackgroundMode(BackgroundMode), + ConfigTimeFormat(String), } impl Fjordgard { - fn title(&self) -> String { - String::from("Fjordgard") + fn new() -> (Self, Task<Message>) { + let (id, open) = window::open(window::Settings::default()); + + ( + Self { + config: Config::default(), + time: Local::now(), + background: BackgroundKind::Color(Color::from_rgb8(255, 255, 255)), + backgrounds: combo_box::State::new(BackgroundMode::VARIANTS.to_vec()), + + settings_window: None, + main_window: id, + }, + open.map(|_| Message::MainWindowOpened), + ) + } + fn title(&self, window_id: window::Id) -> String { + if window_id == self.main_window { + String::from("Fjordgard") + } else { + String::from("Settings - Fjordgard") + } } - fn update(&mut self, msg: Message) { + fn update(&mut self, msg: Message) -> Task<Message> { match msg { - Message::Tick(time) => self.time = time, - _ => {} + Message::Tick(time) => { + self.time = time; + Task::none() + } + Message::OpenSettings => { + if self.settings_window.is_none() { + let (id, open) = window::open(window::Settings { + level: window::Level::AlwaysOnTop, + size: Size::new(350.0, 450.0), + ..Default::default() + }); + + self.settings_window = Some(id); + + open.map(Message::SettingsOpened) + } else { + Task::none() + } + } + Message::WindowClosed(id) => { + if self.main_window == id { + iced::exit() + } else { + self.settings_window = None; + Task::none() + } + } + Message::ConfigBackgroundMode(mode) => { + self.config.background = mode.default_background().to_string(); + self.config.background_mode = mode; + Task::none() + } + Message::ConfigTimeFormat(format) => { + self.config.time_format = format; + Task::none() + } + _ => Task::none(), } } - fn view(&self) -> Element<Message> { - let time_text = self.time.format(&self.config.time_format).to_string(); + fn view(&self, window_id: window::Id) -> Element<Message> { + if self.main_window == window_id { + self.view_main() + } else { + self.view_settings() + } + } + + fn view_main(&self) -> Element<Message> { + let dt = self.time.format(&self.config.time_format); + let mut time_text = String::new(); + + if let Err(_) = dt.write_to(&mut time_text) { + time_text = String::from("Invalid time format") + } let mut bold = Font::DEFAULT; bold.weight = Weight::Bold; @@ -77,7 +161,7 @@ impl Fjordgard { ) .center_x(Length::Fill); - let settings = icon_button("icons/settings.svg", Message::Settings); + let settings = icon_button("icons/settings.svg", Message::OpenSettings); stack![ background(&self.background), @@ -93,23 +177,68 @@ impl Fjordgard { .into() } - fn tick(&self) -> Subscription<Message> { - time::every(time::Duration::from_secs(1)).map(|_| Message::Tick(Local::now())) + fn view_settings(&self) -> Element<Message> { + let placeholder = Config::default(); + let config = &self.config; + + let mut background_mode_row = + row![text(config.background_mode.edit_text()).width(Length::FillPortion(1)),]; + + if config.background_mode == BackgroundMode::Local { + let text = if config.background == "" { + "Select file..." + } else { + &config.background + }; + + background_mode_row = + background_mode_row.push(button(text).width(Length::FillPortion(2))); + } else { + background_mode_row = background_mode_row.push( + text_input( + config.background_mode.default_background(), + &config.background, + ) + .width(Length::FillPortion(2)), + ); + } + + container( + column![ + row![ + text("Time format").width(Length::FillPortion(1)), + text_input(&placeholder.time_format, &config.time_format) + .width(Length::FillPortion(2)) + .on_input(Message::ConfigTimeFormat) + ], + row![ + text("Background mode").width(Length::FillPortion(1)), + combo_box( + &self.backgrounds, + "", + Some(&placeholder.background_mode), + Message::ConfigBackgroundMode + ) + .width(Length::FillPortion(2)) + ], + background_mode_row + ] + .spacing(10), + ) + .padding(15) + .into() } -} -impl Default for Fjordgard { - fn default() -> Self { - Self { - config: Config::default(), - time: Local::now(), - background: BackgroundKind::Color(Color::from_rgb8(255, 255, 255)), - } + fn subscription(&self) -> Subscription<Message> { + Subscription::batch(vec![ + time::every(time::Duration::from_secs(1)).map(|_| Message::Tick(Local::now())), + window::close_events().map(Message::WindowClosed), + ]) } } fn main() -> iced::Result { - iced::application(Fjordgard::title, Fjordgard::update, Fjordgard::view) - .subscription(Fjordgard::tick) - .run() + iced::daemon(Fjordgard::title, Fjordgard::update, Fjordgard::view) + .subscription(Fjordgard::subscription) + .run_with(Fjordgard::new) }