Add Freehand tool drawing new subpaths on an existing layer with Shift held

This commit is contained in:
Keavon Chambers 2024-12-17 22:01:42 -08:00
parent 3423c8ec13
commit ee7d498241
5 changed files with 77 additions and 29 deletions

View file

@ -258,7 +258,7 @@ pub fn input_mappings() -> Mapping {
//
// FreehandToolMessage
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
entry!(KeyDown(MouseLeft); action_dispatch=FreehandToolMessage::DragStart),
entry!(KeyDown(MouseLeft); action_dispatch=FreehandToolMessage::DragStart { append_to_selected: Shift }),
entry!(KeyUp(MouseLeft); action_dispatch=FreehandToolMessage::DragStop),
entry!(KeyDown(MouseRight); action_dispatch=FreehandToolMessage::Abort),
entry!(KeyDown(Escape); action_dispatch=FreehandToolMessage::Abort),

View file

@ -47,7 +47,7 @@ pub enum FreehandToolMessage {
WorkingColorChanged,
// Tool-specific messages
DragStart,
DragStart { append_to_selected: Key },
DragStop,
PointerMove,
UpdateOptions(FreehandOptionsUpdate),
@ -203,7 +203,7 @@ impl Fsm for FreehandToolFsmState {
self
}
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => {
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart { append_to_selected }) => {
responses.add(DocumentMessage::StartTransaction);
tool_data.dragged = false;
@ -216,11 +216,26 @@ impl Fsm for FreehandToolFsmState {
tool_data.layer = Some(layer);
tool_data.end_point = Some((position, point));
extend_path_with_next_segment(tool_data, position, responses);
extend_path_with_next_segment(tool_data, position, true, responses);
return FreehandToolFsmState::Drawing;
}
if input.keyboard.key(append_to_selected) {
let mut selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&document.network_interface);
let existing_layer = selected_layers_except_artboards.next().filter(|_| selected_layers_except_artboards.next().is_none());
if let Some(layer) = existing_layer {
tool_data.layer = Some(layer);
let transform = document.metadata().transform_to_viewport(layer);
let position = transform.inverse().transform_point2(input.mouse.position);
extend_path_with_next_segment(tool_data, position, false, responses);
return FreehandToolFsmState::Drawing;
}
}
responses.add(DocumentMessage::DeselectAllLayers);
let parent = document.new_layer_parent(true);
@ -242,7 +257,7 @@ impl Fsm for FreehandToolFsmState {
let transform = document.metadata().transform_to_viewport(layer);
let position = transform.inverse().transform_point2(input.mouse.position);
extend_path_with_next_segment(tool_data, position, responses);
extend_path_with_next_segment(tool_data, position, true, responses);
}
FreehandToolFsmState::Drawing
@ -279,7 +294,11 @@ impl Fsm for FreehandToolFsmState {
fn update_hints(&self, responses: &mut VecDeque<Message>) {
let hint_data = match self {
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polyline")])]),
FreehandToolFsmState::Ready => HintData(vec![HintGroup(vec![
HintInfo::mouse(MouseMotion::LmbDrag, "Draw Polyline"),
// TODO: Only show this if a single layer is selected and it's of a valid type (e.g. a vector path but not raster or artboard)
HintInfo::keys([Key::Shift], "Append to Selected Layer").prepend_plus(),
])]),
FreehandToolFsmState::Drawing => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
};
@ -291,7 +310,7 @@ impl Fsm for FreehandToolFsmState {
}
}
fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVec2, responses: &mut VecDeque<Message>) {
fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVec2, extend: bool, responses: &mut VecDeque<Message>) {
if !tool_data.end_point.map_or(true, |(last_pos, _)| position != last_pos) || !position.is_finite() {
return;
}
@ -304,18 +323,20 @@ fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVe
modification_type: VectorModificationType::InsertPoint { id, position },
});
if let Some((_, previous_position)) = tool_data.end_point {
let next_id = SegmentId::generate();
let points = [previous_position, id];
if extend {
if let Some((_, previous_position)) = tool_data.end_point {
let next_id = SegmentId::generate();
let points = [previous_position, id];
responses.add(GraphOperationMessage::Vector {
layer,
modification_type: VectorModificationType::InsertSegment {
id: next_id,
points,
handles: [None, None],
},
});
responses.add(GraphOperationMessage::Vector {
layer,
modification_type: VectorModificationType::InsertSegment {
id: next_id,
points,
handles: [None, None],
},
});
}
}
tool_data.dragged = true;

View file

@ -51,7 +51,7 @@ pub enum PenToolMessage {
// Tool-specific messages
// It is necessary to defer this until the transform of the layer can be accuratly computed (quite hacky)
// It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky)
AddPointLayerPosition { layer: LayerNodeIdentifier, viewport: DVec2 },
Confirm,
DragStart { append_to_selected: Key },
@ -500,7 +500,7 @@ impl PenToolData {
// This causes the following message to be run only after the next graph evaluation runs and the transforms are updated
responses.add(Message::StartBuffer);
// It is necessary to defer this until the transform of the layer can be accuratly computed (quite hacky)
// It is necessary to defer this until the transform of the layer can be accurately computed (quite hacky)
responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport });
}

View file

@ -168,6 +168,15 @@ impl Footprint {
pub fn offset(&self) -> DVec2 {
self.transform.transform_point2(DVec2::ZERO)
}
pub fn from_point(point: DVec2) -> Self {
Self {
// TODO: Make the scale 0?
transform: DAffine2::from_translation(point),
resolution: glam::UVec2::ONE,
..Default::default()
}
}
}
impl From<()> for Footprint {

View file

@ -1,6 +1,7 @@
use super::misc::CentroidType;
use super::style::{Fill, Gradient, GradientStops, Stroke};
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData};
use crate::raster::Bitmap;
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
use crate::renderer::GraphicElementRendered;
use crate::transform::{Footprint, Transform, TransformMut};
@ -901,24 +902,41 @@ async fn jitter_points<F: 'n + Send>(
Footprint -> VectorData,
)]
vector_data: impl Node<F, Output = VectorData>,
// TODO: Make amount per-axis
#[default(5.)] amount: f64,
seed: SeedValue,
#[implementations(
Footprint -> crate::raster::ImageFrame<Color>,
)]
image: impl Node<Footprint, Output = crate::raster::ImageFrame<Color>>,
) -> VectorData {
let mut vector_data = vector_data.eval(footprint).await;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
// let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into());
let deltas = (0..vector_data.point_domain.positions().len())
.map(|_| {
let angle = rng.gen::<f64>() * std::f64::consts::TAU;
DVec2::from_angle(angle) * rng.gen::<f64>() * amount
})
.collect::<Vec<_>>();
// let deltas = (0..vector_data.point_domain.positions().len())
// .map(|_| {
// let angle = rng.gen::<f64>() * std::f64::consts::TAU;
// DVec2::from_angle(angle) * rng.gen::<f64>() * amount
// })
// .collect::<Vec<_>>();
let mut already_applied = vec![false; vector_data.point_domain.positions().len()];
for (handles, start, end) in vector_data.segment_domain.handles_and_points_mut() {
let start_delta = deltas[*start];
let end_delta = deltas[*end];
let sample_start = image
.eval(Footprint::from_point(vector_data.point_domain.positions()[*start]))
.await
.image
.get_pixel(0, 0)
.expect("Broke at A");
let start_delta = DVec2::new((sample_start.r() as f64 - 0.5) * amount, (sample_start.g() as f64 - 0.5) * amount);
let sample_end = image
.eval(Footprint::from_point(vector_data.point_domain.positions()[*end]))
.await
.image
.get_pixel(0, 0)
.expect("Broke at A");
let end_delta = DVec2::new((sample_end.r() as f64 - 0.5) * amount, (sample_end.g() as f64 - 0.5) * amount);
if !already_applied[*start] {
let start_position = vector_data.point_domain.positions()[*start];