use super::*;
fn ks(s: &str) -> KuiStr {
KuiStr {
ptr: s.as_ptr(),
len: s.len(),
}
}
fn ctx_with_a_scroller_and_a_pending_event() -> *mut KuiCtx {
let ctx = kui_ctx_new();
let mut spec = unsafe { std::mem::zeroed::<KuiSpec>() };
spec.width = KuiSizing {
tag: 2,
value: 100.0,
};
spec.height = KuiSizing {
tag: 2,
value: 50.0,
};
spec.overflow = 1 | 4; kui_frame_begin(ctx, 200.0, 100.0, 1.0);
kui_open_keyed(ctx, ks("scroller"), &spec, kui_value_str(ks("hit")));
kui_close(ctx);
kui_frame_finish(ctx);
kui_input_cursor(ctx, 10.0, 10.0);
kui_input_mouse(ctx, true, 1);
kui_input_mouse(ctx, false, 1);
ctx
}
fn window_cmds(ctx: *mut KuiCtx) -> Vec<KuiWindowCommand> {
let mut out = Vec::new();
let mut cmd = KuiWindowCommand::default();
while kui_take_window_command(ctx, &raw mut cmd) {
out.push(KuiWindowCommand {
size: cmd.size,
kind: cmd.kind,
window: cmd.window,
origin: cmd.origin,
config: cmd.config,
width: cmd.width,
height: cmd.height,
owner: cmd.owner,
});
}
out
}
fn window_events(ctx: *mut KuiCtx) -> Vec<(String, String)> {
let mut out = Vec::new();
let mut ev = KuiEvent::default();
while kui_poll_event(ctx, &raw mut ev) {
let payload = unsafe { &(*ev.payload).0 };
let kind = payload.get_str("kind").unwrap_or("-");
if kind != "window" {
continue;
}
let phase = payload.get_str("phase").unwrap_or("-");
let name = payload.get_str("name").unwrap_or("-");
out.push((phase.to_string(), name.to_string()));
}
out
}
fn frame_declaring(ctx: *mut KuiCtx, declare: &[(f32, f32)]) {
kui_frame_begin(ctx, 320.0, 240.0, 1.0);
for (w, h) in declare {
let cfg = KuiWindowConfig {
kind: KUI_WINDOW_KIND_NORMAL,
width: *w,
height: *h,
activates: 1,
..Default::default()
};
kui_window_declare(ctx, ks("palette"), &cfg);
}
kui_frame_finish(ctx);
}
#[test]
fn a_declared_window_opens_closes_and_warns_through_the_c_api() {
let ctx = kui_ctx_new();
kui_set_diagnostics(ctx, true);
assert_eq!(kui_ctx_window(ctx), 0);
let mut name = KuiStr {
ptr: std::ptr::null(),
len: 0,
};
assert!(kui_ctx_window_name(ctx, &raw mut name));
assert_eq!(&*kstr(name), "main");
frame_declaring(ctx, &[(400.0, 300.0), (500.0, 500.0)]);
let cmds = window_cmds(ctx);
assert_eq!(cmds.len(), 1);
assert_eq!(
(cmds[0].kind, cmds[0].window, cmds[0].origin),
(KUI_CMD_OPEN, 1, 0)
);
assert_eq!(
(
cmds[0].config.width,
cmds[0].config.height,
cmds[0].config.activates
),
(400.0, 300.0, 1),
"the first declaration's config, not the second's"
);
assert_eq!(cmds[0].size, std::mem::size_of::<KuiWindowCommand>() as u32);
assert_eq!(
window_events(ctx),
vec![("opened".into(), "palette".into())]
);
kui_window_closed(ctx, 1);
assert_eq!(
window_events(ctx),
vec![("closed".into(), "palette".into())]
);
frame_declaring(ctx, &[(400.0, 300.0)]);
assert!(window_cmds(ctx).is_empty());
let mut warnings = [KuiWarning {
code: ks(""),
key: 0,
message: ks(""),
}; 8];
let n = kui_take_warnings(ctx, warnings.as_mut_ptr(), 8);
let codes: Vec<String> = warnings[..n]
.iter()
.map(|w| kstr(w.code).into_owned())
.collect();
assert_eq!(
codes,
vec![
"duplicate-window-config".to_string(),
"window-declared-while-closed".to_string()
]
);
frame_declaring(ctx, &[]);
assert!(window_cmds(ctx).is_empty());
frame_declaring(ctx, &[(400.0, 300.0)]);
let cmds = window_cmds(ctx);
assert_eq!(cmds.len(), 1);
assert_eq!((cmds[0].kind, cmds[0].window), (KUI_CMD_OPEN, 2));
frame_declaring(ctx, &[]);
let cmds = window_cmds(ctx);
assert_eq!(cmds.len(), 1);
assert_eq!((cmds[0].kind, cmds[0].window), (KUI_CMD_CLOSE, 2));
assert_eq!(
window_events(ctx),
vec![
("opened".into(), "palette".into()),
("closed".into(), "palette".into())
]
);
kui_ctx_free(ctx);
}
#[test]
fn a_short_window_command_reservation_is_refused_and_keeps_the_command() {
let ctx = kui_ctx_new();
frame_declaring(ctx, &[(400.0, 300.0)]);
let mut short = KuiWindowCommand {
size: 4,
kind: 0xdead,
..Default::default()
};
assert!(!kui_take_window_command(ctx, &raw mut short));
assert_eq!(short.kind, 0xdead, "nothing was written");
let cmds = window_cmds(ctx);
assert_eq!(cmds.len(), 1, "the refused command is still queued");
assert_eq!(cmds[0].kind, KUI_CMD_OPEN);
kui_ctx_free(ctx);
}
#[test]
fn env_set_window_names_the_context_and_stamps_its_events() {
let ctx = kui_ctx_new();
frame_declaring(ctx, &[(400.0, 300.0)]);
window_cmds(ctx);
window_events(ctx);
kui_env_set_window(ctx, 1, false, false, false, 0.0, 0.0);
assert_eq!(kui_ctx_window(ctx), 1);
let mut name = KuiStr {
ptr: std::ptr::null(),
len: 0,
};
assert!(kui_ctx_window_name(ctx, &raw mut name));
assert_eq!(&*kstr(name), "palette");
kui_frame_begin(ctx, 200.0, 100.0, 1.0);
kui_window_declare(ctx, ks("palette"), std::ptr::null());
kui_frame_finish(ctx);
let mut ev = KuiEvent::default();
assert!(kui_poll_event(ctx, &raw mut ev));
assert_eq!(ev.window, 1);
kui_ctx_free(ctx);
}
#[test]
fn the_exported_version_is_the_one_the_header_states() {
assert_eq!(kui_abi_version(), KUI_ABI_VERSION);
}
#[test]
fn a_short_reservation_is_filled_only_as_far_as_it_goes() {
#[repr(C)]
#[derive(Clone, Copy)]
struct Grown {
size: u32,
was_always_here: u32,
appended_later: u32,
}
unsafe impl OutParam for Grown {
const ABI_V1_SIZE: u32 = abi_through!(Grown, was_always_here, u32);
fn size_mut(&mut self) -> &mut u32 {
&mut self.size
}
}
let mut old = Grown {
size: Grown::ABI_V1_SIZE,
was_always_here: 0,
appended_later: 0xdeadbeef,
};
assert!(write_out(
&raw mut old,
Grown {
size: 0,
was_always_here: 7,
appended_later: 9,
},
));
assert_eq!(old.was_always_here, 7);
assert_eq!(
old.appended_later, 0xdeadbeef,
"the library wrote past what the caller reserved"
);
assert_eq!(old.size, Grown::ABI_V1_SIZE, "size reports what was filled");
let mut new = Grown {
size: std::mem::size_of::<Grown>() as u32,
was_always_here: 0,
appended_later: 0,
};
assert!(write_out(
&raw mut new,
Grown {
size: 0,
was_always_here: 7,
appended_later: 9,
},
));
assert_eq!((new.was_always_here, new.appended_later), (7, 9));
assert_eq!(new.size, std::mem::size_of::<Grown>() as u32);
assert!(write_out(
&raw mut old,
Grown {
size: 0,
was_always_here: 11,
appended_later: 0,
},
));
assert_eq!((old.size, old.was_always_here), (Grown::ABI_V1_SIZE, 11));
assert_eq!(old.appended_later, 0xdeadbeef);
}
#[test]
fn an_abi_3_host_polls_events_without_seeing_the_appended_window() {
let ctx = ctx_with_a_scroller_and_a_pending_event();
let abi3 = KuiEvent::ABI_V1_SIZE;
assert!(
(abi3 as usize) < std::mem::size_of::<KuiEvent>(),
"`window` must sit past the ABI-1 layout, or this proves nothing"
);
let mut ev = KuiEvent {
size: abi3,
window: 0xdead,
..Default::default()
};
assert!(kui_poll_event(ctx, &raw mut ev), "the event still arrives");
assert_ne!(ev.key, 0, "and the fields it knows are filled");
assert!(!ev.payload.is_null());
assert_eq!(ev.size, abi3, "`size` reports the prefix that was filled");
assert_eq!(
ev.window, 0xdead,
"the library wrote past what an ABI 3 host reserved"
);
kui_ctx_free(ctx);
}
#[test]
fn a_current_host_sees_window_and_it_is_the_main_one() {
let ctx = ctx_with_a_scroller_and_a_pending_event();
let mut ev = KuiEvent::default();
assert!(kui_poll_event(ctx, &raw mut ev));
assert_eq!(ev.window, 0);
assert_eq!(ev.size, std::mem::size_of::<KuiEvent>() as u32);
kui_ctx_free(ctx);
}
#[test]
fn an_unreadable_reservation_refuses_without_dropping_the_event() {
let ctx = ctx_with_a_scroller_and_a_pending_event();
let mut stale = KuiEvent {
size: 4,
..Default::default()
};
assert!(!kui_poll_event(ctx, &raw mut stale));
assert_eq!(stale.key, 0, "nothing was written");
let mut ev = KuiEvent::default();
assert!(
kui_poll_event(ctx, &raw mut ev),
"the event was not dropped"
);
assert!(!ev.payload.is_null());
kui_ctx_free(ctx);
}
#[test]
fn every_out_param_refuses_a_reservation_it_cannot_honour() {
let ctx = ctx_with_a_scroller_and_a_pending_event();
let scroller = kui_child_key(ctx, ks("scroller"));
let mut geom = KuiScrollGeometry::default();
assert!(kui_scroll_geometry(ctx, scroller, &raw mut geom));
assert!(geom.h > 0.0);
let mut short = KuiScrollGeometry {
size: 2,
..Default::default()
};
assert!(!kui_scroll_geometry(ctx, scroller, &raw mut short));
assert_eq!(short.h, 0.0, "nothing was written");
let mut m = KuiTextMetrics {
size: 0,
..Default::default()
};
let style: KuiTextStyle = unsafe { std::mem::zeroed() };
assert!(!kui_measure_text(ctx, ks("hello"), &style, 0.0, &raw mut m));
assert_eq!(m.width, 0.0);
let mut draw = KuiDrawData {
size: 1,
..Default::default()
};
assert!(!kui_draw_data(ctx, &raw mut draw));
assert!(draw.quads.is_null());
let mut good = KuiDrawData::default();
assert!(kui_draw_data(ctx, &raw mut good));
assert_eq!(good.size, std::mem::size_of::<KuiDrawData>() as u32);
assert!(!kui_poll_event(ctx, std::ptr::null_mut()));
assert!(!kui_draw_data(ctx, std::ptr::null_mut()));
kui_ctx_free(ctx);
}