Compare commits

..
Author SHA1 Message Date
iris-ai a123d13490 Merge remote-tracking branch 'upstream/main' into split/17-headless-rig 2026-09-14 02:48:37 -04:00
iris-ai deb9c1b6d7 Detect the rig's binaries, and take the machine out of its comments
The comments described the machine the rig was written on -- "this
machine has no display", "there is a real GPU here", an Android
emulator's GLX quirk -- which says nothing to anyone reading it from a
different checkout. What the reader needs is what the script supplies
and why, which is now all they get.

`sway`, `swaymsg` and, when `--shot` is passed, `grim` are checked up
front and named in the failure, rather than surfacing as a compositor
that would not start.

The `# shellcheck disable=SC2086 -- prose` directive did not parse, so
the suppression was not in effect; the prose moves to its own line.
Clean under shellcheck now.
2026-09-14 00:36:19 -04:00
iris 9d13f15bee Bring the headless rig into the repository
A rendering claim about iris was verified by hand from another checkout,
because the compositor script and the input replay lived in ai-app's
submodule and not here.

`scripts/run-headless.sh` starts a headless sway on its own socket, runs
an example against it and screenshots the result. `rig-input`'s
`replay-touch` drives a recorded gesture in through Wayland's virtual
pointer, since a headless compositor has no input device to move.

`iris::harness` gains the `.touch` parser, so a recording means the same
thing replayed into a harness as into a window rather than being read
twice by two parsers.

The ai-app copy's `--phone` became `--mode`, since which phone is not
iris's business; its `IRIS_SCALE` has nothing to hand a density to here,
so it waits for one.
2026-09-13 23:10:23 -04:00
12 changed files with 46 additions and 304 deletions

No files matched your search

