mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 02:48:12 +08:00
Add custom click targets to make Text node output easier to select (#4095)
* Stop pushing duplicate layer entries when re-clicking an already-selected layer * Make Text node generate per-glyph bounding box click targets * Show source-geometry outlines and aggregate all rows for layer click targets * Strip 'editor:click_target' override on Path node so direct edits restore precise hit testing * Fix inverting a zero-determinate transform
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use core_types::ATTR_TRANSFORM;
|
||||
use core_types::table::{Table, TableRow};
|
||||
use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_TRANSFORM};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use parley::GlyphRun;
|
||||
use skrifa::GlyphId;
|
||||
@@ -15,6 +15,8 @@ pub struct PathBuilder {
|
||||
origin: DVec2,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
pub vector_table: Table<Vector>,
|
||||
/// Per-glyph bbox rectangles collected in single-row mode, published as `ATTR_EDITOR_CLICK_TARGET` in `finalize()`.
|
||||
merged_click_target_subpaths: Vec<Subpath<PointId>>,
|
||||
scale: f64,
|
||||
id: PointId,
|
||||
}
|
||||
@@ -25,6 +27,7 @@ impl PathBuilder {
|
||||
current_subpath: Subpath::new(Vec::new(), false),
|
||||
glyph_subpaths: Vec::new(),
|
||||
vector_table: if per_glyph_items { Table::new() } else { Table::new_from_element(Vector::default()) },
|
||||
merged_click_target_subpaths: Vec::new(),
|
||||
scale,
|
||||
id: PointId::ZERO,
|
||||
origin: DVec2::default(),
|
||||
@@ -51,14 +54,24 @@ impl PathBuilder {
|
||||
glyph_subpath.apply_transform(skew);
|
||||
}
|
||||
|
||||
// Bounding-box rectangle for click-targeting the glyph's full bounds (not just the letterform)
|
||||
let glyph_bbox_rectangle = subpaths_bounding_box(&self.glyph_subpaths).map(|[min, max]| Subpath::new_rectangle(min, max));
|
||||
|
||||
if per_glyph_items {
|
||||
self.vector_table
|
||||
.push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset)));
|
||||
let row = TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset));
|
||||
let row = match glyph_bbox_rectangle {
|
||||
Some(rect) => row.with_attribute(ATTR_EDITOR_CLICK_TARGET, Vector::from_subpaths([rect], false)),
|
||||
None => row,
|
||||
};
|
||||
self.vector_table.push(row);
|
||||
} else {
|
||||
for subpath in self.glyph_subpaths.drain(..) {
|
||||
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Table<Vector>` item
|
||||
self.vector_table.element_mut(0).unwrap().append_subpath(subpath, false);
|
||||
}
|
||||
if let Some(rect) = glyph_bbox_rectangle {
|
||||
self.merged_click_target_subpaths.push(rect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,10 +133,24 @@ impl PathBuilder {
|
||||
if self.vector_table.is_empty() {
|
||||
self.vector_table = Table::new_from_element(Vector::default());
|
||||
}
|
||||
|
||||
// With "Separate Glyph Elements" inactive, combine the accumulated per-glyph AABBs as one override `Vector`
|
||||
if !self.merged_click_target_subpaths.is_empty() {
|
||||
self.vector_table
|
||||
.set_attribute(ATTR_EDITOR_CLICK_TARGET, 0, Vector::from_subpaths(self.merged_click_target_subpaths, false));
|
||||
}
|
||||
|
||||
self.vector_table
|
||||
}
|
||||
}
|
||||
|
||||
fn subpaths_bounding_box(subpaths: &[Subpath<PointId>]) -> Option<[DVec2; 2]> {
|
||||
subpaths
|
||||
.iter()
|
||||
.filter_map(|subpath| subpath.bounding_box())
|
||||
.reduce(|[a_min, a_max], [b_min, b_max]| [a_min.min(b_min), a_max.max(b_max)])
|
||||
}
|
||||
|
||||
impl OutlinePen for PathBuilder {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
if !self.current_subpath.is_empty() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use core_types::table::Table;
|
||||
use core_types::uuid::NodeId;
|
||||
use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx};
|
||||
use core_types::{ATTR_EDITOR_CLICK_TARGET, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx};
|
||||
use glam::DAffine2;
|
||||
use graphic_types::Vector;
|
||||
use vector_types::vector::VectorModification;
|
||||
@@ -15,6 +15,9 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Bo
|
||||
}
|
||||
modification.apply(vector.element_mut(0).expect("push should give one item"));
|
||||
|
||||
// Drop stale click-target override so hit testing uses the geometry the user is now editing
|
||||
vector.remove_attribute(ATTR_EDITOR_CLICK_TARGET);
|
||||
|
||||
// Set the path to the encapsulating subgraph (drop our own trailing entry from `node_path`),
|
||||
// matching the `path_of_subgraph` proto so editor tools can route data back to the parent layer.
|
||||
let subgraph_path: Table<NodeId> = {
|
||||
|
||||
Reference in New Issue
Block a user