Repository: GraphiteEditor/Graphite (language: rust)

Issue:
Reduce path editing clutter with options to hide non-selected anchors' handles
There should be three modes, explained below. But in summary:
- The first mode is the current behavior where all handles are shown.
- The second mode is where all selected anchors' segments' handles are shown.
- The third mode is where only the frontier handles are shown.

## Pen tool

In the Pen tool, only these circled handles should appear:
 
- Only the last-drawn anchor and its handles are shown, plus the handle on the other end of its connected segment. The Pen tool only has modes 1 and 2, but not 3.
  
  ![Image](https://github.com/user-attachments/assets/6e535103-c101-4ba7-8b45-6485dc4931ca)

## Path tool

In the Path tool, which allows multi-selection of anchors, these are the cases where it should appear:
  
- When one anchor is selected, its handles should be shown, as well as the handles of its connected segment(s). When one anchor is selected, this same situation applies to both modes 2 and 3, because mode 3 only becomes active with multi-selection (otherwise mode 2 is used).
  
  ![Image](https://github.com/user-attachments/assets/9f9f4c69-020a-4e25-a4d6-46324c8d795e)

- **Mode 2:** When two or more anchors are selected, all handles (circled in red) on either end of the adjacent segments (purple) are shown.
  
  ![Image](https://github.com/user-attachments/assets/42a1e5ba-e84d-4af8-849d-70c2fe13051d)

- **Mode 3:** When two or more anchors are selected, only the handles on the **frontier** are shown. The green shows the adjacent segments to the selected anchors, and the ends of the green are the frontiers. Selected in red are the only handles to be shown.
  
  ![Image](https://github.com/user-attachments/assets/f1873653-390c-489b-9b15-2e4abe7fc801)

Remember: only the handles in the diagrams above which are circled in red should appear, all other handles should be hidden.

The three modes (two for Pen tool) should be in the respective tools' control bars as a radio button. just use "1", "2", and "3" text for now until I come up with names or icons. The control bar is the area on the left above the drawing viewport where the tool controls are located.


Discussion / hints:

Fix patch:
diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs
index 1556699ed9..d9af75f7a4 100644
--- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs
+++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs
@@ -1,11 +1,13 @@
-use super::utility_types::OverlayContext;
+use super::utility_types::{DrawHandles, OverlayContext};
 use crate::consts::HIDE_HANDLE_DISTANCE;
 use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
 use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
 
 use graphene_core::vector::ManipulatorPointId;
+use graphene_std::vector::{PointId, SegmentId};
 
-use glam::DVec2;
+use bezier_rs::{Bezier, BezierHandles};
+use glam::{DAffine2, DVec2};
 use wasm_bindgen::JsCast;
 
 pub fn overlay_canvas_element() -> Option<web_sys::HtmlCanvasElement> {
@@ -23,41 +25,143 @@ pub fn overlay_canvas_context() -> web_sys::CanvasRenderingContext2d {
 	create_context().expect("Failed to get canvas context")
 }
 
-pub fn path_overlays(document: &DocumentMessageHandler, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
+pub fn selected_segments(document: &DocumentMessageHandler, shape_editor: &mut ShapeState) -> Vec<SegmentId> {
+	let selected_points = shape_editor.selected_points();
+	let selected_anchors = selected_points
+		.filter_map(|point_id| if let ManipulatorPointId::Anchor(p) = point_id { Some(*p) } else { None })
+		.collect::<Vec<_>>();
+
+	// Collect the segments whose handles are selected
+	let mut selected_segments = shape_editor
+		.selected_points()
+		.filter_map(|point_id| match point_id {
+			ManipulatorPointId::PrimaryHandle(segment_id) | ManipulatorPointId::EndHandle(segment_id) => Some(*segment_id),
+			ManipulatorPointId::Anchor(_) => None,
+		})
+		.collect::<Vec<_>>();
+
+	// TODO: Currently if there are two duplicate layers, both of their segments get overlays
+	// Adding segments which are are connected to selected anchors
 	for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
-		let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
-			continue;
-		};
-		//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
+		let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
+
+		for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() {
+			if selected_anchors.contains(&start) || selected_anchors.contains(&end) {
+				selected_segments.push(segment_id);
+			}
+		}
+	}
+
+	selected_segments
+}
+
+fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAffine2, is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext) {
+	let bezier = bezier.apply_transformation(|point| transform.transform_point2(point));
+	let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
+
+	match bezier.handles {
+		BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {
+			overlay_context.line(handle, bezier.start, None);
+			overlay_context.line(handle, bezier.end, None);
+			overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
+		}
+		BezierHandles::Cubic { handle_start, handle_end } => {
+			if not_under_anchor(handle_start, bezier.start) {
+				overlay_context.line(handle_start, bezier.start, None);
+				overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
+			}
+			if not_under_anchor(handle_end, bezier.end) {
+				overlay_context.line(handle_end, bezier.end, None);
+				overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);
+			}
+		}
+		_ => {}
+	}
+}
+
+pub fn overlay_bezier_handle_specific_point(
+	bezier: Bezier,
+	segment_id: SegmentId,
+	(start, end): (PointId, PointId),
+	point_to_render: PointId,
+	transform: DAffine2,
+	is_selected: impl Fn(ManipulatorPointId) -> bool,
+	overlay_context: &mut OverlayContext,
+) {
+	let bezier = bezier.apply_transformation(|point| transform.transform_point2(point));
+	let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
+
+	match bezier.handles {
+		BezierHandles::Quadratic { handle } => {
+			if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) {
+				let end = if start == point_to_render { bezier.start } else { bezier.end };
+				overlay_context.line(handle, end, None);
+				overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
+			}
+		}
+		BezierHandles::Cubic { handle_start, handle_end } => {
+			if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) {
+				overlay_context.line(handle_start, bezier.start, None);
+				overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
+			}
+			if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) {
+				overlay_context.line(handle_end, bezier.end, None);
+				overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);
+			}
+		}
+		_ => {}
+	}
+}
+
+pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandles, shape_editor: &mut ShapeState, overlay_context: &mut OverlayContext) {
+	for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
+		let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
 		let transform = document.metadata().transform_to_viewport(layer);
-		let selected = shape_editor.selected_shape_state.get(&layer);
-		let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
 		overlay_context.outline_vector(&vector_data, transform);
 
-		for (segment_id, bezier, _start, _end) in vector_data.segment_bezier_iter() {
-			let bezier = bezier.apply_transformation(|point| transform.transform_point2(point));
-			let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
-			match bezier.handles {
-				bezier_rs::BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {
-					overlay_context.line(handle, bezier.start, None);
-					overlay_context.line(handle, bezier.end, None);
-					overlay_context.manipulator_handle(handle, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None);
-				}
-				bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
-					if not_under_anchor(handle_start, bezier.start) {
-						overlay_context.line(handle_start, bezier.start, None);
-						overlay_context.manipulator_handle(handle_start, is_selected(selected, ManipulatorPointId::PrimaryHandle(segment_id)), None);
-					}
-					if not_under_anchor(handle_end, bezier.end) {
-						overlay_context.line(handle_end, bezier.end, None);
-						overlay_context.manipulator_handle(handle_end, is_selected(selected, ManipulatorPointId::EndHandle(segment_id)), None);
+		let selected = shape_editor.selected_shape_state.get(&layer);
+		let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
+
+		let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
+
+		match draw_handles {
+			DrawHandles::All => {
+				vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
+					overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
+				});
+			}
+			DrawHandles::SelectedAnchors(ref selected_segments) => {
+				vector_data
+					.segment_bezier_iter()
+					.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
+					.for_each(|(segment_id, bezier, _start, _end)| {
+						overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
+					});
+
+				for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
+					if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
+						overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
 					}
 				}
-				_ => {}
 			}
+			DrawHandles::FrontierHandles(ref segment_endpoints) => {
+				vector_data
+					.segment_bezier_iter()
+					.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
+					.for_each(|(segment_id, bezier, start, end)| {
+						if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
+							let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
+							overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
+						} else {
+							overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
+						}
+					});
+			}
+			DrawHandles::None => {}
 		}
