Add Pen and Path tool modes to avoid showing all handles (#2264)

* Path tool only show frontier overlays

* Implemented all modes for pen and path tool

* Fixed formatting issue

* Changes in selection behaviour of handles

* Selection toggle only on drag not click

* Changed comment

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Adesh Gupta 2025-02-12 15:00:15 +05:30 committed by GitHub
parent 1700c3a650
commit 20a5f71bda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 347 additions and 47 deletions

View File

@ -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) {
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 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);
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<_>>();
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);
}
}
_ => {}
// 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 };
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);
overlay_context.outline_vector(&vector_data, transform);
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);
}
}
}

View File

@ -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,
}

View File

@ -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);

View File

@ -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) {

View File

@ -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 {