feat: wallpaper subscription
This commit is contained in:
parent
44a1a9ab90
commit
0508323427
3 changed files with 142 additions and 82 deletions
|
|
@ -16,16 +16,19 @@ use std::{
|
||||||
|
|
||||||
#[cfg(feature = "xdg-portal")]
|
#[cfg(feature = "xdg-portal")]
|
||||||
use cosmic::dialog::file_chooser;
|
use cosmic::dialog::file_chooser;
|
||||||
use cosmic::widget::{
|
|
||||||
button, dropdown, list_column, row,
|
|
||||||
segmented_button::{self, SingleSelectModel},
|
|
||||||
settings, tab_bar, text, toggler,
|
|
||||||
};
|
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
Apply, Element, Task,
|
Apply, Element, Task,
|
||||||
widget::{ColorPickerModel, color_picker::ColorPickerUpdate, icon},
|
widget::{ColorPickerModel, color_picker::ColorPickerUpdate, icon},
|
||||||
};
|
};
|
||||||
use cosmic::{app::ContextDrawer, iced_runtime::core::image::Handle as ImageHandle};
|
use cosmic::{app::ContextDrawer, iced_runtime::core::image::Handle as ImageHandle};
|
||||||
|
use cosmic::{
|
||||||
|
iced::Subscription,
|
||||||
|
widget::{
|
||||||
|
button, dropdown, list_column, row,
|
||||||
|
segmented_button::{self, SingleSelectModel},
|
||||||
|
settings, tab_bar, text, toggler,
|
||||||
|
},
|
||||||
|
};
|
||||||
use cosmic::{
|
use cosmic::{
|
||||||
iced::{Alignment, Color, Length, window},
|
iced::{Alignment, Color, Length, window},
|
||||||
surface,
|
surface,
|
||||||
|
|
@ -39,6 +42,8 @@ use image::{ImageBuffer, Rgba};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use slotmap::{DefaultKey, Key, SecondaryMap, SlotMap};
|
use slotmap::{DefaultKey, Key, SecondaryMap, SlotMap};
|
||||||
|
|
||||||
|
use crate::subscription::WallpaperEvent;
|
||||||
|
|
||||||
const ZOOM: usize = 0;
|
const ZOOM: usize = 0;
|
||||||
const FIT: usize = 1;
|
const FIT: usize = 1;
|
||||||
|
|
||||||
|
|
@ -61,7 +66,6 @@ struct OutputName(String);
|
||||||
pub struct InitUpdate {
|
pub struct InitUpdate {
|
||||||
service_config: wallpaper::Config,
|
service_config: wallpaper::Config,
|
||||||
displays: HashMap<String, (String, (u32, u32))>,
|
displays: HashMap<String, (String, (u32, u32))>,
|
||||||
selection: Context,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Messages for the wallpaper view.
|
/// Messages for the wallpaper view.
|
||||||
|
|
@ -79,6 +83,8 @@ pub enum Message {
|
||||||
ChangeCategory(Category),
|
ChangeCategory(Category),
|
||||||
/// Changes the displayed images in the wallpaper view.
|
/// Changes the displayed images in the wallpaper view.
|
||||||
ChangeFolder(Context),
|
ChangeFolder(Context),
|
||||||
|
/// Emits a wallpaper event.
|
||||||
|
Event(WallpaperEvent),
|
||||||
/// Handles messages from the color dialog.
|
/// Handles messages from the color dialog.
|
||||||
ColorAdd(ColorPickerUpdate),
|
ColorAdd(ColorPickerUpdate),
|
||||||
/// Creates a color context drawer
|
/// Creates a color context drawer
|
||||||
|
|
@ -220,40 +226,12 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_folder = self.config.current_folder();
|
|
||||||
|
|
||||||
let (task, on_enter_handle) = Task::future(async move {
|
let (task, on_enter_handle) = Task::future(async move {
|
||||||
let (service_config, displays) = wallpaper::config().await;
|
let (service_config, displays) = wallpaper::config().await;
|
||||||
|
|
||||||
let mut selection = change_folder(current_folder).await;
|
|
||||||
|
|
||||||
// `selection.active` is usually empty because `change_folder` creates a fresh context.
|
|
||||||
// This leads to blank previews in certain conditions when the program is restarted.
|
|
||||||
let fix_active = match selection.active {
|
|
||||||
Choice::Wallpaper(key) if !selection.paths.contains_key(key) => true,
|
|
||||||
Choice::Color(ref color) if !selection.custom_colors.contains(color) => true,
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
if fix_active {
|
|
||||||
selection.active = match service_config.default_background.source {
|
|
||||||
Source::Path(ref path) if !path.is_dir() => selection
|
|
||||||
.paths
|
|
||||||
.iter()
|
|
||||||
.find(|(_key, valid_path)| path == valid_path.as_path())
|
|
||||||
.map(|(key, _)| Choice::Wallpaper(key))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
Source::Path(_) => Choice::Slideshow,
|
|
||||||
Source::Color(ref color) => {
|
|
||||||
selection.add_custom_color(color.clone());
|
|
||||||
Choice::Color(color.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::pages::Message::DesktopWallpaper(Message::Init(Box::new(InitUpdate {
|
crate::pages::Message::DesktopWallpaper(Message::Init(Box::new(InitUpdate {
|
||||||
service_config,
|
service_config,
|
||||||
displays,
|
displays,
|
||||||
selection,
|
|
||||||
})))
|
})))
|
||||||
})
|
})
|
||||||
.abortable();
|
.abortable();
|
||||||
|
|
@ -299,10 +277,15 @@ impl page::Page<crate::pages::Message> for Page {
|
||||||
&self,
|
&self,
|
||||||
core: &cosmic::Core,
|
core: &cosmic::Core,
|
||||||
) -> cosmic::iced::Subscription<crate::pages::Message> {
|
) -> cosmic::iced::Subscription<crate::pages::Message> {
|
||||||
core.watch_state::<cosmic_bg_config::state::State>(cosmic_bg_config::NAME)
|
let subscriptions = vec![
|
||||||
.map(|update| {
|
core.watch_state::<cosmic_bg_config::state::State>(cosmic_bg_config::NAME)
|
||||||
crate::pages::Message::DesktopWallpaper(Message::UpdateState(update.config))
|
.map(|update| {
|
||||||
})
|
crate::pages::Message::DesktopWallpaper(Message::UpdateState(update.config))
|
||||||
|
}),
|
||||||
|
crate::subscription::wallpapers(self.config.current_folder())
|
||||||
|
.map(|event| crate::pages::Message::DesktopWallpaper(Message::Event(event))),
|
||||||
|
];
|
||||||
|
Subscription::batch(subscriptions)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -590,16 +573,10 @@ impl Page {
|
||||||
|
|
||||||
/// Changes the selection category, such as wallpaper select or color select.
|
/// Changes the selection category, such as wallpaper select or color select.
|
||||||
fn change_category(&mut self, category: Category) -> Task<crate::app::Message> {
|
fn change_category(&mut self, category: Category) -> Task<crate::app::Message> {
|
||||||
let mut task = Task::none();
|
|
||||||
|
|
||||||
match category {
|
match category {
|
||||||
Category::Wallpapers => {
|
Category::Wallpapers => {
|
||||||
if self.config.current_folder.is_some() {
|
if self.config.current_folder.is_some() {
|
||||||
let _ = self.config.set_current_folder(None);
|
let _ = self.config.set_current_folder(None);
|
||||||
task = cosmic::task::future(async move {
|
|
||||||
let folder = change_folder(Config::default_folder()).await;
|
|
||||||
Message::ChangeFolder(folder)
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
self.select_first_wallpaper();
|
self.select_first_wallpaper();
|
||||||
}
|
}
|
||||||
|
|
@ -615,10 +592,6 @@ impl Page {
|
||||||
if let Err(why) = self.config.set_current_folder(Some(path.clone())) {
|
if let Err(why) = self.config.set_current_folder(Some(path.clone())) {
|
||||||
tracing::error!(?path, ?why, "failed to set current folder");
|
tracing::error!(?path, ?why, "failed to set current folder");
|
||||||
}
|
}
|
||||||
|
|
||||||
task = cosmic::task::future(async move {
|
|
||||||
Message::ChangeFolder(change_folder(path).await)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -641,7 +614,7 @@ impl Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.categories.selected = Some(category);
|
self.categories.selected = Some(category);
|
||||||
task
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the output being configured
|
/// Changes the output being configured
|
||||||
|
|
@ -928,13 +901,6 @@ impl Page {
|
||||||
self.categories.selected = Some(Category::RecentFolder(id));
|
self.categories.selected = Some(Category::RecentFolder(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the wallpapers from the selected folder into the view.
|
|
||||||
return cosmic::Task::future(async move {
|
|
||||||
let message = Message::ChangeFolder(change_folder(path).await);
|
|
||||||
let page_message = crate::pages::Message::DesktopWallpaper(message);
|
|
||||||
crate::Message::PageMessage(page_message)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -962,10 +928,66 @@ impl Page {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::Event(event) => match event {
|
||||||
|
WallpaperEvent::Loading => {
|
||||||
|
self.selection = Context::default();
|
||||||
|
}
|
||||||
|
WallpaperEvent::Load {
|
||||||
|
path,
|
||||||
|
display,
|
||||||
|
selection,
|
||||||
|
} => {
|
||||||
|
let key = self.selection.paths.insert(path);
|
||||||
|
self.selection.display_images.insert(key, display);
|
||||||
|
self.selection.selection_handles.insert(
|
||||||
|
key,
|
||||||
|
ImageHandle::from_rgba(
|
||||||
|
selection.width(),
|
||||||
|
selection.height(),
|
||||||
|
selection.into_vec(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// `selection.active` is usually empty because `change_folder` creates a fresh context.
|
||||||
|
// This leads to blank previews in certain conditions when the program is restarted.
|
||||||
|
let fix_active = match self.selection.active {
|
||||||
|
Choice::Wallpaper(key) if !self.selection.paths.contains_key(key) => true,
|
||||||
|
Choice::Color(ref color)
|
||||||
|
if !self.selection.custom_colors.contains(color) =>
|
||||||
|
{
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if fix_active {
|
||||||
|
self.selection.active =
|
||||||
|
match self.wallpaper_service_config.default_background.source {
|
||||||
|
Source::Path(ref path) if !path.is_dir() => self
|
||||||
|
.selection
|
||||||
|
.paths
|
||||||
|
.iter()
|
||||||
|
.find(|(_key, valid_path)| path == valid_path.as_path())
|
||||||
|
.map(|(key, _)| Choice::Wallpaper(key))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
Source::Path(_) => Choice::Slideshow,
|
||||||
|
Source::Color(ref color) => {
|
||||||
|
self.selection.add_custom_color(color.clone());
|
||||||
|
Choice::Color(color.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WallpaperEvent::Loaded => {
|
||||||
|
self.select_first_wallpaper();
|
||||||
|
}
|
||||||
|
WallpaperEvent::Error(error) => {
|
||||||
|
tracing::error!("Failed to load wallpaper: {}", error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
Message::Init(update) => {
|
Message::Init(update) => {
|
||||||
self.outputs.clear();
|
self.outputs.clear();
|
||||||
self.wallpaper_service_config = update.service_config;
|
self.wallpaper_service_config = update.service_config;
|
||||||
self.selection = update.selection;
|
|
||||||
self.show_tab_bar = update.displays.len() > 1;
|
self.show_tab_bar = update.displays.len() > 1;
|
||||||
|
|
||||||
// Sync custom colors from config.
|
// Sync custom colors from config.
|
||||||
|
|
@ -1174,31 +1196,6 @@ impl Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_folder(current_folder: PathBuf) -> Context {
|
|
||||||
let mut update = Context::default();
|
|
||||||
let mut streams = Vec::with_capacity(2);
|
|
||||||
|
|
||||||
streams.push(wallpaper::load_each_from_path(current_folder).await);
|
|
||||||
|
|
||||||
for mut wallpapers in streams {
|
|
||||||
while let Some((path, display_image, selection_image)) = wallpapers.next().await {
|
|
||||||
let id = update.paths.insert(path);
|
|
||||||
|
|
||||||
update.display_images.insert(id, display_image);
|
|
||||||
|
|
||||||
let selection_handle = ImageHandle::from_rgba(
|
|
||||||
selection_image.width(),
|
|
||||||
selection_image.height(),
|
|
||||||
selection_image.into_vec(),
|
|
||||||
);
|
|
||||||
|
|
||||||
update.selection_handles.insert(id, selection_handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn settings() -> Section<crate::pages::Message> {
|
pub fn settings() -> Section<crate::pages::Message> {
|
||||||
let mut descriptions = Slab::new();
|
let mut descriptions = Slab::new();
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,5 @@ pub use desktop_files::*;
|
||||||
mod daytime;
|
mod daytime;
|
||||||
#[cfg(feature = "ashpd")]
|
#[cfg(feature = "ashpd")]
|
||||||
pub use daytime::*;
|
pub use daytime::*;
|
||||||
|
mod wallpapers;
|
||||||
|
pub use wallpapers::*;
|
||||||
|
|
|
||||||
61
cosmic-settings/src/subscription/wallpapers.rs
Normal file
61
cosmic-settings/src/subscription/wallpapers.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use cosmic::iced::{
|
||||||
|
Subscription,
|
||||||
|
futures::{SinkExt, StreamExt, channel::mpsc::Sender, future},
|
||||||
|
stream,
|
||||||
|
};
|
||||||
|
use image::{ImageBuffer, Rgba};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Event emitted by the wallpaper subscription
|
||||||
|
pub enum WallpaperEvent {
|
||||||
|
/// Started loading wallpapers
|
||||||
|
Loading,
|
||||||
|
/// Loaded wallpaper
|
||||||
|
Load {
|
||||||
|
path: PathBuf,
|
||||||
|
display: ImageBuffer<Rgba<u8>, Vec<u8>>,
|
||||||
|
selection: ImageBuffer<Rgba<u8>, Vec<u8>>,
|
||||||
|
},
|
||||||
|
/// Wallpaper source finished loading
|
||||||
|
Loaded,
|
||||||
|
/// An error ocurred loading wallpapers
|
||||||
|
Error(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wallpapers(current_dir: PathBuf) -> cosmic::iced::Subscription<WallpaperEvent> {
|
||||||
|
Subscription::run_with_id(
|
||||||
|
current_dir.clone(),
|
||||||
|
stream::channel(2, |tx| async {
|
||||||
|
if let Err(err) = inner(tx, current_dir).await {
|
||||||
|
tracing::error!("Wallpapers subscription error: {:?}", err);
|
||||||
|
}
|
||||||
|
future::pending().await
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn inner(tx: Sender<WallpaperEvent>, current_dir: PathBuf) -> anyhow::Result<()> {
|
||||||
|
tx.clone().send(WallpaperEvent::Loading).await?;
|
||||||
|
|
||||||
|
let mut stream = cosmic_settings_wallpaper::load_each_from_path(current_dir).await;
|
||||||
|
|
||||||
|
while let Some((path, display, selection)) = stream.next().await {
|
||||||
|
if let Err(e) = tx
|
||||||
|
.clone()
|
||||||
|
.send(WallpaperEvent::Load {
|
||||||
|
path,
|
||||||
|
display,
|
||||||
|
selection,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
tracing::error!("Failed to send WallpaperEvent: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.clone().send(WallpaperEvent::Loaded).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue