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
This commit is contained in:
parent
57b8ee0e86
commit
363b9c7ffa
|
|
@ -112,7 +112,7 @@
|
||||||
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
||||||
|
|
||||||
<ShelfItem :icon="'TextTool'" title="Text Tool (T)" :active="activeTool === 'Text'" @click="'tool not implemented' || selectTool('Text')" />
|
<ShelfItem :icon="'TextTool'" title="Text Tool (T)" :active="activeTool === 'Text'" @click="'tool not implemented' || selectTool('Text')" />
|
||||||
<ShelfItem :icon="'FillTool'" title="Fill Tool (F)" :active="activeTool === 'Fill'" @click="'tool not implemented' || selectTool('Fill')" />
|
<ShelfItem :icon="'FillTool'" title="Fill Tool (F)" :active="activeTool === 'Fill'" @click="selectTool('Fill')" />
|
||||||
<ShelfItem :icon="'GradientTool'" title="Gradient Tool (H)" :active="activeTool === 'Gradient'" @click="'tool not implemented' || selectTool('Gradient')" />
|
<ShelfItem :icon="'GradientTool'" title="Gradient Tool (H)" :active="activeTool === 'Gradient'" @click="'tool not implemented' || selectTool('Gradient')" />
|
||||||
|
|
||||||
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
<Separator :type="SeparatorType.Section" :direction="SeparatorDirection.Vertical" />
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,16 @@ impl Document {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mark_as_dirty(&mut self, path: &[LayerId]) -> Result<(), DocumentError> {
|
||||||
|
let mut root = &mut self.root;
|
||||||
|
root.cache_dirty = true;
|
||||||
|
for id in path {
|
||||||
|
root = root.as_folder_mut()?.layer_mut(*id).ok_or(DocumentError::LayerNotFound)?;
|
||||||
|
root.cache_dirty = true;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Mutate the document by applying the `operation` to it. If the operation necessitates a
|
/// Mutate the document by applying the `operation` to it. If the operation necessitates a
|
||||||
/// reaction from the frontend, responses may be returned.
|
/// reaction from the frontend, responses may be returned.
|
||||||
pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
|
pub fn handle_operation(&mut self, operation: Operation) -> Result<Option<Vec<DocumentResponse>>, DocumentError> {
|
||||||
|
|
@ -361,6 +371,12 @@ impl Document {
|
||||||
let path = path.as_slice()[..path.len() - 1].to_vec();
|
let path = path.as_slice()[..path.len() - 1].to_vec();
|
||||||
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }])
|
Some(vec![DocumentResponse::DocumentChanged, DocumentResponse::FolderChanged { path }])
|
||||||
}
|
}
|
||||||
|
Operation::FillLayer { path, color } => {
|
||||||
|
let layer = self.layer_mut(path).unwrap();
|
||||||
|
layer.style.set_fill(layers::style::Fill::new(*color));
|
||||||
|
self.mark_as_dirty(path)?;
|
||||||
|
Some(vec![DocumentResponse::DocumentChanged])
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if !matches!(
|
if !matches!(
|
||||||
operation,
|
operation,
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,6 @@ use crate::LayerId;
|
||||||
pub use folder::Folder;
|
pub use folder::Folder;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub const SELECTION_TOLERANCE: f64 = 5.0;
|
|
||||||
|
|
||||||
pub trait LayerData {
|
pub trait LayerData {
|
||||||
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle);
|
fn render(&mut self, svg: &mut String, transform: glam::DAffine2, style: style::PathStyle);
|
||||||
fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
|
fn to_kurbo_path(&self, transform: glam::DAffine2, style: style::PathStyle) -> BezPath;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,24 @@ impl PathStyle {
|
||||||
pub fn new(stroke: Option<Stroke>, fill: Option<Fill>) -> Self {
|
pub fn new(stroke: Option<Stroke>, fill: Option<Fill>) -> Self {
|
||||||
Self { stroke, fill }
|
Self { stroke, fill }
|
||||||
}
|
}
|
||||||
|
pub fn fill(&self) -> Option<Fill> {
|
||||||
|
self.fill
|
||||||
|
}
|
||||||
|
pub fn stroke(&self) -> Option<Stroke> {
|
||||||
|
self.stroke
|
||||||
|
}
|
||||||
|
pub fn set_fill(&mut self, fill: Fill) {
|
||||||
|
self.fill = Some(fill);
|
||||||
|
}
|
||||||
|
pub fn set_stroke(&mut self, stroke: Stroke) {
|
||||||
|
self.stroke = Some(stroke);
|
||||||
|
}
|
||||||
|
pub fn clear_fill(&mut self) {
|
||||||
|
self.fill = None;
|
||||||
|
}
|
||||||
|
pub fn clear_stroke(&mut self) {
|
||||||
|
self.stroke = None;
|
||||||
|
}
|
||||||
pub fn render(&self) -> String {
|
pub fn render(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
color::Color,
|
||||||
layers::{style, Layer},
|
layers::{style, Layer},
|
||||||
LayerId,
|
LayerId,
|
||||||
};
|
};
|
||||||
|
|
@ -71,4 +72,8 @@ pub enum Operation {
|
||||||
ToggleVisibility {
|
ToggleVisibility {
|
||||||
path: Vec<LayerId>,
|
path: Vec<LayerId>,
|
||||||
},
|
},
|
||||||
|
FillLayer {
|
||||||
|
path: Vec<LayerId>,
|
||||||
|
color: Color,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,5 @@ pub const WHEEL_ZOOM_RATE: f64 = 1. / 600.;
|
||||||
pub const MOUSE_ZOOM_RATE: f64 = 1. / 400.;
|
pub const MOUSE_ZOOM_RATE: f64 = 1. / 400.;
|
||||||
|
|
||||||
pub const ROTATE_SNAP_INTERVAL: f64 = 15.;
|
pub const ROTATE_SNAP_INTERVAL: f64 = 15.;
|
||||||
|
|
||||||
|
pub const SELECTION_TOLERANCE: f64 = 5.0;
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,10 @@ impl Default for Mapping {
|
||||||
entry! {action=PenMessage::Confirm, key_down=Rmb},
|
entry! {action=PenMessage::Confirm, key_down=Rmb},
|
||||||
entry! {action=PenMessage::Confirm, key_down=KeyEscape},
|
entry! {action=PenMessage::Confirm, key_down=KeyEscape},
|
||||||
entry! {action=PenMessage::Confirm, key_down=KeyEnter},
|
entry! {action=PenMessage::Confirm, key_down=KeyEnter},
|
||||||
|
// Fill
|
||||||
|
entry! {action=FillMessage::MouseDown, key_down=Lmb},
|
||||||
// Tool Actions
|
// Tool Actions
|
||||||
|
entry! {action=ToolMessage::SelectTool(ToolType::Fill), key_down=KeyF},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Rectangle), key_down=KeyM},
|
entry! {action=ToolMessage::SelectTool(ToolType::Rectangle), key_down=KeyM},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Ellipse), key_down=KeyE},
|
entry! {action=ToolMessage::SelectTool(ToolType::Ellipse), key_down=KeyE},
|
||||||
entry! {action=ToolMessage::SelectTool(ToolType::Select), key_down=KeyV},
|
entry! {action=ToolMessage::SelectTool(ToolType::Select), key_down=KeyV},
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ pub mod message_prelude {
|
||||||
pub use super::tool::tool_messages::*;
|
pub use super::tool::tool_messages::*;
|
||||||
pub use super::tool::tools::crop::{CropMessage, CropMessageDiscriminant};
|
pub use super::tool::tools::crop::{CropMessage, CropMessageDiscriminant};
|
||||||
pub use super::tool::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant};
|
pub use super::tool::tools::eyedropper::{EyedropperMessage, EyedropperMessageDiscriminant};
|
||||||
|
pub use super::tool::tools::fill::{FillMessage, FillMessageDiscriminant};
|
||||||
pub use super::tool::tools::line::{LineMessage, LineMessageDiscriminant};
|
pub use super::tool::tools::line::{LineMessage, LineMessageDiscriminant};
|
||||||
pub use super::tool::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant};
|
pub use super::tool::tools::navigate::{NavigateMessage, NavigateMessageDiscriminant};
|
||||||
pub use super::tool::tools::path::{PathMessage, PathMessageDiscriminant};
|
pub use super::tool::tools::path::{PathMessage, PathMessageDiscriminant};
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ impl Default for ToolFsmState {
|
||||||
Line => line::Line,
|
Line => line::Line,
|
||||||
Shape => shape::Shape,
|
Shape => shape::Shape,
|
||||||
Ellipse => ellipse::Ellipse,
|
Ellipse => ellipse::Ellipse,
|
||||||
|
Fill => fill::Fill,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
document_tool_data: DocumentToolData {
|
document_tool_data: DocumentToolData {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ pub enum ToolMessage {
|
||||||
SwapColors,
|
SwapColors,
|
||||||
ResetColors,
|
ResetColors,
|
||||||
#[child]
|
#[child]
|
||||||
|
Fill(FillMessage),
|
||||||
|
#[child]
|
||||||
Rectangle(RectangleMessage),
|
Rectangle(RectangleMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Ellipse(EllipseMessage),
|
Ellipse(EllipseMessage),
|
||||||
|
|
@ -89,6 +91,7 @@ impl MessageHandler<ToolMessage, (&SvgDocument, &InputPreprocessor)> for ToolMes
|
||||||
}
|
}
|
||||||
message => {
|
message => {
|
||||||
let tool_type = match message {
|
let tool_type = match message {
|
||||||
|
Fill(_) => ToolType::Fill,
|
||||||
Rectangle(_) => ToolType::Rectangle,
|
Rectangle(_) => ToolType::Rectangle,
|
||||||
Ellipse(_) => ToolType::Ellipse,
|
Ellipse(_) => ToolType::Ellipse,
|
||||||
Shape(_) => ToolType::Shape,
|
Shape(_) => ToolType::Shape,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
use crate::consts::SELECTION_TOLERANCE;
|
||||||
|
use crate::message_prelude::*;
|
||||||
|
use crate::tool::ToolActionHandlerData;
|
||||||
|
use document_core::Operation;
|
||||||
|
use glam::DVec2;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Fill;
|
||||||
|
|
||||||
|
#[impl_message(Message, ToolMessage, Fill)]
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
|
pub enum FillMessage {
|
||||||
|
MouseDown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MessageHandler<ToolMessage, ToolActionHandlerData<'a>> for Fill {
|
||||||
|
fn process_action(&mut self, _action: ToolMessage, data: ToolActionHandlerData<'a>, responses: &mut VecDeque<Message>) {
|
||||||
|
let mouse_pos = data.2.mouse.position;
|
||||||
|
let (x, y) = (mouse_pos.x as f64, mouse_pos.y as f64);
|
||||||
|
let (point_1, point_2) = (
|
||||||
|
DVec2::new(x - SELECTION_TOLERANCE, y - SELECTION_TOLERANCE),
|
||||||
|
DVec2::new(x + SELECTION_TOLERANCE, y + SELECTION_TOLERANCE),
|
||||||
|
);
|
||||||
|
|
||||||
|
let quad = [
|
||||||
|
DVec2::new(point_1.x, point_1.y),
|
||||||
|
DVec2::new(point_2.x, point_1.y),
|
||||||
|
DVec2::new(point_2.x, point_2.y),
|
||||||
|
DVec2::new(point_1.x, point_2.y),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(path) = data.0.intersects_quad_root(quad).last() {
|
||||||
|
responses.push_back(
|
||||||
|
Operation::FillLayer {
|
||||||
|
path: path.to_vec(),
|
||||||
|
color: data.1.primary_color,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
advertise_actions!(FillMessageDiscriminant; MouseDown);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// already implemented
|
// already implemented
|
||||||
pub mod ellipse;
|
pub mod ellipse;
|
||||||
|
pub mod fill;
|
||||||
pub mod line;
|
pub mod line;
|
||||||
pub mod pen;
|
pub mod pen;
|
||||||
pub mod rectangle;
|
pub mod rectangle;
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use document_core::color::Color;
|
use document_core::color::Color;
|
||||||
|
use document_core::layers::style;
|
||||||
use document_core::layers::style::Fill;
|
use document_core::layers::style::Fill;
|
||||||
use document_core::layers::style::Stroke;
|
use document_core::layers::style::Stroke;
|
||||||
use document_core::layers::{style, SELECTION_TOLERANCE};
|
|
||||||
use document_core::Operation;
|
use document_core::Operation;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
|
||||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||||
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
use crate::tool::{DocumentToolData, Fsm, ToolActionHandlerData};
|
||||||
use crate::{message_prelude::*, SvgDocument};
|
use crate::{consts::SELECTION_TOLERANCE, message_prelude::*, SvgDocument};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Select {
|
pub struct Select {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue