feat(wallpaper): color dialog integration

This commit is contained in:
Michael Aaron Murphy 2023-12-12 15:05:17 +01:00 committed by Michael Murphy
parent daa730682a
commit 5f089ef9a3
4 changed files with 204 additions and 51 deletions

View file

@ -503,16 +503,19 @@ impl cosmic::Application for SettingsApp {
{
return page.dnd_icon();
}
if let Some(Some(page)) = (id == *applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID)
.then(|| self.pages.page::<applets_inner::Page>())
{
return page.add_applet_view(crate::pages::Message::PanelApplet);
}
if let Some(Some(page)) = (id == *appearance::COLOR_PICKER_DIALOG_ID)
.then(|| self.pages.page::<appearance::Page>())
{
return page.color_picker_view();
}
if let Some(Some(page)) =
(id == *ADD_DOCK_APPLET_DIALOGUE_ID).then(|| self.pages.page::<dock::applets::Page>())
{
@ -520,17 +523,25 @@ impl cosmic::Application for SettingsApp {
crate::pages::Message::DockApplet(dock::applets::Message(msg))
});
}
if let Some(Some(page)) = (id == *keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID)
.then(|| self.pages.page::<input::Page>())
{
return page.add_input_source_view();
}
if let Some(Some(page)) = (id == *keyboard::SPECIAL_CHARACTER_DIALOGUE_ID)
.then(|| self.pages.page::<input::Page>())
{
return page.special_character_key_view();
}
if let Some(page) = self.pages.page::<desktop::wallpaper::Page>() {
if id == page.color_dialog {
return page.show_color_dialog();
}
}
panic!("unknown window ID: {id:?}");
}

View file

