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
56
src/main.rs
56
src/main.rs
|
|
@ -6,9 +6,8 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use arboard::Clipboard;
|
||||
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},
|
||||
theme, widget, window,
|
||||
};
|
||||
|
|
@ -29,6 +28,8 @@ enum Message {
|
|||
|
||||
Yank,
|
||||
Put,
|
||||
ImageYanked(Result<(), clipboard::Error>),
|
||||
ImagePut(Result<clipboard::Image, clipboard::Error>),
|
||||
|
||||
FocusNext,
|
||||
FocusPrevious,
|
||||
|
|
@ -49,8 +50,6 @@ struct State {
|
|||
error: Option<Cow<'static, str>>,
|
||||
info: Option<String>,
|
||||
infobar_shown: bool,
|
||||
|
||||
clipboard: Option<Clipboard>,
|
||||
}
|
||||
impl State {
|
||||
fn new() -> (Self, Task<Message>) {
|
||||
|
|
@ -177,44 +176,27 @@ impl State {
|
|||
|
||||
Message::Yank => {
|
||||
if let Some(image) = self.image.as_ref() {
|
||||
match match self.clipboard.take() {
|
||||
Some(c) => Ok(c),
|
||||
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()),
|
||||
}
|
||||
return clipboard::write(utils::clipboard_image_from_rgbaimage(&image))
|
||||
.map(Message::ImageYanked);
|
||||
} else {
|
||||
self.error = Some("no image to yank".into());
|
||||
}
|
||||
}
|
||||
Message::Put => {
|
||||
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);
|
||||
Message::Put => return clipboard::read_image().map(Message::ImagePut),
|
||||
|
||||
self.error = result.as_ref().map_err(|e| e.to_string().into()).err();
|
||||
self.clipboard = Some(clipboard);
|
||||
Message::ImageYanked(result) => {
|
||||
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 {
|
||||
self.info = Some(format!("{}x{}", image.width(), image.height(),));
|
||||
if image.dimensions() < (100, 100) {
|
||||
self.image_filter = widget::image::FilterMethod::Nearest;
|
||||
}
|
||||
self.image = Some(image);
|
||||
return self.allocate_image();
|
||||
}
|
||||
if let Ok(image) = result {
|
||||
self.info = Some(format!("{}x{}", image.size.width, image.size.height,));
|
||||
if (image.size.width, image.size.height) < (100, 100) {
|
||||
self.image_filter = widget::image::FilterMethod::Nearest;
|
||||
}
|
||||
Err(e) => self.error = Some(e.to_string().into()),
|
||||
self.image = Some(utils::rgbaimage_from_clipboard_image(image));
|
||||
return self.allocate_image();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -333,10 +315,10 @@ fn main() -> Result<(), iced::Error> {
|
|||
.title(State::title)
|
||||
.theme(Theme::custom(
|
||||
"custom",
|
||||
theme::Palette {
|
||||
theme::palette::Seed {
|
||||
background: Color::BLACK,
|
||||
text: Color::WHITE,
|
||||
..theme::Palette::DARK
|
||||
..theme::palette::Seed::DARK
|
||||
},
|
||||
))
|
||||
.run()
|
||||
|
|
|
|||
28
src/utils.rs
28
src/utils.rs
|
|
@ -4,8 +4,8 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use iced::widget;
|
||||
use image::{DynamicImage, ImageDecoder, ImageReader, ImageResult, RgbaImage};
|
||||
use iced::{clipboard, widget};
|
||||
use image::{DynamicImage, EncodableLayout, ImageDecoder, ImageReader, ImageResult, RgbaImage};
|
||||
use rfd::AsyncFileDialog;
|
||||
|
||||
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> {
|
||||
arboard::ImageData {
|
||||
width: image.width() as usize,
|
||||
height: image.height() as usize,
|
||||
bytes: Cow::Borrowed(image.as_ref()),
|
||||
pub fn clipboard_image_from_rgbaimage(image: &RgbaImage) -> clipboard::Image {
|
||||
clipboard::Image {
|
||||
size: (image.width(), image.height()).into(),
|
||||
// SAFETY: this is also probably a race condition,
|
||||
// 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 {
|
||||
RgbaImage::from_raw(
|
||||
image.width as u32,
|
||||
image.height as u32,
|
||||
image.bytes.into_owned(),
|
||||
)
|
||||
.unwrap()
|
||||
pub fn rgbaimage_from_clipboard_image(image: clipboard::Image) -> RgbaImage {
|
||||
RgbaImage::from_raw(image.size.width, image.size.height, image.rgba.into()).unwrap()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue