From cd1378e6b24ba9fa3994a2c4cf61051bd69bc2b5 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 29 Jul 2026 17:21:57 +0300 Subject: [PATCH] fix(viewport): preserve tiled pane controls --- src/app/view/mod.rs | 3 +++ src/scene/layout.rs | 28 +++++++++++++++++++++++++--- src/scene/mod.rs | 8 ++++++++ src/ui/wrap_bar.rs | 8 ++++---- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 835005dd..3deb32a4 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -218,6 +218,7 @@ impl OpenCADStudio { ) .width(Fill) .height(Fill) + .min_size(scene.model_pane_min_px()) .spacing(crate::scene::TILE_DIVIDER_PX); stack![size_probe, shaders].width(Fill).height(Fill).into() }; @@ -236,6 +237,7 @@ impl OpenCADStudio { }) .width(Fill) .height(Fill) + .min_size(scene.model_pane_min_px()) .spacing(crate::scene::TILE_DIVIDER_PX) .on_resize(6.0, Message::PaneResized) .into(), @@ -782,6 +784,7 @@ impl OpenCADStudio { .into(), ]) .report_width0(self.render_bar_w.clone()) + .report_width0(tab.scene.model_pane_min_reporter()) .into(); // Pin the bar to the active model tile's top-left corner so it // follows the active panel in a tiled layout. diff --git a/src/scene/layout.rs b/src/scene/layout.rs index cb5dd265..355f0ef5 100644 --- a/src/scene/layout.rs +++ b/src/scene/layout.rs @@ -4,6 +4,24 @@ use super::*; impl Scene { // ── Layout management ───────────────────────────────────────────────── + pub(crate) fn model_pane_min_px(&self) -> f32 { + let measured = f32::from_bits( + self.model_pane_min_px + .load(std::sync::atomic::Ordering::Relaxed), + ); + if measured.is_finite() && measured > 1.0 { + measured + } else { + MODEL_PANE_MIN_FALLBACK_PX + } + } + + pub(crate) fn model_pane_min_reporter( + &self, + ) -> std::sync::Arc { + self.model_pane_min_px.clone() + } + /// Rename a paper-space layout. Updates the Layout object name in the document. pub fn rename_layout(&mut self, old_name: &str, new_name: &str) { for obj in self.document.objects.values_mut() { @@ -361,7 +379,11 @@ impl Scene { let half = TILE_DIVIDER_PX * 0.5; self.model_panes .layout() - .split_regions(TILE_DIVIDER_PX, 0.0, iced::Size::new(vw, vh)) + .split_regions( + TILE_DIVIDER_PX, + self.model_pane_min_px(), + iced::Size::new(vw, vh), + ) .values() .map(|(axis, region, ratio)| match axis { Axis::Vertical => { @@ -394,7 +416,7 @@ impl Scene { } let regions = self.model_panes.layout().pane_regions( TILE_DIVIDER_PX, - 0.0, + self.model_pane_min_px(), iced::Size::new(vw, vh), ); self.model_panes @@ -421,7 +443,7 @@ impl Scene { } let regions = self.model_panes.layout().pane_regions( TILE_DIVIDER_PX, - 0.0, + self.model_pane_min_px(), iced::Size::new(canvas_w, canvas_h), ); let mut tiles = self.model_tiles.borrow_mut(); diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 1a1dd070..f40b2f9b 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1110,6 +1110,9 @@ pub(crate) struct ModelTile { /// the drawn viewports line up exactly with the pane_grid layout. pub const TILE_DIVIDER_PX: f32 = 2.0; +/// Minimum used until the viewport render-mode bar reports its natural width. +pub const MODEL_PANE_MIN_FALLBACK_PX: f32 = 50.0; + /// Shift every vertex of a freshly tessellated `MeshLodSet` into the /// scene's local f32 space by subtracting `world_offset`. ACIS / SAT /// tessellation hands us WCS coordinates; the wire / hatch / face3d @@ -1502,6 +1505,10 @@ pub struct Scene { /// Plain field (not a `RefCell`) so the view can borrow it for the /// `PaneGrid` widget's lifetime; mutated through `&mut Scene` in update. pub(crate) model_panes: iced::widget::pane_grid::State, + /// Natural width of the Model viewport control bar. `DensitySwap` updates + /// it during layout; every pane-grid geometry calculation reads it on the + /// next frame so a pane cannot become narrower than its controls. + model_pane_min_px: std::sync::Arc, pub selection: Rc>, /// The CAD document — single source of truth for all entities. pub document: CadDocument, @@ -1903,6 +1910,7 @@ impl Scene { last_sdf_text: RefCell::new(HashMap::default()), // One pane mapped to tile 0 — matches the single default tile above. model_panes: iced::widget::pane_grid::State::new(0).0, + model_pane_min_px: std::sync::Arc::new(std::sync::atomic::AtomicU32::new(0)), selection: Rc::new(RefCell::new(SelectionState::default())), document: CadDocument::new(), object_data_cache: crate::entities::object_data::ObjectDataCache::default(), diff --git a/src/ui/wrap_bar.rs b/src/ui/wrap_bar.rs index 32dae9b6..f4ba6918 100644 --- a/src/ui/wrap_bar.rs +++ b/src/ui/wrap_bar.rs @@ -761,7 +761,7 @@ pub struct DensitySwap<'a> { /// measured every layout regardless of which variant is shown — so a caller /// can place a neighbouring widget relative to the full-size content even /// while a narrower variant is on screen. - width0_out: Option>, + width0_out: Vec>, } impl<'a> DensitySwap<'a> { @@ -770,7 +770,7 @@ impl<'a> DensitySwap<'a> { variants, chosen: Cell::new(0), height_out: None, - width0_out: None, + width0_out: Vec::new(), } } @@ -783,7 +783,7 @@ impl<'a> DensitySwap<'a> { /// Report the first variant's natural (unconstrained) width — see `width0_out`. pub fn report_width0(mut self, out: Arc) -> Self { - self.width0_out = Some(out); + self.width0_out.push(out); self } } @@ -818,7 +818,7 @@ impl<'a> Widget for DensitySwap<'a> { for (i, v) in self.variants.iter_mut().enumerate() { let n = v.as_widget_mut().layout(&mut tree.children[i], renderer, &natural); if i == 0 { - if let Some(out) = &self.width0_out { + for out in &self.width0_out { out.store(n.size().width.to_bits(), Ordering::Relaxed); } }