refactor(viewport): migrate model-space tiling to iced pane_grid
Replace the custom model_tiles tiling with iced 0.14 pane_grid as the layout source of truth, while paper-space floating viewports are left untouched (they overlap/free-place, which pane_grid cannot model). Each Model pane is its own shader widget rendering its camera into the pane bounds; the renderer Primitive carries a base_slot so several shader widgets share the type-keyed MultiPipeline storage on disjoint slots. A second pane_grid of transparent mouse_areas (plus on_resize) layers above the crosshair overlay to receive input — the overlay's Hidden cursor interaction otherwise levitates the cursor and starves any layer beneath it. The active pane follows the cursor (hover-activate), and grid / crosshair / UCS icon render per pane while the ViewCube and render-mode picker stay on the active pane only. Split / close / VPORTS presets and the VPort table round-trip now drive pane_grid (guillotine reconstruction on load). Adds drag-to-swap via a controls bar handle with a ghost-card preview, visible pane dividers from the pane_grid split regions, a default model background of RGB(33,40,48), and removes all the superseded custom-tile code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
8bdf5228d2
commit
495b13ec4f
18 changed files with 1004 additions and 733 deletions
|
|
@ -113,6 +113,13 @@ pub struct Primitive {
|
|||
pub(in crate::scene) viewports: Vec<ViewportData>,
|
||||
/// Background color used to clear each viewport's MSAA buffer.
|
||||
pub(in crate::scene) bg_color: [f32; 4],
|
||||
/// First `MultiPipeline` inner slot this primitive owns. Paper space (one
|
||||
/// shader widget, many viewports) uses 0. Per-pane Model widgets each own a
|
||||
/// distinct slot (= their tile index) so several shader widgets can share
|
||||
/// the type-keyed pipeline storage without clobbering one another — all
|
||||
/// `prepare` calls run before all `render` calls, so disjoint slots are
|
||||
/// safe.
|
||||
pub(in crate::scene) base_slot: usize,
|
||||
}
|
||||
|
||||
/// Flags the render pipeline consumes, derived from
|
||||
|
|
@ -201,10 +208,10 @@ impl shader::Primitive for Primitive {
|
|||
let phys = viewport.physical_size();
|
||||
let full_size = Size::new(phys.width, phys.height);
|
||||
let scale = viewport.scale_factor() as f32;
|
||||
pipeline.ensure_len(device, queue, self.viewports.len());
|
||||
pipeline.ensure_len(device, queue, self.base_slot + self.viewports.len());
|
||||
|
||||
for (i, vp) in self.viewports.iter().enumerate() {
|
||||
let inner = &mut pipeline.inners[i];
|
||||
let inner = &mut pipeline.inners[self.base_slot + i];
|
||||
// The MSAA / depth / resolve textures are always sized to the
|
||||
// FULL viewport rectangle (not the on-canvas-visible portion)
|
||||
// so the camera matrices render at consistent aspect / scale.
|
||||
|
|
@ -347,7 +354,7 @@ impl shader::Primitive for Primitive {
|
|||
let clip_right = clip.x + clip.width;
|
||||
let clip_bottom = clip.y + clip.height;
|
||||
for (i, vp) in self.viewports.iter().enumerate() {
|
||||
let Some(inner) = pipeline.inners.get(i) else {
|
||||
let Some(inner) = pipeline.inners.get(self.base_slot + i) else {
|
||||
break;
|
||||
};
|
||||
// Where the viewport would land on the surface in absolute
|
||||
|
|
@ -628,7 +635,72 @@ impl Scene {
|
|||
.collect();
|
||||
// Empty viewports → blit nothing; the container background (model bg
|
||||
// or the paper desk colour) stays visible.
|
||||
Primitive { viewports, bg_color }
|
||||
Primitive {
|
||||
viewports,
|
||||
bg_color,
|
||||
base_slot: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a single-pane Model primitive: the viewport for tile `tile_idx`,
|
||||
/// filling the shader widget's own `bounds` (= the pane rectangle the
|
||||
/// `pane_grid` laid out). Each Model pane is its own shader widget, so the
|
||||
/// camera matrices use the pane aspect for free and the primitive owns
|
||||
/// pipeline slot `tile_idx`. The active tile renders the live camera /
|
||||
/// render-mode; the rest use their stored snapshot.
|
||||
pub(in crate::scene) fn build_viewport_for_pane(
|
||||
&self,
|
||||
bounds: Rectangle,
|
||||
tile_idx: usize,
|
||||
model_render_mode: acadrust::entities::ViewportRenderMode,
|
||||
) -> Primitive {
|
||||
let hover_region = self.viewcube_hover.get();
|
||||
let canvas = (bounds.width.max(1.0), bounds.height.max(1.0));
|
||||
let bg_color = [0.0, 0.0, 0.0, 0.0];
|
||||
let tiles = self.model_tiles.borrow();
|
||||
let Some(tile) = tiles.get(tile_idx) else {
|
||||
return Primitive {
|
||||
viewports: vec![],
|
||||
bg_color,
|
||||
base_slot: tile_idx,
|
||||
};
|
||||
};
|
||||
let active = self.active_model_tile.get();
|
||||
let is_active = tile_idx == active;
|
||||
let camera = if is_active {
|
||||
self.camera.borrow().clone()
|
||||
} else {
|
||||
tile.camera.clone()
|
||||
};
|
||||
let inst = ViewportInstance {
|
||||
handle: Handle::NULL,
|
||||
tile_idx: Some(tile_idx),
|
||||
// Fills the whole widget (= pane); normalized rect is (0,0,1,1).
|
||||
screen_rect: Rectangle {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: canvas.0,
|
||||
height: canvas.1,
|
||||
},
|
||||
camera,
|
||||
render_mode: if is_active {
|
||||
model_render_mode
|
||||
} else {
|
||||
tile.render_mode
|
||||
},
|
||||
active: is_active,
|
||||
grid_on: tile.grid_on,
|
||||
paper_sheet: false,
|
||||
};
|
||||
let viewports = self
|
||||
.viewport_data_for(&inst, canvas, hover_region)
|
||||
.into_iter()
|
||||
.collect();
|
||||
Primitive {
|
||||
viewports,
|
||||
bg_color,
|
||||
base_slot: tile_idx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build one `ViewportData` from a `ViewportInstance`: gathers the
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ pub struct ViewportPane<'a> {
|
|||
/// Render mode applied to the Model layout's tiles. Paper-space content
|
||||
/// viewports use the render mode stored on their own viewport entity.
|
||||
pub render_mode: acadrust::entities::ViewportRenderMode,
|
||||
/// `Some(tile_idx)` → this widget renders a single Model pane (one shader
|
||||
/// per `pane_grid` pane, filling its own bounds). `None` → the unified
|
||||
/// full-canvas path (paper layout, or the whole-canvas Model fallback).
|
||||
pub pane: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'a> ViewportPane<'a> {
|
||||
|
|
@ -31,6 +35,23 @@ impl<'a> ViewportPane<'a> {
|
|||
scene,
|
||||
show_viewcube,
|
||||
render_mode,
|
||||
pane: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// One Model `pane_grid` pane: renders just `tile_idx` into this widget's
|
||||
/// own bounds (the pane rectangle).
|
||||
pub fn for_pane(
|
||||
scene: &'a Scene,
|
||||
show_viewcube: bool,
|
||||
render_mode: acadrust::entities::ViewportRenderMode,
|
||||
tile_idx: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
scene,
|
||||
show_viewcube,
|
||||
render_mode,
|
||||
pane: Some(tile_idx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,8 +68,14 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a>
|
|||
_cursor: mouse::Cursor,
|
||||
bounds: Rectangle,
|
||||
) -> Self::Primitive {
|
||||
self.scene
|
||||
.build_viewports(bounds, self.render_mode, state.hover_region)
|
||||
match self.pane {
|
||||
Some(idx) => self
|
||||
.scene
|
||||
.build_viewport_for_pane(bounds, idx, self.render_mode),
|
||||
None => self
|
||||
.scene
|
||||
.build_viewports(bounds, self.render_mode, state.hover_region),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
|
|
|
|||
Loading…
Reference in a new issue