Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db65a413c7 | ||
|
|
0a5eb2d984 |
No files matched your search
@@ -79,7 +79,6 @@ type EventData<Rsc, E> = (E, Rc<dyn for<'a> EventFn<Rsc, <E as Event>::Data<'a>>
|
||||
pub struct TypeEventManager<Rsc: HasEvents, E: Event> {
|
||||
// TODO: reduce visiblity!!
|
||||
pub active: HashMap<LayerId, HashMap<WidgetId, E::State>>,
|
||||
pub global: E::Global,
|
||||
map: HashMap<WidgetId, Vec<EventData<Rsc, E>>>,
|
||||
}
|
||||
|
||||
@@ -108,7 +107,6 @@ impl<Rsc: HasEvents, E: Event> Default for TypeEventManager<Rsc, E> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
active: Default::default(),
|
||||
global: Default::default(),
|
||||
map: Default::default(),
|
||||
}
|
||||
}
|
||||
@@ -140,13 +138,11 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
||||
pub fn run_fn<'a>(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) -> bool + 'a {
|
||||
) -> impl for<'b> FnOnce(EventCtx<'_, Rsc, E::Data<'b>>, &mut Rsc) + 'a {
|
||||
let fs = self.map.get(&id.id()).cloned().unwrap_or_default();
|
||||
move |ctx, rsc| {
|
||||
let mut consumed = false;
|
||||
for (e, f) in fs {
|
||||
if let Some(data) = e.should_run(&ctx.data) {
|
||||
consumed |= e.consumes(&data);
|
||||
f(
|
||||
EventCtx {
|
||||
state: ctx.state,
|
||||
@@ -156,7 +152,6 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
||||
)
|
||||
}
|
||||
}
|
||||
consumed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,19 +9,10 @@ pub use rsc::*;
|
||||
pub trait Event: Sized + 'static + Clone {
|
||||
type Data<'a>: Clone = ();
|
||||
type State: Default = ();
|
||||
/// State the whole event type keeps, rather than one copy per widget.
|
||||
type Global: Default = ();
|
||||
#[allow(unused_variables)]
|
||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||
Some(data.clone())
|
||||
}
|
||||
|
||||
/// Whether having run on this data uses up whatever triggered it, so
|
||||
/// nothing further should see it.
|
||||
#[allow(unused_variables)]
|
||||
fn consumes(&self, data: &Self::Data<'_>) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLike {
|
||||
|
||||
@@ -21,13 +21,12 @@ pub trait HasEvents: Sized + UiRsc + HasState {
|
||||
}
|
||||
|
||||
pub trait RunEvents: HasEvents {
|
||||
/// Whether anything that ran used up what triggered it.
|
||||
fn run_event<E: EventLike>(
|
||||
&mut self,
|
||||
id: impl IdLike,
|
||||
data: <E::Event as Event>::Data<'_>,
|
||||
state: &mut Self::State,
|
||||
) -> bool {
|
||||
) {
|
||||
let f = self.events_mut().get_type::<E>().run_fn(id);
|
||||
f(EventCtx { state, data }, self)
|
||||
}
|
||||
|
||||
+5
-1
@@ -1,6 +1,10 @@
|
||||
use crate::prelude::*;
|
||||
use arboard::Clipboard;
|
||||
use std::{marker::PhantomData, sync::Arc, time::Instant};
|
||||
use std::{
|
||||
marker::{PhantomData, Sized},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use winit::{
|
||||
event::{Ime, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||
|
||||
+33
-93
@@ -4,14 +4,14 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum CursorButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum CursorSense {
|
||||
PressStart(CursorButton),
|
||||
Pressing(CursorButton),
|
||||
@@ -27,10 +27,7 @@ pub struct CursorSenses(Vec<CursorSense>);
|
||||
|
||||
impl Event for CursorSenses {
|
||||
type Data<'a> = CursorData<'a>;
|
||||
/// Who the cursor was inside on the last input, which is what says whose
|
||||
/// hover has ended -- including a widget a higher layer has since covered,
|
||||
/// which this walk never reaches.
|
||||
type Global = Vec<WidgetId>;
|
||||
type State = SensorState;
|
||||
fn should_run<'a>(&self, data: &Self::Data<'a>) -> Option<Self::Data<'a>> {
|
||||
if let Some(sense) = should_run(self, &data.cursor, data.hover) {
|
||||
let mut data = data.clone();
|
||||
@@ -40,12 +37,6 @@ impl Event for CursorSenses {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A press or a scroll is used up by whatever answered it, so it stops
|
||||
/// there. Hovering is not: a cursor resting somewhere goes on resting.
|
||||
fn consumes(&self, data: &Self::Data<'_>) -> bool {
|
||||
!data.sense.position_only()
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorSense {
|
||||
@@ -61,12 +52,6 @@ impl CursorSense {
|
||||
pub fn is_dragging(&self) -> bool {
|
||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||
}
|
||||
|
||||
/// False if the sense is a button or a scroll, true if it is only about
|
||||
/// where the cursor is.
|
||||
fn position_only(&self) -> bool {
|
||||
matches!(self, Self::HoverStart | Self::Hovering | Self::HoverEnd)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
@@ -111,12 +96,6 @@ impl CursorButtons {
|
||||
}
|
||||
|
||||
impl CursorState {
|
||||
/// True if the cursor is only reporting where it is: no button and no
|
||||
/// scroll this frame.
|
||||
pub fn position_only(&self) -> bool {
|
||||
self.scroll_delta == Vec2::ZERO && self.buttons.iter().all(|(_, state)| state.is_off())
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) {
|
||||
self.buttons.end_frame();
|
||||
self.scroll_delta = Vec2::ZERO;
|
||||
@@ -144,6 +123,11 @@ pub struct Sensor<Ctx: HasEvents, Data> {
|
||||
|
||||
pub type SenseShape = UiRegion;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SensorState {
|
||||
pub hover: ActivationState,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CursorData<'a> {
|
||||
/// where this widget was hit
|
||||
@@ -179,92 +163,48 @@ impl SensorUi for UiRenderState {
|
||||
// this would probably be done through a generic parameter that adds yet another rsc /
|
||||
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
||||
// update it there?
|
||||
let active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||
let was = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().global);
|
||||
let position_only = cursor.position_only();
|
||||
let mut now: Vec<WidgetId> = Vec::new();
|
||||
let region_of = |id| Some(self.active.get(&id)?.region.to_px(window_size));
|
||||
|
||||
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||
for layer in self.layers.indices().rev() {
|
||||
let mut consumed = false;
|
||||
for id in active.get(&layer).into_flat_iter().map(|(id, _)| *id) {
|
||||
let Some(region) = region_of(id) else {
|
||||
continue;
|
||||
};
|
||||
if !cursor.exists || !region.contains(cursor.pos) {
|
||||
let mut sensed = false;
|
||||
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
|
||||
let shape = self.active.get(id).unwrap().region;
|
||||
let region = shape.to_px(window_size);
|
||||
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||
sensor.hover.update(in_shape);
|
||||
if sensor.hover == ActivationState::Off {
|
||||
continue;
|
||||
}
|
||||
now.push(id);
|
||||
let hover = match was.contains(&id) {
|
||||
true => ActivationState::On,
|
||||
false => ActivationState::Start,
|
||||
sensed = true;
|
||||
|
||||
let cursor = cursor.clone();
|
||||
|
||||
let data = CursorData {
|
||||
pos: cursor.pos - region.top_left,
|
||||
size: region.bot_right - region.top_left,
|
||||
scroll_delta: cursor.scroll_delta,
|
||||
hover: sensor.hover,
|
||||
cursor,
|
||||
// this does not have any meaning;
|
||||
// might wanna set up Event to have a prepare stage
|
||||
sense: CursorSense::Hovering,
|
||||
render: self,
|
||||
};
|
||||
// A press or a scroll stops where something answered it, so a
|
||||
// button over a list does not swallow the list's scrolling.
|
||||
consumed |= deliver(self, rsc, state, id, hover, &cursor, region);
|
||||
// A cursor doing neither stops at whatever it is over, so
|
||||
// hovering does not reach through.
|
||||
consumed |= position_only;
|
||||
rsc.run_event::<CursorSense>(*id, data, state);
|
||||
}
|
||||
// Applied after the layer, never during it: senses on one layer do
|
||||
// not block each other.
|
||||
if consumed {
|
||||
if sensed {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Whatever the cursor was inside and is not now, whether it left or a
|
||||
// layer above took the input before the walk reached it. A widget that
|
||||
// stopped being drawn has no region to report and is simply dropped.
|
||||
for id in was {
|
||||
if !now.contains(&id)
|
||||
&& let Some(region) = region_of(id)
|
||||
{
|
||||
deliver(self, rsc, state, id, ActivationState::End, &cursor, region);
|
||||
}
|
||||
}
|
||||
|
||||
let senses = rsc.events_mut().get_type::<CursorSense>();
|
||||
senses.active = active;
|
||||
senses.global = now;
|
||||
rsc.events_mut().get_type::<CursorSense>().active = active;
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs one widget's cursor senses, and says whether they used up the input.
|
||||
fn deliver<Rsc: HasEvents>(
|
||||
render: &UiRenderState,
|
||||
rsc: &mut Rsc,
|
||||
state: &mut Rsc::State,
|
||||
id: WidgetId,
|
||||
hover: ActivationState,
|
||||
cursor: &CursorState,
|
||||
region: PixelRegion,
|
||||
) -> bool {
|
||||
let data = CursorData {
|
||||
pos: cursor.pos - region.top_left,
|
||||
size: region.bot_right - region.top_left,
|
||||
scroll_delta: cursor.scroll_delta,
|
||||
hover,
|
||||
cursor: cursor.clone(),
|
||||
// this does not have any meaning;
|
||||
// might wanna set up Event to have a prepare stage
|
||||
sense: CursorSense::Hovering,
|
||||
render,
|
||||
};
|
||||
rsc.run_event::<CursorSense>(id, data, state)
|
||||
}
|
||||
|
||||
pub fn should_run(
|
||||
senses: &CursorSenses,
|
||||
cursor: &CursorState,
|
||||
hover: ActivationState,
|
||||
) -> Option<CursorSense> {
|
||||
for sense in senses.iter() {
|
||||
// A widget the cursor is no longer inside senses only its position:
|
||||
// the press that ended its hover landed on something else.
|
||||
if !hover.is_on() && !sense.position_only() {
|
||||
continue;
|
||||
}
|
||||
if match sense {
|
||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||
|
||||
@@ -4,7 +4,7 @@ mod max_size;
|
||||
mod offset;
|
||||
mod pad;
|
||||
mod scroll;
|
||||
mod set_size;
|
||||
mod sized;
|
||||
mod span;
|
||||
mod stack;
|
||||
|
||||
@@ -14,6 +14,6 @@ pub use max_size::*;
|
||||
pub use offset::*;
|
||||
pub use pad::*;
|
||||
pub use scroll::*;
|
||||
pub use set_size::*;
|
||||
pub use sized::*;
|
||||
pub use span::*;
|
||||
pub use stack::*;
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
pub struct SetSize {
|
||||
pub struct Sized {
|
||||
pub inner: StrongWidget,
|
||||
pub x: Option<Len>,
|
||||
pub y: Option<Len>,
|
||||
}
|
||||
|
||||
impl SetSize {
|
||||
impl Sized {
|
||||
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
||||
if let Some(x) = self.x {
|
||||
ctx.outer.x.select_len(x.apply_rest());
|
||||
@@ -17,7 +17,7 @@ impl SetSize {
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for SetSize {
|
||||
impl Widget for Sized {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
painter.widget(&self.inner);
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
use std::marker::Unsize;
|
||||
use std::marker::{Sized, Unsize};
|
||||
|
||||
pub struct WidgetPtr {
|
||||
pub inner: Option<StrongWidget>,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
use std::marker::{PhantomData, Sized};
|
||||
|
||||
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> {
|
||||
pub content: String,
|
||||
|
||||
@@ -31,9 +31,9 @@ widget_trait! {
|
||||
}
|
||||
}
|
||||
|
||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, SetSize> {
|
||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, Sized> {
|
||||
let size = size.into();
|
||||
move |state| SetSize {
|
||||
move |state| Sized {
|
||||
inner: self.add_strong(state),
|
||||
x: Some(size.x),
|
||||
y: Some(size.y),
|
||||
@@ -58,18 +58,18 @@ widget_trait! {
|
||||
}
|
||||
}
|
||||
|
||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
||||
let len = len.into();
|
||||
move |state| SetSize {
|
||||
move |state| Sized {
|
||||
inner: self.add_strong(state),
|
||||
x: Some(len),
|
||||
y: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
||||
let len = len.into();
|
||||
move |state| SetSize {
|
||||
move |state| Sized {
|
||||
inner: self.add_strong(state),
|
||||
x: None,
|
||||
y: Some(len),
|
||||
|
||||
@@ -1,367 +0,0 @@
|
||||
//! Input across layers: what stops at a layer, what passes through it, and
|
||||
//! where hovering stops. These drive `run_sensors` directly, which needs no
|
||||
//! GPU and no window.
|
||||
|
||||
use iris::prelude::*;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
struct SenseRsc {
|
||||
ui: UiData,
|
||||
events: EventManager<SenseRsc>,
|
||||
}
|
||||
|
||||
impl UiRsc for SenseRsc {
|
||||
fn ui(&self) -> &UiData {
|
||||
&self.ui
|
||||
}
|
||||
fn ui_mut(&mut self) -> &mut UiData {
|
||||
&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 = ();
|
||||
}
|
||||
|
||||
impl HasEvents for SenseRsc {
|
||||
fn events(&self) -> &EventManager<Self> {
|
||||
&self.events
|
||||
}
|
||||
fn events_mut(&mut self) -> &mut EventManager<Self> {
|
||||
&mut self.events
|
||||
}
|
||||
}
|
||||
|
||||
const WINDOW: f32 = 100.0;
|
||||
|
||||
/// Every sense that has fired on one widget since it was last read.
|
||||
#[derive(Default, Clone)]
|
||||
struct Fired(Rc<RefCell<Vec<CursorSense>>>);
|
||||
|
||||
impl Fired {
|
||||
fn take(&self) -> Vec<CursorSense> {
|
||||
std::mem::take(&mut self.0.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
||||
struct Ui {
|
||||
rsc: SenseRsc,
|
||||
render: UiRenderState,
|
||||
state: (),
|
||||
}
|
||||
|
||||
impl Ui {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
rsc: SenseRsc {
|
||||
ui: UiData::default(),
|
||||
events: EventManager::default(),
|
||||
},
|
||||
render: UiRenderState::new(),
|
||||
state: (),
|
||||
}
|
||||
}
|
||||
|
||||
fn listen<W: Widget + ?core::marker::Sized + 'static>(
|
||||
&mut self,
|
||||
widget: &StrongWidget<W>,
|
||||
senses: impl Into<CursorSenses>,
|
||||
) -> Fired {
|
||||
let fired = Fired::default();
|
||||
let sink = fired.clone();
|
||||
self.rsc
|
||||
.register_event(widget.weak(), senses.into(), move |ctx, _rsc| {
|
||||
sink.0.borrow_mut().push(ctx.data.sense)
|
||||
});
|
||||
fired
|
||||
}
|
||||
|
||||
/// Stacks the widgets bottom first, each on its own layer, and lays them
|
||||
/// out in a square window.
|
||||
fn stack(&mut self, children: Vec<StrongWidget>) {
|
||||
let root = self
|
||||
.rsc
|
||||
.ui
|
||||
.widgets
|
||||
.add_strong(Stack {
|
||||
children,
|
||||
size: StackSize::default(),
|
||||
})
|
||||
.any();
|
||||
self.render.resize((WINDOW, WINDOW));
|
||||
self.render.update(&root, &mut self.rsc);
|
||||
}
|
||||
|
||||
fn cursor(&mut self, at: (f32, f32)) -> CursorState {
|
||||
CursorState {
|
||||
pos: at.into(),
|
||||
exists: true,
|
||||
buttons: Default::default(),
|
||||
scroll_delta: Vec2::ZERO,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self, cursor: CursorState) {
|
||||
self.render.run_sensors(
|
||||
&mut self.rsc,
|
||||
&mut self.state,
|
||||
cursor,
|
||||
(WINDOW, WINDOW).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn full(ui: &mut Ui) -> StrongWidget<Rect> {
|
||||
rect(UiColor::WHITE).add_strong(&mut ui.rsc)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_stops_at_the_topmost_widget() {
|
||||
let ui = &mut Ui::new();
|
||||
let (bottom, middle, top) = (full(ui), full(ui), full(ui));
|
||||
let bottom_hover = ui.listen(&bottom, CursorSense::HoverStart);
|
||||
let middle_hover = ui.listen(&middle, CursorSense::HoverStart);
|
||||
let top_hover = ui.listen(&top, CursorSense::HoverStart);
|
||||
ui.stack(vec![bottom.any(), middle.any(), top.any()]);
|
||||
|
||||
let cursor = ui.cursor((50.0, 50.0));
|
||||
ui.run(cursor);
|
||||
|
||||
assert_eq!(top_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(
|
||||
middle_hover.take(),
|
||||
[],
|
||||
"hover is not shared with a layer below"
|
||||
);
|
||||
assert_eq!(bottom_hover.take(), []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_scroll_passes_through_every_widget_that_does_not_want_it() {
|
||||
let ui = &mut Ui::new();
|
||||
let (list, button, overlay) = (full(ui), full(ui), full(ui));
|
||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
||||
let clicked = ui.listen(&button, CursorSense::click());
|
||||
let overlay_clicked = ui.listen(&overlay, CursorSense::click());
|
||||
ui.stack(vec![list.any(), button.any(), overlay.any()]);
|
||||
|
||||
let mut cursor = ui.cursor((50.0, 50.0));
|
||||
cursor.scroll_delta = (0.0, 10.0).into();
|
||||
ui.run(cursor);
|
||||
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
[CursorSense::Scroll],
|
||||
"two layers of click-only widgets do not stop a scroll"
|
||||
);
|
||||
assert_eq!(clicked.take(), []);
|
||||
assert_eq!(overlay_clicked.take(), []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_topmost_listener_takes_a_press() {
|
||||
let ui = &mut Ui::new();
|
||||
let (below, above) = (full(ui), full(ui));
|
||||
let below_clicked = ui.listen(&below, CursorSense::click());
|
||||
let above_clicked = ui.listen(&above, CursorSense::click());
|
||||
ui.stack(vec![below.any(), above.any()]);
|
||||
|
||||
let mut cursor = ui.cursor((50.0, 50.0));
|
||||
cursor.buttons.left = ActivationState::Start;
|
||||
ui.run(cursor);
|
||||
|
||||
assert_eq!(above_clicked.take(), [CursorSense::click()]);
|
||||
assert_eq!(below_clicked.take(), [], "one press goes to one widget");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_press_beside_the_button_reaches_the_layer_below() {
|
||||
let ui = &mut Ui::new();
|
||||
let list = full(ui);
|
||||
// The row above the list covers it, but only its left half is the button.
|
||||
let button = full(ui);
|
||||
let gap = full(ui);
|
||||
let list_clicked = ui.listen(&list, CursorSense::click());
|
||||
let button_clicked = ui.listen(&button, CursorSense::click());
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![button.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![list.any(), row.any()]);
|
||||
|
||||
let mut on_button = ui.cursor((20.0, 50.0));
|
||||
on_button.buttons.left = ActivationState::Start;
|
||||
ui.run(on_button);
|
||||
assert_eq!(button_clicked.take(), [CursorSense::click()]);
|
||||
assert_eq!(list_clicked.take(), []);
|
||||
|
||||
let mut beside_it = ui.cursor((80.0, 50.0));
|
||||
beside_it.buttons.left = ActivationState::Start;
|
||||
ui.run(beside_it);
|
||||
assert_eq!(button_clicked.take(), [], "the cursor is not on the button");
|
||||
assert_eq!(
|
||||
list_clicked.take(),
|
||||
[CursorSense::click()],
|
||||
"a press beside the button belongs to what is under it"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaving_a_widget_still_ends_its_hover() {
|
||||
let ui = &mut Ui::new();
|
||||
let widget = full(ui);
|
||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
ui.stack(vec![widget.any()]);
|
||||
|
||||
let cursor = ui.cursor((50.0, 50.0));
|
||||
ui.run(cursor);
|
||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let mut gone = ui.cursor((50.0, 50.0));
|
||||
gone.exists = false;
|
||||
ui.run(gone);
|
||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn leaving_a_widget_does_not_block_the_layer_below() {
|
||||
let ui = &mut Ui::new();
|
||||
let below = full(ui);
|
||||
// Only the left half of the layer above is a widget, so the cursor can
|
||||
// leave it without leaving the one underneath.
|
||||
let (above, gap) = (full(ui), full(ui));
|
||||
let below_hover = ui.listen(&below, CursorSense::HoverStart);
|
||||
let above_hover = ui.listen(&above, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![above.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![below.any(), row.any()]);
|
||||
|
||||
let on_above = ui.cursor((20.0, 50.0));
|
||||
ui.run(on_above);
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(below_hover.take(), [], "the layer above is over it");
|
||||
|
||||
let beside_it = ui.cursor((80.0, 50.0));
|
||||
ui.run(beside_it);
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverEnd]);
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
[CursorSense::HoverStart],
|
||||
"ending a hover above must not stop the hover below"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hovering_a_button_above_does_not_stop_a_later_scroll() {
|
||||
let ui = &mut Ui::new();
|
||||
let (list, button) = (full(ui), full(ui));
|
||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
||||
let clicked = ui.listen(&button, CursorSense::click());
|
||||
ui.stack(vec![list.any(), button.any()]);
|
||||
|
||||
// The hover arrives in its own frame, as a window delivers it.
|
||||
let hover = ui.cursor((50.0, 50.0));
|
||||
ui.run(hover);
|
||||
assert_eq!(scrolled.take(), []);
|
||||
|
||||
let mut wheel = ui.cursor((50.0, 50.0));
|
||||
wheel.scroll_delta = (0.0, 10.0).into();
|
||||
ui.run(wheel);
|
||||
|
||||
assert_eq!(
|
||||
scrolled.take(),
|
||||
[CursorSense::Scroll],
|
||||
"a hover already resting on the button must not consume the wheel"
|
||||
);
|
||||
assert_eq!(clicked.take(), []);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn covering_a_widget_ends_its_hover() {
|
||||
let ui = &mut Ui::new();
|
||||
let below = full(ui);
|
||||
// Only the left half of the layer above is a widget, so the cursor can
|
||||
// start beside it and then move onto it.
|
||||
let (above, gap) = (full(ui), full(ui));
|
||||
let below_hover = ui.listen(&below, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let above_hover = ui.listen(&above, CursorSense::HoverStart);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![above.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![below.any(), row.any()]);
|
||||
|
||||
let beside_it = ui.cursor((80.0, 50.0));
|
||||
ui.run(beside_it);
|
||||
assert_eq!(below_hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let onto_above = ui.cursor((20.0, 50.0));
|
||||
ui.run(onto_above);
|
||||
assert_eq!(above_hover.take(), [CursorSense::HoverStart]);
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
[CursorSense::HoverEnd],
|
||||
"a widget covered by one that took the input is no longer hovered"
|
||||
);
|
||||
|
||||
let off_again = ui.cursor((80.0, 50.0));
|
||||
ui.run(off_again);
|
||||
assert_eq!(
|
||||
below_hover.take(),
|
||||
[CursorSense::HoverStart],
|
||||
"uncovering it hovers it again"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hover_starts_and_ends_once_each() {
|
||||
let ui = &mut Ui::new();
|
||||
// Only the left half is the widget, so the cursor can leave it without
|
||||
// leaving the window.
|
||||
let (widget, gap) = (full(ui), full(ui));
|
||||
let hover = ui.listen(&widget, CursorSense::HoverStart | CursorSense::HoverEnd);
|
||||
let row = ui.rsc.ui.widgets.add_strong(Span {
|
||||
children: vec![widget.any(), gap.any()],
|
||||
dir: Dir::RIGHT,
|
||||
gap: 0.0,
|
||||
});
|
||||
ui.stack(vec![row.any()]);
|
||||
|
||||
let inside = ui.cursor((20.0, 50.0));
|
||||
ui.run(inside);
|
||||
assert_eq!(hover.take(), [CursorSense::HoverStart]);
|
||||
|
||||
let further_in = ui.cursor((30.0, 50.0));
|
||||
ui.run(further_in);
|
||||
assert_eq!(hover.take(), [], "staying inside is not a second start");
|
||||
|
||||
let outside = ui.cursor((80.0, 50.0));
|
||||
ui.run(outside);
|
||||
assert_eq!(hover.take(), [CursorSense::HoverEnd]);
|
||||
|
||||
let further_out = ui.cursor((90.0, 50.0));
|
||||
ui.run(further_out);
|
||||
assert_eq!(hover.take(), [], "an ended hover does not end again");
|
||||
|
||||
let back_inside = ui.cursor((20.0, 50.0));
|
||||
ui.run(back_inside);
|
||||
assert_eq!(
|
||||
hover.take(),
|
||||
[CursorSense::HoverStart],
|
||||
"re-entering starts it"
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user