From c4c673336cef7be8eb4eec88c18cdc9c88e24f02 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 1 Aug 2026 21:52:25 +0300 Subject: [PATCH] fix(hatch): follow block grip movement --- src/scene/entity.rs | 62 +++++++++++++++++++++++++++++++++++++++++++- src/scene/mod.rs | 22 ++++++++++------ src/scene/preview.rs | 4 +++ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/scene/entity.rs b/src/scene/entity.rs index d604da4a0..23e62aa6a 100644 --- a/src/scene/entity.rs +++ b/src/scene/entity.rs @@ -1026,6 +1026,61 @@ impl Scene { hatch_bg: [f32; 4], tint_selected: bool, frozen: Option<&rustc_hash::FxHashSet>, + ) -> Vec { + self.exploded_insert_hatch_models_filtered( + layout_block, + hatch_bg, + tint_selected, + frozen, + None, + false, + ) + } + + /// Build live hatch overlays for INSERT grip previews. The edited INSERT + /// is intentionally hidden from the resident scene while its current + /// document entity moves, so include that hidden target and reuse the full + /// block-expansion path for nested inserts, inherited styles and XCLIP. + pub(super) fn preview_insert_hatch_models(&self, handles: &[Handle]) -> Vec { + let targets: rustc_hash::FxHashSet = handles + .iter() + .copied() + .filter(|&handle| { + matches!(self.document.get_entity(handle), Some(EntityType::Insert(_))) + }) + .collect(); + if targets.is_empty() { + return Vec::new(); + } + let hatch_bg = if self.current_layout != "Model" { + self.paper_bg_color + } else { + self.bg_color + }; + let frozen: rustc_hash::FxHashSet = self + .interaction_viewport_frozen_layers() + .into_iter() + .flatten() + .copied() + .collect(); + self.exploded_insert_hatch_models_filtered( + self.interaction_block_handle(), + hatch_bg, + true, + (!frozen.is_empty()).then_some(&frozen), + Some(&targets), + true, + ) + } + + fn exploded_insert_hatch_models_filtered( + &self, + layout_block: Handle, + hatch_bg: [f32; 4], + tint_selected: bool, + frozen: Option<&rustc_hash::FxHashSet>, + targets: Option<&rustc_hash::FxHashSet>, + include_preview_hidden: bool, ) -> Vec { let layer_hidden = |layer: &str| { self.document @@ -1117,8 +1172,13 @@ impl Scene { let EntityType::Insert(ins) = contextual.as_ref() else { continue; }; + if targets.is_some_and(|targets| !targets.contains(&ins.common.handle)) { + continue; + } + let temporarily_hidden = self.object_isolation.hides(ins.common.handle) + || (!include_preview_hidden && self.preview_hidden.contains(&ins.common.handle)); if ins.common.invisible - || self.entity_temporarily_hidden(ins.common.handle) + || temporarily_hidden || layer_hidden(&ins.common.layer) { continue; diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 0f8ae8ac7..6594a0c7f 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -5104,14 +5104,19 @@ impl Scene { let cache = self.hatch_cache.borrow(); match cache.get(&space) { // Selection tint is baked in, so the selected set must also - // match; category = a changed handle that is a hatch/solid fill. + // match; category = a changed direct fill or an INSERT + // whose expanded block tree can contain hatch fills. Some((cached_epoch, cached_sel, arc)) if *cached_sel == sel_sig && self.category_cache_valid( *cached_epoch, - CACHE_CATEGORY_HATCH, + CACHE_CATEGORY_HATCH | CACHE_CATEGORY_INSERT_HATCH, |h| { - self.hatches.contains_key(&h) + self.hatches.contains_key(&h) + || matches!( + self.document.get_entity(h), + Some(EntityType::Insert(_)) + ) }, ) => { @@ -5149,11 +5154,12 @@ impl Scene { fn selected_hatch_sig(&self) -> u64 { let mut sig = 0u64; let mut count = 0u64; - for h in self - .selected - .iter() - .filter(|handle| self.hatches.contains_key(handle)) - { + for &h in &self.selected { + if !self.hatches.contains_key(&h) + && !matches!(self.document.get_entity(h), Some(EntityType::Insert(_))) + { + continue; + } count += 1; sig ^= h.value().wrapping_mul(0x9E37_79B9_7F4A_7C15); } diff --git a/src/scene/preview.rs b/src/scene/preview.rs index d57c0ee33..740ad3b0b 100644 --- a/src/scene/preview.rs +++ b/src/scene/preview.rs @@ -18,8 +18,12 @@ impl Scene { pub fn set_preview_hatches(&mut self, handles: &[Handle]) { let mut models = Vec::new(); for &handle in handles { + // Keep the existing direct Hatch/Solid preview path. INSERT has no + // entry in `self.hatches`, so this is a no-op for it and the full + // block expansion below supplies its fills. self.append_preview_hatch(handle, &mut models); } + models.extend(self.preview_insert_hatch_models(handles)); self.preview_hatches = std::sync::Arc::new(models); }