iris: drive animation from painter frame time

This commit is contained in:
iris committed 2026-09-12 21:37:47 -04:00
1 parent 5f4975285d
commit eb30a01f2d
14 files changed
+148 -116

No files matched your search

+12 -26
View File
@@ -26,7 +26,6 @@ pub struct UiData {
pub text: Rc<RefCell<TextResources>>,
pub masks: TrackedArena<Mask, u32>,
pub move_offsets: TrackedArena<MoveOffset, u32>,
animating: Vec<WidgetId>,
}
#[derive(Clone)]
@@ -169,40 +168,27 @@ impl DerefMut for Ui {
}
}
impl UiData {
/// Ask for `id`'s [`crate::Widget::tick`] to run every frame until it
/// says it is done. Idempotent -- registering an already-animating
/// widget is the ordinary case (a second fling before the first
/// settled) and must not tick it twice per frame.
pub fn animate(&mut self, id: WidgetId) {
if !self.animating.contains(&id) {
self.animating.push(id);
}
}
pub fn tick_animations(&mut self, now: std::time::Instant) -> bool {
let mut registered = std::mem::take(&mut self.animating);
registered.retain(|&id| match self.widgets.get_dyn_mut(id) {
Some(widget) => widget.tick(now),
None => false,
});
for id in registered {
self.animate(id);
}
!self.animating.is_empty()
}
}
pub trait UiRsc {
fn ui(&self) -> &Ui;
fn ui_mut(&mut self) -> &mut Ui;
fn draw<'a>(&mut self, root: impl Into<Option<&'a crate::StrongWidget>>)
where
Self: Sized,
{
self.draw_at(root, std::time::Instant::now());
}
fn draw_at<'a>(
&mut self,
root: impl Into<Option<&'a crate::StrongWidget>>,
frame_time: std::time::Instant,
) -> bool
where
Self: Sized,
{
let render_state = self.ui().render_state.clone();
render_state.get_mut().update(root, self);
render_state.get_mut().update_at(root, self, frame_time)
}
#[allow(unused_variables)]
+16 -1
View File
@@ -9,7 +9,7 @@ use crate::{
ui::render_state::Retained,
util::Vec2,
};
use std::{cell::RefCell, rc::Rc};
use std::{cell::RefCell, rc::Rc, time::Instant};
pub struct Painter<'a> {
pub(super) render_state: &'a mut UiRenderState,
@@ -52,6 +52,21 @@ impl DrawResult<'_, '_> {
}
impl<'a> Painter<'a> {
/// The presentation time of this frame, supplied by the platform. On a
/// backend with a display clock this is the vsync timestamp, not the time
/// at which this widget happened to be drawn.
pub fn frame_time(&self) -> Instant {
self.render_state.frame_time
}
/// Redraw this widget on the following frame and ask the platform to
/// produce that frame. The invalidation is staged until the current
/// retained redraw has finished, so it cannot be consumed again in this
/// frame.
pub fn request_next_frame(&mut self) {
self.render_state.next_frame.insert(self.id);
}
pub fn set_size(&mut self, size: Size) {
assert!(
self.size.replace(size).is_none(),
+26
View File
@@ -45,6 +45,11 @@ pub struct UiRenderState {
/// guard could never fire and the set grew by one entry per widget
/// ever drawn and was never emptied.
draw_started: HashSet<WidgetId>,
/// Widgets which asked from inside `draw` to be drawn on the following
/// frame. Kept separate from `Widgets::needs_redraw` until `update_at`
/// finishes because `redraw_updates` drains that set to completion.
pub(super) next_frame: HashSet<WidgetId>,
pub(super) frame_time: Instant,
/// `Widget::draw` calls and `Primitives::region_mut` rewrites since the
/// last `take_counters`. LAYOUT.md section 8's pass conditions are
@@ -111,6 +116,8 @@ impl UiRenderState {
old_root: None,
resized: false,
draw_started: Default::default(),
next_frame: Default::default(),
frame_time: Instant::now(),
draw_count: 0,
region_mut_count: 0,
mov_count: 0,
@@ -225,6 +232,17 @@ impl UiRenderState {
}
pub fn update<'a>(&mut self, root: impl Into<Option<&'a StrongWidget>>, rsc: &mut dyn UiRsc) {
self.update_at(root, rsc, Instant::now());
}
/// Draw a frame dated on the platform's presentation clock. Returns
/// whether a widget requested another frame.
pub fn update_at<'a>(
&mut self,
root: impl Into<Option<&'a StrongWidget>>,
rsc: &mut dyn UiRsc,
frame_time: Instant,
) -> bool {
// safety mechanism for memory leaks; might wanna return a result instead so user can
// decide whether to panic or not
if !rsc.widgets().waiting.is_empty() {
@@ -241,6 +259,7 @@ impl UiRenderState {
weak widgets: {all:#?}"
);
}
self.frame_time = frame_time;
let root = root.into();
debug_assert!(
self.draw_started.is_empty(),
@@ -270,6 +289,13 @@ impl UiRenderState {
self.apply_free();
#[cfg(debug_assertions)]
debug_assert!(self.primitive_counts_agree(), "{}", self.orphan_report(rsc),);
let next: Vec<_> = std::mem::take(&mut self.next_frame)
.into_iter()
.filter(|id| self.active.contains_key(id))
.collect();
let requested = !next.is_empty();
rsc.widgets_mut().needs_redraw.extend(next);
requested
}
pub fn epoch(&self) -> Instant {
-5
View File
@@ -47,11 +47,6 @@ pub trait Widget: Any {
fn child_order(&self) -> ChildOrder {
ChildOrder::Draw
}
#[allow(unused_variables)]
fn tick(&mut self, now: std::time::Instant) -> bool {
false
}
}
impl Widget for () {