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:
parent
1700c3a650
commit
20a5f71bda
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue