use crate::event::{Color, Event};
use crate::sys;
use crate::util::{cstring, ensure_abi, NotThreadSafe};
use std::os::raw::c_void;
use std::panic::{catch_unwind, AssertUnwindSafe};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum Backend {
D3d11 = 0,
Metal = 1,
Gl = 2,
Wgpu = 3,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum PixelFormat {
#[default]
Default = 0,
Rgba8 = 1,
Bgra8 = 2,
Depth = 3,
DepthStencil = 4,
}
#[derive(Clone, Copy, Debug)]
pub struct GpuContext {
pub backend: Backend,
pub d3d11_device: *const c_void,
pub d3d11_device_context: *const c_void,
pub metal_device: *const c_void,
pub wgpu_device: *const c_void,
pub color_format: PixelFormat,
pub depth_format: PixelFormat,
pub sample_count: i32,
}
impl GpuContext {
fn empty(backend: Backend) -> GpuContext {
GpuContext {
backend,
d3d11_device: std::ptr::null(),
d3d11_device_context: std::ptr::null(),
metal_device: std::ptr::null(),
wgpu_device: std::ptr::null(),
color_format: PixelFormat::Default,
depth_format: PixelFormat::Default,
sample_count: 1,
}
}
pub fn d3d11(device: *const c_void, context: *const c_void) -> GpuContext {
GpuContext { d3d11_device: device, d3d11_device_context: context, ..GpuContext::empty(Backend::D3d11) }
}
pub fn metal(device: *const c_void) -> GpuContext {
GpuContext { metal_device: device, ..GpuContext::empty(Backend::Metal) }
}
pub fn gl() -> GpuContext {
GpuContext::empty(Backend::Gl)
}
pub fn wgpu(device: *const c_void) -> GpuContext {
GpuContext { wgpu_device: device, ..GpuContext::empty(Backend::Wgpu) }
}
pub fn color_format(mut self, f: PixelFormat) -> GpuContext {
self.color_format = f;
self
}
pub fn depth_format(mut self, f: PixelFormat) -> GpuContext {
self.depth_format = f;
self
}
pub fn sample_count(mut self, n: i32) -> GpuContext {
self.sample_count = n;
self
}
fn to_sys(self) -> sys::affineui_gpu_context {
sys::affineui_gpu_context {
backend: self.backend as i32,
d3d11_device: self.d3d11_device,
d3d11_device_context: self.d3d11_device_context,
metal_device: self.metal_device,
wgpu_device: self.wgpu_device,
color_format: self.color_format as i32,
depth_format: self.depth_format as i32,
sample_count: self.sample_count,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct FrameTarget {
pub width: i32,
pub height: i32,
pub dpi_scale: f32,
pub sample_count: i32,
pub clear: bool,
pub viewport: Option<(i32, i32, i32, i32)>,
pub d3d11_render_view: *const c_void,
pub d3d11_resolve_view: *const c_void,
pub d3d11_depth_stencil_view: *const c_void,
pub metal_current_drawable: *const c_void,
pub metal_depth_stencil_texture: *const c_void,
pub metal_msaa_color_texture: *const c_void,
pub wgpu_render_view: *const c_void,
pub wgpu_resolve_view: *const c_void,
pub wgpu_depth_stencil_view: *const c_void,
pub gl_framebuffer: u32,
}
impl FrameTarget {
fn empty(width: i32, height: i32, dpi_scale: f32) -> FrameTarget {
FrameTarget {
width,
height,
dpi_scale,
sample_count: 1,
clear: true,
viewport: None,
d3d11_render_view: std::ptr::null(),
d3d11_resolve_view: std::ptr::null(),
d3d11_depth_stencil_view: std::ptr::null(),
metal_current_drawable: std::ptr::null(),
metal_depth_stencil_texture: std::ptr::null(),
metal_msaa_color_texture: std::ptr::null(),
wgpu_render_view: std::ptr::null(),
wgpu_resolve_view: std::ptr::null(),
wgpu_depth_stencil_view: std::ptr::null(),
gl_framebuffer: 0,
}
}
pub fn d3d11(
rtv: *const c_void,
dsv: *const c_void,
width: i32,
height: i32,
dpi_scale: f32,
) -> FrameTarget {
FrameTarget {
d3d11_render_view: rtv,
d3d11_depth_stencil_view: dsv,
..FrameTarget::empty(width, height, dpi_scale)
}
}
pub fn metal(drawable: *const c_void, width: i32, height: i32, dpi_scale: f32) -> FrameTarget {
FrameTarget { metal_current_drawable: drawable, ..FrameTarget::empty(width, height, dpi_scale) }
}
pub fn gl(framebuffer: u32, width: i32, height: i32, dpi_scale: f32) -> FrameTarget {
FrameTarget { gl_framebuffer: framebuffer, ..FrameTarget::empty(width, height, dpi_scale) }
}
pub fn wgpu(view: *const c_void, width: i32, height: i32, dpi_scale: f32) -> FrameTarget {
FrameTarget { wgpu_render_view: view, ..FrameTarget::empty(width, height, dpi_scale) }
}
pub fn viewport(mut self, x: i32, y: i32, w: i32, h: i32) -> FrameTarget {
self.viewport = Some((x, y, w, h));
self
}
pub fn sample_count(mut self, n: i32) -> FrameTarget {
self.sample_count = n;
self
}
pub fn clear(mut self, on: bool) -> FrameTarget {
self.clear = on;
self
}
fn to_sys(self) -> sys::affineui_frame_target {
let (vx, vy, vw, vh) = self.viewport.unwrap_or((0, 0, 0, 0));
sys::affineui_frame_target {
width: self.width,
height: self.height,
dpi_scale: self.dpi_scale,
sample_count: self.sample_count,
clear: self.clear as i32,
viewport_x: vx,
viewport_y: vy,
viewport_w: vw,
viewport_h: vh,
d3d11_render_view: self.d3d11_render_view,
d3d11_resolve_view: self.d3d11_resolve_view,
d3d11_depth_stencil_view: self.d3d11_depth_stencil_view,
metal_current_drawable: self.metal_current_drawable,
metal_depth_stencil_texture: self.metal_depth_stencil_texture,
metal_msaa_color_texture: self.metal_msaa_color_texture,
wgpu_render_view: self.wgpu_render_view,
wgpu_resolve_view: self.wgpu_resolve_view,
wgpu_depth_stencil_view: self.wgpu_depth_stencil_view,
gl_framebuffer: self.gl_framebuffer,
}
}
}
unsafe extern "C" fn ui_click_trampoline(user: *mut c_void) {
let cb = &mut *(user as *mut Box<dyn FnMut()>);
if catch_unwind(AssertUnwindSafe(&mut *cb)).is_err() {
eprintln!("affineui: panic in on_click callback (suppressed)");
}
}
unsafe extern "C" fn ui_click_free(user: *mut c_void) {
drop(Box::from_raw(user as *mut Box<dyn FnMut()>));
}
pub struct Ui {
raw: *mut sys::affineui_ui,
_not_send: NotThreadSafe,
}
impl Drop for Ui {
fn drop(&mut self) {
unsafe { sys::affineui_ui_destroy(self.raw) };
}
}
impl Ui {
pub fn new() -> Ui {
ensure_abi();
Ui { raw: unsafe { sys::affineui_ui_create() }, _not_send: NotThreadSafe::default() }
}
pub unsafe fn init(&self, gpu: Option<&GpuContext>, font: Option<(&str, i32)>) {
let gpu_sys = gpu.map(|g| g.to_sys());
let font_name = font.map(|(family, _)| cstring(family));
let desc = sys::affineui_init_desc {
gpu: gpu_sys.as_ref().map_or(std::ptr::null(), |g| g as *const _),
allocator: std::ptr::null(),
log: None,
log_user: std::ptr::null_mut(),
default_font_family: font_name.as_ref().map_or(std::ptr::null(), |f| f.as_ptr()),
default_font_size: font.map_or(0, |(_, size)| size),
};
sys::affineui_ui_init(self.raw, &desc);
}
pub fn set_html(&self, html: &str) {
let html = cstring(html);
unsafe { sys::affineui_ui_set_html(self.raw, html.as_ptr()) };
}
pub fn set_css(&self, css: &str) {
let css = cstring(css);
unsafe { sys::affineui_ui_set_css(self.raw, css.as_ptr()) };
}
pub fn load_file(&self, path: &str) -> bool {
let path = cstring(path);
unsafe { sys::affineui_ui_load_file(self.raw, path.as_ptr()) != 0 }
}
pub fn set_clear_color(&self, c: Color) {
unsafe { sys::affineui_ui_set_clear_color(self.raw, c.r, c.g, c.b, c.a) };
}
pub unsafe fn render(&self, target: &FrameTarget) {
let t = target.to_sys();
sys::affineui_ui_render(self.raw, &t);
}
pub fn dispatch(&self, ev: &Event) -> bool {
ev.with_sys(|sys_ev| unsafe { sys::affineui_ui_dispatch(self.raw, sys_ev) != 0 })
}
pub fn on_click(&self, selector: &str, f: impl FnMut() + 'static) {
let selector = cstring(selector);
let boxed: Box<dyn FnMut()> = Box::new(f);
let user = Box::into_raw(Box::new(boxed)) as *mut c_void;
unsafe {
sys::affineui_ui_on_click(
self.raw,
selector.as_ptr(),
Some(ui_click_trampoline),
user,
Some(ui_click_free),
);
}
}
pub fn hovered_cursor(&self) -> i32 {
unsafe { sys::affineui_ui_hovered_cursor(self.raw) }
}
pub fn set_attr(&self, elem_id: &str, name: &str, value: &str) -> bool {
let (id, name, value) = (cstring(elem_id), cstring(name), cstring(value));
unsafe { sys::affineui_ui_set_attr(self.raw, id.as_ptr(), name.as_ptr(), value.as_ptr()) != 0 }
}
pub fn remove_attr(&self, elem_id: &str, name: &str) -> bool {
let (id, name) = (cstring(elem_id), cstring(name));
unsafe { sys::affineui_ui_remove_attr(self.raw, id.as_ptr(), name.as_ptr()) != 0 }
}
pub fn set_text(&self, elem_id: &str, text: &str) -> bool {
let (id, text) = (cstring(elem_id), cstring(text));
unsafe { sys::affineui_ui_set_text(self.raw, id.as_ptr(), text.as_ptr()) != 0 }
}
pub fn content_size(&self) -> (i32, i32) {
let (mut w, mut h) = (0, 0);
unsafe { sys::affineui_ui_content_size(self.raw, &mut w, &mut h) };
(w, h)
}
pub fn needs_update(&self) -> bool {
unsafe { sys::affineui_ui_needs_update(self.raw) != 0 }
}
pub fn mark_dirty(&self) {
unsafe { sys::affineui_ui_mark_dirty(self.raw) };
}
pub fn reset(&self) {
unsafe { sys::affineui_ui_reset(self.raw) };
}
}
impl Default for Ui {
fn default() -> Ui {
Ui::new()
}
}