use kui_native::testing::Drive;
use kui_native::{App, Core, KeyPhase, NodeSpec, TextStyle, Ui, UiEvent, Vec2};
#[derive(Default)]
struct Pad {
clicks: u32,
typed: String,
split: f32,
drags: Vec<&'static str>,
}
impl App for Pad {
fn view(&mut self, ui: &mut Ui<'_>) {
ui.with(NodeSpec::column().fill(), |ui| {
ui.text_in_keyed(
"add",
NodeSpec::row().size(80.0, 30.0).on_click("add"),
&format!("clicked {}", self.clicks),
TextStyle::new(12.0),
);
ui.leaf_keyed("sink", NodeSpec::row().size(80.0, 30.0).on_key("keys"));
ui.with(NodeSpec::row().size(200.0, 40.0), |ui| {
ui.leaf_keyed("bar", NodeSpec::row().size(4.0, 40.0).on_drag("bar"));
});
});
}
fn on_event(&mut self, ev: UiEvent) {
if let Some(d) = ev.drag() {
self.split = d.ratio().x;
self.drags.push(match d.phase {
kui_native::DragPhase::Start => "start",
kui_native::DragPhase::Move => "move",
kui_native::DragPhase::End => "end",
});
} else if let Some((KeyPhase::Down, k)) = ev.key_press() {
self.typed.extend(k.text);
} else if ev.payload.as_str() == Some("add") {
self.clicks += 1;
}
}
}
#[test]
fn an_owned_drive_clicks_types_drags_and_reads_back() {
let mut app = Pad::default();
let mut d = Drive::new(Core::new(), 400.0, 300.0).framing();
d.frame(&mut app);
let add = d.key_of("add").expect("the button");
d.click_key(&mut app, add);
assert_eq!(app.clicks, 1);
assert_eq!(
d.texts_under("add"),
["clicked 1"],
"framed after the click"
);
let r = d.rect_of(add).expect("a rect");
d.click(&mut app, r.center().x, r.center().y);
assert_eq!(app.clicks, 2, "a pointer click at its middle");
let sink = d.key_of("sink").expect("the sink");
d.focus(&mut app, sink);
d.keys(&mut app, "ab c");
assert_eq!(app.typed, "ab c", "each press carries its text, space too");
let bar = d.key_of("bar").unwrap();
let bar = d.rect_of(bar).unwrap();
d.drag(&mut app, bar.center(), Vec2::new(150.0, bar.center().y));
assert_eq!(app.drags.first(), Some(&"start"));
assert_eq!(app.drags.last(), Some(&"end"));
assert!((app.split - 0.75).abs() < 0.01, "150 of 200: {}", app.split);
assert!(d.warnings().is_empty(), "{:?}", d.warnings());
assert!(d.frames() > 5, "framing frames after each gesture");
assert!(d.log().iter().any(|l| l.contains("add")), "{:?}", d.log());
}
#[test]
fn a_borrowing_drive_frames_only_when_asked() {
let mut app = Pad::default();
let mut core = Core::new();
let mut d = Drive::new(&mut core, 400.0, 300.0);
d.frame(&mut app);
let add = d.key_of("add").unwrap();
d.click_key(&mut app, add);
assert_eq!(app.clicks, 1);
assert_eq!(d.texts_under("add"), ["clicked 0"], "no frame since");
d.frame(&mut app);
assert_eq!(d.texts_under("add"), ["clicked 1"]);
assert_eq!(d.frames(), 2);
}
#[test]
fn a_handler_acts_on_the_core_its_event_came_from() {
use kui_native::MenuAction;
struct Copier;
impl App for Copier {
fn view(&mut self, ui: &mut Ui<'_>) {
ui.with(NodeSpec::row(), |ui| {
ui.leaf_keyed("copy", NodeSpec::row().size(80.0, 30.0).on_click("copy"));
ui.leaf_keyed("sink", NodeSpec::row().size(80.0, 30.0).on_key("keys"));
});
}
fn on_event_with(&mut self, ev: UiEvent, core: &mut Core) {
if ev.payload.as_str() == Some("copy") {
core.set_clipboard("copied", None);
let sink = core.key_of("sink");
core.set_key_focus(sink);
}
}
}
let mut app = Copier;
let mut d = Drive::new(Core::new(), 400.0, 300.0);
d.frame(&mut app);
let copy = d.key_of("copy").unwrap();
d.click_key(&mut app, copy);
let actions = d.core.take_menu_actions();
assert!(
actions
.iter()
.any(|a| matches!(a, MenuAction::SetClipboard { text, .. } if text == "copied")),
"the write goes out with this turn: {actions:?}"
);
d.frame(&mut app);
let sink = d.key_of("sink");
assert_eq!(
d.core.key_focus(),
sink,
"the focus lands for the next frame"
);
}
#[test]
fn a_handlers_focus_move_is_reported_as_the_programs() {
struct Mover;
impl App for Mover {
fn view(&mut self, ui: &mut Ui<'_>) {
ui.with(NodeSpec::row(), |ui| {
ui.leaf_keyed("go", NodeSpec::row().size(80.0, 30.0).on_click("go"));
ui.with_keyed("pane", NodeSpec::row().on_focus("pane"), |ui| {
ui.leaf_keyed("sink", NodeSpec::row().size(80.0, 30.0).on_key("keys"));
});
});
}
fn on_event_with(&mut self, ev: UiEvent, core: &mut Core) {
if ev.payload.as_str() == Some("go") {
let sink = core.key_of("sink");
core.set_key_focus(sink);
}
}
}
let mut app = Mover;
let mut d = Drive::new(Core::new(), 400.0, 300.0);
d.frame(&mut app);
d.frame(&mut app);
d.click(&mut app, 40.0, 15.0);
let evs = d.move_to(&mut app, 300.0, 200.0);
let by: Vec<_> = evs
.iter()
.filter(|e| e.kind() == Some("focus"))
.map(|e| {
let phase = e.payload.get_str("phase").unwrap();
format!("{phase}:{}", e.payload.get_str("by").unwrap())
})
.collect();
assert_eq!(by, ["in:program"]);
}
#[test]
fn the_plain_handler_still_hears_everything() {
let mut app = Pad::default();
let mut d = Drive::new(Core::new(), 400.0, 300.0);
d.frame(&mut app);
let add = d.key_of("add").unwrap();
d.click_key(&mut app, add);
assert_eq!(app.clicks, 1);
}
#[test]
fn a_drive_presses_the_middle_button_and_scrolls_a_gesture() {
use kui_native::{Buttons, MouseButton};
#[derive(Default)]
struct Pane {
heard: Vec<String>,
}
impl App for Pane {
fn view(&mut self, ui: &mut Ui<'_>) {
ui.with_keyed(
"page",
NodeSpec::column().size(200.0, 100.0).scroll_y(),
|ui| {
ui.leaf_keyed(
"pane",
NodeSpec::row()
.size(200.0, 400.0)
.on_button("pane")
.buttons(Buttons::MIDDLE),
);
},
);
}
fn on_event(&mut self, ev: UiEvent) {
if let Some(b) = ev.button() {
self.heard.push(format!("{:?}", b.phase));
}
}
}
let mut app = Pane::default();
let mut d = Drive::new(Core::new(), 400.0, 300.0).framing();
d.frame(&mut app);
d.frame(&mut app);
d.button_click(&mut app, 50.0, 50.0, MouseButton::Middle);
assert_eq!(app.heard, ["Press", "Release"]);
d.scroll_gesture(&mut app, 50.0, 50.0, Vec2::new(0.0, -20.0), true);
d.scroll_gesture(&mut app, 50.0, 50.0, Vec2::new(0.0, -20.0), false);
let page = d.key_of("page").unwrap();
assert_eq!(d.core.scroll_offset(page), Vec2::new(0.0, 40.0));
}
#[test]
fn a_view_reads_why_its_frame_runs() {
use kui_native::{FrameCause, KeyMods};
#[derive(Default)]
struct Ledger {
ask: bool,
asked_at: u32,
seen: Vec<(FrameCause, Vec<(&'static str, u32)>)>,
}
impl App for Ledger {
fn view(&mut self, ui: &mut Ui<'_>) {
let core = ui.core();
let asks = core
.owed_by()
.requests
.iter()
.map(|r| (r.at.file(), r.at.line()))
.collect();
self.seen.push((core.frame_cause(), asks));
ui.leaf_keyed("sink", NodeSpec::row().size(80.0, 30.0).on_key("keys"));
if std::mem::take(&mut self.ask) {
self.asked_at = line!() + 1;
ui.request_frame();
}
}
}
let mut app = Ledger::default();
let mut d = Drive::new(Core::new(), 200.0, 100.0);
d.core.set_frame_trace(true);
d.frame(&mut app);
let sink = d.key_of("sink").expect("the sink");
d.focus(&mut app, sink);
d.frame(&mut app);
d.key(&mut app, "a", KeyMods::default());
app.ask = true;
d.frame(&mut app);
d.frame(&mut app);
let (cause, _) = &app.seen[2];
assert!(cause.contains(FrameCause::KEY), "{cause:?}");
let (cause, asks) = &app.seen[3];
assert_eq!(*cause, FrameCause::OWED);
assert_eq!(asks.len(), 1);
assert!(asks[0].0.ends_with("drive.rs"), "{asks:?}");
assert_eq!(asks[0].1, app.asked_at);
}