Skip to content

Commit 9d34aa4

Browse files
authored
Implement fill tool (#254)
* Implement fill tool * Add fill tool shortcut * Add getters and setters to styles * Make fill tool act on the topmost layer clicked * Refactor fill operation * Refactor and unify selection tolerance * Add mark_as_dirty function * Fix getter names
1 parent 2b894c5 commit 9d34aa4

File tree

13 files changed

+96
-5
lines changed

13 files changed

+96
-5
lines changed

client/web/src/components/panels/Document.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
113113

114114
<ShelfItem :icon="'TextTool'" title="Text Tool (T)" :active="activeTool === 'Text'" @click="'tool not implemented' || selectTool('Text')" />
115-
<ShelfItem :icon="'FillTool'" title="Fill Tool (F)" :active="activeTool === 'Fill'" @click="'tool not implemented' || selectTool('Fill')" />
115+
<ShelfItem :icon="'FillTool'" title="Fill Tool (F)" :active="activeTool === 'Fill'" @click="selectTool('Fill')" />
116116
<ShelfItem :icon="'GradientTool'" title="Gradient Tool (H)" :active="activeTool === 'Gradient'" @click="'tool not implemented' || selectTool('Gradient')" />
117117

118118
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />

core/document/src/document.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,16 @@ impl Document {
227227
Ok(())
228228
}
229229

230+
fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> {
231+
let mut root = &mut self.root;
232+
root.cache_dirty = true;
233+
for id in path {
234+
root = root.as_folder_mut()?.layer_mut(*id).ok_or(DocumentError::LayerNotFound)?;
235+
root.cache_dirty = true;
236+
}
237+
Ok(())
238+
}
239+
230240
/// Mutate the document by applying the `operation` to it. If the operation necessitates a
231241
/// reaction from the frontend, responses may be returned.
232242
pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
@@ -361,6 +371,12 @@ impl Document {
361371
let path = path.as_slice()[..path.len() - 1].to_vec();
362372
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }])
363373
}
374+
Operation::FillLayer { path, color } => {
375+
let layer = self.layer_mut(path).unwrap();
376+
layer.style.set_fill(layers::style::Fill::new(*color));
377+
self.mark_as_dirty(path)?;
378+
Some(vec![DocumentResponse::DocumentChanged])
379+
}
364380
};
365381
if !matches!(
366382
operation,

core/document/src/layers/mod.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ use crate::LayerId;
2323
pub use folder::Folder;
2424
use serde::{Deserialize, Serialize};
2525

26-
pub const SELECTION_TOLERANCE: f64 = 5.0;
27-
2826
pub trait LayerData {
2927
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle);
3028
fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;

core/document/src/layers/style/mod.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,24 @@ impl PathStyle {
5656
pub fn new(stroke: Option<Stroke>, fill: Option<Fill>) -> Self {
5757
Self { stroke, fill }
5858
}
59+
pub fn fill(&self) -> Option<Fill> {
60+
self.fill
61+
}
62+
pub fn stroke(&self) -> Option<Stroke> {
63+
self.stroke
64+
}
65+
pub fn set_fill(&mut self, fill: Fill) {
66+
self.fill = Some(fill);
67+
}
68+
pub fn set_stroke(&mut self, stroke: Stroke) {
69+
self.stroke = Some(stroke);
70+
}
71+
pub fn clear_fill(&mut self) {
72+
self.fill = None;
73+
}
74+
pub fn clear_stroke(&mut self) {
75+
self.stroke = None;
76+
}
5977
pub fn render(&self) -> String {
6078
format!(
6179
"{}{}",

core/document/src/operation.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::{
2+
color::Color,
23
layers::{style, Layer},
34
LayerId,
45
};
@@ -71,4 +72,8 @@ pub enum Operation {
7172
ToggleVisibility {
7273
path: Vec<LayerId>,
7374
},
75+
FillLayer {
76+
path: Vec<LayerId>,
77+
color: Color,
78+
},
7479
}

core/editor/src/consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ pub const WHEEL_ZOOM_RATE: f64 = 1. / 600.;
1010
pub const MOUSE_ZOOM_RATE: f64 = 1. / 400.;
1111

1212
pub const ROTATE_SNAP_INTERVAL: f64 = 15.;
13+
14+
pub const SELECTION_TOLERANCE: f64 = 5.0;

core/editor/src/input/input_mapper.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,10 @@ impl Default for Mapping {
169169
entry! {action=PenMessage::Confirm, key_down=Rmb},
170170
entry! {action=PenMessage::Confirm, key_down=KeyEscape},
171171
entry! {action=PenMessage::Confirm, key_down=KeyEnter},
172+
// Fill
173+
entry! {action=FillMessage::MouseDown, key_down=Lmb},
172174
// Tool Actions
175+
entry! {action=ToolMessage::SelectTool(ToolType::Fill), key_down=KeyF},
173176
entry! {action=ToolMessage::SelectTool(ToolType::Rectangle), key_down=KeyM},
174177
entry! {action=ToolMessage::SelectTool(ToolType::Ellipse), key_down=KeyE},
175178
entry! {action=ToolMessage::SelectTool(ToolType::Select), key_down=KeyV},

core/editor/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ pub mod message_prelude {
6060
pub use super::tool::tool_messages::*;
6161
pub use super::tool::tools::crop::{CropMessage, CropMessageDiscriminant};
6262
pub use super::tool::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant};
63+
pub use super::tool::tools::fill::{FillMessage, FillMessageDiscriminant};
6364
pub use super::tool::tools::line::{LineMessage, LineMessageDiscriminant};
6465
pub use super::tool::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant};
6566
pub use super::tool::tools::path::{PathMessage, PathMessageDiscriminant};

core/editor/src/tool/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ impl Default for ToolFsmState {
8383
Line => line::Line,
8484
Shape => shape::Shape,
8585
Ellipse => ellipse::Ellipse,
86+
Fill => fill::Fill,
8687
},
8788
},
8889
document_tool_data: DocumentToolData {

core/editor/src/tool/tool_message_handler.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ pub enum ToolMessage {
1717
SwapColors,
1818
ResetColors,
1919
#[child]
20+
Fill(FillMessage),
21+
#[child]
2022
Rectangle(RectangleMessage),
2123
#[child]
2224
Ellipse(EllipseMessage),
@@ -89,6 +91,7 @@ impl MessageHandler<ToolMessage, (&SvgDocument, &InputPreprocessor)> for ToolMes
8991
}
9092
message => {
9193
let tool_type = match message {
94+
Fill(_) => ToolType::Fill,
9295
Rectangle(_) => ToolType::Rectangle,
9396
Ellipse(_) => ToolType::Ellipse,
9497
Shape(_) => ToolType::Shape,

core/editor/src/tool/tools/fill.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use crate::consts::SELECTION_TOLERANCE;
2+
use crate::message_prelude::*;
3+
use crate::tool::ToolActionHandlerData;
4+
use document_core::Operation;
5+
use glam::DVec2;
6+
7+
#[derive(Default)]
8+
pub struct Fill;
9+
10+
#[impl_message(Message, ToolMessage, Fill)]
11+
#[derive(PartialEq, Clone, Debug)]
12+
pub enum FillMessage {
13+
MouseDown,
14+
}
15+
16+
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
17+
fn process_action(&mut self, _action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
18+
let mouse_pos = data.2.mouse.position;
19+
let (x, y) = (mouse_pos.x as f64, mouse_pos.y as f64);
20+
let (point_1, point_2) = (
21+
DVec2::new(x - SELECTION_TOLERANCE, y - SELECTION_TOLERANCE),
22+
DVec2::new(x + SELECTION_TOLERANCE, y + SELECTION_TOLERANCE),
23+
);
24+
25+
let quad = [
26+
DVec2::new(point_1.x, point_1.y),
27+
DVec2::new(point_2.x, point_1.y),
28+
DVec2::new(point_2.x, point_2.y),
29+
DVec2::new(point_1.x, point_2.y),
30+
];
31+
32+
if let Some(path) = data.0.intersects_quad_root(quad).last() {
33+
responses.push_back(
34+
Operation::FillLayer {
35+
path: path.to_vec(),
36+
color: data.1.primary_color,
37+
}
38+
.into(),
39+
);
40+
}
41+
}
42+
advertise_actions!(FillMessageDiscriminant; MouseDown);
43+
}

core/editor/src/tool/tools/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// already implemented
22
pub mod ellipse;
3+
pub mod fill;
34
pub mod line;
45
pub mod pen;
56
pub mod rectangle;

core/editor/src/tool/tools/select.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use document_core::color::Color;
2+
use document_core::layers::style;
23
use document_core::layers::style::Fill;
34
use document_core::layers::style::Stroke;
4-
use document_core::layers::{style, SELECTION_TOLERANCE};
55
use document_core::Operation;
66
use glam::{DAffine2, DVec2};
77

88
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
99
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
10-
use crate::{message_prelude::*, SvgDocument};
10+
use crate::{consts::SELECTION_TOLERANCE, message_prelude::*, SvgDocument};
1111

1212
#[derive(Default)]
1313
pub struct Select {

0 commit comments

Comments
 (0)