iris: eliminate retained-frame heap churn
This commit is contained in:
1 parent
acf206ef19
commit
32f6ad8c79
20 files changed
+507
-184
No files matched your search
@@ -617,6 +617,15 @@ impl From<&TextAttrs> for TextShapeAttrs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextShapeAttrs {
|
||||||
|
fn matches(&self, attrs: &TextAttrs) -> bool {
|
||||||
|
self.color == attrs.color
|
||||||
|
&& self.font_size == attrs.font_size
|
||||||
|
&& self.line_height == attrs.line_height
|
||||||
|
&& self.family == attrs.family
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const LINE_HEIGHT_MULT: f32 = 1.1;
|
pub const LINE_HEIGHT_MULT: f32 = 1.1;
|
||||||
|
|
||||||
impl Default for TextAttrs {
|
impl Default for TextAttrs {
|
||||||
@@ -701,10 +710,16 @@ impl TextBuffer {
|
|||||||
width: Option<f32>,
|
width: Option<f32>,
|
||||||
density: f32,
|
density: f32,
|
||||||
) {
|
) {
|
||||||
let shape_attrs = TextShapeAttrs::from(attrs);
|
if self
|
||||||
if self.shaped.as_ref() == Some(&(shape_attrs.clone(), width, density)) {
|
.shaped
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(old, old_width, old_density)| {
|
||||||
|
old.matches(attrs) && *old_width == width && *old_density == density
|
||||||
|
})
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let shape_attrs = TextShapeAttrs::from(attrs);
|
||||||
let base_family = data.resolve_family(&attrs.family);
|
let base_family = data.resolve_family(&attrs.family);
|
||||||
let span_families: Vec<Option<String>> = self
|
let span_families: Vec<Option<String>> = self
|
||||||
.spans
|
.spans
|
||||||
|
|||||||
@@ -34,6 +34,14 @@ pub struct TextureHandle {
|
|||||||
rsc: RscHandle<TextureRsc>,
|
rsc: RscHandle<TextureRsc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TextureHandle {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.rsc.id() == other.rsc.id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for TextureHandle {}
|
||||||
|
|
||||||
/// a texture manager for a ui
|
/// a texture manager for a ui
|
||||||
/// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped
|
/// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped
|
||||||
pub struct Textures {
|
pub struct Textures {
|
||||||
|
|||||||
+13
-3
@@ -112,6 +112,14 @@ impl WgpuErrorLog {
|
|||||||
pub fn snapshot(&self) -> Vec<String> {
|
pub fn snapshot(&self) -> Vec<String> {
|
||||||
self.errors.lock().unwrap().iter().cloned().collect()
|
self.errors.lock().unwrap().iter().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.errors.lock().unwrap().len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.errors.lock().unwrap().is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UiRenderNode {
|
pub struct UiRenderNode {
|
||||||
@@ -206,11 +214,13 @@ impl UiRenderNode {
|
|||||||
rlayer.order.update(device, queue, entries, dirty);
|
rlayer.order.update(device, queue, entries, dirty);
|
||||||
let (entries, dirty) = order.images_for_upload();
|
let (entries, dirty) = order.images_for_upload();
|
||||||
rlayer.images.update(device, queue, entries, dirty);
|
rlayer.images.update(device, queue, entries, dirty);
|
||||||
rlayer.image_tex_indices = order
|
rlayer.image_tex_indices.clear();
|
||||||
|
rlayer.image_tex_indices.extend(
|
||||||
|
order
|
||||||
.images()
|
.images()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&slot| ui_render.primitives.instance(slot).idx)
|
.map(|&slot| ui_render.primitives.instance(slot).idx),
|
||||||
.collect();
|
);
|
||||||
order.updated = false;
|
order.updated = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,19 +450,26 @@ impl LayerOrder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_free(&mut self) -> Vec<OrderChange> {
|
pub fn apply_free(&mut self) -> Vec<OrderChange> {
|
||||||
let mut changes = Self::apply_free_list(
|
let mut changes = Vec::new();
|
||||||
|
self.apply_free_into(&mut changes);
|
||||||
|
changes
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn apply_free_into(&mut self, changes: &mut Vec<OrderChange>) {
|
||||||
|
Self::apply_free_list(
|
||||||
&mut self.free,
|
&mut self.free,
|
||||||
&mut self.order,
|
&mut self.order,
|
||||||
&mut self.order_dirty,
|
&mut self.order_dirty,
|
||||||
false,
|
false,
|
||||||
|
changes,
|
||||||
);
|
);
|
||||||
changes.extend(Self::apply_free_list(
|
Self::apply_free_list(
|
||||||
&mut self.image_free,
|
&mut self.image_free,
|
||||||
&mut self.images,
|
&mut self.images,
|
||||||
&mut self.images_dirty,
|
&mut self.images_dirty,
|
||||||
true,
|
true,
|
||||||
));
|
changes,
|
||||||
changes
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn order_for_upload(&mut self) -> (&[u32], &mut Dirty) {
|
pub fn order_for_upload(&mut self) -> (&[u32], &mut Dirty) {
|
||||||
@@ -478,22 +485,20 @@ impl LayerOrder {
|
|||||||
list: &mut Vec<u32>,
|
list: &mut Vec<u32>,
|
||||||
dirty: &mut Dirty,
|
dirty: &mut Dirty,
|
||||||
is_image: bool,
|
is_image: bool,
|
||||||
) -> Vec<OrderChange> {
|
changes: &mut Vec<OrderChange>,
|
||||||
|
) {
|
||||||
free.sort_by(|a, b| b.cmp(a));
|
free.sort_by(|a, b| b.cmp(a));
|
||||||
free.drain(..)
|
for pos in free.drain(..) {
|
||||||
.filter_map(|pos| {
|
|
||||||
list.swap_remove(pos);
|
list.swap_remove(pos);
|
||||||
if pos == list.len() {
|
if pos != list.len() {
|
||||||
return None;
|
|
||||||
}
|
|
||||||
dirty.mark(pos);
|
dirty.mark(pos);
|
||||||
Some(OrderChange {
|
changes.push(OrderChange {
|
||||||
slot: list[pos],
|
slot: list[pos],
|
||||||
is_image,
|
is_image,
|
||||||
pos,
|
pos,
|
||||||
})
|
});
|
||||||
})
|
}
|
||||||
.collect()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn order(&self) -> &Vec<u32> {
|
pub fn order(&self) -> &Vec<u32> {
|
||||||
@@ -523,7 +528,7 @@ pub enum Drawn {
|
|||||||
|
|
||||||
pub const NOT_DRAWN: usize = usize::MAX;
|
pub const NOT_DRAWN: usize = usize::MAX;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct PrimitiveHandle {
|
pub struct PrimitiveHandle {
|
||||||
pub layer: usize,
|
pub layer: usize,
|
||||||
pub pos: usize,
|
pub pos: usize,
|
||||||
|
|||||||
@@ -90,13 +90,13 @@ impl<T: Pod> ArrBuf<T> {
|
|||||||
}
|
}
|
||||||
self.len = data.len();
|
self.len = data.len();
|
||||||
let stride = std::mem::size_of::<T>() as BufferAddress;
|
let stride = std::mem::size_of::<T>() as BufferAddress;
|
||||||
for range in dirty.ranges(data.len(), Self::MERGE_GAP) {
|
dirty.for_each_range(data.len(), Self::MERGE_GAP, |range| {
|
||||||
queue.write_buffer(
|
queue.write_buffer(
|
||||||
&self.buffer,
|
&self.buffer,
|
||||||
range.start as BufferAddress * stride,
|
range.start as BufferAddress * stride,
|
||||||
bytemuck::cast_slice(&data[range]),
|
bytemuck::cast_slice(&data[range]),
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
dirty.clear();
|
dirty.clear();
|
||||||
reallocated
|
reallocated
|
||||||
}
|
}
|
||||||
|
|||||||
+52
-17
@@ -10,11 +10,11 @@ fn node_id(id: WidgetId) -> NodeId {
|
|||||||
NodeId(id.as_u64())
|
NodeId(id.as_u64())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
name: String,
|
name: String,
|
||||||
role: Role,
|
role: Role,
|
||||||
bounds: PixelRegion,
|
bounds: PixelRegion,
|
||||||
|
seen: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn entry_node(entry: &Entry) -> Node {
|
fn entry_node(entry: &Entry) -> Node {
|
||||||
@@ -37,6 +37,7 @@ fn entry_node(entry: &Entry) -> Node {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AccessTree {
|
pub struct AccessTree {
|
||||||
known: HashMap<WidgetId, Entry>,
|
known: HashMap<WidgetId, Entry>,
|
||||||
|
generation: u64,
|
||||||
rebuilds: u64,
|
rebuilds: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,12 +46,17 @@ impl AccessTree {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect(
|
/// Refresh the retained accessibility state and report whether it changed.
|
||||||
widgets: &Widgets,
|
/// Existing entries are updated in place so an ordinary frame allocates
|
||||||
render: &UiRenderState,
|
/// nothing, including one where only bounds changed.
|
||||||
rsc: &dyn UiRsc,
|
pub fn refresh(&mut self, widgets: &Widgets, render: &UiRenderState, rsc: &dyn UiRsc) -> bool {
|
||||||
) -> HashMap<WidgetId, Entry> {
|
self.generation = self.generation.wrapping_add(1);
|
||||||
let mut current = HashMap::default();
|
if self.generation == 0 {
|
||||||
|
self.known.clear();
|
||||||
|
self.generation = 1;
|
||||||
|
}
|
||||||
|
let generation = self.generation;
|
||||||
|
let mut changed = false;
|
||||||
for id in widgets.named() {
|
for id in widgets.named() {
|
||||||
let Some(bounds) = render.window_region(&id, rsc) else {
|
let Some(bounds) = render.window_region(&id, rsc) else {
|
||||||
continue;
|
continue;
|
||||||
@@ -58,16 +64,42 @@ impl AccessTree {
|
|||||||
let Some(widget) = widgets.get_dyn(id) else {
|
let Some(widget) = widgets.get_dyn(id) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
current.insert(
|
let name = widgets.label(id);
|
||||||
|
let role = widget.access_role();
|
||||||
|
match self.known.get_mut(&id) {
|
||||||
|
Some(entry) => {
|
||||||
|
if entry.name != *name {
|
||||||
|
entry.name.clone_from(name);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
if entry.role != role || entry.bounds != bounds {
|
||||||
|
entry.role = role;
|
||||||
|
entry.bounds = bounds;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
entry.seen = generation;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.known.insert(
|
||||||
id,
|
id,
|
||||||
Entry {
|
Entry {
|
||||||
name: widgets.label(id).clone(),
|
name: name.clone(),
|
||||||
role: widget.access_role(),
|
role,
|
||||||
bounds,
|
bounds,
|
||||||
|
seen: generation,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
current
|
}
|
||||||
|
}
|
||||||
|
let old_len = self.known.len();
|
||||||
|
self.known.retain(|_, entry| entry.seen == generation);
|
||||||
|
changed |= self.known.len() != old_len;
|
||||||
|
if changed {
|
||||||
|
self.rebuilds += 1;
|
||||||
|
}
|
||||||
|
changed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walks `widgets.named()`, looks up each one's current screen bounds
|
/// Walks `widgets.named()`, looks up each one's current screen bounds
|
||||||
@@ -84,13 +116,14 @@ impl AccessTree {
|
|||||||
render: &UiRenderState,
|
render: &UiRenderState,
|
||||||
rsc: &dyn UiRsc,
|
rsc: &dyn UiRsc,
|
||||||
) -> Option<TreeUpdate> {
|
) -> Option<TreeUpdate> {
|
||||||
let current = Self::collect(widgets, render, rsc);
|
if !self.refresh(widgets, render, rsc) {
|
||||||
if current == self.known {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.known = current.clone();
|
Some(self.tree_update())
|
||||||
self.rebuilds += 1;
|
}
|
||||||
Some(build_update(¤t))
|
|
||||||
|
pub fn tree_update(&self) -> TreeUpdate {
|
||||||
|
build_update(&self.known)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The unconditional twin of `update`, for a platform adapter's
|
/// The unconditional twin of `update`, for a platform adapter's
|
||||||
@@ -100,7 +133,9 @@ impl AccessTree {
|
|||||||
/// meant to answer (it may have already sent this same snapshot to a
|
/// meant to answer (it may have already sent this same snapshot to a
|
||||||
/// client that has since detached and reattached).
|
/// client that has since detached and reattached).
|
||||||
pub fn build_full(widgets: &Widgets, render: &UiRenderState, rsc: &dyn UiRsc) -> TreeUpdate {
|
pub fn build_full(widgets: &Widgets, render: &UiRenderState, rsc: &dyn UiRsc) -> TreeUpdate {
|
||||||
build_update(&Self::collect(widgets, render, rsc))
|
let mut tree = Self::new();
|
||||||
|
tree.refresh(widgets, render, rsc);
|
||||||
|
tree.tree_update()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_rebuilds(&mut self) -> u64 {
|
pub fn take_rebuilds(&mut self) -> u64 {
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ pub struct ActiveData {
|
|||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
pub parent: Option<WidgetId>,
|
pub parent: Option<WidgetId>,
|
||||||
pub textures: Vec<TextureHandle>,
|
pub textures: Vec<TextureHandle>,
|
||||||
|
pub(crate) spare_textures: Vec<TextureHandle>,
|
||||||
/// Paint slots retained by this draw. The GPU primitive stores only the
|
/// Paint slots retained by this draw. The GPU primitive stores only the
|
||||||
/// slot index, so these handles are what prevent a live primitive from
|
/// slot index, so these handles are what prevent a live primitive from
|
||||||
/// observing a recycled paint.
|
/// observing a recycled paint.
|
||||||
pub paints: Vec<PaintId>,
|
pub paints: Vec<PaintId>,
|
||||||
|
pub(crate) spare_paints: Vec<PaintId>,
|
||||||
pub primitives: Vec<PrimitiveHandle>,
|
pub primitives: Vec<PrimitiveHandle>,
|
||||||
|
pub(crate) spare_primitives: Vec<PrimitiveHandle>,
|
||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
|
pub(crate) spare_children: Vec<WidgetId>,
|
||||||
pub size_dependencies: Vec<WidgetId>,
|
pub size_dependencies: Vec<WidgetId>,
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
/// The widget's retained mask slot, or `MaskIdx::NONE`.
|
/// The widget's retained mask slot, or `MaskIdx::NONE`.
|
||||||
|
|||||||
+4
-6
@@ -122,13 +122,11 @@ impl Ui {
|
|||||||
update(&mut text)?;
|
update(&mut text)?;
|
||||||
text.invalidate_all()
|
text.invalidate_all()
|
||||||
};
|
};
|
||||||
let active: Vec<WidgetId> = {
|
let mut active = owners;
|
||||||
|
{
|
||||||
let render = self.render_state.get();
|
let render = self.render_state.get();
|
||||||
owners
|
active.retain(|owner| render.active.contains_key(owner));
|
||||||
.into_iter()
|
}
|
||||||
.filter(|owner| render.active.contains_key(owner))
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
self.data.widgets.needs_redraw.extend(active);
|
self.data.widgets.needs_redraw.extend(active);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-8
@@ -21,9 +21,12 @@ pub struct Painter<'a> {
|
|||||||
pub(super) child_move_slot: Option<MoveIdx>,
|
pub(super) child_move_slot: Option<MoveIdx>,
|
||||||
pub(super) own_mask: MaskIdx,
|
pub(super) own_mask: MaskIdx,
|
||||||
pub(super) textures: Vec<TextureHandle>,
|
pub(super) textures: Vec<TextureHandle>,
|
||||||
|
pub(super) recycle_textures: Vec<TextureHandle>,
|
||||||
pub(super) paints: Vec<PaintId>,
|
pub(super) paints: Vec<PaintId>,
|
||||||
|
pub(super) recycle_paints: Vec<PaintId>,
|
||||||
pub(super) primitives: Vec<PrimitiveHandle>,
|
pub(super) primitives: Vec<PrimitiveHandle>,
|
||||||
pub(super) recycle: std::iter::Peekable<std::vec::IntoIter<PrimitiveHandle>>,
|
pub(super) recycle: Vec<PrimitiveHandle>,
|
||||||
|
pub(super) recycle_at: usize,
|
||||||
pub(super) children: Vec<WidgetId>,
|
pub(super) children: Vec<WidgetId>,
|
||||||
pub(super) size_dependencies: Vec<WidgetId>,
|
pub(super) size_dependencies: Vec<WidgetId>,
|
||||||
pub(super) size: Option<Size>,
|
pub(super) size: Option<Size>,
|
||||||
@@ -87,12 +90,13 @@ impl<'a> Painter<'a> {
|
|||||||
/// anyway. Stopping is also what keeps the invariant simple: every
|
/// anyway. Stopping is also what keeps the invariant simple: every
|
||||||
/// handle from `recycled` on is untouched and gets freed together.
|
/// handle from `recycled` on is untouched and gets freed together.
|
||||||
fn take_recycled(&mut self, binding: u32, drawn: Drawn) -> Option<PrimitiveHandle> {
|
fn take_recycled(&mut self, binding: u32, drawn: Drawn) -> Option<PrimitiveHandle> {
|
||||||
let h = self.recycle.peek()?;
|
let h = *self.recycle.get(self.recycle_at)?;
|
||||||
let drawn_matches = (h.pos == NOT_DRAWN) == (drawn == Drawn::No);
|
let drawn_matches = (h.pos == NOT_DRAWN) == (drawn == Drawn::No);
|
||||||
if h.binding != binding || h.layer != self.layer || !drawn_matches {
|
if h.binding != binding || h.layer != self.layer || !drawn_matches {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
self.recycle.next()
|
self.recycle_at += 1;
|
||||||
|
Some(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_primitive<P: Primitive>(
|
fn write_primitive<P: Primitive>(
|
||||||
@@ -139,14 +143,18 @@ impl<'a> Painter<'a> {
|
|||||||
/// primitive and retains the handle for exactly as long as that draw.
|
/// primitive and retains the handle for exactly as long as that draw.
|
||||||
pub fn paint(&mut self, paint: &PaintId) -> u32 {
|
pub fn paint(&mut self, paint: &PaintId) -> u32 {
|
||||||
if !self.paints.contains(paint) {
|
if !self.paints.contains(paint) {
|
||||||
|
if let Some(i) = self.recycle_paints.iter().position(|old| old == paint) {
|
||||||
|
self.paints.push(self.recycle_paints.swap_remove(i));
|
||||||
|
} else {
|
||||||
self.paints.push(paint.clone());
|
self.paints.push(paint.clone());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
paint.slot()
|
paint.slot()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn paint_value(&mut self, paint: &mut crate::PaintValue) -> u32 {
|
pub fn paint_value(&mut self, paint: &mut crate::PaintValue) -> u32 {
|
||||||
let paint = paint.resolve(&mut self.rsc.ui_mut().paints).clone();
|
let paint = paint.resolve(&mut self.rsc.ui_mut().paints);
|
||||||
self.paint(&paint)
|
self.paint(paint)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
pub fn primitive_within<P: Primitive>(&mut self, primitive: P, region: UiRegion) {
|
||||||
@@ -387,20 +395,31 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
pub fn texture_within(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||||
self.textures.push(handle.clone());
|
self.retain_texture(handle);
|
||||||
self.write_image(handle.image_index(), region.within(&self.region));
|
self.write_image(handle.image_index(), region.within(&self.region));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn texture(&mut self, handle: &TextureHandle) {
|
pub fn texture(&mut self, handle: &TextureHandle) {
|
||||||
self.textures.push(handle.clone());
|
self.retain_texture(handle);
|
||||||
self.write_image(handle.image_index(), self.region);
|
self.write_image(handle.image_index(), self.region);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
pub fn texture_at(&mut self, handle: &TextureHandle, region: UiRegion) {
|
||||||
self.textures.push(handle.clone());
|
self.retain_texture(handle);
|
||||||
self.write_image(handle.image_index(), region);
|
self.write_image(handle.image_index(), region);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn retain_texture(&mut self, handle: &TextureHandle) {
|
||||||
|
if self.textures.contains(handle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(i) = self.recycle_textures.iter().position(|old| old == handle) {
|
||||||
|
self.textures.push(self.recycle_textures.swap_remove(i));
|
||||||
|
} else {
|
||||||
|
self.textures.push(handle.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
|
fn write_image(&mut self, texture_idx: u32, region: UiRegion) {
|
||||||
let h = match self.take_recycled(IMAGE_BINDING, Drawn::Yes) {
|
let h = match self.take_recycled(IMAGE_BINDING, Drawn::Yes) {
|
||||||
Some(h) => {
|
Some(h) => {
|
||||||
|
|||||||
+82
-21
@@ -5,8 +5,8 @@ use crate::{
|
|||||||
ActiveData, Axis, ChildOrder, IdLike, MaskIdx, MoveIdx, Painter, PixelRegion, PrimitiveLayers,
|
ActiveData, Axis, ChildOrder, IdLike, MaskIdx, MoveIdx, Painter, PixelRegion, PrimitiveLayers,
|
||||||
RegionAlign, Size, StrongWidget, UiRegion, UiRsc, UiVec2, Widget, WidgetId, Widgets,
|
RegionAlign, Size, StrongWidget, UiRegion, UiRsc, UiVec2, Widget, WidgetId, Widgets,
|
||||||
render::{
|
render::{
|
||||||
Drawn, MoveOffset, NOT_DRAWN, Primitive, PrimitiveHandle, PrimitiveInst, Primitives,
|
Drawn, MoveOffset, NOT_DRAWN, OrderChange, Primitive, PrimitiveHandle, PrimitiveInst,
|
||||||
RectPrimitive, rounded_rect_coverage,
|
Primitives, RectPrimitive, rounded_rect_coverage,
|
||||||
},
|
},
|
||||||
util::{HashMap, HashSet, Id, Vec2},
|
util::{HashMap, HashSet, Id, Vec2},
|
||||||
};
|
};
|
||||||
@@ -30,6 +30,7 @@ pub struct UiRenderState {
|
|||||||
pub active: HashMap<WidgetId, ActiveData>,
|
pub active: HashMap<WidgetId, ActiveData>,
|
||||||
pub primitives: Primitives,
|
pub primitives: Primitives,
|
||||||
pub layers: PrimitiveLayers,
|
pub layers: PrimitiveLayers,
|
||||||
|
order_changes: Vec<OrderChange>,
|
||||||
pub(super) output_size: Vec2,
|
pub(super) output_size: Vec2,
|
||||||
/// Physical pixels per `dp` -- see `LayoutLen::dp`'s field doc. `1.0` (an
|
/// Physical pixels per `dp` -- see `LayoutLen::dp`'s field doc. `1.0` (an
|
||||||
/// unscaled display) until a backend that knows its own density calls
|
/// unscaled display) until a backend that knows its own density calls
|
||||||
@@ -80,11 +81,17 @@ pub struct UiRenderState {
|
|||||||
pub(crate) struct Retained {
|
pub(crate) struct Retained {
|
||||||
pub region: Option<UiRegion>,
|
pub region: Option<UiRegion>,
|
||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
|
pub spare_children: Vec<WidgetId>,
|
||||||
pub move_slot: Option<MoveIdx>,
|
pub move_slot: Option<MoveIdx>,
|
||||||
pub child_move_slot: Option<MoveIdx>,
|
pub child_move_slot: Option<MoveIdx>,
|
||||||
pub own_mask: MaskIdx,
|
pub own_mask: MaskIdx,
|
||||||
|
pub textures: Vec<crate::TextureHandle>,
|
||||||
|
pub spare_textures: Vec<crate::TextureHandle>,
|
||||||
pub primitives: Vec<PrimitiveHandle>,
|
pub primitives: Vec<PrimitiveHandle>,
|
||||||
|
pub spare_primitives: Vec<PrimitiveHandle>,
|
||||||
pub paints: Vec<crate::PaintId>,
|
pub paints: Vec<crate::PaintId>,
|
||||||
|
pub spare_paints: Vec<crate::PaintId>,
|
||||||
|
pub size_dependencies: Vec<WidgetId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Retained {
|
impl Default for Retained {
|
||||||
@@ -92,11 +99,17 @@ impl Default for Retained {
|
|||||||
Self {
|
Self {
|
||||||
region: None,
|
region: None,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
spare_children: Vec::new(),
|
||||||
move_slot: None,
|
move_slot: None,
|
||||||
child_move_slot: None,
|
child_move_slot: None,
|
||||||
own_mask: MaskIdx::NONE,
|
own_mask: MaskIdx::NONE,
|
||||||
|
textures: Vec::new(),
|
||||||
|
spare_textures: Vec::new(),
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
|
spare_primitives: Vec::new(),
|
||||||
paints: Vec::new(),
|
paints: Vec::new(),
|
||||||
|
spare_paints: Vec::new(),
|
||||||
|
size_dependencies: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,6 +122,7 @@ impl UiRenderState {
|
|||||||
active: Default::default(),
|
active: Default::default(),
|
||||||
primitives: Default::default(),
|
primitives: Default::default(),
|
||||||
layers: Default::default(),
|
layers: Default::default(),
|
||||||
|
order_changes: Vec::new(),
|
||||||
output_size: Vec2::ZERO,
|
output_size: Vec2::ZERO,
|
||||||
density: 1.0,
|
density: 1.0,
|
||||||
old_root: None,
|
old_root: None,
|
||||||
@@ -184,8 +198,11 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn apply_free(&mut self) {
|
fn apply_free(&mut self) {
|
||||||
|
let mut changes = std::mem::take(&mut self.order_changes);
|
||||||
for (layer, order) in self.layers.iter_mut() {
|
for (layer, order) in self.layers.iter_mut() {
|
||||||
for change in order.apply_free() {
|
changes.clear();
|
||||||
|
order.apply_free_into(&mut changes);
|
||||||
|
for change in changes.drain(..) {
|
||||||
let owner = self.primitives.owner(change.slot);
|
let owner = self.primitives.owner(change.slot);
|
||||||
let Some(idx) = self.primitives.handle_index(change.slot) else {
|
let Some(idx) = self.primitives.handle_index(change.slot) else {
|
||||||
continue;
|
continue;
|
||||||
@@ -204,6 +221,7 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.order_changes = changes;
|
||||||
self.primitives.release_freed();
|
self.primitives.release_freed();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,11 +398,17 @@ impl UiRenderState {
|
|||||||
let Retained {
|
let Retained {
|
||||||
region: mut old_region,
|
region: mut old_region,
|
||||||
children: mut old_children,
|
children: mut old_children,
|
||||||
|
spare_children: mut children,
|
||||||
move_slot: mut old_move_slot,
|
move_slot: mut old_move_slot,
|
||||||
mut child_move_slot,
|
mut child_move_slot,
|
||||||
mut own_mask,
|
mut own_mask,
|
||||||
|
textures: mut recycle_textures,
|
||||||
|
spare_textures: mut textures,
|
||||||
primitives: mut recycle,
|
primitives: mut recycle,
|
||||||
paints: _old_paints,
|
spare_primitives: mut primitives,
|
||||||
|
paints: mut recycle_paints,
|
||||||
|
spare_paints: mut paints,
|
||||||
|
mut size_dependencies,
|
||||||
} = retained;
|
} = retained;
|
||||||
let dirty = rsc.widgets_mut().needs_redraw.remove(&id);
|
let dirty = rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
let requires_exact_region = rsc
|
let requires_exact_region = rsc
|
||||||
@@ -434,10 +458,17 @@ impl UiRenderState {
|
|||||||
let active = self.remove(id, false, true, rsc).unwrap();
|
let active = self.remove(id, false, true, rsc).unwrap();
|
||||||
old_region = Some(active.region);
|
old_region = Some(active.region);
|
||||||
old_children = active.children;
|
old_children = active.children;
|
||||||
|
children = active.spare_children;
|
||||||
old_move_slot = Some(active.move_slot);
|
old_move_slot = Some(active.move_slot);
|
||||||
child_move_slot = active.child_move_slot;
|
child_move_slot = active.child_move_slot;
|
||||||
own_mask = active.own_mask;
|
own_mask = active.own_mask;
|
||||||
|
recycle_textures = active.textures;
|
||||||
|
textures = active.spare_textures;
|
||||||
recycle = active.primitives;
|
recycle = active.primitives;
|
||||||
|
primitives = active.spare_primitives;
|
||||||
|
recycle_paints = active.paints;
|
||||||
|
paints = active.spare_paints;
|
||||||
|
size_dependencies = active.size_dependencies;
|
||||||
} else if self.active.contains_key(&id) {
|
} else if self.active.contains_key(&id) {
|
||||||
let layer_changed = self
|
let layer_changed = self
|
||||||
.active
|
.active
|
||||||
@@ -449,12 +480,25 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
old_region = Some(active.region);
|
old_region = Some(active.region);
|
||||||
old_children = active.children;
|
old_children = active.children;
|
||||||
|
children = active.spare_children;
|
||||||
old_move_slot = Some(active.move_slot);
|
old_move_slot = Some(active.move_slot);
|
||||||
child_move_slot = active.child_move_slot;
|
child_move_slot = active.child_move_slot;
|
||||||
own_mask = active.own_mask;
|
own_mask = active.own_mask;
|
||||||
|
recycle_textures = active.textures;
|
||||||
|
textures = active.spare_textures;
|
||||||
recycle = active.primitives;
|
recycle = active.primitives;
|
||||||
|
primitives = active.spare_primitives;
|
||||||
|
recycle_paints = active.paints;
|
||||||
|
paints = active.spare_paints;
|
||||||
|
size_dependencies = active.size_dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textures.clear();
|
||||||
|
paints.clear();
|
||||||
|
primitives.clear();
|
||||||
|
children.clear();
|
||||||
|
size_dependencies.clear();
|
||||||
|
|
||||||
let reentrant = !self.draw_started.insert(id);
|
let reentrant = !self.draw_started.insert(id);
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
!reentrant,
|
!reentrant,
|
||||||
@@ -481,12 +525,15 @@ impl UiRenderState {
|
|||||||
own_mask,
|
own_mask,
|
||||||
layer,
|
layer,
|
||||||
id,
|
id,
|
||||||
textures: Vec::new(),
|
textures,
|
||||||
paints: Vec::new(),
|
recycle_textures,
|
||||||
primitives: Vec::new(),
|
paints,
|
||||||
recycle: recycle.into_iter().peekable(),
|
recycle_paints,
|
||||||
children: Vec::new(),
|
primitives,
|
||||||
size_dependencies: Vec::new(),
|
recycle,
|
||||||
|
recycle_at: 0,
|
||||||
|
children,
|
||||||
|
size_dependencies,
|
||||||
size: None,
|
size: None,
|
||||||
reuse_child_sizes,
|
reuse_child_sizes,
|
||||||
rsc,
|
rsc,
|
||||||
@@ -534,9 +581,12 @@ impl UiRenderState {
|
|||||||
child_move_slot,
|
child_move_slot,
|
||||||
own_mask,
|
own_mask,
|
||||||
textures,
|
textures,
|
||||||
|
mut recycle_textures,
|
||||||
paints,
|
paints,
|
||||||
|
mut recycle_paints,
|
||||||
primitives,
|
primitives,
|
||||||
recycle,
|
mut recycle,
|
||||||
|
recycle_at,
|
||||||
children,
|
children,
|
||||||
size_dependencies,
|
size_dependencies,
|
||||||
size: _,
|
size: _,
|
||||||
@@ -545,18 +595,31 @@ impl UiRenderState {
|
|||||||
id,
|
id,
|
||||||
} = painter;
|
} = painter;
|
||||||
|
|
||||||
for h in recycle {
|
for h in &recycle[recycle_at..] {
|
||||||
self.free_primitive(&h);
|
self.free_primitive(h);
|
||||||
}
|
}
|
||||||
|
for c in &old_children {
|
||||||
|
if !children.contains(c) {
|
||||||
|
self.remove_rec(*c, rsc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recycle.clear();
|
||||||
|
recycle_textures.clear();
|
||||||
|
recycle_paints.clear();
|
||||||
|
old_children.clear();
|
||||||
|
|
||||||
let active = ActiveData {
|
let active = ActiveData {
|
||||||
id,
|
id,
|
||||||
region,
|
region,
|
||||||
parent,
|
parent,
|
||||||
textures,
|
textures,
|
||||||
|
spare_textures: recycle_textures,
|
||||||
paints,
|
paints,
|
||||||
|
spare_paints: recycle_paints,
|
||||||
primitives,
|
primitives,
|
||||||
|
spare_primitives: recycle,
|
||||||
children,
|
children,
|
||||||
|
spare_children: old_children,
|
||||||
size_dependencies,
|
size_dependencies,
|
||||||
mask: inherited_mask,
|
mask: inherited_mask,
|
||||||
layer: inherited_layer,
|
layer: inherited_layer,
|
||||||
@@ -567,12 +630,6 @@ impl UiRenderState {
|
|||||||
move_applied: Vec2::ZERO,
|
move_applied: Vec2::ZERO,
|
||||||
};
|
};
|
||||||
|
|
||||||
for c in &old_children {
|
|
||||||
if !active.children.contains(c) {
|
|
||||||
self.remove_rec(*c, rsc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rsc.on_draw(&active);
|
rsc.on_draw(&active);
|
||||||
self.active.insert(id, active);
|
self.active.insert(id, active);
|
||||||
size
|
size
|
||||||
@@ -713,8 +770,6 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::remask_shape_users(&self.active, id, active.own_mask, &active.primitives, rsc);
|
Self::remask_shape_users(&self.active, id, active.own_mask, &active.primitives, rsc);
|
||||||
active.textures.clear();
|
|
||||||
rsc.ui_mut().textures.free();
|
|
||||||
if undraw {
|
if undraw {
|
||||||
// A captured widget that goes away mid-gesture (LazySpan's
|
// A captured widget that goes away mid-gesture (LazySpan's
|
||||||
// virtualisation retiring a row, a rebuild) must not leave
|
// virtualisation retiring a row, a rebuild) must not leave
|
||||||
@@ -1137,11 +1192,17 @@ impl UiRenderState {
|
|||||||
Retained {
|
Retained {
|
||||||
region: Some(active.region),
|
region: Some(active.region),
|
||||||
children: active.children,
|
children: active.children,
|
||||||
|
spare_children: active.spare_children,
|
||||||
move_slot: Some(active.move_slot),
|
move_slot: Some(active.move_slot),
|
||||||
child_move_slot: active.child_move_slot,
|
child_move_slot: active.child_move_slot,
|
||||||
own_mask: active.own_mask,
|
own_mask: active.own_mask,
|
||||||
|
textures: active.textures,
|
||||||
|
spare_textures: active.spare_textures,
|
||||||
primitives: active.primitives,
|
primitives: active.primitives,
|
||||||
|
spare_primitives: active.spare_primitives,
|
||||||
paints: active.paints,
|
paints: active.paints,
|
||||||
|
spare_paints: active.spare_paints,
|
||||||
|
size_dependencies: active.size_dependencies,
|
||||||
},
|
},
|
||||||
rsc,
|
rsc,
|
||||||
);
|
);
|
||||||
|
|||||||
+26
-6
@@ -62,10 +62,24 @@ impl Dirty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ranges(&self, len: usize, gap: usize) -> Vec<Range<usize>> {
|
pub fn ranges(&self, len: usize, gap: usize) -> Vec<Range<usize>> {
|
||||||
if self.all {
|
let mut ranges = Vec::new();
|
||||||
return Vec::from_iter((len > 0).then_some(0..len));
|
self.for_each_range(len, gap, |range| ranges.push(range));
|
||||||
|
ranges
|
||||||
}
|
}
|
||||||
let mut ranges: Vec<Range<usize>> = Vec::new();
|
|
||||||
|
pub(crate) fn for_each_range(
|
||||||
|
&self,
|
||||||
|
len: usize,
|
||||||
|
gap: usize,
|
||||||
|
mut visit: impl FnMut(Range<usize>),
|
||||||
|
) {
|
||||||
|
if self.all {
|
||||||
|
if len > 0 {
|
||||||
|
visit(0..len);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut pending: Option<Range<usize>> = None;
|
||||||
for (w, word) in self.words.iter().enumerate() {
|
for (w, word) in self.words.iter().enumerate() {
|
||||||
let mut bits = *word;
|
let mut bits = *word;
|
||||||
while bits != 0 {
|
while bits != 0 {
|
||||||
@@ -75,14 +89,20 @@ impl Dirty {
|
|||||||
if start >= len {
|
if start >= len {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
match ranges.last_mut() {
|
match pending.as_mut() {
|
||||||
Some(last) if start - last.end <= gap => last.end = end,
|
Some(last) if start - last.end <= gap => last.end = end,
|
||||||
_ => ranges.push(start..end),
|
_ => {
|
||||||
|
if let Some(range) = pending.replace(start..end) {
|
||||||
|
visit(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
bits &= !(((1u128 << run) - 1) as u64) << (start - w * 64);
|
bits &= !(((1u128 << run) - 1) as u64) << (start - w * 64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ranges
|
if let Some(range) = pending {
|
||||||
|
visit(range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
|
|||||||
+11
-3
@@ -8,10 +8,18 @@ pub struct WidgetData {
|
|||||||
|
|
||||||
impl WidgetData {
|
impl WidgetData {
|
||||||
pub fn new<W: Widget>(widget: W) -> Self {
|
pub fn new<W: Widget>(widget: W) -> Self {
|
||||||
let mut label = std::any::type_name::<W>().to_string();
|
let name = std::any::type_name::<W>();
|
||||||
if let (Some(first), Some(last)) = (label.find(":"), label.rfind(":")) {
|
let label = match (name.find("::"), name.rfind("::")) {
|
||||||
label = label.split_at(first).0.to_string() + "::" + label.split_at(last + 1).1;
|
(Some(first), Some(last)) => {
|
||||||
|
let suffix = &name[last + 2..];
|
||||||
|
let mut label = String::with_capacity(first + 2 + suffix.len());
|
||||||
|
label.push_str(&name[..first]);
|
||||||
|
label.push_str("::");
|
||||||
|
label.push_str(suffix);
|
||||||
|
label
|
||||||
}
|
}
|
||||||
|
_ => name.to_owned(),
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
widget: Box::new(widget),
|
widget: Box::new(widget),
|
||||||
label,
|
label,
|
||||||
|
|||||||
+9
-4
@@ -379,7 +379,7 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
|||||||
frame_diagnostics.paints_resized,
|
frame_diagnostics.paints_resized,
|
||||||
frame_diagnostics.atlas_pages_grown_prev,
|
frame_diagnostics.atlas_pages_grown_prev,
|
||||||
frame_diagnostics.image_bind_group_creates_prev,
|
frame_diagnostics.image_bind_group_creates_prev,
|
||||||
renderer.wgpu_errors.snapshot().len(),
|
renderer.wgpu_errors.len(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let mut parts = renderer.draw();
|
let mut parts = renderer.draw();
|
||||||
@@ -410,13 +410,18 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let ui_state = self.state.android_state_mut();
|
let ui_state = self.state.android_state_mut();
|
||||||
if let Some(tree_update) = ui_state.access.update(
|
let access_changed = ui_state.access.refresh(
|
||||||
self.rsc.widgets(),
|
self.rsc.widgets(),
|
||||||
&self.rsc.ui().render_state().get(),
|
&self.rsc.ui().render_state().get(),
|
||||||
&self.rsc,
|
&self.rsc,
|
||||||
) {
|
);
|
||||||
|
if access_changed {
|
||||||
let ui_state = self.state.android_state_mut();
|
let ui_state = self.state.android_state_mut();
|
||||||
if let Some(events) = ui_state.access_adapter.update_if_active(|| tree_update) {
|
let access = &ui_state.access;
|
||||||
|
if let Some(events) = ui_state
|
||||||
|
.access_adapter
|
||||||
|
.update_if_active(|| access.tree_update())
|
||||||
|
{
|
||||||
ctx.push_dynamic_deferred_callback(move |env, view| {
|
ctx.push_dynamic_deferred_callback(move |env, view| {
|
||||||
raise_if_enabled(env, view, events);
|
raise_if_enabled(env, view, events);
|
||||||
});
|
});
|
||||||
|
|||||||
+5
-3
@@ -269,9 +269,11 @@ impl<State: DesktopAppState> AppState for DesktopApp<State> {
|
|||||||
let render_state = rsc.ui.render_state();
|
let render_state = rsc.ui.render_state();
|
||||||
let render_state = render_state.get();
|
let render_state = render_state.get();
|
||||||
crate::diagnostics::log_frame(&render_state, frame_start, parts, animating);
|
crate::diagnostics::log_frame(&render_state, frame_start, parts, animating);
|
||||||
if let Some(tree_update) = ui_state.access.update(rsc.widgets(), &render_state, rsc)
|
if ui_state.access.refresh(rsc.widgets(), &render_state, rsc) {
|
||||||
{
|
let access = &ui_state.access;
|
||||||
ui_state.access_adapter.update_if_active(|| tree_update);
|
ui_state
|
||||||
|
.access_adapter
|
||||||
|
.update_if_active(|| access.tree_update());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(size) => {
|
WindowEvent::Resized(size) => {
|
||||||
|
|||||||
+16
-16
@@ -328,18 +328,17 @@ impl SensorUi for UiRenderState {
|
|||||||
// Platform cancellation reaches every tracker and produces no other sense.
|
// Platform cancellation reaches every tracker and produces no other sense.
|
||||||
if cursor.cancelled {
|
if cursor.cancelled {
|
||||||
let captured = pointer.captured.take();
|
let captured = pointer.captured.take();
|
||||||
let mut told: Vec<WidgetId> = captured.into_iter().collect();
|
|
||||||
for id in pointer.pressed.drain(..) {
|
|
||||||
if Some(id) != captured {
|
|
||||||
told.push(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
requests.release();
|
requests.release();
|
||||||
pointer.press_origin = None;
|
pointer.press_origin = None;
|
||||||
pointer.drag_axis = None;
|
pointer.drag_axis = None;
|
||||||
for id in told {
|
if let Some(id) = captured {
|
||||||
deliver_cancel(self, rsc, state, id, &cursor, window_size, &requests);
|
deliver_cancel(self, rsc, state, id, &cursor, window_size, &requests);
|
||||||
}
|
}
|
||||||
|
for id in pointer.pressed.drain(..) {
|
||||||
|
if Some(id) != captured {
|
||||||
|
deliver_cancel(self, rsc, state, id, &cursor, window_size, &requests);
|
||||||
|
}
|
||||||
|
}
|
||||||
rsc.events_mut().get_type::<CursorSense>().global = pointer;
|
rsc.events_mut().get_type::<CursorSense>().global = pointer;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -442,15 +441,16 @@ impl SensorUi for UiRenderState {
|
|||||||
pointer.captured = requests.holder();
|
pointer.captured = requests.holder();
|
||||||
match pointer.captured {
|
match pointer.captured {
|
||||||
Some(winner) => {
|
Some(winner) => {
|
||||||
let losers: Vec<WidgetId> = pointer
|
let mut winner_was_pressed = false;
|
||||||
.pressed
|
for id in pointer.pressed.drain(..) {
|
||||||
.iter()
|
if id == winner {
|
||||||
.copied()
|
winner_was_pressed = true;
|
||||||
.filter(|&id| id != winner)
|
} else {
|
||||||
.collect();
|
deliver_cancel(self, rsc, state, id, &cursor, window_size, &requests);
|
||||||
pointer.pressed.retain(|&id| id == winner);
|
}
|
||||||
for loser in losers {
|
}
|
||||||
deliver_cancel(self, rsc, state, loser, &cursor, window_size, &requests);
|
if winner_was_pressed {
|
||||||
|
pointer.pressed.push(winner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None if !button_down => pointer.pressed.clear(),
|
None if !button_down => pointer.pressed.clear(),
|
||||||
|
|||||||
+42
-39
@@ -17,17 +17,13 @@ impl Widget for Span {
|
|||||||
let axis = self.dir.axis;
|
let axis = self.dir.axis;
|
||||||
let gap = self.gap.apply_rest(painter.density()).abs;
|
let gap = self.gap.apply_rest(painter.density()).abs;
|
||||||
|
|
||||||
let mut lens: Vec<Option<LayoutLen>> = self
|
let gap_total = gap * self.children.len().saturating_sub(1) as f32;
|
||||||
.children
|
let mut measured = Vec::with_capacity(self.children.len());
|
||||||
.iter()
|
let mut total = LayoutLen::abs(gap_total);
|
||||||
.map(|child| painter.known_len(child, axis))
|
|
||||||
.collect();
|
|
||||||
let mut drawn = vec![false; self.children.len()];
|
|
||||||
|
|
||||||
let mut cursor = UiScalar::rel_min();
|
let mut cursor = UiScalar::rel_min();
|
||||||
for (i, child) in self.children.iter().enumerate() {
|
for child in &self.children {
|
||||||
let len = match lens[i] {
|
let (len, drawn) = match painter.known_len(child, axis) {
|
||||||
Some(len) => len,
|
Some(len) => (len, false),
|
||||||
None => {
|
None => {
|
||||||
let mut slot = UiSpan::new(cursor, UiScalar::rel_max());
|
let mut slot = UiSpan::new(cursor, UiScalar::rel_max());
|
||||||
if self.dir.sign == Sign::Neg {
|
if self.dir.sign == Sign::Neg {
|
||||||
@@ -35,47 +31,25 @@ impl Widget for Span {
|
|||||||
}
|
}
|
||||||
let region = UiRegion::from_axis(axis, slot, UiSpan::FULL);
|
let region = UiRegion::from_axis(axis, slot, UiSpan::FULL);
|
||||||
let len = painter.widget_within(child, region).size().axis(axis);
|
let len = painter.widget_within(child, region).size().axis(axis);
|
||||||
lens[i] = Some(len);
|
(len, true)
|
||||||
drawn[i] = true;
|
|
||||||
len
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
measured.push((len, drawn));
|
||||||
|
total += len;
|
||||||
cursor.abs += len.abs + gap;
|
cursor.abs += len.abs + gap;
|
||||||
cursor.rel += len.rel;
|
cursor.rel += len.rel;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lens: Vec<LayoutLen> = lens.into_iter().map(Option::unwrap).collect();
|
|
||||||
|
|
||||||
let gap_total = gap * self.children.len().saturating_sub(1) as f32;
|
|
||||||
let total = lens.iter().fold(LayoutLen::abs(gap_total), |s, &l| s + l);
|
|
||||||
|
|
||||||
let mut start = UiScalar::rel_min();
|
let mut start = UiScalar::rel_min();
|
||||||
let mut ortho_len = LayoutLen::ZERO;
|
let mut ortho_len = LayoutLen::ZERO;
|
||||||
let mut ortho_mixed = false;
|
let mut ortho_mixed = false;
|
||||||
let mut placed = Vec::with_capacity(self.children.len());
|
for (child, &(len, drawn)) in self.children.iter().zip(&measured) {
|
||||||
for (i, (child, &len)) in self.children.iter().zip(&lens).enumerate() {
|
let child_region = child_region(axis, self.dir.sign, total, gap, &mut start, len);
|
||||||
let mut span = UiSpan::FULL;
|
let used = if drawn {
|
||||||
span.start = start;
|
|
||||||
if len.rest > 0.0 {
|
|
||||||
let offset = UiScalar::new(total.rel, total.abs);
|
|
||||||
let rel_end = UiScalar::rel(len.rest / total.rest);
|
|
||||||
let end = (UiScalar::rel_max() + start) - offset;
|
|
||||||
start = rel_end.within(&start.to(end));
|
|
||||||
}
|
|
||||||
start.abs += len.abs;
|
|
||||||
start.rel += len.rel;
|
|
||||||
span.end = start;
|
|
||||||
let mut child_region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
|
||||||
if self.dir.sign == Sign::Neg {
|
|
||||||
child_region.flip(axis);
|
|
||||||
}
|
|
||||||
let used = if drawn[i] {
|
|
||||||
painter.place(child, child_region).size()
|
painter.place(child, child_region).size()
|
||||||
} else {
|
} else {
|
||||||
painter.widget_within(child, child_region).size()
|
painter.widget_within(child, child_region).size()
|
||||||
};
|
};
|
||||||
placed.push(child_region);
|
|
||||||
start.abs += gap;
|
|
||||||
|
|
||||||
let ortho = used.axis(!axis);
|
let ortho = used.axis(!axis);
|
||||||
if ortho.rel > 0.0 || ortho.rest > 0.0 {
|
if ortho.rel > 0.0 || ortho.rest > 0.0 {
|
||||||
@@ -90,7 +64,9 @@ impl Widget for Span {
|
|||||||
let ortho = ortho_len
|
let ortho = ortho_len
|
||||||
.apply_rest(painter.density())
|
.apply_rest(painter.density())
|
||||||
.align(AxisAlign::Neg);
|
.align(AxisAlign::Neg);
|
||||||
for (child, mut region) in self.children.iter().zip(placed) {
|
let mut start = UiScalar::rel_min();
|
||||||
|
for (child, &(len, _)) in self.children.iter().zip(&measured) {
|
||||||
|
let mut region = child_region(axis, self.dir.sign, total, gap, &mut start, len);
|
||||||
*region.axis_mut(!axis) = ortho;
|
*region.axis_mut(!axis) = ortho;
|
||||||
painter.place(child, region);
|
painter.place(child, region);
|
||||||
}
|
}
|
||||||
@@ -106,6 +82,33 @@ impl Widget for Span {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn child_region(
|
||||||
|
axis: Axis,
|
||||||
|
sign: Sign,
|
||||||
|
total: LayoutLen,
|
||||||
|
gap: f32,
|
||||||
|
start: &mut UiScalar,
|
||||||
|
len: LayoutLen,
|
||||||
|
) -> UiRegion {
|
||||||
|
let mut span = UiSpan::FULL;
|
||||||
|
span.start = *start;
|
||||||
|
if len.rest > 0.0 {
|
||||||
|
let offset = UiScalar::new(total.rel, total.abs);
|
||||||
|
let rel_end = UiScalar::rel(len.rest / total.rest);
|
||||||
|
let end = (UiScalar::rel_max() + *start) - offset;
|
||||||
|
*start = rel_end.within(&start.to(end));
|
||||||
|
}
|
||||||
|
start.abs += len.abs;
|
||||||
|
start.rel += len.rel;
|
||||||
|
span.end = *start;
|
||||||
|
start.abs += gap;
|
||||||
|
let mut region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
||||||
|
if sign == Sign::Neg {
|
||||||
|
region.flip(axis);
|
||||||
|
}
|
||||||
|
region
|
||||||
|
}
|
||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
pub fn empty(dir: Dir) -> Self {
|
pub fn empty(dir: Dir) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
+18
-13
@@ -20,27 +20,32 @@ impl Widget for Stack {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
let region = known.map(|size| size.to_uivec2(density).align(RegionAlign::TOP_LEFT));
|
let region = known.map(|size| size.to_uivec2(density).align(RegionAlign::TOP_LEFT));
|
||||||
let mut used = Vec::with_capacity(self.children.len());
|
let mut used = known
|
||||||
let mut iter = self.children.iter();
|
.is_none()
|
||||||
if let Some(child) = iter.next() {
|
.then(|| Vec::with_capacity(self.children.len()));
|
||||||
|
let mut selected = known.unwrap_or_default();
|
||||||
|
for (i, child) in self.children.iter().enumerate() {
|
||||||
|
if i == 0 {
|
||||||
painter.child_layer();
|
painter.child_layer();
|
||||||
used.push(match region {
|
} else {
|
||||||
Some(region) => painter.widget_within(child, region).size(),
|
|
||||||
None => painter.widget(child).size(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for child in iter {
|
|
||||||
painter.next_layer();
|
painter.next_layer();
|
||||||
used.push(match region {
|
}
|
||||||
|
let child_size = match region {
|
||||||
Some(region) => painter.widget_within(child, region).size(),
|
Some(region) => painter.widget_within(child, region).size(),
|
||||||
None => painter.widget(child).size(),
|
None => painter.widget(child).size(),
|
||||||
});
|
};
|
||||||
|
if matches!(self.size, StackSize::Child(target) if target == i) {
|
||||||
|
selected = child_size;
|
||||||
|
}
|
||||||
|
if let Some(used) = &mut used {
|
||||||
|
used.push(child_size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let size = match self.size {
|
let size = match self.size {
|
||||||
StackSize::Default => Size::default(),
|
StackSize::Default => Size::default(),
|
||||||
StackSize::Child(i) => used.get(i).copied().unwrap_or_default(),
|
StackSize::Child(_) => selected,
|
||||||
};
|
};
|
||||||
if known.is_none() {
|
if let Some(used) = used {
|
||||||
let final_region = size.to_uivec2(density).align(RegionAlign::TOP_LEFT);
|
let final_region = size.to_uivec2(density).align(RegionAlign::TOP_LEFT);
|
||||||
for (child, child_size) in self.children.iter().zip(used) {
|
for (child, child_size) in self.children.iter().zip(used) {
|
||||||
let child_region = child_size
|
let child_region = child_size
|
||||||
|
|||||||
+12
-13
@@ -188,16 +188,18 @@ impl TextView {
|
|||||||
self.overflow = OverflowState::default();
|
self.overflow = OverflowState::default();
|
||||||
let available = painter.px_size().x.max(0.0);
|
let available = painter.px_size().x.max(0.0);
|
||||||
let excess = (tex.size.x - available).max(0.0);
|
let excess = (tex.size.x - available).max(0.0);
|
||||||
let attrs = self.text.attrs().clone();
|
let (overflow, overflow_position) = {
|
||||||
if excess <= 0.0 || matches!(attrs.overflow, TextOverflow::Visible | TextOverflow::Wrap) {
|
let attrs = self.text.attrs();
|
||||||
|
(attrs.overflow, attrs.overflow_position)
|
||||||
|
};
|
||||||
|
if excess <= 0.0 || matches!(overflow, TextOverflow::Visible | TextOverflow::Wrap) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let marker =
|
let marker =
|
||||||
(attrs.overflow == TextOverflow::Ellipsis).then(|| painter.render_ellipsis(&self.text));
|
(overflow == TextOverflow::Ellipsis).then(|| painter.render_ellipsis(&self.text));
|
||||||
|
|
||||||
let mut pan = attrs
|
let mut pan = overflow_position
|
||||||
.overflow_position
|
|
||||||
.resolve(painter.density())
|
.resolve(painter.density())
|
||||||
.to_abs(excess)
|
.to_abs(excess)
|
||||||
.clamp(0.0, excess);
|
.clamp(0.0, excess);
|
||||||
@@ -229,10 +231,7 @@ impl TextView {
|
|||||||
}
|
}
|
||||||
pan = pan.clamp(0.0, excess);
|
pan = pan.clamp(0.0, excess);
|
||||||
}
|
}
|
||||||
let old_pan = attrs
|
let old_pan = overflow_position.resolve(painter.density()).to_abs(excess);
|
||||||
.overflow_position
|
|
||||||
.resolve(painter.density())
|
|
||||||
.to_abs(excess);
|
|
||||||
if pan != old_pan {
|
if pan != old_pan {
|
||||||
self.text.set_overflow_position(Len::abs(pan));
|
self.text.set_overflow_position(Len::abs(pan));
|
||||||
}
|
}
|
||||||
@@ -242,7 +241,7 @@ impl TextView {
|
|||||||
self.overflow.viewport = vec2(available, tex.size.y);
|
self.overflow.viewport = vec2(available, tex.size.y);
|
||||||
self.overflow.pan = pan;
|
self.overflow.pan = pan;
|
||||||
self.overflow.content_end = available;
|
self.overflow.content_end = available;
|
||||||
if attrs.overflow != TextOverflow::Ellipsis {
|
if overflow != TextOverflow::Ellipsis {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,14 +286,14 @@ impl TextView {
|
|||||||
return painter.widget(hint).size();
|
return painter.widget(hint).size();
|
||||||
}
|
}
|
||||||
let marker = self.prepare_overflow(painter, &tex, follow_caret);
|
let marker = self.prepare_overflow(painter, &tex, follow_caret);
|
||||||
let attrs = self.text.attrs().clone();
|
let align = self.text.attrs().align;
|
||||||
if !self.overflow.active {
|
if !self.overflow.active {
|
||||||
let within = tex.size.align(attrs.align).within(&painter.region());
|
let within = tex.size.align(align).within(&painter.region());
|
||||||
painter.glyphs(&tex, within);
|
painter.glyphs(&tex, within);
|
||||||
return Size::abs(tex.size);
|
return Size::abs(tex.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewport = self.overflow.viewport.align(attrs.align);
|
let viewport = self.overflow.viewport.align(align);
|
||||||
let marker_highlight = painter.paint(&PaintId::SKY);
|
let marker_highlight = painter.paint(&PaintId::SKY);
|
||||||
if self.overflow.suppress_content
|
if self.overflow.suppress_content
|
||||||
&& self
|
&& self
|
||||||
|
|||||||
@@ -312,15 +312,16 @@ impl SelectionController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn deselect(&mut self, rsc: &mut impl UiRsc) {
|
fn deselect(&mut self, rsc: &mut impl UiRsc) {
|
||||||
let mut ids = std::mem::take(&mut self.selected);
|
let anchor = self.anchor.take().map(|(id, _)| id);
|
||||||
if let Some((anchor, _)) = self.anchor.take()
|
let anchor_was_selected = anchor.is_some_and(|anchor| self.selected.contains(&anchor));
|
||||||
&& !ids.contains(&anchor)
|
for id in self.selected.drain(..) {
|
||||||
{
|
|
||||||
ids.push(anchor);
|
|
||||||
}
|
|
||||||
for id in ids {
|
|
||||||
Self::with_text(rsc, id, |text| text.deselect());
|
Self::with_text(rsc, id, |text| text.deselect());
|
||||||
}
|
}
|
||||||
|
if let Some(anchor) = anchor
|
||||||
|
&& !anchor_was_selected
|
||||||
|
{
|
||||||
|
Self::with_text(rsc, anchor, |text| text.deselect());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn begin(&mut self, rsc: &mut impl UiRsc, id: WidgetId, pos: Vec2, size: Vec2) {
|
fn begin(&mut self, rsc: &mut impl UiRsc, id: WidgetId, pos: Vec2, size: Vec2) {
|
||||||
@@ -352,8 +353,7 @@ impl SelectionController {
|
|||||||
(focus_at, anchor_at)
|
(focus_at, anchor_at)
|
||||||
};
|
};
|
||||||
|
|
||||||
let old = std::mem::take(&mut self.selected);
|
for &old_id in &self.selected {
|
||||||
for old_id in old {
|
|
||||||
if !self.order[lo..=hi].contains(&old_id) {
|
if !self.order[lo..=hi].contains(&old_id) {
|
||||||
Self::with_text(rsc, old_id, |text| text.deselect());
|
Self::with_text(rsc, old_id, |text| text.deselect());
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,8 @@ impl SelectionController {
|
|||||||
text.select_between(start, end);
|
text.select_between(start, end);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.selected = self.order[lo..=hi].to_vec();
|
self.selected.clear();
|
||||||
|
self.selected.extend_from_slice(&self.order[lo..=hi]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drag<Rsc: HasEvents>(
|
pub fn drag<Rsc: HasEvents>(
|
||||||
@@ -486,12 +487,11 @@ impl<Rsc: HasEvents> Controller<Rsc> for SelectionController {
|
|||||||
.map(CommandResult::Copy)
|
.map(CommandResult::Copy)
|
||||||
.unwrap_or(CommandResult::Unused),
|
.unwrap_or(CommandResult::Unused),
|
||||||
Command::SelectAll => {
|
Command::SelectAll => {
|
||||||
let order = self.order.clone();
|
|
||||||
self.deselect(rsc);
|
self.deselect(rsc);
|
||||||
for &id in &order {
|
for &id in &self.order {
|
||||||
Self::with_text(rsc, id, |text| text.select_all());
|
Self::with_text(rsc, id, |text| text.select_all());
|
||||||
}
|
}
|
||||||
self.selected = order;
|
self.selected.extend_from_slice(&self.order);
|
||||||
CommandResult::Used
|
CommandResult::Used
|
||||||
}
|
}
|
||||||
Command::Escape => {
|
Command::Escape => {
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
use iris::prelude::*;
|
||||||
|
use std::{
|
||||||
|
alloc::{GlobalAlloc, Layout, System},
|
||||||
|
cell::Cell,
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CountingAllocator;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static TRACKING: Cell<bool> = const { Cell::new(false) };
|
||||||
|
static ALLOCATIONS: Cell<usize> = const { Cell::new(0) };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn note_allocation() {
|
||||||
|
TRACKING.with(|tracking| {
|
||||||
|
if tracking.get() {
|
||||||
|
ALLOCATIONS.with(|allocations| allocations.set(allocations.get() + 1));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl GlobalAlloc for CountingAllocator {
|
||||||
|
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let ptr = unsafe { System.alloc(layout) };
|
||||||
|
note_allocation();
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
|
||||||
|
let ptr = unsafe { System.alloc_zeroed(layout) };
|
||||||
|
note_allocation();
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||||
|
unsafe { System.dealloc(ptr, layout) }
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
|
||||||
|
let ptr = unsafe { System.realloc(ptr, layout, new_size) };
|
||||||
|
note_allocation();
|
||||||
|
ptr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOCATOR: CountingAllocator = CountingAllocator;
|
||||||
|
|
||||||
|
fn allocations_during(run: impl FnOnce()) -> usize {
|
||||||
|
ALLOCATIONS.with(|allocations| allocations.set(0));
|
||||||
|
TRACKING.with(|tracking| tracking.set(true));
|
||||||
|
run();
|
||||||
|
TRACKING.with(|tracking| tracking.set(false));
|
||||||
|
ALLOCATIONS.with(Cell::get)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TestRsc {
|
||||||
|
ui: Ui,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiRsc for TestRsc {
|
||||||
|
fn ui(&self) -> &Ui {
|
||||||
|
&self.ui
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_mut(&mut self) -> &mut Ui {
|
||||||
|
&mut self.ui
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FrameRequester;
|
||||||
|
|
||||||
|
impl Widget for FrameRequester {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
|
let paint = painter.paint(&PaintId::WHITE);
|
||||||
|
painter.primitive(RectPrimitive::color(paint));
|
||||||
|
painter.request_next_frame();
|
||||||
|
painter.set_size(Size::REST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_warmed_self_redraw_allocates_nothing() {
|
||||||
|
let mut rsc = TestRsc { ui: Ui::default() };
|
||||||
|
let root = rsc.ui.widgets.add_strong(FrameRequester).any();
|
||||||
|
let mut render = UiRenderState::new();
|
||||||
|
render.resize((800.0, 600.0));
|
||||||
|
let start = Instant::now();
|
||||||
|
|
||||||
|
for frame in 0..3 {
|
||||||
|
assert!(render.update_at(&root, &mut rsc, start + Duration::from_millis(frame * 16),));
|
||||||
|
}
|
||||||
|
|
||||||
|
let allocations = allocations_during(|| {
|
||||||
|
assert!(render.update_at(&root, &mut rsc, start + Duration::from_millis(48),));
|
||||||
|
});
|
||||||
|
assert_eq!(allocations, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refreshing_retained_accessibility_allocates_nothing() {
|
||||||
|
let mut rsc = TestRsc { ui: Ui::default() };
|
||||||
|
let leaf = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE));
|
||||||
|
rsc.ui.widgets.set_label(&leaf, "named".to_owned());
|
||||||
|
let offset = rsc.ui.widgets.add_strong(Offset {
|
||||||
|
inner: leaf.any(),
|
||||||
|
amt: UiVec2::ZERO,
|
||||||
|
});
|
||||||
|
let offset_id = offset.weak();
|
||||||
|
let root = offset.any();
|
||||||
|
let mut render = UiRenderState::new();
|
||||||
|
render.resize((800.0, 600.0));
|
||||||
|
render.update(&root, &mut rsc);
|
||||||
|
let mut access = AccessTree::new();
|
||||||
|
assert!(access.refresh(rsc.widgets(), &render, &rsc));
|
||||||
|
|
||||||
|
rsc.ui.widgets.get_mut(&offset_id).unwrap().amt = UiVec2::abs(Vec2::new(20.0, 0.0));
|
||||||
|
render.update(&root, &mut rsc);
|
||||||
|
|
||||||
|
let moved = allocations_during(|| assert!(access.refresh(rsc.widgets(), &render, &rsc)));
|
||||||
|
assert_eq!(moved, 0);
|
||||||
|
|
||||||
|
let unchanged = allocations_during(|| assert!(!access.refresh(rsc.widgets(), &render, &rsc)));
|
||||||
|
assert_eq!(unchanged, 0);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user