use kui_core::cells::{Cell, CellGrid};
use kui_core::testing::{click_at, kinds};
use kui_core::{
ButtonPhase, Buttons, Core, EditOptions, InputEvent, Key, MouseButton, NodeSpec, Size,
TextStyle, UiEvent, Value, Vec2,
};
const VIEW: Size = Size { w: 400.0, h: 300.0 };
fn down(button: MouseButton) -> InputEvent {
InputEvent::MouseDown { button, clicks: 1 }
}
fn up(button: MouseButton) -> InputEvent {
InputEvent::MouseUp { button }
}
fn to(core: &mut Core, x: f32, y: f32) -> Vec<UiEvent> {
core.handle_input(InputEvent::CursorMoved(Vec2::new(x, y)))
}
fn mono() -> TextStyle {
TextStyle::new(14.0).mono().line_height(20.0)
}
struct Keys {
pane: Key,
term: Key,
child: Key,
go: Key,
}
fn frame(core: &mut Core, buttons: Buttons, term_claims: bool) -> Keys {
let cells = vec![Cell::new('x', 0xffffffff, 0); 40];
let mut ui = core.frame(VIEW, 1.0);
ui.configure_root(NodeSpec::column().fill());
let mut keys = Keys {
pane: Key::ROOT,
term: Key::ROOT,
child: Key::ROOT,
go: Key::ROOT,
};
keys.pane = ui.with_keyed(
"pane",
NodeSpec::column()
.size(300.0, 280.0)
.on_context_menu("menu")
.on_button(Value::map([("kind", Value::str("pane"))]))
.buttons(buttons),
|ui| {
let term = NodeSpec::default();
ui.cells_keyed(
"term",
&CellGrid {
rows: 4,
cols: 10,
cells: &cells,
style: mono(),
cursor: None,
origin_line: 0,
},
if term_claims {
term.on_button(Value::map([("kind", Value::str("term"))]))
.buttons(Buttons::MIDDLE)
} else {
term
},
);
keys.child = ui.leaf_keyed("child", NodeSpec::row().size(100.0, 40.0).hoverable());
ui.text_edit(
"note",
"hello world",
&EditOptions {
style: TextStyle::new(13.0),
..Default::default()
},
NodeSpec::row().size(200.0, 30.0),
);
keys.go = ui.leaf_keyed("go", NodeSpec::row().size(100.0, 30.0).on_click("go"));
},
);
ui.finish();
keys.term = core.key_of("term").expect("the grid is keyed");
keys
}
fn tag_kind(ev: &UiEvent) -> Option<&str> {
ev.payload.get("tag").and_then(|t| t.get_str("kind"))
}
const TERM_Y: f32 = 10.0;
const CHILD_Y: f32 = 100.0;
const EDIT_Y: f32 = 135.0;
#[test]
fn middle_press_move_and_release_reach_the_grid_with_their_cells() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, true);
let w = core.measure_text("M", &mono(), None).width.round();
to(&mut core, 2.5 * w, TERM_Y);
let press = core.handle_input(down(MouseButton::Middle));
assert_eq!(kinds(&press), ["button"], "{press:?}");
let b = press[0].button().expect("a button event");
assert_eq!(press[0].key, k.term, "the grid's own claim wins");
assert_eq!(tag_kind(&press[0]), Some("term"));
assert_eq!(b.phase, ButtonPhase::Press);
assert_eq!(b.button, MouseButton::Middle);
assert_eq!(press[0].payload.get_str("button"), Some("middle"));
assert_eq!(b.clicks, Some(1));
assert_eq!(b.cell, Some((0, 2)));
let moved = to(&mut core, 5.5 * w, 45.0);
let m = moved
.iter()
.find_map(UiEvent::button)
.expect("the move while held");
assert_eq!(m.phase, ButtonPhase::Move);
assert_eq!(m.cell, Some((2, 5)));
assert_eq!(m.clicks, None, "the count is the press's");
let released = core.handle_input(up(MouseButton::Middle));
assert_eq!(kinds(&released), ["button"]);
let r = released[0].button().unwrap();
assert_eq!(r.phase, ButtonPhase::Release);
assert_eq!(r.cell, Some((2, 5)));
assert_eq!(released[0].key, k.term);
assert!(
to(&mut core, 10.0, 10.0)
.iter()
.all(|e| e.button().is_none())
);
}
#[test]
fn the_capture_follows_the_pointer_off_the_node_until_the_release() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, true);
let w = core.measure_text("M", &mono(), None).width.round();
to(&mut core, 20.0, TERM_Y);
core.handle_input(down(MouseButton::Middle));
let over_child = to(&mut core, 20.0, CHILD_Y);
let moves: Vec<_> = over_child.iter().filter(|e| e.button().is_some()).collect();
assert_eq!(moves.len(), 1);
assert_eq!(
moves[0].key, k.term,
"the move is the grid's, not the child's"
);
assert_eq!(
moves[0].button().unwrap().cell,
Some((3, (20.0 / w).floor() as u32)),
"a cell past the grid is clamped to it"
);
to(&mut core, 380.0, 290.0);
core.handle_input(InputEvent::CursorLeft);
let released = core.handle_input(up(MouseButton::Middle));
assert_eq!(kinds(&released), ["button"]);
assert_eq!(released[0].key, k.term);
let r = released[0].button().unwrap();
assert_eq!(r.phase, ButtonPhase::Release);
assert_eq!(
r.pos,
Vec2::new(380.0, 290.0),
"where the pointer was last seen"
);
}
#[test]
fn a_child_that_claims_nothing_is_its_ancestors() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, true);
to(&mut core, 20.0, CHILD_Y);
let press = core.handle_input(down(MouseButton::Middle));
assert_eq!(kinds(&press), ["button"]);
assert_eq!(press[0].key, k.pane, "the owner's key and tag");
assert_eq!(tag_kind(&press[0]), Some("pane"));
assert_eq!(press[0].payload.get("cell"), None, "the pane is no grid");
let released = core.handle_input(up(MouseButton::Middle));
assert_eq!(released[0].key, k.pane);
to(&mut core, 20.0, TERM_Y);
let other = MouseButton::Other(0);
let press = core.handle_input(down(other));
assert_eq!(press[0].key, k.pane);
assert_eq!(press[0].payload.get("button"), Some(&Value::Int(3)));
assert_eq!(press[0].button().unwrap().button, other);
core.handle_input(up(other));
}
#[test]
fn several_buttons_are_held_at_once_each_its_own_capture() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, true);
to(&mut core, 20.0, TERM_Y);
core.handle_input(down(MouseButton::Middle));
to(&mut core, 20.0, CHILD_Y);
core.handle_input(down(MouseButton::Secondary));
assert_eq!(
core.interaction.button_owner(MouseButton::Middle),
Some(k.term)
);
assert_eq!(
core.interaction.button_owner(MouseButton::Secondary),
Some(k.pane)
);
let moved = to(&mut core, 30.0, CHILD_Y);
let owners: Vec<_> = moved
.iter()
.filter_map(|e| e.button().map(|b| (e.key, b.button)))
.collect();
assert_eq!(
owners,
[
(k.term, MouseButton::Middle),
(k.pane, MouseButton::Secondary)
]
);
let released = core.handle_input(up(MouseButton::Secondary));
assert_eq!(released.len(), 1);
assert_eq!(released[0].key, k.pane);
assert_eq!(
core.interaction.button_owner(MouseButton::Middle),
Some(k.term)
);
core.handle_input(up(MouseButton::Middle));
assert_eq!(core.interaction.button_owner(MouseButton::Middle), None);
}
#[test]
fn a_claimed_secondary_press_is_the_button_event_and_no_menu() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, false);
to(&mut core, 20.0, EDIT_Y);
let press = core.handle_input(down(MouseButton::Secondary));
assert_eq!(kinds(&press), ["button"], "{press:?}");
assert_eq!(press[0].key, k.pane);
assert_eq!(press[0].payload.get_str("button"), Some("secondary"));
assert!(core.menu().is_none(), "no stock menu");
let released = core.handle_input(up(MouseButton::Secondary));
assert_eq!(kinds(&released), ["button"]);
}
#[test]
fn without_secondary_in_the_mask_the_context_menu_is_as_it_was() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::MIDDLE | Buttons::OTHER, false);
to(&mut core, 20.0, CHILD_Y);
let press = core.handle_input(down(MouseButton::Secondary));
assert_eq!(kinds(&press), ["contextmenu"]);
assert_eq!(press[0].key, k.pane);
assert!(core.handle_input(up(MouseButton::Secondary)).is_empty());
assert_eq!(core.interaction.button_owner(MouseButton::Secondary), None);
to(&mut core, 20.0, EDIT_Y);
core.handle_input(down(MouseButton::Secondary));
assert!(core.menu().is_some(), "the editor's stock menu");
}
#[test]
fn a_nearer_context_menu_wins_the_secondary_button() {
let mut core = Core::new();
let mut ui = core.frame(VIEW, 1.0);
ui.with_keyed(
"pane",
NodeSpec::column().size(300.0, 200.0).on_button("pane"),
|ui| {
ui.leaf_keyed(
"row",
NodeSpec::row().size(100.0, 40.0).on_context_menu("row"),
);
},
);
ui.finish();
to(&mut core, 20.0, 20.0);
let press = core.handle_input(down(MouseButton::Secondary));
assert_eq!(kinds(&press), ["contextmenu"], "the nested declaration");
core.handle_input(up(MouseButton::Secondary));
let press = core.handle_input(down(MouseButton::Middle));
assert_eq!(kinds(&press), ["button"], "the row offers no middle button");
assert_eq!(press[0].payload.get_str("tag"), Some("pane"));
}
#[test]
fn middle_with_no_owner_does_nothing() {
let mut core = Core::new();
let mut ui = core.frame(VIEW, 1.0);
ui.leaf_keyed(
"row",
NodeSpec::row()
.size(100.0, 40.0)
.on_click("go")
.on_context_menu("row"),
);
ui.finish();
to(&mut core, 20.0, 20.0);
assert!(core.handle_input(down(MouseButton::Middle)).is_empty());
assert!(to(&mut core, 30.0, 20.0).is_empty());
assert!(core.handle_input(up(MouseButton::Middle)).is_empty());
assert_eq!(core.focus(), None);
}
#[test]
fn the_primary_button_is_untouched() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, true);
let evs = click_at(&mut core, 20.0, EDIT_Y + 40.0);
assert!(
evs.iter().any(|e| e.payload.as_str() == Some("go")),
"{evs:?}"
);
assert!(evs.iter().all(|e| e.button().is_none()));
assert_eq!(core.key_focus(), Some(k.go));
to(&mut core, 20.0, CHILD_Y);
core.handle_input(down(MouseButton::Middle));
core.handle_input(InputEvent::mouse_down(1));
let moved = to(&mut core, 25.0, CHILD_Y);
assert_eq!(kinds(&moved), ["button"]);
let released = core.handle_input(InputEvent::mouse_up());
assert!(released.iter().all(|e| e.button().is_none()));
assert_eq!(
core.interaction.button_owner(MouseButton::Middle),
Some(k.pane)
);
let released = core.handle_input(up(MouseButton::Middle));
assert_eq!(kinds(&released), ["button"]);
}
#[test]
fn a_non_primary_press_moves_no_focus() {
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, true);
click_at(&mut core, 20.0, EDIT_Y + 40.0);
assert_eq!(core.key_focus(), Some(k.go));
for (x, y) in [(20.0, EDIT_Y), (20.0, TERM_Y), (20.0, CHILD_Y)] {
for b in [MouseButton::Middle, MouseButton::Secondary] {
to(&mut core, x, y);
let press = core.handle_input(down(b));
assert_eq!(kinds(&press), ["button"]);
core.handle_input(up(b));
assert_eq!(core.key_focus(), Some(k.go), "{b:?} at ({x}, {y})");
}
}
}
#[test]
fn a_modal_keeps_its_outside_to_itself() {
let mut core = Core::new();
let mut ui = core.frame(VIEW, 1.0);
ui.configure_root(NodeSpec::column().fill());
ui.leaf_keyed(
"behind",
NodeSpec::row().size(400.0, 100.0).on_button("behind"),
);
ui.leaf_keyed(
"dialog",
NodeSpec::column()
.size(200.0, 100.0)
.modal("dialog")
.label("Dialog")
.on_button("dialog"),
);
ui.finish();
to(&mut core, 20.0, 20.0);
let press = core.handle_input(down(MouseButton::Middle));
assert!(press.iter().all(|e| e.button().is_none()), "{press:?}");
core.handle_input(up(MouseButton::Middle));
to(&mut core, 20.0, 150.0);
let press = core.handle_input(down(MouseButton::Middle));
assert_eq!(kinds(&press), ["button"]);
assert_eq!(press[0].payload.get_str("tag"), Some("dialog"));
}
#[test]
fn an_owner_gone_from_the_frame_lets_go() {
let mut core = Core::new();
frame(&mut core, Buttons::ALL, true);
to(&mut core, 20.0, TERM_Y);
core.handle_input(down(MouseButton::Middle));
let mut ui = core.frame(VIEW, 1.0);
ui.leaf_keyed("other", NodeSpec::row().size(100.0, 40.0));
ui.finish();
assert_eq!(core.interaction.button_owner(MouseButton::Middle), None);
assert!(to(&mut core, 30.0, TERM_Y).is_empty());
assert!(core.handle_input(up(MouseButton::Middle)).is_empty());
}
#[test]
fn buttons_parse_from_their_names() {
assert_eq!(Buttons::parse("middle"), Buttons::MIDDLE);
assert_eq!(
Buttons::parse("secondary middle"),
Buttons::SECONDARY | Buttons::MIDDLE
);
assert_eq!(Buttons::parse("secondary, middle,other"), Buttons::ALL);
assert_eq!(
Buttons::parse("wheel"),
Buttons::NONE,
"a typo claims nothing"
);
assert_eq!(Buttons::default(), Buttons::ALL);
assert_eq!(Buttons::from_bits(2), Buttons::MIDDLE);
assert!(!Buttons::ALL.contains(MouseButton::Primary));
assert!(Buttons::OTHER.contains(MouseButton::Other(7)));
}
fn two_owners(core: &mut Core) {
let mut ui = core.frame(VIEW, 1.0);
ui.configure_root(NodeSpec::row().fill());
for name in ["a", "b"] {
ui.leaf_keyed(
name,
NodeSpec::row()
.size(100.0, 100.0)
.on_button(name)
.buttons(Buttons::MIDDLE),
);
}
ui.finish();
}
fn buttons_of(evs: &[UiEvent]) -> Vec<String> {
evs.iter()
.filter_map(|e| {
let b = e.button()?;
let phase = match b.phase {
ButtonPhase::Press => "press",
ButtonPhase::Move => "move",
ButtonPhase::Release => "release",
};
Some(format!("{phase}:{}", e.tag()?.as_str()?))
})
.collect()
}
#[test]
fn the_window_losing_the_keyboard_ends_every_capture_with_a_release() {
let mut core = Core::new();
two_owners(&mut core);
to(&mut core, 50.0, 50.0);
let press = core.handle_input(down(MouseButton::Middle));
assert_eq!(buttons_of(&press), ["press:a"]);
core.take_pending_events();
core.set_focused(false);
let pending = core.take_pending_events();
assert_eq!(buttons_of(&pending), ["release:a"], "{pending:?}");
assert_eq!(core.interaction.button_owner(MouseButton::Middle), None);
assert!(buttons_of(&to(&mut core, 150.0, 50.0)).is_empty());
assert!(buttons_of(&core.handle_input(up(MouseButton::Middle))).is_empty());
let mut core = Core::new();
let k = frame(&mut core, Buttons::ALL, true);
let w = core.measure_text("M", &mono(), None).width.round();
to(&mut core, 2.5 * w, TERM_Y);
core.handle_input(down(MouseButton::Middle));
core.set_focused(false);
let pending = core.take_pending_events();
let r = pending
.iter()
.find(|e| e.button().is_some())
.expect("the release");
assert_eq!(r.key, k.term);
assert_eq!(r.button().unwrap().phase, ButtonPhase::Release);
assert_eq!(r.button().unwrap().cell, Some((0, 2)));
}
#[test]
fn a_second_press_of_a_held_button_releases_the_first_capture() {
let mut core = Core::new();
two_owners(&mut core);
to(&mut core, 50.0, 50.0);
core.handle_input(down(MouseButton::Middle));
to(&mut core, 150.0, 50.0);
let press = core.handle_input(down(MouseButton::Middle));
assert_eq!(buttons_of(&press), ["release:a", "press:b"], "{press:?}");
assert_eq!(
core.interaction.button_owner(MouseButton::Middle),
core.key_of("b")
);
let moved = to(&mut core, 160.0, 50.0);
assert_eq!(buttons_of(&moved), ["move:b"]);
}