mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-09-09 22:46:16 +00:00
Factor in artboard clipping to the click target x-ray function (#2028)
* Add clipping to the click xray function * Clip area rays as well * Code review nits --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
3b0e9587eb
commit
d6f46f73da
11 changed files with 204 additions and 57 deletions
|
@ -17,7 +17,7 @@ use dyn_any::DynAny;
|
|||
use base64::Engine;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use num_traits::Zero;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
#[cfg(feature = "vello")]
|
||||
use vello::*;
|
||||
|
@ -57,34 +57,28 @@ impl ClickTarget {
|
|||
self.bounding_box = self.subpath.bounding_box();
|
||||
}
|
||||
|
||||
/// Does the click target intersect the rectangle
|
||||
pub fn intersect_rectangle(&self, document_quad: Quad, layer_transform: DAffine2) -> bool {
|
||||
/// Does the click target intersect the path
|
||||
pub fn intersect_path<It: Iterator<Item = bezier_rs::Bezier>>(&self, mut bezier_iter: impl FnMut() -> It, layer_transform: DAffine2) -> bool {
|
||||
// Check if the matrix is not invertible
|
||||
if layer_transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
return false;
|
||||
}
|
||||
let quad = layer_transform.inverse() * document_quad;
|
||||
let inverse = layer_transform.inverse();
|
||||
let mut bezier_iter = || bezier_iter().map(|bezier| bezier.apply_transformation(|point| inverse.transform_point2(point)));
|
||||
|
||||
// Check if outlines intersect
|
||||
if self
|
||||
.subpath
|
||||
.iter()
|
||||
.any(|path_segment| quad.bezier_lines().any(|line| !path_segment.intersections(&line, None, None).is_empty()))
|
||||
{
|
||||
let outline_intersects = |path_segment: bezier_rs::Bezier| bezier_iter().any(|line| !path_segment.intersections(&line, None, None).is_empty());
|
||||
if self.subpath.iter().any(outline_intersects) {
|
||||
return true;
|
||||
}
|
||||
// Check if selection is entirely within the shape
|
||||
if self.subpath.closed() && self.subpath.contains_point(quad.center()) {
|
||||
if self.subpath.closed() && bezier_iter().next().is_some_and(|bezier| self.subpath.contains_point(bezier.start)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if shape is entirely within selection
|
||||
self.subpath
|
||||
.manipulator_groups()
|
||||
.first()
|
||||
.map(|group| group.anchor)
|
||||
.map(|shape_point| quad.contains(shape_point))
|
||||
.unwrap_or_default()
|
||||
let any_point_from_subpath = self.subpath.manipulator_groups().first().map(|group| group.anchor);
|
||||
any_point_from_subpath.is_some_and(|shape_point| bezier_iter().map(|bezier| bezier.winding(shape_point)).sum::<i32>() != 0)
|
||||
}
|
||||
|
||||
/// Does the click target intersect the point (accounting for stroke size)
|
||||
|
@ -102,7 +96,7 @@ impl ClickTarget {
|
|||
// Allows for selecting lines
|
||||
// TODO: actual intersection of stroke
|
||||
let inflated_quad = Quad::from_box(target_bounds);
|
||||
self.intersect_rectangle(inflated_quad, layer_transform)
|
||||
self.intersect_path(|| inflated_quad.bezier_lines(), layer_transform)
|
||||
}
|
||||
|
||||
/// Does the click target intersect the point (not accounting for stroke size)
|
||||
|
@ -277,6 +271,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
|
|||
pub struct RenderMetadata {
|
||||
pub footprints: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
pub click_targets: HashMap<NodeId, Vec<ClickTarget>>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
}
|
||||
|
||||
pub trait GraphicElementRendered {
|
||||
|
@ -650,6 +645,9 @@ impl GraphicElementRendered for Artboard {
|
|||
let subpath = Subpath::new_rect(DVec2::ZERO, self.dimensions.as_dvec2());
|
||||
metadata.click_targets.insert(element_id, vec![ClickTarget::new(subpath, 0.)]);
|
||||
metadata.footprints.insert(element_id, (footprint, DAffine2::from_translation(self.location.as_dvec2())));
|
||||
if self.clip {
|
||||
metadata.clip_targets.insert(element_id);
|
||||
}
|
||||
}
|
||||
footprint.transform *= self.transform();
|
||||
self.graphic_group.collect_metadata(metadata, footprint, None);
|
||||
|
|
|
@ -435,9 +435,9 @@ fn construct_closed_subpath() {
|
|||
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
|
||||
let vector_data = VectorData::from_subpath(&circle);
|
||||
assert_eq!(vector_data.point_domain.ids().len(), 4);
|
||||
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
|
||||
assert_eq!(bézier_paths.len(), 4);
|
||||
assert!(bézier_paths.iter().all(|&bézier| circle.iter().any(|original_bézier| original_bézier == bézier)));
|
||||
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths.len(), 4);
|
||||
assert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier)));
|
||||
|
||||
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
|
||||
assert_subpath_eq(&generated, &[circle]);
|
||||
|
@ -445,12 +445,12 @@ fn construct_closed_subpath() {
|
|||
|
||||
#[test]
|
||||
fn construct_open_subpath() {
|
||||
let bézier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||
let subpath = bezier_rs::Subpath::from_bezier(&bézier);
|
||||
let bezier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
|
||||
let subpath = bezier_rs::Subpath::from_bezier(&bezier);
|
||||
let vector_data = VectorData::from_subpath(&subpath);
|
||||
assert_eq!(vector_data.point_domain.ids().len(), 2);
|
||||
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
|
||||
assert_eq!(bézier_paths, vec![bézier]);
|
||||
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths, vec![bezier]);
|
||||
|
||||
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
|
||||
assert_subpath_eq(&generated, &[subpath]);
|
||||
|
@ -465,9 +465,9 @@ fn construct_many_subpath() {
|
|||
let vector_data = VectorData::from_subpaths([&curve, &circle], false);
|
||||
assert_eq!(vector_data.point_domain.ids().len(), 6);
|
||||
|
||||
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
|
||||
assert_eq!(bézier_paths.len(), 5);
|
||||
assert!(bézier_paths.iter().all(|&bézier| circle.iter().chain(curve.iter()).any(|original_bézier| original_bézier == bézier)));
|
||||
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
|
||||
assert_eq!(bezier_paths.len(), 5);
|
||||
assert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier)));
|
||||
|
||||
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
|
||||
assert_subpath_eq(&generated, &[curve, circle]);
|
||||
|
|
|
@ -5,6 +5,7 @@ use graphene_core::transform::Transform;
|
|||
use graphene_core::vector::misc::BooleanOperation;
|
||||
pub use graphene_core::vector::*;
|
||||
use graphene_core::{Color, GraphicElement, GraphicGroup};
|
||||
pub use path_bool as path_bool_lib;
|
||||
use path_bool::FillRule;
|
||||
use path_bool::PathBooleanOperation;
|
||||
|
||||
|
@ -342,6 +343,6 @@ fn boolean_subtract(a: Path, b: Path) -> Vec<Path> {
|
|||
path_bool(a, b, PathBooleanOperation::Difference)
|
||||
}
|
||||
|
||||
fn boolean_intersect(a: Path, b: Path) -> Vec<Path> {
|
||||
pub fn boolean_intersect(a: Path, b: Path) -> Vec<Path> {
|
||||
path_bool(a, b, PathBooleanOperation::Intersection)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ use graphene_core::{Color, WasmNotSend};
|
|||
use base64::Engine;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use glam::DAffine2;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::Clamped;
|
||||
|
@ -225,6 +225,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
|
|||
let mut metadata = RenderMetadata {
|
||||
footprints: HashMap::new(),
|
||||
click_targets: HashMap::new(),
|
||||
clip_targets: HashSet::new(),
|
||||
};
|
||||
data.collect_metadata(&mut metadata, footprint, None);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue