Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c4b74f2367 | ||
|
|
f312db60c2 |
No files matched your search
@@ -135,11 +135,6 @@ impl<Rsc: HasEvents + 'static, E: Event> TypeEventManager<Rsc, E> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What this widget registered, without running any of it.
|
|
||||||
pub fn registered(&self, id: WidgetId) -> impl Iterator<Item = &E> {
|
|
||||||
self.map.get(&id).into_iter().flatten().map(|(e, _)| e)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_fn<'a>(
|
pub fn run_fn<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: impl IdLike,
|
id: impl IdLike,
|
||||||
|
|||||||
+1
-5
@@ -1,10 +1,6 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use arboard::Clipboard;
|
use arboard::Clipboard;
|
||||||
use std::{
|
use std::{marker::PhantomData, sync::Arc, time::Instant};
|
||||||
marker::{PhantomData, Sized},
|
|
||||||
sync::Arc,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{Ime, WindowEvent},
|
event::{Ime, WindowEvent},
|
||||||
event_loop::{ActiveEventLoop, EventLoopProxy},
|
event_loop::{ActiveEventLoop, EventLoopProxy},
|
||||||
|
|||||||
+15
-72
@@ -4,14 +4,14 @@ use std::{
|
|||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CursorButton {
|
pub enum CursorButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
Middle,
|
Middle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum CursorSense {
|
pub enum CursorSense {
|
||||||
PressStart(CursorButton),
|
PressStart(CursorButton),
|
||||||
Pressing(CursorButton),
|
Pressing(CursorButton),
|
||||||
@@ -52,19 +52,6 @@ impl CursorSense {
|
|||||||
pub fn is_dragging(&self) -> bool {
|
pub fn is_dragging(&self) -> bool {
|
||||||
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
matches!(self, CursorSense::Pressing(CursorButton::Left))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes what this sense answers to out of `cursor`, so a widget below
|
|
||||||
/// does not also get it. Hovering takes nothing: it goes to the topmost
|
|
||||||
/// widget in shape, which is not a question about the input.
|
|
||||||
fn take(&self, cursor: &mut CursorState) {
|
|
||||||
match self {
|
|
||||||
Self::PressStart(button) | Self::Pressing(button) | Self::PressEnd(button) => {
|
|
||||||
*cursor.buttons.select_mut(button) = ActivationState::Off
|
|
||||||
}
|
|
||||||
Self::Scroll => cursor.scroll_delta = Vec2::ZERO,
|
|
||||||
Self::HoverStart | Self::Hovering | Self::HoverEnd => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -91,14 +78,6 @@ impl CursorButtons {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_mut(&mut self, button: &CursorButton) -> &mut ActivationState {
|
|
||||||
match button {
|
|
||||||
CursorButton::Left => &mut self.left,
|
|
||||||
CursorButton::Right => &mut self.right,
|
|
||||||
CursorButton::Middle => &mut self.middle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn end_frame(&mut self) {
|
pub fn end_frame(&mut self) {
|
||||||
self.left.end_frame();
|
self.left.end_frame();
|
||||||
self.middle.end_frame();
|
self.middle.end_frame();
|
||||||
@@ -185,46 +164,19 @@ impl SensorUi for UiRenderState {
|
|||||||
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
// state like thing, but local to render state, and is passed to UiRsc events so you can
|
||||||
// update it there?
|
// update it there?
|
||||||
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
let mut active = std::mem::take(&mut rsc.events_mut().get_type::<CursorSense>().active);
|
||||||
// Narrowed as it descends: a widget takes what it answers to, and
|
|
||||||
// what is left is what the layers below see.
|
|
||||||
let mut cursor = cursor;
|
|
||||||
let mut hovered = false;
|
|
||||||
for layer in self.layers.indices().rev() {
|
for layer in self.layers.indices().rev() {
|
||||||
let mut below = cursor.clone();
|
let mut sensed = false;
|
||||||
let mut hovered_here = false;
|
|
||||||
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
|
for (id, sensor) in active.get_mut(&layer).into_flat_iter() {
|
||||||
let shape = self.active.get(id).unwrap().region;
|
let shape = self.active.get(id).unwrap().region;
|
||||||
let region = shape.to_px(window_size);
|
let region = shape.to_px(window_size);
|
||||||
let over = cursor.exists && region.contains(cursor.pos);
|
let in_shape = cursor.exists && region.contains(cursor.pos);
|
||||||
// Hover goes to the topmost widget in shape and no further.
|
sensor.hover.update(in_shape);
|
||||||
sensor.hover.update(over && !hovered);
|
if sensor.hover == ActivationState::Off {
|
||||||
// `is_off` would be wrong here: it counts `End`, which is
|
|
||||||
// the one frame a hover-end handler has to run on.
|
|
||||||
if !over && sensor.hover == ActivationState::Off {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
hovered_here |= over;
|
sensed = true;
|
||||||
|
|
||||||
// Momentary input belongs to whatever the cursor is on. A
|
let cursor = cursor.clone();
|
||||||
// widget it has just left still hears its hover ending, but
|
|
||||||
// a press landing elsewhere is neither its press to receive
|
|
||||||
// nor its press to take.
|
|
||||||
let cursor = match over {
|
|
||||||
true => cursor.clone(),
|
|
||||||
false => CursorState {
|
|
||||||
pos: cursor.pos,
|
|
||||||
exists: cursor.exists,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for senses in rsc.events_mut().get_type::<CursorSense>().registered(*id) {
|
|
||||||
for sense in senses.iter() {
|
|
||||||
if matches(sense, &cursor, sensor.hover) {
|
|
||||||
sense.take(&mut below);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = CursorData {
|
let data = CursorData {
|
||||||
pos: cursor.pos - region.top_left,
|
pos: cursor.pos - region.top_left,
|
||||||
@@ -239,9 +191,7 @@ impl SensorUi for UiRenderState {
|
|||||||
};
|
};
|
||||||
rsc.run_event::<CursorSense>(*id, data, state);
|
rsc.run_event::<CursorSense>(*id, data, state);
|
||||||
}
|
}
|
||||||
hovered |= hovered_here;
|
if sensed {
|
||||||
cursor = below;
|
|
||||||
if hovered && !is_momentary(&cursor) {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,14 +204,8 @@ pub fn should_run(
|
|||||||
cursor: &CursorState,
|
cursor: &CursorState,
|
||||||
hover: ActivationState,
|
hover: ActivationState,
|
||||||
) -> Option<CursorSense> {
|
) -> Option<CursorSense> {
|
||||||
senses
|
for sense in senses.iter() {
|
||||||
.iter()
|
if match sense {
|
||||||
.find(|sense| matches(sense, cursor, hover))
|
|
||||||
.copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn matches(sense: &CursorSense, cursor: &CursorState, hover: ActivationState) -> bool {
|
|
||||||
match sense {
|
|
||||||
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
CursorSense::PressStart(button) => cursor.buttons.select(button).is_start(),
|
||||||
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
CursorSense::Pressing(button) => cursor.buttons.select(button).is_on(),
|
||||||
CursorSense::PressEnd(button) => cursor.buttons.select(button).is_end(),
|
CursorSense::PressEnd(button) => cursor.buttons.select(button).is_end(),
|
||||||
@@ -269,12 +213,11 @@ fn matches(sense: &CursorSense, cursor: &CursorState, hover: ActivationState) ->
|
|||||||
CursorSense::Hovering => hover.is_on(),
|
CursorSense::Hovering => hover.is_on(),
|
||||||
CursorSense::HoverEnd => hover.is_end(),
|
CursorSense::HoverEnd => hover.is_end(),
|
||||||
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
CursorSense::Scroll => cursor.scroll_delta != Vec2::ZERO,
|
||||||
|
} {
|
||||||
|
return Some(*sense);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
None
|
||||||
/// Whether anything is happening to the cursor beyond where it rests.
|
|
||||||
fn is_momentary(cursor: &CursorState) -> bool {
|
|
||||||
cursor.scroll_delta != Vec2::ZERO || cursor.buttons.iter().any(|(_, state)| !state.is_off())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActivationState {
|
impl ActivationState {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ mod max_size;
|
|||||||
mod offset;
|
mod offset;
|
||||||
mod pad;
|
mod pad;
|
||||||
mod scroll;
|
mod scroll;
|
||||||
mod sized;
|
mod set_size;
|
||||||
mod span;
|
mod span;
|
||||||
mod stack;
|
mod stack;
|
||||||
|
|
||||||
@@ -14,6 +14,6 @@ pub use max_size::*;
|
|||||||
pub use offset::*;
|
pub use offset::*;
|
||||||
pub use pad::*;
|
pub use pad::*;
|
||||||
pub use scroll::*;
|
pub use scroll::*;
|
||||||
pub use sized::*;
|
pub use set_size::*;
|
||||||
pub use span::*;
|
pub use span::*;
|
||||||
pub use stack::*;
|
pub use stack::*;
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct Sized {
|
pub struct SetSize {
|
||||||
pub inner: StrongWidget,
|
pub inner: StrongWidget,
|
||||||
pub x: Option<Len>,
|
pub x: Option<Len>,
|
||||||
pub y: Option<Len>,
|
pub y: Option<Len>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sized {
|
impl SetSize {
|
||||||
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
fn apply_to_outer(&self, ctx: &mut SizeCtx) {
|
||||||
if let Some(x) = self.x {
|
if let Some(x) = self.x {
|
||||||
ctx.outer.x.select_len(x.apply_rest());
|
ctx.outer.x.select_len(x.apply_rest());
|
||||||
@@ -17,7 +17,7 @@ impl Sized {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Sized {
|
impl Widget for SetSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) {
|
fn draw(&mut self, painter: &mut Painter) {
|
||||||
painter.widget(&self.inner);
|
painter.widget(&self.inner);
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::marker::{Sized, Unsize};
|
use std::marker::Unsize;
|
||||||
|
|
||||||
pub struct WidgetPtr {
|
pub struct WidgetPtr {
|
||||||
pub inner: Option<StrongWidget>,
|
pub inner: Option<StrongWidget>,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::marker::{PhantomData, Sized};
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> {
|
pub struct TextBuilder<State, O = TextOutput, H: WidgetOption<State> = ()> {
|
||||||
pub content: String,
|
pub content: String,
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, Sized> {
|
fn sized(self, size: impl Into<Size>) -> impl WidgetFn<Rsc, SetSize> {
|
||||||
let size = size.into();
|
let size = size.into();
|
||||||
move |state| Sized {
|
move |state| SetSize {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: Some(size.x),
|
x: Some(size.x),
|
||||||
y: Some(size.y),
|
y: Some(size.y),
|
||||||
@@ -58,18 +58,18 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
fn width(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |state| Sized {
|
move |state| SetSize {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: Some(len),
|
x: Some(len),
|
||||||
y: None,
|
y: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, Sized> {
|
fn height(self, len: impl Into<Len>) -> impl WidgetFn<Rsc, SetSize> {
|
||||||
let len = len.into();
|
let len = len.into();
|
||||||
move |state| Sized {
|
move |state| SetSize {
|
||||||
inner: self.add_strong(state),
|
inner: self.add_strong(state),
|
||||||
x: None,
|
x: None,
|
||||||
y: Some(len),
|
y: Some(len),
|
||||||
|
|||||||
@@ -1,255 +0,0 @@
|
|||||||
//! Input across layers: what a widget takes, 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 mut ui = Ui::new();
|
|
||||||
let (bottom, middle, top) = (full(&mut ui), full(&mut ui), full(&mut 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 mut ui = Ui::new();
|
|
||||||
let (list, button, overlay) = (full(&mut ui), full(&mut ui), full(&mut 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 a_click_and_a_scroll_in_one_frame_go_to_different_widgets() {
|
|
||||||
let mut ui = Ui::new();
|
|
||||||
let (list, button) = (full(&mut ui), full(&mut ui));
|
|
||||||
let scrolled = ui.listen(&list, CursorSense::Scroll);
|
|
||||||
let clicked = ui.listen(&button, CursorSense::click());
|
|
||||||
ui.stack(vec![list.any(), button.any()]);
|
|
||||||
|
|
||||||
let mut cursor = ui.cursor((50.0, 50.0));
|
|
||||||
cursor.scroll_delta = (0.0, 10.0).into();
|
|
||||||
cursor.buttons.left = ActivationState::Start;
|
|
||||||
ui.run(cursor);
|
|
||||||
|
|
||||||
assert_eq!(clicked.take(), [CursorSense::click()]);
|
|
||||||
assert_eq!(
|
|
||||||
scrolled.take(),
|
|
||||||
[CursorSense::Scroll],
|
|
||||||
"taking the click must not take the scroll with it"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn only_the_topmost_listener_takes_a_press() {
|
|
||||||
let mut ui = Ui::new();
|
|
||||||
let (below, above) = (full(&mut ui), full(&mut 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 mut ui = Ui::new();
|
|
||||||
let list = full(&mut ui);
|
|
||||||
// The row above the list covers it, but only its left half is the button.
|
|
||||||
let button = full(&mut ui);
|
|
||||||
let gap = full(&mut 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 mut ui = Ui::new();
|
|
||||||
let widget = full(&mut 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]);
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user