commit a151e8921f824d5e4990af558692bab675656295
parent b4acad021c0651512479ca419719e80a1b863a7c
Author: Sylvia Ivory <git@sivory.net>
Date: Wed, 18 Jun 2025 18:59:14 -0700
Finish settings layout
Diffstat:
| M | src/config.rs | | | 4 | ++-- |
| M | src/main.rs | | | 95 | +++++++++++++++++++------------------------------------------------------------ |
| A | src/settings.rs | | | 222 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3 files changed, 246 insertions(+), 75 deletions(-)
diff --git a/src/config.rs b/src/config.rs
@@ -26,14 +26,15 @@ impl BackgroundMode {
}
}
+#[derive(Clone)]
pub struct Location {
pub longitude: f64,
pub latitude: f64,
pub name: Option<String>,
}
+#[derive(Clone)]
pub struct Config {
- pub timezone: String,
pub time_format: String,
pub background_mode: BackgroundMode,
pub background: String,
@@ -43,7 +44,6 @@ pub struct Config {
impl Default for Config {
fn default() -> Self {
Self {
- timezone: String::from("Etc/UTC"),
time_format: String::from("%-I:%M:%S"),
background_mode: BackgroundMode::Unsplash,
background: BackgroundMode::Unsplash.default_background().to_string(),
diff --git a/src/main.rs b/src/main.rs
@@ -1,3 +1,5 @@
+use std::{cell::RefCell, rc::Rc};
+
use chrono::{DateTime, Local};
use iced::{
Color, Element, Font, Length, Size, Subscription, Task,
@@ -11,23 +13,21 @@ use iced::{
};
use background::{BackgroundKind, background};
-use config::Config;
+use config::{BackgroundMode, Config};
use icon::{icon, icon_button};
use strum::VariantArray;
-use crate::config::BackgroundMode;
-
mod background;
mod config;
mod icon;
+mod settings;
struct Fjordgard {
- config: Config,
+ config: Rc<RefCell<Config>>,
time: DateTime<Local>,
background: BackgroundKind,
- backgrounds: combo_box::State<BackgroundMode>,
- settings_window: Option<window::Id>,
+ settings_window: Option<settings::Settings>,
main_window: window::Id,
}
@@ -48,8 +48,7 @@ enum Message {
MainWindowOpened,
WindowClosed(window::Id),
- ConfigBackgroundMode(BackgroundMode),
- ConfigTimeFormat(String),
+ Settings(settings::Message),
}
impl Fjordgard {
@@ -58,10 +57,9 @@ impl Fjordgard {
(
Self {
- config: Config::default(),
+ config: Rc::new(RefCell::new(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,
@@ -91,7 +89,7 @@ impl Fjordgard {
..Default::default()
});
- self.settings_window = Some(id);
+ self.settings_window = Some(settings::Settings::new(id, self.config.clone()));
open.map(Message::SettingsOpened)
} else {
@@ -106,14 +104,12 @@ impl Fjordgard {
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()
+ Message::Settings(msg) => {
+ if let Some(settings) = &mut self.settings_window {
+ settings.update(msg).map(Message::Settings)
+ } else {
+ Task::none()
+ }
}
_ => Task::none(),
}
@@ -123,12 +119,17 @@ impl Fjordgard {
if self.main_window == window_id {
self.view_main()
} else {
- self.view_settings()
+ self.settings_window
+ .as_ref()
+ .expect("settings window")
+ .view()
+ .map(Message::Settings)
}
}
fn view_main(&self) -> Element<Message> {
- let dt = self.time.format(&self.config.time_format);
+ let config = self.config.borrow();
+ let dt = self.time.format(&config.time_format);
let mut time_text = String::new();
if let Err(_) = dt.write_to(&mut time_text) {
@@ -177,58 +178,6 @@ impl Fjordgard {
.into()
}
- 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()
- }
-
fn subscription(&self) -> Subscription<Message> {
Subscription::batch(vec![
time::every(time::Duration::from_secs(1)).map(|_| Message::Tick(Local::now())),
diff --git a/src/settings.rs b/src/settings.rs
@@ -0,0 +1,222 @@
+use std::{cell::RefCell, rc::Rc};
+
+use iced::{
+ Element, Length, Task,
+ widget::{
+ button, column, combo_box, container, row, scrollable, text, text_input, vertical_space,
+ },
+ window,
+};
+use strum::VariantArray;
+
+use crate::config::{BackgroundMode, Config};
+
+#[derive(Debug, Clone, PartialEq, strum::Display, strum::VariantArray)]
+enum WeatherLocation {
+ Disabled,
+ #[strum(to_string = "Location name")]
+ LocationName,
+ Coordinates,
+}
+
+pub struct Location {
+ name: String,
+ latitude: f64,
+ longitude: f64,
+}
+
+pub struct Settings {
+ pub id: window::Id,
+ config: Rc<RefCell<Config>>,
+ backgrounds: combo_box::State<BackgroundMode>,
+ locations: combo_box::State<WeatherLocation>,
+
+ time_format: String,
+ background_mode: BackgroundMode,
+ background: String,
+
+ location: WeatherLocation,
+ name: String,
+ latitude: String,
+ longitude: String,
+
+ location_results: Vec<Location>,
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ TimeFormat(String),
+ BackgroundMode(BackgroundMode),
+ Background(String),
+ Location(WeatherLocation),
+ Name(String),
+ NameSubmitted,
+ Latitude(String),
+ Longitude(String),
+ Save,
+ FileSelector,
+}
+
+impl Settings {
+ pub fn new(id: window::Id, config: Rc<RefCell<Config>>) -> Self {
+ let original_config = config.borrow().clone();
+ let location = original_config.location;
+
+ let latitude = location
+ .as_ref()
+ .map(|l| l.latitude.to_string())
+ .unwrap_or_default();
+ let longitude = location
+ .as_ref()
+ .map(|l| l.longitude.to_string())
+ .unwrap_or_default();
+ let name = location
+ .as_ref()
+ .map(|l| l.name.clone())
+ .flatten()
+ .unwrap_or_default();
+ let location = location
+ .as_ref()
+ .map(|l| {
+ l.name
+ .as_ref()
+ .map(|_| WeatherLocation::LocationName)
+ .unwrap_or(WeatherLocation::Coordinates)
+ })
+ .unwrap_or(WeatherLocation::Disabled);
+
+ Self {
+ id,
+ config,
+ backgrounds: combo_box::State::new(BackgroundMode::VARIANTS.to_vec()),
+ locations: combo_box::State::new(WeatherLocation::VARIANTS.to_vec()),
+
+ time_format: original_config.time_format,
+ background_mode: original_config.background_mode,
+ background: original_config.background,
+
+ location,
+ latitude,
+ longitude,
+ name,
+
+ location_results: vec![],
+ }
+ }
+
+ pub fn update(&mut self, msg: Message) -> Task<Message> {
+ match msg {
+ Message::Location(location) => {
+ self.location = location;
+ Task::none()
+ }
+ Message::BackgroundMode(mode) => {
+ self.background = mode.default_background().to_string();
+ self.background_mode = mode;
+ Task::none()
+ }
+ _ => Task::none(),
+ }
+ }
+
+ pub fn view(&self) -> Element<Message> {
+ let mut background_mode_row =
+ row![text(self.background_mode.edit_text()).width(Length::FillPortion(1))];
+
+ if self.background_mode == BackgroundMode::Local {
+ let text = if self.background == "" {
+ "Select file..."
+ } else {
+ &self.background
+ };
+
+ background_mode_row = background_mode_row.push(
+ button(text)
+ .on_press(Message::FileSelector)
+ .width(Length::FillPortion(2)),
+ );
+ } else {
+ background_mode_row = background_mode_row.push(
+ text_input(self.background_mode.default_background(), &self.background)
+ .on_input(Message::Background)
+ .width(Length::FillPortion(2)),
+ );
+ }
+
+ let (latitude, longitude, name) = match self.location {
+ WeatherLocation::Disabled => (None, None, None),
+ WeatherLocation::LocationName => (None, None, Some(Message::Name)),
+ WeatherLocation::Coordinates => {
+ (Some(Message::Latitude), Some(Message::Longitude), None)
+ }
+ };
+
+ let mut results = column![];
+
+ for res in self.location_results.iter() {
+ results = results.push(
+ button(text(format!(
+ "{} ({}, {})",
+ res.name, res.latitude, res.longitude
+ )))
+ .style(button::text),
+ )
+ }
+
+ scrollable(
+ container(
+ column![
+ row![
+ text("Time format").width(Length::FillPortion(1)),
+ text_input("", &self.time_format)
+ .width(Length::FillPortion(2))
+ .on_input(Message::TimeFormat)
+ ],
+ row![
+ text("Background mode").width(Length::FillPortion(1)),
+ combo_box(
+ &self.backgrounds,
+ "",
+ Some(&self.background_mode),
+ Message::BackgroundMode
+ )
+ .width(Length::FillPortion(2))
+ ],
+ background_mode_row,
+ row![
+ text("Weather Location").width(Length::FillPortion(1)),
+ combo_box(&self.locations, "", Some(&self.location), Message::Location)
+ .width(Length::FillPortion(2))
+ ],
+ row![
+ text("Latitude").width(Length::FillPortion(1)),
+ text_input("", &self.latitude)
+ .width(Length::FillPortion(2))
+ .on_input_maybe(latitude)
+ ],
+ row![
+ text("Longitude").width(Length::FillPortion(1)),
+ text_input("", &self.longitude)
+ .width(Length::FillPortion(2))
+ .on_input_maybe(longitude)
+ ],
+ row![
+ text("Location").width(Length::FillPortion(1)),
+ text_input("", &self.name)
+ .width(Length::FillPortion(2))
+ .on_input_maybe(name),
+ ],
+ scrollable(results)
+ .height(Length::Fixed(
+ 64.0 * (self.location_results.len().clamp(0, 1) as f32)
+ ))
+ .width(Length::Fill),
+ button("Save").on_press(Message::Save)
+ ]
+ .spacing(10),
+ )
+ .padding(15),
+ )
+ .into()
+ }
+}