Compare commits
2
Commits
main
...
8223a55cfb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8223a55cfb | ||
|
|
f9ef7514e7 |
No files matched your search
+46
-4
@@ -1,11 +1,10 @@
|
|||||||
use crate::{UiRegion, util::Id};
|
use crate::{UiRegion, util::Id, util::Vec2};
|
||||||
use wgpu::*;
|
use wgpu::*;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
|
||||||
pub struct WindowUniform {
|
pub struct WindowUniform {
|
||||||
pub width: f32,
|
pub dim: Vec2,
|
||||||
pub height: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -13,15 +12,17 @@ pub struct WindowUniform {
|
|||||||
pub struct PrimitiveInstance {
|
pub struct PrimitiveInstance {
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
pub mask_idx: MaskIdx,
|
pub mask_idx: MaskIdx,
|
||||||
|
pub move_idx: MoveIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrimitiveInstance {
|
impl PrimitiveInstance {
|
||||||
const ATTRIBS: [VertexAttribute; 5] = vertex_attr_array![
|
const ATTRIBS: [VertexAttribute; 6] = vertex_attr_array![
|
||||||
0 => Float32x2,
|
0 => Float32x2,
|
||||||
1 => Float32x2,
|
1 => Float32x2,
|
||||||
2 => Float32x2,
|
2 => Float32x2,
|
||||||
3 => Float32x2,
|
3 => Float32x2,
|
||||||
4 => Uint32,
|
4 => Uint32,
|
||||||
|
5 => Uint32,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub fn desc() -> VertexBufferLayout<'static> {
|
pub fn desc() -> VertexBufferLayout<'static> {
|
||||||
@@ -43,4 +44,45 @@ impl MaskIdx {
|
|||||||
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct Mask {
|
pub struct Mask {
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
|
pub move_idx: MoveIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Its own type rather than another `Id<u32>`, because it sits beside
|
||||||
|
/// `MaskIdx` in an instance and the two must not be swappable.
|
||||||
|
#[repr(transparent)]
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
|
pub struct MoveIdx(u32);
|
||||||
|
|
||||||
|
impl MoveIdx {
|
||||||
|
pub const NONE: Self = Self(u32::MAX);
|
||||||
|
|
||||||
|
pub(crate) fn slot(idx: usize) -> Self {
|
||||||
|
Self(idx as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn idx(self) -> usize {
|
||||||
|
self.0 as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One link of the chain a primitive's position is resolved through: a
|
||||||
|
/// translation in physical pixels, and the slot it is relative to. Moving a
|
||||||
|
/// subtree writes its own slot and nothing else.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct MoveOffset {
|
||||||
|
pub delta: Vec2,
|
||||||
|
pub parent: MoveIdx,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl bytemuck::Pod for MoveOffset {}
|
||||||
|
unsafe impl bytemuck::Zeroable for MoveOffset {}
|
||||||
|
|
||||||
|
impl MoveOffset {
|
||||||
|
pub fn root(parent: MoveIdx) -> Self {
|
||||||
|
Self {
|
||||||
|
delta: Vec2::ZERO,
|
||||||
|
parent,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+38
-11
@@ -17,7 +17,7 @@ mod texture;
|
|||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
pub use atlas::*;
|
pub use atlas::*;
|
||||||
pub use data::{Mask, MaskIdx};
|
pub use data::{Mask, MaskIdx, MoveIdx, MoveOffset};
|
||||||
pub use primitive::*;
|
pub use primitive::*;
|
||||||
|
|
||||||
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
||||||
@@ -34,6 +34,7 @@ pub struct UiRenderNode {
|
|||||||
active: Vec<usize>,
|
active: Vec<usize>,
|
||||||
window_buffer: Buffer,
|
window_buffer: Buffer,
|
||||||
masks: ArrBuf<Mask>,
|
masks: ArrBuf<Mask>,
|
||||||
|
moves: ArrBuf<MoveOffset>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RenderLayer {
|
struct RenderLayer {
|
||||||
@@ -127,32 +128,35 @@ impl UiRenderNode {
|
|||||||
for primitive in &mut self.primitives {
|
for primitive in &mut self.primitives {
|
||||||
primitive.render.update(ui);
|
primitive.render.update(ui);
|
||||||
}
|
}
|
||||||
|
let mut regroup = false;
|
||||||
if ui.masks.changed {
|
if ui.masks.changed {
|
||||||
ui.masks.changed = false;
|
ui.masks.changed = false;
|
||||||
if self.masks.update(device, queue, &ui.masks[..]) {
|
regroup |= self.masks.update(device, queue, &ui.masks[..]);
|
||||||
|
}
|
||||||
|
if ui_render.moves.changed {
|
||||||
|
ui_render.moves.changed = false;
|
||||||
|
regroup |= self.moves.update(device, queue, ui_render.moves.entries());
|
||||||
|
}
|
||||||
|
if regroup {
|
||||||
self.shared_group = Self::shared_group(
|
self.shared_group = Self::shared_group(
|
||||||
device,
|
device,
|
||||||
&self.shared_layout,
|
&self.shared_layout,
|
||||||
&self.window_buffer,
|
&self.window_buffer,
|
||||||
&self.masks,
|
&self.masks,
|
||||||
|
&self.moves,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>, queue: &Queue) {
|
pub fn resize(&mut self, size: impl Into<Vec2>, queue: &Queue) {
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
let slice = &[WindowUniform {
|
let slice = &[WindowUniform { dim: size }];
|
||||||
width: size.x,
|
|
||||||
height: size.y,
|
|
||||||
}];
|
|
||||||
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
|
||||||
let window_uniform = WindowUniform {
|
let window_uniform = WindowUniform {
|
||||||
width: config.width as f32,
|
dim: Vec2::new(config.width as f32, config.height as f32),
|
||||||
height: config.height as f32,
|
|
||||||
};
|
};
|
||||||
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
|
||||||
label: Some("window"),
|
label: Some("window"),
|
||||||
@@ -166,7 +170,13 @@ impl UiRenderNode {
|
|||||||
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
"ui masks",
|
"ui masks",
|
||||||
);
|
);
|
||||||
let shared_group = Self::shared_group(device, &shared_layout, &window_buffer, &masks);
|
let moves = ArrBuf::new(
|
||||||
|
device,
|
||||||
|
BufferUsages::STORAGE | BufferUsages::COPY_DST,
|
||||||
|
"ui move offsets",
|
||||||
|
);
|
||||||
|
let shared_group =
|
||||||
|
Self::shared_group(device, &shared_layout, &window_buffer, &masks, &moves);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
shared_layout,
|
shared_layout,
|
||||||
@@ -177,6 +187,7 @@ impl UiRenderNode {
|
|||||||
layers: HashMap::default(),
|
layers: HashMap::default(),
|
||||||
active: Vec::new(),
|
active: Vec::new(),
|
||||||
masks,
|
masks,
|
||||||
|
moves,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +263,8 @@ impl UiRenderNode {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What every draw in the ui is given: the window and the masks.
|
/// What every draw in the ui is given: the window, the masks and the
|
||||||
|
/// move chain every position is resolved through.
|
||||||
fn shared_layout(device: &Device) -> BindGroupLayout {
|
fn shared_layout(device: &Device) -> BindGroupLayout {
|
||||||
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
|
||||||
entries: &[
|
entries: &[
|
||||||
@@ -276,6 +288,16 @@ impl UiRenderNode {
|
|||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
},
|
},
|
||||||
|
BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
|
||||||
|
ty: BindingType::Buffer {
|
||||||
|
ty: BufferBindingType::Storage { read_only: true },
|
||||||
|
has_dynamic_offset: false,
|
||||||
|
min_binding_size: BufferSize::new(size_of::<MoveOffset>() as u64),
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui shared"),
|
label: Some("ui shared"),
|
||||||
})
|
})
|
||||||
@@ -286,6 +308,7 @@ impl UiRenderNode {
|
|||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
window: &Buffer,
|
window: &Buffer,
|
||||||
masks: &ArrBuf<Mask>,
|
masks: &ArrBuf<Mask>,
|
||||||
|
moves: &ArrBuf<MoveOffset>,
|
||||||
) -> BindGroup {
|
) -> BindGroup {
|
||||||
device.create_bind_group(&BindGroupDescriptor {
|
device.create_bind_group(&BindGroupDescriptor {
|
||||||
layout,
|
layout,
|
||||||
@@ -298,6 +321,10 @@ impl UiRenderNode {
|
|||||||
binding: 1,
|
binding: 1,
|
||||||
resource: masks.buffer.as_entire_binding(),
|
resource: masks.buffer.as_entire_binding(),
|
||||||
},
|
},
|
||||||
|
BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: moves.buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
label: Some("ui shared"),
|
label: Some("ui shared"),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use std::{any::TypeId, marker::PhantomData};
|
|||||||
use crate::{
|
use crate::{
|
||||||
Color, TextureHandle, UiData, UiRegion, WidgetId,
|
Color, TextureHandle, UiData, UiRegion, WidgetId,
|
||||||
render::{
|
render::{
|
||||||
data::{MaskIdx, PrimitiveInstance},
|
data::{MaskIdx, MoveIdx, PrimitiveInstance},
|
||||||
page::GlyphRender,
|
page::GlyphRender,
|
||||||
texture::ImageRender,
|
texture::ImageRender,
|
||||||
},
|
},
|
||||||
@@ -246,6 +246,7 @@ impl LayerDraws {
|
|||||||
primitive,
|
primitive,
|
||||||
region,
|
region,
|
||||||
mask_idx,
|
mask_idx,
|
||||||
|
move_idx,
|
||||||
}: PrimitiveInst<P>,
|
}: PrimitiveInst<P>,
|
||||||
) -> PrimitiveHandle {
|
) -> PrimitiveHandle {
|
||||||
self.updated = true;
|
self.updated = true;
|
||||||
@@ -258,7 +259,11 @@ impl LayerDraws {
|
|||||||
.get_or_insert_with(InstanceList::new::<P>)
|
.get_or_insert_with(InstanceList::new::<P>)
|
||||||
.push(
|
.push(
|
||||||
id,
|
id,
|
||||||
PrimitiveInstance { region, mask_idx },
|
PrimitiveInstance {
|
||||||
|
region,
|
||||||
|
mask_idx,
|
||||||
|
move_idx,
|
||||||
|
},
|
||||||
bytemuck::bytes_of(&primitive),
|
bytemuck::bytes_of(&primitive),
|
||||||
);
|
);
|
||||||
PrimitiveHandle {
|
PrimitiveHandle {
|
||||||
@@ -304,6 +309,7 @@ pub struct PrimitiveInst<P> {
|
|||||||
pub primitive: P,
|
pub primitive: P,
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
pub mask_idx: MaskIdx,
|
pub mask_idx: MaskIdx,
|
||||||
|
pub move_idx: MoveIdx,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrimitiveChange {
|
pub struct PrimitiveChange {
|
||||||
@@ -347,7 +353,7 @@ impl RectPrimitive {
|
|||||||
|
|
||||||
/// `color` is multiplied by the atlas alpha for a mask glyph; a colour glyph
|
/// `color` is multiplied by the atlas alpha for a mask glyph; a colour glyph
|
||||||
/// takes the texel unchanged, which `GlyphEntry::IS_COLORED` selects.
|
/// takes the texel unchanged, which `GlyphEntry::IS_COLORED` selects.
|
||||||
#[repr(C, align(8))]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct GlyphPrimitive {
|
pub struct GlyphPrimitive {
|
||||||
pub uv_min: Vec2,
|
pub uv_min: Vec2,
|
||||||
@@ -358,8 +364,8 @@ pub struct GlyphPrimitive {
|
|||||||
pub flags: u32,
|
pub flags: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manual rather than derived: the align(8) leaves four bytes of padding, which
|
// Manual rather than derived: `Vec2`'s alignment leaves four bytes of padding
|
||||||
// is how WGSL lays the struct out.
|
// here, which is how WGSL lays the struct out.
|
||||||
unsafe impl bytemuck::Pod for GlyphPrimitive {}
|
unsafe impl bytemuck::Pod for GlyphPrimitive {}
|
||||||
unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
|
unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
|
||||||
impl Primitive for GlyphPrimitive {
|
impl Primitive for GlyphPrimitive {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
var<uniform> window: WindowUniform;
|
var<uniform> window: WindowUniform;
|
||||||
@group(0) @binding(1)
|
@group(0) @binding(1)
|
||||||
var<storage> masks: array<Mask>;
|
var<storage> masks: array<Mask>;
|
||||||
|
@group(0) @binding(2)
|
||||||
|
var<storage> move_offsets: array<MoveOffset>;
|
||||||
|
|
||||||
struct WindowUniform {
|
struct WindowUniform {
|
||||||
dim: vec2<f32>,
|
dim: vec2<f32>,
|
||||||
@@ -15,6 +17,32 @@ struct WindowUniform {
|
|||||||
struct Mask {
|
struct Mask {
|
||||||
x: UiSpan,
|
x: UiSpan,
|
||||||
y: UiSpan,
|
y: UiSpan,
|
||||||
|
move_idx: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MoveOffset {
|
||||||
|
delta: vec2<f32>,
|
||||||
|
parent: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOVE_NONE: u32 = 4294967295u;
|
||||||
|
// Keep in step with `iris_core::CHAIN_LIMIT`. It bounds a malformed cycle
|
||||||
|
// rather than any real tree, and the CPU walk uses the same number so both
|
||||||
|
// resolve a deep one the same way.
|
||||||
|
const CHAIN_LIMIT: u32 = 64u;
|
||||||
|
|
||||||
|
fn resolve_move(idx: u32) -> vec2<f32> {
|
||||||
|
var total = vec2<f32>(0.0, 0.0);
|
||||||
|
var at = idx;
|
||||||
|
for (var step = 0u; step < CHAIN_LIMIT; step++) {
|
||||||
|
if at == MOVE_NONE {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let entry = move_offsets[at];
|
||||||
|
total += entry.delta;
|
||||||
|
at = entry.parent;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct UiSpan {
|
struct UiSpan {
|
||||||
@@ -33,6 +61,7 @@ struct InstanceInput {
|
|||||||
@location(2) y_start: vec2<f32>,
|
@location(2) y_start: vec2<f32>,
|
||||||
@location(3) y_end: vec2<f32>,
|
@location(3) y_end: vec2<f32>,
|
||||||
@location(4) mask_idx: u32,
|
@location(4) mask_idx: u32,
|
||||||
|
@location(5) move_idx: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VertexOutput {
|
struct VertexOutput {
|
||||||
@@ -57,8 +86,9 @@ fn vs_main(
|
|||||||
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
let bot_right_rel = vec2(in.x_end.x, in.y_end.x);
|
||||||
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
let bot_right_abs = vec2(in.x_end.y, in.y_end.y);
|
||||||
|
|
||||||
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
|
let moved = resolve_move(in.move_idx);
|
||||||
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
|
let top_left = floor(top_left_rel * window.dim) + floor(top_left_abs + moved);
|
||||||
|
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs + moved);
|
||||||
let size = bot_right - top_left;
|
let size = bot_right - top_left;
|
||||||
|
|
||||||
let uv = vec2<f32>(
|
let uv = vec2<f32>(
|
||||||
@@ -86,8 +116,11 @@ fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
|
|||||||
let br = vec2(mask.x.end.rel, mask.y.end.rel);
|
let br = vec2(mask.x.end.rel, mask.y.end.rel);
|
||||||
let br_abs = vec2(mask.x.end.abs, mask.y.end.abs);
|
let br_abs = vec2(mask.x.end.abs, mask.y.end.abs);
|
||||||
|
|
||||||
let top_left = floor(tl * window.dim) + floor(tl_abs);
|
// Its own chain, not the drawn primitive's, so a stationary viewport
|
||||||
let bot_right = floor(br * window.dim) + floor(br_abs);
|
// clips content that moves inside it.
|
||||||
|
let moved = resolve_move(mask.move_idx);
|
||||||
|
let top_left = floor(tl * window.dim) + floor(tl_abs + moved);
|
||||||
|
let bot_right = floor(br * window.dim) + floor(br_abs + moved);
|
||||||
let pos = in.clip_position.xy;
|
let pos = in.clip_position.xy;
|
||||||
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
if pos.x < top_left.x || pos.x > bot_right.x || pos.y < top_left.y || pos.y > bot_right.y {
|
||||||
return color * 0.0;
|
return color * 0.0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{LayerId, MaskIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId};
|
use crate::{LayerId, MaskIdx, MoveIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId};
|
||||||
|
|
||||||
/// important non rendering data for retained drawing
|
/// important non rendering data for retained drawing
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -15,6 +15,8 @@ pub struct ActiveData {
|
|||||||
pub size_deps: Vec<WidgetId>,
|
pub size_deps: Vec<WidgetId>,
|
||||||
/// Whether it read the output's size, and so is wrong when that changes.
|
/// Whether it read the output's size, and so is wrong when that changes.
|
||||||
pub reads_output: bool,
|
pub reads_output: bool,
|
||||||
|
/// The move slot its primitives are positioned through.
|
||||||
|
pub move_idx: MoveIdx,
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
pub layer: LayerId,
|
pub layer: LayerId,
|
||||||
}
|
}
|
||||||
+67
-1
@@ -1,7 +1,14 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
Mask, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena,
|
Mask, MoveIdx, MoveOffset, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId,
|
||||||
|
Widgets,
|
||||||
|
util::{Arena, Id, TrackedArena, Vec2},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// How far the shader will walk a move chain. It bounds a malformed cycle
|
||||||
|
/// rather than any real tree; `Moves::resolve` uses the same number so the
|
||||||
|
/// two agree on what a deep tree resolves to.
|
||||||
|
pub const CHAIN_LIMIT: u32 = 64;
|
||||||
|
|
||||||
mod active;
|
mod active;
|
||||||
mod painter;
|
mod painter;
|
||||||
mod render_state;
|
mod render_state;
|
||||||
@@ -20,6 +27,65 @@ pub struct UiData {
|
|||||||
pub masks: TrackedArena<Mask, u32>,
|
pub masks: TrackedArena<Mask, u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where each widget's drawing sits relative to its parent's slot, so moving
|
||||||
|
/// a subtree writes one entry rather than every descendant's primitives.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Moves {
|
||||||
|
arena: Arena<MoveOffset, u32>,
|
||||||
|
pub changed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Moves {
|
||||||
|
pub fn push(&mut self, parent: MoveIdx) -> MoveIdx {
|
||||||
|
self.changed = true;
|
||||||
|
MoveIdx::slot(self.arena.push(MoveOffset::root(parent)).idx())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, idx: MoveIdx) {
|
||||||
|
self.changed = true;
|
||||||
|
self.arena.remove(Id::preset(idx.idx() as u32));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a slot's translation, in physical pixels, relative to its parent.
|
||||||
|
pub fn set(&mut self, idx: MoveIdx, delta: Vec2) {
|
||||||
|
let entry = self.arena.get_mut(Id::preset(idx.idx() as u32));
|
||||||
|
if entry.delta != delta {
|
||||||
|
entry.delta = delta;
|
||||||
|
self.changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The translation a primitive in `idx` has accumulated, which is the
|
||||||
|
/// same walk the vertex shader does.
|
||||||
|
pub fn resolve(&self, idx: MoveIdx) -> Vec2 {
|
||||||
|
let mut total = Vec2::ZERO;
|
||||||
|
let mut at = idx;
|
||||||
|
for _ in 0..CHAIN_LIMIT {
|
||||||
|
if at == MoveIdx::NONE {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
let entry = self.arena[at.idx()];
|
||||||
|
total += entry.delta;
|
||||||
|
at = entry.parent;
|
||||||
|
}
|
||||||
|
debug_assert!(
|
||||||
|
at == MoveIdx::NONE,
|
||||||
|
"a move chain longer than {CHAIN_LIMIT} resolves to the wrong place, \
|
||||||
|
and the shader stops at the same depth"
|
||||||
|
);
|
||||||
|
total
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entries(&self) -> &[MoveOffset] {
|
||||||
|
&self.arena
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.changed = true;
|
||||||
|
self.arena = Arena::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait UiRsc {
|
pub trait UiRsc {
|
||||||
fn ui(&self) -> &UiData;
|
fn ui(&self) -> &UiData;
|
||||||
fn ui_mut(&mut self) -> &mut UiData;
|
fn ui_mut(&mut self) -> &mut UiData;
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use crate::{
|
|||||||
Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
||||||
UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId,
|
UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId,
|
||||||
render::{
|
render::{
|
||||||
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
|
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
||||||
TexturePrimitive,
|
PrimitiveKind, TexturePrimitive,
|
||||||
},
|
},
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
@@ -21,6 +21,8 @@ pub struct Painter<'a> {
|
|||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub(super) size_deps: Vec<WidgetId>,
|
pub(super) size_deps: Vec<WidgetId>,
|
||||||
pub(super) reads_output: bool,
|
pub(super) reads_output: bool,
|
||||||
|
/// The move slot this widget's primitives are positioned through.
|
||||||
|
pub(super) move_idx: MoveIdx,
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub(super) id: WidgetId,
|
pub(super) id: WidgetId,
|
||||||
}
|
}
|
||||||
@@ -41,6 +43,7 @@ impl<'a> Painter<'a> {
|
|||||||
primitive,
|
primitive,
|
||||||
region,
|
region,
|
||||||
mask_idx: self.mask,
|
mask_idx: self.mask,
|
||||||
|
move_idx: self.move_idx,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
self.push_primitive(h);
|
self.push_primitive(h);
|
||||||
@@ -67,7 +70,10 @@ impl<'a> Painter<'a> {
|
|||||||
|
|
||||||
pub fn set_mask(&mut self, region: UiRegion) {
|
pub fn set_mask(&mut self, region: UiRegion) {
|
||||||
assert!(self.mask == MaskIdx::NONE);
|
assert!(self.mask == MaskIdx::NONE);
|
||||||
self.mask = self.rsc.ui_mut().masks.push(Mask { region });
|
self.mask = self.rsc.ui_mut().masks.push(Mask {
|
||||||
|
region,
|
||||||
|
move_idx: self.move_idx,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
/// Draws a widget within this widget's region.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Remap, Size,
|
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, Moves, OnResize, Painter, PixelRegion,
|
||||||
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
Remap, Size, StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -12,6 +12,10 @@ pub struct UiRenderState {
|
|||||||
old_root: Option<WidgetId>,
|
old_root: Option<WidgetId>,
|
||||||
resized: bool,
|
resized: bool,
|
||||||
draw_started: HashSet<WidgetId>,
|
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.
|
||||||
|
slots: HashMap<WidgetId, MoveIdx>,
|
||||||
|
pub moves: Moves,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UiRenderState {
|
impl UiRenderState {
|
||||||
@@ -23,6 +27,8 @@ impl UiRenderState {
|
|||||||
old_root: None,
|
old_root: None,
|
||||||
resized: false,
|
resized: false,
|
||||||
draw_started: Default::default(),
|
draw_started: Default::default(),
|
||||||
|
slots: Default::default(),
|
||||||
|
moves: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +109,8 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// draw widget
|
// draw widget
|
||||||
|
let move_idx = self.move_slot(id, parent);
|
||||||
|
self.moves.set(move_idx, Vec2::ZERO);
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
self.draw_started.insert(id);
|
self.draw_started.insert(id);
|
||||||
|
|
||||||
@@ -117,6 +125,7 @@ impl UiRenderState {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
size_deps: Vec::new(),
|
size_deps: Vec::new(),
|
||||||
reads_output: false,
|
reads_output: false,
|
||||||
|
move_idx,
|
||||||
rsc,
|
rsc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -134,6 +143,7 @@ impl UiRenderState {
|
|||||||
children,
|
children,
|
||||||
size_deps,
|
size_deps,
|
||||||
reads_output,
|
reads_output,
|
||||||
|
move_idx,
|
||||||
layer,
|
layer,
|
||||||
id,
|
id,
|
||||||
} = painter;
|
} = painter;
|
||||||
@@ -155,6 +165,7 @@ impl UiRenderState {
|
|||||||
children,
|
children,
|
||||||
size_deps,
|
size_deps,
|
||||||
reads_output,
|
reads_output,
|
||||||
|
move_idx,
|
||||||
mask,
|
mask,
|
||||||
layer,
|
layer,
|
||||||
};
|
};
|
||||||
@@ -171,6 +182,20 @@ impl UiRenderState {
|
|||||||
size
|
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>) -> MoveIdx {
|
||||||
|
if let Some(&idx) = self.slots.get(&id) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
let parent = parent
|
||||||
|
.and_then(|p| self.slots.get(&p).copied())
|
||||||
|
.unwrap_or(MoveIdx::NONE);
|
||||||
|
let idx = self.moves.push(parent);
|
||||||
|
self.slots.insert(id, idx);
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
|
||||||
/// The drawing a widget already has, kept for a new box if the box has not
|
/// The drawing a widget already has, kept for a new box if the box has not
|
||||||
/// changed in a way it depends on.
|
/// changed in a way it depends on.
|
||||||
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
|
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
|
||||||
@@ -178,12 +203,18 @@ impl UiRenderState {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let active = self.active.get(&id)?;
|
let active = self.active.get(&id)?;
|
||||||
let (size, old) = (active.size, active.region);
|
let (size, old, slot) = (active.size, active.region, active.move_idx);
|
||||||
if old == region {
|
// TODO: epsilon?
|
||||||
|
if old.size() == region.size() {
|
||||||
|
// The right shape and only somewhere else, which is one slot to
|
||||||
|
// write however much is under it. Both boxes are in the
|
||||||
|
// coordinates its parent drew, so the chain above applies alike.
|
||||||
|
let moved =
|
||||||
|
region.to_px(self.output_size).top_left - old.to_px(self.output_size).top_left;
|
||||||
|
self.moves.set(slot, moved);
|
||||||
return Some(size);
|
return Some(size);
|
||||||
}
|
}
|
||||||
// TODO: epsilon?
|
if !self.reusable(id, region, rsc) {
|
||||||
if old.size() != region.size() && !self.reusable(id, region, rsc) {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Its drawing stands, if the new box can be reached from the old one.
|
// Its drawing stands, if the new box can be reached from the old one.
|
||||||
@@ -228,6 +259,9 @@ impl UiRenderState {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Rewrites a subtree's regions into a new box, for a change a slot
|
||||||
|
/// cannot express. Every region it rewrites is a region some slot was a
|
||||||
|
/// delta from, so those go back to zero.
|
||||||
fn mov(&mut self, id: WidgetId, remap: &Remap) {
|
fn mov(&mut self, id: WidgetId, remap: &Remap) {
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
for h in &active.primitives {
|
for h in &active.primitives {
|
||||||
@@ -235,6 +269,7 @@ impl UiRenderState {
|
|||||||
*region = remap.apply(*region);
|
*region = remap.apply(*region);
|
||||||
}
|
}
|
||||||
active.region = remap.apply(active.region);
|
active.region = remap.apply(active.region);
|
||||||
|
self.moves.set(active.move_idx, Vec2::ZERO);
|
||||||
// SAFETY: children cannot be recursive
|
// SAFETY: children cannot be recursive
|
||||||
let children = unsafe { forget_ref(&active.children) };
|
let children = unsafe { forget_ref(&active.children) };
|
||||||
for child in children {
|
for child in children {
|
||||||
@@ -268,6 +303,10 @@ impl UiRenderState {
|
|||||||
self.remove_rec(*c, rsc);
|
self.remove_rec(*c, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// After the descendants, whose slots name this one as their parent.
|
||||||
|
if let Some(idx) = self.slots.remove(&id) {
|
||||||
|
self.moves.remove(idx);
|
||||||
|
}
|
||||||
inst
|
inst
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,6 +314,8 @@ impl UiRenderState {
|
|||||||
for (_, active) in self.active.drain() {
|
for (_, active) in self.active.drain() {
|
||||||
rsc.on_undraw(&active);
|
rsc.on_undraw(&active);
|
||||||
}
|
}
|
||||||
|
self.slots.clear();
|
||||||
|
self.moves.clear();
|
||||||
self.layers.clear();
|
self.layers.clear();
|
||||||
rsc.widgets_mut().needs_redraw.clear();
|
rsc.widgets_mut().needs_redraw.clear();
|
||||||
rsc.free();
|
rsc.free();
|
||||||
@@ -322,9 +363,16 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where a widget is on screen: the box it drew against plus whatever the
|
||||||
|
/// chain above has moved since, which is the walk the vertex shader does.
|
||||||
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
|
pub fn window_region(&self, id: &impl IdLike) -> Option<PixelRegion> {
|
||||||
let region = self.active.get(&id.id())?.region;
|
let active = self.active.get(&id.id())?;
|
||||||
Some(region.to_px(self.output_size))
|
let moved = self.moves.resolve(active.move_idx);
|
||||||
|
let region = active.region.to_px(self.output_size);
|
||||||
|
Some(PixelRegion {
|
||||||
|
top_left: region.top_left + moved,
|
||||||
|
bot_right: region.bot_right + moved,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// redraws a widget that's currently active (drawn)
|
/// redraws a widget that's currently active (drawn)
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ impl<T, I: IdNum> Arena<T, I> {
|
|||||||
self.tracker.free(id);
|
self.tracker.free(id);
|
||||||
self.data[i]
|
self.data[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||||
|
&mut self.data[id.idx()]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, I: IdNum> Default for Arena<T, I> {
|
impl<T, I: IdNum> Default for Arena<T, I> {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
use crate::util::impl_op;
|
use crate::util::impl_op;
|
||||||
use std::{hash::Hash, ops::*};
|
use std::{hash::Hash, ops::*};
|
||||||
|
|
||||||
#[repr(C)]
|
/// `align(8)` because that is WGSL's alignment for a `vec2<f32>`, so any GPU
|
||||||
|
/// struct holding one is laid out the way its shader reads it without having
|
||||||
|
/// to say so itself. Those structs still need a manual `unsafe impl Pod`,
|
||||||
|
/// since the trailing padding this introduces is what `derive(Pod)` refuses.
|
||||||
|
#[repr(C, align(8))]
|
||||||
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct Vec2 {
|
pub struct Vec2 {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
|
|||||||
+5
-2
@@ -22,8 +22,8 @@ use std::time::Instant;
|
|||||||
|
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use iris_core::{
|
use iris_core::{
|
||||||
GlyphPrimitive, MaskIdx, PrimitiveInst, RectPrimitive, TextureHandle, TexturePrimitive, UiData,
|
GlyphPrimitive, MaskIdx, MoveIdx, PrimitiveInst, RectPrimitive, TextureHandle,
|
||||||
UiRegion, UiRenderNode, UiRenderState,
|
TexturePrimitive, UiData, UiRegion, UiRenderNode, UiRenderState,
|
||||||
};
|
};
|
||||||
use wgpu::{Color as GpuColor, *};
|
use wgpu::{Color as GpuColor, *};
|
||||||
|
|
||||||
@@ -95,6 +95,7 @@ fn fill(
|
|||||||
primitive: RectPrimitive::color(UiColor::WHITE),
|
primitive: RectPrimitive::color(UiColor::WHITE),
|
||||||
region: UiRegion::FULL,
|
region: UiRegion::FULL,
|
||||||
mask_idx: MaskIdx::NONE,
|
mask_idx: MaskIdx::NONE,
|
||||||
|
move_idx: MoveIdx::NONE,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
render.layers.write(
|
render.layers.write(
|
||||||
@@ -111,6 +112,7 @@ fn fill(
|
|||||||
},
|
},
|
||||||
region: UiRegion::FULL,
|
region: UiRegion::FULL,
|
||||||
mask_idx: MaskIdx::NONE,
|
mask_idx: MaskIdx::NONE,
|
||||||
|
move_idx: MoveIdx::NONE,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -123,6 +125,7 @@ fn fill(
|
|||||||
primitive: TexturePrimitive::from(h),
|
primitive: TexturePrimitive::from(h),
|
||||||
region: UiRegion::FULL,
|
region: UiRegion::FULL,
|
||||||
mask_idx: MaskIdx::NONE,
|
mask_idx: MaskIdx::NONE,
|
||||||
|
move_idx: MoveIdx::NONE,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,3 +109,20 @@ fn a_fixed_box_is_drawn_again_rather_than_stretched() {
|
|||||||
|
|
||||||
assert_corners!(h, panel, (0, 0), (400, 250));
|
assert_corners!(h, panel, (0, 0), (400, 250));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_moved_subtree_takes_its_children_with_it() {
|
||||||
|
let mut h = Harness::new((400, 400));
|
||||||
|
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||||
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let row = inner.pad(10).height(40).add(&mut h.rsc);
|
||||||
|
h.set_root((first, row).span(Dir::DOWN));
|
||||||
|
assert_corners!(h, inner, (10, 50), (390, 70));
|
||||||
|
|
||||||
|
h.rsc[first].y = Some(Len::abs(80));
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
// The row is the same shape somewhere else, so one slot moved it and
|
||||||
|
// `inner`'s own region was never rewritten.
|
||||||
|
assert_corners!(h, inner, (10, 90), (390, 110));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user