feat: 实现喷枪功能

This commit is contained in:
2026-02-28 14:32:39 +08:00
parent 3ed3c12f47
commit a9243c498f
4 changed files with 197 additions and 12 deletions

85
Cargo.lock generated
View File

@@ -197,6 +197,17 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "async-fs"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5"
dependencies = [
"async-lock",
"blocking",
"futures-lite",
]
[[package]] [[package]]
name = "async-io" name = "async-io"
version = "2.6.0" version = "2.6.0"
@@ -226,6 +237,17 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-net"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
dependencies = [
"async-io",
"blocking",
"futures-lite",
]
[[package]] [[package]]
name = "async-process" name = "async-process"
version = "2.5.0" version = "2.5.0"
@@ -549,6 +571,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chacha20"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
dependencies = [
"cfg-if",
"cpufeatures",
"rand_core 0.10.0",
]
[[package]] [[package]]
name = "clipboard-win" name = "clipboard-win"
version = "5.4.1" version = "5.4.1"
@@ -727,6 +760,15 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
] ]
[[package]]
name = "cpufeatures"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "crc32fast" name = "crc32fast"
version = "1.5.0" version = "1.5.0"
@@ -1222,6 +1264,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"r-efi", "r-efi",
"rand_core 0.10.0",
"wasip2", "wasip2",
"wasip3", "wasip3",
] ]
@@ -1458,6 +1501,7 @@ dependencies = [
"iced_core", "iced_core",
"log", "log",
"rustc-hash 2.1.1", "rustc-hash 2.1.1",
"smol",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasmtimer", "wasmtimer",
] ]
@@ -1954,6 +1998,7 @@ dependencies = [
"iced", "iced",
"iced_core", "iced_core",
"image", "image",
"rand 0.10.0",
] ]
[[package]] [[package]]
@@ -2795,7 +2840,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [ dependencies = [
"rand_chacha", "rand_chacha",
"rand_core", "rand_core 0.9.5",
]
[[package]]
name = "rand"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
dependencies = [
"chacha20",
"getrandom 0.4.1",
"rand_core 0.10.0",
] ]
[[package]] [[package]]
@@ -2805,7 +2861,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.9.5",
] ]
[[package]] [[package]]
@@ -2817,6 +2873,12 @@ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
] ]
[[package]]
name = "rand_core"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
[[package]] [[package]]
name = "range-alloc" name = "range-alloc"
version = "0.1.4" version = "0.1.4"
@@ -2856,7 +2918,7 @@ dependencies = [
"num-traits", "num-traits",
"paste", "paste",
"profiling", "profiling",
"rand", "rand 0.9.2",
"rand_chacha", "rand_chacha",
"simd_helpers", "simd_helpers",
"thiserror 2.0.18", "thiserror 2.0.18",
@@ -3230,6 +3292,23 @@ dependencies = [
"wayland-backend", "wayland-backend",
] ]
[[package]]
name = "smol"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f"
dependencies = [
"async-channel",
"async-executor",
"async-fs",
"async-io",
"async-lock",
"async-net",
"async-process",
"blocking",
"futures-lite",
]
[[package]] [[package]]
name = "smol_str" name = "smol_str"
version = "0.2.2" version = "0.2.2"

View File

@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
iced = {version = "0.14.0", features = ["advanced", "image"]} iced = {version = "0.14.0", features = ["advanced", "image", "smol"]}
iced_core = "0.14.0" iced_core = "0.14.0"
image = "0.25.9" image = "0.25.9"
rand = "0.10.0"

View File

@@ -1,4 +1,5 @@
use iced::Point; use iced::Point;
use rand::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub struct MSColor { pub struct MSColor {
@@ -540,7 +541,6 @@ impl MSCanvas {
} }
pub fn stroke_rect1(&mut self, p1: Point, p2: Point) { pub fn stroke_rect1(&mut self, p1: Point, p2: Point) {
self.draw_cross_color(p1, MSColor::new(255, 0, 0, 255));
let mut x = p1.x; let mut x = p1.x;
let mut y = p1.y; let mut y = p1.y;
let mut width = (p2.x - p1.x); let mut width = (p2.x - p1.x);
@@ -582,4 +582,21 @@ impl MSCanvas {
self.pixels[index + 3] = 255; // A self.pixels[index + 3] = 255; // A
} }
} }
/// 实现喷枪效果的函数
pub fn spray_paint(&mut self, center: Point, radius: i32, density: u32) {
let mut rng = rand::rng();
for _ in 0..density {
// 在给定半径内随机产生偏移量
let offset_x = rng.random_range(-radius..=radius);
let offset_y = rng.random_range(-radius..=radius);
// 确保我们只在圆形区域内绘制,而非整个正方形区域
if (offset_x * offset_x + offset_y * offset_y) <= (radius * radius) {
let point = Point::new(center.x + offset_x as f32, center.y + offset_y as f32);
self.draw_pixel_at(point);
}
}
}
} }

View File

