#[cfg(any(feature = "android-jni", test))]
use core::ptr::NonNull;
#[cfg(not(target_vendor = "apple"))]
use core::ptr::null_mut;
use waterui::window::{Window, WindowBackground, WindowManager, WindowState, WindowStyle};
use waterui::{AnyView, Str};
use waterui_layout::{Rect, Size};
#[cfg(feature = "c-api")]
use crate::ffi_binding;
use crate::{
IntoFFI, IntoRust, WuiAnyView, WuiEnv,
closure::ForeignCallbackContext,
color::WuiColor,
reactive::{WuiBinding, WuiComputed},
};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiWindowStyle {
Titled = 0,
Borderless = 1,
FullSizeContentView = 2,
}
impl From<WindowStyle> for WuiWindowStyle {
fn from(style: WindowStyle) -> Self {
match style {
WindowStyle::Titled => Self::Titled,
WindowStyle::Borderless => Self::Borderless,
WindowStyle::FullSizeContentView => Self::FullSizeContentView,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WuiWindowState {
Normal = 0,
Closed = 1,
Minimized = 2,
Fullscreen = 3,
}
impl From<WindowState> for WuiWindowState {
fn from(state: WindowState) -> Self {
match state {
WindowState::Normal => Self::Normal,
WindowState::Closed => Self::Closed,
WindowState::Minimized => Self::Minimized,
WindowState::Fullscreen => Self::Fullscreen,
}
}
}
impl IntoFFI for WindowState {
type FFI = WuiWindowState;
fn into_ffi(self) -> Self::FFI {
self.into()
}
}
impl IntoRust for WuiWindowState {
type Rust = WindowState;
unsafe fn into_rust(self) -> Self::Rust {
match self {
Self::Normal => WindowState::Normal,
Self::Closed => WindowState::Closed,
Self::Minimized => WindowState::Minimized,
Self::Fullscreen => WindowState::Fullscreen,
}
}
}
#[cfg(feature = "c-api")]
ffi_binding!(WindowState, WuiWindowState, window_state);
crate::ffi_watcher!(WindowState, WuiWindowState, window_state);
#[repr(C)]
#[derive(Debug)]
pub enum WuiWindowBackground {
Opaque,
Color {
color: *mut WuiColor,
},
}
impl From<WindowBackground> for WuiWindowBackground {
fn from(bg: WindowBackground) -> Self {
match bg {
WindowBackground::Opaque => Self::Opaque,
WindowBackground::Color(color) => Self::Color {
color: color.into_ffi(),
},
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct WuiWindow {
pub title: *mut WuiComputed<Str>,
pub closable: bool,
pub resizable: bool,
pub frame: *mut WuiBinding<Rect>,
pub content: *mut WuiAnyView,
pub state: *mut WuiBinding<WindowState>,
pub toolbar: *mut WuiAnyView,
pub style: WuiWindowStyle,
pub background: WuiWindowBackground,
pub min_size: *mut WuiComputed<Size>,
pub max_size: *mut WuiComputed<Size>,
}
#[cfg(any(feature = "android-jni", test))]
pub(crate) struct OwnedFfiHandle<T>(NonNull<T>);
#[cfg(any(feature = "android-jni", test))]
impl<T> OwnedFfiHandle<T> {
#[track_caller]
pub(crate) fn required(pointer: *mut T, field: &'static str) -> Self {
let pointer = NonNull::new(pointer).unwrap_or_else(|| panic!("{field} must not be null"));
Self(pointer)
}
fn optional(pointer: *mut T) -> Option<Self> {
NonNull::new(pointer).map(Self)
}
pub(crate) const fn as_ptr(&self) -> *mut T {
self.0.as_ptr()
}
pub(crate) const fn into_raw(self) -> *mut T {
let pointer = self.as_ptr();
core::mem::forget(self);
pointer
}
}
#[cfg(any(feature = "android-jni", test))]
impl<T> Drop for OwnedFfiHandle<T> {
fn drop(&mut self) {
unsafe {
drop(alloc::boxed::Box::from_raw(self.0.as_ptr()));
}
}
}
#[cfg(any(feature = "android-jni", test))]
impl WuiWindowBackground {
fn into_android_owned_color(self) -> Option<OwnedFfiHandle<WuiColor>> {
match self {
Self::Opaque => None,
Self::Color { color } => Some(OwnedFfiHandle::required(
color,
"WuiWindow.background.color",
)),
}
}
}
#[cfg(any(feature = "android-jni", test))]
impl WuiWindow {
pub(crate) fn into_android_content(self) -> OwnedFfiHandle<WuiAnyView> {
let Self {
title,
closable: _,
resizable: _,
frame,
content,
state,
toolbar,
style: _,
background,
min_size,
max_size,
} = self;
let unused_handles = (
OwnedFfiHandle::required(title, "WuiWindow.title"),
OwnedFfiHandle::optional(frame),
OwnedFfiHandle::required(state, "WuiWindow.state"),
OwnedFfiHandle::optional(toolbar),
background.into_android_owned_color(),
OwnedFfiHandle::optional(min_size),
OwnedFfiHandle::optional(max_size),
);
let content = OwnedFfiHandle::required(content, "WuiWindow.content");
drop(unused_handles);
content
}
pub(crate) fn dispose_android(self) {
drop(self.into_android_content());
}
}
#[cfg(target_vendor = "apple")]
fn toolbar_into_ffi(toolbar: Option<AnyView>) -> *mut WuiAnyView {
toolbar.into_ffi()
}
#[cfg(not(target_vendor = "apple"))]
fn toolbar_into_ffi(_toolbar: Option<AnyView>) -> *mut WuiAnyView {
null_mut()
}
impl IntoFFI for Window {
type FFI = WuiWindow;
fn into_ffi(self) -> Self::FFI {
let content = self.build_content();
let title = self.display_title();
let toolbar = toolbar_into_ffi(self.toolbar);
WuiWindow {
title: title.into_ffi(),
closable: self.closable,
resizable: self.resizable,
frame: self.frame.into_ffi(),
content: content.into_ffi(),
state: self.state.into_ffi(),
toolbar,
style: self.style.into(),
background: self.background.into(),
min_size: self.min_size.into_ffi(),
max_size: self.max_size.into_ffi(),
}
}
}
pub type WindowShowFn = unsafe extern "C" fn(context: *mut (), window: WuiWindow);
struct FFIWindowManager {
context: ForeignCallbackContext,
show_fn: WindowShowFn,
}
impl FFIWindowManager {
fn show(&self, window: Window) {
let ffi_window = window.into_ffi();
unsafe {
(self.show_fn)(self.context.data(), ffi_window);
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn waterui_env_install_window_manager(
env: *mut WuiEnv,
context: *mut (),
show_fn: WindowShowFn,
drop_context: unsafe extern "C" fn(*mut ()),
) {
let env = unsafe { crate::borrow_ffi_mut(env) };
let ffi_manager = FFIWindowManager {
context: unsafe { ForeignCallbackContext::new(context, drop_context) },
show_fn,
};
let manager = WindowManager::new(move |window| {
ffi_manager.show(window);
});
env.insert(manager);
}