Skip to content

Insert point on segment by clicking once (no more sliding) and Alt+click to delete a segment #2495

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Apr 30, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7590fff
segment overlay change
4adex Mar 28, 2025
722079f
Segment split and delete
4adex Mar 31, 2025
b530d0b
Cleanup
4adex Mar 31, 2025
0ca2676
graceful handling of edge cases
4adex Mar 31, 2025
04f7634
Merge branch 'master' into segment_features_path_tool
4adex Mar 31, 2025
b4fb9bb
Merge branch 'master' into segment_features_path_tool
Keavon Apr 6, 2025
d2e40d5
Merge branch 'master' into segment_features_path_tool
4adex Apr 13, 2025
ab84190
Moved constants to conts.rs and tuned the threshold
4adex Apr 13, 2025
84c323a
Merge branch 'master' into segment_features_path_tool
Keavon Apr 14, 2025
660ce80
Merge branch 'master' into segment_features_path_tool
4adex Apr 17, 2025
177a374
Remove going into another state
4adex Apr 18, 2025
dc3d41f
Insert point mode cleanup
4adex Apr 18, 2025
e0a5f1b
Merge branch 'master' into segment_features_path_tool
4adex Apr 18, 2025
b09e475
Linting fix
4adex Apr 18, 2025
ddd05da
Code review
4adex Apr 18, 2025
c62edec
Added hints
4adex Apr 18, 2025
eb41b42
Added field for delete segment
4adex Apr 21, 2025
3ace632
Merge branch 'master' into segment_features_path_tool
Keavon Apr 23, 2025
8f1930e
Change controls and fix too far logic
4adex Apr 26, 2025
6659b1c
Merge branch 'segment_features_path_tool' of github.com:4adex/Graphit…
4adex Apr 26, 2025
cd3ddc7
Merge branch 'master' into segment_features_path_tool
4adex Apr 26, 2025
4d612ed
Merge branch 'master' into segment_features_path_tool
Keavon Apr 29, 2025
eb12696
Fixes
Keavon Apr 29, 2025
a587596
Merge branch 'master' into segment_features_path_tool
Keavon Apr 29, 2025
b582aa0
Code review
Keavon Apr 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ pub const SELECTION_THRESHOLD: f64 = 10.;
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
pub const INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE: f64 = 50.;
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
pub const SEGMENT_INSERTION_TOLERANCE: f64 = 7.5;
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;

// PEN TOOL
pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
Expand Down
2 changes: 1 addition & 1 deletion editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { direct_insert_without_sliding: Control, extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt }),
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { delete_segment: Control, extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt }),
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
Expand Down
31 changes: 31 additions & 0 deletions editor/src/messages/tool/common_functionality/shape_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ impl ClosestSegment {
self.layer
}

pub fn segment(&self) -> SegmentId {
self.segment
}

pub fn points(&self) -> [PointId; 2] {
self.points
}

pub fn closest_point_to_viewport(&self) -> DVec2 {
self.bezier_point_to_viewport
}
Expand Down Expand Up @@ -892,6 +900,29 @@ impl ShapeState {
.collect::<HashMap<_, _>>()
}

pub fn dissolve_segment(&self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData, segment: SegmentId, points: [PointId; 2]) {
// Checking which point is terminal point
let is_point1_terminal = vector_data.connected_count(points[0]) == 1;
let is_point2_terminal = vector_data.connected_count(points[1]) == 1;

// Delete the segment and terminal points
let modification_type = VectorModificationType::RemoveSegment { id: segment };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
for &handles in vector_data.colinear_manipulators.iter().filter(|handles| handles.iter().any(|handle| handle.segment == segment)) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}

