refactor: use iced 0.15 rich clipboard
it does use arboard internally, and yet somehow it's able to copy huge (25MB) images. either way, it's nicer to use stuff built into iced instead of dealing with the Clipboard object ourselves.
This commit is contained in:
parent
95a24fa987
commit
6b4af8cd7d
4 changed files with 228 additions and 486 deletions
619
Cargo.lock
generated
619
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@ size = "0.5.0"
|
||||||
zip = "8.6.0"
|
zip = "8.6.0"
|
||||||
|
|
||||||
[dependencies.iced]
|
[dependencies.iced]
|
||||||
version = "0.14.0"
|
version = "0.15.0-dev"
|
||||||
features = [ "image" ]
|
features = [ "image" ]
|
||||||
|
|
||||||
[dependencies.image]
|
[dependencies.image]
|
||||||
|
|
@ -25,6 +25,5 @@ version = "0.12.6"
|
||||||
# I really wish the image crate was named better
|
# I really wish the image crate was named better
|
||||||
features = [ "image" ]
|
features = [ "image" ]
|
||||||
|
|
||||||
[dependencies.arboard]
|
[patch.crates-io]
|
||||||
version = "3.6.1"
|
iced.git = "https://github.com/iced-rs/iced"
|
||||||
features = [ "wayland-data-control" ]
|
|
||||||
|
|
|
||||||
50
src/main.rs
50
src/main.rs
|
|
@ -6,9 +6,8 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use arboard::Clipboard;
|
|
||||||
use iced::{
|
use iced::{
|
||||||
Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, event,
|
Alignment, Color, Element, Length, Renderer, Subscription, Task, Theme, clipboard, event,
|
||||||
keyboard::{self, Key, key},
|
keyboard::{self, Key, key},
|
||||||
theme, widget, window,
|
theme, widget, window,
|
||||||
};
|
};
|
||||||
|
|
@ -29,6 +28,8 @@ enum Message {
|
||||||
|
|
||||||
Yank,
|
Yank,
|
||||||
Put,
|
Put,
|
||||||
|
ImageYanked(Result<(), clipboard::Error>),
|
||||||
|
ImagePut(Result<clipboard::Image, clipboard::Error>),
|
||||||
|
|
||||||
FocusNext,
|
FocusNext,
|
||||||
FocusPrevious,
|
FocusPrevious,
|
||||||
|
|
@ -49,8 +50,6 @@ struct State {
|
||||||
error: Option<Cow<'static, str>>,
|
error: Option<Cow<'static, str>>,
|
||||||
info: Option<String>,
|
info: Option<String>,
|
||||||
infobar_shown: bool,
|
infobar_shown: bool,
|
||||||
|
|
||||||
clipboard: Option<Clipboard>,
|
|
||||||
}
|
}
|
||||||
impl State {
|
impl State {
|
||||||
fn new() -> (Self, Task<Message>) {
|
fn new() -> (Self, Task<Message>) {
|
||||||
|
|
@ -177,46 +176,29 @@ impl State {
|
||||||
|
|
||||||
Message::Yank => {
|
Message::Yank => {
|
||||||
if let Some(image) = self.image.as_ref() {
|
if let Some(image) = self.image.as_ref() {
|
||||||
match match self.clipboard.take() {
|
return clipboard::write(utils::clipboard_image_from_rgbaimage(&image))
|
||||||
Some(c) => Ok(c),
|
.map(Message::ImageYanked);
|
||||||
None => Clipboard::new(),
|
|
||||||
} {
|
|
||||||
Ok(mut clipboard) => {
|
|
||||||
self.error = clipboard
|
|
||||||
.set_image(utils::arboard_from_rgbaimage(image))
|
|
||||||
.map_err(|e| e.to_string().into())
|
|
||||||
.err();
|
|
||||||
self.clipboard = Some(clipboard);
|
|
||||||
}
|
|
||||||
Err(e) => self.error = Some(e.to_string().into()),
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
self.error = Some("no image to yank".into());
|
self.error = Some("no image to yank".into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Message::Put => {
|
Message::Put => return clipboard::read_image().map(Message::ImagePut),
|
||||||
match match self.clipboard.take() {
|
|
||||||
Some(c) => Ok(c),
|
|
||||||
None => Clipboard::new(),
|
|
||||||
} {
|
|
||||||
Ok(mut clipboard) => {
|
|
||||||
let result = clipboard.get_image().map(utils::rgbaimage_from_arboard);
|
|
||||||
|
|
||||||
self.error = result.as_ref().map_err(|e| e.to_string().into()).err();
|
Message::ImageYanked(result) => {
|
||||||
self.clipboard = Some(clipboard);
|
self.error = result.err().map(|e| format!("{e:?}").into());
|
||||||
|
}
|
||||||
|
Message::ImagePut(result) => {
|
||||||
|
self.error = result.as_ref().err().map(|e| format!("{e:?}").into());
|
||||||
|
|
||||||
if let Ok(image) = result {
|
if let Ok(image) = result {
|
||||||
self.info = Some(format!("{}x{}", image.width(), image.height(),));
|
self.info = Some(format!("{}x{}", image.size.width, image.size.height,));
|
||||||
if image.dimensions() < (100, 100) {
|
if (image.size.width, image.size.height) < (100, 100) {
|
||||||
self.image_filter = widget::image::FilterMethod::Nearest;
|
self.image_filter = widget::image::FilterMethod::Nearest;
|
||||||
}
|
}
|
||||||
self.image = Some(image);
|
self.image = Some(utils::rgbaimage_from_clipboard_image(image));
|
||||||
return self.allocate_image();
|
return self.allocate_image();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => self.error = Some(e.to_string().into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Message::FileDropped(path) => match self.load_image(path) {
|
Message::FileDropped(path) => match self.load_image(path) {
|
||||||
Ok(task) => {
|
Ok(task) => {
|
||||||
|
|
@ -333,10 +315,10 @@ fn main() -> Result<(), iced::Error> {
|
||||||
.title(State::title)
|
.title(State::title)
|
||||||
.theme(Theme::custom(
|
.theme(Theme::custom(
|
||||||
"custom",
|
"custom",
|
||||||
theme::Palette {
|
theme::palette::Seed {
|
||||||
background: Color::BLACK,
|
background: Color::BLACK,
|
||||||
text: Color::WHITE,
|
text: Color::WHITE,
|
||||||
..theme::Palette::DARK
|
..theme::palette::Seed::DARK
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.run()
|
.run()
|
||||||
|
|
|
||||||
28
src/utils.rs
28
src/utils.rs
|
|
@ -4,8 +4,8 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use iced::widget;
|
use iced::{clipboard, widget};
|
||||||
use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage};
|
use image::{DynamicImage, EncodableLayout, ImageDecoder, ImageReader, ImageResult, RgbaImage};
|
||||||
use rfd::AsyncFileDialog;
|
use rfd::AsyncFileDialog;
|
||||||
|
|
||||||
pub fn load_image(path: impl AsRef<Path>) -> Result<RgbaImage, String> {
|
pub fn load_image(path: impl AsRef<Path>) -> Result<RgbaImage, String> {
|
||||||
|
|
@ -67,18 +67,18 @@ pub const fn string_from_filter_type(f: widget::image::FilterMethod) -> &'static
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arboard_from_rgbaimage<'a>(image: &'a RgbaImage) -> arboard::ImageData<'a> {
|
pub fn clipboard_image_from_rgbaimage(image: &RgbaImage) -> clipboard::Image {
|
||||||
arboard::ImageData {
|
clipboard::Image {
|
||||||
width: image.width() as usize,
|
size: (image.width(), image.height()).into(),
|
||||||
height: image.height() as usize,
|
// SAFETY: this is also probably a race condition,
|
||||||
bytes: Cow::Borrowed(image.as_ref()),
|
// where if the user switches the image before it's done,
|
||||||
|
// then something will die.
|
||||||
|
//
|
||||||
|
// however, especially with larger images,
|
||||||
|
// copying them is just too expensive.
|
||||||
|
rgba: unsafe { std::mem::transmute::<&[u8], &'static [u8]>(image.as_bytes()) }.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn rgbaimage_from_arboard(image: arboard::ImageData) -> RgbaImage {
|
pub fn rgbaimage_from_clipboard_image(image: clipboard::Image) -> RgbaImage {
|
||||||
RgbaImage::from_raw(
|
RgbaImage::from_raw(image.size.width, image.size.height, image.rgba.into()).unwrap()
|
||||||
image.width as u32,
|
|
||||||
image.height as u32,
|
|
||||||
image.bytes.into_owned(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue