feat: partial implementation of wallpaper settings

Will take some time to refactor the rest
This commit is contained in:
Michael Aaron Murphy 2023-05-22 17:26:14 +02:00
parent 14a4a23bc9
commit a4eee2186c
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
14 changed files with 375 additions and 80 deletions

65
Cargo.lock generated
View file

@ -715,12 +715,25 @@ dependencies = [
"libc",
]
[[package]]
name = "cosmic-bg-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-bg#c9fec966262a9a3572e662b4e98f647f4807ba33"
dependencies = [
"cosmic-config",
"derive_setters",
"image",
"ron",
"serde",
]
[[package]]
name = "cosmic-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"atomicwrites",
"calloop",
"cosmic-config-derive",
"dirs 5.0.1",
"iced_futures",
@ -732,7 +745,7 @@ dependencies = [
[[package]]
name = "cosmic-config-derive"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"quote",
"syn 1.0.109",
@ -741,7 +754,7 @@ dependencies = [
[[package]]
name = "cosmic-panel-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/cosmic-panel#adbe256782887cb0a15b9b421a477e70d1ca1b80"
source = "git+https://github.com/pop-os/cosmic-panel?branch=settings_jammy#a71a4cba13184f22ba8874c910b20e99f60871c0"
dependencies = [
"anyhow",
"cosmic-config",
@ -760,6 +773,7 @@ dependencies = [
"async-channel",
"color-eyre",
"cosmic-panel-config",
"cosmic-settings-desktop",
"cosmic-settings-page",
"cosmic-settings-system",
"cosmic-settings-time",
@ -779,6 +793,21 @@ dependencies = [
"tracing-subscriber",
]
[[package]]
name = "cosmic-settings-desktop"
version = "0.1.0"
dependencies = [
"cosmic-bg-config",
"cosmic-config",
"dirs 5.0.1",
"freedesktop-icons",
"futures-lite",
"image",
"rayon",
"tokio",
"tracing",
]
[[package]]
name = "cosmic-settings-page"
version = "0.1.0"
@ -836,7 +865,7 @@ dependencies = [
[[package]]
name = "cosmic-theme"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"anyhow",
"cosmic-config",
@ -2015,7 +2044,7 @@ dependencies = [
[[package]]
name = "iced"
version = "0.9.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"iced_accessibility",
"iced_core",
@ -2031,7 +2060,7 @@ dependencies = [
[[package]]
name = "iced_accessibility"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"accesskit",
"accesskit_unix",
@ -2040,7 +2069,7 @@ dependencies = [
[[package]]
name = "iced_core"
version = "0.9.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"bitflags 1.3.2",
"iced_accessibility",
@ -2055,7 +2084,7 @@ dependencies = [
[[package]]
name = "iced_futures"
version = "0.6.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"futures",
"iced_core",
@ -2068,7 +2097,7 @@ dependencies = [
[[package]]
name = "iced_graphics"
version = "0.8.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"bitflags 1.3.2",
"bytemuck",
@ -2085,7 +2114,7 @@ dependencies = [
[[package]]
name = "iced_renderer"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"iced_graphics",
"iced_tiny_skia",
@ -2097,7 +2126,7 @@ dependencies = [
[[package]]
name = "iced_runtime"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"iced_accessibility",
"iced_core",
@ -2109,7 +2138,7 @@ dependencies = [
[[package]]
name = "iced_sctk"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"enum-repr",
"float-cmp",
@ -2130,7 +2159,7 @@ dependencies = [
[[package]]
name = "iced_style"
version = "0.8.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"iced_core",
"once_cell",
@ -2140,7 +2169,7 @@ dependencies = [
[[package]]
name = "iced_tiny_skia"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"bytemuck",
"cosmic-text",
@ -2158,7 +2187,7 @@ dependencies = [
[[package]]
name = "iced_wgpu"
version = "0.10.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"bitflags 1.3.2",
"bytemuck",
@ -2180,7 +2209,7 @@ dependencies = [
[[package]]
name = "iced_widget"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"iced_renderer",
"iced_runtime",
@ -2195,7 +2224,7 @@ dependencies = [
[[package]]
name = "iced_winit"
version = "0.9.1"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"iced_graphics",
"iced_runtime",
@ -2487,7 +2516,7 @@ checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "libcosmic"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic?rev=f06a81c#f06a81ccf9fdeaef0033bfc07aa493ff8675f420"
source = "git+https://github.com/pop-os/libcosmic#31f7e97d5bf4860be5afd406209eed733f736f04"
dependencies = [
"apply",
"cosmic-config",

View file

@ -2,12 +2,20 @@
members = ["app", "page", "pages/*"]
default-members = ["app"]
[workspace.dependencies.iced_core]
git = "https://github.com/pop-os/libcosmic"
[workspace.dependencies.libcosmic]
git = "https://github.com/pop-os/libcosmic"
rev = "f06a81c"
default-features = false
features = ["debug", "wayland", "tokio"]
[workspace.dependencies.cosmic-config]
git = "https://github.com/pop-os/libcosmic"
rev = "f06a81c"
[workspace.dependencies.cosmic-bg-config]
git = "https://github.com/pop-os/cosmic-bg"
[workspace.dependencies.cosmic-panel-config]
git = "https://github.com/pop-os/cosmic-panel"
branch = "settings_jammy"

View file

@ -9,6 +9,7 @@ rust-version = "1.65.0"
apply = "0.3.0"
async-channel = "1.8.0"
color-eyre = "0.6.2"
cosmic-settings-desktop = { path = "../pages/desktop" }
cosmic-settings-page = { path = "../page" }
cosmic-settings-system = { path = "../pages/system" }
cosmic-settings-time = { path = "../pages/time" }
@ -23,7 +24,8 @@ rust-embed = "6.6.1"
slotmap = "1.0.6"
tokio = "1.28.2"
downcast-rs = "1.2.0"
cosmic-panel-config = { git = "https://github.com/pop-os/cosmic-panel" }
# TODO: migrate this dependency to the pages/desktop crate.
cosmic-panel-config = { workspace = true }
tracing = "0.1.37"
tracing-subscriber = { version = "0.3.17", features = ["env-filter"]}
@ -33,6 +35,4 @@ features = ["fluent-system", "desktop-requester"]
[profile.release]
opt-level = "s"
overflow-checks = true
lto = "thin"
incremental = false

View file

@ -235,19 +235,16 @@ impl Application for SettingsApp {
Message::None | Message::Search(_) => {}
Message::PageMessage(message) => match message {
crate::pages::Message::About(message) => {
if let Some(page) = self.pages.page_mut::<system::about::Page>() {
page.update(message);
}
page::update!(self.pages, message, system::about::Page);
}
crate::pages::Message::DateAndTime(message) => {
if let Some(page) = self.pages.page_mut::<time::date::Page>() {
page.update(message);
}
page::update!(self.pages, message, time::date::Page);
}
crate::pages::Message::Desktop(message) => {
if let Some(page) = self.pages.page_mut::<desktop::Page>() {
page.update(message);
}
page::update!(self.pages, message, desktop::Page);
}
crate::pages::Message::DesktopWallpaper(message) => {
page::update!(self.pages, message, desktop::wallpaper::Page);
}
crate::pages::Message::External { .. } => {
todo!("external plugins not supported yet");
@ -457,7 +454,10 @@ impl SettingsApp {
);
}
settings::view_column(column_widgets).into()
settings::view_column(column_widgets)
.max_width(683)
.padding(0)
.into()
}
fn search_changed(&mut self, phrase: String) {

View file

@ -52,8 +52,8 @@ pub fn main() -> color_eyre::Result<()> {
let localizer = crate::localize::localizer();
let requested_languages = DesktopLanguageRequester::requested_languages();
if let Err(error) = localizer.select(&requested_languages) {
eprintln!("error while loading fluent localizations: {error}");
if let Err(why) = localizer.select(&requested_languages) {
tracing::error!(%why, "error while loading fluent localizations");
}
cosmic::settings::set_default_icon_theme("Pop");

View file

@ -19,8 +19,6 @@ pub struct Page {
pub show_applications_button: bool,
pub show_minimize_button: bool,
pub show_maximize_button: bool,
pub slideshow: bool,
pub same_background: bool,
}
impl page::Page<crate::pages::Message> for Page {
@ -39,10 +37,8 @@ impl page::AutoBind<crate::pages::Message> for Page {
}
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Debug)]
pub enum Message {
Slideshow(bool),
SameBackground(bool),
ShowWorkspacesButton(bool),
ShowApplicationsButton(bool),
ShowMinimizeButton(bool),
@ -53,12 +49,10 @@ pub enum Message {
impl Page {
pub fn update(&mut self, message: Message) {
match message {
Message::SameBackground(value) => self.same_background = value,
Message::ShowApplicationsButton(value) => self.show_applications_button = value,
Message::ShowMaximizeButton(value) => self.show_maximize_button = value,
Message::ShowMinimizeButton(value) => self.show_minimize_button = value,
Message::ShowWorkspacesButton(value) => self.show_workspaces_button = value,
Message::Slideshow(value) => self.slideshow = value,
Message::TopLeftHotCorner(value) => self.top_left_hot_corner = value,
}
}

View file

@ -1,21 +1,74 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: GPL-3.0-only
use super::Message;
use std::{path::PathBuf, time::Instant};
use apply::Apply;
use cosmic::{
iced::widget::{column, container, horizontal_space, image, row, svg, text},
iced::alignment::Horizontal,
iced::widget::{column, container, horizontal_space, row, text},
iced::Length,
iced_runtime::core::image::Handle as ImageHandle,
theme,
widget::{list_column, settings, toggler},
Element,
};
use cosmic_settings_desktop::wallpaper::{self, Entry, Output};
use cosmic_settings_page::Section;
use cosmic_settings_page::{self as page, section};
use slotmap::SlotMap;
use slotmap::{DefaultKey, SecondaryMap, SlotMap};
#[derive(Default)]
pub struct Page;
#[derive(Clone, Debug)]
pub enum Message {
SameBackground(bool),
Select(DefaultKey),
Slideshow(bool),
Update((wallpaper::Config, Context)),
}
pub struct Page {
pub config: wallpaper::Config,
pub selection: Context,
pub same_background: bool,
pub slideshow: bool,
}
impl Default for Page {
fn default() -> Self {
Page {
config: wallpaper::Config::default(),
selection: Context::default(),
same_background: true,
slideshow: false,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Context {
active: DefaultKey,
handles: SlotMap<DefaultKey, ImageHandle>,
paths: SecondaryMap<DefaultKey, PathBuf>,
}
impl Page {
pub fn update(&mut self, message: Message) {
match message {
Message::SameBackground(value) => self.same_background = value,
Message::Select(id) => {
if let Some(path) = self.selection.paths.get(id) {
wallpaper::set(&mut self.config, Entry::new(Output::All, path.to_owned()));
self.selection.active = id;
}
}
Message::Slideshow(value) => self.slideshow = value,
Message::Update((config, selection)) => {
self.config = config;
self.selection = selection;
}
}
}
}
impl page::Page<crate::pages::Message> for Page {
fn content(
@ -30,6 +83,32 @@ impl page::Page<crate::pages::Message> for Page {
.title(fl!("wallpaper"))
.description(fl!("wallpaper", "desc"))
}
fn load(&self, _page: page::Entity) -> Option<page::Task<crate::pages::Message>> {
Some(Box::pin(async move {
let config = wallpaper::config();
let mut backgrounds = wallpaper::load_each_from_path("/usr/share/backgrounds".into());
let mut update = Context::default();
let start = Instant::now();
while let Some((path, image)) = backgrounds.recv().await {
let handle =
ImageHandle::from_pixels(image.width(), image.height(), image.into_vec());
let id = update.handles.insert(handle);
update.paths.insert(id, path);
}
tracing::info!(
"loaded wallpapers in {:?}",
Instant::now().duration_since(start)
);
crate::pages::Message::DesktopWallpaper(Message::Update((config, update)))
}))
}
}
impl page::AutoBind<crate::pages::Message> for Page {}
@ -42,57 +121,84 @@ pub fn settings() -> Section<crate::pages::Message> {
fl!("wallpaper", "slide"),
fl!("wallpaper", "change"),
])
.view::<Page>(|binder, _page, section| {
let desktop = binder
.page::<super::Page>()
.expect("desktop page not found");
.view::<Page>(|_binder, page, section| {
let descriptions = &section.descriptions;
let image_paths: Vec<std::path::PathBuf> = Vec::new();
let mut image_column = Vec::with_capacity(image_paths.len() / 4);
for chunk in image_paths.chunks(4) {
let mut image_row = Vec::with_capacity(chunk.len());
for image_path in chunk.iter() {
image_row.push(if image_path.ends_with(".svg") {
svg(svg::Handle::from_path(image_path))
.width(Length::Fixed(150.))
.into()
} else {
image(image_path).width(Length::Fixed(150.)).into()
});
let mut image_column = Vec::with_capacity(page.selection.handles.len() / 4);
let mut image_handles = page.selection.handles.iter();
while let Some((id, handle)) = image_handles.next() {
let mut image_row = Vec::with_capacity(4);
image_row.push(wallpaper_button(handle, id));
for (id, handle) in image_handles.by_ref().take(3) {
image_row.push(wallpaper_button(handle, id));
}
image_column.push(row(image_row).spacing(16).into());
}
let children = vec![
row!(
let mut children = Vec::with_capacity(3);
if let Some(image) = page.selection.handles.get(page.selection.active) {
let display_preview = row!(
horizontal_space(Length::Fill),
container(
image("/usr/share/backgrounds/pop/kate-hazen-COSMIC-desktop-wallpaper.png")
.width(Length::Fixed(300.))
cosmic::iced::widget::image(image.clone()).width(Length::Fixed(300.0))
)
.padding(4)
.style(theme::Container::Background),
horizontal_space(Length::Fill),
)
.into(),
.padding([0, 0, 8, 0])
.into();
children.push(display_preview);
}
children.push(
cosmic::widget::text("All Displays")
.horizontal_alignment(Horizontal::Center)
.width(Length::Fill)
.apply(cosmic::iced::widget::container)
.width(Length::Fill)
.padding([0, 0, 16, 0])
.into(),
);
children.push(
list_column()
.add(settings::item(
&descriptions[0],
toggler(None, desktop.same_background, Message::SameBackground),
toggler(None, page.same_background, Message::SameBackground),
))
.add(settings::item(&descriptions[1], text("TODO")))
.add(settings::item(
&descriptions[2],
toggler(None, desktop.slideshow, Message::Slideshow),
toggler(None, page.slideshow, Message::Slideshow),
))
.into(),
column(image_column).spacing(16).into(),
];
);
settings::view_column(children)
children.push(column(image_column).spacing(12).padding(0).into());
cosmic::iced::widget::column(children)
.spacing(22)
.padding(0)
.max_width(683)
.apply(Element::from)
.map(crate::pages::Message::Desktop)
.map(crate::pages::Message::DesktopWallpaper)
})
}
pub fn wallpaper_button(handle: &ImageHandle, id: DefaultKey) -> Element<Message> {
let image = cosmic::iced::widget::image(handle.clone()).apply(cosmic::iced::Element::from);
cosmic::iced::widget::button(image)
.width(Length::Fixed(158.0))
.height(Length::Fixed(105.0))
.style(cosmic::theme::Button::Transparent)
.on_press(Message::Select(id))
.into()
}

View file

@ -15,6 +15,7 @@ pub enum Message {
DateAndTime(time::date::Message),
Desktop(desktop::Message),
Panel(desktop::panel::Message),
DesktopWallpaper(desktop::wallpaper::Message),
External { id: String, message: Vec<u8> },
Page(Entity),
}

View file

@ -25,7 +25,7 @@ pub fn search_header<Message>(
column_children.push(
text(parent_meta.title.as_str())
.size(16)
.size(14)
.apply(container)
.padding([0, 0, 0, 6])
.into(),
@ -47,7 +47,7 @@ pub fn search_header<Message>(
#[must_use]
pub fn search_page_link<Message: 'static>(title: &str) -> Button<Message, cosmic::Renderer> {
text(title)
.size(32)
.size(24)
.horizontal_alignment(iced::alignment::Horizontal::Left)
.apply(button)
.style(cosmic::theme::Button::Link)
@ -56,7 +56,7 @@ pub fn search_page_link<Message: 'static>(title: &str) -> Button<Message, cosmic
#[must_use]
pub fn page_title<Message: 'static>(page: &page::Info) -> Element<Message> {
row!(
text(page.title.as_str()).size(32),
text(page.title.as_str()).size(24),
horizontal_space(Length::Fill)
)
.into()
@ -71,13 +71,13 @@ pub fn parent_page_button<'a, Message: Clone + 'static>(
column!(
button(row!(
icon("go-previous-symbolic", 20).style(theme::Svg::SymbolicLink),
text(parent.title.as_str()).size(20),
text(parent.title.as_str()).size(14),
))
.padding(0)
.style(theme::Button::Link)
.on_press(on_press),
row!(
text(sub_page.title.as_str()).size(32),
text(sub_page.title.as_str()).size(24),
horizontal_space(Length::Fill),
)
.align_items(iced::alignment::Alignment::Center),

View file

@ -1,7 +1,7 @@
name := 'cosmic-settings'
appid := 'com.system76.CosmicSettings'
# Use the lld linker if it is available.
# Use lld linker if available
ld-args := if `which lld || true` != '' {
'-C link-arg=-fuse-ld=lld -C link-arg=-Wl,--build-id=sha1'
} else {

View file

@ -83,3 +83,12 @@ impl Info {
}
}
}
#[macro_export]
macro_rules! update {
($binder:expr, $message:expr, $page:ty) => {{
if let Some(page) = $binder.page_mut::<$page>() {
page.update($message);
}
}};
}

17
pages/desktop/Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "cosmic-settings-desktop"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmic-bg-config = { workspace = true }
cosmic-config = { workspace = true }
dirs = "5.0.1"
freedesktop-icons = "0.2.3"
futures-lite = "1.13.0"
image = "0.24.6"
rayon = "1.7.0"
tokio = { version = "1.28.0", features = ["sync"] }
tracing = "0.1.37"

1
pages/desktop/src/lib.rs Normal file
View file

@ -0,0 +1 @@
pub mod wallpaper;

View file

@ -0,0 +1,130 @@
pub use cosmic_bg_config::{Config, Entry, Output};
use image::RgbaImage;
use std::{
collections::hash_map::DefaultHasher,
fs::DirEntry,
hash::{Hash, Hasher},
path::{Path, PathBuf},
sync::Arc,
};
use tokio::sync::mpsc::{self, Receiver};
pub fn config() -> Config {
let helper = Config::helper().expect("failed to get helper for cosmic bg config");
match Config::load(&helper) {
Ok(conf) => conf,
Err(why) => {
tracing::warn!(?why, "Config file error, falling back to defaults");
Config::default()
}
}
}
pub fn set(config: &mut Config, entry: Entry) {
if let Ok(context) = Config::helper() {
tracing::info!(
"setting wallpaper for {} to {}",
entry.output.to_string(),
entry.source.display()
);
if let Err(why) = config.set_entry(&context, entry) {
tracing::error!(?why, "failed to set background");
}
}
}
/// Path to directory where wallpaper thumbnails are stored.
#[must_use]
pub fn cache_dir() -> Option<PathBuf> {
dirs::cache_dir().map(|path| {
let cache = path.join("cosmic-settings/wallpapers");
let _res = std::fs::create_dir_all(&cache);
cache
})
}
/// Loads wallpapers in parallel by spawning tasks with a rayon thread pool.
#[must_use]
pub fn load_each_from_path(path: PathBuf) -> Receiver<(PathBuf, RgbaImage)> {
let cache_dir = Arc::new(cache_dir());
let (tx, rx) = mpsc::channel(1);
tokio::task::spawn_blocking(move || {
let mut paths = vec![path];
while let Some(path) = paths.pop() {
if let Ok(dir) = path.read_dir() {
for entry in dir.filter_map(Result::ok) {
let Ok(file_type) = entry.file_type() else {
continue
};
let path = entry.path();
if file_type.is_dir() {
paths.push(path);
} else if file_type.is_file() {
let tx = tx.clone();
let cache_dir = cache_dir.clone();
rayon::spawn_fifo(move || {
let thumbnail =
load_thumbnail(cache_dir.as_deref(), &path, &entry, 300, 169);
if let Some(image) = thumbnail {
let _res = tx.blocking_send((path, image));
}
});
}
}
}
}
});
rx
}
/// Generates and caches the thumbnail of a wallpaper.
///
///
/// Caching reduces time required to load a wallpaper by 99%.
#[must_use]
pub fn load_thumbnail(
cache_dir: Option<&Path>,
path: &Path,
entry: &DirEntry,
width: u32,
height: u32,
) -> Option<RgbaImage> {
if let Some(cache_dir) = cache_dir {
if let Ok(ctime) = entry.metadata().and_then(|meta| meta.created()) {
// Search for thumbnail by a unique hash string.
let mut hasher = DefaultHasher::new();
path.hash(&mut hasher);
ctime.hash(&mut hasher);
let hash = hasher.finish();
let thumbnail_path = cache_dir.join(format!("{hash:x}.png"));
// Load image from thumbnail if it exists and can be opened.
if thumbnail_path.exists() {
if let Ok(image) = image::open(&thumbnail_path) {
return Some(image.into_rgba8());
}
}
// Create new thumbnail and save it if not.
return image::open(path).ok().map(|mut image| {
image = image.thumbnail_exact(width, height);
let _res = image.save(&thumbnail_path);
image.into_rgba8()
});
}
}
// Generate thumbnail from wallpaper without saving it
image::open(path).ok().map(|mut image| {
image = image.thumbnail_exact(width, height);
image.into_rgba8()
})
}