use kui_core::{Core, CursorShape, EditOptions, InputEvent, NodeSpec, Size, Vec2, widgets};
const W: f32 = 400.0;
const H: f32 = 400.0;
const BAND: f32 = 40.0;
fn in_band(i: usize) -> Vec2 {
Vec2::new(W / 2.0, i as f32 * BAND + BAND / 2.0)
}
fn band(spec: NodeSpec) -> NodeSpec {
spec.grow_width().height(BAND)
}
fn frame(core: &mut Core) {
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill());
ui.text_edit(
"doc",
"hello",
&EditOptions::default(),
band(NodeSpec::column()),
);
ui.leaf_keyed(
"button",
band(NodeSpec::column())
.on_click("go")
.cursor(CursorShape::Pointer),
);
ui.leaf_keyed("plain", band(NodeSpec::column()));
ui.leaf_keyed(
"handle",
band(NodeSpec::column())
.on_drag("h")
.cursor(CursorShape::Grab),
);
ui.leaf_keyed(
"splitter",
band(NodeSpec::column())
.on_drag("s")
.cursor(CursorShape::EwResize),
);
ui.leaf_keyed(
"refused",
band(NodeSpec::column())
.on_click("nope")
.disabled(true)
.cursor(CursorShape::NotAllowed),
);
ui.leaf_keyed(
"quiet",
band(NodeSpec::column()).on_click("nope").disabled(true),
);
ui.leaf_keyed("titlebar", band(NodeSpec::column()).window_drag());
ui.leaf_keyed("row", band(NodeSpec::column()).focusable());
ui.leaf_keyed("badge", band(NodeSpec::column()).hoverable());
ui.leaf_keyed("silent", band(NodeSpec::column()).on_click("go"));
ui.leaf_keyed("mute", band(NodeSpec::column()).on_drag("m"));
ui.finish();
}
fn shape_over(core: &mut Core, i: usize) -> CursorShape {
core.handle_input(InputEvent::CursorMoved(in_band(i)));
core.cursor_shape()
}
#[test]
fn editor_is_a_caret_a_declared_hand_a_hand_plain_box_an_arrow() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(shape_over(&mut core, 0), CursorShape::Text, "an editor");
assert_eq!(shape_over(&mut core, 1), CursorShape::Pointer, "declared");
assert_eq!(
shape_over(&mut core, 2),
CursorShape::Default,
"a plain box"
);
}
#[test]
fn a_node_that_declared_nothing_is_the_arrow_whatever_it_does() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(shape_over(&mut core, 10), CursorShape::Default, "on_click");
assert_eq!(shape_over(&mut core, 8), CursorShape::Default, "focusable");
assert_eq!(shape_over(&mut core, 11), CursorShape::Default, "on_drag");
assert_eq!(shape_over(&mut core, 9), CursorShape::Default, "hoverable");
shape_over(&mut core, 11);
core.handle_input(InputEvent::mouse_down(1));
core.handle_input(InputEvent::CursorMoved(in_band(0)));
assert_eq!(
core.cursor_shape(),
CursorShape::Default,
"captured over the editor, and still not its I-beam"
);
core.handle_input(InputEvent::mouse_up());
}
#[test]
fn the_stock_button_declares_the_hand() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill());
let (t, m) = (ui.theme(), ui.metrics());
let band = |ui: &mut kui_core::Ui<'_>, body: &dyn Fn(&mut kui_core::Ui<'_>)| {
ui.with(
NodeSpec::column().grow_width().height(BAND).center(),
|ui| body(ui),
);
};
band(&mut ui, &|ui| widgets::button(ui, "Go", "go"));
band(&mut ui, &|ui| {
widgets::button_with(
ui,
"wait",
"Wait",
widgets::button_spec(&t, &m)
.on_click("wait")
.cursor(CursorShape::NotAllowed),
None,
)
});
band(&mut ui, &|ui| {
widgets::button_with(
ui,
"off",
"Off",
widgets::button_spec(&t, &m).on_click("off").disabled(true),
None,
)
});
ui.finish();
for (i, want) in [
CursorShape::Pointer,
CursorShape::NotAllowed,
CursorShape::Default,
]
.into_iter()
.enumerate()
{
core.handle_input(InputEvent::CursorMoved(in_band(i)));
assert_eq!(core.cursor_shape(), want, "band {i}");
}
}
#[test]
fn window_chrome_keeps_the_platform_arrow() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(shape_over(&mut core, 7), CursorShape::Default);
}
#[test]
fn no_pointer_is_the_arrow() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(
core.cursor_shape(),
CursorShape::Default,
"before any input"
);
shape_over(&mut core, 0);
core.handle_input(InputEvent::CursorLeft);
assert_eq!(core.cursor_shape(), CursorShape::Default, "pointer gone");
}
#[test]
fn a_drag_source_holds_its_declared_shape_through_the_drag() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(shape_over(&mut core, 3), CursorShape::Grab, "resting");
core.handle_input(InputEvent::mouse_down(1));
assert_eq!(
core.cursor_shape(),
CursorShape::Grab,
"pressed: as declared"
);
core.handle_input(InputEvent::CursorMoved(in_band(2)));
assert_eq!(core.cursor_shape(), CursorShape::Grab, "off the node");
core.handle_input(InputEvent::mouse_up());
assert_eq!(
core.cursor_shape(),
CursorShape::Default,
"over the box now"
);
assert_eq!(shape_over(&mut core, 3), CursorShape::Grab, "back to rest");
}
#[test]
fn a_splitter_declares_its_own_arrows_and_keeps_them_captured() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(shape_over(&mut core, 4), CursorShape::EwResize, "resting");
core.handle_input(InputEvent::mouse_down(1));
core.handle_input(InputEvent::CursorMoved(in_band(2)));
assert_eq!(
core.cursor_shape(),
CursorShape::EwResize,
"the captured drag keeps the splitter's own shape"
);
core.handle_input(InputEvent::mouse_up());
}
#[test]
fn a_disabled_node_says_nothing_unless_it_declares_a_cursor() {
let mut core = Core::new();
frame(&mut core);
assert_eq!(
shape_over(&mut core, 5),
CursorShape::NotAllowed,
"declared"
);
assert_eq!(shape_over(&mut core, 6), CursorShape::Default, "quiet");
}
#[test]
fn the_topmost_node_answers() {
let mut core = Core::new();
let mut ui = core.frame(Size::new(W, H), 1.0);
ui.configure_root(NodeSpec::column().fill());
ui.with_keyed(
"card",
NodeSpec::column().fill().pad(20.0).hoverable(),
|ui| {
ui.leaf_keyed(
"button",
NodeSpec::column()
.size(100.0, 30.0)
.on_click("go")
.cursor(CursorShape::Pointer),
);
},
);
ui.finish();
core.handle_input(InputEvent::CursorMoved(Vec2::new(50.0, 30.0)));
assert_eq!(core.cursor_shape(), CursorShape::Pointer, "over the button");
core.handle_input(InputEvent::CursorMoved(Vec2::new(50.0, 200.0)));
assert_eq!(core.cursor_shape(), CursorShape::Default, "over the card");
}