use crate::sys;
use crate::util::{cstring, ensure_abi, take_string, NotThreadSafe};
use std::os::raw::{c_char, c_void};
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::rc::Rc;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(i32)]
pub enum Theme {
Plain = 0,
#[default]
Bootstrap = 1,
Decius = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum WidgetKind {
None = -1,
Root = 0,
Container = 1,
Text = 2,
RawHtml = 3,
Heading = 4,
Panel = 5,
Button = 6,
Checkbox = 7,
Slider = 8,
Knob = 9,
TextInput = 10,
TextArea = 11,
Dropdown = 12,
ButtonGroup = 13,
VirtualList = 14,
Card = 15,
}
impl WidgetKind {
fn from_raw(v: i32) -> WidgetKind {
match v {
0 => WidgetKind::Root,
1 => WidgetKind::Container,
2 => WidgetKind::Text,
3 => WidgetKind::RawHtml,
4 => WidgetKind::Heading,
5 => WidgetKind::Panel,
6 => WidgetKind::Button,
7 => WidgetKind::Checkbox,
8 => WidgetKind::Slider,
9 => WidgetKind::Knob,
10 => WidgetKind::TextInput,
11 => WidgetKind::TextArea,
12 => WidgetKind::Dropdown,
13 => WidgetKind::ButtonGroup,
14 => WidgetKind::VirtualList,
15 => WidgetKind::Card,
_ => WidgetKind::None,
}
}
}
pub(crate) struct ViewInner {
raw: *mut sys::affineui_view,
_not_send: NotThreadSafe,
}
impl Drop for ViewInner {
fn drop(&mut self) {
unsafe { sys::affineui_view_destroy(self.raw) };
}
}
#[derive(Clone)]
pub struct View {
pub(crate) inner: Rc<ViewInner>,
}
unsafe extern "C" fn 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 click_free(user: *mut c_void) {
drop(Box::from_raw(user as *mut Box<dyn FnMut()>));
}
unsafe extern "C" fn change_trampoline(user: *mut c_void, value: *const c_char) {
let cb = &mut *(user as *mut Box<dyn FnMut(&str)>);
let value = if value.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(value).to_string_lossy().into_owned()
};
if catch_unwind(AssertUnwindSafe(|| cb(&value))).is_err() {
eprintln!("affineui: panic in on_change callback (suppressed)");
}
}
unsafe extern "C" fn change_free(user: *mut c_void) {
drop(Box::from_raw(user as *mut Box<dyn FnMut(&str)>));
}
struct BuildEnv<'a, F: FnOnce(&View)> {
view: &'a View,
f: Option<F>,
}
unsafe extern "C" fn build_trampoline<F: FnOnce(&View)>(
user: *mut c_void,
_view: *mut sys::affineui_view,
) {
let env = &mut *(user as *mut BuildEnv<'_, F>);
if let Some(f) = env.f.take() {
let view = env.view;
if catch_unwind(AssertUnwindSafe(|| f(view))).is_err() {
eprintln!("affineui: panic in build callback (suppressed)");
}
}
}
impl View {
pub fn new(theme: Theme) -> View {
ensure_abi();
let raw = unsafe { sys::affineui_view_create(theme as i32) };
View { inner: Rc::new(ViewInner { raw, _not_send: NotThreadSafe::default() }) }
}
pub(crate) fn raw(&self) -> *mut sys::affineui_view {
self.inner.raw
}
fn wrap(&self, raw: *mut sys::affineui_widget) -> Widget {
Widget { raw, view: Rc::clone(&self.inner), _not_send: NotThreadSafe::default() }
}
fn with_build<F: FnOnce(&View), R>(
&self,
f: F,
call: impl FnOnce(sys::affineui_build_fn, *mut c_void) -> R,
) -> R {
let mut env = BuildEnv { view: self, f: Some(f) };
call(Some(build_trampoline::<F>), &mut env as *mut _ as *mut c_void)
}
pub fn clear(&self) {
unsafe { sys::affineui_view_clear(self.raw()) };
}
pub fn build(&self, f: impl FnOnce(&View)) {
self.begin();
f(self);
self.end();
}
pub fn begin(&self) {
unsafe { sys::affineui_view_begin(self.raw()) };
}
pub fn end(&self) {
unsafe { sys::affineui_view_end(self.raw()) };
}
pub fn set_theme(&self, theme: Theme) {
unsafe { sys::affineui_view_set_theme(self.raw(), theme as i32) };
}
pub fn theme(&self) -> Theme {
match unsafe { sys::affineui_view_get_theme(self.raw()) } {
0 => Theme::Plain,
2 => Theme::Decius,
_ => Theme::Bootstrap,
}
}
pub fn set_framework_version(&self, version: &str) {
let version = cstring(version);
unsafe { sys::affineui_view_set_framework_version(self.raw(), version.as_ptr()) };
}
pub fn selector(&self, name: &str, value: &str) -> &Self {
let name = cstring(name);
let value = cstring(value);
unsafe { sys::affineui_view_selector(self.raw(), name.as_ptr(), value.as_ptr()) };
self
}
pub fn to_html_fragment(&self) -> String {
unsafe { take_string(sys::affineui_view_to_html_fragment(self.raw())) }
}
pub fn to_html_document(&self) -> String {
unsafe { take_string(sys::affineui_view_to_html_document(self.raw())) }
}
pub fn heading(&self, level: i32, text: &str, classes: &str, key: &str) -> Widget {
let (text, classes, key) = (cstring(text), cstring(classes), cstring(key));
self.wrap(unsafe {
sys::affineui_view_heading(self.raw(), level, text.as_ptr(), classes.as_ptr(), key.as_ptr())
})
}
pub fn paragraph(&self, text: &str, classes: &str, key: &str) -> Widget {
let (text, classes, key) = (cstring(text), cstring(classes), cstring(key));
self.wrap(unsafe {
sys::affineui_view_paragraph(self.raw(), text.as_ptr(), classes.as_ptr(), key.as_ptr())
})
}
pub fn text(&self, text: &str, key: &str) -> Widget {
let (text, key) = (cstring(text), cstring(key));
self.wrap(unsafe { sys::affineui_view_text(self.raw(), text.as_ptr(), key.as_ptr()) })
}
pub fn html(&self, markup: &str, key: &str) -> Widget {
let (markup, key) = (cstring(markup), cstring(key));
self.wrap(unsafe { sys::affineui_view_html(self.raw(), markup.as_ptr(), key.as_ptr()) })
}
pub fn button(&self, label: &str, primary: bool, key: &str) -> Widget {
let (label, key) = (cstring(label), cstring(key));
self.wrap(unsafe {
sys::affineui_view_button(self.raw(), label.as_ptr(), primary as i32, key.as_ptr())
})
}
pub fn checkbox(&self, label: &str, checked: bool, key: &str) -> Widget {
let (label, key) = (cstring(label), cstring(key));
self.wrap(unsafe {
sys::affineui_view_checkbox(self.raw(), label.as_ptr(), checked as i32, key.as_ptr())
})
}
pub fn toggle(&self, label: &str, on: bool, key: &str) -> Widget {
let (label, key) = (cstring(label), cstring(key));
self.wrap(unsafe {
sys::affineui_view_toggle(self.raw(), label.as_ptr(), on as i32, key.as_ptr())
})
}
pub fn input(&self, label: &str, value: &str, input_type: &str, key: &str) -> Widget {
let (label, value, ty, key) = (cstring(label), cstring(value), cstring(input_type), cstring(key));
self.wrap(unsafe {
sys::affineui_view_input(self.raw(), label.as_ptr(), value.as_ptr(), ty.as_ptr(), key.as_ptr())
})
}
pub fn password(&self, label: &str, value: &str, key: &str) -> Widget {
let (label, value, key) = (cstring(label), cstring(value), cstring(key));
self.wrap(unsafe {
sys::affineui_view_password(self.raw(), label.as_ptr(), value.as_ptr(), key.as_ptr())
})
}
pub fn textarea(&self, label: &str, value: &str, rows: i32, key: &str) -> Widget {
let (label, value, key) = (cstring(label), cstring(value), cstring(key));
self.wrap(unsafe {
sys::affineui_view_textarea(self.raw(), label.as_ptr(), value.as_ptr(), rows, key.as_ptr())
})
}
pub fn dropdown(&self, label: &str, options: &[&str], selected: &str, key: &str) -> Widget {
let (label, selected, key) = (cstring(label), cstring(selected), cstring(key));
let opts: Vec<_> = options.iter().map(|o| cstring(o)).collect();
let ptrs: Vec<*const c_char> = opts.iter().map(|o| o.as_ptr()).collect();
self.wrap(unsafe {
sys::affineui_view_dropdown(
self.raw(), label.as_ptr(), ptrs.as_ptr(), ptrs.len(), selected.as_ptr(), key.as_ptr(),
)
})
}
pub fn button_group(&self, label: &str, options: &[&str], selected: &str, key: &str) -> Widget {
let (label, selected, key) = (cstring(label), cstring(selected), cstring(key));
let opts: Vec<_> = options.iter().map(|o| cstring(o)).collect();
let ptrs: Vec<*const c_char> = opts.iter().map(|o| o.as_ptr()).collect();
self.wrap(unsafe {
sys::affineui_view_button_group(
self.raw(), label.as_ptr(), ptrs.as_ptr(), ptrs.len(), selected.as_ptr(), key.as_ptr(),
)
})
}
pub fn slider(&self, label: &str, value: f64, min: f64, max: f64, key: &str) -> Widget {
let (label, key) = (cstring(label), cstring(key));
self.wrap(unsafe {
sys::affineui_view_slider(self.raw(), label.as_ptr(), value, min, max, key.as_ptr())
})
}
pub fn knob(&self, label: &str, value: f64, min: f64, max: f64, bipolar: bool, key: &str) -> Widget {
let (label, key) = (cstring(label), cstring(key));
self.wrap(unsafe {
sys::affineui_view_knob(self.raw(), label.as_ptr(), value, min, max, bipolar as i32, key.as_ptr())
})
}
pub fn combo(&self, label: &str, value: f64, step: f64, key: &str) -> Widget {
let (label, key) = (cstring(label), cstring(key));
self.wrap(unsafe {
sys::affineui_view_combo(self.raw(), label.as_ptr(), value, step, key.as_ptr())
})
}
pub fn color_field(&self, label: &str, value: &str, swatches: &[&str], key: &str) -> Widget {
let (label, value, key) = (cstring(label), cstring(value), cstring(key));
let sw: Vec<_> = swatches.iter().map(|s| cstring(s)).collect();
let ptrs: Vec<*const c_char> = sw.iter().map(|s| s.as_ptr()).collect();
self.wrap(unsafe {
sys::affineui_view_color_field(
self.raw(), label.as_ptr(), value.as_ptr(), ptrs.as_ptr(), ptrs.len(), key.as_ptr(),
)
})
}
pub fn colorfield(&self, label: &str, value: &str, key: &str) -> Widget {
let (label, value, key) = (cstring(label), cstring(value), cstring(key));
self.wrap(unsafe {
sys::affineui_view_colorfield(self.raw(), label.as_ptr(), value.as_ptr(), key.as_ptr())
})
}
pub fn container(&self, classes: &str, key: &str, build: impl FnOnce(&View)) -> Widget {
let (classes, key) = (cstring(classes), cstring(key));
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_container(self.raw(), classes.as_ptr(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn element(&self, tag: &str, classes: &str, key: &str, build: impl FnOnce(&View)) -> Widget {
let (tag, classes, key) = (cstring(tag), cstring(classes), cstring(key));
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_element(self.raw(), tag.as_ptr(), classes.as_ptr(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn panel(&self, key: &str, build: impl FnOnce(&View)) -> Widget {
let key = cstring(key);
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_panel(self.raw(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn card(&self, title: &str, classes: &str, key: &str, build: impl FnOnce(&View)) -> Widget {
let (title, classes, key) = (cstring(title), cstring(classes), cstring(key));
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_card(self.raw(), title.as_ptr(), classes.as_ptr(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn toolbar(&self, key: &str, build: impl FnOnce(&View)) -> Widget {
let key = cstring(key);
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_toolbar(self.raw(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn menu_bar(&self, key: &str, build: impl FnOnce(&View)) -> Widget {
let key = cstring(key);
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_menu_bar(self.raw(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn status_bar(&self, key: &str, build: impl FnOnce(&View)) -> Widget {
let key = cstring(key);
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_status_bar(self.raw(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn tree(&self, key: &str, build: impl FnOnce(&View)) -> Widget {
let key = cstring(key);
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_tree(self.raw(), key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn foldout(&self, title: &str, expanded: bool, key: &str, build: impl FnOnce(&View)) -> Widget {
let (title, key) = (cstring(title), cstring(key));
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_foldout(self.raw(), title.as_ptr(), expanded as i32, key.as_ptr(), cb, user)
});
self.wrap(raw)
}
pub fn toolbar_separator(&self, key: &str) -> Widget {
let key = cstring(key);
self.wrap(unsafe { sys::affineui_view_toolbar_separator(self.raw(), key.as_ptr()) })
}
pub fn icon_button(&self, icon: &str, key: &str) -> Widget {
let (icon, key) = (cstring(icon), cstring(key));
self.wrap(unsafe { sys::affineui_view_icon_button(self.raw(), icon.as_ptr(), key.as_ptr()) })
}
pub fn menu_button(&self, label: &str, key: &str, build: impl FnOnce(&View)) -> Widget {
let (label, key) = (cstring(label), cstring(key));
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_menu_button(self.raw(), label.as_ptr(), cb, user, key.as_ptr())
});
self.wrap(raw)
}
pub fn menu_item(&self, label: &str, icon: &str, shortcut: &str, key: &str) -> Widget {
let (label, icon, shortcut, key) = (cstring(label), cstring(icon), cstring(shortcut), cstring(key));
self.wrap(unsafe {
sys::affineui_view_menu_item(self.raw(), label.as_ptr(), icon.as_ptr(), shortcut.as_ptr(), key.as_ptr())
})
}
pub fn menu_separator(&self, key: &str) -> Widget {
let key = cstring(key);
self.wrap(unsafe { sys::affineui_view_menu_separator(self.raw(), key.as_ptr()) })
}
pub fn submenu(&self, label: &str, icon: &str, key: &str, build: impl FnOnce(&View)) -> Widget {
let (label, icon, key) = (cstring(label), cstring(icon), cstring(key));
let raw = self.with_build(build, |cb, user| unsafe {
sys::affineui_view_submenu(self.raw(), label.as_ptr(), cb, user, icon.as_ptr(), key.as_ptr())
});
self.wrap(raw)
}
pub fn menu_brand(&self, title: &str, icon: &str, key: &str) -> Widget {
let (title, icon, key) = (cstring(title), cstring(icon), cstring(key));
self.wrap(unsafe {
sys::affineui_view_menu_brand(self.raw(), title.as_ptr(), icon.as_ptr(), key.as_ptr())
})
}
pub fn menu_spacer(&self, key: &str) -> Widget {
let key = cstring(key);
self.wrap(unsafe { sys::affineui_view_menu_spacer(self.raw(), key.as_ptr()) })
}
pub fn menu_meta(&self, text: &str, key: &str) -> Widget {
let (text, key) = (cstring(text), cstring(key));
self.wrap(unsafe { sys::affineui_view_menu_meta(self.raw(), text.as_ptr(), key.as_ptr()) })
}
pub fn tree_row(&self, label: &str, selected: bool, depth: i32, key: &str) -> Widget {
let (label, key) = (cstring(label), cstring(key));
self.wrap(unsafe {
sys::affineui_view_tree_row(self.raw(), label.as_ptr(), selected as i32, depth, key.as_ptr())
})
}
pub fn splitter(&self, horizontal: bool, key: &str) -> Widget {
let key = cstring(key);
self.wrap(unsafe {
sys::affineui_view_splitter(self.raw(), horizontal as i32, key.as_ptr())
})
}
pub fn find_widget(&self, name: &str) -> Widget {
let name = cstring(name);
self.wrap(unsafe { sys::affineui_view_find_widget(self.raw(), name.as_ptr()) })
}
}
pub struct Widget {
raw: *mut sys::affineui_widget,
pub(crate) view: Rc<ViewInner>,
_not_send: NotThreadSafe,
}
impl Drop for Widget {
fn drop(&mut self) {
unsafe { sys::affineui_widget_destroy(self.raw) };
}
}
impl Widget {
pub(crate) fn view_handle(&self) -> View {
View { inner: Rc::clone(&self.view) }
}
pub fn is_valid(&self) -> bool {
unsafe { sys::affineui_widget_valid(self.raw) != 0 }
}
pub fn kind(&self) -> WidgetKind {
WidgetKind::from_raw(unsafe { sys::affineui_widget_get_kind(self.raw) })
}
pub fn name(&self) -> String {
unsafe { take_string(sys::affineui_widget_name(self.raw)) }
}
pub fn attr(&self, name: &str, fallback: &str) -> String {
let (name, fallback) = (cstring(name), cstring(fallback));
unsafe { take_string(sys::affineui_widget_attr(self.raw, name.as_ptr(), fallback.as_ptr())) }
}
pub fn text(&self) -> String {
unsafe { take_string(sys::affineui_widget_text(self.raw)) }
}
pub fn has_attr(&self, name: &str) -> bool {
let name = cstring(name);
unsafe { sys::affineui_widget_has_attr(self.raw, name.as_ptr()) != 0 }
}
pub fn set_text(&self, text: &str) -> &Self {
let text = cstring(text);
unsafe { sys::affineui_widget_set_text(self.raw, text.as_ptr()) };
self
}
pub fn set_attr(&self, name: &str, value: &str) -> &Self {
let (name, value) = (cstring(name), cstring(value));
unsafe { sys::affineui_widget_set_attr(self.raw, name.as_ptr(), value.as_ptr()) };
self
}
pub fn remove_attr(&self, name: &str) -> &Self {
let name = cstring(name);
unsafe { sys::affineui_widget_remove_attr(self.raw, name.as_ptr()) };
self
}
pub fn set_selector(&self, name: &str, value: &str) -> &Self {
let (name, value) = (cstring(name), cstring(value));
unsafe { sys::affineui_widget_set_selector(self.raw, name.as_ptr(), value.as_ptr()) };
self
}
pub fn add_class(&self, classes: &str) -> &Self {
let classes = cstring(classes);
unsafe { sys::affineui_widget_add_class(self.raw, classes.as_ptr()) };
self
}
pub fn clear(&self) -> &Self {
unsafe { sys::affineui_widget_clear(self.raw) };
self
}
pub fn on_click(&self, f: impl FnMut() + 'static) -> &Self {
let boxed: Box<dyn FnMut()> = Box::new(f);
let user = Box::into_raw(Box::new(boxed)) as *mut c_void;
unsafe {
sys::affineui_widget_on_click(self.raw, Some(click_trampoline), user, Some(click_free));
}
self
}
pub fn on_change(&self, f: impl FnMut(&str) + 'static) -> &Self {
let boxed: Box<dyn FnMut(&str)> = Box::new(f);
let user = Box::into_raw(Box::new(boxed)) as *mut c_void;
unsafe {
sys::affineui_widget_on_change(self.raw, Some(change_trampoline), user, Some(change_free));
}
self
}
pub fn append<F: FnOnce(&View)>(&self, build: F) -> &Self {
let view = self.view_handle();
let mut env = BuildEnv { view: &view, f: Some(build) };
unsafe {
sys::affineui_widget_append(
self.raw,
Some(build_trampoline::<F>),
&mut env as *mut _ as *mut c_void,
);
}
self
}
pub fn replace<F: FnOnce(&View)>(&self, build: F) -> &Self {
let view = self.view_handle();
let mut env = BuildEnv { view: &view, f: Some(build) };
unsafe {
sys::affineui_widget_replace(
self.raw,
Some(build_trampoline::<F>),
&mut env as *mut _ as *mut c_void,
);
}
self
}
pub fn find_widget(&self, name: &str) -> Widget {
let name = cstring(name);
let raw = unsafe { sys::affineui_widget_find_widget(self.raw, name.as_ptr()) };
Widget { raw, view: Rc::clone(&self.view), _not_send: NotThreadSafe::default() }
}
}