use crate::{UiLayer, handlers};
use jni::{JNIEnv, objects::JClass, objects::JString, sys::*};
use crate::jcall::jcall;
use std::cell::Cell;
use yog_abi::YogStr;
fn dispatch(id: &str, ev: &str) {
let h = handlers();
let Some((module, ud, handler)) = h.ui_handlers.get(id).cloned() else {
return;
};
if module.is_retiring() {
return;
}
if let Some(_call) = module.enter() {
unsafe {
handler(ud, YogStr::from_str(id), YogStr::from_str(ev));
}
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIShow<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
parent_id: JString<'l>,
modal: jboolean,
pause_game: jboolean,
screen_w: jint,
screen_h: jint,
) {
let id = jstr!(env, ui_id);
let parent = jstr!(env, parent_id);
let parent = (!parent.is_empty()).then_some(parent);
let h = handlers();
let mut active = h.active_uis.lock().expect("active_uis");
active.retain(|l| l.id != id);
let layer = UiLayer {
id: id.clone(),
parent,
modal: modal != 0,
pause_game: pause_game != 0,
visible: true,
enabled: true,
z_index: active.iter().map(|l| l.z_index).max().unwrap_or(0) + 1,
};
active.push(layer);
active.sort_by_key(|l| l.z_index);
yog_logging::info!(
"UI show: {} modal={} pause={} layers={}",
id,
modal != 0,
pause_game != 0,
active.len()
);
drop(active);
dispatch(&id, &format!("open:{screen_w}:{screen_h}"));
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIHide<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
) {
let id = jstr!(env, ui_id);
let h = handlers();
let mut active = h.active_uis.lock().expect("active_uis");
let closed: Vec<String> = active
.iter()
.filter(|l| l.id == id || l.parent.as_deref() == Some(&id))
.map(|l| l.id.clone())
.collect();
active.retain(|l| !closed.contains(&l.id));
yog_logging::info!("UI hide: {} (layers left: {})", id, active.len());
drop(active);
for c in &closed {
dispatch(c, "close");
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIMove<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
mx: jfloat,
my: jfloat,
) {
let id = jstr!(env, ui_id);
dispatch(&id, &format!("move:{mx:.1}:{my:.1}"));
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeIsUIActive<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
) -> jboolean {
let id = jstr!(env, ui_id);
let h = handlers();
let active = h.active_uis.lock().expect("active_uis");
active.iter().any(|l| l.id == id && l.visible) as jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIClick<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
mx: jfloat,
my: jfloat,
button: jint,
) -> jboolean {
let id = jstr!(env, ui_id);
let h = handlers();
let active = h.active_uis.lock().expect("active_uis");
let top_modal = active
.iter()
.rev()
.find(|l| l.visible && l.modal)
.map(|l| l.id.clone());
if let Some(ref modal_id) = top_modal
&& id != *modal_id
{
return 0;
}
drop(active);
{
let mut renderers = h.book_renderers.lock().expect("book_renderers");
if let Some(renderer) = renderers.get_mut(&id)
&& renderer.ui.is_some()
{
if let Some(ui) = &renderer.ui
&& let Some(hit) = yog_ui::layout::hit_test(&ui.layout_root, mx, my)
&& let Some(event) = &hit.on_click
{
let ev = event.clone();
drop(renderers);
if let Some(r) = h.book_renderers.lock().unwrap().get_mut(&id) {
r.handle_event(&ev);
}
yog_logging::info!("book click '{}' → '{}'", &id, &ev);
return 1;
}
return 1; }
}
let _ = button;
dispatch_consumable(&id, &format!("click:{mx:.1}:{my:.1}")) as jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIScroll<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
dy: jfloat,
) {
let id = jstr!(env, ui_id);
let h = handlers();
if let Some((module, ud, handler)) = h.ui_handlers.get(&id).cloned()
&& !module.is_retiring()
&& let Some(_call) = module.enter()
{
let ev = format!("scroll:{dy:.2}");
unsafe {
handler(ud, YogStr::from_str(&id), YogStr::from_str(&ev));
}
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIDrag<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
mx: jfloat,
my: jfloat,
) {
let id = jstr!(env, ui_id);
let h = handlers();
if let Some((module, ud, handler)) = h.ui_handlers.get(&id).cloned()
&& !module.is_retiring()
&& let Some(_call) = module.enter()
{
let ev = format!("drag:{mx:.1}:{my:.1}");
unsafe {
handler(ud, YogStr::from_str(&id), YogStr::from_str(&ev));
}
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIRelease<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
mx: jfloat,
my: jfloat,
) {
let id = jstr!(env, ui_id);
let h = handlers();
if let Some((module, ud, handler)) = h.ui_handlers.get(&id).cloned()
&& !module.is_retiring()
&& let Some(_call) = module.enter()
{
let ev = format!("release:{mx:.1}:{my:.1}");
unsafe {
handler(ud, YogStr::from_str(&id), YogStr::from_str(&ev));
}
}
}
thread_local! {
static CONSUMED: Cell<bool> = const { Cell::new(false) };
static HIGH_SURROGATE: Cell<Option<u16>> = const { Cell::new(None) };
}
pub(crate) fn consume_event() {
CONSUMED.with(|c| c.set(true));
}
fn dispatch_consumable(id: &str, ev: &str) -> bool {
let h = handlers();
let Some((module, ud, handler)) = h.ui_handlers.get(id).cloned() else {
return false;
};
if module.is_retiring() {
return false;
}
let Some(_call) = module.enter() else {
return false;
};
let before = CONSUMED.with(|c| c.replace(false));
unsafe {
handler(ud, YogStr::from_str(id), YogStr::from_str(ev));
}
CONSUMED.with(|c| c.replace(before))
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIKey<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
key: jint,
_scan: jint,
mods: jint,
action: jint,
) -> jboolean {
let id = jstr!(env, ui_id);
if action == 0 {
return 0;
}
dispatch_consumable(&id, &format!("key:{key}:{mods}")) as jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIChar<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
chr: jchar,
_mods: jint,
) -> jboolean {
let id = jstr!(env, ui_id);
let pending = HIGH_SURROGATE.with(std::cell::Cell::take);
let c = match chr {
0xD800..=0xDBFF => {
HIGH_SURROGATE.with(|p| p.set(Some(chr)));
return 1;
}
0xDC00..=0xDFFF => match pending {
Some(hi) => char::decode_utf16([hi, chr])
.next()
.and_then(std::result::Result::ok),
None => None,
},
_ => char::from_u32(chr as u32),
};
let Some(c) = c else { return 0 };
dispatch_consumable(&id, &format!("char:{}", c as u32)) as jboolean
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeUIRender<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
ui_id: JString<'l>,
screen_w: jint,
screen_h: jint,
) {
let id = jstr!(env, ui_id);
let n = (inv_slot_count(&mut env).max(0) as usize).min(yog_inventory::MAX_SNAPSHOT_SLOTS);
let mut slot_item_ids: Vec<String> = Vec::with_capacity(n);
let mut snapshot = yog_inventory::YogInvSnapshotRaw::EMPTY;
if n > 0 {
snapshot.slot_count = n as u32;
for i in 0..n {
let item = inv_slot_item(&mut env, i as i32);
let pos = inv_slot_pos(&mut env, i as i32).unwrap_or((0, 0));
slot_item_ids.push(item.as_ref().map(|(id, _)| id.clone()).unwrap_or_default());
snapshot.slots[i].count = item.map(|(_, c)| c as u32).unwrap_or(0);
snapshot.slots[i].x = pos.0;
snapshot.slots[i].y = pos.1;
}
for (slot, id) in snapshot.slots.iter_mut().zip(&slot_item_ids) {
slot.item_id = YogStr::from_str(id);
}
push_inventory_snapshot(&snapshot);
}
let h = crate::handlers();
let mut gfx = crate::GFX_FN_TABLE;
gfx.screen_w = screen_w;
gfx.screen_h = screen_h;
gfx.delta_tick = 1.0;
gfx.scale_factor = 1.0;
let ctx = unsafe { yog_gfx::GfxContext::from_raw(&gfx as *const _) };
let sw = screen_w as f32;
let sh = screen_h as f32;
{
let mut book_renderers = h.book_renderers.lock().expect("book_renderers");
if let Some(book_renderer) = book_renderers.get_mut(&id)
&& book_renderer.ui.is_some()
{
let gfx_ptr = &gfx as *const _;
book_renderer.render_with(&ctx, sw, sh, &mut |_, drawer, data, [x, y, w, bh]| {
let Some((module, ud, f)) = h.page_drawers.get(drawer).cloned() else {
return;
};
if module.is_retiring() {
return;
}
let Some(_call) = module.enter() else {
return;
};
let bytes = yog_data::binary::encode(data);
unsafe { f(ud, gfx_ptr, bytes.as_ptr(), bytes.len(), x, y, w, bh) };
});
return;
}
}
let render_cbs: Vec<_> = h.ui_render_handlers.get(&id).cloned().unwrap_or_default();
for (module, ud, f) in render_cbs {
if module.is_retiring() {
continue;
}
let Some(_call) = module.enter() else {
continue;
};
unsafe { f(ud, &gfx as *const _) };
}
}
fn push_inventory_snapshot(snapshot: &yog_inventory::YogInvSnapshotRaw) {
type SetSnapshotFn = unsafe extern "C" fn(*const yog_inventory::YogInvSnapshotRaw);
for (_id, module) in crate::hot_reloader().all_active() {
if module.is_retiring() {
continue;
}
let Some(_call) = module.enter() else {
continue;
};
module.with_library(|lib| unsafe {
if let Ok(f) = lib.get::<SetSnapshotFn>(b"yog_inventory_set_snapshot") {
f(snapshot as *const _);
}
});
}
}
pub fn inv_slot_count(env: &mut JNIEnv) -> i32 {
unsafe { jcall!(env, "dev/yog/NativeBridge", "getSlotCount", []) }.unwrap_or(0)
}
pub fn inv_slot_item(env: &mut JNIEnv, index: i32) -> Option<(String, i32)> {
let s: String = unsafe { jcall!(env, "dev/yog/NativeBridge", "getSlotItem", [I(index)]) }?;
let (id, count) = s.split_once('\t')?;
Some((id.to_owned(), count.parse().ok()?))
}
pub fn inv_slot_pos(env: &mut JNIEnv, index: i32) -> Option<(i32, i32)> {
let s: String = unsafe { jcall!(env, "dev/yog/NativeBridge", "getSlotPos", [I(index)]) }?;
let [x, y] = crate::parse_tsv(&s)?;
Some((x, y))
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeMenuEntries<'l>(
env: JNIEnv<'l>,
_class: JClass<'l>,
) -> jstring {
let h = handlers();
let lines: Vec<String> = h
.menu_entries
.iter()
.map(|(label, ui_id)| format!("{label}\t{ui_id}"))
.collect();
let out = lines.join("\n");
env.new_string(out).unwrap().into_raw()
}
fn tooltip_of(h: &crate::RuntimeHandlers, item: &str, snbt: &str) -> Option<(crate::TooltipEntry, f32, f32)> {
for entry in h.item_tooltips.get(item)? {
let (module, ud, size, _) = entry.clone();
if module.is_retiring() {
continue;
}
let Some(_call) = module.enter() else {
continue;
};
let (mut w, mut hh) = (0.0f32, 0.0f32);
if unsafe { size(ud, YogStr::from_str(item), YogStr::from_str(snbt), &mut w, &mut hh) } && w > 0.0 && hh > 0.0 {
return Some((entry.clone(), w, hh));
}
}
None
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeTooltipSize<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
item: JString<'l>,
snbt: JString<'l>,
) -> jfloatArray {
let item = jstr!(env, item, std::ptr::null_mut());
let snbt = jstr!(env, snbt, std::ptr::null_mut());
let h = handlers();
let Some((_, w, hh)) = tooltip_of(&h, &item, &snbt) else {
return std::ptr::null_mut();
};
let Ok(arr) = env.new_float_array(2) else {
return std::ptr::null_mut();
};
let _ = env.set_float_array_region(&arr, 0, &[w, hh]);
arr.into_raw()
}
#[unsafe(no_mangle)]
#[allow(clippy::too_many_arguments)]
pub extern "system" fn Java_dev_yog_NativeBridge_nativeTooltipDraw<'l>(
mut env: JNIEnv<'l>,
_class: JClass<'l>,
item: JString<'l>,
snbt: JString<'l>,
x: jint,
y: jint,
w: jint,
hh: jint,
screen_w: jint,
screen_h: jint,
) {
let item = jstr!(env, item);
let snbt = jstr!(env, snbt);
let h = handlers();
let Some(((module, ud, _, draw), ..)) = tooltip_of(&h, &item, &snbt) else {
return;
};
let Some(_call) = module.enter() else {
return;
};
let mut gfx = crate::GFX_FN_TABLE;
gfx.screen_w = screen_w;
gfx.screen_h = screen_h;
gfx.delta_tick = 1.0;
gfx.scale_factor = 1.0;
unsafe { draw(ud, &gfx as *const _, YogStr::from_str(&item), YogStr::from_str(&snbt), x as f32, y as f32, w as f32, hh as f32) };
}