commit 9a4456496275c4fcc7d3fe4efef2f867216bc1ae
parent 203e97dee608c460c4c5b68d8e1d9ab1621f2539
Author: Sylvia Ivory <git@sivory.net>
Date: Thu, 19 Jun 2025 00:46:17 -0700
Allow loading local images
Diffstat:
5 files changed, 160 insertions(+), 57 deletions(-)
diff --git a/src/background.rs b/src/background.rs
@@ -1,12 +1,10 @@
use iced::{
- Color, Element, Length, Point, Renderer, Size, Theme, mouse,
+ Color, ContentFit, Element, Length, Point, Renderer, Size, Task, Theme, mouse,
widget::{canvas, container, image, stack, text},
};
+use tokio::fs;
-pub enum BackgroundKind {
- Image(image::Handle),
- Color(Color),
-}
+use crate::config::{BackgroundMode, Config};
struct Solid(Color);
@@ -33,20 +31,99 @@ impl<Message> canvas::Program<Message> for Solid {
}
}
-pub fn background<'a, Message: 'a>(kind: &'a BackgroundKind) -> Element<'a, Message> {
- match kind {
- BackgroundKind::Color(c) => canvas(Solid(*c))
+pub struct BackgroundHandle {
+ pub mode: BackgroundMode,
+ background: String,
+
+ image_handle: Option<image::Handle>,
+}
+
+#[derive(Debug, Clone)]
+pub enum Message {
+ BackgroundRead(Result<Vec<u8>, String>),
+}
+
+impl BackgroundHandle {
+ pub fn new(config: &Config) -> (Self, Task<Message>) {
+ let mut handle = Self {
+ mode: config.background_mode,
+ background: config.background.clone(),
+ image_handle: None,
+ };
+
+ let task = handle.refresh();
+
+ return (handle, task);
+ }
+
+ pub fn load_config(&mut self, config: &Config) -> Task<Message> {
+ self.mode = config.background_mode;
+ self.background = config.background.clone();
+
+ self.refresh()
+ }
+
+ fn refresh(&mut self) -> Task<Message> {
+ match self.mode {
+ BackgroundMode::Local => {
+ let path = self.background.clone();
+
+ Task::future(async move { fs::read(&path).await })
+ .map(|r| Message::BackgroundRead(r.map_err(|e| e.to_string())))
+ }
+ _ => Task::none(),
+ }
+ }
+
+ pub fn update(&mut self, msg: Message) -> Task<Message> {
+ match msg {
+ Message::BackgroundRead(res) => match res {
+ // TODO; log error
+ Err(_) => Task::none(),
+ Ok(bytes) => {
+ self.image_handle = Some(image::Handle::from_bytes(bytes));
+ Task::none()
+ }
+ },
+ }
+ }
+
+ fn solid<'a>(color: Color) -> Element<'a, Message> {
+ canvas(Solid(color))
.width(Length::Fill)
.height(Length::Fill)
- .into(),
- BackgroundKind::Image(i) => stack![
- image(i).width(Length::Fill).height(Length::Fill),
- // TODO; finish credits
- container(text("Photo, John Doe, Unsplash"))
- .align_left(Length::Fill)
- .align_bottom(Length::Fill)
- .padding(15)
- ]
- .into(),
+ .into()
+ }
+
+ pub fn view(&self) -> Element<Message> {
+ match self.mode {
+ BackgroundMode::Solid => {
+ Self::solid(Color::parse(&self.background).unwrap_or(Color::BLACK))
+ }
+ _ => {
+ if let Some(handle) = &self.image_handle {
+ let img = image(handle)
+ .content_fit(ContentFit::Cover)
+ .width(Length::Fill)
+ .height(Length::Fill);
+
+ if self.mode == BackgroundMode::Local {
+ img.into()
+ } else {
+ stack![
+ img,
+ // TODO; finish credits
+ container(text("Photo, John Doe, Unsplash"))
+ .align_left(Length::Fill)
+ .align_bottom(Length::Fill)
+ .padding(15)
+ ]
+ .into()
+ }
+ } else {
+ Self::solid(Color::BLACK)
+ }
+ }
+ }
}
}
diff --git a/src/config.rs b/src/config.rs
@@ -1,4 +1,4 @@
-#[derive(Debug, Clone, PartialEq, strum::Display, strum::VariantArray)]
+#[derive(Debug, Clone, Copy, PartialEq, strum::Display, strum::VariantArray)]
pub enum BackgroundMode {
Unsplash,
Solid,
@@ -10,7 +10,7 @@ impl BackgroundMode {
match self {
// https://unsplash.com/collections/1053828/tabliss-official
Self::Unsplash => "1053828",
- Self::Solid => "#ffffff",
+ Self::Solid => "#000000",
Self::Local => "",
}
}
@@ -43,8 +43,8 @@ impl Default for Config {
fn default() -> Self {
Self {
time_format: String::from("%-I:%M:%S"),
- background_mode: BackgroundMode::Unsplash,
- background: BackgroundMode::Unsplash.default_background().to_string(),
+ background_mode: BackgroundMode::Solid,
+ background: BackgroundMode::Solid.default_background().to_string(),
location: None,
}
}
diff --git a/src/icon.rs b/src/icon.rs
@@ -1,5 +1,5 @@
use iced::{
- Element, Length,
+ Color, Element, Length, Theme,
widget::{button, svg},
};
@@ -7,6 +7,7 @@ pub fn icon<'a, Message>(handle: impl Into<svg::Handle>) -> Element<'a, Message>
svg(handle)
.height(Length::Fixed(16.0))
.width(Length::Fixed(16.0))
+ .style(white)
.into()
}
@@ -19,3 +20,9 @@ pub fn icon_button<'a, Message: 'a + Clone>(
.on_press(on_press)
.into()
}
+
+fn white(_theme: &Theme, _status: svg::Status) -> svg::Style {
+ svg::Style {
+ color: Some(Color::WHITE),
+ }
+}
diff --git a/src/main.rs b/src/main.rs
@@ -13,10 +13,12 @@ use iced::{
window,
};
-use background::{BackgroundKind, background};
+use background::BackgroundHandle;
use config::Config;
use icon::{icon, icon_button};
+use crate::config::BackgroundMode;
+
mod background;
mod config;
mod icon;
@@ -26,7 +28,7 @@ struct Fjordgard {
config: Rc<RefCell<Config>>,
meteo: Arc<MeteoClient>,
time: DateTime<Local>,
- background: BackgroundKind,
+ background: BackgroundHandle,
format_string: String,
format_parsed: Vec<Item<'static>>,
@@ -47,11 +49,12 @@ enum Message {
Media(MediaControl),
OpenSettings,
- SettingsOpened(window::Id),
+ SettingsOpened,
MainWindowOpened,
WindowClosed(window::Id),
Settings(settings::Message),
+ Background(background::Message),
}
impl Fjordgard {
@@ -65,20 +68,24 @@ impl Fjordgard {
.unwrap();
let meteo = MeteoClient::new(None).unwrap();
+ let (background, task) = BackgroundHandle::new(&config);
(
Self {
config: Rc::new(RefCell::new(config)),
meteo: Arc::new(meteo),
time: Local::now(),
- background: BackgroundKind::Color(Color::from_rgb8(255, 255, 255)),
+ background,
format_string,
format_parsed,
settings_window: None,
main_window: id,
},
- open.map(|_| Message::MainWindowOpened),
+ Task::batch(vec![
+ open.map(|_| Message::MainWindowOpened),
+ task.map(Message::Background),
+ ]),
)
}
@@ -95,15 +102,6 @@ impl Fjordgard {
Message::Tick(time) => {
self.time = time;
- let config_format = &self.config.borrow().time_format;
-
- if &self.format_string != config_format {
- self.format_string = config_format.clone();
- self.format_parsed = StrftimeItems::new_lenient(config_format)
- .parse_to_owned()
- .unwrap();
- }
-
Task::none()
}
Message::OpenSettings => {
@@ -119,7 +117,7 @@ impl Fjordgard {
self.meteo.clone(),
));
- open.map(Message::SettingsOpened)
+ open.map(|_| Message::SettingsOpened)
} else {
Task::none()
}
@@ -132,6 +130,21 @@ impl Fjordgard {
Task::none()
}
}
+ Message::Settings(settings::Message::Committed) => {
+ let config = self.config.borrow();
+ let config_format = &config.time_format;
+
+ if &self.format_string != config_format {
+ self.format_string = config_format.clone();
+ self.format_parsed = StrftimeItems::new_lenient(config_format)
+ .parse_to_owned()
+ .unwrap();
+ }
+
+ self.background
+ .load_config(&config)
+ .map(Message::Background)
+ }
Message::Settings(msg) => {
if let Some(settings) = &mut self.settings_window {
settings.update(msg).map(Message::Settings)
@@ -139,6 +152,7 @@ impl Fjordgard {
Task::none()
}
}
+ Message::Background(msg) => self.background.update(msg).map(Message::Background),
_ => Task::none(),
}
}
@@ -163,36 +177,38 @@ impl Fjordgard {
let time_widget = text(time_text.to_string())
.size(100)
.font(bold)
+ .color(Color::WHITE)
.width(Length::Fill)
.center();
let weather_widget = container(row![
icon("icons/weather/not-available.svg"),
horizontal_space().width(Length::Fixed(7.25)),
- text("Weather unknown")
+ text("Weather unknown").color(Color::WHITE)
])
.center_x(Length::Fill);
- let control = container(
- row![
- icon_button("icons/previous.svg", Message::Media(MediaControl::Previous)),
- icon_button("icons/pause.svg", Message::Media(MediaControl::Pause)),
- icon_button("icons/next.svg", Message::Media(MediaControl::Next)),
- ]
- .spacing(5),
- )
- .center_x(Length::Fill);
-
let settings = icon_button("icons/settings.svg", Message::OpenSettings);
+ let mut main_column = column![settings, center(column![time_widget, weather_widget])];
+
+ if self.background.mode == BackgroundMode::Unsplash {
+ main_column = main_column.push(
+ container(
+ row![
+ icon_button("icons/previous.svg", Message::Media(MediaControl::Previous)),
+ icon_button("icons/pause.svg", Message::Media(MediaControl::Pause)),
+ icon_button("icons/next.svg", Message::Media(MediaControl::Next)),
+ ]
+ .spacing(5),
+ )
+ .center_x(Length::Fill),
+ )
+ }
+
stack![
- background(&self.background),
- container(column![
- settings,
- center(column![time_widget, weather_widget]),
- control
- ])
- .padding(15)
+ self.background.view().map(Message::Background),
+ container(main_column).padding(15)
]
.height(Length::Fill)
.width(Length::Fill)
diff --git a/src/settings.rs b/src/settings.rs
@@ -60,6 +60,8 @@ pub enum Message {
FileSelector,
FileSelected(Option<FileHandle>),
Save,
+
+ Committed,
}
impl Settings {
@@ -229,8 +231,9 @@ impl Settings {
}
}
- Task::none()
+ Task::done(Message::Committed)
}
+ _ => Task::none(),
}
}