iris: drive animation from painter frame time
This commit is contained in:
1 parent
5f4975285d
commit
eb30a01f2d
14 files changed
+148
-116
No files matched your search
+12
-26
@@ -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
@@ -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(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
+4
-3
@@ -356,13 +356,14 @@ impl<State: AndroidAppState> IrisViewPeer<State> {
|
||||
);
|
||||
}
|
||||
let frame_start = Instant::now();
|
||||
let animating = self.rsc.ui_mut().tick_animations(now);
|
||||
let ui_state = self.state.android_state_mut();
|
||||
let animating = self.rsc.draw_at(&ui_state.root, now);
|
||||
// Ask before renderer update and swapchain acquisition: the latter
|
||||
// may block until near the next presentation boundary.
|
||||
if animating {
|
||||
ctx.view.post_frame_callback(&mut ctx.env);
|
||||
}
|
||||
let ui_state = self.state.android_state_mut();
|
||||
self.rsc.draw(&ui_state.root);
|
||||
let ui_state = self.state.android_state_mut();
|
||||
let Some(renderer) = &mut ui_state.renderer else {
|
||||
return;
|
||||
};
|
||||
|
||||
+3
-3
@@ -255,14 +255,14 @@ impl<State: DesktopAppState> AppState for DesktopApp<State> {
|
||||
match &event {
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
WindowEvent::RedrawRequested => {
|
||||
// Advance animations before drawing and keep requesting frames while active.
|
||||
let frame_start = std::time::Instant::now();
|
||||
let animating = rsc.ui_mut().tick_animations(frame_start);
|
||||
let ui_state = state.desktop_state_mut();
|
||||
let animating = rsc.draw_at(&ui_state.root, frame_start);
|
||||
// Ask before renderer update and swapchain acquisition: the latter
|
||||
// may block until near the next presentation boundary.
|
||||
if animating {
|
||||
ui_state.window.request_redraw();
|
||||
}
|
||||
rsc.draw(&ui_state.root);
|
||||
ui_state.renderer.update(&mut rsc.ui);
|
||||
let mut parts = ui_state.renderer.draw();
|
||||
parts.total = frame_start.elapsed();
|
||||
|
||||
+3
-4
@@ -198,8 +198,8 @@ impl Harness {
|
||||
}
|
||||
|
||||
/// The `Instant` this harness means by `t_ms`. Public because a
|
||||
/// caller driving `ScrollController::tick` or `DragGesture` by hand needs
|
||||
/// to date those calls on the same clock the touch samples use.
|
||||
/// caller driving a `DragGesture` by hand needs to date those calls on the
|
||||
/// same clock the touch samples and frame painter use.
|
||||
pub fn at(&self, t_ms: u64) -> Instant {
|
||||
self.base + Duration::from_millis(t_ms)
|
||||
}
|
||||
@@ -218,8 +218,7 @@ impl Harness {
|
||||
}
|
||||
let now = self.at(t_ms);
|
||||
let at = Instant::now();
|
||||
let animating = self.rsc.ui.tick_animations(now);
|
||||
self.rsc.draw(&self.state.root);
|
||||
let animating = self.rsc.draw_at(&self.state.root, now);
|
||||
// No GPU here, so there is nothing to acquire and nothing to
|
||||
// submit: the frame is all `build`, which is honest rather than
|
||||
// zero-filled (`FrameParts::whole`). `layout`/`redraw`/
|
||||
|
||||
@@ -69,6 +69,23 @@ struct CountedLeaf {
|
||||
draws: Rc<Cell<u32>>,
|
||||
}
|
||||
|
||||
struct FrameRequester {
|
||||
draws: Rc<Cell<u32>>,
|
||||
seen_time: Rc<Cell<Option<std::time::Instant>>>,
|
||||
request: bool,
|
||||
}
|
||||
|
||||
impl Widget for FrameRequester {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
self.draws.set(self.draws.get() + 1);
|
||||
self.seen_time.set(Some(painter.frame_time()));
|
||||
if self.request {
|
||||
painter.request_next_frame();
|
||||
}
|
||||
painter.set_size(Size::ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for CountedLeaf {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
self.draws.set(self.draws.get() + 1);
|
||||
@@ -93,6 +110,32 @@ impl Widget for TracedParent {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_next_frame_request_is_staged_after_the_current_redraw() {
|
||||
let mut rsc = TestRsc { ui: Ui::default() };
|
||||
let draws = Rc::new(Cell::new(0));
|
||||
let seen_time = Rc::new(Cell::new(None));
|
||||
let widget = rsc.ui.widgets.add_strong(FrameRequester {
|
||||
draws: draws.clone(),
|
||||
seen_time: seen_time.clone(),
|
||||
request: true,
|
||||
});
|
||||
let weak = widget.weak();
|
||||
let root = widget.any();
|
||||
let mut render = UiRenderState::new();
|
||||
let frame_time = std::time::Instant::now();
|
||||
|
||||
assert!(render.update_at(&root, &mut rsc, frame_time));
|
||||
assert_eq!(draws.get(), 1, "the request was consumed in the same frame");
|
||||
assert_eq!(seen_time.get(), Some(frame_time));
|
||||
assert!(rsc.ui.widgets.needs_redraw.contains(&weak.id()));
|
||||
|
||||
rsc.ui.widgets.get_mut(&weak).unwrap().request = false;
|
||||
assert!(!render.update_at(&root, &mut rsc, frame_time));
|
||||
assert_eq!(draws.get(), 2);
|
||||
assert!(!rsc.ui.widgets.needs_redraw.contains(&weak.id()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_widget_retains_its_entry_layer_not_its_child_cursor() {
|
||||
let mut rsc = TestRsc { ui: Ui::default() };
|
||||
|
||||
+8
-10
@@ -1344,8 +1344,8 @@ impl FlingCalculator {
|
||||
}
|
||||
|
||||
/// The signed distance covered by `elapsed` into a fling of this
|
||||
/// `velocity` -- what a per-frame ticker (`ScrollController::tick`) calls to
|
||||
/// find how far to have scrolled by now. Clamped to the full
|
||||
/// `velocity` -- what a scrolling widget uses to find how far it should
|
||||
/// have moved by this frame. Clamped to the full
|
||||
/// `distance()` once `elapsed` reaches `duration()`, so a caller need
|
||||
/// not special-case "past the end."
|
||||
pub fn position_at(&self, velocity: f32, elapsed: Duration) -> f32 {
|
||||
@@ -1373,7 +1373,7 @@ impl FlingCalculator {
|
||||
/// go, are the caller's -- a `LazySpan` scrolls its anchor one way and a
|
||||
/// `ScrollArea` moves its `amt` the other, and a `Flinger` that tried to know
|
||||
/// which would have to be told, which is the same thing as not knowing.
|
||||
/// So a caller applies [`Self::tick`]'s delta in its own convention and
|
||||
/// So a caller applies [`Self::advance`]'s delta in its own convention and
|
||||
/// calls [`Self::stop`] when it runs out of content.
|
||||
pub struct Flinger {
|
||||
fling: Option<InFlight>,
|
||||
@@ -1382,7 +1382,7 @@ pub struct Flinger {
|
||||
struct InFlight {
|
||||
calc: FlingCalculator,
|
||||
velocity: f32,
|
||||
/// When the curve begins -- **the first [`Flinger::tick`], not the
|
||||
/// When the curve begins -- **the first [`Flinger::advance`], not the
|
||||
/// release**. Set there so the only clock this reads is the one its
|
||||
/// driver hands it: a caller running frames on an explicit clock
|
||||
/// (`iris::harness`, `bench_client.rs`'s scripted phases) would
|
||||
@@ -1406,15 +1406,13 @@ impl Flinger {
|
||||
}
|
||||
|
||||
/// Start a fling at `velocity_px_per_s`, in whatever pixel space the
|
||||
/// caller applies [`Self::tick`]'s delta in. `density` is physical
|
||||
/// caller applies [`Self::advance`]'s delta in. `density` is physical
|
||||
/// pixels per dp, from the painter -- it does **not** cancel out of
|
||||
/// the spline (see [`FlingCalculator`]), and a hardcoded 1.0 against a
|
||||
/// 2.55-density screen made a one-second coast run for 45.
|
||||
///
|
||||
/// Answers whether a fling actually started, which is a caller's cue
|
||||
/// to register for frames (`UiData::animate`): registering a widget
|
||||
/// that is not animating only asks the next frame to find that out.
|
||||
/// Cancels any fling already in progress.
|
||||
/// Cancels any fling already in progress. A widget which owns one advances
|
||||
/// it during `draw` and asks its painter for the following frame.
|
||||
///
|
||||
/// Compose's two thresholds at a release, and **only** those two. The
|
||||
/// maximum is `ViewConfiguration.getScaledMaximumFlingVelocity()`
|
||||
@@ -1476,7 +1474,7 @@ impl Flinger {
|
||||
/// itself on the spline's own schedule, after which
|
||||
/// [`Self::is_flinging`] is false and the caller stops asking for
|
||||
/// frames.
|
||||
pub fn tick(&mut self, now: Instant) -> f32 {
|
||||
pub fn advance(&mut self, now: Instant) -> f32 {
|
||||
let Some(f) = &mut self.fling else {
|
||||
return 0.0;
|
||||
};
|
||||
|
||||
@@ -616,8 +616,9 @@ fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
||||
let mut c = cursor_at((50.0, y).into());
|
||||
c.buttons.left = button;
|
||||
c.time = base + std::time::Duration::from_millis(at_ms);
|
||||
let frame_time = c.time;
|
||||
render.run_sensors(rsc, state, c, win);
|
||||
render.update(&root, rsc);
|
||||
render.update_at(&root, rsc, frame_time);
|
||||
};
|
||||
sample(
|
||||
&mut render,
|
||||
@@ -645,8 +646,6 @@ fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
||||
"the flick must leave the top area coasting -- the press below is \
|
||||
only dangerous while something is still moving",
|
||||
);
|
||||
let flung_to = rsc.ui.widgets.get(&top_w).unwrap().amt();
|
||||
|
||||
t += 8;
|
||||
sample(
|
||||
&mut render,
|
||||
@@ -672,9 +671,8 @@ fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
||||
"the area actually under the finger should have panned by the 40px \
|
||||
past the slop, got {moved}"
|
||||
);
|
||||
assert_eq!(
|
||||
rsc.ui.widgets.get(&top_w).unwrap().amt(),
|
||||
flung_to,
|
||||
assert!(
|
||||
rsc.ui.widgets.get(&top_w).unwrap().is_scrolling(),
|
||||
"the area the pointer had left must not have seen the press at all -- \
|
||||
a catch would have stopped its fling on the touch-down"
|
||||
);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
use iris_core::util::HashMap;
|
||||
use std::collections::VecDeque;
|
||||
use std::time::Instant;
|
||||
|
||||
pub type RowKey = u64;
|
||||
|
||||
@@ -745,15 +744,13 @@ impl Widget for LazySpan {
|
||||
ChildOrder::Axis(self.dir.axis)
|
||||
}
|
||||
|
||||
/// A lazy span animates exactly one thing, its fling -- and it drives
|
||||
/// its own rather than being handed deltas by a `ScrollArea` around
|
||||
/// it, since which rows exist at all is a function of where it is
|
||||
/// scrolled to and a moved lump would never update them.
|
||||
fn tick(&mut self, now: Instant) -> bool {
|
||||
self.tick_fling(now)
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
// A lazy span drives its own fling because which rows exist is a
|
||||
// function of where it is scrolled to; it cannot be moved as one
|
||||
// retained lump by a `ScrollArea` around it.
|
||||
if self.ctl.advance(painter.frame_time()) {
|
||||
painter.request_next_frame();
|
||||
}
|
||||
let axis = self.dir.axis;
|
||||
let output_len = painter.output_size().axis(axis);
|
||||
self.viewport_len = painter.region().axis(axis).len().to_abs(output_len);
|
||||
@@ -1529,14 +1526,11 @@ mod tests {
|
||||
|
||||
fn fling_frame(
|
||||
rsc: &mut TestRsc,
|
||||
scroll: &WeakWidget<LazySpan>,
|
||||
root: &StrongWidget,
|
||||
render: &mut UiRenderState,
|
||||
now: Instant,
|
||||
) -> bool {
|
||||
let still = rsc.ui.widgets.get_mut(scroll).unwrap().tick(now);
|
||||
render.update(root, rsc);
|
||||
still
|
||||
render.update_at(root, rsc, now)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1552,7 +1546,7 @@ mod tests {
|
||||
let mut still = true;
|
||||
for step in 0..600 {
|
||||
let now = start + std::time::Duration::from_millis(step * 16);
|
||||
still = fling_frame(&mut rsc, &scroll, &root, &mut render, now);
|
||||
still = fling_frame(&mut rsc, &root, &mut render, now);
|
||||
if !still {
|
||||
break;
|
||||
}
|
||||
@@ -1566,24 +1560,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_registered_fling_is_driven_by_tick_animations_and_then_unregisters() {
|
||||
fn a_fling_requests_frames_until_it_settles() {
|
||||
let mut rsc = TestRsc { ui: Ui::default() };
|
||||
let (scroll, root, mut render) = build_flingable_list(&mut rsc);
|
||||
let list_weak = scroll;
|
||||
let before = scroll_position(rsc.ui.widgets.get(&list_weak).unwrap());
|
||||
|
||||
if rsc.ui.widgets.get_mut(&scroll).unwrap().fling(8000.0) {
|
||||
let id = scroll.id();
|
||||
rsc.ui.animate(id);
|
||||
}
|
||||
assert!(rsc.ui.widgets.get_mut(&scroll).unwrap().fling(8000.0));
|
||||
|
||||
let start = Instant::now();
|
||||
let mut steps = 0;
|
||||
let mut animating = true;
|
||||
while animating && steps < 600 {
|
||||
let now = start + std::time::Duration::from_millis(steps * 16);
|
||||
animating = rsc.ui.tick_animations(now);
|
||||
render.update(&root, &mut rsc);
|
||||
animating = render.update_at(&root, &mut rsc, now);
|
||||
steps += 1;
|
||||
}
|
||||
assert!(!animating, "the driver never stopped within 600 frames");
|
||||
@@ -1595,7 +1585,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let now = start + std::time::Duration::from_millis(steps * 16);
|
||||
assert!(!rsc.ui.tick_animations(now));
|
||||
assert!(!render.update_at(&root, &mut rsc, now));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1650,7 +1640,7 @@ mod tests {
|
||||
let start = Instant::now();
|
||||
for step in 0..2000 {
|
||||
let now = start + std::time::Duration::from_millis(step * 16);
|
||||
if !fling_frame(&mut rsc, &scroll, &root, &mut render, now) {
|
||||
if !fling_frame(&mut rsc, &root, &mut render, now) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::prelude::*;
|
||||
use std::time::Instant;
|
||||
|
||||
pub struct ScrollArea {
|
||||
inner: StrongWidget,
|
||||
@@ -19,11 +18,10 @@ impl Scrollable for ScrollArea {
|
||||
}
|
||||
|
||||
impl Widget for ScrollArea {
|
||||
fn tick(&mut self, now: Instant) -> bool {
|
||||
self.tick_fling(now)
|
||||
}
|
||||
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
if self.ctl.advance(painter.frame_time()) {
|
||||
painter.request_next_frame();
|
||||
}
|
||||
let axis = self.ctl.axis();
|
||||
let container_len = painter.px_size().axis(axis);
|
||||
self.container_len = container_len;
|
||||
@@ -87,7 +85,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::layout_tests::TestRsc;
|
||||
use crate::sense::{CursorButton, DRAG_SLOP, PointerRequests};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn area() -> (Fixture, WidgetId) {
|
||||
area_on(Axis::Y)
|
||||
@@ -146,9 +144,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn fling_frame(&mut self, now: Instant) -> bool {
|
||||
let still = self.get().tick(now);
|
||||
self.draw();
|
||||
still
|
||||
self.render.update_at(&self.root, &mut self.rsc, now)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,11 +154,7 @@ mod tests {
|
||||
|
||||
fn drag(f: &mut Fixture, id: WidgetId, sense: CursorSense, pos: Vec2, t: Instant) {
|
||||
let pointer = PointerRequests::default();
|
||||
let flung = f.get().drag(&pointer, id, sense, pos, t);
|
||||
if flung {
|
||||
let id = f.area.id();
|
||||
f.rsc.ui.animate(id);
|
||||
}
|
||||
f.get().drag(&pointer, id, sense, pos, t);
|
||||
f.draw();
|
||||
}
|
||||
|
||||
|
||||
@@ -159,9 +159,9 @@ impl ScrollController {
|
||||
}
|
||||
|
||||
/// Start a fling at `velocity`, in [`Self::scroll`]'s direction
|
||||
/// convention. Answers whether one actually started, which is the
|
||||
/// caller's cue to register the widget for frames (`UiData::animate`).
|
||||
/// Cancels any fling already in progress.
|
||||
/// convention, cancelling any fling already in progress. Answers whether
|
||||
/// one actually started. The owning widget advances it from `draw` and
|
||||
/// requests another frame while it remains active.
|
||||
pub fn fling(&mut self, velocity: f32) -> bool {
|
||||
self.fling.start(velocity, self.density)
|
||||
}
|
||||
@@ -189,8 +189,8 @@ impl ScrollController {
|
||||
self.fling.velocity()
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, now: Instant) -> bool {
|
||||
let delta = self.fling.tick(now);
|
||||
pub(crate) fn advance(&mut self, now: Instant) -> bool {
|
||||
let delta = self.fling.advance(now);
|
||||
self.scroll(delta);
|
||||
self.fling.is_flinging()
|
||||
}
|
||||
@@ -210,9 +210,8 @@ impl ScrollController {
|
||||
/// this takes the gesture over. Android's own `EditText` behaves the
|
||||
/// same way -- a vertical drag scrolls, and only a long press selects.
|
||||
///
|
||||
/// Answers whether this frame *started a fling*, which is the caller's
|
||||
/// cue to register the widget for frames (`UiData::animate`) -- see
|
||||
/// [`Self::fling`].
|
||||
/// Answers whether this frame started a fling, for callers that need to
|
||||
/// distinguish a release which began coasting from one which did not.
|
||||
pub fn drag(
|
||||
&mut self,
|
||||
pointer: &PointerRequests,
|
||||
@@ -306,10 +305,6 @@ pub trait Scrollable {
|
||||
fn set_pinned_to_end(&mut self, pinned: bool) {
|
||||
self.controller_mut().set_pinned_to_end(pinned);
|
||||
}
|
||||
|
||||
fn tick_fling(&mut self, now: Instant) -> bool {
|
||||
self.controller_mut().tick(now)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_senses<Rsc, Tag, W, WL>(w: WL, axis: Axis) -> impl WidgetIdFn<Rsc, W>
|
||||
@@ -325,11 +320,7 @@ where
|
||||
.on(CursorSense::drag(axis), |ctx, rsc: &mut Rsc| {
|
||||
let id = ctx.widget.id();
|
||||
let (sense, pos) = (ctx.data.sense, ctx.data.cursor.pos);
|
||||
let flung = ctx
|
||||
.widget(rsc)
|
||||
ctx.widget(rsc)
|
||||
.drag(ctx.data.pointer, id, sense, pos, ctx.data.cursor.time);
|
||||
if flung {
|
||||
rsc.ui_mut().animate(id);
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -432,10 +432,8 @@ impl SelectionController {
|
||||
SelectionInput::Handled
|
||||
}
|
||||
GestureOutcome::Released(Some(velocity)) => {
|
||||
if let Some(scroll) = self.scroll
|
||||
&& scroll(rsc).fling(velocity)
|
||||
{
|
||||
rsc.ui_mut().animate(scroll.id());
|
||||
if let Some(scroll) = self.scroll {
|
||||
scroll(rsc).fling(velocity);
|
||||
}
|
||||
SelectionInput::Handled
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user