Add selection cycle and gray pre-selection outlines to the Path tool, and Tab to swap Select/Path tools (#2818)
* Added initial version of this feature for the path tool * Removed debug statements * Thickened the overlay width * Added hover highlighting for path tool * Experimental switch to path tool at leaf layer * Ghost outline initial implementation * Added tab swap for select tool -> path tool * Minor fix for Select Tool dbl click -> Path Tool * Added support for ghosts when using GRS in the path tool * Fixed GRS undo bug, vastly improved hover behavior and now clearly visualize next double click target * Fixed unused import warnings * Updated behavior to handle mouse movement cases, reverted line width to 1px * Fixed merge behavioral issues * Disabled Select Tool to Path Tool double click toggle, fixed single click drill through for special case * Clean up of unused consts and comment * Properly cancel the drill through state when the mouse moves * Fix some stuff --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
d6d1bbb1e6
commit
e89cded4b8
|
|
@ -101,6 +101,7 @@ pub const MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY: f64 = 48.;
|
|||
// PATH TOOL
|
||||
pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
|
||||
pub const SELECTION_THRESHOLD: f64 = 10.;
|
||||
pub const DRILL_THROUGH_THRESHOLD: f64 = 10.;
|
||||
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
||||
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
|
||||
|
|
@ -134,13 +135,15 @@ pub const SCALE_EFFECT: f64 = 0.5;
|
|||
|
||||
// COLORS
|
||||
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
||||
pub const COLOR_OVERLAY_BLUE_50: &str = "#00a8ff80";
|
||||
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
||||
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
|
||||
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
||||
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
||||
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
||||
pub const COLOR_OVERLAY_GRAY_25: &str = "#cccccc40";
|
||||
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
|
||||
pub const COLOR_OVERLAY_LABEL_BACKGROUND: &str = "#000000cc";
|
||||
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
||||
|
||||
// DOCUMENT
|
||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }),
|
||||
entry!(KeyDownNoRepeat(Tab); action_dispatch=ToolMessage::ToggleSelectVsPath),
|
||||
//
|
||||
// DocumentMessage
|
||||
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
|
||||
|
|
|
|||
|
|
@ -1683,6 +1683,11 @@ impl DocumentMessageHandler {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||
self.click_xray(ipp)
|
||||
.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]) && !layer.has_children(self.network_interface.document_metadata()))
|
||||
}
|
||||
|
||||
/// Find the deepest layer that has been clicked on from a location in viewport space.
|
||||
pub fn click(&self, ipp: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||
self.click_list(ipp).last()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use super::utility_functions::overlay_canvas_context;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
|
||||
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE,
|
||||
COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH,
|
||||
PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
};
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
|
|
@ -349,6 +350,7 @@ impl OverlayContext {
|
|||
self.render_context.rect(corner.x, corner.y, size, size);
|
||||
self.render_context.set_fill_style_str(color_fill);
|
||||
self.render_context.set_stroke_style_str(color_stroke);
|
||||
self.render_context.set_line_width(1.);
|
||||
self.render_context.fill();
|
||||
self.render_context.stroke();
|
||||
|
||||
|
|
@ -631,11 +633,9 @@ impl OverlayContext {
|
|||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style_str(&color);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.stroke();
|
||||
|
||||
|
|
@ -727,6 +727,7 @@ impl OverlayContext {
|
|||
|
||||
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||
self.render_context.set_stroke_style_str(color);
|
||||
self.render_context.set_line_width(1.);
|
||||
self.render_context.stroke();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ mod grid_snapper;
|
|||
mod layer_snapper;
|
||||
mod snap_results;
|
||||
|
||||
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_LABEL_BACKGROUND, COLOR_OVERLAY_WHITE};
|
||||
use crate::consts::{COLOR_OVERLAY_BLACK_75, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_WHITE};
|
||||
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, PathSnapTarget, SnapTarget};
|
||||
|
|
@ -482,7 +482,7 @@ impl SnapManager {
|
|||
if !any_align && ind.distribution_equal_distance_horizontal.is_none() && ind.distribution_equal_distance_vertical.is_none() {
|
||||
let text = format!("[{}] from [{}]", ind.target, ind.source);
|
||||
let transform = DAffine2::from_translation(viewport - DVec2::new(0., 4.));
|
||||
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_LABEL_BACKGROUND), transform, 4., [Pivot::Start, Pivot::End]);
|
||||
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_BLACK_75), transform, 4., [Pivot::Start, Pivot::End]);
|
||||
overlay_context.square(viewport, Some(4.), Some(COLOR_OVERLAY_BLUE), Some(COLOR_OVERLAY_BLUE));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ pub enum ToolMessage {
|
|||
SelectRandomWorkingColor {
|
||||
primary: bool,
|
||||
},
|
||||
ToggleSelectVsPath,
|
||||
SwapColors,
|
||||
Undo,
|
||||
UpdateCursor,
|
||||
|
|
|
|||
|
|
@ -284,6 +284,16 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::ToggleSelectVsPath => {
|
||||
// If we have the select tool active, toggle to the path tool and vice versa
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
let active_tool_type = tool_data.active_tool_type;
|
||||
if active_tool_type == ToolType::Select {
|
||||
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Path });
|
||||
} else {
|
||||
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
|
||||
}
|
||||
}
|
||||
ToolMessage::SwapColors => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
|
||||
|
|
@ -359,9 +369,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
|
||||
ActivateToolBrush,
|
||||
|
||||
ToggleSelectVsPath,
|
||||
|
||||
SelectRandomWorkingColor,
|
||||
ResetColors,
|
||||
SwapColors,
|
||||
|
||||
Undo,
|
||||
);
|
||||
list.extend(self.tool_state.tool_data.active_tool().actions());
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use super::select_tool::extend_lasso;
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE,
|
||||
SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, DRILL_THROUGH_THRESHOLD,
|
||||
HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, 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};
|
||||
|
|
@ -20,6 +20,7 @@ use crate::messages::tool::common_functionality::utility_functions::{calculate_s
|
|||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
|
||||
use std::vec;
|
||||
|
|
@ -516,6 +517,11 @@ struct PathToolData {
|
|||
started_drawing_from_inside: bool,
|
||||
first_selected_with_single_click: bool,
|
||||
stored_selection: Option<HashMap<LayerNodeIdentifier, SelectedLayerState>>,
|
||||
last_drill_through_click_position: Option<DVec2>,
|
||||
drill_through_cycle_index: usize,
|
||||
drill_through_cycle_count: usize,
|
||||
hovered_layers: Vec<LayerNodeIdentifier>,
|
||||
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
|
|
@ -524,10 +530,6 @@ impl PathToolData {
|
|||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
}
|
||||
|
||||
fn remove_saved_points(&mut self) {
|
||||
self.saved_points_before_anchor_select_toggle.clear();
|
||||
}
|
||||
|
||||
pub fn selection_quad(&self, metadata: &DocumentMetadata) -> Quad {
|
||||
let bbox = self.selection_box(metadata);
|
||||
Quad::from_box(bbox)
|
||||
|
|
@ -576,6 +578,49 @@ impl PathToolData {
|
|||
self.selection_status = selection_status;
|
||||
}
|
||||
|
||||
fn remove_saved_points(&mut self) {
|
||||
self.saved_points_before_anchor_select_toggle.clear();
|
||||
}
|
||||
|
||||
fn reset_drill_through_cycle(&mut self) {
|
||||
self.last_drill_through_click_position = None;
|
||||
self.drill_through_cycle_index = 0;
|
||||
}
|
||||
|
||||
fn next_drill_through_cycle(&mut self, position: DVec2) -> usize {
|
||||
if self.last_drill_through_click_position.map_or(true, |last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD) {
|
||||
// New position, reset cycle
|
||||
self.drill_through_cycle_index = 0;
|
||||
} else {
|
||||
// Same position, advance cycle
|
||||
self.drill_through_cycle_index = (self.drill_through_cycle_index + 1) % self.drill_through_cycle_count.max(1);
|
||||
}
|
||||
self.last_drill_through_click_position = Some(position);
|
||||
self.drill_through_cycle_index
|
||||
}
|
||||
|
||||
fn peek_drill_through_index(&self) -> usize {
|
||||
if self.drill_through_cycle_count == 0 {
|
||||
0
|
||||
} else {
|
||||
(self.drill_through_cycle_index + 1) % self.drill_through_cycle_count.max(1)
|
||||
}
|
||||
}
|
||||
|
||||
fn has_drill_through_mouse_moved(&self, position: DVec2) -> bool {
|
||||
self.last_drill_through_click_position.map_or(true, |last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD)
|
||||
}
|
||||
|
||||
fn set_ghost_outline(&mut self, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
|
||||
self.ghost_outline.clear();
|
||||
for &layer in shape_editor.selected_shape_state.keys() {
|
||||
// We probably need to collect here
|
||||
let outline: Vec<ClickTargetType> = document.metadata().layer_with_free_points_outline(layer).cloned().collect();
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
self.ghost_outline.push((outline, transform));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This function is for basic point select mode. We definitely need to make a new one for the segment select mode.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn mouse_down(
|
||||
|
|
@ -619,6 +664,8 @@ impl PathToolData {
|
|||
) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
self.set_ghost_outline(shape_editor, document);
|
||||
|
||||
self.last_clicked_point_was_selected = already_selected;
|
||||
|
||||
// If the point is already selected and shift (`extend_selection`) is used, keep the selection unchanged.
|
||||
|
|
@ -703,6 +750,8 @@ impl PathToolData {
|
|||
else if let Some(segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
self.set_ghost_outline(shape_editor, document);
|
||||
|
||||
if segment_editing_mode && !molding_in_segment_edit {
|
||||
let layer = segment.layer();
|
||||
let segment_id = segment.segment();
|
||||
|
|
@ -723,7 +772,6 @@ impl PathToolData {
|
|||
}
|
||||
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
|
||||
let viewport_to_document = document.metadata().document_to_viewport.inverse();
|
||||
self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position);
|
||||
|
||||
|
|
@ -746,6 +794,8 @@ impl PathToolData {
|
|||
else if let Some(layer) = document.click(input) {
|
||||
if shape_editor.selected_shape_state.is_empty() {
|
||||
self.first_selected_with_single_click = true;
|
||||
// This ensures we don't need to double click a second time to get the drill through to work
|
||||
self.last_drill_through_click_position = Some(input.mouse.position);
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
}
|
||||
|
||||
|
|
@ -1408,6 +1458,7 @@ impl Fsm for PathToolFsmState {
|
|||
(_, PathToolMessage::SelectionChanged) => {
|
||||
// Set the newly targeted layers to visible
|
||||
let target_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect();
|
||||
|
||||
shape_editor.set_selected_layers(target_layers);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
|
@ -1426,6 +1477,12 @@ impl Fsm for PathToolFsmState {
|
|||
self
|
||||
}
|
||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||
if matches!(self, Self::Dragging(_)) {
|
||||
for (outline, transform) in &tool_data.ghost_outline {
|
||||
overlay_context.outline(outline.iter(), *transform, Some(COLOR_OVERLAY_GRAY));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: find the segment ids of which the selected points are a part of
|
||||
|
||||
match tool_options.path_overlay_mode {
|
||||
|
|
@ -1527,6 +1584,34 @@ impl Fsm for PathToolFsmState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show outlines for hovered layers with appropriate highlighting
|
||||
let currently_selected_layer = document.network_interface.selected_nodes().selected_layers(document.metadata()).next();
|
||||
let next_selected_index = tool_data.peek_drill_through_index();
|
||||
let mouse_has_moved = tool_data.has_drill_through_mouse_moved(input.mouse.position);
|
||||
|
||||
for (index, &hovered_layer) in tool_data.hovered_layers.iter().enumerate() {
|
||||
// Skip already highlighted selected layer
|
||||
if Some(hovered_layer) == currently_selected_layer {
|
||||
continue;
|
||||
}
|
||||
|
||||
let layer_to_viewport = document.metadata().transform_to_viewport(hovered_layer);
|
||||
let outline = document.metadata().layer_with_free_points_outline(hovered_layer);
|
||||
|
||||
// Determine highlight color based on drill-through state
|
||||
let color = match (index, mouse_has_moved) {
|
||||
// If the layer is the next selected one and mouse has not moved, highlight it blue
|
||||
(i, false) if i == next_selected_index => COLOR_OVERLAY_BLUE,
|
||||
// If the layer is the first hovered one and mouse has moved, highlight it blue
|
||||
(0, true) => COLOR_OVERLAY_BLUE,
|
||||
// Otherwise, use gray
|
||||
_ => COLOR_OVERLAY_GRAY,
|
||||
};
|
||||
|
||||
// TODO: Make this draw underneath all other overlays
|
||||
overlay_context.outline(outline, layer_to_viewport, Some(color));
|
||||
}
|
||||
}
|
||||
Self::Drawing { selection_shape } => {
|
||||
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
|
||||
|
|
@ -1794,6 +1879,23 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.adjacent_anchor_offset = None;
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
if tool_data.has_drill_through_mouse_moved(input.mouse.position) {
|
||||
tool_data.reset_drill_through_cycle();
|
||||
}
|
||||
|
||||
// When moving the cursor around we want to update the hovered layers
|
||||
let new_hovered_layers: Vec<LayerNodeIdentifier> = document
|
||||
.click_list_no_parents(input)
|
||||
.filter(|&layer| {
|
||||
// Filter out artboards and parent holders, and already selected layers
|
||||
!document.network_interface.is_artboard(&layer.to_node(), &[])
|
||||
})
|
||||
.collect();
|
||||
|
||||
if tool_data.hovered_layers != new_hovered_layers {
|
||||
tool_data.hovered_layers = new_hovered_layers;
|
||||
}
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
self
|
||||
|
|
@ -1994,6 +2096,7 @@ impl Fsm for PathToolFsmState {
|
|||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::DragStop { extend_selection, .. }) => {
|
||||
tool_data.ghost_outline.clear();
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
|
||||
|
||||
|
|
@ -2137,6 +2240,20 @@ impl Fsm for PathToolFsmState {
|
|||
(_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => {
|
||||
// Double-clicked on a point (flip smooth/sharp behavior)
|
||||
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
|
||||
|
||||
let mut get_drill_through_layer = || -> Option<LayerNodeIdentifier> {
|
||||
let drill_through_layers = document.click_list_no_parents(input).collect::<Vec<LayerNodeIdentifier>>();
|
||||
if drill_through_layers.is_empty() {
|
||||
tool_data.reset_drill_through_cycle();
|
||||
None
|
||||
} else {
|
||||
tool_data.drill_through_cycle_count = drill_through_layers.len();
|
||||
let cycle_index = tool_data.next_drill_through_cycle(input.mouse.position);
|
||||
let layer = drill_through_layers.get(cycle_index);
|
||||
if cycle_index == 0 { drill_through_layers.first().copied() } else { layer.copied() }
|
||||
}
|
||||
};
|
||||
|
||||
if nearest_point.is_some() {
|
||||
// Flip the selected point between smooth and sharp
|
||||
if !tool_data.double_click_handled && tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {
|
||||
|
|
@ -2153,17 +2270,17 @@ impl Fsm for PathToolFsmState {
|
|||
return PathToolFsmState::Ready;
|
||||
}
|
||||
// Double-clicked on a filled region
|
||||
else if let Some(layer) = document.click(input) {
|
||||
else if let Some(layer) = &get_drill_through_layer() {
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
let shrink_selection = input.keyboard.get(shrink_selection as usize);
|
||||
|
||||
if shape_editor.is_selected_layer(layer) {
|
||||
if shape_editor.is_selected_layer(*layer) {
|
||||
if extend_selection && !tool_data.first_selected_with_single_click {
|
||||
responses.add(NodeGraphMessage::SelectedNodesRemove { nodes: vec![layer.to_node()] });
|
||||
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let mut selection = selection.clone();
|
||||
selection.remove(&layer);
|
||||
selection.remove(layer);
|
||||
shape_editor.selected_shape_state = selection;
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
|
|
@ -2175,19 +2292,19 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.stored_selection = None;
|
||||
}
|
||||
|
||||
let state = shape_editor.selected_shape_state.get_mut(&layer).expect("No state for selected layer");
|
||||
let state = shape_editor.selected_shape_state.get_mut(layer).expect("No state for selected layer");
|
||||
state.deselect_all_points_in_layer();
|
||||
state.deselect_all_segments_in_layer();
|
||||
} else if !tool_data.first_selected_with_single_click {
|
||||
// Select according to the selected editing mode
|
||||
let point_editing_mode = tool_options.path_editing_mode.point_editing_mode;
|
||||
let segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||
shape_editor.select_connected(document, layer, input.mouse.position, point_editing_mode, segment_editing_mode);
|
||||
shape_editor.select_connected(document, *layer, input.mouse.position, point_editing_mode, segment_editing_mode);
|
||||
|
||||
// Select all the other layers back again
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let mut selection = selection.clone();
|
||||
selection.remove(&layer);
|
||||
selection.remove(layer);
|
||||
|
||||
for (layer, state) in selection {
|
||||
shape_editor.selected_shape_state.insert(layer, state);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, SLOWING_DIVISOR};
|
||||
use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY_25, SLOWING_DIVISOR};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::{DocumentPosition, ViewportPosition};
|
||||
use crate::messages::portfolio::document::overlays::utility_types::{OverlayProvider, Pivot};
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
|
|
@ -12,6 +12,7 @@ use crate::messages::tool::utility_types::{ToolData, ToolType};
|
|||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::ManipulatorPointId;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::{VectorData, VectorModificationType};
|
||||
use std::f64::consts::{PI, TAU};
|
||||
|
||||
|
|
@ -61,6 +62,9 @@ pub struct TransformLayerMessageHandler {
|
|||
handle: DVec2,
|
||||
last_point: DVec2,
|
||||
grs_pen_handle: bool,
|
||||
|
||||
// Ghost outlines for Path Tool
|
||||
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
|
||||
}
|
||||
|
||||
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
|
||||
|
|
@ -171,6 +175,12 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||
return;
|
||||
}
|
||||
|
||||
if using_path_tool {
|
||||
for (outline, transform) in &self.ghost_outline {
|
||||
overlay_context.outline(outline.iter(), *transform, Some(COLOR_OVERLAY_GRAY_25));
|
||||
}
|
||||
}
|
||||
|
||||
let viewport_box = input.viewport_bounds.size();
|
||||
let axis_constraint = self.transform_operation.axis_constraint();
|
||||
|
||||
|
|
@ -284,6 +294,10 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
if using_path_tool {
|
||||
self.ghost_outline.clear();
|
||||
}
|
||||
|
||||
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
||||
|
||||
if final_transform {
|
||||
|
|
@ -337,13 +351,16 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||
let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect();
|
||||
let selected_segments = shape_editor.selected_segments().collect::<Vec<_>>();
|
||||
|
||||
if (using_path_tool && selected_points.is_empty() && selected_segments.is_empty())
|
||||
if using_path_tool {
|
||||
Self::set_ghost_outline(&mut self.ghost_outline, shape_editor, document);
|
||||
if (selected_points.is_empty() && selected_segments.is_empty())
|
||||
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|
||||
|| selected_layers.is_empty()
|
||||
|| transform_type.equivalent_to(self.transform_operation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
if let [point] = selected_points.as_slice() {
|
||||
|
|
@ -399,6 +416,8 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||
|
||||
responses.add(PenToolMessage::Abort);
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
} else if using_path_tool {
|
||||
self.ghost_outline.clear();
|
||||
} else {
|
||||
selected.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
|
@ -657,6 +676,16 @@ impl TransformLayerMessageHandler {
|
|||
pub fn hints(&self, responses: &mut VecDeque<Message>) {
|
||||
self.transform_operation.hints(responses, self.local);
|
||||
}
|
||||
|
||||
fn set_ghost_outline(ghost_outline: &mut Vec<(Vec<ClickTargetType>, DAffine2)>, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
|
||||
ghost_outline.clear();
|
||||
for &layer in shape_editor.selected_shape_state.keys() {
|
||||
// We probably need to collect here
|
||||
let outline = document.metadata().layer_with_free_points_outline(layer).cloned().collect();
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
ghost_outline.push((outline, transform));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_pivot(
|
||||
|
|
|
|||
Loading…
Reference in New Issue