From 71475813c089c3119b6b06d09e41c684e2016109 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 27 Jun 2023 03:17:46 +0200 Subject: [PATCH] feat(wallpaper): simulated display --- Cargo.lock | 1 + app/Cargo.toml | 1 + app/src/pages/desktop/wallpaper/mod.rs | 148 ++++++++++++++++++------- app/src/theme.rs | 13 ++- app/src/widget/mod.rs | 4 +- pages/desktop/src/wallpaper.rs | 4 +- 6 files changed, 129 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ff348d..1655f24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -791,6 +791,7 @@ dependencies = [ "generator", "i18n-embed", "i18n-embed-fl", + "image", "libcosmic", "log", "notify", diff --git a/app/Cargo.toml b/app/Cargo.toml index 798aeb7..0bc7e75 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -34,6 +34,7 @@ url = "2.3.1" freedesktop-desktop-entry = "0.5.0" notify = "6.0.0" anyhow = "1.0" +image = "0.24.6" [dependencies.i18n-embed] version = "0.13.9" diff --git a/app/src/pages/desktop/wallpaper/mod.rs b/app/src/pages/desktop/wallpaper/mod.rs index bb80d3c..497aa80 100644 --- a/app/src/pages/desktop/wallpaper/mod.rs +++ b/app/src/pages/desktop/wallpaper/mod.rs @@ -20,6 +20,7 @@ use cosmic::{iced_core::alignment, iced_runtime::core::image::Handle as ImageHan use cosmic_settings_desktop::wallpaper::{self, Entry, ScalingMode}; use cosmic_settings_page::Section; use cosmic_settings_page::{self as page, section}; +use image::imageops::FilterType::Lanczos3; use slotmap::{DefaultKey, SecondaryMap, SlotMap}; const SYSTEM_WALLPAPER_DIR: &str = "/usr/share/backgrounds/pop/"; @@ -31,6 +32,9 @@ const FIT: usize = 0; const STRETCH: usize = 1; const ZOOM: usize = 2; +const SIMULATED_WIDTH: u16 = 300; +const SIMULATED_HEIGHT: u16 = 169; + const MINUTES_5: usize = 0; const MINUTES_10: usize = 1; const MINUTES_15: usize = 2; @@ -59,6 +63,7 @@ pub enum Category { pub struct Page { pub active_output: Option, pub active_category: usize, + pub cached_display_handle: Option, pub categories: Vec, pub config: wallpaper::Config, pub current_directory: PathBuf, @@ -76,6 +81,7 @@ impl Default for Page { Page { active_output: None, active_category: CATEGORY_SYSTEM_WALLPAPERS, + cached_display_handle: None, categories: vec![fl!("system-backgrounds"), fl!("colors")], config: wallpaper::Config::default(), current_directory: PathBuf::from(SYSTEM_WALLPAPER_DIR), @@ -127,11 +133,76 @@ impl Default for Choice { pub struct Context { active: Choice, paths: SlotMap, - display_handles: SecondaryMap, + display_images: SecondaryMap, selection_handles: SecondaryMap, } impl Page { + fn cache_display_image(&mut self) { + let choice = match self.selection.active { + Choice::Background(id) => self.selection.display_images.get(id), + + Choice::Slideshow => self.selection.display_images.values().next(), + + Choice::Color(_) => None, + }; + + let Some(image) = choice else { + return; + }; + + self.cached_display_handle = None; + + let temp_image; + + let image = match self.selected_fit { + FIT => image, + + STRETCH => { + temp_image = image::imageops::resize( + image, + SIMULATED_WIDTH as u32, + SIMULATED_HEIGHT as u32, + Lanczos3, + ); + &temp_image + } + + ZOOM => { + let (w, h) = (image.width(), image.height()); + + let ratio = + (SIMULATED_WIDTH as f64 / w as f64).max(SIMULATED_HEIGHT as f64 / h as f64); + + let (new_width, new_height) = ( + (w as f64 * ratio).round() as u32, + (h as f64 * ratio).round() as u32, + ); + + let mut new_image = image::imageops::resize(image, new_width, new_height, Lanczos3); + + temp_image = image::imageops::crop( + &mut new_image, + (new_width - SIMULATED_WIDTH as u32) / 2, + (new_height - SIMULATED_HEIGHT as u32) / 2, + SIMULATED_WIDTH as u32, + SIMULATED_HEIGHT as u32, + ) + .to_image(); + + &temp_image + } + + _ => return, + }; + + self.cached_display_handle = Some(ImageHandle::from_pixels( + image.width(), + image.height(), + image.to_vec(), + )); + } + fn config_output(&self) -> Option { if self.config.same_on_all { Some(String::from("all")) @@ -261,6 +332,7 @@ impl Page { CATEGORY_COLOR => { self.selection.active = Choice::Color(wallpaper::DEFAULT_COLORS[0].clone()); + self.cache_display_image(); } _ => (), @@ -296,6 +368,17 @@ impl Page { }; } + #[must_use] + pub fn display_image_view(&self) -> cosmic::Element { + match self.cached_display_handle { + Some(ref handle) => cosmic::iced::widget::image(handle.clone()) + .width(Length::Fixed(SIMULATED_WIDTH as f32)) + .into(), + + None => cosmic::iced::widget::Space::new(SIMULATED_WIDTH, SIMULATED_HEIGHT).into(), + } + } + #[allow(clippy::too_many_lines)] pub fn update(&mut self, message: Message) { match message { @@ -303,6 +386,7 @@ impl Page { Message::ColorSelect(color) => { self.selection.active = Choice::Color(color); + self.cached_display_handle = None; } Message::Fit(option) => { @@ -312,6 +396,8 @@ impl Page { .enumerate() .find(|(_, key)| **key == option) .map_or(0, |(indice, _)| indice); + + self.cache_display_image(); } Message::Output(id) => self.change_output(id), @@ -325,11 +411,13 @@ impl Page { Message::Select(id) => { self.selection.active = Choice::Background(id); + self.cache_display_image(); } Message::Slideshow(enable) => { if enable { self.selection.active = Choice::Slideshow; + self.cache_display_image(); } else { self.select_first_background(); } @@ -347,6 +435,7 @@ impl Page { wallpaper::Source::Path(ref path) => { if path.is_dir() { self.selection.active = Choice::Slideshow; + self.cache_display_image(); } else if let Some(entity) = self.background_id_from_path(path) { self.select_background(entry, entity, path.is_dir()); } @@ -355,6 +444,7 @@ impl Page { wallpaper::Source::Color(ref color) => { self.selection.active = Choice::Color(color.clone()); self.active_category = CATEGORY_COLOR; + self.cache_display_image(); } } } @@ -399,6 +489,8 @@ impl Page { } self.rotation_frequency = entry.rotation_frequency; + + self.cache_display_image(); } /// Locate the ID of a background that's already stored in memory @@ -438,13 +530,7 @@ impl page::Page for Page { while let Some((path, display_image, selection_image)) = backgrounds.recv().await { let id = update.paths.insert(path); - let display_handle = ImageHandle::from_pixels( - display_image.width(), - display_image.height(), - display_image.into_vec(), - ); - - update.display_handles.insert(id, display_handle); + update.display_images.insert(id, display_image); let selection_handle = ImageHandle::from_pixels( selection_image.width(), @@ -485,38 +571,24 @@ pub fn settings() -> Section { let mut show_slideshow_toggle = true; let mut slideshow_enabled = false; - let display_demo = match page.selection.active { - // Shows background options, with the slideshow toggle enabled - Choice::Slideshow => { - slideshow_enabled = true; - page.selection - .display_handles - .values() - .next() - .map(|handle| { - cosmic::iced::widget::image(handle.clone()) - .width(Length::Fixed(300.0)) - .into() - }) - } + children.push(crate::widget::display_container( + match page.selection.active { + // Shows background options, with the slideshow toggle enabled + Choice::Slideshow => { + slideshow_enabled = true; + page.display_image_view() + } - // Shows background options, with the slideshow toggle visible - Choice::Background(key) => page.selection.display_handles.get(key).map(|handle| { - cosmic::iced::widget::image(handle.clone()) - .width(Length::Fixed(300.0)) - .into() - }), + // Shows background options, with the slideshow toggle visible + Choice::Background(_) => page.display_image_view(), - // Displays color options, and hides the slideshow toggle - Choice::Color(ref color) => { - show_slideshow_toggle = false; - Some(widgets::color_image(color.clone(), 300, 169, 0.0)) - } - }; - - if let Some(element) = display_demo { - children.push(crate::widget::display_container(element)); - } + // Displays color options, and hides the slideshow toggle + Choice::Color(ref color) => { + show_slideshow_toggle = false; + widgets::color_image(color.clone(), SIMULATED_WIDTH, SIMULATED_HEIGHT, 0.0) + } + }, + )); children.push(if page.config.same_on_all { cosmic::widget::text(fl!("all-displays")) diff --git a/app/src/theme.rs b/app/src/theme.rs index bc75e61..14df9d9 100644 --- a/app/src/theme.rs +++ b/app/src/theme.rs @@ -4,7 +4,7 @@ use cosmic::{iced_widget::core::BorderRadius, theme}; #[must_use] -pub fn display_container() -> cosmic::theme::Container { +pub fn display_container_frame() -> cosmic::theme::Container { theme::Container::custom(|_theme| cosmic::iced::widget::container::Appearance { text_color: None, background: Some(cosmic::iced::Background::Color(cosmic::iced::Color::WHITE)), @@ -13,3 +13,14 @@ pub fn display_container() -> cosmic::theme::Container { border_width: 3.0, }) } + +#[must_use] +pub fn display_container_screen() -> cosmic::theme::Container { + theme::Container::custom(|_theme| cosmic::iced::widget::container::Appearance { + text_color: None, + background: Some(cosmic::iced::Background::Color(cosmic::iced::Color::BLACK)), + border_color: cosmic::iced::Color::BLACK, + border_radius: BorderRadius::from(0.0), + border_width: 0.0, + }) +} diff --git a/app/src/widget/mod.rs b/app/src/widget/mod.rs index d8c77ef..6da9797 100644 --- a/app/src/widget/mod.rs +++ b/app/src/widget/mod.rs @@ -137,8 +137,10 @@ pub fn display_container<'a, Message: 'a>(widget: Element<'a, Message>) -> Eleme row!( horizontal_space(Length::Fill), container(widget) + .style(crate::theme::display_container_screen()) + .apply(container) .padding(4) - .style(crate::theme::display_container()), + .style(crate::theme::display_container_frame()), horizontal_space(Length::Fill), ) .padding([0, 0, 8, 0]) diff --git a/pages/desktop/src/wallpaper.rs b/pages/desktop/src/wallpaper.rs index ab4603f..249bdcf 100644 --- a/pages/desktop/src/wallpaper.rs +++ b/pages/desktop/src/wallpaper.rs @@ -70,7 +70,7 @@ pub fn config() -> (Config, HashMap) { pub fn set(config: &mut Config, entry: Entry) { if let Ok(context) = Config::helper() { - tracing::info!( + tracing::debug!( output = entry.output.to_string(), source = ?entry.source, "setting wallpaper", @@ -312,7 +312,7 @@ fn border_radius( let mut alpha: u16 = 0; let mut skip_draw = true; - let draw = |img: &mut image::ImageBuffer, Vec>, alpha, x, y| { + let draw = |img: &mut RgbaImage, alpha, x, y| { debug_assert!((1..=256).contains(&alpha)); let pixel_alpha = &mut img[coordinates(r0 - x, r0 - y)].0[3]; *pixel_alpha = ((alpha * *pixel_alpha as u16 + 128) / 256) as u8;