+
 		for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
-			overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(selected, ManipulatorPointId::Anchor(id)), None);
+			overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
 		}
 	}
 }
diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs
index 6773f13548..a16eda48c4 100644
--- a/editor/src/messages/portfolio/document/overlays/utility_types.rs
+++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs
@@ -6,11 +6,12 @@ use crate::messages::prelude::Message;
 
 use bezier_rs::{Bezier, Subpath};
 use graphene_core::renderer::Quad;
-use graphene_std::vector::{PointId, VectorData};
+use graphene_std::vector::{PointId, SegmentId, VectorData};
 
 use core::borrow::Borrow;
 use core::f64::consts::TAU;
 use glam::{DAffine2, DVec2};
+use std::collections::HashMap;
 use wasm_bindgen::JsValue;
 
 pub type OverlayProvider = fn(OverlayContext) -> Message;
@@ -482,3 +483,10 @@ pub enum Pivot {
 	Middle,
 	End,
 }
+
+pub enum DrawHandles {
+	All,
+	SelectedAnchors(Vec<SegmentId>),
+	FrontierHandles(HashMap<SegmentId, Vec<PointId>>),
+	None,
+}
diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs
index ea7b7da23f..a87fccbfcb 100644
--- a/editor/src/messages/tool/tool_messages/path_tool.rs
+++ b/editor/src/messages/tool/tool_messages/path_tool.rs
@@ -3,8 +3,8 @@ use super::tool_prelude::*;
 use crate::consts::{
 	COLOR_OVERLAY_BLUE, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, INSERT_POINT_ON_SEGMENT_TOO_FAR_DISTANCE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
 };