@ -16,7 +16,7 @@ const RECENT_FOLDERS: &str = "recent-folders";
#[derive(Debug, Default)]
pub struct Config {
context: Option<cosmic_config::Config>,
current_folder: Option<PathBuf>,
pub(super) current_folder: Option<PathBuf>,
custom_colors: Vec<wallpaper::Color>,
custom_images: Vec<PathBuf>,
recent_folders: VecDeque<PathBuf>,
@ -34,8 +34,8 @@ impl Config {
}
};
if let Ok(path) = dbg!(context.get::<PathBuf>(CURRENT_FOLDER)) {
config.current_folder = Some(path);
if let Ok(path) = context.get::<Option<PathBuf>>(CURRENT_FOLDER) {
config.current_folder = path;
}
if let Ok(colors) = context.get::<Vec<wallpaper::Color>>(CUSTOM_COLORS) {
@ -59,7 +59,12 @@ impl Config {
pub fn current_folder(&self) -> &Path {
self.current_folder
.as_deref()
.unwrap_or(Path::new("/usr/share/backgrounds/"))
.unwrap_or(Self::default_folder())
}
#[must_use]
pub fn default_folder() -> &'static Path {
Path::new("/usr/share/backgrounds/")
}
/// Sets the current background folder
@ -67,9 +72,12 @@ impl Config {
/// # Errors
///
/// Returns an error if the on-disk configuration could not be updated.
pub fn set_current_folder(&mut self, folder: PathBuf) -> Result<(), cosmic_config::Error> {
pub fn set_current_folder(
&mut self,
folder: Option<PathBuf>,
) -> Result<(), cosmic_config::Error> {
let result = self.update(CURRENT_FOLDER, &folder);
self.current_folder = Some(folder);
self.current_folder = folder;
result
}

View file

@ -13,8 +13,12 @@ use std::{
};
use apply::Apply;
use cosmic::{iced::Length, Element};
use cosmic::{iced_core::alignment, iced_runtime::core::image::Handle as ImageHandle};
use cosmic::iced::{wayland::actions::window::SctkWindowSettings, window, Color, Length};
use cosmic::iced_sctk::commands::window::{close_window, get_window};
use cosmic::{
iced_core::{alignment, layout},
iced_runtime::core::image::Handle as ImageHandle,
};
use cosmic::{
widget::{
button, dropdown, list_column, row,
@ -23,6 +27,10 @@ use cosmic::{
},
Command,
};
use cosmic::{
widget::{color_picker::ColorPickerUpdate, ColorPickerModel},
Element,
};
use cosmic_settings_desktop::wallpaper::{self, Entry, ScalingMode};
use cosmic_settings_page::Section;
use cosmic_settings_page::{self as page, section};
@ -50,11 +58,12 @@ pub type Image = ImageBuffer<Rgba<u8>, Vec<u8>>;
#[derive(Clone, Debug)]
pub enum Message {
ChangeFolder(Context),
ColorAdd(wallpaper::Color),
ColorAddDialog,
ColorDialogUpdate(ColorPickerUpdate),
ColorRemove(wallpaper::Color),
ChangeCategory(Category),
ColorSelect(wallpaper::Color),
DragColorDialog,
Fit(usize),
ImageAdd(Option<Arc<(PathBuf, Image, Image)>>),
ImageAddDialog,
@ -79,6 +88,8 @@ pub struct Page {
pub background_service_config: wallpaper::Config,
pub cached_display_handle: Option<ImageHandle>,
pub categories: dropdown::multi::Model<String, Category>,
pub color_dialog: window::Id,
pub color_model: ColorPickerModel,
pub config: Config,
pub fit_options: Vec<String>,
pub outputs: SingleSelectModel,
@ -173,6 +184,8 @@ impl Default for Page {
categories
},
background_service_config: wallpaper::Config::default(),
color_dialog: window::Id::unique(),
color_model: ColorPickerModel::new(fl!("hex"), fl!("rgb"), None, Some(Color::WHITE)),
config: Config::new(),
fit_options: vec![fl!("fit-to-screen"), fl!("stretch"), fl!("zoom")],
outputs: SingleSelectModel::default(),
@ -204,11 +217,6 @@ impl Default for Page {
selection: Context::default(),
};
// Sync custom colors from config.
for color in page.config.custom_colors() {
page.selection.add_custom_color(color.clone());
}
page.assign_recent_folders();
page
@ -355,19 +363,13 @@ impl Page {
wallpaper::set(&mut self.background_service_config, entry);
}
/// Updates configuration for background image.
fn config_background_entry(&self, output: String, path: PathBuf) -> Option<Entry> {
let scaling_mode = match self.selected_fit {
FIT => ScalingMode::Fit([0.0, 0.0, 0.0]),
STRETCH => ScalingMode::Stretch,
ZOOM => ScalingMode::Zoom,
_ => return None,
};
Entry::new(output, wallpaper::Source::Path(path))
.scaling_mode(scaling_mode)
.rotation_frequency(self.rotation_frequency)
.apply(Some)
/// Locate the ID of a background that's already stored in memory
fn background_id_from_path(&self, path: &Path) -> Option<DefaultKey> {
self.selection
.paths
.iter()
.find(|(_id, background)| *background == path)
.map(|(id, _)| id)
}
/// Updates configuration from the background service.
@ -405,7 +407,7 @@ impl Page {
self.select_background_entry(&entry);
if let Some(current) = entry_directory(self.config.current_folder(), &entry) {
if let Err(why) = self.config.set_current_folder(current) {
if let Err(why) = self.config.set_current_folder(Some(current)) {
tracing::error!(?why, "cannot set current folder");
}
}
@ -423,7 +425,7 @@ impl Page {
if let Some(current) = entry_directory(self.config.current_folder(), background)
{
if let Err(why) = self.config.set_current_folder(current) {
if let Err(why) = self.config.set_current_folder(Some(current)) {
tracing::error!(?why, "cannot set current folder");
}
}
@ -445,7 +447,18 @@ impl Page {
match category {
Category::Backgrounds => {
self.select_first_background();
if self.config.current_folder.is_some() {
let _ = self.config.set_current_folder(None);
command = cosmic::command::future(async move {
crate::app::Message::PageMessage(crate::pages::Message::DesktopWallpaper(
Message::ChangeFolder(
change_folder(Config::default_folder().to_owned()).await,
),
))
});
} else {
self.select_first_background();
}
}
Category::Colors => {
@ -455,7 +468,7 @@ impl Page {
Category::RecentFolder(id) => {
if let Some(path) = self.config.recent_folders().get(id).cloned() {
if let Err(why) = self.config.set_current_folder(path.clone()) {
if let Err(why) = self.config.set_current_folder(Some(path.clone())) {
tracing::error!(?path, ?why, "failed to set current folder");
}
@ -495,6 +508,21 @@ impl Page {
};
}
/// Updates configuration for background image.
fn config_background_entry(&self, output: String, path: PathBuf) -> Option<Entry> {
let scaling_mode = match self.selected_fit {
FIT => ScalingMode::Fit([0.0, 0.0, 0.0]),
STRETCH => ScalingMode::Stretch,
ZOOM => ScalingMode::Zoom,
_ => return None,
};
Entry::new(output, wallpaper::Source::Path(path))
.scaling_mode(scaling_mode)
.rotation_frequency(self.rotation_frequency)
.apply(Some)
}
#[must_use]
pub fn display_image_view(&self) -> cosmic::Element<Message> {
match self.cached_display_handle {
@ -509,6 +537,44 @@ impl Page {
#[allow(clippy::too_many_lines)]
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
match message {
Message::DragColorDialog => {
return cosmic::iced_sctk::commands::window::start_drag_window(self.color_dialog)
}
Message::ColorDialogUpdate(update) => {
let cmd = match update {
ColorPickerUpdate::AppliedColor
| ColorPickerUpdate::Cancel
| ColorPickerUpdate::Reset => {
if let Some(color) = self.color_model.get_applied_color() {
let color = wallpaper::Color::Single([color.r, color.g, color.b]);
if let Err(why) = self.config.add_custom_color(color.clone()) {
tracing::error!(?why, "could not set custom color");
}
self.selection.add_custom_color(color);
}
close_window(self.color_dialog)
}
ColorPickerUpdate::ActionFinished => {
let _res = self
.color_model
.update::<crate::app::Message>(ColorPickerUpdate::AppliedColor);
Command::none()
}
_ => Command::none(),
};
return Command::batch(vec![
cmd,
self.color_model.update::<crate::app::Message>(update),
]);
}
Message::ChangeFolder(mut context) => {
// Reassign custom colors and images to the new context.
std::mem::swap(&mut context, &mut self.selection);
@ -531,14 +597,9 @@ impl Page {
self.select_first_background();
}
Message::ColorAdd(color) => {
if let Err(why) = self.config.add_custom_color(color) {
tracing::error!(?why, "could not set custom color");
}
}
Message::ColorAddDialog => {
unimplemented!();
return get_window(color_picker_window_settings(self.color_dialog));
}
Message::ColorRemove(color) => {
@ -625,7 +686,22 @@ impl Page {
self.background_service_config_update(update.0, update.1, update.2);
self.config_apply();
// Load custom content
// Sync custom colors from config.
for color in self.config.custom_colors() {
self.selection.add_custom_color(color.clone());
}
// Set the default selection if an image was selected.
if let Choice::Background(_) | Choice::Slideshow = self.selection.active {
let folder = self.config.current_folder();
for (id, recent) in self.config.recent_folders().iter().enumerate() {
if recent == folder {
self.categories.selected = Some(Category::RecentFolder(id));
}
}
}
// Load preview images for each custom image stored in the on-disk config.
return cosmic::command::batch(self.config.custom_images().iter().cloned().map(
|path| {
cosmic::command::future(async move {
@ -717,13 +793,13 @@ impl Page {
self.cache_display_image();
}
/// Locate the ID of a background that's already stored in memory
fn background_id_from_path(&self, path: &Path) -> Option<DefaultKey> {
self.selection
.paths
.iter()
.find(|(_id, background)| *background == path)
.map(|(id, _)| id)
pub fn show_color_dialog(&self) -> Element<crate::app::Message> {
color_picker_view(
&self.color_model,
Message::DragColorDialog,
Message::ColorDialogUpdate,
)
.map(|m| crate::app::Message::PageMessage(crate::pages::Message::DesktopWallpaper(m)))
}
}
@ -754,7 +830,7 @@ pub struct Context {
impl Context {
fn add_custom_color(&mut self, color: wallpaper::Color) {
if !self.custom_colors.contains(&color) {
self.add_custom_color(color);
self.custom_colors.push(color);
}
}
@ -923,7 +999,11 @@ pub fn settings() -> Section<crate::pages::Message> {
(fl!("add-image"), Message::ImageAddDialog)
};
button::link(text).on_press(message)
button::link(text)
.trailing_icon(true)
.on_press(message)
.apply(cosmic::widget::container)
.align_y(alignment::Vertical::Bottom)
};
children.push(
@ -985,3 +1065,60 @@ fn entry_directory(current_folder: &Path, entry: &wallpaper::Entry) -> Option<Pa
wallpaper::Source::Color(_) => PathBuf::from(current_folder),
})
}
fn color_picker_window_settings(window_id: window::Id) -> SctkWindowSettings {
SctkWindowSettings {
window_id,
app_id: Some("com.system76.CosmicSettings".to_string()),
title: Some(fl!("color-picker")),
parent: Some(window::Id::MAIN),
autosize: false,
size_limits: layout::Limits::NONE
.min_width(300.0)
.max_width(800.0)
.min_height(520.0)
.max_height(520.0),
size: (300, 520),
resizable: Some(8.0),
client_decorations: true,
transparent: true,
..Default::default()
}
}
// TODO: Reuse with the appearance page
pub fn color_picker_view<Message: Clone + 'static>(
model: &ColorPickerModel,
on_drag: Message,
on_message: fn(ColorPickerUpdate) -> Message,
) -> Element<Message> {
let header = cosmic::widget::header_bar()
.title(fl!("color-picker"))
.on_close(on_message(ColorPickerUpdate::AppliedColor))
.on_drag(on_drag);
let content = cosmic::widget::container(
model
.builder(on_message)
.width(Length::Fixed(254.0))
.height(Length::Fixed(174.0))
.reset_label(fl!("reset-to-default"))
.build(
fl!("recent-colors"),
fl!("copy-to-clipboard"),
fl!("copied-to-clipboard"),
),
)
.width(Length::Fill)
.height(Length::Fill)
.center_x()
.style(cosmic::theme::style::Container::Background);
cosmic::widget::column::with_capacity(2)
.push(header)
.push(content)
.width(Length::Fill)
.height(Length::Fill)
.align_items(cosmic::iced_core::Alignment::Center)
.apply(Element::from)
}

View file

@ -4,7 +4,6 @@ use image::{DynamicImage, ImageBuffer, Rgba, RgbaImage};
use std::{
borrow::Cow,
collections::{hash_map::DefaultHasher, BTreeSet, HashMap},
fs::DirEntry,
hash::{Hash, Hasher},
io::Read,
path::{Path, PathBuf},
@ -97,8 +96,6 @@ pub fn cache_dir() -> Option<PathBuf> {
/// 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, RgbaImage)> {
let cache_dir = cache_dir();
let (tx, rx) = mpsc::channel(1);
tokio::task::spawn(async move {
@ -187,7 +184,7 @@ pub async fn load_image_with_thumbnail(
let _res = tx.send(Some((path, display_thumbnail, selection_thumbnail)));
});
} else {
tx.send(None);
let _res = tx.send(None);
}
rx.await.unwrap_or(None)