From 220a886acc844bfc68b5b82e89ca67ed7a0d7618 Mon Sep 17 00:00:00 2001 From: wfx Date: Thu, 15 Jan 2026 20:37:14 +0100 Subject: [PATCH] feat: add set as wallpaper functionality Add ability to set current image as desktop wallpaper with keyboard shortcut 'W' and icon button in Properties panel. Supports COSMIC, GNOME, KDE, XFCE, and tiling window managers via automatic detection and fallback mechanism. Implementation uses wallpaper crate with custom COSMIC config file integration and gsettings/feh fallbacks. --- Cargo.lock | 160 +++++++++++++++++++++++++++++++++----- Cargo.toml | 1 + docs/features.md | 21 +++++ docs/usage.md | 26 +++++++ i18n/en/noctua.ftl | 6 ++ src/app/document/mod.rs | 32 ++++++++ src/app/document/utils.rs | 107 +++++++++++++++++++++++++ src/app/message.rs | 4 + src/app/mod.rs | 3 + src/app/update.rs | 20 +++++ src/app/view/header.rs | 9 ++- src/app/view/panels.rs | 34 +++++++- 12 files changed, 399 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc7d1f5..5043e86 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "arrayvec" version = "0.7.6" @@ -644,7 +650,7 @@ dependencies = [ "aligned", "anyhow", "arg_enum_proc_macro", - "arrayvec", + "arrayvec 0.7.6", "log", "num-rational", "num-traits", @@ -662,7 +668,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8" dependencies = [ "anyhow", - "arrayvec", + "arrayvec 0.7.6", "log", "nom", "num-rational", @@ -675,9 +681,15 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47c8fbc0f831f4519fe8b810b6a7a91410ec83031b8233f730a0480029f6a23f" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.22.1" @@ -738,6 +750,17 @@ dependencies = [ "core2", ] +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -1150,6 +1173,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core-foundation" version = "0.9.4" @@ -1518,6 +1547,17 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users 0.3.5", + "winapi", +] + [[package]] name = "dirs" version = "5.0.1" @@ -1681,6 +1721,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" +[[package]] +name = "enquote" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c36cb11dbde389f4096111698d8b567c0720e3452fd5ac3e6b4e47e1939932" +dependencies = [ + "thiserror 1.0.69", +] + [[package]] name = "enumflags2" version = "0.7.12" @@ -2212,6 +2261,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -2220,7 +2280,7 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", ] [[package]] @@ -2927,7 +2987,7 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3e98b1520e49e252237edc238a39869da9f3241f2ec19dc788c1d24694d1e4" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", ] [[package]] @@ -3183,7 +3243,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "smallvec", ] @@ -3193,7 +3253,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "euclid", "smallvec", ] @@ -3399,7 +3459,7 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e260b6de923e6e47adfedf6243013a7a874684165a6a277594ee3906021b2343" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "euclid", "num-traits", ] @@ -3527,7 +3587,7 @@ checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -3553,7 +3613,7 @@ version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "bit-set", "bitflags 2.10.0", "cfg_aliases 0.1.1", @@ -3644,6 +3704,7 @@ dependencies = [ "rust-embed", "simple_logger", "tokio", + "wallpaper", ] [[package]] @@ -4700,7 +4761,7 @@ dependencies = [ "aligned-vec", "arbitrary", "arg_enum_proc_macro", - "arrayvec", + "arrayvec 0.7.6", "av-scenechange", "av1-grain", "bitstream-io", @@ -4788,6 +4849,12 @@ dependencies = [ "font-types", ] +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -4815,6 +4882,17 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom 0.1.16", + "redox_syscall 0.1.57", + "rust-argon2", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -4927,7 +5005,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ - "base64", + "base64 0.22.1", "bitflags 2.10.0", "serde", "serde_derive", @@ -4940,6 +5018,18 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64 0.13.1", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rust-embed" version = "8.9.0" @@ -4974,6 +5064,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-ini" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac66e816614e124a692b6ac1b8437237a518c9155a3aacab83a373982630c715" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -5500,7 +5596,7 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ba83ebaf2954d31d05d67340fd46cebe99da2b7133b0dd68d70c65473a437b" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "grid", "serde", "slotmap", @@ -5622,7 +5718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" dependencies = [ "arrayref", - "arrayvec", + "arrayvec 0.7.6", "bytemuck", "cfg-if", "log", @@ -5959,7 +6055,7 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b84ea542ae85c715f07b082438a4231c3760539d902e11d093847a0b22963032" dependencies = [ - "base64", + "base64 0.22.1", "data-url", "flate2", "fontdb 0.18.0", @@ -6036,6 +6132,25 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wallpaper" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0547c84bf49b1096b20ce49736b86cd27f8225fc426665d3fba19e71e44c4d46" +dependencies = [ + "dirs 1.0.5", + "enquote", + "rust-ini", + "winapi", + "winreg", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -6306,7 +6421,7 @@ version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d1c4ba43f80542cf63a0a6ed3134629ae73e8ab51e4b765a67f3aa062eb433" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "cfg_aliases 0.1.1", "document-features", "js-sys", @@ -6331,7 +6446,7 @@ version = "22.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "bit-vec", "bitflags 2.10.0", "cfg_aliases 0.1.1", @@ -6357,7 +6472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", - "arrayvec", + "arrayvec 0.7.6", "ash", "bit-set", "bitflags 2.10.0", @@ -6955,6 +7070,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cdb3898397cf7f624c294948669beafaeebc5577d5ec53d0afb76633593597" +dependencies = [ + "winapi", +] + [[package]] name = "wit-bindgen" version = "0.46.0" diff --git a/Cargo.toml b/Cargo.toml index 291859b..bea26a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ dirs = "5.0" image = "0.25.9" clap = { version = "4.5.54", features = ["derive"] } env_logger = "0.11.8" +wallpaper = "3.2" [dependencies.libcosmic] git = "https://github.com/pop-os/libcosmic.git" diff --git a/docs/features.md b/docs/features.md index 05f006e..932eeed 100644 --- a/docs/features.md +++ b/docs/features.md @@ -104,6 +104,10 @@ This document describes the implemented and planned features of Noctua, a modern - **Properties panel**: - Image metadata display - File information + - Action buttons: + - Set as Wallpaper (works with COSMIC, GNOME, KDE, XFCE, and tiling WMs) + - Open With… (planned) + - Show in Folder (planned) - Toggle with `i` key or toolbar button - **Navigation panel** (Left sidebar): - Toggle with `n` key or toolbar button @@ -116,6 +120,23 @@ Full keyboard-driven workflow: - Pan: `Ctrl + ←` `Ctrl + →` `Ctrl + ↑` `Ctrl + ↓` - Transform: `r` `Shift+r` `h` `v` - Panels: `i` `n` +- Actions: `w` (Set as Wallpaper) + +### Desktop Integration + +#### Wallpaper Support (Implemented) +- **Set as Wallpaper**: One-click wallpaper setting with cross-desktop compatibility +- **Supported desktop environments**: + - COSMIC Desktop (direct config file integration) + - GNOME (via gsettings) + - KDE Plasma (via wallpaper crate) + - XFCE (via wallpaper crate) + - Tiling window managers (via feh) +- **Multiple access methods**: + - Keyboard shortcut: `w` + - Icon button in Properties panel + - Tooltip support for discoverability +- **Automatic fallback**: Tries multiple methods until one succeeds ### Configuration diff --git a/docs/usage.md b/docs/usage.md index b1bc2b6..9524677 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -75,6 +75,12 @@ All transformations are lossless and show in real-time. | `i` | Toggle properties | Show/hide the properties panel (metadata)| | `n` | Toggle navigation | Show/hide the navigation sidebar | +### Actions + +| Key | Action | Description | +|:----|:-----------------------|:-----------------------------------------| +| `w` | Set as wallpaper | Set the current image as desktop wallpaper| + ## Mouse Controls ### Zoom @@ -101,6 +107,26 @@ The header toolbar provides quick access to common operations: ### Right Side - **Properties toggle**: Show/hide the metadata panel +## Properties Panel + +The properties panel (toggle with `i`) displays image metadata and provides quick actions: + +### Action Buttons +Located at the top-right of the properties panel: + +- **Set as Wallpaper** (`w` key): Set the current image as your desktop wallpaper + - Works with COSMIC, GNOME, KDE, XFCE, and tiling window managers + - Automatically detects your desktop environment + - Falls back to alternative methods if the primary method fails + +- **Open With** (planned): Open the image with another application + +- **Show in Folder** (planned): Open the containing folder in your file manager + +### Metadata Display +- **File Information**: Name, format, dimensions, file size, color type +- **Camera Information** (if available): Camera model, date taken, exposure settings, GPS location + ## Footer Information The footer displays useful information: diff --git a/i18n/en/noctua.ftl b/i18n/en/noctua.ftl index 63f1c28..f736b39 100644 --- a/i18n/en/noctua.ftl +++ b/i18n/en/noctua.ftl @@ -40,9 +40,15 @@ error-unsupported-format = Unsupported file format. ## Properties panel panel-properties = Properties +panel-actions = Actions meta-section-file = File Information meta-section-exif = Camera Information +## Action buttons +action-set-wallpaper = Set as Wallpaper +action-open-with = Open With… +action-show-in-folder = Show in Folder + ## Basic metadata meta-filename = Name meta-format = Format diff --git a/src/app/document/mod.rs b/src/app/document/mod.rs index 0a08be1..e5faa21 100644 --- a/src/app/document/mod.rs +++ b/src/app/document/mod.rs @@ -110,3 +110,35 @@ impl DocumentContent { } } } + +/// Set an image file as desktop wallpaper. +/// +/// This function attempts multiple methods in order: +/// 1. COSMIC Desktop (direct config file modification) +/// 2. wallpaper crate (KDE, XFCE, Windows, macOS) +/// 3. gsettings (GNOME) +/// 4. feh (tiling window managers) +/// +/// The operation is performed asynchronously and logs success/failure. +pub fn set_as_wallpaper(path: &Path) { + // Canonicalize to absolute path + let abs_path = match path.canonicalize() { + Ok(p) => p, + Err(e) => { + log::error!("Failed to canonicalize path {}: {}", path.display(), e); + return; + } + }; + + // Convert to string + let path_str = match abs_path.to_str() { + Some(s) => s.to_string(), + None => { + log::error!("Invalid UTF-8 in path: {}", abs_path.display()); + return; + } + }; + + // Delegate to utils with concrete string type + utils::set_as_wallpaper(&path_str); +} diff --git a/src/app/document/utils.rs b/src/app/document/utils.rs index 2271d98..63f546a 100644 --- a/src/app/document/utils.rs +++ b/src/app/document/utils.rs @@ -1,2 +1,109 @@ // SPDX-License-Identifier: GPL-3.0-or-later // src/app/document/utils.rs +// +// Utility functions for document operations. + +/// Set an image as desktop wallpaper using multiple fallback methods. +/// +/// Expects an absolute path as string. +pub fn set_as_wallpaper(path_str: &str) { + log::info!("Attempting to set wallpaper: {}", path_str); + + // Method 1: Try COSMIC Desktop (direct config file modification) + if let Some(home) = dirs::home_dir() { + let cosmic_config = home.join(".config/cosmic/com.system76.CosmicBackground/v1/all"); + + if cosmic_config.exists() { + let config_content = format!( + r#"( + output: "all", + source: Path("{}"), + filter_by_theme: true, + rotation_frequency: 300, + filter_method: Lanczos, + scaling_mode: Zoom, + sampling_method: Alphanumeric, +)"#, + path_str + ); + + match std::fs::write(&cosmic_config, config_content) { + Ok(_) => { + log::info!("✓ Wallpaper set via COSMIC config file"); + return; + } + Err(e) => { + log::warn!("Failed to write COSMIC config: {}", e); + } + } + } + } + + // Method 2: Try wallpaper crate (supports KDE, XFCE, Windows, macOS) + match wallpaper::set_from_path(path_str) { + Ok(_) => { + log::info!("✓ Wallpaper set successfully via wallpaper crate"); + return; + } + Err(e) => { + log::warn!("wallpaper crate failed: {}", e); + } + } + + // Method 3: Try GNOME via gsettings + let uri = format!("file://{}", path_str); + log::info!("Trying gsettings with URI: {}", uri); + + match std::process::Command::new("gsettings") + .args(&[ + "set", + "org.gnome.desktop.background", + "picture-uri", + &uri, + ]) + .output() + { + Ok(output) if output.status.success() => { + log::info!("✓ Wallpaper set via gsettings (light mode)"); + + // Also set dark mode wallpaper + let _ = std::process::Command::new("gsettings") + .args(&[ + "set", + "org.gnome.desktop.background", + "picture-uri-dark", + &uri, + ]) + .output(); + return; + } + Ok(output) => { + log::warn!( + "gsettings failed: {}", + String::from_utf8_lossy(&output.stderr) + ); + } + Err(e) => { + log::warn!("gsettings command failed: {}", e); + } + } + + // Method 4: Try feh (common on tiling WMs like i3, sway) + match std::process::Command::new("feh") + .args(&["--bg-scale", path_str]) + .output() + { + Ok(output) if output.status.success() => { + log::info!("✓ Wallpaper set via feh"); + return; + } + Ok(_) => { + log::warn!("feh failed"); + } + Err(_) => { + log::warn!("feh not available"); + } + } + + log::error!("✗ All methods failed to set wallpaper"); +} diff --git a/src/app/message.rs b/src/app/message.rs index 8fc4eae..b020cda 100644 --- a/src/app/message.rs +++ b/src/app/message.rs @@ -70,6 +70,10 @@ pub enum AppMessage { #[allow(dead_code)] RefreshMetadata, + // === Wallpaper === + /// Set current image as wallpaper. + SetAsWallpaper, + // === Errors === /// Display an error message. #[allow(dead_code)] diff --git a/src/app/mod.rs b/src/app/mod.rs index 9f684b2..b9d2c42 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -244,6 +244,9 @@ fn handle_key_press(key: Key, modifiers: Modifiers) -> Option { } Key::Character(ch) if ch.eq_ignore_ascii_case("n") => Some(ToggleNavBar), + // Wallpaper. + Key::Character(ch) if ch.eq_ignore_ascii_case("w") => Some(SetAsWallpaper), + _ => None, } } diff --git a/src/app/update.rs b/src/app/update.rs index 8ec2b56..1201683 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -104,6 +104,11 @@ pub fn update(model: &mut AppModel, msg: AppMessage) { refresh_metadata(model); } + // ===== Wallpaper ================================================================= + AppMessage::SetAsWallpaper => { + set_as_wallpaper(model); + } + // ===== Error handling ============================================================ AppMessage::ShowError(msg) => { model.set_error(msg); @@ -154,3 +159,18 @@ fn current_zoom(model: &AppModel) -> f32 { fn refresh_metadata(model: &mut AppModel) { model.metadata = model.document.as_ref().map(|doc| doc.extract_meta()); } + +/// Set the current image as desktop wallpaper. +fn set_as_wallpaper(model: &mut AppModel) { + let Some(path) = model.current_path.as_ref() else { + model.set_error("No image loaded"); + return; + }; + + let path = path.clone(); + + // Spawn async task to set wallpaper + tokio::spawn(async move { + document::set_as_wallpaper(&path); + }); +} diff --git a/src/app/view/header.rs b/src/app/view/header.rs index 3082992..ed47d8f 100644 --- a/src/app/view/header.rs +++ b/src/app/view/header.rs @@ -52,7 +52,10 @@ pub fn header_start(model: &AppModel) -> Vec> { /// Build the right side of the header bar. pub fn header_end(_model: &AppModel) -> Vec> { - vec![button::icon(icon::from_name("dialog-information-symbolic")) - .on_press(AppMessage::ToggleContextPage(ContextPage::Properties)) - .into()] + vec![ + // Info panel toggle + button::icon(icon::from_name("dialog-information-symbolic")) + .on_press(AppMessage::ToggleContextPage(ContextPage::Properties)) + .into(), + ] } diff --git a/src/app/view/panels.rs b/src/app/view/panels.rs index e5c618a..df5bfc8 100644 --- a/src/app/view/panels.rs +++ b/src/app/view/panels.rs @@ -3,7 +3,8 @@ // // Panel content for COSMIC context drawer. -use cosmic::widget::{column, divider, row, text}; +use cosmic::iced::Length; +use cosmic::widget::{button, column, divider, horizontal_space, icon, row, text}; use cosmic::Element; use crate::app::{AppMessage, AppModel}; @@ -13,8 +14,8 @@ use crate::fl; pub fn properties_panel(model: &AppModel) -> Element<'static, AppMessage> { let mut content = column::with_capacity(16).spacing(8); - // Header. - content = content.push(text::title4(fl!("panel-properties"))); + // Header with action icons + content = content.push(panel_header(model)); // Display document metadata if available. if let Some(ref doc) = model.document { @@ -117,3 +118,30 @@ fn meta_row_small(label: String, value: String) -> Element<'static, AppMessage> .push(text::caption(value)) .into() } + +/// Panel header with title and action icon buttons. +fn panel_header(model: &AppModel) -> Element<'static, AppMessage> { + let has_doc = model.document.is_some(); + + row::with_capacity(5) + .spacing(4) + .align_y(cosmic::iced::Alignment::Center) + .push(text::title4(fl!("panel-properties"))) + .push(horizontal_space().width(Length::Fill)) + .push( + button::icon(icon::from_name("image-x-generic-symbolic")) + .on_press_maybe(has_doc.then_some(AppMessage::SetAsWallpaper)) + .tooltip(fl!("action-set-wallpaper")) + ) + // .push( + // button::icon(icon::from_name("system-run-symbolic")) + // .on_press_maybe(has_doc.then_some(AppMessage::NoOp)) // TODO: Implement + // .tooltip(fl!("action-open-with")) + // ) + // .push( + // button::icon(icon::from_name("system-file-manager-symbolic")) + // .on_press_maybe(has_doc.then_some(AppMessage::NoOp)) // TODO: Implement + // .tooltip(fl!("action-show-in-folder")) + // ) + .into() +}