-use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
-use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
+use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};
+use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
 use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
 use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
 use crate::messages::preferences::SelectionMode;
@@ -15,8 +15,8 @@ use crate::messages::tool::common_functionality::shape_editor::{
 use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
 
 use graphene_core::renderer::Quad;
-use graphene_core::vector::ManipulatorPointId;
-use graphene_std::vector::NoHashBuilder;
+use graphene_core::vector::{ManipulatorPointId, PointId};
+use graphene_std::vector::{NoHashBuilder, SegmentId};
 
 use std::vec;
 
@@ -24,6 +24,12 @@ use std::vec;
 pub struct PathTool {
 	fsm_state: PathToolFsmState,
 	tool_data: PathToolData,
+	options: PathToolOptions,
+}
+
+#[derive(Default)]
+pub struct PathToolOptions {
+	path_overlay_mode: PathOverlayMode,
 }
 
 #[impl_message(Message, ToolMessage, Path)]
@@ -89,6 +95,20 @@ pub enum PathToolMessage {
 		new_y: f64,
 	},
 	SwapSelectedHandles,
+	UpdateOptions(PathOptionsUpdate),
+}
+
+#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize, specta::Type)]
+pub enum PathOverlayMode {
+	AllHandles = 0,
+	#[default]
+	SelectedPointHandles = 1,
+	FrontierHandles = 2,
+}
+
+#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
+pub enum PathOptionsUpdate {
+	OverlayModeType(PathOverlayMode),
 }
 
 impl ToolMetadata for PathTool {
@@ -170,15 +190,31 @@ impl LayoutHolder for PathTool {
 			.tooltip(colinear_handles_tooltip)
 			.widget_holder();
 
+		let path_overlay_mode_widget = RadioInput::new(vec![
+			RadioEntryData::new("1")
+				.label("1")
+				.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles)).into()),
+			RadioEntryData::new("2")
+				.label("2")
+				.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles)).into()),
+			RadioEntryData::new("3")
+				.label("3")
+				.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles)).into()),
+		])
+		.selected_index(Some(self.options.path_overlay_mode as u32))
+		.widget_holder();
+
 		Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
 			widgets: vec![
 				x_location,
 				related_seperator.clone(),
 				y_location,
-				unrelated_seperator,
+				unrelated_seperator.clone(),
 				colinear_handle_checkbox,
 				related_seperator,
 				colinear_handles_label,
+				unrelated_seperator,
+				path_overlay_mode_widget,
 			],
 		}]))
 	}