+4 -46
View File
@@ -1,10 +1,11 @@
use crate::{UiRegion, util::Id, util::Vec2};
use crate::{UiRegion, util::Id};
use wgpu::*;
#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)]
pub struct WindowUniform {
pub dim: Vec2,
pub width: f32,
pub height: f32,
}
#[repr(C)]
@@ -12,17 +13,15 @@ pub struct WindowUniform {
pub struct PrimitiveInstance {
pub region: UiRegion,
pub mask_idx: MaskIdx,
pub move_idx: MoveIdx,
}
impl PrimitiveInstance {
const ATTRIBS: [VertexAttribute; 6] = vertex_attr_array![
const ATTRIBS: [VertexAttribute; 5] = vertex_attr_array![
0 => Float32x2,
1 => Float32x2,
2 => Float32x2,
3 => Float32x2,
4 => Uint32,
5 => Uint32,
];
pub fn desc() -> VertexBufferLayout<'static> {
@@ -44,45 +43,4 @@ impl MaskIdx {
#[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Mask {
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,
}
}
}
+17 -44
View File
@@ -17,7 +17,7 @@ mod texture;
mod util;
pub use atlas::*;
pub use data::{Mask, MaskIdx, MoveIdx, MoveOffset};
pub use data::{Mask, MaskIdx};
pub use primitive::*;
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
@@ -34,7 +34,6 @@ pub struct UiRenderNode {
active: Vec<usize>,
window_buffer: Buffer,
masks: ArrBuf<Mask>,
moves: ArrBuf<MoveOffset>,
}
struct RenderLayer {
@@ -128,35 +127,32 @@ impl UiRenderNode {
for primitive in &mut self.primitives {
primitive.render.update(ui);
}
let mut regroup = false;
if ui.masks.changed {
ui.masks.changed = false;
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(
device,
&self.shared_layout,
&self.window_buffer,
&self.masks,
&self.moves,
);
if self.masks.update(device, queue, &ui.masks[..]) {
self.shared_group = Self::shared_group(
device,
&self.shared_layout,
&self.window_buffer,
&self.masks,
);
}
}
}
pub fn resize(&mut self, size: impl Into<Vec2>, queue: &Queue) {
let size = size.into();
let slice = &[WindowUniform { dim: size }];
let slice = &[WindowUniform {
width: size.x,
height: size.y,
}];
queue.write_buffer(&self.window_buffer, 0, bytemuck::cast_slice(slice));
}
pub fn new(device: &Device, config: &SurfaceConfiguration) -> Self {
let window_uniform = WindowUniform {
dim: Vec2::new(config.width as f32, config.height as f32),
width: config.width as f32,
height: config.height as f32,
};
let window_buffer = device.create_buffer_init(&BufferInitDescriptor {
label: Some("window"),
@@ -170,13 +166,7 @@ impl UiRenderNode {
BufferUsages::STORAGE | BufferUsages::COPY_DST,
"ui 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);
let shared_group = Self::shared_group(device, &shared_layout, &window_buffer, &masks);
Self {
shared_layout,
@@ -187,7 +177,6 @@ impl UiRenderNode {
layers: HashMap::default(),
active: Vec::new(),
masks,
moves,
}
}
@@ -263,8 +252,7 @@ impl UiRenderNode {
})
}
/// What every draw in the ui is given: the window, the masks and the
/// move chain every position is resolved through.
/// What every draw in the ui is given: the window and the masks.
fn shared_layout(device: &Device) -> BindGroupLayout {
device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[
@@ -288,16 +276,6 @@ impl UiRenderNode {
},
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"),
})
@@ -308,7 +286,6 @@ impl UiRenderNode {
layout: &BindGroupLayout,
window: &Buffer,
masks: &ArrBuf<Mask>,
moves: &ArrBuf<MoveOffset>,
) -> BindGroup {
device.create_bind_group(&BindGroupDescriptor {
layout,
@@ -321,10 +298,6 @@ impl UiRenderNode {
binding: 1,
resource: masks.buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 2,
resource: moves.buffer.as_entire_binding(),
},
],
label: Some("ui shared"),
})
+5 -11
View File
@@ -3,7 +3,7 @@ use std::{any::TypeId, marker::PhantomData};
use crate::{
Color, TextureHandle, UiData, UiRegion, WidgetId,
render::{
data::{MaskIdx, MoveIdx, PrimitiveInstance},
data::{MaskIdx, PrimitiveInstance},
page::GlyphRender,
texture::ImageRender,
},
@@ -246,7 +246,6 @@ impl LayerDraws {
primitive,
region,
mask_idx,
move_idx,
}: PrimitiveInst<P>,
) -> PrimitiveHandle {
self.updated = true;
@@ -259,11 +258,7 @@ impl LayerDraws {
.get_or_insert_with(InstanceList::new::<P>)
.push(
id,
PrimitiveInstance {
region,
mask_idx,
move_idx,
},
PrimitiveInstance { region, mask_idx },
bytemuck::bytes_of(&primitive),
);
PrimitiveHandle {
@@ -309,7 +304,6 @@ pub struct PrimitiveInst<P> {
pub primitive: P,
pub region: UiRegion,
pub mask_idx: MaskIdx,
pub move_idx: MoveIdx,
}
pub struct PrimitiveChange {
@@ -353,7 +347,7 @@ impl RectPrimitive {
/// `color` is multiplied by the atlas alpha for a mask glyph; a colour glyph
/// takes the texel unchanged, which `GlyphEntry::IS_COLORED` selects.
#[repr(C)]
#[repr(C, align(8))]
#[derive(Debug, Copy, Clone)]
pub struct GlyphPrimitive {
pub uv_min: Vec2,
@@ -364,8 +358,8 @@ pub struct GlyphPrimitive {
pub flags: u32,
}
// Manual rather than derived: `Vec2`'s alignment leaves four bytes of padding
// here, which is how WGSL lays the struct out.
// Manual rather than derived: the align(8) leaves four bytes of padding, which
// is how WGSL lays the struct out.
unsafe impl bytemuck::Pod for GlyphPrimitive {}
unsafe impl bytemuck::Zeroable for GlyphPrimitive {}
impl Primitive for GlyphPrimitive {
+4 -37
View File
@@ -7,8 +7,6 @@
var<uniform> window: WindowUniform;
@group(0) @binding(1)
var<storage> masks: array<Mask>;
@group(0) @binding(2)
var<storage> move_offsets: array<MoveOffset>;
struct WindowUniform {
dim: vec2<f32>,
@@ -17,32 +15,6 @@ struct WindowUniform {
struct Mask {
x: 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 {
@@ -61,7 +33,6 @@ struct InstanceInput {
@location(2) y_start: vec2<f32>,
@location(3) y_end: vec2<f32>,
@location(4) mask_idx: u32,
@location(5) move_idx: u32,
}
struct VertexOutput {
@@ -86,9 +57,8 @@ fn vs_main(
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 moved = resolve_move(in.move_idx);
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 top_left = floor(top_left_rel * window.dim) + floor(top_left_abs);
let bot_right = floor(bot_right_rel * window.dim) + floor(bot_right_abs);
let size = bot_right - top_left;
let uv = vec2<f32>(
@@ -116,11 +86,8 @@ fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
let br = vec2(mask.x.end.rel, mask.y.end.rel);
let br_abs = vec2(mask.x.end.abs, mask.y.end.abs);
// Its own chain, not the drawn primitive's, so a stationary viewport
// 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 top_left = floor(tl * window.dim) + floor(tl_abs);
let bot_right = floor(br * window.dim) + floor(br_abs);
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 {
return color * 0.0;
+1 -3
View File
@@ -1,4 +1,4 @@
use crate::{LayerId, MaskIdx, MoveIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId};
use crate::{LayerId, MaskIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId};
/// important non rendering data for retained drawing
#[derive(Debug)]
@@ -15,8 +15,6 @@ pub struct ActiveData {
pub size_deps: Vec<WidgetId>,
/// Whether it read the output's size, and so is wrong when that changes.
pub reads_output: bool,
/// The move slot its primitives are positioned through.
pub move_idx: MoveIdx,
pub mask: MaskIdx,
pub layer: LayerId,
}
+1 -67
View File
@@ -1,14 +1,7 @@
use crate::{
Mask, MoveIdx, MoveOffset, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId,
Widgets,
util::{Arena, Id, TrackedArena, Vec2},
Mask, PrimitiveRegistry, TextData, Textures, WeakWidget, WidgetId, Widgets, util::TrackedArena,
};
/// 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 painter;
mod render_state;
@@ -27,65 +20,6 @@ pub struct UiData {
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 {
fn ui(&self) -> &UiData;
fn ui_mut(&mut self) -> &mut UiData;
+3 -9
View File
@@ -2,8 +2,8 @@ use crate::{
Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, WidgetId,
render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
PrimitiveKind, TexturePrimitive,
GlyphPrimitive, Mask, MaskIdx, Primitive, PrimitiveHandle, PrimitiveInst, PrimitiveKind,
TexturePrimitive,
},
util::Vec2,
};
@@ -21,8 +21,6 @@ pub struct Painter<'a> {
/// The children whose size this widget read while drawing.
pub(super) size_deps: Vec<WidgetId>,
pub(super) reads_output: bool,
/// The move slot this widget's primitives are positioned through.
pub(super) move_idx: MoveIdx,
pub layer: usize,
pub(super) id: WidgetId,
}
@@ -43,7 +41,6 @@ impl<'a> Painter<'a> {
primitive,
region,
mask_idx: self.mask,
move_idx: self.move_idx,
},
);
self.push_primitive(h);
@@ -70,10 +67,7 @@ impl<'a> Painter<'a> {
pub fn set_mask(&mut self, region: UiRegion) {
assert!(self.mask == MaskIdx::NONE);
self.mask = self.rsc.ui_mut().masks.push(Mask {
region,
move_idx: self.move_idx,
});
self.mask = self.rsc.ui_mut().masks.push(Mask { region });
}
/// Draws a widget within this widget's region.
+8 -56
View File
@@ -1,6 +1,6 @@
use crate::{
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, Moves, OnResize, Painter, PixelRegion,
Remap, Size, StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Remap, Size,
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
util::{HashMap, HashSet, Vec2, forget_ref},
};
@@ -12,10 +12,6 @@ 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.
slots: HashMap<WidgetId, MoveIdx>,
pub moves: Moves,
}
impl UiRenderState {
@@ -27,8 +23,6 @@ impl UiRenderState {
old_root: None,
resized: false,
draw_started: Default::default(),
slots: Default::default(),
moves: Default::default(),
}
}
@@ -109,8 +103,6 @@ impl UiRenderState {
}
// draw widget
let move_idx = self.move_slot(id, parent);
self.moves.set(move_idx, Vec2::ZERO);
rsc.widgets_mut().needs_redraw.remove(&id);
self.draw_started.insert(id);
@@ -125,7 +117,6 @@ impl UiRenderState {
children: Vec::new(),
size_deps: Vec::new(),
reads_output: false,
move_idx,
rsc,
};
@@ -143,7 +134,6 @@ impl UiRenderState {
children,
size_deps,
reads_output,
move_idx,
layer,
id,
} = painter;
@@ -165,7 +155,6 @@ impl UiRenderState {
children,
size_deps,
reads_output,
move_idx,
mask,
layer,
};
@@ -182,20 +171,6 @@ 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>) -> 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
/// changed in a way it depends on.
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
@@ -203,18 +178,12 @@ impl UiRenderState {
return None;
}
let active = self.active.get(&id)?;
let (size, old, slot) = (active.size, active.region, active.move_idx);
// 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);
let (size, old) = (active.size, active.region);
if old == region {
return Some(size);
}
if !self.reusable(id, region, rsc) {
// TODO: epsilon?
if old.size() != region.size() && !self.reusable(id, region, rsc) {
return None;
}
// Its drawing stands, if the new box can be reached from the old one.
@@ -259,9 +228,6 @@ 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) {
let active = self.active.get_mut(&id).unwrap();
for h in &active.primitives {
@@ -269,7 +235,6 @@ impl UiRenderState {
*region = remap.apply(*region);
}
active.region = remap.apply(active.region);
self.moves.set(active.move_idx, Vec2::ZERO);
// SAFETY: children cannot be recursive
let children = unsafe { forget_ref(&active.children) };
for child in children {
@@ -303,10 +268,6 @@ impl UiRenderState {
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
}
@@ -314,8 +275,6 @@ impl UiRenderState {
for (_, active) in self.active.drain() {
rsc.on_undraw(&active);
}
self.slots.clear();
self.moves.clear();
self.layers.clear();
rsc.widgets_mut().needs_redraw.clear();
rsc.free();
@@ -363,16 +322,9 @@ 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> {
let active = self.active.get(&id.id())?;
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,
})
let region = self.active.get(&id.id())?.region;
Some(region.to_px(self.output_size))
}
/// redraws a widget that's currently active (drawn)
-4
View File
@@ -34,10 +34,6 @@ impl<T, I: IdNum> Arena<T, I> {
self.tracker.free(id);
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> {
+1 -5
View File
@@ -1,11 +1,7 @@
use crate::util::impl_op;
use std::{hash::Hash, ops::*};
/// `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))]
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Default, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vec2 {
pub x: f32,
+2 -5
View File
@@ -22,8 +22,8 @@ use std::time::Instant;
use iris::prelude::*;
use iris_core::{
GlyphPrimitive, MaskIdx, MoveIdx, PrimitiveInst, RectPrimitive, TextureHandle,
TexturePrimitive, UiData, UiRegion, UiRenderNode, UiRenderState,
GlyphPrimitive, MaskIdx, PrimitiveInst, RectPrimitive, TextureHandle, TexturePrimitive, UiData,
UiRegion, UiRenderNode, UiRenderState,
};
use wgpu::{Color as GpuColor, *};
@@ -95,7 +95,6 @@ fn fill(
primitive: RectPrimitive::color(UiColor::WHITE),
region: UiRegion::FULL,
mask_idx: MaskIdx::NONE,
move_idx: MoveIdx::NONE,
},
);
render.layers.write(
@@ -112,7 +111,6 @@ fn fill(
},
region: UiRegion::FULL,
mask_idx: MaskIdx::NONE,
move_idx: MoveIdx::NONE,
},
);
}
@@ -125,7 +123,6 @@ fn fill(
primitive: TexturePrimitive::from(h),
region: UiRegion::FULL,
mask_idx: MaskIdx::NONE,
move_idx: MoveIdx::NONE,
},
);
}
-17
View File
@@ -109,20 +109,3 @@ fn a_fixed_box_is_drawn_again_rather_than_stretched() {
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));
}