use super::*;
#[repr(C)]
#[derive(Clone, Copy)]
pub struct KuiMenuItem {
pub label: KuiStr,
pub role: u32,
pub enabled: u32,
pub id: *const KuiValue,
pub accel: KuiStr,
pub checked: u32,
pub replay: u32,
pub submenu: *const KuiMenuItem,
pub submenu_count: usize,
}
const MAX_MENU_DEPTH: usize = 32;
impl KuiMenuItem {
pub(crate) fn to_core(self) -> Option<kui_core::MenuItem> {
self.to_core_at(0)
}
fn to_core_at(self, depth: usize) -> Option<kui_core::MenuItem> {
let role = *kui_core::MenuRole::ALL.get(self.role as usize)?;
let submenu = if self.submenu.is_null() || self.submenu_count == 0 {
Vec::new()
} else if depth >= MAX_MENU_DEPTH {
return None;
} else {
let rows = unsafe { std::slice::from_raw_parts(self.submenu, self.submenu_count) };
rows.iter()
.map(|r| r.to_core_at(depth + 1))
.collect::<Option<Vec<_>>>()?
};
Some(kui_core::MenuItem {
label: kstr(self.label).into_owned(),
role,
enabled: self.enabled != 0,
checked: self.checked != 0,
id: unsafe { self.id.as_ref() }.map(|v| v.0.clone()),
accel: opt_str(self.accel).map(|s| s.into_owned()),
submenu,
replay: self.replay != 0,
})
}
}
unsafe fn path_of<'a>(path: *const usize, depth: usize) -> Option<&'a [usize]> {
if depth == 0 {
return Some(&[]);
}
if path.is_null() {
return None;
}
Some(unsafe { std::slice::from_raw_parts(path, depth) })
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_open_menu(
ptr: *mut KuiCtx,
key: u64,
x: f32,
y: f32,
items: *const KuiMenuItem,
count: usize,
) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
if key == 0 || items.is_null() || count == 0 {
return false;
}
let rows = unsafe { std::slice::from_raw_parts(items, count) };
let mut parsed = Vec::with_capacity(count);
for row in rows {
match row.to_core() {
Some(item) => parsed.push(item),
None => return false,
}
}
c.core().open_menu(kui_core::Menu::new(
Key(key),
kui_core::Vec2::new(x, y),
parsed,
));
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_set_lookup_available(ptr: *mut KuiCtx, on: bool) {
guard((), || {
if let Some(c) = unsafe { ctx(ptr) } {
c.core().set_lookup_available(on);
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_set_native_menus(ptr: *mut KuiCtx, on: bool) {
guard((), || {
if let Some(c) = unsafe { ctx(ptr) } {
c.core().set_native_menus(on);
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_activate_menu_item(ptr: *mut KuiCtx, index: usize) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let Some(events) = c.core().activate_menu_item(index) else {
return false;
};
c.absorb(events);
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_request_copy(ptr: *mut KuiCtx, out: *mut KuiStr) -> u32 {
guard(2, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return 2;
};
match c.core().request_copy() {
kui_core::CopyRequest::Ready(text) => {
c.copy_text = text;
if !out.is_null() {
unsafe {
out.write(KuiStr {
ptr: c.copy_text.as_ptr(),
len: c.copy_text.len(),
})
};
}
KUI_COPY_READY
}
kui_core::CopyRequest::Asked => KUI_COPY_ASKED,
kui_core::CopyRequest::Nothing => KUI_COPY_NOTHING,
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_answer_selection_range(ptr: *mut KuiCtx, text: KuiStr) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let text = kstr(text).into_owned();
c.core().answer_selection_range(&text)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_set_clipboard(ptr: *mut KuiCtx, text: KuiStr, html: KuiStr) {
guard((), || {
if let Some(c) = unsafe { ctx(ptr) } {
let text = kstr(text).into_owned();
let html = kstr(html).into_owned();
c.core()
.set_clipboard(text, (!html.is_empty()).then_some(html));
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_set_clipboard_secret(ptr: *mut KuiCtx, text: KuiStr) {
guard((), || {
if let Some(c) = unsafe { ctx(ptr) } {
let text = kstr(text).into_owned();
c.core().set_clipboard_secret(text);
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_request_paste(ptr: *mut KuiCtx) {
guard((), || {
if let Some(c) = unsafe { ctx(ptr) } {
c.core().request_paste();
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_awaiting_paste(ptr: *mut KuiCtx) -> bool {
guard(false, || {
unsafe { ctx(ptr) }.is_some_and(|c| c.core().awaiting_paste())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_close_menu(ptr: *mut KuiCtx) -> bool {
guard(false, || {
unsafe { ctx(ptr) }.is_some_and(|c| c.core().close_menu())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_take_menu_action(ptr: *mut KuiCtx, out: *mut KuiMenuAction) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
if c.menu_actions.is_empty() {
c.menu_actions = c.core().take_menu_actions().into();
}
if !out_accepts(out) {
return false;
}
let Some(action) = c.menu_actions.pop_front() else {
return false;
};
let mut at = kui_core::Vec2::new(0.0, 0.0);
let (kind, text, html) = match action {
kui_core::MenuAction::SetClipboard { text, html } => {
(KUI_MENU_ACTION_SET_CLIPBOARD, text, html)
}
kui_core::MenuAction::SetClipboardSecret { text } => {
(KUI_MENU_ACTION_SET_CLIPBOARD_SECRET, text, None)
}
kui_core::MenuAction::Paste => (KUI_MENU_ACTION_PASTE, String::new(), None),
kui_core::MenuAction::LookUp { text, at: point } => {
at = point;
(KUI_MENU_ACTION_LOOK_UP, text, None)
}
};
c.menu_text = text;
c.menu_html = html.unwrap_or_default();
let written = KuiMenuAction {
size: std::mem::size_of::<KuiMenuAction>() as u32,
kind,
text: KuiStr {
ptr: c.menu_text.as_ptr(),
len: c.menu_text.len(),
},
html: KuiStr {
ptr: c.menu_html.as_ptr(),
len: c.menu_html.len(),
},
x: at.x,
y: at.y,
};
write_out(out, written)
})
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct KuiMenu {
pub label: KuiStr,
pub items: *const KuiMenuItem,
pub count: usize,
pub enabled: u32,
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_bar(ptr: *mut KuiCtx, menus: *const KuiMenu, count: usize) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let mut out = Vec::with_capacity(count);
if !menus.is_null() {
for menu in unsafe { std::slice::from_raw_parts(menus, count) } {
let mut items = Vec::with_capacity(menu.count);
if !menu.items.is_null() {
for row in unsafe { std::slice::from_raw_parts(menu.items, menu.count) } {
match row.to_core() {
Some(item) => items.push(item),
None => return false,
}
}
}
out.push(kui_core::BarMenu {
label: kstr(menu.label).into_owned(),
items,
enabled: menu.enabled != 0,
});
}
}
let mut ui = kui_core::Ui::wrap(c.core());
kui_core::widgets::menu_bar(&mut ui, kui_core::MenuBar::new(out));
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_set_native_menu_bar(ptr: *mut KuiCtx, on: bool) {
guard((), || {
if let Some(c) = unsafe { ctx(ptr) } {
c.core().set_native_menu_bar(on);
}
});
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_bar_menu_count(ptr: *mut KuiCtx, revision: *mut u64) -> usize {
guard(0, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return 0;
};
if !revision.is_null() {
unsafe { revision.write(c.core().menu_bar_revision()) };
}
c.core().menu_bar().map_or(0, |b| b.menus.len())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_bar_menu(
ptr: *mut KuiCtx,
menu: usize,
label: *mut KuiStr,
enabled: *mut bool,
) -> usize {
guard(0, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return 0;
};
let Some(m) = c.core().menu_bar().and_then(|b| b.menus.get(menu)) else {
return 0;
};
let (text, on, count) = (m.label.clone(), m.enabled, m.items.len());
c.row_text = text;
if !label.is_null() {
unsafe {
label.write(KuiStr {
ptr: c.row_text.as_ptr(),
len: c.row_text.len(),
})
};
}
if !enabled.is_null() {
unsafe { enabled.write(on) };
}
count
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_bar_item(
ptr: *mut KuiCtx,
menu: usize,
item: usize,
label: *mut KuiStr,
accel: *mut KuiStr,
role: *mut u32,
flags: *mut u32,
) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let Some(row) = c.core().menu_bar().and_then(|b| b.item(menu, item)) else {
return false;
};
let row = row.clone();
write_row(c, &row, label, accel, role, flags);
true
})
}
fn write_row(
c: &mut KuiCtx,
row: &kui_core::MenuItem,
label: *mut KuiStr,
accel: *mut KuiStr,
role: *mut u32,
flags: *mut u32,
) {
c.row_text = row.text().to_string();
c.menu_accel = row.accel_text().unwrap_or_default().to_string();
if !label.is_null() {
unsafe {
label.write(KuiStr {
ptr: c.row_text.as_ptr(),
len: c.row_text.len(),
})
};
}
if !accel.is_null() {
unsafe {
accel.write(KuiStr {
ptr: c.menu_accel.as_ptr(),
len: c.menu_accel.len(),
})
};
}
if !role.is_null() {
unsafe { role.write(role_code(row.role)) };
}
if !flags.is_null() {
let mut bits = 0;
if row.enabled {
bits |= KUI_MENU_ITEM_ENABLED;
}
if row.checked {
bits |= KUI_MENU_ITEM_CHECKED;
}
if row.has_submenu() {
bits |= KUI_MENU_ITEM_SUBMENU;
}
if row.replay {
bits |= KUI_MENU_ITEM_REPLAY;
}
unsafe { flags.write(bits) };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_item_count(
ptr: *mut KuiCtx,
target: *mut u64,
x: *mut f32,
y: *mut f32,
) -> usize {
guard(0, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return 0;
};
let Some(menu) = c.core().menu() else {
return 0;
};
if !target.is_null() {
unsafe { target.write(menu.target.0) };
}
if !x.is_null() {
unsafe { x.write(menu.at.x) };
}
if !y.is_null() {
unsafe { y.write(menu.at.y) };
}
menu.items.len()
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_item(
ptr: *mut KuiCtx,
item: usize,
label: *mut KuiStr,
accel: *mut KuiStr,
role: *mut u32,
flags: *mut u32,
) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let Some(row) = c.core().menu().and_then(|m| m.items.get(item)) else {
return false;
};
let row = row.clone();
write_row(c, &row, label, accel, role, flags);
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_activate_menu_bar_item(ptr: *mut KuiCtx, menu: usize, item: usize) -> bool {
guard(false, || {
let Some(c) = (unsafe { ctx(ptr) }) else {
return false;
};
let events = c.core().activate_menu_bar_item(menu, item);
let any = !events.is_empty();
c.absorb(events);
any
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_submenu_count(
ptr: *mut KuiCtx,
path: *const usize,
depth: usize,
) -> usize {
guard(0, || {
let (Some(c), Some(path)) = (unsafe { ctx(ptr) }, unsafe { path_of(path, depth) }) else {
return 0;
};
let Some(menu) = c.core().menu() else {
return 0;
};
if path.is_empty() {
return menu.items.len();
}
kui_core::MenuItem::at_path(&menu.items, path).map_or(0, |r| r.submenu.len())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_item_path(
ptr: *mut KuiCtx,
path: *const usize,
depth: usize,
label: *mut KuiStr,
accel: *mut KuiStr,
role: *mut u32,
flags: *mut u32,
) -> bool {
guard(false, || {
let (Some(c), Some(path)) = (unsafe { ctx(ptr) }, unsafe { path_of(path, depth) }) else {
return false;
};
let Some(row) = c
.core()
.menu()
.and_then(|m| kui_core::MenuItem::at_path(&m.items, path))
.cloned()
else {
return false;
};
write_row(c, &row, label, accel, role, flags);
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_activate_menu_path(
ptr: *mut KuiCtx,
path: *const usize,
depth: usize,
) -> bool {
guard(false, || {
let (Some(c), Some(path)) = (unsafe { ctx(ptr) }, unsafe { path_of(path, depth) }) else {
return false;
};
if path.is_empty() {
return false;
}
let Some(events) = c.core().activate_menu_path(path) else {
return false;
};
c.absorb(events);
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_bar_submenu_count(
ptr: *mut KuiCtx,
menu: usize,
path: *const usize,
depth: usize,
) -> usize {
guard(0, || {
let (Some(c), Some(path)) = (unsafe { ctx(ptr) }, unsafe { path_of(path, depth) }) else {
return 0;
};
let Some(bar) = c.core().menu_bar() else {
return 0;
};
if path.is_empty() {
return bar.menus.get(menu).map_or(0, |m| m.items.len());
}
bar.item_at(menu, path).map_or(0, |r| r.submenu.len())
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_menu_bar_item_path(
ptr: *mut KuiCtx,
menu: usize,
path: *const usize,
depth: usize,
label: *mut KuiStr,
accel: *mut KuiStr,
role: *mut u32,
flags: *mut u32,
) -> bool {
guard(false, || {
let (Some(c), Some(path)) = (unsafe { ctx(ptr) }, unsafe { path_of(path, depth) }) else {
return false;
};
let Some(row) = c
.core()
.menu_bar()
.and_then(|b| b.item_at(menu, path))
.cloned()
else {
return false;
};
write_row(c, &row, label, accel, role, flags);
true
})
}
#[unsafe(no_mangle)]
pub extern "C" fn kui_activate_menu_bar_path(
ptr: *mut KuiCtx,
menu: usize,
path: *const usize,
depth: usize,
) -> bool {
guard(false, || {
let (Some(c), Some(path)) = (unsafe { ctx(ptr) }, unsafe { path_of(path, depth) }) else {
return false;
};
if path.is_empty() {
return false;
}
let events = c.core().activate_menu_bar_path(menu, path);
let any = !events.is_empty();
c.absorb(events);
any
})
}
fn role_code(role: kui_core::MenuRole) -> u32 {
kui_core::MenuRole::ALL
.iter()
.position(|r| *r == role)
.expect("every role is in MenuRole::ALL") as u32
}