improv: update libcosmic with wallpaper page UI improvements

This commit is contained in:
Michael Murphy 2024-01-29 20:14:57 +01:00 committed by GitHub
parent 039aeb1e74
commit 55dc777bb3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 533 additions and 423 deletions

View file

@ -18,9 +18,8 @@ use crate::subscription::desktop_files;
use crate::widget::{page_title, search_header};
use crate::PageCommands;
use cosmic::app::DbusActivationMessage;
use cosmic::dialog::file_chooser;
use cosmic::iced::Subscription;
use cosmic::widget::row;
use cosmic::widget::{button, row, text_input};
use cosmic::{
app::{Command, Core},
cosmic_config::config_subscription,
@ -30,7 +29,7 @@ use cosmic::{
window, Length,
},
prelude::*,
widget::{column, container, icon, nav_bar, scrollable, search, segmented_button, settings},
widget::{column, container, icon, nav_bar, scrollable, segmented_button, settings},
Element,
};
use cosmic_panel_config::CosmicPanelConfig;
@ -44,7 +43,6 @@ pub struct SettingsApp {
active_page: page::Entity,
config: Config,
core: Core,
file_chooser: Option<(file_chooser::Sender, page::Entity)>,
nav_model: nav_bar::Model,
pages: page::Binder<crate::pages::Message>,
search_active: bool,
@ -70,7 +68,6 @@ impl SettingsApp {
#[derive(Clone, Debug)]
pub enum Message {
DesktopInfo,
FileChooser(FileChooser),
Error(String),
Page(page::Entity),
PageMessage(crate::pages::Message),
@ -79,28 +76,12 @@ pub enum Message {
SearchChanged(String),
SearchClear,
SearchSubmit,
Search(search::Message),
SetWindowTitle,
OpenContextDrawer(Cow<'static, str>),
CloseContextDrawer,
SetTheme(cosmic::theme::Theme),
}
#[derive(Clone, Debug)]
pub enum FileChooser {
Closed,
Init(file_chooser::Sender),
Open {
title: String,
accept_label: String,
include_directories: bool,
modal: bool,
multiple_files: bool,
},
Opened,
Selected(Vec<url::Url>),
}
impl cosmic::Application for SettingsApp {
type Executor = cosmic::executor::single::Executor;
type Flags = crate::Args;
@ -121,7 +102,6 @@ impl cosmic::Application for SettingsApp {
active_page: page::Entity::default(),
config: Config::new(),
core,
file_chooser: None,
nav_model: nav_bar::Model::default(),
pages: page::Binder::default(),
search_active: false,
@ -158,7 +138,7 @@ impl cosmic::Application for SettingsApp {
let mut widgets = Vec::new();
widgets.push(if self.search_active {
cosmic::widget::text_input::search_input("", &self.search_input)
text_input::search_input("", &self.search_input)
.width(Length::Fixed(240.0))
.id(self.search_id.clone())
.on_clear(Message::SearchClear)
@ -166,7 +146,11 @@ impl cosmic::Application for SettingsApp {
.on_submit(Message::SearchSubmit)
.into()
} else {
cosmic::widget::search::button(Message::SearchActivate)
icon::from_name("system-search-symbolic")
.apply(button::icon)
.padding([0, 16])
.on_press(Message::SearchActivate)
.into()
});
widgets
@ -234,53 +218,19 @@ impl cosmic::Application for SettingsApp {
Subscription::batch(vec![
window_break,
desktop_files(0).map(|_| Message::DesktopInfo),
config_subscription(0, "com.system76.CosmicPanel.Panel".into(), 1).map(
|(_, e)| match e {
Ok(config) => Message::PanelConfig(config),
Err((errors, config)) => {
for why in errors {
tracing::error!(?why, "panel config load error");
}
Message::PanelConfig(config)
}
},
),
config_subscription(0, "com.system76.CosmicPanel.Dock".into(), 1).map(
|(_, e)| match e {
Ok(config) => Message::PanelConfig(config),
Err((errors, config)) => {
for why in errors {
tracing::error!(?why, "dock config load error");
}
Message::PanelConfig(config)
}
},
),
file_chooser::subscription(|response| match response {
file_chooser::Message::Closed => Message::FileChooser(FileChooser::Closed),
file_chooser::Message::Opened => Message::FileChooser(FileChooser::Opened),
file_chooser::Message::Selected(files) => {
Message::FileChooser(FileChooser::Selected(files.uris().to_owned()))
config_subscription(0, "com.system76.CosmicPanel.Panel".into(), 1).map(|update| {
for why in update.errors {
tracing::error!(?why, "panel config load error");
}
file_chooser::Message::Err(why) => {
let mut source: &dyn std::error::Error = &why;
let mut string =
format!("open dialog subscription errored\n cause: {source}");
while let Some(new_source) = source.source() {
string.push_str(&format!("\n cause: {new_source}"));
source = new_source;
}
Message::Error(string)
Message::PanelConfig(update.config)
}),
config_subscription(0, "com.system76.CosmicPanel.Dock".into(), 1).map(|update| {
for why in update.errors {
tracing::error!(?why, "dock config load error");
}
file_chooser::Message::Init(sender) => {
Message::FileChooser(FileChooser::Init(sender))
}
Message::PanelConfig(update.config)
}),
])
}
@ -381,50 +331,6 @@ impl cosmic::Application for SettingsApp {
}
},
Message::FileChooser(message) => match message {
FileChooser::Selected(files) => {
return self.pages.page[self.active_page]
.file_chooser(files)
.map(crate::app::Message::PageMessage)
.map(cosmic::app::Message::App)
}
FileChooser::Closed => {}
FileChooser::Opened => {
if let Some((sender, _)) = self.file_chooser.as_mut() {
return sender.response().map(|_| cosmic::app::Message::None);
}
}
FileChooser::Open {
title,
accept_label,
include_directories,
modal,
multiple_files,
} => {
if let Some((sender, entity)) = self.file_chooser.as_mut() {
if let Some(dialog) = file_chooser::open_file() {
*entity = self.active_page;
return dialog
.title(title)
.accept_label(accept_label)
.include_directories(include_directories)
.modal(modal)
.multiple_files(multiple_files)
.create(sender)
.map(|_| cosmic::app::message::none());
}
}
}
FileChooser::Init(sender) => {
self.file_chooser = Some((sender, page::Entity::default()));
}
},
Message::PanelConfig(config) if config.name.to_lowercase().contains("panel") => {
page::update!(
self.pages,
@ -477,7 +383,7 @@ impl cosmic::Application for SettingsApp {
}
}
Message::PanelConfig(_) | Message::Search(_) => {}
Message::PanelConfig(_) => {}
Message::SetTheme(t) => return cosmic::app::command::set_theme(t),

View file

@ -719,7 +719,9 @@ impl Page {
let Ok(path) = f.to_file_path() else {
return Command::none();
};
let Ok(builder) = ron::ser::to_string_pretty(&self.theme_builder, PrettyConfig::default()) else {
let Ok(builder) =
ron::ser::to_string_pretty(&self.theme_builder, PrettyConfig::default())
else {
return Command::none();
};
Command::perform(

View file

@ -386,7 +386,7 @@ impl Page {
/// View for the display configuration section.
pub fn display_view(&self) -> Element<pages::Message> {
let Some(&active_id) = self.display_tabs.active_data::<OutputKey>() else {
return column().into()
return column().into();
};
let active_output = &self.list.outputs[active_id];
@ -467,7 +467,7 @@ impl Page {
for (name, id) in sorted_outputs {
let Some(output) = self.list.outputs.get(id) else {
continue
continue;
};
let inches =

View file

@ -351,7 +351,13 @@ impl Page {
return Command::none();
};
let Some((list, _)) = config.plugins_wings.as_mut() else {
config.plugins_wings = Some((start_list.into_iter().map(|a: Applet| a.id.into()).collect(), Vec::new()));
config.plugins_wings = Some((
start_list
.into_iter()
.map(|a: Applet| a.id.into())
.collect(),
Vec::new(),
));
return Command::none();
};
*list = start_list.into_iter().map(|a| a.id.into()).collect();
@ -361,7 +367,12 @@ impl Page {
return Command::none();
};
let Some(list) = config.plugins_center.as_mut() else {
config.plugins_center = Some(center_list.into_iter().map(|a: Applet| a.id.into()).collect());
config.plugins_center = Some(
center_list
.into_iter()
.map(|a: Applet| a.id.into())
.collect(),
);
return Command::none();
};
*list = center_list.into_iter().map(|a| a.id.into()).collect();
@ -371,7 +382,10 @@ impl Page {
return Command::none();
};
let Some((_, list)) = config.plugins_wings.as_mut() else {
config.plugins_wings = Some((Vec::new(), end_list.into_iter().map(|a: Applet| a.id.into()).collect()));
config.plugins_wings = Some((
Vec::new(),
end_list.into_iter().map(|a: Applet| a.id.into()).collect(),
));
return Command::none();
};
*list = end_list.into_iter().map(|a| a.id.into()).collect();
@ -504,9 +518,7 @@ pub fn lists<
Section::default().view::<P>(move |_binder, page, _section| {
let page = page.inner();
let Some(config) = page.current_config.as_ref() else {
return Element::from(
text(fl!("unknown"))
);
return Element::from(text(fl!("unknown")));
};
let button = button::standard(fl!("add-applet"));

View file

@ -350,7 +350,7 @@ impl PageInner {
pub fn update(&mut self, message: Message) {
let helper = self.config_helper.as_ref().unwrap();
let Some(mut panel_config) = self.panel_config.as_mut() else {
return
return;
};
match message {

View file

@ -5,6 +5,7 @@ mod config;
pub mod widgets;
pub use config::Config;
use url::Url;
use std::{
collections::HashMap,
@ -13,12 +14,15 @@ use std::{
};
use apply::Apply;
use cosmic::widget::{
button, dropdown, list_column, row,
segmented_button::{self, SingleSelectModel},
segmented_selection, settings, text, toggler,
};
use cosmic::{command, Command};
use cosmic::{
dialog::file_chooser,
widget::{
button, dropdown, list_column, row,
segmented_button::{self, SingleSelectModel},
settings, text, toggler, view_switcher,
},
};
use cosmic::{
iced::{wayland::actions::window::SctkWindowSettings, window, Color, Length},
prelude::CollectionWidget,
@ -45,8 +49,7 @@ use slotmap::{DefaultKey, SecondaryMap, SlotMap};
use static_init::dynamic;
const FIT: usize = 0;
const STRETCH: usize = 1;
const ZOOM: usize = 2;
const ZOOM: usize = 1;
const SIMULATED_WIDTH: u16 = 300;
const SIMULATED_HEIGHT: u16 = 169;
@ -63,6 +66,10 @@ pub type Image = ImageBuffer<Rgba<u8>, Vec<u8>>;
/// Messages for the wallpaper view.
#[derive(Clone, Debug)]
pub enum Message {
/// Adds a new wallpaper folder.
AddFolder(Arc<Result<Url, file_chooser::Error>>),
/// Adds a new image file the system wallpaper folder.
AddFile(Arc<Result<Url, file_chooser::Error>>),
/// Selects an option in the category dropdown menu.
ChangeCategory(Category),
/// Changes the displayed images in the wallpaper view.
@ -112,17 +119,6 @@ pub enum Category {
Wallpapers,
}
/// The status of active dialog requests.
#[derive(Copy, Clone)]
enum ActiveDialog {
/// The active dialog is a folder dialog.
AddFolder,
/// The active dialog is an image dialog.
AddImage,
/// No request has been made for a dialog.
None,
}
/// The page struct for the wallpaper view.
pub struct Page {
/// The display that is currently being configured.
@ -130,9 +126,6 @@ pub struct Page {
/// If set to `None`, all displays will have the same wallpaper.
active_output: Option<String>,
/// The state of an active dialog request.
active_dialog: ActiveDialog,
/// Configuration parameters used by the cosmic-bg service.
wallpaper_service_config: wallpaper::Config,
@ -187,67 +180,6 @@ impl page::Page<crate::pages::Message> for Page {
.description(fl!("wallpaper", "desc"))
}
fn file_chooser(&mut self, selections: Vec<url::Url>) -> Command<crate::pages::Message> {
let active_dialog = self.active_dialog;
self.active_dialog = ActiveDialog::None;
if let Some(selection) = selections.first() {
let Ok(path) = selection.to_file_path() else {
tracing::error!(path = selection.path(), "not a valid file path");
return Command::none();
};
match active_dialog {
ActiveDialog::AddFolder => {
if path.is_dir() {
tracing::info!(?path, "opening new folder");
let _res = self.config.set_current_folder(Some(path.clone()));
// Add the selected folder to the recent folders list.
self.add_recent_folder(path.clone());
// Select that folder in the recent folders list.
for (id, recent) in self.config.recent_folders().iter().enumerate() {
if &path == recent {
self.categories.selected = Some(Category::RecentFolder(id));
}
}
// Load the wallpapers from the selected folder into the view.
return cosmic::command::future(async move {
crate::pages::Message::DesktopWallpaper(Message::ChangeFolder(
change_folder(path).await,
))
});
}
}
ActiveDialog::AddImage => {
if path.is_file() {
tracing::info!(?path, "opening custom image");
// Loads a single custom image and its thumbnail for display in the backgrounds view.
return cosmic::command::future(async move {
let result =
wallpaper::load_image_with_thumbnail(&mut Vec::new(), path).await;
crate::pages::Message::DesktopWallpaper(Message::ImageAdd(
result.map(Arc::new),
))
});
}
}
ActiveDialog::None => {
tracing::error!("not actively handling a dialog");
}
}
}
Command::none()
}
fn reload(&mut self, _page: page::Entity) -> Command<crate::pages::Message> {
let current_folder = self.config.current_folder().to_owned();
@ -270,7 +202,6 @@ impl page::AutoBind<crate::pages::Message> for Page {}
impl Default for Page {
fn default() -> Self {
let mut page = Page {
active_dialog: ActiveDialog::None,
active_output: None,
cached_display_handle: None,
categories: {
@ -304,7 +235,7 @@ impl Default for Page {
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")],
fit_options: vec![fl!("fill"), fl!("fit-to-screen")],
outputs: SingleSelectModel::default(),
rotation_frequency: 300,
rotation_options: vec![
@ -380,18 +311,6 @@ impl Page {
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());
@ -417,6 +336,8 @@ impl Page {
&temp_image
}
FIT => image,
_ => return,
};
@ -585,16 +506,19 @@ impl Page {
}
Category::AddFolder => {
self.active_dialog = ActiveDialog::AddFolder;
return cosmic::command::message(crate::Message::FileChooser(
crate::app::FileChooser::Open {
title: fl!("wallpaper", "folder-dialog"),
accept_label: fl!("dialog-add"),
include_directories: true,
modal: false,
multiple_files: false,
},
));
return cosmic::command::future(async {
let dialog_result = file_chooser::open::Dialog::new()
.title(fl!("wallpaper", "folder-dialog"))
.accept_label(fl!("dialog-add"))
.modal(false)
.open_folder()
.await
.map(|response| response.url().to_owned());
let message = Message::AddFolder(Arc::new(dialog_result));
let page_message = crate::pages::Message::DesktopWallpaper(message);
crate::Message::PageMessage(page_message)
});
}
}
@ -628,9 +552,8 @@ impl Page {
/// Updates configuration for wallpaper image.
fn config_wallpaper_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,
FIT => ScalingMode::Fit([0.0, 0.0, 0.0]),
_ => return None,
};
@ -749,16 +672,19 @@ impl Page {
}
Message::ImageAddDialog => {
self.active_dialog = ActiveDialog::AddImage;
return cosmic::command::message(crate::Message::FileChooser(
crate::app::FileChooser::Open {
title: fl!("wallpaper", "image-dialog"),
accept_label: fl!("dialog-add"),
include_directories: false,
modal: false,
multiple_files: false,
},
));
return cosmic::command::future(async {
let dialog_result = file_chooser::open::Dialog::new()
.title(fl!("wallpaper", "image-dialog"))
.accept_label(fl!("dialog-add"))
.modal(false)
.open_file()
.await
.map(|response| response.url().to_owned());
let message = Message::AddFile(Arc::new(dialog_result));
let page_message = crate::pages::Message::DesktopWallpaper(message);
crate::Message::PageMessage(page_message)
});
}
Message::ImageRemove(image) => {
@ -806,6 +732,75 @@ impl Page {
}
}
Message::AddFolder(result) => {
let selection = match Arc::into_inner(result) {
Some(Ok(response)) => response,
Some(Err(why)) => {
// TODO:
return Command::none();
}
None => return Command::none(),
};
let Ok(path) = selection.to_file_path() else {
tracing::error!(path = selection.path(), "not a valid file path");
return Command::none();
};
if path.is_dir() {
tracing::info!(?path, "opening new folder");
let _res = self.config.set_current_folder(Some(path.clone()));
// Add the selected folder to the recent folders list.
self.add_recent_folder(path.clone());
// Select that folder in the recent folders list.
for (id, recent) in self.config.recent_folders().iter().enumerate() {
if &path == recent {
self.categories.selected = Some(Category::RecentFolder(id));
}
}
// Load the wallpapers from the selected folder into the view.
return cosmic::command::future(async move {
let message = Message::ChangeFolder(change_folder(path).await);
let page_message = crate::pages::Message::DesktopWallpaper(message);
crate::Message::PageMessage(page_message)
});
}
}
Message::AddFile(result) => {
let selection = match Arc::into_inner(result) {
Some(Ok(response)) => response,
Some(Err(why)) => {
// TODO:
return Command::none();
}
None => return Command::none(),
};
let Ok(path) = selection.to_file_path() else {
tracing::error!(path = selection.path(), "not a valid file path");
return Command::none();
};
if path.is_file() {
tracing::info!(?path, "opening custom image");
// Loads a single custom image and its thumbnail for display in the backgrounds view.
return cosmic::command::future(async move {
let result =
wallpaper::load_image_with_thumbnail(&mut Vec::new(), path).await;
let message = Message::ImageAdd(result.map(Arc::new));
let page_message = crate::pages::Message::DesktopWallpaper(message);
crate::Message::PageMessage(page_message)
});
}
}
Message::Init(update) => {
self.wallpaper_service_config_update(update.0, update.1, update.2);
self.config_apply();
@ -905,9 +900,8 @@ impl Page {
};
match entry.scaling_mode {
ScalingMode::Zoom | ScalingMode::Stretch => self.selected_fit = ZOOM,
ScalingMode::Fit(_) => self.selected_fit = FIT,
ScalingMode::Stretch => self.selected_fit = STRETCH,
ScalingMode::Zoom => self.selected_fit = ZOOM,
}
match entry.rotation_frequency {
@ -1079,7 +1073,7 @@ pub fn settings() -> Section<crate::pages::Message> {
.height(Length::Fixed(32.0))
.into()
} else {
segmented_selection::horizontal(&page.outputs)
view_switcher::horizontal(&page.outputs)
.on_activate(Message::Output)
.into()
});