feat: 实现选择虚线框效果

This commit is contained in:
2026-03-04 00:58:35 +08:00
parent 09d29473bb
commit de76574654
2 changed files with 194 additions and 11 deletions

View File

@@ -145,16 +145,19 @@ impl MSCanvas {
} }
} }
pub fn line_width(&mut self, line_width: i32) { pub fn line_width(&mut self, line_width: i32) -> &mut Self {
self.line_width = line_width; self.line_width = line_width;
self
} }
pub fn fill_color(&mut self, color: MSColor) { pub fn fill_color(&mut self, color: MSColor) -> &mut Self {
self.fill_color = color; self.fill_color = color;
self
} }
pub fn stroke_color(&mut self, color: MSColor) { pub fn stroke_color(&mut self, color: MSColor) -> &mut Self {
self.stroke_color = color; self.stroke_color = color;
self
} }
pub fn size(&self) -> (i32, i32) { pub fn size(&self) -> (i32, i32) {
(self.width, self.height) (self.width, self.height)
@@ -221,6 +224,31 @@ impl MSCanvas {
self.pixels[index + 3], self.pixels[index + 3],
) )
} }
pub fn overlay_pixels(&mut self, canvas: &MSCanvas) -> Vec<u8> {
let mut pixels = canvas.get_pixels();
let width = canvas.width.min(self.width);
let height = canvas.height.min(self.height);
for y in 0..height {
for x in 0..width {
let index = ((y * width + x) * 4) as usize;
// 跳过透明像素
if self.pixels[index + 3] == 0 {
continue;
}
pixels[index] = self.pixels[index];
pixels[index + 1] = self.pixels[index + 1];
pixels[index + 2] = self.pixels[index + 2];
pixels[index + 3] = self.pixels[index + 3];
}
}
pixels
}
} }
#[allow(unused)] #[allow(unused)]
@@ -267,7 +295,26 @@ impl MSCanvas {
self.pixels[index + 3] = self.stroke_color.a; // A self.pixels[index + 3] = self.stroke_color.a; // A
} }
fn fill_row(&mut self, xs: i32, xe: i32, y: i32) { fn draw_column_with_color(&mut self, ys: i32, ye: i32, x: i32, color: MSColor) {
if x < 0 || x >= self.width {
return;
}
let ys = ys.clamp(0, self.height - 1);
let ye = ye.clamp(0, self.height);
for y in ys..ye {
let index = ((y * self.width + x) * 4) as usize;
// 写入 RGBA 数据
// 注意Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
self.pixels[index] = color.r; // R
self.pixels[index + 1] = color.g; // G
self.pixels[index + 2] = color.b; // B
self.pixels[index + 3] = color.a; // A
}
}
fn draw_row_with_color(&mut self, xs: i32, xe: i32, y: i32, color: MSColor) {
if y < 0 || y >= self.height { if y < 0 || y >= self.height {
return; return;
} }
@@ -279,20 +326,36 @@ impl MSCanvas {
// 写入 RGBA 数据 // 写入 RGBA 数据
// 注意Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255 // 注意Color 的 r, g, b, a 是 0.0 - 1.0,需要转为 0 - 255
self.pixels[index] = self.fill_color.r; // R self.pixels[index] = color.r; // R
self.pixels[index + 1] = self.fill_color.g; // G self.pixels[index + 1] = color.g; // G
self.pixels[index + 2] = self.fill_color.b; // B self.pixels[index + 2] = color.b; // B
self.pixels[index + 3] = self.fill_color.a; // A self.pixels[index + 3] = color.a; // A
} }
} }
fn fill_column(&mut self, ys: i32, ye: i32, x: i32) {
self.draw_column_with_color(ys, ye, x, self.fill_color);
}
fn fill_row(&mut self, xs: i32, xe: i32, y: i32) {
self.draw_row_with_color(xs, xe, y, self.fill_color);
}
fn stroke_column(&mut self, ys: i32, ye: i32, x: i32) {
self.draw_column_with_color(ys, ye, x, self.stroke_color);
}
fn stroke_row(&mut self, xs: i32, xe: i32, y: i32) {
self.draw_row_with_color(xs, xe, y, self.stroke_color);
}
pub fn fill_pixels(&mut self, points: Vec<Point>) { pub fn fill_pixels(&mut self, points: Vec<Point>) {
for point in points { for point in points {
self.fill_pixel_at(point); self.fill_pixel_at(point);
} }
} }
pub fn stroke_cross_color(&mut self, point: Point) { pub fn stroke_cross(&mut self, point: Point) {
let Point { x, y } = point; let Point { x, y } = point;
let r = 10; let r = 10;
for dy in -r..=r { for dy in -r..=r {
@@ -875,10 +938,71 @@ impl MSCanvas {
self.stroke_rect(x as i32, y as i32, width as i32, height as i32); self.stroke_rect(x as i32, y as i32, width as i32, height as i32);
} }
pub fn stroke_dashed_rect(
&mut self,
x: i32,
y: i32,
width: i32,
height: i32,
dash_length: i32,
gap_length: i32,
) {
let mut is_dash = true;
// 上下两边边框
for dx in (x..x + width).step_by((dash_length + gap_length) as usize) {
if is_dash {
self.stroke_row(dx, dx + dash_length, y);
self.stroke_row(dx, dx + dash_length, y + height - 1);
}
is_dash = !is_dash;
}
// 左右两边边框
for dy in (y..y + height).step_by((dash_length + gap_length) as usize) {
if is_dash {
self.stroke_column(dy, dy + dash_length, x);
self.stroke_column(dy, dy + dash_length, x + width - 1);
}
is_dash = !is_dash;
}
}
pub fn stroke_dashed_rect1(&mut self, p1: Point, p2: Point, dash_length: i32, gap_length: i32) {
let mut x = p1.x;
let mut y = p1.y;
let mut width = (p2.x - p1.x);
let mut height = (p2.y - p1.y);
if width < 0.0 && height < 0.0 {
x = p2.x;
y = p2.y;
width = (p1.x - p2.x);
height = (p1.y - p2.y);
} else if width < 0.0 {
x += width;
width = -width;
} else if height < 0.0 {
y += height;
height = -height;
}
self.stroke_dashed_rect(
x as i32,
y as i32,
width as i32,
height as i32,
dash_length,
gap_length,
);
}
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.pixels.fill(255); self.pixels.fill(255);
} }
pub fn clear_zero(&mut self) {
self.pixels.fill(0);
}
pub fn clear_row(&mut self, xs: i32, xe: i32, y: i32) { pub fn clear_row(&mut self, xs: i32, xe: i32, y: i32) {
if y < 0 || y >= self.height { if y < 0 || y >= self.height {
return; return;

View File

@@ -301,7 +301,10 @@ struct PaintApp {
tool_states: [bool; Tool::Count as usize], tool_states: [bool; Tool::Count as usize],
tool_selected: Tool, tool_selected: Tool,
/// 画图层
canvas: MSCanvas, canvas: MSCanvas,
/// 显示层
view_canvas: MSCanvas,
/// 是否正在绘制 /// 是否正在绘制
is_drawing: bool, is_drawing: bool,
@@ -336,10 +339,18 @@ impl PaintApp {
let (width, height) = canvas.size(); let (width, height) = canvas.size();
let pixels = canvas.get_pixels(); let pixels = canvas.get_pixels();
let mut view_canvas = MSCanvas::new(width, height);
view_canvas.clear_zero();
view_canvas
.fill_color(MSColor::WHITE)
.stroke_color(MSColor::BLACK)
.line_width(1);
let mut ins = Self { let mut ins = Self {
tool_states: [false; Tool::Count as usize], tool_states: [false; Tool::Count as usize],
tool_selected: Tool::Count, tool_selected: Tool::Count,
canvas, canvas,
view_canvas,
is_drawing: false, is_drawing: false,
begin_point: Point::ORIGIN, begin_point: Point::ORIGIN,
end_point: Point::ORIGIN, end_point: Point::ORIGIN,
@@ -568,6 +579,9 @@ impl PaintApp {
pub fn update(&mut self, message: Message) -> Task<Message> { pub fn update(&mut self, message: Message) -> Task<Message> {
match self.tool_selected { match self.tool_selected {
Tool::Select => {
self.update_with_select(message);
}
Tool::Eraser => { Tool::Eraser => {
self.update_with_eraser(message); self.update_with_eraser(message);
} }
@@ -702,6 +716,33 @@ impl PaintApp {
// region tool update // region tool update
pub fn update_with_select(&mut self, message: Message) {
match message {
Message::MousePressed(pos) => {
self.is_drawing = true;
self.view_canvas.save_pixels();
self.begin_point = pos;
}
Message::MouseReleased(pos) => {
self.is_drawing = false;
self.view_canvas.restore_pixels();
self.view_canvas
.stroke_dashed_rect1(self.begin_point, pos, 1, 1);
self.begin_point = pos;
self.dirty = true;
}
Message::MouseMoved(pos) => {
if self.is_drawing {
self.view_canvas.restore_pixels();
self.view_canvas
.stroke_dashed_rect1(self.begin_point, pos, 3, 1);
self.dirty = true;
}
}
_ => {}
}
}
pub fn update_with_eraser(&mut self, message: Message) { pub fn update_with_eraser(&mut self, message: Message) {
match message { match message {
Message::MousePressed(pos) => { Message::MousePressed(pos) => {
@@ -727,6 +768,20 @@ impl PaintApp {
); );
self.dirty = true; self.dirty = true;
} }
self.view_canvas.clear_zero();
self.view_canvas.clear_rect(
(pos.x as i32) - self.config.eraser_width / 2,
(pos.y as i32) - self.config.eraser_width / 2,
self.config.eraser_width,
self.config.eraser_width,
);
self.view_canvas.stroke_rect(
(pos.x as i32) - self.config.eraser_width / 2,
(pos.y as i32) - self.config.eraser_width / 2,
self.config.eraser_width,
self.config.eraser_width,
);
self.dirty = true;
} }
_ => {} _ => {}
} }
@@ -1145,15 +1200,19 @@ impl PaintApp {
self.is_drawing = false; self.is_drawing = false;
self.control_state = ControlState::Zero; self.control_state = ControlState::Zero;
self.view_canvas.clear_zero();
self.dirty = true;
} }
/// 将原始字节转换为 Iced 的图像句柄 /// 将原始字节转换为 Iced 的图像句柄
fn update_image_handle(&mut self) { fn update_image_handle(&mut self) {
// 克隆数据以避免所有权问题,或者使用 Arc 如果数据量大 // 克隆数据以避免所有权问题,或者使用 Arc 如果数据量大
// 这里为了简单直接 clone对于 800x600 (约 2MB) 来说很快 // 这里为了简单直接 clone对于 800x600 (约 2MB) 来说很快
let data = self.canvas.get_pixels(); // let data = self.canvas.get_pixels();
let data = self.view_canvas.overlay_pixels(&self.canvas);
let (width, height) = self.canvas.size();
self.image_handle = image::Handle::from_rgba(WIDTH, HEIGHT, data); self.image_handle = image::Handle::from_rgba(width as u32, height as u32, data);
} }
} }