if is_point1_terminal {
let modification_type = VectorModificationType::RemovePoint { id: points[0] };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
if is_point2_terminal {
let modification_type = VectorModificationType::RemovePoint { id: points[1] };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}

fn dissolve_anchor(anchor: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) -> Option<[(HandleId, PointId); 2]> {
// Delete point
let modification_type = VectorModificationType::RemovePoint { id: anchor };
Expand Down
213 changes: 93 additions & 120 deletions editor/src/messages/tool/tool_messages/path_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::select_tool::extend_lasso;
use super::tool_prelude::*;
use crate::consts::{
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE,
SELECTION_THRESHOLD, SELECTION_TOLERANCE,
SEGMENT_INSERTION_TOLERANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
};
use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
Expand Down Expand Up @@ -63,7 +63,7 @@ pub enum PathToolMessage {
ManipulatorMakeHandlesFree,
ManipulatorMakeHandlesColinear,
MouseDown {
direct_insert_without_sliding: Key,
delete_segment: Key,
extend_selection: Key,
lasso_select: Key,
handle_drag_from_anchor: Key,
Expand Down Expand Up @@ -273,6 +273,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
BreakPath,
DeleteAndBreakPath,
ClosePath,
PointerMove,
),
PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant;
Escape,
Expand All @@ -296,15 +297,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
Escape,
RightClick,
),
PathToolFsmState::InsertPoint => actions!(PathToolMessageDiscriminant;
Enter,
MouseDown,
PointerMove,
Escape,
Delete,
RightClick,
GRS,
),
}
}
}
Expand Down Expand Up @@ -341,12 +333,6 @@ enum PathToolFsmState {
Drawing {
selection_shape: SelectionShapeType,
},
InsertPoint,
}

enum InsertEndKind {
Abort,
Add { extend_selection: bool },
}

