mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-30 15:08:12 +08:00
Add lasso selection mode (#2235)
* add document method to check intersection and insideness with polygon * impl lasso selection * impl lasso select to always add to selection * add hint for lasso selection * fix calculating selection mode based on direction on each pointer move * fix lasso polygon lagging behing mouse position * add overlay to draw lasso polygon with fill color same as quad * fix comment * change removing from selection key binding from Sift + Ctrl to just Alt * impl Alt to shrink selection for quad in path tool * refactor rename SelectionType to SelectionShape * impl lasso overlay for path tool * impl selecting anchors and handles intersection lasso in path tool * add keys hint info * fix converting lasso polygon to closed subpath which is has less than two points * Code review * impl preferences-based selection mode to the Path tool for only for overlays, --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
f462963a36
commit
6802365e14
@@ -25,13 +25,14 @@ use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeNetwork, OldNodeNetwork};
|
||||
use graphene_core::raster::image::ImageFrame;
|
||||
use graphene_core::raster::BlendMode;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
use graphene_std::renderer::{ClickTarget, Quad};
|
||||
use graphene_std::vector::path_bool_lib;
|
||||
use graphene_std::vector::{path_bool_lib, PointId};
|
||||
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
|
||||
@@ -1405,6 +1406,19 @@ impl DocumentMessageHandler {
|
||||
self.intersect_quad(viewport_quad, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space subpath
|
||||
pub fn intersect_polygon<'a>(&'a self, mut viewport_polygon: Subpath<PointId>, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
viewport_polygon.apply_transform(document_to_viewport.inverse());
|
||||
|
||||
ClickXRayIter::new(&self.network_interface, XRayTarget::Polygon(viewport_polygon))
|
||||
}
|
||||
|
||||
/// Runs an intersection test with all layers and a viewport space subpath; ignoring artboards
|
||||
pub fn intersect_polygon_no_artboards<'a>(&'a self, viewport_polygon: Subpath<PointId>, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
|
||||
self.intersect_polygon(viewport_polygon, ipp).filter(|layer| !self.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
}
|
||||
|
||||
pub fn is_layer_fully_inside(&self, layer: &LayerNodeIdentifier, quad: graphene_core::renderer::Quad) -> bool {
|
||||
// Get the bounding box of the layer in document space
|
||||
let Some(bounding_box) = self.metadata().bounding_box_viewport(*layer) else { return false };
|
||||
@@ -1428,6 +1442,22 @@ impl DocumentMessageHandler {
|
||||
layer_left >= quad_left && layer_right <= quad_right && layer_top <= quad_top && layer_bottom >= quad_bottom
|
||||
}
|
||||
|
||||
pub fn is_layer_fully_inside_polygon(&self, layer: &LayerNodeIdentifier, ipp: &InputPreprocessorMessageHandler, mut viewport_polygon: Subpath<PointId>) -> bool {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
viewport_polygon.apply_transform(document_to_viewport.inverse());
|
||||
|
||||
let layer_click_targets = self.network_interface.document_metadata().click_targets(*layer);
|
||||
let layer_transform = self.network_interface.document_metadata().transform_to_document(*layer);
|
||||
|
||||
layer_click_targets.is_some_and(|targets| {
|
||||
targets.iter().all(|target| {
|
||||
let mut subpath = target.subpath().clone();
|
||||
subpath.apply_transform(layer_transform);
|
||||
subpath.is_inside_subpath(&viewport_polygon, None, None)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Find all of the layers that were clicked on from a viewport space location
|
||||
pub fn click_xray(&self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
|
||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
@@ -2172,6 +2202,7 @@ enum XRayTarget {
|
||||
Point(DVec2),
|
||||
Quad(Quad),
|
||||
Path(Vec<path_bool_lib::PathSegment>),
|
||||
Polygon(Subpath<PointId>),
|
||||
}
|
||||
|
||||
/// The result for the [`ClickXRayIter`] on the layer
|
||||
@@ -2275,6 +2306,10 @@ impl<'a> ClickXRayIter<'a> {
|
||||
}
|
||||
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
|
||||
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
|
||||
XRayTarget::Polygon(polygon) => {
|
||||
let polygon = polygon.iter_closed().map(|line| path_bool_lib::PathSegment::Line(line.start, line.end)).collect();
|
||||
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,22 @@ impl core::hash::Hash for OverlayContext {
|
||||
|
||||
impl OverlayContext {
|
||||
pub fn quad(&mut self, quad: Quad, color_fill: Option<&str>) {
|
||||
self.dashed_quad(quad, color_fill, None, None, None);
|
||||
self.dashed_polygon(&quad.0, color_fill, None, None, None);
|
||||
}
|
||||
|
||||
pub fn dashed_quad(&mut self, quad: Quad, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
self.dashed_polygon(&quad.0, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||
}
|
||||
|
||||
pub fn polygon(&mut self, polygon: &[DVec2], color_fill: Option<&str>) {
|
||||
self.dashed_polygon(&polygon, color_fill, None, None, None);
|
||||
}
|
||||
|
||||
pub fn dashed_polygon(&mut self, polygon: &[DVec2], color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||
if polygon.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
// Set the dash pattern
|
||||
@@ -63,10 +75,10 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.render_context.move_to(quad.0[3].x.round() - 0.5, quad.0[3].y.round() - 0.5);
|
||||
self.render_context.move_to(polygon.last().unwrap().x.round() - 0.5, polygon.last().unwrap().y.round() - 0.5);
|
||||
|
||||
for i in 0..4 {
|
||||
self.render_context.line_to(quad.0[i].x.round() - 0.5, quad.0[i].y.round() - 0.5);
|
||||
for point in polygon {
|
||||
self.render_context.line_to(point.x.round() - 0.5, point.y.round() - 0.5);
|
||||
}
|
||||
|
||||
if let Some(color_fill) = color_fill {
|
||||
|
||||
Reference in New Issue
Block a user