use std::{
cell::Cell,
sync::{
Arc,
atomic::{AtomicI32, Ordering},
},
};
use jni::{
Env,
refs::{Global, LoaderContext, Reference},
};
use jni_min_helper::{DynamicProxy, JBoolean};
use winio_callback::SyncCallback;
use winio_handle::{
AsMenuContainer, AsMenuItem, BorrowedMenuBar, BorrowedMenuContainer, BorrowedMenuItem,
};
use crate::{
JCharSequenceExt, Result,
java::android::view::{
Menu as JMenu, MenuItem as JMenuItem, MenuItemOnMenuItemClickListener, SubMenu as JSubMenu,
menu, menu_item,
},
};
mod bar;
pub use bar::*;
mod bar_item;
pub use bar_item::*;
mod item;
pub use item::*;
mod popup;
pub use popup::*;
mod check;
pub use check::*;
mod radio;
pub use radio::*;
mod separator;
pub use separator::*;
fn next_id() -> i32 {
static NEXT: AtomicI32 = AtomicI32::new(1);
NEXT.fetch_add(1, Ordering::Relaxed)
}
fn bar_menu<'local>(env: &mut Env<'local>, bar: BorrowedMenuBar<'_>) -> Result<JMenu<'local>> {
let menu = env.new_local_ref(bar.to_android())?;
Ok(unsafe { JMenu::from_raw(env, menu.into_raw()) })
}
fn container_menu<'local>(
env: &mut Env<'local>,
container: BorrowedMenuContainer<'_>,
) -> Result<JMenu<'local>> {
let menu = env.new_local_ref(container.to_android())?;
Ok(unsafe { JMenu::from_raw(env, menu.into_raw()) })
}
fn click_listener(env: &mut Env<'_>, on_click: &Arc<SyncCallback>) -> Result<DynamicProxy> {
let on_click = on_click.clone();
Ok(DynamicProxy::build(
env,
&LoaderContext::None,
[MenuItemOnMenuItemClickListener::class_name()],
move |env, _method, _args| {
on_click.signal(());
Ok(JBoolean::new(env, true)?.into())
},
)?)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LeafKind {
Plain,
Check,
Radio,
}
#[derive(Debug)]
pub(crate) struct LeafItem {
menu: Global<JMenu<'static>>,
item: Global<JMenuItem<'static>>,
id: i32,
group: Option<i32>,
checked: Cell<bool>,
on_click: Arc<SyncCallback>,
#[allow(dead_code)]
click_proxy: DynamicProxy,
}
impl LeafItem {
pub fn new(
env: &mut Env<'_>,
parent: BorrowedMenuContainer<'_>,
kind: LeafKind,
) -> Result<Self> {
let menu = container_menu(env, parent)?;
let on_click = Arc::new(SyncCallback::new());
let click_proxy = click_listener(env, &on_click)?;
let id = next_id();
let group = match kind {
LeafKind::Radio => next_id(),
LeafKind::Plain | LeafKind::Check => menu::NONE,
};
let title = env.new_string("")?;
let item = menu.add(env, group, id, id, title)?;
match kind {
LeafKind::Plain => {}
LeafKind::Check => {
item.set_checkable(env, true)?;
}
LeafKind::Radio => {
menu.set_group_checkable(env, group, true, true)?;
}
}
item.set_on_menu_item_click_listener(env, &click_proxy)?;
Ok(Self {
menu: env.new_global_ref(&menu)?,
item: env.new_global_ref(&item)?,
id,
checked: Cell::new(false),
group: (kind == LeafKind::Radio).then_some(group),
on_click,
click_proxy,
})
}
pub fn text(&self, env: &mut Env<'_>) -> Result<String> {
Ok(self.item.get_title(env)?.try_to_string(env)?)
}
pub fn set_text(&self, env: &mut Env<'_>, s: impl AsRef<str>) -> Result<()> {
let title = env.new_string(s.as_ref())?;
self.item.set_title(env, title)?;
Ok(())
}
pub fn is_enabled(&self, env: &mut Env<'_>) -> Result<bool> {
Ok(self.item.is_enabled(env)?)
}
pub fn set_enabled(&self, env: &mut Env<'_>, enabled: bool) -> Result<()> {
self.item.set_enabled(env, enabled)?;
Ok(())
}
pub fn is_checked(&self, _env: &mut Env<'_>) -> Result<bool> {
Ok(self.checked.get())
}
pub fn set_checked(&self, env: &mut Env<'_>, checked: bool) -> Result<()> {
self.checked.set(checked);
match self.group {
Some(group) if !checked => {
self.menu.set_group_checkable(env, group, true, false)?;
self.item.set_checked(env, false)?;
self.menu.set_group_checkable(env, group, true, true)?;
}
_ => {
self.item.set_checked(env, checked)?;
}
}
Ok(())
}
pub fn toggle_checked(&self, env: &mut Env<'_>) -> Result<()> {
let checked = self.item.is_checked(env)?;
self.set_checked(env, !checked)
}
pub async fn wait_click(&self) {
self.on_click.wait().await;
}
}
impl Drop for LeafItem {
fn drop(&mut self) {
let id = self.id;
let _ = crate::vm_exec(move |env| -> Result<()> {
self.menu.remove_item(env, id)?;
Ok(())
});
}
}
impl AsMenuItem for LeafItem {
fn as_menu_item(&self) -> BorrowedMenuItem<'_> {
unsafe { BorrowedMenuItem::android(&self.item) }
}
}
#[derive(Debug)]
pub(crate) struct SubMenuItem {
menu: Global<JMenu<'static>>,
submenu: Global<JSubMenu<'static>>,
item: Global<JMenuItem<'static>>,
id: i32,
}
impl SubMenuItem {
pub fn new(env: &mut Env<'_>, menu: &JMenu<'_>) -> Result<Self> {
let id = next_id();
let title = env.new_string("")?;
let submenu = menu.add_sub_menu(env, menu::NONE, id, id, title)?;
let item = submenu.get_item(env)?;
Ok(Self {
menu: env.new_global_ref(menu)?,
submenu: env.new_global_ref(&submenu)?,
item: env.new_global_ref(&item)?,
id,
})
}
pub fn text(&self, env: &mut Env<'_>) -> Result<String> {
Ok(self.item.get_title(env)?.try_to_string(env)?)
}
pub fn set_text(&self, env: &mut Env<'_>, s: impl AsRef<str>) -> Result<()> {
let title = env.new_string(s.as_ref())?;
self.item.set_title(env, title)?;
Ok(())
}
pub fn is_enabled(&self, env: &mut Env<'_>) -> Result<bool> {
Ok(self.item.is_enabled(env)?)
}
pub fn set_enabled(&self, env: &mut Env<'_>, enabled: bool) -> Result<()> {
self.item.set_enabled(env, enabled)?;
Ok(())
}
pub fn set_show_as_action(&self, env: &mut Env<'_>) -> Result<()> {
self.item
.set_show_as_action(env, menu_item::SHOW_AS_ACTION_ALWAYS)?;
Ok(())
}
}
impl Drop for SubMenuItem {
fn drop(&mut self) {
let id = self.id;
let _ = crate::vm_exec(move |env| -> Result<()> {
self.menu.remove_item(env, id)?;
Ok(())
});
}
}
impl AsMenuContainer for SubMenuItem {
fn as_menu_container(&self) -> BorrowedMenuContainer<'_> {
unsafe { BorrowedMenuContainer::android(&self.submenu) }
}
}
impl AsMenuItem for SubMenuItem {
fn as_menu_item(&self) -> BorrowedMenuItem<'_> {
unsafe { BorrowedMenuItem::android(&self.item) }
}
}