@@ -1,6 +1,7 @@
use ::image::{ImageBuffer, ImageError, Rgba}; use ::image::{ImageBuffer, ImageError, Rgba};
use iced::Theme; use iced::Theme;
use iced::padding; use iced::padding;
use iced::time::{self, Instant, milliseconds};
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};
@@ -67,9 +68,11 @@ enum Message {
/// 全局鼠标释放 /// 全局鼠标释放
WindowMouseRelease, WindowMouseRelease,
Tick(Instant),
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Tool { enum Tool {
FreeFormSelect, FreeFormSelect,
Select, Select,
@@ -143,12 +146,16 @@ impl From<Tool> for usize {
pub enum ConfigOption { pub enum ConfigOption {
EraserWidth, EraserWidth,
LineWidth, LineWidth,
AirbrushRadius,
AirbrushDensity,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct Config { struct Config {
eraser_width: i32, eraser_width: i32,
line_width: i32, line_width: i32,
airbrush_radius: i32,
airbrush_density: i32,
} }
impl Default for Config { impl Default for Config {
@@ -161,6 +168,8 @@ impl Config {
const DEFAULT: Config = Config { const DEFAULT: Config = Config {
line_width: 1, line_width: 1,
eraser_width: 4, eraser_width: 4,
airbrush_radius: 4,
airbrush_density: 16,
}; };
fn incr(&mut self, option: ConfigOption, step: i32) { fn incr(&mut self, option: ConfigOption, step: i32) {
match option { match option {
@@ -176,6 +185,18 @@ impl Config {
self.line_width = Self::DEFAULT.line_width; self.line_width = Self::DEFAULT.line_width;
} }
} }
ConfigOption::AirbrushRadius => {
self.airbrush_radius += step;
if self.airbrush_radius < Self::DEFAULT.airbrush_radius {
self.airbrush_radius = Self::DEFAULT.airbrush_radius;
}
}
ConfigOption::AirbrushDensity => {
self.airbrush_density += step;
if self.airbrush_density < Self::DEFAULT.airbrush_density {
self.airbrush_density = Self::DEFAULT.airbrush_density;
}
}
} }
} }
} }
@@ -303,7 +324,8 @@ impl PaintApp {
text(self.config.eraser_width).size(20).center(), text(self.config.eraser_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::EraserWidth)), button("-").on_press(Message::Decrement(ConfigOption::EraserWidth)),
], ],
], ]
.padding(padding::right(5)),
column![ column![
text("Line Width"), text("Line Width"),
row![ row![
@@ -311,13 +333,32 @@ impl PaintApp {
text(self.config.line_width).size(20).center(), text(self.config.line_width).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::LineWidth)), button("-").on_press(Message::Decrement(ConfigOption::LineWidth)),
], ],
], ]
.padding(padding::right(5)),
pick_list( pick_list(
&BrushKind::ALL[..], &BrushKind::ALL[..],
self.brush_selected, self.brush_selected,
Message::BrushSelected, Message::BrushSelected,
) )
.placeholder("Brush..."), .placeholder("Brush..."),
column![
text("Airbrush Radius"),
row![
button("+").on_press(Message::Increment(ConfigOption::AirbrushRadius)),
text(self.config.airbrush_radius).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::AirbrushRadius)),
],
]
.padding(padding::right(5)),
column![
text("Airbrush Density"),
row![
button("+").on_press(Message::Increment(ConfigOption::AirbrushDensity)),
text(self.config.airbrush_density).size(20).center(),
button("-").on_press(Message::Decrement(ConfigOption::AirbrushDensity)),
],
]
.padding(padding::right(5)),
]; ];
debug_area = debug_area.padding(padding::top(10).left(5).bottom(10)); debug_area = debug_area.padding(padding::top(10).left(5).bottom(10));
@@ -345,6 +386,9 @@ impl PaintApp {
Tool::Brush => { Tool::Brush => {
self.update_with_brush(message); self.update_with_brush(message);
} }
Tool::Airbrush => {
self.update_with_airbrush(message);
}
Tool::Line => { Tool::Line => {
self.update_with_line(message); self.update_with_line(message);
} }
@@ -394,6 +438,16 @@ impl PaintApp {
// 处理鼠标在 canvas_area 外面释放 // 处理鼠标在 canvas_area 外面释放
self.is_drawing = false; self.is_drawing = false;
} }
Message::Tick(_) => {
if self.is_drawing && self.tool_selected == Tool::Airbrush {
self.canvas.spray_paint(
self.begin_point,
self.config.airbrush_radius,
self.config.airbrush_density as u32,
);
self.dirty = true;
}
}
_ => {} _ => {}
} }
@@ -409,13 +463,15 @@ impl PaintApp {
} }
pub fn subscription(&self) -> Subscription<Message> { pub fn subscription(&self) -> Subscription<Message> {
event::listen().filter_map(|event| match event { let ev = event::listen().filter_map(|event| match event {
// 只关心鼠标释放事件 // 只关心鼠标释放事件
iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => { iced::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
Some(Message::WindowMouseRelease) Some(Message::WindowMouseRelease)
} }
_ => None, _ => None,
}) });
let tick = time::every(milliseconds(50)).map(Message::Tick);
return Subscription::batch(vec![ev, tick]);
} }
// endregion // endregion
@@ -468,6 +524,7 @@ impl PaintApp {
self.is_drawing = true; self.is_drawing = true;
self.canvas.draw_pixel_at(pos); self.canvas.draw_pixel_at(pos);
self.begin_point = pos; self.begin_point = pos;
self.dirty = true;
} }
Message::MouseReleased(pos) => { Message::MouseReleased(pos) => {
self.is_drawing = false; self.is_drawing = false;
@@ -516,6 +573,37 @@ impl PaintApp {
} }
} }
pub fn update_with_airbrush(&mut self, message: Message) {
match message {
Message::MousePressed(pos) => {
self.is_drawing = true;
self.canvas.spray_paint(
pos,
self.config.airbrush_radius,
self.config.airbrush_density as u32,
);
self.begin_point = pos;
self.dirty = true;
}
Message::MouseReleased(pos) => {
self.is_drawing = false;
self.begin_point = pos;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
self.canvas.spray_paint(
pos,
self.config.airbrush_radius,
self.config.airbrush_density as u32,
);
self.begin_point = pos;
self.dirty = true;
}
}
_ => {}
}
}
pub fn update_with_line(&mut self, message: Message) { pub fn update_with_line(&mut self, message: Message) {
match message { match message {
Message::MousePressed(pos) => { Message::MousePressed(pos) => {