winio-ui-android 0.1.0

Android backend for winio.
//! Android window widget, based on JNI and FrameLayout

use std::sync::{Arc, Mutex};

use inherit_methods_macro::inherit_methods;
use jni::{
    objects::JObject,
    refs::{Global, LoaderContext, Reference},
};
use jni_min_helper::{DynamicProxy, JInteger};
use winio_callback::SyncCallback;
use winio_handle::{AsWindow, BorrowedContainer, BorrowedWindow};
use winio_primitive::{Point, Size};

use crate::{
    BaseWidget, Result, current_activity,
    java::{
        android::{
            view::{ViewOnLayoutChangeListener, WindowInsetsType},
            widget::{FrameLayout, FrameLayoutLayoutParams},
        },
        custom::Activity,
    },
    platform::dpi::{logical_margin, logical_size},
    vm_exec,
};

#[derive(Debug)]
pub struct Window {
    inner: BaseWidget<FrameLayout<'static>>,
    inner_view: BaseWidget<FrameLayout<'static>>,
    activity: Global<Activity<'static>>,
    on_resize: Arc<SyncCallback>,
    #[allow(unused)]
    on_resize_proxy: DynamicProxy,
    size_update: Arc<Mutex<(i32, i32)>>,
}

#[inherit_methods(from = "self.inner")]
impl Window {
    pub fn new() -> Result<Self> {
        vm_exec(move |env| {
            let act = current_activity(env)?;
            let act = env.new_global_ref(act)?;
            let window = FrameLayout::new(env, &act)?;
            act.set_content_view(env, &window)?;
            let params = FrameLayoutLayoutParams::new(env, -1, -1)?;
            window.as_view().set_layout_params(env, &params)?;
            let inner = env.new_global_ref(&window)?;
            let inner_view = FrameLayout::new(env, &act)?;
            let inner_view = BaseWidget::new_with_env(
                env,
                unsafe { BorrowedContainer::android(&inner) },
                inner_view,
            )?;

            let on_resize = Arc::new(SyncCallback::new());
            WINDOW_RESIZE_CALLBACK
                .lock()
                .unwrap()
                .replace(on_resize.clone());
            let size_update = Arc::new(Mutex::new((0, 0)));
            let on_resize_proxy = DynamicProxy::build(
                env,
                &LoaderContext::None,
                [ViewOnLayoutChangeListener::class_name()],
                {
                    let on_resize = on_resize.clone();
                    let size_update = size_update.clone();
                    move |env, method, args| {
                        let name = method.get_name(env)?;
                        if name.try_to_string(env)? == "onLayoutChange" {
                            let mut get_element = |i: usize| -> jni::errors::Result<i32> {
                                let obj = args.get_element(env, i)?;
                                let int = unsafe { JInteger::from_raw(env, obj.into_raw()) };
                                int.value(env)
                            };

                            let left = get_element(1)?;
                            let top = get_element(2)?;
                            let right = get_element(3)?;
                            let bottom = get_element(4)?;
                            let old_left = get_element(5)?;
                            let old_top = get_element(6)?;
                            let old_right = get_element(7)?;
                            let old_bottom = get_element(8)?;

                            if left != old_left
                                || top != old_top
                                || right != old_right
                                || bottom != old_bottom
                            {
                                *size_update.lock().unwrap() = (right - left, bottom - top);
                                on_resize.signal(());
                            }
                        }
                        Ok(JObject::null())
                    }
                },
            )?;
            inner
                .as_view()
                .add_on_layout_change_listener(env, &on_resize_proxy)?;
            Ok(Self {
                inner: inner.into(),
                inner_view,
                activity: act,
                on_resize,
                on_resize_proxy,
                size_update,
            })
        })
    }

    pub fn client_size(&self) -> Result<Size> {
        let size = self.size()?;
        let (top, right, bottom, left) = vm_exec(|env| {
            let insets = self.inner.as_view().get_root_window_insets(env)?;
            if !insets.is_null() {
                let system_bars = WindowInsetsType::system_bars(env)?;
                let insets = insets.get_insets_ignoring_visibility(env, system_bars)?;
                let left = insets.left(env)?;
                let top = insets.top(env)?;
                let right = insets.right(env)?;
                let bottom = insets.bottom(env)?;
                Result::Ok((top as _, right as _, bottom as _, left as _))
            } else {
                Ok((0, 0, 0, 0))
            }
        })?;
        let margin = logical_margin(top, right, bottom, left)?;
        let size = Size::new(
            size.width - margin.horizontal(),
            size.height - margin.vertical(),
        );
        self.inner_view
            .set_loc(Point::new(margin.left, margin.top))?;
        self.inner_view.set_size(size)?;
        Ok(size)
    }

    pub fn is_visible(&self) -> Result<bool>;

    pub fn set_visible(&mut self, visible: bool) -> Result<()>;

    fn size_update(&self) -> Result<Size> {
        let (width, height) = *self.size_update.lock().unwrap();
        logical_size(width as _, height as _)
    }

    pub fn loc(&self) -> Result<Point> {
        Ok(Point::zero())
    }

    pub fn set_loc(&mut self, _p: Point) -> Result<()> {
        Ok(())
    }

    pub fn size(&self) -> Result<Size> {
        let size = self.size_update()?;
        if size == Size::zero() {
            self.inner.preferred_size()
        } else {
            Ok(size)
        }
    }

    pub fn set_size(&mut self, _size: Size) -> Result<()> {
        Ok(())
    }

    pub fn text(&self) -> Result<String> {
        Ok(String::new())
    }

    pub fn set_text(&mut self, _text: impl AsRef<str>) -> Result<()> {
        Ok(())
    }

    pub async fn wait_close(&self) {
        std::future::pending().await
    }

    pub async fn wait_move(&self) {
        std::future::pending().await
    }

    pub async fn wait_size(&self) {
        self.on_resize.wait().await
    }

    pub async fn wait_theme_changed(&self) {
        std::future::pending().await
    }
}

impl AsWindow for Window {
    fn as_window(&self) -> BorrowedWindow<'_> {
        unsafe { BorrowedWindow::android(&self.activity) }
    }
}

winio_handle::impl_as_container!(Window, inner_view);

impl Drop for Window {
    fn drop(&mut self) {
        WINDOW_RESIZE_CALLBACK.lock().unwrap().take();
    }
}

pub(crate) static WINDOW_RESIZE_CALLBACK: Mutex<Option<Arc<SyncCallback>>> = Mutex::new(None);