use kui_core::testing::{click_at, kinds};
use kui_core::{
Core, EditOptions, InputEvent, Key, MouseButton, NodeSpec, Size, TextStyle, UiEvent, Vec2,
};
const W: f32 = 200.0;
const H: f32 = 200.0;
fn secondary_down() -> InputEvent {
InputEvent::MouseDown {
button: MouseButton::Secondary,
clicks: 1,
}
}
fn secondary_up() -> InputEvent {
InputEvent::MouseUp {
button: MouseButton::Secondary,
}
}
fn right_click(core: &mut Core, x: f32, y: f32) -> Vec<UiEvent> {
let mut out = core.handle_input(InputEvent::CursorMoved(Vec2::new(x, y)));
out.extend(core.handle_input(secondary_down()));
out.extend(core.handle_input(secondary_up()));
out
}
struct Keys {
panel: Key,
row: Key,
edit: Key,
}
fn frame(core: &mut Core) -> Keys {
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill());
let row = || NodeSpec::row().size(100.0, 40.0);
let mut keys = Keys {
panel: Key::ROOT,
row: Key::ROOT,
edit: Key::ROOT,
};
keys.panel = ui.with_keyed(
"panel",
NodeSpec::column().fill().gap(0.0).on_context_menu("panel"),
|ui| {
keys.row = ui.leaf_keyed("row", row().on_click("open").on_context_menu("row"));
ui.leaf_keyed("off", row().disabled(true).on_context_menu("off"));
keys.edit = ui.text_edit(
"note",
"hello world",
&EditOptions {
style: TextStyle::new(13.0),
..Default::default()
},
row(),
);
},
);
ui.finish();
keys
}
#[test]
fn a_secondary_press_emits_the_menu_where_it_landed() {
let mut core = Core::new();
let k = frame(&mut core);
let evs = right_click(&mut core, 30.0, 20.0);
assert_eq!(kinds(&evs), ["contextmenu"]);
assert_eq!(evs[0].key, k.row, "the topmost node is the one asked");
assert_eq!(evs[0].payload.get("tag").unwrap().as_str(), Some("row"));
assert_eq!(evs[0].payload.get("x").unwrap().as_float(), Some(30.0));
assert_eq!(evs[0].payload.get("y").unwrap().as_float(), Some(20.0));
}
#[test]
fn the_panel_answers_where_no_child_does() {
let mut core = Core::new();
let k = frame(&mut core);
let evs = right_click(&mut core, 30.0, 180.0);
assert_eq!(kinds(&evs), ["contextmenu"]);
assert_eq!(evs[0].key, k.panel);
assert_eq!(evs[0].payload.get("tag").unwrap().as_str(), Some("panel"));
}
#[test]
fn a_secondary_press_is_never_a_click() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(kinds(&right_click(&mut core, 30.0, 20.0)), ["contextmenu"]);
assert_eq!(kinds(&click_at(&mut core, 30.0, 20.0)), ["open"]);
}
#[test]
fn a_disabled_node_offers_nothing_and_the_panel_answers() {
let mut core = Core::new();
let k = frame(&mut core);
let evs = right_click(&mut core, 30.0, 60.0);
assert_eq!(kinds(&evs), ["contextmenu"]);
assert_eq!(evs[0].key, k.panel);
assert_eq!(evs[0].payload.get("tag").unwrap().as_str(), Some("panel"));
}
#[test]
fn a_child_without_a_menu_reaches_the_enclosing_one() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill().on_context_menu("shell"));
let sink = ui.with_keyed("sink", NodeSpec::column().fill().on_key("keys"), |ui| {
ui.leaf_keyed("button", NodeSpec::row().size(100.0, 40.0).on_click("open"));
});
ui.finish();
let evs = right_click(&mut core, 150.0, 150.0);
assert_eq!(kinds(&evs), ["contextmenu"]);
assert_eq!(evs[0].key, Key::ROOT);
assert_eq!(evs[0].payload.get("tag").unwrap().as_str(), Some("shell"));
assert_eq!(evs[0].payload.get("x").unwrap().as_float(), Some(150.0));
assert_ne!(evs[0].key, sink);
let evs = right_click(&mut core, 30.0, 20.0);
assert_eq!(kinds(&evs), ["contextmenu"]);
assert_eq!(evs[0].key, Key::ROOT);
assert_eq!(kinds(&click_at(&mut core, 30.0, 20.0)), ["open"]);
}
#[test]
fn the_walk_stops_at_the_modal_boundary() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill().on_context_menu("shell"));
ui.with_keyed(
"dialog",
NodeSpec::column()
.float(kui_core::FloatConfig::viewport())
.size(80.0, 40.0)
.modal("dialog"),
|ui| {
ui.leaf_keyed("ok", NodeSpec::row().size(40.0, 20.0).on_click("ok"));
},
);
ui.finish();
assert!(right_click(&mut core, 10.0, 10.0).is_empty());
assert!(right_click(&mut core, 70.0, 35.0).is_empty());
}
#[test]
fn a_secondary_press_moves_neither_focus_nor_the_caret() {
let mut core = Core::new();
let k = frame(&mut core);
core.handle_input(InputEvent::CursorMoved(Vec2::new(20.0, 86.0)));
core.handle_input(InputEvent::mouse_down(2));
core.handle_input(InputEvent::mouse_up());
frame(&mut core);
assert_eq!(core.focus(), Some(k.edit));
let selected = core.copy_selection();
assert_eq!(selected.as_deref(), Some("hello"));
let evs = right_click(&mut core, 30.0, 20.0);
assert_eq!(kinds(&evs), ["contextmenu"]);
assert_eq!(core.focus(), Some(k.edit), "focus stayed in the editor");
assert_eq!(core.copy_selection(), selected, "and so did its selection");
right_click(&mut core, 60.0, 86.0);
assert_eq!(core.focus(), Some(k.edit));
assert_eq!(core.copy_selection(), selected);
right_click(&mut core, 30.0, 180.0);
assert_eq!(core.focus(), Some(k.edit));
assert_eq!(core.copy_selection(), selected);
click_at(&mut core, 30.0, 180.0);
assert_eq!(core.focus(), None);
}
#[test]
fn a_secondary_press_holds_nothing() {
let mut core = Core::new();
let k = frame(&mut core);
core.handle_input(InputEvent::CursorMoved(Vec2::new(30.0, 20.0)));
core.handle_input(secondary_down());
assert!(!core.interaction.is_pressed(k.row));
let out = core.handle_input(InputEvent::CursorMoved(Vec2::new(30.0, 180.0)));
assert!(out.is_empty());
let out = core.handle_input(secondary_up());
assert!(out.is_empty());
}
#[test]
fn a_secondary_press_does_not_break_a_drag() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill());
ui.leaf_keyed(
"handle",
NodeSpec::row()
.size(100.0, 40.0)
.on_drag("h")
.on_context_menu("h"),
);
ui.finish();
core.handle_input(InputEvent::CursorMoved(Vec2::new(20.0, 20.0)));
let out = core.handle_input(InputEvent::mouse_down(1));
assert_eq!(kinds(&out), ["drag"]);
let out = core.handle_input(secondary_down());
assert_eq!(kinds(&out), ["contextmenu"]);
let out = core.handle_input(secondary_up());
assert!(out.is_empty());
let out = core.handle_input(InputEvent::CursorMoved(Vec2::new(60.0, 20.0)));
assert_eq!(kinds(&out), ["drag"]);
let out = core.handle_input(InputEvent::mouse_up());
assert_eq!(kinds(&out), ["drag"]);
assert_eq!(
out[0].payload.get("phase").unwrap().as_str(),
Some("end"),
"the primary release is what ends the drag"
);
}
#[test]
fn the_middle_button_routes_nowhere() {
let mut core = Core::new();
let k = frame(&mut core);
core.handle_input(InputEvent::CursorMoved(Vec2::new(30.0, 20.0)));
let out = core.handle_input(InputEvent::MouseDown {
button: MouseButton::Middle,
clicks: 1,
});
assert!(out.is_empty());
let out = core.handle_input(InputEvent::MouseUp {
button: MouseButton::Middle,
});
assert!(out.is_empty());
assert_eq!(core.focus(), None);
assert!(!core.interaction.is_pressed(k.row));
}
#[test]
fn a_modal_contains_and_dismisses_on_secondary_presses() {
let mut core = Core::new();
let menu = |core: &mut Core| {
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill());
let behind = ui.leaf_keyed(
"behind",
NodeSpec::row().size(W, 100.0).on_context_menu("behind"),
);
let menu = ui.leaf_keyed(
"menu",
NodeSpec::column()
.float(kui_core::FloatConfig::viewport())
.size(80.0, 40.0)
.modal("menu")
.on_context_menu("menu"),
);
ui.finish();
(behind, menu)
};
let (_, menu_key) = menu(&mut core);
let evs = right_click(&mut core, 20.0, 20.0);
assert_eq!(kinds(&evs), ["contextmenu"]);
assert_eq!(evs[0].key, menu_key);
let evs = right_click(&mut core, 20.0, 80.0);
assert_eq!(kinds(&evs), ["dismiss"]);
assert_eq!(evs[0].key, menu_key);
assert_eq!(
evs[0].payload.get("reason").unwrap().as_str(),
Some("outside")
);
}