use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn kui_slot(ptr: *mut KuiCtx, name: KuiStr, params: *const KuiValue) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let name = kstr(name);
let params = unsafe { params.as_ref() }.map_or(&kui_core::Value::Null, |v| &v.0);
with_host_ui(c, |ui| ui.slot_with(&name, params))
})
}
fn with_host_ui<T>(c: &mut KuiCtx, f: impl FnOnce(&mut kui_core::Ui<'_>) -> T) -> T {
if c.host_ui.is_null() {
let mut local = kui_core::Ui::with_filler(unsafe { &mut *c.core }, &mut c.extensions);
f(&mut local)
} else {
f(unsafe { &mut *c.host_ui.cast() })
}
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_slot_kept(ptr: *mut KuiCtx, name: KuiStr, params: *const KuiValue) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let name = kstr(name);
let params = unsafe { params.as_ref() }.map_or(&kui_core::Value::Null, |v| &v.0);
with_host_ui(c, |ui| ui.slot_kept(&name, params))
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_slot_replay(ptr: *mut KuiCtx, name: KuiStr, params: *const KuiValue) -> i32 {
guard(-1, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return -1;
};
let name = kstr(name);
let params = unsafe { params.as_ref() }.map_or(&kui_core::Value::Null, |v| &v.0);
with_host_ui(c, |ui| ui.slot_replay(&name, params)).map_or(-1, |f| f.code())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_slot_fill(ptr: *mut KuiCtx, name: KuiStr) -> i32 {
guard(-1, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return -1;
};
let name = kstr(name);
unsafe { &*c.core }
.slot_fill(&name)
.map_or(-1, |f| f.code())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_slot_name(ptr: *mut KuiCtx, out: *mut KuiStr) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let (Some(name), Some(out)) = (c.slot_name.as_deref(), unsafe { out.as_mut() }) else {
return false;
};
*out = KuiStr {
ptr: name.as_ptr(),
len: name.len(),
};
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_slot_namespace(ptr: *mut KuiCtx, out: *mut KuiStr) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let (Some(ns), Some(out)) = (c.slot_namespace.as_deref(), unsafe { out.as_mut() }) else {
return false;
};
*out = KuiStr {
ptr: ns.as_ptr(),
len: ns.len(),
};
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_slot_params(ptr: *mut KuiCtx) -> *const KuiValue {
guard(std::ptr::null(), || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return std::ptr::null();
};
c.slot_params
.as_ref()
.map_or(std::ptr::null(), |v| v as *const KuiValue)
})
}
#[repr(C)]
struct HostSink {
head: KuiReplySink,
replies: Vec<Value>,
}
unsafe extern "C" fn push_reply(sink: *mut KuiReplySink, value: *const KuiValue) -> bool {
guard(false, || {
let (Some(sink), Some(value)) = (unsafe { sink.cast::<HostSink>().as_mut() }, unsafe {
value.as_ref()
}) else {
return false;
};
sink.replies.push(value.0.clone());
true
})
}
pub(crate) fn collect_replies(ev: &mut KuiEvent, cb: impl FnOnce(&KuiEvent)) -> Vec<Value> {
let mut sink = HostSink {
head: KuiReplySink {
push: Some(push_reply),
},
replies: Vec::new(),
};
ev.reply_sink = (&raw mut sink).cast::<KuiReplySink>();
cb(ev);
ev.reply_sink = std::ptr::null_mut();
sink.head.push = None;
sink.replies
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_reply(ev: *const KuiEvent, reply: *const KuiValue) -> bool {
guard(false, || {
let (Some(ev), Some(reply)) = (unsafe { ev.as_ref() }, unsafe { reply.as_ref() }) else {
return false;
};
let sink = ev.reply_sink;
let Some(push) = (unsafe { sink.as_ref() }).and_then(|s| s.push) else {
return false;
};
unsafe { push(sink, reply) }
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_ctx_add_extension(ptr: *mut KuiCtx, namespace: KuiStr, path: KuiStr) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
c.last_ext_error.clear();
if !c.host_ui.is_null() {
c.last_ext_error = "cannot load an extension into a context that borrows a frame: \
its slots are filled from the list one level up. Load into a \
context of your own, before `kui_run_with`"
.to_owned();
return false;
}
let loaded = unsafe { crate::CExtension::open(&*kstr(path)) }
.and_then(|ext| c.extensions.push_as(kstr(namespace), Box::new(ext)));
if let Err(err) = loaded {
c.last_ext_error = err;
}
c.last_ext_error.is_empty()
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_ctx_extension_error(ptr: *mut KuiCtx, out: *mut KuiStr) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let Some(out) = (unsafe { out.as_mut() }) else {
return false;
};
if c.last_ext_error.is_empty() {
return false;
}
*out = KuiStr {
ptr: c.last_ext_error.as_ptr(),
len: c.last_ext_error.len(),
};
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_ctx_extension_count(ptr: *mut KuiCtx) -> u32 {
guard(0, || {
unsafe { ctx(ptr) }.map_or(0, |c| c.extensions.len() as u32)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_ctx_extension_namespace(
ptr: *mut KuiCtx,
origin: u16,
out: *mut KuiStr,
) -> bool {
guard(false, || {
let (Some(c), Some(out)) = (unsafe { ctx(ptr) }, unsafe { out.as_mut() }) else {
return false;
};
let Some(ns) = c.extensions.namespace_of(kui_core::OriginId(origin)) else {
return false;
};
*out = KuiStr {
ptr: ns.as_ptr(),
len: ns.len(),
};
true
})
}
#[cfg(test)]
mod tests {
use super::*;
fn ks(s: &str) -> KuiStr {
KuiStr {
ptr: s.as_ptr(),
len: s.len(),
}
}
#[test]
fn a_host_slot_is_declared_and_named() {
let ctx = kui_ctx_new();
kui_set_diagnostics(ctx, true);
kui_frame_begin(ctx, 200.0, 100.0, 1.0);
let root: KuiSpec = unsafe { std::mem::zeroed() };
kui_root(ctx, &root);
assert!(kui_slot(ctx, ks("side"), std::ptr::null()));
assert!(
!kui_slot(ctx, ks("side"), std::ptr::null()),
"declared twice"
);
kui_frame_finish(ctx);
assert_eq!(
kui_key_of(ctx, ks("side")),
kui_core::Key::ROOT.str("side").0,
"the slot's key is `parent.str(name)`, and `kui_key_of` answers it"
);
let c = unsafe { ctx.as_mut() }.unwrap();
let ws = c.core().take_warnings();
assert_eq!(ws.len(), 1);
assert_eq!(ws[0].code, kui_core::diag::DUPLICATE_SLOT);
let mut name = ks("");
assert!(!kui_slot_name(ctx, &mut name));
assert!(!kui_slot_namespace(ctx, &mut name));
assert!(kui_slot_params(ctx).is_null());
kui_ctx_free(ctx);
}
#[test]
fn replies_are_collected_for_the_event_in_progress_only() {
let payload = KuiValue(Value::Null);
let mut ev = KuiEvent {
payload: &payload,
..Default::default()
};
let other = KuiEvent {
payload: &payload,
..Default::default()
};
let reply = KuiValue(Value::map([("kind", "open".into())]));
assert!(!kui_reply(&ev, &reply), "no callback in progress");
let got = collect_replies(&mut ev, |ev| {
assert!(kui_reply(ev, &reply));
assert!(!kui_reply(&other, &reply), "an event with no sink");
assert!(!kui_reply(ev, std::ptr::null()), "no value");
assert!(kui_reply(ev, &reply));
});
assert_eq!(got, vec![reply.0.clone(), reply.0.clone()]);
assert!(!kui_reply(&ev, &reply), "the sink closed with the callback");
assert!(ev.reply_sink.is_null(), "and the event no longer names one");
}
#[test]
fn a_c_host_is_told_why_an_extension_would_not_load() {
let ctx = kui_ctx_new();
assert_eq!(kui_ctx_extension_count(ctx), 0);
let mut out = ks("");
assert!(
!kui_ctx_extension_error(ctx, &mut out),
"nothing has been tried yet"
);
let lib = crate::ext::tests::a_library_with_no_kui_symbols();
assert!(!kui_ctx_add_extension(ctx, ks("libc"), ks(lib)));
assert!(kui_ctx_extension_error(ctx, &mut out));
let msg =
std::str::from_utf8(unsafe { std::slice::from_raw_parts(out.ptr, out.len) }).unwrap();
assert!(
msg.contains("declares no ABI"),
"the reason should be the ABI refusal, got {msg:?}"
);
assert_eq!(kui_ctx_extension_count(ctx), 0, "nothing was kept");
assert!(!kui_ctx_add_extension(
ctx,
ks("nope"),
ks("no/such/library")
));
assert!(kui_ctx_extension_error(ctx, &mut out));
assert!(
!kui_ctx_extension_namespace(ctx, 0, &mut out),
"0 is the host"
);
assert!(!kui_ctx_extension_namespace(ctx, 1, &mut out));
kui_frame_begin(ctx, 200.0, 100.0, 1.0);
assert!(kui_slot(ctx, ks("todos/panel"), std::ptr::null()));
assert!(
!kui_slot(ctx, ks("todos/panel"), std::ptr::null()),
"duplicate"
);
kui_frame_finish(ctx);
kui_ctx_free(ctx);
}
#[test]
fn a_borrowed_frame_will_not_take_an_extension() {
let mut core = kui_core::Core::new();
let mut ui = core.frame(kui_core::Size::new(200.0, 100.0), 1.0);
let mut borrowed = KuiCtx::borrowing_in(&mut ui);
let ctx: *mut KuiCtx = &mut borrowed;
let lib = crate::ext::tests::a_library_with_no_kui_symbols();
assert!(!kui_ctx_add_extension(ctx, ks("libc"), ks(lib)));
assert_eq!(kui_ctx_extension_count(ctx), 0);
let mut out = ks("");
assert!(kui_ctx_extension_error(ctx, &mut out));
let msg =
std::str::from_utf8(unsafe { std::slice::from_raw_parts(out.ptr, out.len) }).unwrap();
assert!(
msg.contains("borrows a frame"),
"the reason should name the borrowed frame, got {msg:?}"
);
}
}