Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::utility_types::{DrawHandles, OverlayContext};
use crate::consts::HIDE_HANDLE_DISTANCE;
use crate::consts::{HIDE_HANDLE_DISTANCE, SNAP_POINT_TOLERANCE};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
pub use crate::messages::portfolio::document::utility_types::text_metrics::text_width;
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
use crate::messages::tool::common_functionality::utility_functions::closest_open_path_endpoint;
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2};
use graphene_std::vector::misc::{BezierHandles, ManipulatorPointId, point_to_dvec2, segment_to_handles};
Expand Down Expand Up @@ -132,7 +133,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
let display_handles = overlay_context.visibility_settings.handles();
let display_anchors = overlay_context.visibility_settings.anchors();

for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
if display_path {
Expand Down Expand Up @@ -201,6 +202,43 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
}
}

/// Draws an anchor overlay at each endpoint of every open path on the selected visible layers, in the selected style for endpoints that are part of the path editing selection.
/// Given a pointer position, the endpoint a press there would continue from is drawn in the hover style instead.
pub fn open_path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &ShapeState, pointer: Option<DVec2>, overlay_context: &mut OverlayContext) {
if !overlay_context.visibility_settings.anchors() {
return;
}

let selected_nodes = document.network_interface.selected_nodes();
let is_selected = |layer: LayerNodeIdentifier, id: PointId| {
shape_editor
.selected_shape_state
.get(&layer)
.is_some_and(|state| state.is_point_selected(ManipulatorPointId::Anchor(id)))
};
let hovered = pointer.and_then(|pointer| closest_open_path_endpoint(document, pointer, SNAP_POINT_TOLERANCE, selected_nodes.selected_visible_layers(&document.network_interface)));

for layer in selected_nodes.selected_visible_layers(&document.network_interface) {
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);

for id in vector.anchor_endpoints() {
if hovered.is_some_and(|(hovered_layer, hovered_id, _)| hovered_layer == layer && hovered_id == id) {
continue;
}
let Some(position) = vector.point_domain.position_from_id(id) else { continue };

overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(layer, id), None);
}
}

// Drawn last so its halo sits above any other endpoint at the same spot
if let Some((layer, id, position)) = hovered {
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
overlay_context.hover_manipulator_anchor(transform.transform_point2(position), is_selected(layer, id));
}
}

pub fn hex_to_rgba_u8(hex: &str) -> [u8; 4] {
let hex = hex.trim().trim_start_matches('#');
if hex.len() != 6 && hex.len() != 8 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,13 @@ impl NodeNetworkInterface {
self.query(network_path, "is_visible", |view| view.is_visible(node_id)).unwrap_or_default()
}

/// Whether a layer in the document network is visible, which also requires every ancestor to be visible.
pub fn is_layer_visible(&self, layer: LayerNodeIdentifier) -> bool {
layer
.ancestors(self.document_metadata())
.all(|ancestor| ancestor == LayerNodeIdentifier::ROOT_PARENT || self.is_visible(&ancestor.to_node(), &[]))
}

pub fn is_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
self.query(network_path, "is_layer", |view| view.is_layer(node_id)).unwrap_or_default()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,7 @@ pub struct SelectedNodes(pub Vec<NodeId>);

impl SelectedNodes {
pub fn layer_visible(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> bool {
layer.ancestors(network_interface.document_metadata()).all(|layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
network_interface.is_visible(&layer.to_node(), &[])
} else {
true
}
})
network_interface.is_layer_visible(layer)
}

pub fn selected_visible_layers<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
Expand Down
31 changes: 25 additions & 6 deletions editor/src/messages/tool/common_functionality/utility_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ pub fn should_extend(document: &DocumentMessageHandler, goal: DVec2, tolerance:
closest_point(document, goal, tolerance, layers, |_| false)
}

/// Finds the endpoint of an open path closest to the goal (in viewport space) across the given layers, if one lies within the tolerance.
/// Only anchors with a single connected segment qualify, so closed paths are never matched. Returns the endpoint's position in the layer's local space.
pub fn closest_open_path_endpoint(document: &DocumentMessageHandler, goal: DVec2, tolerance: f64, layers: impl Iterator<Item = LayerNodeIdentifier>) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
closest_candidate_point(document, goal, tolerance, layers, |vector| vector.anchor_endpoints().collect())
}

/// Determine the closest point to the goal point under max_distance.
/// Additionally exclude checking closeness to the point which given to exclude() returns true.
pub fn closest_point<T>(
Expand All @@ -35,19 +41,32 @@ pub fn closest_point<T>(
where
T: Fn(PointId) -> bool,
{
closest_candidate_point(document, goal, max_distance, layers, |vector| vector.anchor_points().filter(|&id| !exclude(id)).collect())
}

/// Determines the closest of each visible layer's candidate points to the goal (in viewport space) under max_distance. Returns the point's position in the layer's local space.
fn closest_candidate_point(
document: &DocumentMessageHandler,
goal: DVec2,
max_distance: f64,
layers: impl Iterator<Item = LayerNodeIdentifier>,
candidates: impl Fn(&Vector) -> Vec<PointId>,
) -> Option<(LayerNodeIdentifier, PointId, DVec2)> {
let mut best = None;
let mut best_distance_squared = max_distance * max_distance;

for layer in layers {
let viewspace = document.metadata().transform_to_viewport(layer);
if !document.network_interface.is_layer_visible(layer) {
continue;
}

let viewspace = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
Comment thread
Keavon marked this conversation as resolved.
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for id in vector.anchor_points() {
if exclude(id) {
continue;
}

for id in candidates(&vector) {
let Some(point) = vector.point_domain.position_from_id(id) else { continue };

let distance_squared = viewspace.transform_point2(point).distance_squared(goal);

if distance_squared < best_distance_squared {
best = Some((layer, id, point));
best_distance_squared = distance_squared;
Expand Down
Loading
Loading