@@ -189,6 +225,12 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
 		let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
 
 		match message {
+			ToolMessage::Path(PathToolMessage::UpdateOptions(action)) => match action {
+				PathOptionsUpdate::OverlayModeType(overlay_mode_type) => {
+					self.options.path_overlay_mode = overlay_mode_type;
+					responses.add(OverlaysMessage::Draw);
+				}
+			},
 			ToolMessage::Path(PathToolMessage::ClosePath) => {
 				responses.add(DocumentMessage::AddTransaction);
 				tool_data.shape_editor.close_selected_path(tool_data.document, responses);
@@ -204,7 +246,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
 				}
 			}
 			_ => {
-				self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
+				self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
 			}
 		}
 
@@ -321,6 +363,8 @@ struct PathToolData {
 	auto_panning: AutoPanning,
 	saved_points_before_anchor_select_toggle: Vec<ManipulatorPointId>,
 	select_anchor_toggled: bool,
+	saved_points_before_handle_drag: Vec<ManipulatorPointId>,
+	handle_drag_toggle: bool,
 	dragging_state: DraggingState,
 	current_selected_handle_id: Option<ManipulatorPointId>,
 	angle: f64,
@@ -430,12 +474,27 @@ impl PathToolData {
 
 		self.drag_start_pos = input.mouse.position;
 
+		let old_selection = shape_editor.selected_points().cloned().collect::<Vec<_>>();
+
 		// Select the first point within the threshold (in pixels)
 		if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, extend_selection) {
 			responses.add(DocumentMessage::StartTransaction);
 
 			if let Some(selected_points) = selected_points {
 				self.drag_start_pos = input.mouse.position;
+
+				// If selected points contain only handles and there was some selection before, then it is stored and becomes restored upon release
+				let mut dragging_only_handles = true;
+				for point in &selected_points.points {
+					if matches!(point.point_id, ManipulatorPointId::Anchor(_)) {
+						dragging_only_handles = false;
+						break;
+					}
+				}
+				if dragging_only_handles && !self.handle_drag_toggle && !old_selection.is_empty() {
+					self.saved_points_before_handle_drag = old_selection;
+				}
+
 				self.start_dragging_point(selected_points, input, document, shape_editor);
 				responses.add(OverlaysMessage::Draw);
 			}
@@ -687,9 +746,9 @@ impl PathToolData {
 
 impl Fsm for PathToolFsmState {
 	type ToolData = PathToolData;
-	type ToolOptions = ();
+	type ToolOptions = PathToolOptions;
 
-	fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
+	fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
 		let ToolActionHandlerData { document, input, shape_editor, .. } = tool_action_data;
 		let ToolMessage::Path(event) = event else { return self };
 		match (self, event) {
@@ -704,7 +763,51 @@ impl Fsm for PathToolFsmState {
 				self
 			}
 			(_, PathToolMessage::Overlays(mut overlay_context)) => {
-				path_overlays(document, shape_editor, &mut overlay_context);
+				// TODO: find the segment ids of which the selected points are a part of
+
+				match tool_options.path_overlay_mode {
+					PathOverlayMode::AllHandles => {
+						path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
+					}
+					PathOverlayMode::SelectedPointHandles => {
+						let selected_segments = selected_segments(document, shape_editor);
+
+						path_overlays(document, DrawHandles::SelectedAnchors(selected_segments), shape_editor, &mut overlay_context);
+					}
+					PathOverlayMode::FrontierHandles => {
+						let selected_segments = selected_segments(document, shape_editor);
+
+						// Match the behavior of `PathOverlayMode::SelectedPointHandles` when only one point is selected
+						if shape_editor.selected_points().count() == 1 {
+							path_overlays(document, DrawHandles::SelectedAnchors(selected_segments), shape_editor, &mut overlay_context);
+						} else {
+							let mut segment_endpoints: HashMap<SegmentId, Vec<PointId>> = HashMap::new();
+
+							for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()) {
+								let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
+
+								// The points which are part of only one segment will be rendered
+								let mut selected_segments_by_point: HashMap<PointId, Vec<SegmentId>> = HashMap::new();
+
+								for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() {
+									if selected_segments.contains(&segment_id) {
+										selected_segments_by_point.entry(start).or_default().push(segment_id);
+										selected_segments_by_point.entry(end).or_default().push(segment_id);
+									}
+								}
+
+								for (point, attached_segments) in selected_segments_by_point {
+									if attached_segments.len() == 1 {
+										segment_endpoints.entry(attached_segments[0]).or_default().push(point);
+									}
+								}
+							}
+
+							// Now frontier anchors can be sent for rendering overlays
+							path_overlays(document, DrawHandles::FrontierHandles(segment_endpoints), shape_editor, &mut overlay_context);
+						}
+					}
+				}
 
 				match self {
 					Self::Drawing { selection_shape } => {
@@ -839,6 +942,21 @@ impl Fsm for PathToolFsmState {
 					lock_angle,
 				},
 			) => {
+				let mut selected_only_handles = true;
+
+				let selected_points = shape_editor.selected_points();
+
+				for point in selected_points {
+					if matches!(point, ManipulatorPointId::Anchor(_)) {
+						selected_only_handles = false;
+						break;
+					}
+				}
+
+				if !tool_data.saved_points_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) {
+					tool_data.handle_drag_toggle = true;
+				}
+
 				if tool_data.selection_status.is_none() {
 					if let Some(layer) = document.click(input) {
 						shape_editor.select_all_anchors_in_layer(document, layer);
@@ -1027,6 +1145,14 @@ impl Fsm for PathToolFsmState {
 				PathToolFsmState::Ready
 			}
 			(_, PathToolMessage::DragStop { extend_selection, .. }) => {
+				if tool_data.handle_drag_toggle && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
+					shape_editor.deselect_all_points();
+					shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_handle_drag);
+
+					tool_data.saved_points_before_handle_drag.clear();
+					tool_data.handle_drag_toggle = false;
+				}
+
 				if tool_data.select_anchor_toggled {
 					shape_editor.deselect_all_points();
 					shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_anchor_select_toggle);
diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs
index 9ec62caebc..d59af4ff25 100644
--- a/editor/src/messages/tool/tool_messages/pen_tool.rs
+++ b/editor/src/messages/tool/tool_messages/pen_tool.rs
@@ -2,7 +2,7 @@ use super::tool_prelude::*;
 use crate::consts::{DEFAULT_STROKE_WIDTH, HIDE_HANDLE_DISTANCE, LINE_ROTATE_SNAP_ANGLE};
 use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
 use crate::messages::portfolio::document::overlays::utility_functions::path_overlays;
-use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
+use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
 use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
 use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
 use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
@@ -27,6 +27,7 @@ pub struct PenOptions {
 	line_weight: f64,
 	fill: ToolColorOptions,
 	stroke: ToolColorOptions,
+	pen_overlay_mode: PenOverlayMode,
 }
 
 impl Default for PenOptions {
@@ -35,6 +36,7 @@ impl Default for PenOptions {
 			line_weight: DEFAULT_STROKE_WIDTH,
 			fill: ToolColorOptions::new_secondary(),
 			stroke: ToolColorOptions::new_primary(),
+			pen_overlay_mode: PenOverlayMode::FrontierHandles,
 		}
 	}
 }
@@ -75,6 +77,12 @@ enum PenToolFsmState {
 	GRSHandle,
 }
 
+#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
+pub enum PenOverlayMode {
+	AllHandles = 0,
+	FrontierHandles = 1,
+}
+
 #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
 pub enum PenOptionsUpdate {
 	FillColor(Option<Color>),
@@ -83,6 +91,7 @@ pub enum PenOptionsUpdate {
 	StrokeColor(Option<Color>),
 	StrokeColorType(ToolColorType),
 	WorkingColors(Option<Color>, Option<Color>),
+	OverlayModeType(PenOverlayMode),
 }
 
 impl ToolMetadata for PenTool {
@@ -126,9 +135,26 @@ impl LayoutHolder for PenTool {
 			|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()),
 			|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
 		));
+
 		widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
+
 		widgets.push(create_weight_widget(self.options.line_weight));
 
+		widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
+
+		widgets.push(
+			RadioInput::new(vec![
+				RadioEntryData::new("1")
+					.label("1")
+					.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles)).into()),
+				RadioEntryData::new("2")
+					.label("2")
+					.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles)).into()),
+			])
+			.selected_index(Some(self.options.pen_overlay_mode as u32))
+			.widget_holder(),
+		);
+
 		Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
 	}
 }
