mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 06:58:11 +08:00
Properly track the Text node's text frame via the attribute system to fix misalignment (#4097)
* Compensate for upstream row-0 transform absorption in viewport-space 'TransformSet' * Add 'editor:text_frame' row attribute so the Text tool's drag cage tracks multi-row text * "Separate Glyph Elements" -> "Separate Glyphs" * Improve artboard migration robustness from older documents * Code review * Make the tools visualize the text frame based on attribute not upstream node
This commit is contained in:
@@ -218,6 +218,9 @@ pub enum DocumentMessage {
|
||||
UpdateOutlines {
|
||||
outlines: HashMap<NodeId, Vec<Arc<ClickTarget>>>,
|
||||
},
|
||||
UpdateTextFrames {
|
||||
text_frames: HashMap<NodeId, DAffine2>,
|
||||
},
|
||||
UpdateClipTargets {
|
||||
clip_targets: HashSet<NodeId>,
|
||||
},
|
||||
|
||||
@@ -1283,6 +1283,19 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
||||
.collect();
|
||||
self.network_interface.update_outlines(layer_outlines);
|
||||
}
|
||||
DocumentMessage::UpdateTextFrames { text_frames } => {
|
||||
let layer_text_frames = text_frames
|
||||
.into_iter()
|
||||
.filter(|(node_id, _)| self.network_interface.document_network().nodes.contains_key(node_id))
|
||||
.filter_map(|(node_id, frame)| {
|
||||
self.network_interface.is_layer(&node_id, &[]).then(|| {
|
||||
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface);
|
||||
(layer, frame)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
self.network_interface.update_text_frames(layer_text_frames);
|
||||
}
|
||||
DocumentMessage::UpdateClipTargets { clip_targets } => {
|
||||
self.network_interface.update_clip_targets(clip_targets);
|
||||
}
|
||||
|
||||
@@ -652,15 +652,30 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
// Get the existing upstream Transform node, if present
|
||||
let transform_node_id = self.existing_network_node_id("Transform", false);
|
||||
|
||||
// Get a transform appropriate for the requested space
|
||||
let to_transform = match transform_in {
|
||||
TransformIn::Local => DAffine2::IDENTITY,
|
||||
TransformIn::Scope { scope } => scope,
|
||||
TransformIn::Viewport => self.network_interface.document_metadata().downstream_transform_to_viewport(self.layer_node.unwrap()).inverse(),
|
||||
// Compute the Transform node value so `transform_to_viewport` matches the target after re-render
|
||||
let final_transform = match transform_in {
|
||||
TransformIn::Local => transform,
|
||||
TransformIn::Scope { scope } => scope * transform,
|
||||
TransformIn::Viewport => {
|
||||
let Some(layer) = self.layer_node else { return };
|
||||
let metadata = self.network_interface.document_metadata();
|
||||
let parent_inverse = metadata.downstream_transform_to_viewport(layer).inverse();
|
||||
|
||||
// Compensate for item 0's baseline offset (multi-item Text only) so the layer doesn't jump by it.
|
||||
// Gated on `text_frames` because metadata can be stale mid-handler.
|
||||
if metadata.text_frames.contains_key(&layer) {
|
||||
let local_transform = metadata.local_transforms.get(&layer.to_node()).copied().unwrap_or(DAffine2::IDENTITY);
|
||||
let current_transform_node_value = transform_node_id
|
||||
.and_then(|id| self.network_interface.document_network().nodes.get(&id))
|
||||
.map(|node| transform_utils::get_current_transform(&node.inputs))
|
||||
.unwrap_or(DAffine2::IDENTITY);
|
||||
parent_inverse * transform * local_transform.inverse() * current_transform_node_value
|
||||
} else {
|
||||
parent_inverse * transform
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Set the transform value to the Transform node
|
||||
let final_transform = to_transform * transform;
|
||||
self.transform_set_direct(final_transform, skip_rerender, transform_node_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ pub struct DocumentMetadata {
|
||||
/// Source-geometry outlines for hover/selection overlays, separate from `click_targets` so
|
||||
/// nodes with an `editor:click_target` override still outline the precise geometry.
|
||||
pub outlines: HashMap<LayerNodeIdentifier, Vec<Arc<ClickTarget>>>,
|
||||
/// Per-layer text frame from item 0's `editor:text_frame` attribute (`DAffine2` mapping the (0, 0)-(1, 1) unit square onto the frame).
|
||||
/// The Text tool composes this with `transform_to_viewport(layer)` to position its drag cage.
|
||||
pub text_frames: HashMap<LayerNodeIdentifier, DAffine2>,
|
||||
pub clip_targets: HashSet<NodeId>,
|
||||
pub vector_modify: HashMap<NodeId, Vector>,
|
||||
/// Vector data keyed by layer ID, used as fallback when no Path node exists.
|
||||
@@ -163,6 +166,12 @@ impl DocumentMetadata {
|
||||
self.outlines.get(&layer).or_else(|| self.click_targets.get(&layer)).map(|v| v.as_slice())
|
||||
}
|
||||
|
||||
/// Whether to treat this layer as a text layer for tool UI. Checks the surfaced `editor:text_frame`
|
||||
/// metadata rather than upstream topology, so layers that strip the attribute downstream correctly drop out.
|
||||
pub fn is_text_layer(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.text_frames.contains_key(&layer)
|
||||
}
|
||||
|
||||
/// Get the bounding box of the click target of the specified layer in the specified transform space
|
||||
pub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.visual_targets(layer)?
|
||||
|
||||
@@ -3277,6 +3277,7 @@ impl NodeNetworkInterface {
|
||||
self.document_metadata.vector_modify.retain(|node, _| nodes.contains(node));
|
||||
self.document_metadata.click_targets.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
|
||||
self.document_metadata.outlines.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
|
||||
self.document_metadata.text_frames.retain(|layer, _| self.document_metadata.structure.contains_key(layer));
|
||||
}
|
||||
|
||||
/// Update the cached transforms of the layers
|
||||
@@ -3300,6 +3301,12 @@ impl NodeNetworkInterface {
|
||||
self.document_metadata.outlines = new_outlines;
|
||||
}
|
||||
|
||||
/// Update the cached per-layer 'Text' node text frames in row-local space (as `DAffine2`
|
||||
/// mapping the unit square onto the frame).
|
||||
pub fn update_text_frames(&mut self, new_text_frames: HashMap<LayerNodeIdentifier, DAffine2>) {
|
||||
self.document_metadata.text_frames = new_text_frames;
|
||||
}
|
||||
|
||||
/// Update the cached clip targets of the layers
|
||||
pub fn update_clip_targets(&mut self, new_clip_targets: HashSet<NodeId>) {
|
||||
self.document_metadata.clip_targets = new_clip_targets;
|
||||
|
||||
Reference in New Issue
Block a user