Replace the oversized header and reference rail with stable editor slots and a retained supporting drawer. Bind diagnostic, result and menu actions to their original publication and visit; preserve effective selections on navigation and dismiss Actions, Find and the drawer one surface per Escape. Retain exact Windows source captures, eight final gates (871 desktop passes), independent reviews, native selection/rename/recovery trials and the original failures. All 526 tested inputs and staged evidence were byte-checked before commit. Combined preview integration and packaging remain with issue 27; Linux remains paused.
627 lines
20 KiB
Rust
627 lines
20 KiB
Rust
use super::*;
|
|
use iced::advanced::widget::operation::{self, Focusable, Scrollable};
|
|
use iced::widget::{Space, button, column, container, mouse_area, scrollable, text_input};
|
|
use iced::{Point, Theme, event, keyboard};
|
|
|
|
type View = Element<'static, Message, Theme, ()>;
|
|
const BOUNDS: Rectangle = Rectangle {
|
|
x: 0.,
|
|
y: 0.,
|
|
width: 400.,
|
|
height: 300.,
|
|
};
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
enum Message {
|
|
Primary(u8),
|
|
Related(u8),
|
|
BackgroundButton,
|
|
BackgroundRelease,
|
|
Submit(u8),
|
|
Edit,
|
|
}
|
|
|
|
fn rows(version: u8) -> View {
|
|
column![
|
|
button(Space::new())
|
|
.width(200.)
|
|
.height(40.)
|
|
.padding(0)
|
|
.on_press(Message::Primary(version)),
|
|
button(Space::new())
|
|
.width(200.)
|
|
.height(40.)
|
|
.padding(0)
|
|
.on_press(Message::Related(version)),
|
|
]
|
|
.into()
|
|
}
|
|
fn guarded(generation: Option<Id>, version: u8) -> View {
|
|
let child: View = if generation.is_some() {
|
|
rows(version)
|
|
} else {
|
|
Space::new().into()
|
|
};
|
|
Guard::new(
|
|
generation.map(|publication| (publication, Id::nil())),
|
|
child,
|
|
)
|
|
.into()
|
|
}
|
|
fn press() -> Event {
|
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
|
}
|
|
fn release() -> Event {
|
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
|
}
|
|
fn enter() -> Event {
|
|
Event::Keyboard(keyboard::Event::KeyPressed {
|
|
key: keyboard::Key::Named(keyboard::key::Named::Enter),
|
|
modified_key: keyboard::Key::Named(keyboard::key::Named::Enter),
|
|
physical_key: keyboard::key::Physical::Code(keyboard::key::Code::Enter),
|
|
location: keyboard::Location::Standard,
|
|
modifiers: keyboard::Modifiers::empty(),
|
|
text: None,
|
|
repeat: false,
|
|
})
|
|
}
|
|
fn touch_down(id: u64, position: Point) -> Event {
|
|
Event::Touch(touch::Event::FingerPressed {
|
|
id: touch::Finger(id),
|
|
position,
|
|
})
|
|
}
|
|
fn touch_up(id: u64, position: Point) -> Event {
|
|
Event::Touch(touch::Event::FingerLifted {
|
|
id: touch::Finger(id),
|
|
position,
|
|
})
|
|
}
|
|
fn touch_lost(id: u64, position: Point) -> Event {
|
|
Event::Touch(touch::Event::FingerLost {
|
|
id: touch::Finger(id),
|
|
position,
|
|
})
|
|
}
|
|
|
|
struct Mounted {
|
|
view: View,
|
|
tree: widget::Tree,
|
|
node: layout::Node,
|
|
}
|
|
impl Mounted {
|
|
fn new(mut view: View) -> Self {
|
|
let mut tree = widget::Tree::new(view.as_widget());
|
|
let node = view.as_widget_mut().layout(
|
|
&mut tree,
|
|
&(),
|
|
&layout::Limits::new(Size::ZERO, BOUNDS.size()),
|
|
);
|
|
Self { view, tree, node }
|
|
}
|
|
fn replace(&mut self, mut view: View) {
|
|
self.tree.diff(view.as_widget());
|
|
self.node = view.as_widget_mut().layout(
|
|
&mut self.tree,
|
|
&(),
|
|
&layout::Limits::new(Size::ZERO, BOUNDS.size()),
|
|
);
|
|
self.view = view;
|
|
}
|
|
fn event(&mut self, event: Event, point: Point) -> (Vec<Message>, event::Status) {
|
|
self.event_with_capture(event, point, false)
|
|
}
|
|
fn event_with_capture(
|
|
&mut self,
|
|
event: Event,
|
|
point: Point,
|
|
captured: bool,
|
|
) -> (Vec<Message>, event::Status) {
|
|
let mut messages = Vec::new();
|
|
let mut shell = Shell::new(&mut messages);
|
|
if captured {
|
|
shell.capture_event();
|
|
}
|
|
self.view.as_widget_mut().update(
|
|
&mut self.tree,
|
|
&event,
|
|
Layout::new(&self.node),
|
|
mouse::Cursor::Available(point),
|
|
&(),
|
|
&mut iced::advanced::clipboard::Null,
|
|
&mut shell,
|
|
&BOUNDS,
|
|
);
|
|
let status = shell.event_status();
|
|
(messages, status)
|
|
}
|
|
fn operate(&mut self, operation: &mut dyn widget::Operation) {
|
|
self.view
|
|
.as_widget_mut()
|
|
.operate(&mut self.tree, Layout::new(&self.node), &(), operation);
|
|
}
|
|
fn focus(&mut self) {
|
|
self.operate(&mut operation::focusable::focus::<()>("row".into()));
|
|
}
|
|
fn focused(&mut self) -> bool {
|
|
#[derive(Default)]
|
|
struct Focus(bool);
|
|
impl widget::Operation for Focus {
|
|
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
|
visit(self);
|
|
}
|
|
fn focusable(
|
|
&mut self,
|
|
_: Option<&widget::Id>,
|
|
_: Rectangle,
|
|
state: &mut dyn Focusable,
|
|
) {
|
|
self.0 |= state.is_focused();
|
|
}
|
|
}
|
|
let mut focus = Focus::default();
|
|
self.operate(&mut focus);
|
|
focus.0
|
|
}
|
|
fn scroll(&mut self) -> Vector {
|
|
#[derive(Default)]
|
|
struct Offset(Option<Vector>);
|
|
impl widget::Operation for Offset {
|
|
fn traverse(&mut self, visit: &mut dyn FnMut(&mut dyn widget::Operation)) {
|
|
visit(self);
|
|
}
|
|
fn scrollable(
|
|
&mut self,
|
|
_: Option<&widget::Id>,
|
|
_: Rectangle,
|
|
_: Rectangle,
|
|
translation: Vector,
|
|
_: &mut dyn Scrollable,
|
|
) {
|
|
self.0 = Some(translation);
|
|
}
|
|
}
|
|
let mut offset = Offset::default();
|
|
self.operate(&mut offset);
|
|
offset.0.unwrap()
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn current_primary_and_related_mouse_clicks_survive_same_publication_diff() {
|
|
for (y, expected) in [(20., Message::Primary(1)), (60., Message::Related(1))] {
|
|
let generation = Id::new_v4();
|
|
let point = Point::new(20., y);
|
|
let mut ui = Mounted::new(guarded(Some(generation), 1));
|
|
assert_eq!(ui.event(press(), point), (vec![], event::Status::Captured));
|
|
ui.replace(guarded(Some(generation), 1));
|
|
assert_eq!(
|
|
ui.event(release(), point),
|
|
(vec![expected], event::Status::Captured)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn same_publication_new_visit_cancels_armed_rows_without_a_hidden_tree_frame() {
|
|
for touch in [false, true] {
|
|
for (y, expected) in [(20., Message::Primary(2)), (60., Message::Related(2))] {
|
|
let publication = Id::new_v4();
|
|
let old_visit = Id::new_v4();
|
|
let new_visit = Id::new_v4();
|
|
let point = Point::new(20., y);
|
|
let mut ui = Mounted::new(Guard::new(Some((publication, old_visit)), rows(1)).into());
|
|
let down = || if touch { touch_down(7, point) } else { press() };
|
|
let up = || if touch { touch_up(7, point) } else { release() };
|
|
assert_eq!(ui.event(down(), point), (vec![], event::Status::Captured));
|
|
// Hide→Show has already happened in application state. No None
|
|
// publication or empty child is ever diffed into this retained Tree.
|
|
ui.replace(Guard::new(Some((publication, new_visit)), rows(2)).into());
|
|
assert_eq!(ui.event(up(), point), (vec![], event::Status::Captured));
|
|
assert_eq!(ui.event(down(), point), (vec![], event::Status::Captured));
|
|
assert_eq!(
|
|
ui.event(up(), point),
|
|
(vec![expected], event::Status::Captured)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn negative_control_iced_button_retargets_a_press_after_plain_tree_diff() {
|
|
for (y, expected) in [(20., Message::Primary(2)), (60., Message::Related(2))] {
|
|
let point = Point::new(20., y);
|
|
let mut ui = Mounted::new(rows(1));
|
|
assert!(ui.event(press(), point).0.is_empty());
|
|
ui.replace(rows(2));
|
|
assert_eq!(ui.event(release(), point).0, vec![expected]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn replaced_primary_and_related_rows_consume_stale_release_then_accept_new_click() {
|
|
for y in [20., 60.] {
|
|
let point = Point::new(20., y);
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
ui.event(press(), point);
|
|
ui.replace(guarded(Some(Id::new_v4()), 2));
|
|
ui.replace(guarded(Some(Id::new_v4()), 3));
|
|
assert_eq!(
|
|
ui.event(release(), point),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
ui.event(press(), point);
|
|
let expected = if y < 40. {
|
|
Message::Primary(3)
|
|
} else {
|
|
Message::Related(3)
|
|
};
|
|
assert_eq!(
|
|
ui.event(release(), point),
|
|
(vec![expected], event::Status::Captured)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn hidden_zero_size_list_consumes_release_outside_old_bounds_once() {
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
ui.event(press(), Point::new(20., 20.));
|
|
ui.replace(guarded(None, 0));
|
|
ui.replace(guarded(None, 0));
|
|
assert_eq!(ui.node.size(), Size::ZERO);
|
|
let outside = Point::new(350., 250.);
|
|
assert_eq!(
|
|
ui.event(release(), outside),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert_eq!(
|
|
ui.event(release(), outside),
|
|
(vec![], event::Status::Ignored)
|
|
);
|
|
ui.replace(guarded(Some(Id::new_v4()), 2));
|
|
assert_eq!(ui.event(release(), Point::new(20., 20.)).0, vec![]);
|
|
}
|
|
|
|
fn with_background(content: View) -> View {
|
|
mouse_area(
|
|
button(container(content).width(250.).height(100.))
|
|
.padding(0)
|
|
.on_press(Message::BackgroundButton),
|
|
)
|
|
.on_release(Message::BackgroundRelease)
|
|
.into()
|
|
}
|
|
|
|
#[test]
|
|
fn stale_release_does_not_bubble_to_real_background_controls() {
|
|
let point = Point::new(20., 20.);
|
|
let mut ui = Mounted::new(with_background(guarded(Some(Id::new_v4()), 1)));
|
|
ui.event(press(), point);
|
|
ui.replace(with_background(guarded(None, 0)));
|
|
assert_eq!(
|
|
ui.event(release(), point),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
// The enclosing real button still supports a fresh ordinary click.
|
|
ui.event(press(), point);
|
|
assert_eq!(
|
|
ui.event(release(), point).0,
|
|
vec![Message::BackgroundButton]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn negative_control_removing_unguarded_rows_exposes_background_release() {
|
|
let point = Point::new(20., 20.);
|
|
let mut ui = Mounted::new(with_background(rows(1)));
|
|
ui.event(press(), point);
|
|
ui.replace(with_background(Space::new().into()));
|
|
assert_eq!(
|
|
ui.event(release(), point).0,
|
|
vec![Message::BackgroundRelease]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn current_touch_activates_primary_and_related_and_cancel_does_not_activate() {
|
|
for (y, expected) in [(20., Message::Primary(1)), (60., Message::Related(1))] {
|
|
let point = Point::new(20., y);
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
assert_eq!(
|
|
ui.event(touch_down(1, point), point),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert_eq!(
|
|
ui.event(touch_up(1, point), point),
|
|
(vec![expected], event::Status::Captured)
|
|
);
|
|
ui.event(touch_down(2, point), point);
|
|
assert_eq!(
|
|
ui.event(touch_lost(2, point), point),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert!(ui.event(touch_up(2, point), point).0.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn replaced_touch_consumes_lift_or_cancel_even_when_hidden() {
|
|
for hide in [false, true] {
|
|
for lost in [false, true] {
|
|
for y in [20., 60.] {
|
|
let point = Point::new(20., y);
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
ui.event(touch_down(1, point), point);
|
|
ui.replace(guarded(if hide { None } else { Some(Id::new_v4()) }, 2));
|
|
let end = if lost {
|
|
touch_lost(1, point)
|
|
} else {
|
|
touch_up(1, point)
|
|
};
|
|
assert_eq!(ui.event(end, point), (vec![], event::Status::Captured));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn unrelated_finger_or_mouse_release_cannot_complete_another_fingers_press() {
|
|
let point = Point::new(20., 20.);
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
ui.event(touch_down(1, point), point);
|
|
ui.event(touch_down(2, point), point);
|
|
assert_eq!(
|
|
ui.event(touch_up(2, point), point),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert_eq!(
|
|
ui.event(release(), point),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert_eq!(
|
|
ui.event(touch_up(1, point), point).0,
|
|
vec![Message::Primary(1)]
|
|
);
|
|
}
|
|
|
|
fn keyboard_row(version: u8) -> View {
|
|
// Iced 0.14.2 Button has no focus/keyboard activation. A real focusable
|
|
// submit control tests the wrapper's focus lifecycle without inventing it.
|
|
text_input("Diagnostic action", "row")
|
|
.id("row")
|
|
.on_input(|_| Message::Edit)
|
|
.on_submit(Message::Submit(version))
|
|
.into()
|
|
}
|
|
|
|
#[test]
|
|
fn same_publication_preserves_focus_but_replacement_never_inherits_activation() {
|
|
let generation = Id::new_v4();
|
|
let mut ui = Mounted::new(Guard::new(Some((generation, Id::nil())), keyboard_row(1)).into());
|
|
ui.focus();
|
|
assert!(ui.focused());
|
|
ui.replace(Guard::new(Some((generation, Id::nil())), keyboard_row(1)).into());
|
|
assert!(ui.focused());
|
|
assert_eq!(ui.event(enter(), Point::ORIGIN).0, vec![Message::Submit(1)]);
|
|
ui.replace(Guard::new(Some((Id::new_v4(), Id::nil())), keyboard_row(2)).into());
|
|
assert!(!ui.focused());
|
|
assert!(ui.event(enter(), Point::ORIGIN).0.is_empty());
|
|
ui.focus();
|
|
assert_eq!(ui.event(enter(), Point::ORIGIN).0, vec![Message::Submit(2)]);
|
|
ui.replace(Guard::new(None, Space::new()).into());
|
|
ui.replace(Guard::new(Some((Id::new_v4(), Id::nil())), keyboard_row(3)).into());
|
|
assert!(!ui.focused());
|
|
assert!(ui.event(enter(), Point::ORIGIN).0.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn negative_control_plain_focusable_row_migrates_activation_to_replacement() {
|
|
let mut ui = Mounted::new(keyboard_row(1));
|
|
ui.focus();
|
|
ui.replace(keyboard_row(2));
|
|
assert!(ui.focused());
|
|
assert_eq!(ui.event(enter(), Point::ORIGIN).0, vec![Message::Submit(2)]);
|
|
}
|
|
|
|
#[test]
|
|
fn outer_scroll_offset_survives_same_and_changed_publications() {
|
|
fn pane(generation: Id) -> View {
|
|
scrollable(Guard::new(
|
|
Some((generation, Id::nil())),
|
|
column![rows(1), Space::new().height(600.)],
|
|
))
|
|
.width(240.)
|
|
.height(100.)
|
|
.into()
|
|
}
|
|
let generation = Id::new_v4();
|
|
let mut ui = Mounted::new(pane(generation));
|
|
ui.event(
|
|
Event::Mouse(mouse::Event::WheelScrolled {
|
|
delta: mouse::ScrollDelta::Pixels { x: 0., y: -120. },
|
|
}),
|
|
Point::new(20., 20.),
|
|
);
|
|
let offset = ui.scroll();
|
|
assert_eq!(offset, Vector::new(0., 120.));
|
|
ui.replace(pane(generation));
|
|
assert_eq!(ui.scroll(), offset);
|
|
ui.replace(pane(Id::new_v4()));
|
|
assert_eq!(ui.scroll(), offset);
|
|
}
|
|
|
|
#[test]
|
|
fn stale_touch_release_does_not_disarm_a_new_current_gesture() {
|
|
let point = Point::new(20., 20.);
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
ui.event(touch_down(1, point), point);
|
|
ui.replace(guarded(Some(Id::new_v4()), 2));
|
|
ui.event(touch_down(2, point), point);
|
|
assert_eq!(
|
|
ui.event(touch_up(1, point), point),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert_eq!(
|
|
ui.event(touch_up(2, point), point).0,
|
|
vec![Message::Primary(2)]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn fresh_mouse_press_after_missing_stale_release_can_activate_current_row() {
|
|
let point = Point::new(20., 20.);
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
ui.event(press(), point);
|
|
ui.replace(guarded(Some(Id::new_v4()), 2));
|
|
// The platform delivers another down without the previous up.
|
|
ui.event(press(), point);
|
|
assert_eq!(ui.event(release(), point).0, vec![Message::Primary(2)]);
|
|
}
|
|
|
|
#[test]
|
|
fn missed_press_is_ignored_and_dragging_release_outside_does_not_activate() {
|
|
let inside = Point::new(20., 20.);
|
|
let outside = Point::new(350., 250.);
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
assert_eq!(ui.event(press(), outside), (vec![], event::Status::Ignored));
|
|
assert_eq!(
|
|
ui.event(release(), inside),
|
|
(vec![], event::Status::Ignored)
|
|
);
|
|
ui.event(press(), inside);
|
|
assert_eq!(
|
|
ui.event(release(), outside),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert_eq!(
|
|
ui.event(release(), inside),
|
|
(vec![], event::Status::Ignored)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn same_publication_missing_terminal_recovers_on_fresh_mouse_or_touch_down() {
|
|
let primary = Point::new(20., 20.);
|
|
let related = Point::new(20., 60.);
|
|
for is_touch in [false, true] {
|
|
let generation = Id::new_v4();
|
|
let mut ui = Mounted::new(guarded(Some(generation), 1));
|
|
let down = |point| {
|
|
if is_touch {
|
|
touch_down(1, point)
|
|
} else {
|
|
press()
|
|
}
|
|
};
|
|
let up = |point| {
|
|
if is_touch {
|
|
touch_up(1, point)
|
|
} else {
|
|
release()
|
|
}
|
|
};
|
|
ui.event(down(primary), primary);
|
|
ui.replace(guarded(Some(generation), 1));
|
|
// The first gesture never receives its terminal event; same Id means
|
|
// no publication reset can accidentally make this regression pass.
|
|
assert_eq!(
|
|
ui.event(down(related), related),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
assert_eq!(ui.event(up(related), related).0, vec![Message::Related(1)]);
|
|
assert!(ui.event(up(primary), primary).0.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn already_captured_terminal_cancels_child_without_activation_or_lingering_press() {
|
|
let primary = Point::new(20., 20.);
|
|
let related = Point::new(20., 60.);
|
|
for is_touch in [false, true] {
|
|
let mut ui = Mounted::new(guarded(Some(Id::new_v4()), 1));
|
|
let down = |point| {
|
|
if is_touch {
|
|
touch_down(1, point)
|
|
} else {
|
|
press()
|
|
}
|
|
};
|
|
let up = |point| {
|
|
if is_touch {
|
|
touch_up(1, point)
|
|
} else {
|
|
release()
|
|
}
|
|
};
|
|
ui.event(down(primary), primary);
|
|
assert_eq!(
|
|
ui.event_with_capture(up(primary), primary, true),
|
|
(vec![], event::Status::Captured)
|
|
);
|
|
// An unpaired second release must not expose a lingering button press.
|
|
assert_eq!(
|
|
ui.event(up(primary), primary),
|
|
(vec![], event::Status::Ignored)
|
|
);
|
|
ui.event(down(related), related);
|
|
assert_eq!(ui.event(up(related), related).0, vec![Message::Related(1)]);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn captured_terminal_cancellation_preserves_same_publication_focusable_child_state() {
|
|
let generation = Id::new_v4();
|
|
let mut ui = Mounted::new(
|
|
Guard::new(
|
|
Some((generation, Id::nil())),
|
|
column![rows(1), keyboard_row(1)],
|
|
)
|
|
.into(),
|
|
);
|
|
ui.event(press(), Point::new(20., 20.));
|
|
// Focus through an operation avoids making the old pointer gesture whole.
|
|
ui.focus();
|
|
assert!(ui.focused());
|
|
assert!(
|
|
ui.event_with_capture(release(), Point::new(20., 20.), true)
|
|
.0
|
|
.is_empty()
|
|
);
|
|
assert!(
|
|
ui.focused(),
|
|
"gesture cancellation must not remount the row tree"
|
|
);
|
|
assert_eq!(ui.event(enter(), Point::ORIGIN).0, vec![Message::Submit(1)]);
|
|
}
|
|
|
|
#[test]
|
|
fn hidden_zero_height_scroller_delivers_cancelled_mouse_and_touch_terminals_to_guard() {
|
|
fn pane(generation: Option<Id>) -> View {
|
|
scrollable(guarded(generation, 1))
|
|
.width(240.)
|
|
.height(if generation.is_some() { 100. } else { 0. })
|
|
.into()
|
|
}
|
|
let point = Point::new(20., 20.);
|
|
let outside = Point::new(350., 250.);
|
|
for is_touch in [false, true] {
|
|
let mut ui = Mounted::new(pane(Some(Id::new_v4())));
|
|
let down = if is_touch {
|
|
touch_down(1, point)
|
|
} else {
|
|
press()
|
|
};
|
|
assert_eq!(ui.event(down, point), (vec![], event::Status::Captured));
|
|
ui.replace(pane(None));
|
|
ui.replace(pane(None));
|
|
assert_eq!(ui.node.size().height, 0.);
|
|
let up = if is_touch {
|
|
touch_up(1, outside)
|
|
} else {
|
|
release()
|
|
};
|
|
assert_eq!(ui.event(up, outside), (vec![], event::Status::Captured));
|
|
}
|
|
}
|