687 lines
21 KiB
Rust
687 lines
21 KiB
Rust
use crate::prelude::*;
|
|
use std::{cell::Cell, rc::Rc, time::Instant};
|
|
|
|
struct SenseRsc {
|
|
ui: Ui,
|
|
events: EventManager<SenseRsc>,
|
|
}
|
|
|
|
impl UiRsc for SenseRsc {
|
|
fn ui(&self) -> &Ui {
|
|
&self.ui
|
|
}
|
|
fn ui_mut(&mut self) -> &mut Ui {
|
|
&mut self.ui
|
|
}
|
|
fn on_draw(&mut self, active: &ActiveData) {
|
|
self.events.draw(active);
|
|
}
|
|
fn on_undraw(&mut self, active: &ActiveData) {
|
|
self.events.undraw(active);
|
|
}
|
|
fn on_remove(&mut self, id: WidgetId) {
|
|
self.events.remove(id);
|
|
}
|
|
}
|
|
|
|
impl HasState for SenseRsc {
|
|
type State = ();
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct RenderProbe;
|
|
|
|
impl Event for RenderProbe {}
|
|
|
|
#[test]
|
|
fn an_event_can_read_the_completed_frame_while_mutating_ui_data() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
rsc.ui.resize((100.0, 100.0));
|
|
let widget = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE));
|
|
let weak = widget.weak();
|
|
let saw_active = Rc::new(Cell::new(false));
|
|
rsc.register_event(weak, RenderProbe, {
|
|
let saw_active = saw_active.clone();
|
|
move |_, rsc| {
|
|
let render_handle = rsc.ui().render_state();
|
|
let render_state = render_handle.get();
|
|
saw_active.set(render_state.active.contains_key(&weak.id()));
|
|
rsc.widgets_mut().set_label(weak, "changed".to_string());
|
|
assert!(render_state.active.contains_key(&weak.id()));
|
|
}
|
|
});
|
|
rsc.draw(&widget.any());
|
|
|
|
rsc.run_event::<RenderProbe>(weak, (), &mut ());
|
|
|
|
assert!(saw_active.get());
|
|
assert_eq!(rsc.widgets().label(weak), "changed");
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic(expected = "render state cannot be mutated while it is being read")]
|
|
fn an_event_cannot_start_a_draw() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
let widget = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE));
|
|
let weak = widget.weak();
|
|
rsc.register_event(weak, RenderProbe, |_, rsc| rsc.draw(None));
|
|
|
|
rsc.run_event::<RenderProbe>(weak, (), &mut ());
|
|
}
|
|
|
|
impl HasEvents for SenseRsc {
|
|
fn events(&self) -> &EventManager<Self> {
|
|
&self.events
|
|
}
|
|
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
|
&mut self.events
|
|
}
|
|
}
|
|
|
|
fn cursor_at(pos: Vec2) -> CursorState {
|
|
CursorState {
|
|
pos,
|
|
exists: true,
|
|
buttons: Default::default(),
|
|
scroll_delta: Vec2::ZERO,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_button_over_a_list_scrolls_the_list_and_still_clicks() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
|
|
// A button nested over a scrolling surface must participate in both gestures.
|
|
let list = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE));
|
|
let list_weak = list.weak();
|
|
let button = rsc.ui.widgets.add_strong(Rect::new(PaintId::RED));
|
|
let button_weak = button.weak();
|
|
|
|
let scrolled = Rc::new(Cell::new(false));
|
|
let clicked = Rc::new(Cell::new(false));
|
|
{
|
|
let scrolled = scrolled.clone();
|
|
rsc.register_event(
|
|
list_weak,
|
|
CursorSense::Scroll(Axis::Y),
|
|
move |_ctx, _rsc| {
|
|
scrolled.set(true);
|
|
},
|
|
);
|
|
}
|
|
{
|
|
let clicked = clicked.clone();
|
|
rsc.register_event(button_weak, CursorSense::click(), move |_ctx, _rsc| {
|
|
clicked.set(true);
|
|
});
|
|
}
|
|
|
|
let root = rsc
|
|
.ui
|
|
.widgets
|
|
.add_strong(Stack {
|
|
children: vec![list.any(), button.any()],
|
|
size: StackSize::default(),
|
|
})
|
|
.any();
|
|
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&root, &mut rsc);
|
|
|
|
let mut state = ();
|
|
let mut scroll_cursor = cursor_at((50.0, 50.0).into());
|
|
scroll_cursor.scroll_delta = (0.0, 10.0).into();
|
|
render.run_sensors(&mut rsc, &mut state, scroll_cursor, (100.0, 100.0).into());
|
|
render.update(&root, &mut rsc);
|
|
|
|
assert!(
|
|
scrolled.get(),
|
|
"a scroll over the button must still reach the list underneath it"
|
|
);
|
|
assert!(
|
|
!clicked.get(),
|
|
"a scroll is not a click; the button must not have fired"
|
|
);
|
|
|
|
let mut click_cursor = cursor_at((50.0, 50.0).into());
|
|
click_cursor.buttons.left = ActivationState::Start;
|
|
render.run_sensors(&mut rsc, &mut state, click_cursor, (100.0, 100.0).into());
|
|
render.update(&root, &mut rsc);
|
|
|
|
assert!(
|
|
clicked.get(),
|
|
"the button on top must still receive an actual click"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_release_outside_every_hit_region_still_reaches_the_captured_widget() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
|
|
let draggable = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE)).any();
|
|
let draggable_weak = draggable.weak();
|
|
|
|
let dropped = Rc::new(Cell::new(false));
|
|
{
|
|
let dropped = dropped.clone();
|
|
rsc.register_event(
|
|
draggable_weak,
|
|
CursorSense::click_or_drag() | CursorSense::unclick() | CursorSense::Drop,
|
|
move |ctx, rsc| match ctx.data.sense {
|
|
CursorSense::PressStart(_) | CursorSense::Pressing(_) => {
|
|
ctx.data.pointer.capture(draggable_weak.id());
|
|
let _ = rsc;
|
|
}
|
|
CursorSense::Drop => dropped.set(true),
|
|
_ => {}
|
|
},
|
|
);
|
|
}
|
|
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&draggable, &mut rsc);
|
|
|
|
let mut state = ();
|
|
let mut press = cursor_at((5.0, 5.0).into());
|
|
press.buttons.left = ActivationState::Start;
|
|
render.run_sensors(&mut rsc, &mut state, press, (100.0, 100.0).into());
|
|
render.update(&draggable, &mut rsc);
|
|
assert_eq!(
|
|
pointer_input(&mut rsc).holder(),
|
|
Some(draggable.id()),
|
|
"the press should have taken capture"
|
|
);
|
|
|
|
let mut release = cursor_at((95.0, 95.0).into());
|
|
release.buttons.left = ActivationState::End;
|
|
render.run_sensors(&mut rsc, &mut state, release, (100.0, 100.0).into());
|
|
render.update(&draggable, &mut rsc);
|
|
|
|
assert!(
|
|
dropped.get(),
|
|
"a release outside every widget's hit region must still reach \
|
|
the widget holding pointer capture"
|
|
);
|
|
assert_eq!(
|
|
pointer_input(&mut rsc).holder(),
|
|
None,
|
|
"Drop must release the capture"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn capturing_one_widget_starves_every_other_widget_of_events() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
|
|
let a = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE));
|
|
let a_weak = a.weak();
|
|
let b = rsc.ui.widgets.add_strong(Rect::new(PaintId::RED));
|
|
let b_weak = b.weak();
|
|
|
|
let b_hovered = Rc::new(Cell::new(false));
|
|
{
|
|
let b_hovered = b_hovered.clone();
|
|
rsc.register_event(b_weak, CursorSense::Hovering, move |_ctx, _rsc| {
|
|
b_hovered.set(true);
|
|
});
|
|
}
|
|
|
|
let root = rsc
|
|
.ui
|
|
.widgets
|
|
.add_strong(Stack {
|
|
children: vec![a.any(), b.any()],
|
|
size: StackSize::default(),
|
|
})
|
|
.any();
|
|
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&root, &mut rsc);
|
|
pointer_input(&mut rsc).set_holder(Some(a_weak.id()));
|
|
|
|
let mut state = ();
|
|
let cursor = cursor_at((50.0, 50.0).into());
|
|
render.run_sensors(&mut rsc, &mut state, cursor, (100.0, 100.0).into());
|
|
render.update(&root, &mut rsc);
|
|
|
|
assert!(
|
|
!b_hovered.get(),
|
|
"while a's drag holds capture, b must see no hover at all"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_finger_drag_over_a_scroll_area_pans_it() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
|
|
let scroll_strong = rect(PaintId::WHITE)
|
|
.height(LayoutLen::abs(1000.0))
|
|
.scrollable(Axis::Y, Pin::Start)
|
|
.add_strong(&mut rsc);
|
|
let scroll = scroll_strong.weak();
|
|
let root = scroll_strong.any();
|
|
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&root, &mut rsc);
|
|
rsc.ui.widgets.get_mut(&scroll).unwrap().scroll(0.0);
|
|
render.update(&root, &mut rsc);
|
|
assert_eq!(rsc.ui.widgets.get(&scroll).unwrap().amt(), 0.0);
|
|
|
|
let mut state = ();
|
|
let mut down = cursor_at((50.0, 80.0).into());
|
|
down.buttons.left = ActivationState::Start;
|
|
render.run_sensors(&mut rsc, &mut state, down, (100.0, 100.0).into());
|
|
render.update(&root, &mut rsc);
|
|
assert_eq!(
|
|
rsc.ui.widgets.get(&scroll).unwrap().amt(),
|
|
0.0,
|
|
"the touch-down alone must not move anything"
|
|
);
|
|
|
|
let mut nudge = cursor_at((50.0, 80.0 - (DRAG_SLOP - 1.0)).into());
|
|
nudge.buttons.left = ActivationState::On;
|
|
render.run_sensors(&mut rsc, &mut state, nudge, (100.0, 100.0).into());
|
|
render.update(&root, &mut rsc);
|
|
assert_eq!(
|
|
rsc.ui.widgets.get(&scroll).unwrap().amt(),
|
|
0.0,
|
|
"a press inside DRAG_SLOP must not scroll"
|
|
);
|
|
|
|
let mut drag = cursor_at((50.0, 80.0 - (DRAG_SLOP + 40.0)).into());
|
|
drag.buttons.left = ActivationState::On;
|
|
render.run_sensors(&mut rsc, &mut state, drag, (100.0, 100.0).into());
|
|
render.update(&root, &mut rsc);
|
|
let after = rsc.ui.widgets.get(&scroll).unwrap().amt();
|
|
assert!(
|
|
(after - 40.0).abs() < 0.01,
|
|
"expected the 40px past the slop to pan it, got {after}"
|
|
);
|
|
|
|
assert_eq!(pointer_input(&mut rsc).holder(), Some(scroll.id()));
|
|
}
|
|
|
|
#[test]
|
|
fn the_first_events_batched_samples_are_dated_apart() {
|
|
const MS: i64 = 1_000_000;
|
|
let now = Instant::now();
|
|
let clock = DeviceClock::anchored(now, 12 * MS, 0);
|
|
|
|
assert_eq!(
|
|
clock.at(12 * MS),
|
|
now,
|
|
"the event's own sample is the one that arrived now"
|
|
);
|
|
let batch = [clock.at(0), clock.at(4 * MS), clock.at(8 * MS)];
|
|
assert!(
|
|
batch[0] < batch[1] && batch[1] < batch[2] && batch[2] < now,
|
|
"the batch must keep the 4ms between its samples, got {:?}",
|
|
batch
|
|
.iter()
|
|
.map(|t| now.duration_since(*t))
|
|
.collect::<Vec<_>>()
|
|
);
|
|
assert_eq!(clock.ms_since_anchor(8 * MS), 8);
|
|
}
|
|
|
|
#[test]
|
|
fn the_clock_orders_samples_across_events() {
|
|
const MS: i64 = 1_000_000;
|
|
let mut clock = DeviceClock::anchored(Instant::now(), 12 * MS, 0);
|
|
let first = clock.sample(12 * MS);
|
|
let second = clock.sample(28 * MS);
|
|
assert!(second > first);
|
|
assert_eq!(
|
|
second.duration_since(first),
|
|
std::time::Duration::from_millis(16)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_scroll_area_that_captured_the_pointer_learns_its_gesture_ended() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
let scroll_strong = rect(PaintId::WHITE)
|
|
.height(LayoutLen::abs(1000.0))
|
|
.scrollable(Axis::Y, Pin::Start)
|
|
.add_strong(&mut rsc);
|
|
let scroll = scroll_strong.weak();
|
|
let root = scroll_strong.any();
|
|
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&root, &mut rsc);
|
|
rsc.ui.widgets.get_mut(&scroll).unwrap().scroll(0.0);
|
|
render.update(&root, &mut rsc);
|
|
|
|
let mut state = ();
|
|
let win = Vec2::new(100.0, 100.0);
|
|
let mut send = |render: &mut UiRenderState, rsc: &mut SenseRsc, y: f32, button| {
|
|
let mut c = cursor_at((50.0, y).into());
|
|
c.buttons.left = button;
|
|
render.run_sensors(rsc, &mut state, c, win);
|
|
render.update(&root, rsc);
|
|
};
|
|
|
|
send(&mut render, &mut rsc, 80.0, ActivationState::Start);
|
|
send(
|
|
&mut render,
|
|
&mut rsc,
|
|
80.0 - (DRAG_SLOP + 40.0),
|
|
ActivationState::On,
|
|
);
|
|
let after_first = rsc.ui.widgets.get(&scroll).unwrap().amt();
|
|
assert!((after_first - 40.0).abs() < 0.01, "amt={after_first}");
|
|
send(&mut render, &mut rsc, 400.0, ActivationState::End);
|
|
assert_eq!(
|
|
pointer_input(&mut rsc).holder(),
|
|
None,
|
|
"the release must give the pointer back"
|
|
);
|
|
|
|
send(&mut render, &mut rsc, 80.0, ActivationState::Start);
|
|
let after_second = rsc.ui.widgets.get(&scroll).unwrap().amt();
|
|
assert!(
|
|
(after_second - after_first).abs() < 0.01,
|
|
"a fresh touch-down moved the content by {} -- the previous \
|
|
gesture was never closed",
|
|
after_second - after_first,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn taking_the_pointer_cancels_everyone_else_tracking_the_press() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
|
|
let capturer = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE));
|
|
let capturer_weak = capturer.weak();
|
|
let bystander = rsc.ui.widgets.add_strong(Stack {
|
|
children: vec![capturer.any()],
|
|
size: StackSize::default(),
|
|
});
|
|
let bystander_weak = bystander.weak();
|
|
|
|
let capturer_saw = Rc::new(Cell::new(0u32));
|
|
{
|
|
let capturer_saw = capturer_saw.clone();
|
|
rsc.register_event(
|
|
capturer_weak,
|
|
CursorSense::drag(Axis::X),
|
|
move |ctx, _rsc| {
|
|
capturer_saw.set(capturer_saw.get() + 1);
|
|
if matches!(ctx.data.sense, CursorSense::Pressing(_)) {
|
|
ctx.data.pointer.capture(capturer_weak.id());
|
|
}
|
|
},
|
|
);
|
|
}
|
|
let cancelled = Rc::new(Cell::new(0u32));
|
|
let ended = Rc::new(Cell::new(0u32));
|
|
{
|
|
let (cancelled, ended) = (cancelled.clone(), ended.clone());
|
|
rsc.register_event(
|
|
bystander_weak,
|
|
CursorSense::drag(Axis::Y),
|
|
move |ctx, _rsc| match ctx.data.sense {
|
|
CursorSense::Cancel => cancelled.set(cancelled.get() + 1),
|
|
CursorSense::PressEnd(_) | CursorSense::Drop => ended.set(ended.get() + 1),
|
|
_ => {}
|
|
},
|
|
);
|
|
}
|
|
|
|
let root = bystander.any();
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&root, &mut rsc);
|
|
|
|
let mut state = ();
|
|
let win = Vec2::new(100.0, 100.0);
|
|
let mut down = cursor_at((50.0, 50.0).into());
|
|
down.buttons.left = ActivationState::Start;
|
|
render.run_sensors(&mut rsc, &mut state, down, win);
|
|
render.update(&root, &mut rsc);
|
|
assert_eq!(cancelled.get(), 0, "nothing has captured yet");
|
|
|
|
let mut moved = cursor_at((80.0, 50.0).into());
|
|
moved.buttons.left = ActivationState::On;
|
|
render.run_sensors(&mut rsc, &mut state, moved, win);
|
|
render.update(&root, &mut rsc);
|
|
assert!(capturer_saw.get() > 0, "the capturer never saw the press");
|
|
assert_eq!(
|
|
pointer_input(&mut rsc).holder(),
|
|
Some(capturer_weak.id()),
|
|
"the capture should have been taken on this frame"
|
|
);
|
|
assert_eq!(
|
|
cancelled.get(),
|
|
1,
|
|
"the widget that lost the gesture must be told exactly once"
|
|
);
|
|
|
|
let mut more = cursor_at((50.0, 10.0).into());
|
|
more.buttons.left = ActivationState::On;
|
|
render.run_sensors(&mut rsc, &mut state, more, win);
|
|
render.update(&root, &mut rsc);
|
|
let mut up = cursor_at((50.0, 10.0).into());
|
|
up.buttons.left = ActivationState::End;
|
|
render.run_sensors(&mut rsc, &mut state, up, win);
|
|
render.update(&root, &mut rsc);
|
|
assert_eq!(cancelled.get(), 1, "cancelled more than once");
|
|
assert_eq!(
|
|
ended.get(),
|
|
0,
|
|
"a cancelled widget must not also be told the gesture ended \
|
|
normally -- acting on that is the tap it never made"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_drag_pans_whichever_nested_scroll_area_owns_its_axis() {
|
|
for (name, to, pans, still) in [
|
|
("vertical", Vec2::new(50.0, 80.0 - (DRAG_SLOP + 40.0)), 0, 1),
|
|
(
|
|
"horizontal",
|
|
Vec2::new(50.0 - (DRAG_SLOP + 40.0), 80.0),
|
|
1,
|
|
0,
|
|
),
|
|
] {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
let seen = Rc::new(Cell::new(None));
|
|
let record = seen.clone();
|
|
let outer_strong = rect(PaintId::WHITE)
|
|
.width(LayoutLen::abs(1000.0))
|
|
.height(LayoutLen::abs(1000.0))
|
|
.scrollable(Axis::X, Pin::Start)
|
|
.with_id(move |_rsc, id| {
|
|
record.set(Some(id));
|
|
id
|
|
})
|
|
.layer_offset(1)
|
|
.scrollable(Axis::Y, Pin::Start)
|
|
.add_strong(&mut rsc);
|
|
let inner = seen.get().unwrap();
|
|
let outer = outer_strong.weak();
|
|
let root = outer_strong.any();
|
|
let areas = [outer, inner];
|
|
|
|
let mut render = UiRenderState::new();
|
|
render.resize((100.0, 100.0));
|
|
render.update(&root, &mut rsc);
|
|
for a in areas {
|
|
rsc.ui.widgets.get_mut(&a).unwrap().scroll(0.0);
|
|
}
|
|
render.update(&root, &mut rsc);
|
|
|
|
let mut state = ();
|
|
let win = Vec2::new(100.0, 100.0);
|
|
let mut down = cursor_at((50.0, 80.0).into());
|
|
down.buttons.left = ActivationState::Start;
|
|
render.run_sensors(&mut rsc, &mut state, down, win);
|
|
render.update(&root, &mut rsc);
|
|
let mut drag = cursor_at(to);
|
|
drag.buttons.left = ActivationState::On;
|
|
render.run_sensors(&mut rsc, &mut state, drag, win);
|
|
render.update(&root, &mut rsc);
|
|
|
|
let moved = rsc.ui.widgets.get(&areas[pans]).unwrap().amt();
|
|
let unmoved = rsc.ui.widgets.get(&areas[still]).unwrap().amt();
|
|
assert!(
|
|
(moved - 40.0).abs() < 0.01,
|
|
"a {name} drag should have panned the {name} area by the 40px \
|
|
past the slop, got {moved}"
|
|
);
|
|
assert_eq!(
|
|
unmoved, 0.0,
|
|
"a {name} drag must not move the area that owns the other axis"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_press_does_not_reach_a_widget_the_pointer_has_just_left() {
|
|
let mut rsc = SenseRsc {
|
|
ui: Ui::default(),
|
|
events: EventManager::default(),
|
|
};
|
|
|
|
let seen: [Rc<Cell<Option<WeakWidget<ScrollArea>>>>; 2] = Default::default();
|
|
let half = |slot: &Rc<Cell<Option<WeakWidget<ScrollArea>>>>| {
|
|
let record = slot.clone();
|
|
rect(PaintId::WHITE)
|
|
.height(LayoutLen::abs(1000.0))
|
|
.scrollable(Axis::Y, Pin::Start)
|
|
.with_id(move |_rsc, id| {
|
|
record.set(Some(id));
|
|
id
|
|
})
|
|
.height(LayoutLen::rel(0.5))
|
|
};
|
|
let root = (half(&seen[0]), half(&seen[1]))
|
|
.span(Dir::DOWN)
|
|
.add_strong(&mut rsc)
|
|
.any();
|
|
let (top_w, bottom_w) = (seen[0].get().unwrap(), seen[1].get().unwrap());
|
|
|
|
let win: Vec2 = (100.0, 200.0).into();
|
|
let mut render = UiRenderState::new();
|
|
render.resize((win.x, win.y));
|
|
render.update(&root, &mut rsc);
|
|
for w in [&top_w, &bottom_w] {
|
|
rsc.ui.widgets.get_mut(w).unwrap().scroll(0.0);
|
|
}
|
|
render.update(&root, &mut rsc);
|
|
|
|
let mut state = ();
|
|
let base = Instant::now();
|
|
let mut t = 0;
|
|
let sample = |render: &mut UiRenderState,
|
|
rsc: &mut SenseRsc,
|
|
state: &mut (),
|
|
y: f32,
|
|
button: ActivationState,
|
|
at_ms: u64| {
|
|
let mut c = cursor_at((50.0, y).into());
|
|
c.buttons.left = button;
|
|
c.time = base + std::time::Duration::from_millis(at_ms);
|
|
render.run_sensors(rsc, state, c, win);
|
|
render.update(&root, rsc);
|
|
};
|
|
sample(
|
|
&mut render,
|
|
&mut rsc,
|
|
&mut state,
|
|
50.0,
|
|
ActivationState::Start,
|
|
t,
|
|
);
|
|
for y in [44.0, 32.0, 14.0] {
|
|
t += 8;
|
|
sample(&mut render, &mut rsc, &mut state, y, ActivationState::On, t);
|
|
}
|
|
t += 8;
|
|
sample(
|
|
&mut render,
|
|
&mut rsc,
|
|
&mut state,
|
|
14.0,
|
|
ActivationState::End,
|
|
t,
|
|
);
|
|
assert!(
|
|
rsc.ui.widgets.get(&top_w).unwrap().is_scrolling(),
|
|
"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,
|
|
&mut rsc,
|
|
&mut state,
|
|
150.0,
|
|
ActivationState::Start,
|
|
t,
|
|
);
|
|
t += 8;
|
|
sample(
|
|
&mut render,
|
|
&mut rsc,
|
|
&mut state,
|
|
150.0 - (DRAG_SLOP + 40.0),
|
|
ActivationState::On,
|
|
t,
|
|
);
|
|
|
|
let moved = rsc.ui.widgets.get(&bottom_w).unwrap().amt();
|
|
assert!(
|
|
(moved - 40.0).abs() < 0.01,
|
|
"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,
|
|
"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"
|
|
);
|
|
assert_eq!(
|
|
pointer_input(&mut rsc).holder(),
|
|
Some(bottom_w.id()),
|
|
"the gesture belongs to the widget under the finger",
|
|
);
|
|
}
|