From 038052f5f9b8f335bcf2a2d3e6ca6d0b017e759d Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sun, 21 Jun 2026 11:46:43 +0300 Subject: [PATCH] feat(block): fade context, free editing and a Save/Discard toolbar in REFEDIT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In-place block edit now reads more like a dedicated editor: - Fade everything except the edited entities toward the background (wires, block instances and 3D solid meshes) so the block stands out while the surrounding drawing stays visible for context. - Don't leave a command active on enter — the user edits the block's geometry freely (move, grips, draw, erase) and finishes explicitly. - Show the right-edge vertical toolbar with clickable Save (REFCLOSE_SAVE) and Discard (REFCLOSE_DISCARD) buttons while a session is active, so the edit can be ended without typing. The fade clears on save/discard, restoring full brightness. Co-Authored-By: Claude Opus 4.8 --- src/app/commands.rs | 21 +++++++++--- src/app/view.rs | 10 ++++++ src/modules/draw/modify/refedit.rs | 20 +++++++++++ src/scene/mod.rs | 55 +++++++++++++++++++++++++++--- 4 files changed, 97 insertions(+), 9 deletions(-) diff --git a/src/app/commands.rs b/src/app/commands.rs index 2c07512f9..669447a0e 100644 --- a/src/app/commands.rs +++ b/src/app/commands.rs @@ -2192,6 +2192,13 @@ impl OpenCADStudio { } self.tabs[i].refedit_session.as_mut().unwrap().temp_handles = temp_handles.clone(); + // Fade everything except the entities being edited, so the + // surrounding drawing stays visible for context but the block's + // geometry stands out. (#136) + self.tabs[i] + .scene + .set_refedit_keep(Some(temp_handles.iter().copied().collect())); + // Select the temp entities so user can see what they're editing. self.tabs[i].scene.deselect_all(); for h in &temp_handles { @@ -2199,14 +2206,13 @@ impl OpenCADStudio { } self.tabs[i].dirty = true; + // No active command — the user edits the block's geometry freely + // (move, grips, draw, erase…) and runs REFCLOSE when done. (#136) + self.tabs[i].active_cmd = None; self.command_line.push_info(&format!( - "REFEDIT: Editing block \"{}\". Use REFCLOSE when done.", + "REFEDIT: Editing block \"{}\". Run REFCLOSE to save, REFCLOSE_DISCARD to cancel.", insert.block_name )); - use crate::modules::draw::modify::refedit::RefCloseCommand; - let cmd_obj = RefCloseCommand::new(); - self.command_line.push_info(&cmd_obj.prompt()); - self.tabs[i].active_cmd = Some(Box::new(cmd_obj)); } "REFCLOSE" => { @@ -2291,6 +2297,9 @@ impl OpenCADStudio { "REFCLOSE: Block \"{}\" saved. All references updated.", session.block_name )); + // End the edit fade before rebuilding, so the restored geometry + // recolours bright. (#136) + self.tabs[i].scene.set_refedit_keep(None); // Rebuild hatch/image/mesh caches since block content changed. self.tabs[i].scene.rebuild_derived_caches(); } @@ -2307,6 +2316,8 @@ impl OpenCADStudio { // Remove temp entities without modifying the block. self.tabs[i].scene.erase_entities(&session.temp_handles); self.tabs[i].scene.deselect_all(); + // End the edit fade — restore the drawing to full brightness. + self.tabs[i].scene.set_refedit_keep(None); self.command_line .push_output("REFCLOSE: Changes discarded."); } diff --git a/src/app/view.rs b/src/app/view.rs index 59e688753..cc8bf2952 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -687,6 +687,16 @@ impl OpenCADStudio { } } + // In-place block edit (REFEDIT): right-edge toolbar with Save / Discard + // so the edit can be finished by clicking. (#136) + if tab.refedit_session.is_some() && !tab.is_start { + if let Some(tb) = crate::ui::side_toolbar::view( + &crate::modules::draw::modify::refedit::refedit_tools(), + ) { + viewport_stack = viewport_stack.push(tb); + } + } + // Quick Properties: compact floating property panel on selection, // anchored at the canvas top-left so it doesn't track the cursor. if self.quick_properties && !tab.is_start { diff --git a/src/modules/draw/modify/refedit.rs b/src/modules/draw/modify/refedit.rs index 318b4dcea..bc332d28f 100644 --- a/src/modules/draw/modify/refedit.rs +++ b/src/modules/draw/modify/refedit.rs @@ -17,6 +17,26 @@ use acadrust::{EntityType, Handle}; use glam::Vec3; use crate::command::{CadCommand, CmdResult}; +use crate::modules::{IconKind, ModuleEvent, ToolDef}; + +/// Right-edge side-toolbar buttons shown while a REFEDIT session is active: +/// save the in-place block edit, or discard it. (#136) +pub fn refedit_tools() -> Vec { + vec![ + ToolDef { + id: "REFCLOSE_SAVE", + label: "Save Block Edit", + icon: IconKind::Svg(include_bytes!("../../../../assets/icons/mt_ok.svg")), + event: ModuleEvent::Command("REFCLOSE_SAVE".to_string()), + }, + ToolDef { + id: "REFCLOSE_DISCARD", + label: "Discard Block Edit", + icon: IconKind::Svg(include_bytes!("../../../../assets/icons/mt_cancel.svg")), + event: ModuleEvent::Command("REFCLOSE_DISCARD".to_string()), + }, + ] +} // ── Session state (held in DocumentTab) ─────────────────────────────────── diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 3a74d2764..33e54798c 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -663,6 +663,11 @@ pub struct Scene { /// `tessellate_block`'s visibility test skips these, so they neither /// render nor hit-test until isolation ends. pub hidden: HashSet, + /// During in-place block edit (REFEDIT), the handles of the entities being + /// edited. Everything else is rendered faded toward the background so the + /// edited geometry stands out while the surrounding drawing stays visible + /// for context. `None` = not editing. (#136) + pub refedit_keep: Option>, /// Entity drawn with the selection-highlight colour without being part /// of the real selection — used to preview a row in the cycling list box. pub hover_highlight: Option, @@ -883,6 +888,7 @@ impl Scene { document: CadDocument::new(), selected: HashSet::default(), hidden: HashSet::default(), + refedit_keep: None, hover_highlight: None, transparency_display: true, selection_filter: HashSet::default(), @@ -1097,14 +1103,22 @@ impl Scene { // re-entrantly through `render_style` inside a `&mut self` loop. // Covers both top-level solid meshes and block-definition meshes // (instanced per INSERT), so a solid recolours wherever it lives. + // During REFEDIT, solids outside the edited set render faded. + let bg = self.bg_color; let colors: HashMap = self .meshes .keys() .chain(self.block_meshes.keys()) .filter_map(|&h| { - self.document - .get_entity(h) - .map(|e| (h, self.render_style(e).0)) + self.document.get_entity(h).map(|e| { + let mut c = self.render_style(e).0; + if let Some(keep) = &self.refedit_keep { + if !keep.contains(&h) { + c = crate::scene::cache::block_cache::fade_toward_bg(c, bg); + } + } + (h, c) + }) }) .collect(); for (h, set) in self.meshes.iter_mut().chain(self.block_meshes.iter_mut()) { @@ -1116,6 +1130,31 @@ impl Scene { } } + /// Enter / leave the REFEDIT fade. `keep` holds the edited entities (left + /// bright); everything else renders faded. Re-tessellates wires and + /// recolours solids so the change shows immediately. (#136) + pub fn set_refedit_keep(&mut self, keep: Option>) { + self.refedit_keep = keep; + self.recolor_meshes(); + self.bump_geometry(); + } + + /// Fade the colours of wires that belong to entities outside the REFEDIT + /// keep set (no-op when not editing). The geometry is untouched, so + /// hit-testing still works on faded entities. + fn apply_refedit_fade(&self, wires: &mut [WireModel], bg: [f32; 4]) { + let Some(keep) = &self.refedit_keep else { + return; + }; + for w in wires.iter_mut() { + let keep_bright = + Self::handle_from_wire_name(&w.name).is_some_and(|h| keep.contains(&h)); + if !keep_bright { + w.color = crate::scene::cache::block_cache::fade_toward_bg(w.color, bg); + } + } + } + /// Switch the active layout. Bumps `geometry_epoch` so the wire cache /// re-tessellates — `render_style`'s `adapt_to_bg` picks the model or /// paper background depending on `current_layout`, so cached wires @@ -1779,7 +1818,9 @@ impl Scene { }; let block = self.model_space_block_handle(); let t_tess = iced::time::Instant::now(); - let arc = Arc::new(self.wires_for_block_culled(block, tess_region, wpp, None, None)); + let mut wires = self.wires_for_block_culled(block, tess_region, wpp, None, None); + self.apply_refedit_fade(&mut wires, self.bg_color); + let arc = Arc::new(wires); self.last_tess_ms.set(t_tess.elapsed().as_secs_f32() * 1000.0); self.last_tess_wires.set(arc.len()); let stored_region = tess_region @@ -1818,6 +1859,12 @@ impl Scene { let sheet_name = sheet.value().to_string(); wires.retain(|w| w.name != sheet_name); } + let bg = if self.current_layout == "Model" { + self.bg_color + } else { + self.paper_bg_color + }; + self.apply_refedit_fade(&mut wires, bg); let arc = Arc::new(wires); *self.paper_sheet_cache.borrow_mut() = Some((key, Arc::clone(&arc))); arc