feat: 实现喷枪功能
This commit is contained in:
85
Cargo.lock
generated
85
Cargo.lock
generated
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
98
src/paint.rs
98
src/paint.rs
@@ -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) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user