@@ -140,6 +166,10 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool
 			return;
 		};
 		match action {
+			PenOptionsUpdate::OverlayModeType(overlay_mode_type) => {
+				self.options.pen_overlay_mode = overlay_mode_type;
+				responses.add(OverlaysMessage::Draw);
+			}
 			PenOptionsUpdate::LineWeight(line_weight) => self.options.line_weight = line_weight,
 			PenOptionsUpdate::FillColor(color) => {
 				self.options.fill.custom_color = color;
@@ -795,7 +825,7 @@ impl Fsm for PenToolFsmState {
 				self
 			}
 			(PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => {
-				path_overlays(document, shape_editor, &mut overlay_context);
+				path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
 				tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
 				self
 			}
@@ -839,7 +869,19 @@ impl Fsm for PenToolFsmState {
 						overlay_context.dashed_line(anchor_start, next_anchor, None, Some(4.), Some(4.), Some(0.5));
 					}
 
-					path_overlays(document, shape_editor, &mut overlay_context);
+					match tool_options.pen_overlay_mode {
+						PenOverlayMode::AllHandles => {
+							path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
+						}
+						PenOverlayMode::FrontierHandles => {
+							// Find the last segment ID to have its handles drawn
+							if let Some(latest_segment) = tool_data.latest_point().and_then(|point| point.in_segment) {
+								path_overlays(document, DrawHandles::SelectedAnchors(vec![latest_segment]), shape_editor, &mut overlay_context);
+							} else {
+								path_overlays(document, DrawHandles::None, shape_editor, &mut overlay_context);
+							};
+						}
+					}
 
 					if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, handle_end) {
 						// Draw the handle circle for the currently-being-dragged-out incoming handle (opposite the one currently being dragged out)
@@ -852,7 +894,7 @@ impl Fsm for PenToolFsmState {
 					}
 				} else {
 					// Draw the whole path and its manipulators when the user is clicking-and-dragging out from the most recently placed anchor to set its outgoing handle, during which it would otherwise not have its overlays drawn
-					path_overlays(document, shape_editor, &mut overlay_context);
+					path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
 				}
 
 				if self == PenToolFsmState::DraggingHandle(tool_data.handle_mode) && valid(next_anchor, next_handle_start) {
diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gcore/src/vector/vector_data.rs
index bd5cf324eb..8502b7178b 100644
--- a/node-graph/gcore/src/vector/vector_data.rs
+++ b/node-graph/gcore/src/vector/vector_data.rs
@@ -293,6 +293,26 @@ impl VectorData {
 			None
 		}
 	}
+
+	pub fn adjacent_segment(&self, manipulator_id: &ManipulatorPointId) -> Option<(PointId, SegmentId)> {
+		match manipulator_id {
+			ManipulatorPointId::PrimaryHandle(segment_id) => {
+				// For start handle, find segments ending at our start point
+				let (start_point_id, _, _) = self.segment_points_from_id(*segment_id)?;
+				let start_index = self.point_domain.resolve_id(start_point_id)?;
+
+				self.segment_domain.end_connected(start_index).find(|&id| id != *segment_id).map(|id| (start_point_id, id))
+			}
+			ManipulatorPointId::EndHandle(segment_id) => {
+				// For end handle, find segments starting at our end point
+				let (_, end_point_id, _) = self.segment_points_from_id(*segment_id)?;
+				let end_index = self.point_domain.resolve_id(end_point_id)?;
+
+				self.segment_domain.start_connected(end_index).find(|&id| id != *segment_id).map(|id| (end_point_id, id))
+			}
+			ManipulatorPointId::Anchor(_) => None,
+		}
+	}
 }
 
 impl Default for VectorData {


Test patch: