feat: 监听全局鼠标释放和异步保存图片

This commit is contained in:
2026-02-28 12:57:19 +08:00
parent ae095946f0
commit 3ed3c12f47

View File

@@ -4,7 +4,8 @@ use iced::padding;
use iced::widget::container; use iced::widget::container;
use iced::widget::{Column, button, column, image, pick_list, row, text}; use iced::widget::{Column, button, column, image, pick_list, row, text};
use iced::{Border, Color, Element, Length, Point, Renderer, Task}; use iced::{Border, Color, Element, Length, Point, Renderer, Task};
use iced_core::color; use iced::{Subscription, color, event, mouse};
use std::thread;
use crate::image_button::image_button; use crate::image_button::image_button;
use crate::mouse_area::mouse_area; use crate::mouse_area::mouse_area;
@@ -54,7 +55,7 @@ enum Message {
Clear, Clear,
SavePNG, SavePNG,
// 内部消息:请求刷新图像 /// 内部消息:请求刷新图像
RefreshImage, RefreshImage,
ClickTool(Tool), ClickTool(Tool),
@@ -63,6 +64,9 @@ enum Message {
Decrement(ConfigOption), Decrement(ConfigOption),
BrushSelected(BrushKind), BrushSelected(BrushKind),
/// 全局鼠标释放
WindowMouseRelease,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@@ -200,7 +204,7 @@ struct PaintApp {
} }
impl PaintApp { impl PaintApp {
// region iced application (new view update) // region iced application
pub fn new() -> Self { pub fn new() -> Self {
let mut canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32); let mut canvas = MSCanvas::new(WIDTH as i32, HEIGHT as i32);
@@ -357,17 +361,10 @@ impl PaintApp {
self.dirty = true; self.dirty = true;
} }
Message::SavePNG => { Message::SavePNG => {
let scale = 4;
let (width, height) = self.canvas.size(); let (width, height) = self.canvas.size();
let pixels = self.canvas.get_pixels_scale(4); let pixels = self.canvas.get_pixels();
save_rgba_to_png( let path = "mspaint.png";
pixels, save_pixels_async(pixels, width as u32, height as u32, 4, path.to_string());
(width * scale) as u32,
(height * scale) as u32,
"mspaint.png",
)
.unwrap();
println!("save png");
} }
Message::RefreshImage => { Message::RefreshImage => {
if self.dirty { if self.dirty {
@@ -393,6 +390,10 @@ impl PaintApp {
Message::BrushSelected(kind) => { Message::BrushSelected(kind) => {
self.brush_selected = Some(kind); self.brush_selected = Some(kind);
} }
Message::WindowMouseRelease => {
// 处理鼠标在 canvas_area 外面释放
self.is_drawing = false;
}
_ => {} _ => {}
} }
@@ -407,6 +408,16 @@ impl PaintApp {
Task::none() Task::none()
} }
pub fn subscription(&self) -> Subscription<Message> {
event::listen().filter_map(|event| match event {
// 只关心鼠标释放事件
iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
Some(Message::WindowMouseRelease)
}
_ => None,
})
}
// endregion // endregion
// region tool update // region tool update
@@ -580,6 +591,7 @@ impl PaintApp {
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application(PaintApp::new, PaintApp::update, PaintApp::view) iced::application(PaintApp::new, PaintApp::update, PaintApp::view)
.theme(Theme::Dark) .theme(Theme::Dark)
.subscription(PaintApp::subscription)
.run() .run()
} }
@@ -596,3 +608,52 @@ fn save_rgba_to_png(
img.save(path)?; img.save(path)?;
Ok(()) Ok(())
} }
fn scale_pixels(pixels: Vec<u8>, width: u32, height: u32, scale: u32) -> Vec<u8> {
if scale <= 1 {
return pixels;
}
let dst_width = width * scale;
let dst_height = height * scale;
let mut dst = vec![0; (dst_width * dst_height * 4) as usize]; // RGBA
for y in 0..height {
for x in 0..width {
// 源像素索引
let src_idx = ((y * width + x) * 4) as usize;
// 源像素颜色
let r = pixels[src_idx];
let g = pixels[src_idx + 1];
let b = pixels[src_idx + 2];
let a = pixels[src_idx + 3];
// 在目标图像中填充 scale×scale 区域
for dy in 0..scale {
for dx in 0..scale {
let dst_x = x * scale + dx;
let dst_y = y * scale + dy;
let dst_idx = ((dst_y * dst_width + dst_x) * 4) as usize;
dst[dst_idx] = r;
dst[dst_idx + 1] = g;
dst[dst_idx + 2] = b;
dst[dst_idx + 3] = a;
}
}
}
}
dst
}
fn save_pixels_async(pixels: Vec<u8>, width: u32, height: u32, scale: u32, path: String) {
// 用 Arc 共享数据(或直接 move 进去)
thread::spawn(move || {
let pixels = scale_pixels(pixels, width, height, scale);
match save_rgba_to_png(pixels, width, height, &path) {
Ok(()) => println!("✅ Image saved to {}", path),
Err(e) => eprintln!("❌ Failed to save image: {}", e),
}
});
}