#[derive(Default)]
Expand All @@ -367,6 +353,7 @@ struct PathToolData {
segment: Option<ClosestSegment>,
snap_cache: SnapCache,
double_click_handled: bool,
delete_segment_pressed: bool,
auto_panning: AutoPanning,
saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
select_anchor_toggled: bool,
Expand Down Expand Up @@ -437,53 +424,6 @@ impl PathToolData {
self.selection_status = selection_status;
}

fn start_insertion(&mut self, responses: &mut VecDeque<Message>, segment: ClosestSegment) -> PathToolFsmState {
if self.segment.is_some() {
warn!("Segment was `Some(..)` before `start_insertion`")
}
self.segment = Some(segment);
responses.add(OverlaysMessage::Draw);
PathToolFsmState::InsertPoint
}

fn update_insertion(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>, input: &InputPreprocessorMessageHandler) -> PathToolFsmState {
if let Some(closed_segment) = &mut self.segment {
closed_segment.update_closest_point(document.metadata(), input.mouse.position);
if closed_segment.too_far(input.mouse.position, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, document.metadata()) {
self.end_insertion(shape_editor, responses, InsertEndKind::Abort)
} else {
PathToolFsmState::InsertPoint
}
} else {
warn!("Segment was `None` on `update_insertion`");
PathToolFsmState::Ready
}
}

fn end_insertion(&mut self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, kind: InsertEndKind) -> PathToolFsmState {
let mut commit_transaction = false;
match self.segment.as_mut() {
None => {
warn!("Segment was `None` before `end_insertion`")
}
Some(closed_segment) => {
if let InsertEndKind::Add { extend_selection } = kind {
closed_segment.adjusted_insert_and_select(shape_editor, responses, extend_selection);
commit_transaction = true;
}
}
}

self.segment = None;
if commit_transaction {
responses.add(DocumentMessage::EndTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}

#[allow(clippy::too_many_arguments)]
fn mouse_down(
&mut self,
Expand All @@ -492,7 +432,6 @@ impl PathToolData {
input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>,
extend_selection: bool,
direct_insert_without_sliding: bool,
lasso_select: bool,
handle_drag_from_anchor: bool,
) -> PathToolFsmState {
Expand Down Expand Up @@ -562,17 +501,27 @@ impl PathToolData {
}
PathToolFsmState::Dragging(self.dragging_state)
}
// We didn't find a point nearby, so now we'll try to add a point into the closest path segment
else if let Some(closed_segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE) {
//We didn't found a point so we will see if there is a segment to insert the point
else if let Some(closed_segment) = &mut self.segment {
responses.add(DocumentMessage::StartTransaction);
if direct_insert_without_sliding {
self.start_insertion(responses, closed_segment);
self.end_insertion(shape_editor, responses, InsertEndKind::Add { extend_selection })

if self.delete_segment_pressed {
let segment = closed_segment.segment();
let layer = closed_segment.layer();
let points = closed_segment.points();
if let Some(vector_data) = document.network_interface.compute_modified_vector(layer) {
shape_editor.dissolve_segment(responses, layer, &vector_data, segment, points);
responses.add(DocumentMessage::EndTransaction);
}
} else {
self.start_insertion(responses, closed_segment)
closed_segment.adjusted_insert_and_select(shape_editor, responses, extend_selection);
responses.add(DocumentMessage::EndTransaction);
}
self.segment = None;

PathToolFsmState::Ready
}
// We didn't find a segment path, so consider selecting the nearest shape instead
// We didn't find a segment, so consider selecting the nearest shape instead
else if let Some(layer) = document.click(input) {
shape_editor.deselect_all_points();
if extend_selection {
Expand Down Expand Up @@ -1029,6 +978,44 @@ impl Fsm for PathToolFsmState {
}

match self {
Self::Ready => {
//closest segment should be set when pointermoved and tabhi update segment bhi hona chahiye

//check for a close segment if it is there
if let Some(closest_segment) = &tool_data.segment {
//insert a perpendicular line
let tangent = if let (Some(handle1), Some(handle2)) = closest_segment.handle_positions(document.metadata()) {
(handle1 - handle2).try_normalize()
} else {
let layer = closest_segment.layer();
let points = closest_segment.points();
if let Some(vector_data) = document.network_interface.compute_modified_vector(layer) {
if let (Some(pos1), Some(pos2)) = (
ManipulatorPointId::Anchor(points[0]).get_position(&vector_data),
ManipulatorPointId::Anchor(points[1]).get_position(&vector_data),
) {
(pos1 - pos2).try_normalize()
} else {
None
}
} else {
None
}
}
.unwrap_or(DVec2::ZERO);
let perp = tangent.perp();
let point = closest_segment.closest_point_to_viewport();
if tool_data.delete_segment_pressed {
let degrees: f64 = 45.0;
let tilted_line = DVec2::from_angle(degrees.to_radians()).rotate(tangent);
let tilted_perp = tilted_line.perp();
overlay_context.line(point - tilted_line * SEGMENT_OVERLAY_SIZE, point + tilted_line * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.line(point - tilted_perp * SEGMENT_OVERLAY_SIZE, point + tilted_perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
} else {
overlay_context.line(point - perp * SEGMENT_OVERLAY_SIZE, point + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
}
}
}
Self::Drawing { selection_shape } => {
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
.unwrap()
Expand Down Expand Up @@ -1078,71 +1065,30 @@ impl Fsm for PathToolFsmState {
}
}
}
Self::InsertPoint => {
let state = tool_data.update_insertion(shape_editor, document, responses, input);

if let Some(closest_segment) = &tool_data.segment {
overlay_context.manipulator_anchor(closest_segment.closest_point_to_viewport(), false, Some(COLOR_OVERLAY_BLUE));
if let (Some(handle1), Some(handle2)) = closest_segment.handle_positions(document.metadata()) {
overlay_context.line(closest_segment.closest_point_to_viewport(), handle1, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.line(closest_segment.closest_point_to_viewport(), handle2, Some(COLOR_OVERLAY_BLUE), None);
overlay_context.manipulator_handle(handle1, false, Some(COLOR_OVERLAY_BLUE));
overlay_context.manipulator_handle(handle2, false, Some(COLOR_OVERLAY_BLUE));
}
}

responses.add(PathToolMessage::SelectedPointUpdated);
return state;
}
_ => {}
}

responses.add(PathToolMessage::SelectedPointUpdated);
self
}

// `Self::InsertPoint` case:
(Self::InsertPoint, PathToolMessage::MouseDown { extend_selection, .. } | PathToolMessage::Enter { extend_selection, .. }) => {
tool_data.double_click_handled = true;
let extend_selection = input.keyboard.get(extend_selection as usize);
tool_data.end_insertion(shape_editor, responses, InsertEndKind::Add { extend_selection })
}
(Self::InsertPoint, PathToolMessage::PointerMove { .. }) => {
responses.add(OverlaysMessage::Draw);
// `tool_data.update_insertion` would be called on `OverlaysMessage::Draw`
// we anyway should to call it on `::Draw` because we can change scale by ctrl+scroll without `::PointerMove`
self
}
(Self::InsertPoint, PathToolMessage::Escape | PathToolMessage::Delete | PathToolMessage::RightClick) => tool_data.end_insertion(shape_editor, responses, InsertEndKind::Abort),
(Self::InsertPoint, PathToolMessage::GRS { key: _ }) => PathToolFsmState::InsertPoint,
// Mouse down
(
_,
PathToolMessage::MouseDown {
direct_insert_without_sliding,
extend_selection,
lasso_select,
handle_drag_from_anchor,
..
},
) => {
let extend_selection = input.keyboard.get(extend_selection as usize);
let lasso_select = input.keyboard.get(lasso_select as usize);
let direct_insert_without_sliding = input.keyboard.get(direct_insert_without_sliding as usize);
let handle_drag_from_anchor = input.keyboard.get(handle_drag_from_anchor as usize);

tool_data.selection_mode = None;
tool_data.lasso_polygon.clear();

tool_data.mouse_down(
shape_editor,
document,
input,
responses,
extend_selection,
direct_insert_without_sliding,
lasso_select,
handle_drag_from_anchor,
)
tool_data.mouse_down(shape_editor, document, input, responses, extend_selection, lasso_select, handle_drag_from_anchor)
}
(
PathToolFsmState::Drawing { selection_shape },
Expand Down Expand Up @@ -1273,6 +1219,37 @@ impl Fsm for PathToolFsmState {

PathToolFsmState::Dragging(tool_data.dragging_state)
}
(PathToolFsmState::Ready, PathToolMessage::PointerMove { lock_angle, .. }) => {
let lock_angle_state = input.keyboard.get(lock_angle as usize);
if lock_angle_state {
tool_data.delete_segment_pressed = true;
} else {
tool_data.delete_segment_pressed = false;
}

// If there is a point nearby then remove the overlay
if shape_editor
.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD)
.is_some()
{
tool_data.segment = None;
responses.add(OverlaysMessage::Draw)
}
// If already hovering on a segment, then recalculate it's closest point
else if let Some(closest_segment) = &mut tool_data.segment {
closest_segment.update_closest_point(document.metadata(), input.mouse.position);
if closest_segment.too_far(input.mouse.position, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, document.metadata()) {
tool_data.segment = None;
}
responses.add(OverlaysMessage::Draw)
}
// If not check that if there is some closest segment or not
else if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SEGMENT_INSERTION_TOLERANCE) {
tool_data.segment = Some(closest_segment);
responses.add(OverlaysMessage::Draw)
}
self
}
(PathToolFsmState::Drawing { selection_shape: selection_type }, PathToolMessage::PointerOutsideViewport { .. }) => {
// Auto-panning
if let Some(offset) = tool_data.auto_panning.shift_viewport(input, responses) {
Expand Down Expand Up @@ -1501,7 +1478,7 @@ impl Fsm for PathToolFsmState {
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}
(_, PathToolMessage::PointerMove { .. }) => self,
// (_, PathToolMessage::PointerMove { .. }) => self,
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
shape_editor.move_selected_points(
tool_data.opposing_handle_lengths.take(),
Expand Down Expand Up @@ -1652,10 +1629,6 @@ impl Fsm for PathToolFsmState {
HintInfo::keys([Key::Alt], "Subtract").prepend_plus(),
]),
]),
PathToolFsmState::InsertPoint => HintData(vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point")]),
]),
};

responses.add(FrontendMessage::UpdateInputHints { hint_data });
Expand Down
Loading