From c4c96ac993d1fb900ff9824ac857a08a19b00e62 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Fri, 5 Sep 2025 01:16:04 -0500 Subject: [PATCH] image btn --- Cargo.lock | 210 ++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 3 +- src/fleen_app.rs | 42 +++++++++- src/main.rs | 27 +++++- 4 files changed, 264 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5beb67..7f43131 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -694,6 +694,22 @@ dependencies = [ "libc", ] +[[package]] +name = "clipboard-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4320896621317ada426b440ebb0534c6234d2da9714ac05ae604ea7e2ddc3a5" +dependencies = [ + "clipboard-win", + "image", + "objc2 0.6.2", + "objc2-app-kit 0.3.1", + "objc2-foundation 0.3.1", + "objc2-ui-kit 0.3.1", + "windows 0.59.0", + "x11rb", +] + [[package]] name = "clipboard-win" version = "5.4.1" @@ -701,6 +717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" dependencies = [ "error-code", + "windows-win", ] [[package]] @@ -1133,13 +1150,14 @@ name = "fleen" version = "0.1.0" dependencies = [ "axum", - "bytes", + "clipboard-rs", "eframe", "egui_ltreeview", "markdown", "rfd", "serde", "thiserror 2.0.16", + "tinyrand", "tokio", "toml", ] @@ -1672,6 +1690,8 @@ dependencies = [ "num-traits", "png", "tiff", + "zune-core", + "zune-jpeg", ] [[package]] @@ -2088,10 +2108,10 @@ dependencies = [ "block2 0.5.1", "libc", "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -2102,10 +2122,15 @@ checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.9.2", "block2 0.6.1", + "libc", "objc2 0.6.2", + "objc2-cloud-kit 0.3.1", + "objc2-core-data 0.3.1", "objc2-core-foundation", "objc2-core-graphics", + "objc2-core-image 0.3.1", "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", ] [[package]] @@ -2117,10 +2142,21 @@ dependencies = [ "bitflags 2.9.2", "block2 0.5.1", "objc2 0.5.2", - "objc2-core-location", + "objc2-core-location 0.2.2", "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-cloud-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" +dependencies = [ + "bitflags 2.9.2", + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-contacts" version = "0.2.2" @@ -2144,6 +2180,17 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-data" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" +dependencies = [ + "bitflags 2.9.2", + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-core-foundation" version = "0.3.1" @@ -2180,6 +2227,16 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-core-image" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" +dependencies = [ + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-core-location" version = "0.2.2" @@ -2192,6 +2249,16 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-core-location" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac0f75792558aa9d618443bbb5db7426a7a0b6fddf96903f86ef9ad02e135740" +dependencies = [ + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-encode" version = "4.1.0" @@ -2218,6 +2285,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags 2.9.2", + "block2 0.6.1", + "libc", "objc2 0.6.2", "objc2-core-foundation", ] @@ -2270,6 +2339,18 @@ dependencies = [ "objc2-metal", ] +[[package]] +name = "objc2-quartz-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" +dependencies = [ + "bitflags 2.9.2", + "objc2 0.6.2", + "objc2-core-foundation", + "objc2-foundation 0.3.1", +] + [[package]] name = "objc2-symbols" version = "0.2.2" @@ -2289,16 +2370,36 @@ dependencies = [ "bitflags 2.9.2", "block2 0.5.1", "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", + "objc2-cloud-kit 0.2.2", + "objc2-core-data 0.2.2", + "objc2-core-image 0.2.2", + "objc2-core-location 0.2.2", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", - "objc2-user-notifications", + "objc2-user-notifications 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" +dependencies = [ + "bitflags 2.9.2", + "block2 0.6.1", + "objc2 0.6.2", + "objc2-cloud-kit 0.3.1", + "objc2-core-data 0.3.1", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image 0.3.1", + "objc2-core-location 0.3.1", + "objc2-foundation 0.3.1", + "objc2-quartz-core 0.3.1", + "objc2-user-notifications 0.3.1", ] [[package]] @@ -2321,10 +2422,20 @@ dependencies = [ "bitflags 2.9.2", "block2 0.5.1", "objc2 0.5.2", - "objc2-core-location", + "objc2-core-location 0.2.2", "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-user-notifications" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a3f5ec77a81d9e0c5a0b32159b0cb143d7086165e79708351e02bf37dfc65cd" +dependencies = [ + "objc2 0.6.2", + "objc2-foundation 0.3.1", +] + [[package]] name = "object" version = "0.36.7" @@ -3113,6 +3224,12 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyrand" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ffaad2263779579369d45f65cad0647c860893d27e4543cdcc1e428d07da2c" + [[package]] name = "tinystr" version = "0.8.1" @@ -3785,6 +3902,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" +dependencies = [ + "windows-core 0.59.0", + "windows-targets 0.53.3", +] + [[package]] name = "windows" version = "0.61.3" @@ -3820,6 +3947,19 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" +dependencies = [ + "windows-implement 0.59.0", + "windows-interface 0.59.1", + "windows-result 0.3.4", + "windows-strings 0.3.1", + "windows-targets 0.53.3", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -3855,6 +3995,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-implement" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "windows-implement" version = "0.60.0" @@ -3932,6 +4083,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -4049,6 +4209,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-win" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58e23e33622b3b52f948049acbec9bcc34bf6e26d74176b88941f213c75cf2dc" +dependencies = [ + "error-code", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4255,7 +4424,7 @@ dependencies = [ "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", - "objc2-ui-kit", + "objc2-ui-kit 0.2.2", "orbclient", "percent-encoding", "pin-project", @@ -4563,6 +4732,21 @@ dependencies = [ "syn", ] +[[package]] +name = "zune-core" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" + +[[package]] +name = "zune-jpeg" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "5.6.0" diff --git a/Cargo.toml b/Cargo.toml index e80cc48..165d197 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ markdown = "1.0.0" serde = { version = "1.0.219", features = ["derive"] } axum = "0.8.4" tokio = { version = "1.47.1", features = ["full"] } -bytes = "1.10.1" +tinyrand = "0.5.0" +clipboard-rs = "0.3.0" \ No newline at end of file diff --git a/src/fleen_app.rs b/src/fleen_app.rs index 1ccd5ee..dc41337 100644 --- a/src/fleen_app.rs +++ b/src/fleen_app.rs @@ -2,7 +2,10 @@ use std::cell::RefCell; use std::{fs, io}; use std::path::{Path, PathBuf}; use std::process::Command; +use clipboard_rs::Clipboard; +use clipboard_rs::common::RustImage; use thiserror::Error; +use tinyrand::Rand; use crate::fleen_app::FleenError::{RootDirNonexistence, RootDirPopulated}; use crate::fleen_app::TreeEntry::{CloseDir, Dir}; @@ -17,7 +20,11 @@ pub enum FleenError { #[error("Can't create {0} because it already exists")] FileExists(PathBuf), #[error("Can't create {0}: {1}")] - FileCreate(PathBuf, String) + FileCreate(PathBuf, String), + #[error("Image dir doesn't exist")] + NoImageDir, + #[error("No image on clipboard")] + NoClipboardImage } #[derive(Clone, Debug)] @@ -181,4 +188,37 @@ impl FleenApp { pub fn image_dir_exists(&self) -> bool { self.root.join("images").is_dir() } + + pub fn unique_image_name(&self) -> Result { + if !self.image_dir_exists() { return Err(FleenError::NoImageDir) } + let mut rng = tinyrand::StdRand::default(); + loop { + let fname = format!("image_{}.png", Self::random_name(&mut rng)); + let path = self.root.join("images").join(fname); + if !path.exists() { + return Ok(path) + } + } + } + + fn random_name(rng: &mut tinyrand::StdRand) -> String { + let mut s = String::new(); + let chs = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + for _ in 0..8 { + let n = rng.next_lim_usize(chs.len()); + s.push(chs[n]); + } + s + } + + pub fn paste_image(&self) -> Result { + let c = clipboard_rs::ClipboardContext::new().map_err(|_| FleenError::NoClipboardImage)?; + let img = c.get_image().map_err(|_| FleenError::NoClipboardImage)?; + let target_path = self.unique_image_name()?; + img.save_to_path(target_path.to_str().unwrap()).map_err(|e| FleenError::FileCreate(target_path.clone(), e.to_string()))?; + self.refresh_file_cache(true); + let uri = format!("/images/{}", target_path.file_name().unwrap().to_str().unwrap()); + let _ = c.set_text(format!("![]({})", uri)); + Ok("Image saved!".to_string()) + } } diff --git a/src/main.rs b/src/main.rs index 52068fe..b2b9b9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod server; mod ui_ext; use std::path::{Path, PathBuf}; +use std::time::{Duration, Instant}; use eframe::egui::{Button, Context, Id, RichText}; use eframe::{egui, Frame}; use egui_ltreeview::Action; @@ -26,13 +27,19 @@ enum DialogMode { RenameFile(String) } +struct TempMessage { + created: Instant, + message: String +} + struct FleenUi { app: Option, error: Option, selected_file: Option, dialog_mode: Option, server_handle: Option>, - server_port: String + server_port: String, + image_message: Option } impl Default for FleenUi { @@ -43,6 +50,7 @@ impl Default for FleenUi { selected_file: None, dialog_mode: None, server_handle: None, + image_message: None, server_port: "3000".to_string(), } } @@ -157,9 +165,17 @@ impl FleenUi { } } - ui.add_enabled_ui(self.app.as_ref().unwrap().image_dir_exists(), |ui| { - if ui.add_fill_width(Button::blue("Image from clipboard")).clicked() { + ui.add_enabled_ui(self.app.as_ref().unwrap().image_dir_exists() && self.image_message.is_none(), |ui| { + let label = match &self.image_message { + Some(TempMessage { message, .. }) => message.as_str(), + _ => "Image from clipboard" + }; + if ui.add_fill_width(Button::blue(label)).clicked() { + self.image_message = match self.app.as_ref().unwrap().paste_image() { + Ok(message) => Some(TempMessage { message, created: Instant::now() }), + Err(e) => Some(TempMessage { message: e.to_string(), created: Instant::now() }) + } } }); just_clicked @@ -323,6 +339,11 @@ impl eframe::App for FleenUi { } }); } + + if let Some(TempMessage { created, .. }) = &self.image_message + && *created < Instant::now() - Duration::from_secs(2) { + self.image_message = None + } } }