mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 11:08:12 +08:00
Add Freehand tool drawing new subpaths on an existing layer with Shift held
This commit is contained in:
@@ -258,7 +258,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
//
|
//
|
||||||
// FreehandToolMessage
|
// FreehandToolMessage
|
||||||
entry!(PointerMove; action_dispatch=FreehandToolMessage::PointerMove),
|
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!(KeyUp(MouseLeft); action_dispatch=FreehandToolMessage::DragStop),
|
||||||
entry!(KeyDown(MouseRight); action_dispatch=FreehandToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=FreehandToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); action_dispatch=FreehandToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=FreehandToolMessage::Abort),
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ pub enum FreehandToolMessage {
|
|||||||
WorkingColorChanged,
|
WorkingColorChanged,
|
||||||
|
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
DragStart,
|
DragStart { append_to_selected: Key },
|
||||||
DragStop,
|
DragStop,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
UpdateOptions(FreehandOptionsUpdate),
|
UpdateOptions(FreehandOptionsUpdate),
|
||||||
@@ -203,7 +203,7 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart) => {
|
(FreehandToolFsmState::Ready, FreehandToolMessage::DragStart { append_to_selected }) => {
|
||||||
responses.add(DocumentMessage::StartTransaction);
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
tool_data.dragged = false;
|
tool_data.dragged = false;
|
||||||
@@ -216,11 +216,26 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
tool_data.layer = Some(layer);
|
tool_data.layer = Some(layer);
|
||||||
tool_data.end_point = Some((position, point));
|
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;
|
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);
|
responses.add(DocumentMessage::DeselectAllLayers);
|
||||||
|
|
||||||
let parent = document.new_layer_parent(true);
|
let parent = document.new_layer_parent(true);
|
||||||
@@ -242,7 +257,7 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
let transform = document.metadata().transform_to_viewport(layer);
|
let transform = document.metadata().transform_to_viewport(layer);
|
||||||
let position = transform.inverse().transform_point2(input.mouse.position);
|
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
|
FreehandToolFsmState::Drawing
|
||||||
@@ -279,7 +294,11 @@ impl Fsm for FreehandToolFsmState {
|
|||||||
|
|
||||||
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
fn update_hints(&self, responses: &mut VecDeque<Message>) {
|
||||||
let hint_data = match self {
|
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()])]),
|
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() {
|
if !tool_data.end_point.map_or(true, |(last_pos, _)| position != last_pos) || !position.is_finite() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -304,18 +323,20 @@ fn extend_path_with_next_segment(tool_data: &mut FreehandToolData, position: DVe
|
|||||||
modification_type: VectorModificationType::InsertPoint { id, position },
|
modification_type: VectorModificationType::InsertPoint { id, position },
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((_, previous_position)) = tool_data.end_point {
|
if extend {
|
||||||
let next_id = SegmentId::generate();
|
if let Some((_, previous_position)) = tool_data.end_point {
|
||||||
let points = [previous_position, id];
|
let next_id = SegmentId::generate();
|
||||||
|
let points = [previous_position, id];
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::Vector {
|
responses.add(GraphOperationMessage::Vector {
|
||||||
layer,
|
layer,
|
||||||
modification_type: VectorModificationType::InsertSegment {
|
modification_type: VectorModificationType::InsertSegment {
|
||||||
id: next_id,
|
id: next_id,
|
||||||
points,
|
points,
|
||||||
handles: [None, None],
|
handles: [None, None],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tool_data.dragged = true;
|
tool_data.dragged = true;
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ pub enum PenToolMessage {
|
|||||||
|
|
||||||
// Tool-specific messages
|
// 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 },
|
AddPointLayerPosition { layer: LayerNodeIdentifier, viewport: DVec2 },
|
||||||
Confirm,
|
Confirm,
|
||||||
DragStart { append_to_selected: Key },
|
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
|
// This causes the following message to be run only after the next graph evaluation runs and the transforms are updated
|
||||||
responses.add(Message::StartBuffer);
|
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 });
|
responses.add(PenToolMessage::AddPointLayerPosition { layer, viewport });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,6 +168,15 @@ impl Footprint {
|
|||||||
pub fn offset(&self) -> DVec2 {
|
pub fn offset(&self) -> DVec2 {
|
||||||
self.transform.transform_point2(DVec2::ZERO)
|
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 {
|
impl From<()> for Footprint {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::misc::CentroidType;
|
use super::misc::CentroidType;
|
||||||
use super::style::{Fill, Gradient, GradientStops, Stroke};
|
use super::style::{Fill, Gradient, GradientStops, Stroke};
|
||||||
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData};
|
use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData};
|
||||||
|
use crate::raster::Bitmap;
|
||||||
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
|
use crate::registry::types::{Angle, Fraction, IntegerCount, Length, SeedValue};
|
||||||
use crate::renderer::GraphicElementRendered;
|
use crate::renderer::GraphicElementRendered;
|
||||||
use crate::transform::{Footprint, Transform, TransformMut};
|
use crate::transform::{Footprint, Transform, TransformMut};
|
||||||
@@ -901,24 +902,41 @@ async fn jitter_points<F: 'n + Send>(
|
|||||||
Footprint -> VectorData,
|
Footprint -> VectorData,
|
||||||
)]
|
)]
|
||||||
vector_data: impl Node<F, Output = VectorData>,
|
vector_data: impl Node<F, Output = VectorData>,
|
||||||
|
// TODO: Make amount per-axis
|
||||||
#[default(5.)] amount: f64,
|
#[default(5.)] amount: f64,
|
||||||
seed: SeedValue,
|
seed: SeedValue,
|
||||||
|
#[implementations(
|
||||||
|
Footprint -> crate::raster::ImageFrame<Color>,
|
||||||
|
)]
|
||||||
|
image: impl Node<Footprint, Output = crate::raster::ImageFrame<Color>>,
|
||||||
) -> VectorData {
|
) -> VectorData {
|
||||||
let mut vector_data = vector_data.eval(footprint).await;
|
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())
|
// let deltas = (0..vector_data.point_domain.positions().len())
|
||||||
.map(|_| {
|
// .map(|_| {
|
||||||
let angle = rng.gen::<f64>() * std::f64::consts::TAU;
|
// let angle = rng.gen::<f64>() * std::f64::consts::TAU;
|
||||||
DVec2::from_angle(angle) * rng.gen::<f64>() * amount
|
// DVec2::from_angle(angle) * rng.gen::<f64>() * amount
|
||||||
})
|
// })
|
||||||
.collect::<Vec<_>>();
|
// .collect::<Vec<_>>();
|
||||||
let mut already_applied = vec![false; vector_data.point_domain.positions().len()];
|
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() {
|
for (handles, start, end) in vector_data.segment_domain.handles_and_points_mut() {
|
||||||
let start_delta = deltas[*start];
|
let sample_start = image
|
||||||
let end_delta = deltas[*end];
|
.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] {
|
if !already_applied[*start] {
|
||||||
let start_position = vector_data.point_domain.positions()[*start];
|
let start_position = vector_data.point_domain.positions()[*start];
|
||||||
|
|||||||
Reference in New Issue
Block a user