Resolve a primitive's position through a chain of move slots
The plumbing for O(1) subtree movement (LAYOUT.md §2), with every slot still at zero, so this changes no pixels and the next commit can change behaviour against a known-good picture. Every active widget owns a slot in `UiData::moves`: a translation in physical pixels and the slot it is relative to. A primitive instance and a mask each name one, and `prelude.wgsl` walks the chain and adds the accumulated delta. A mask resolves its own chain rather than the drawn primitive's, so a stationary viewport can clip content that moves inside it. `CHAIN_LIMIT` is stated on both sides; it bounds a malformed cycle rather than any real tree. A slot outlives any one `ActiveData`, because a redraw replaces that while the widget's children go on pointing at the slot, so it lives in `UiRenderState::moves` keyed by widget and is retired when the widget stops being drawn. `MoveIdx` is its own type rather than another `Id<u32>`: it sits beside `MaskIdx` in an instance and the two must not be swappable. `Vec2` is now `repr(align(8))`, which is WGSL's alignment for a `vec2<f32>`, so a GPU struct holding one is laid out the way its shader reads it without saying so itself -- `GlyphPrimitive` no longer states its own alignment, and `MoveOffset` never has to. Both keep a manual `unsafe impl Pod`, since the trailing padding that alignment introduces is what `derive(Pod)` refuses. `WindowUniform` holds the `Vec2` its shader has always called `dim` rather than two loose floats, which was the last place the two sides described the same bytes differently. Checked: fmt, clippy and 40 tests. `tabs` (with the image replay), `view` and `minimal` render byte-identical to `upstream/main`, and `text` is unchanged.
This commit is contained in:
1 parent
ca2b4b2173
commit
f9ef7514e7
11 files changed
+262
-40
No files matched your search
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Remap, Size,
|
||||
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, OnResize, Painter, PixelRegion, Remap,
|
||||
Size, StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||
};
|
||||
|
||||
@@ -12,6 +12,9 @@ pub struct UiRenderState {
|
||||
old_root: Option<WidgetId>,
|
||||
resized: bool,
|
||||
draw_started: HashSet<WidgetId>,
|
||||
/// A widget's move slot, which outlives any one `ActiveData`: a redraw
|
||||
/// replaces that while its children go on pointing at the slot.
|
||||
moves: HashMap<WidgetId, MoveIdx>,
|
||||
}
|
||||
|
||||
impl UiRenderState {
|
||||
@@ -23,6 +26,7 @@ impl UiRenderState {
|
||||
old_root: None,
|
||||
resized: false,
|
||||
draw_started: Default::default(),
|
||||
moves: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +107,7 @@ impl UiRenderState {
|
||||
}
|
||||
|
||||
// draw widget
|
||||
let move_idx = self.move_slot(id, parent, rsc);
|
||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||
self.draw_started.insert(id);
|
||||
|
||||
@@ -117,6 +122,7 @@ impl UiRenderState {
|
||||
children: Vec::new(),
|
||||
size_deps: Vec::new(),
|
||||
reads_output: false,
|
||||
move_idx,
|
||||
rsc,
|
||||
};
|
||||
|
||||
@@ -134,6 +140,7 @@ impl UiRenderState {
|
||||
children,
|
||||
size_deps,
|
||||
reads_output,
|
||||
move_idx,
|
||||
layer,
|
||||
id,
|
||||
} = painter;
|
||||
@@ -155,6 +162,7 @@ impl UiRenderState {
|
||||
children,
|
||||
size_deps,
|
||||
reads_output,
|
||||
move_idx,
|
||||
mask,
|
||||
layer,
|
||||
};
|
||||
@@ -171,6 +179,25 @@ impl UiRenderState {
|
||||
size
|
||||
}
|
||||
|
||||
/// The slot a widget's drawing is positioned through, made on its first
|
||||
/// draw and kept until it stops being drawn.
|
||||
fn move_slot(
|
||||
&mut self,
|
||||
id: WidgetId,
|
||||
parent: Option<WidgetId>,
|
||||
rsc: &mut dyn UiRsc,
|
||||
) -> MoveIdx {
|
||||
if let Some(&idx) = self.moves.get(&id) {
|
||||
return idx;
|
||||
}
|
||||
let parent = parent
|
||||
.and_then(|p| self.moves.get(&p).copied())
|
||||
.unwrap_or(MoveIdx::NONE);
|
||||
let idx = rsc.ui_mut().moves.push(parent);
|
||||
self.moves.insert(id, idx);
|
||||
idx
|
||||
}
|
||||
|
||||
/// The drawing a widget already has, kept for a new box if the box has not
|
||||
/// changed in a way it depends on.
|
||||
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
|
||||
@@ -255,6 +282,9 @@ impl UiRenderState {
|
||||
active.textures.clear();
|
||||
rsc.ui_mut().textures.free();
|
||||
if undraw {
|
||||
if let Some(idx) = self.moves.remove(&id) {
|
||||
rsc.ui_mut().moves.remove(idx);
|
||||
}
|
||||
rsc.on_undraw(active);
|
||||
}
|
||||
}
|
||||
@@ -275,6 +305,8 @@ impl UiRenderState {
|
||||
for (_, active) in self.active.drain() {
|
||||
rsc.on_undraw(&active);
|
||||
}
|
||||
self.moves.clear();
|
||||
rsc.ui_mut().moves.clear();
|
||||
self.layers.clear();
|
||||
rsc.widgets_mut().needs_redraw.clear();
|
||||
rsc.free();
|
||||
|
||||
Reference in new issue
Block a user