use crate::compat::HashMap;
use crate::compat::Mutex;
use crate::compat::OnceLock;
use crate::control_backend::get_control_backend;
use crate::{c_try, c_try_void};
use alloc::boxed::Box;
use alloc::ffi::CString;
use core::ffi::{c_char, c_float, c_int, c_uint, CStr};
type CBool = bool;
fn harmony_node_registry() -> &'static Mutex<HashMap<u64, u64>> {
static REGISTRY: OnceLock<Mutex<HashMap<u64, u64>>> = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
fn harmony_lookup_widget(node_handle: u64) -> Option<u64> {
if node_handle == 0 {
return None;
}
harmony_node_registry().lock().unwrap_or_else(|e| e.into_inner()).get(&node_handle).copied()
}
fn trigger_kind_from_code(code: c_uint) -> crate::platform::WidgetTriggerKind {
match code {
1 => crate::platform::WidgetTriggerKind::Clicked,
2 => crate::platform::WidgetTriggerKind::ValueChanged,
3 => crate::platform::WidgetTriggerKind::SelectionChanged,
4 => crate::platform::WidgetTriggerKind::Closed,
_ => crate::platform::WidgetTriggerKind::Unknown,
}
}
fn capability_contract_mask(contract: crate::platform::CapabilityContract) -> c_uint {
match contract {
crate::platform::CapabilityContract::Native(native) => {
let mut mask: c_uint = 0;
mask |= 1 << 0;
if native.dpi_scaling {
mask |= 1 << 1;
}
if native.ime {
mask |= 1 << 2;
}
if native.accessibility {
mask |= 1 << 3;
}
if native.native_menu {
mask |= 1 << 4;
}
if native.typed_widget_trigger {
mask |= 1 << 5;
}
mask
}
crate::platform::CapabilityContract::Embedded(embedded) => {
let mut mask: c_uint = 0;
if embedded.fixed_dpi {
mask |= 1 << 1;
}
if embedded.low_memory_mode {
mask |= 1 << 2;
}
if embedded.typed_widget_trigger {
mask |= 1 << 3;
}
mask
}
}
}
fn c_str_or_default(ptr: *const c_char) -> String {
if ptr.is_null() {
log::warn!("[bindings] c_str_or_default: received null C string pointer");
return String::new();
}
unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }
}
fn to_c_string_or_empty(s: impl Into<String>) -> *const c_char {
let owned: String = s.into();
match CString::new(owned) {
Ok(cs) => cs.into_raw(),
Err(nul_err) => {
let pos = nul_err.nul_position();
log::warn!(
"[bindings] CString::new failed (interior NUL at position {pos}), truncating"
);
CString::new("").unwrap().into_raw()
}
}
}
#[no_mangle]
pub extern "C" fn rw_init() {
c_try_void!({
crate::init();
})
}
#[no_mangle]
pub extern "C" fn rw_run() {
c_try_void!({
crate::run();
})
}
#[no_mangle]
pub extern "C" fn rw_quit() {
c_try_void!({
crate::quit();
})
}
#[no_mangle]
pub extern "C" fn rw_destroy_widget(widget_id: u64) -> CBool {
c_try!({ get_control_backend().destroy_widget(widget_id) })
}
#[no_mangle]
pub extern "C" fn rw_create_window(
title: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_window(&c_str_or_default(title), x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_button(
parent: u64,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_button(parent, &c_str_or_default(text), x, y, width, height)
})
}
#[no_mangle]
pub extern "C" fn rw_create_checkbox(
parent: u64,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_checkbox(parent, &c_str_or_default(text), x, y, width, height)
})
}
#[no_mangle]
pub extern "C" fn rw_create_line_edit(
parent: u64,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_line_edit(parent, &c_str_or_default(text), x, y, width, height)
})
}
#[no_mangle]
pub extern "C" fn rw_create_label(
parent: u64,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_label(parent, &c_str_or_default(text), x, y, width, height)
})
}
#[no_mangle]
pub extern "C" fn rw_create_radio_button(
parent: u64,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_radio_button(
parent,
&c_str_or_default(text),
x,
y,
width,
height,
)
})
}
#[no_mangle]
pub extern "C" fn rw_create_slider(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_slider(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_progress_bar(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_progress_bar(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_combo_box(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_combo_box(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_list_box(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_list_box(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_panel(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_panel(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_message_box(
parent: u64,
title: *const c_char,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_message_box(
parent,
&c_str_or_default(title),
&c_str_or_default(text),
x,
y,
width,
height,
)
})
}
#[no_mangle]
pub extern "C" fn rw_create_file_dialog(
parent: u64,
title: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_file_dialog(
parent,
&c_str_or_default(title),
x,
y,
width,
height,
)
})
}
#[no_mangle]
pub extern "C" fn rw_create_color_dialog(
parent: u64,
title: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_color_dialog(
parent,
&c_str_or_default(title),
x,
y,
width,
height,
)
})
}
#[no_mangle]
pub extern "C" fn rw_create_font_dialog(
parent: u64,
title: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_font_dialog(
parent,
&c_str_or_default(title),
x,
y,
width,
height,
)
})
}
#[no_mangle]
pub extern "C" fn rw_create_spin_box(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_spin_box(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_list_view(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_list_view(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_scroll_area(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_scroll_area(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_set_widget_geometry(
widget_id: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) {
c_try_void!({
get_control_backend().set_widget_geometry(widget_id, x, y, width, height);
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_get_widget_geometry(
widget_id: u64,
x_out: *mut c_int,
y_out: *mut c_int,
width_out: *mut c_uint,
height_out: *mut c_uint,
) -> CBool {
c_try!({
let geo = get_control_backend().get_widget_geometry(widget_id);
if let Some((x, y, w, h)) = geo {
unsafe {
if !x_out.is_null() {
*x_out = x;
}
if !y_out.is_null() {
*y_out = y;
}
if !width_out.is_null() {
*width_out = w;
}
if !height_out.is_null() {
*height_out = h;
}
}
true
} else {
false
}
})
}
#[no_mangle]
pub extern "C" fn rw_create_widget_of_kind(
parent: u64,
kind_name: *const c_char,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_widget(
&c_str_or_default(kind_name),
parent,
&c_str_or_default(text),
x,
y,
width,
height,
)
})
}
#[no_mangle]
pub extern "C" fn rw_widget_kind_names(out: *mut c_char, cap: c_uint) -> c_uint {
c_try!({
#[cfg(full_widgets)]
let names = crate::widget::capability::WidgetFactory::new_with_defaults().widget_names();
#[cfg(not(full_widgets))]
let names: Vec<&str> = Vec::new();
write_space_separated(&names, out, cap)
})
}
#[no_mangle]
pub extern "C" fn rw_widget_property_names(
widget_id: u64,
out: *mut c_char,
cap: c_uint,
) -> c_uint {
c_try!({
let names = crate::widget::runtime::with_widget(widget_id, |widget| {
crate::widget::capability::widget_property_names(widget).map(|names| names.to_vec())
})
.flatten();
match names {
Some(names) => write_space_separated(&names, out, cap),
None => 0,
}
})
}
#[cfg(not(stripped_widgets))]
#[no_mangle]
pub unsafe extern "C" fn rw_widget_property_tokens(
widget_id: u64,
name: *const c_char,
out: *mut c_char,
cap: c_uint,
) -> c_uint {
c_try!({
if name.is_null() {
return 0;
}
let name = c_str_or_default(name);
let tokens = crate::widget::runtime::with_widget(widget_id, |widget| {
crate::widget::capability::properties_trait::widget_property_tokens(widget, &name)
.to_vec()
});
match tokens {
Some(tokens) => write_space_separated(&tokens, out, cap),
None => 0,
}
})
}
fn write_space_separated(items: &[&str], out: *mut c_char, cap: c_uint) -> c_uint {
let joined = items.join(" ");
let bytes = joined.as_bytes();
let required = bytes.len() as c_uint;
if out.is_null() || cap == 0 {
return required;
}
let writable = (cap as usize).saturating_sub(1).min(bytes.len());
unsafe {
core::ptr::copy_nonoverlapping(bytes.as_ptr(), out.cast::<u8>(), writable);
*out.add(writable) = 0;
}
required
}
#[no_mangle]
pub unsafe extern "C" fn rw_get_widget_property(
widget_id: u64,
name: *const c_char,
out_kind: *mut c_int,
out_num: *mut i64,
out_str: *mut *mut c_char,
) -> CBool {
c_try!({
let property = c_str_or_default(name);
match crate::widget::capability::read_widget_property_by_id(widget_id, &property) {
Ok(value) => {
let (kind, num, text) = encode_capability_value(value);
unsafe {
if !out_kind.is_null() {
*out_kind = kind;
}
if !out_num.is_null() {
*out_num = num;
}
if !out_str.is_null() {
*out_str = text.unwrap_or(core::ptr::null_mut());
}
}
true
}
Err(error) => {
crate::error::ffi::record_capability_error(error);
false
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_set_widget_property(
widget_id: u64,
name: *const c_char,
kind: c_int,
num: i64,
str_value: *const c_char,
) -> CBool {
c_try!({
let property = c_str_or_default(name);
let value = decode_capability_value(kind, num, str_value);
let Some(value) = value else {
crate::error::ffi::record_capability_error(
crate::widget::capability::CapabilityAccessError::TypeMismatch,
);
return false;
};
match crate::widget::capability::write_widget_property_by_id(widget_id, &property, value) {
Ok(()) => true,
Err(error) => {
crate::error::ffi::record_capability_error(error);
false
}
}
})
}
#[no_mangle]
pub extern "C" fn rw_set_theme(name: *const c_char) -> CBool {
c_try!({
let requested = c_str_or_default(name);
#[cfg(device_profile)]
{
let activated = crate::theme::global_theme_manager().set_theme(&requested);
if activated {
crate::reapply_active_theme();
}
activated
}
#[cfg(not(device_profile))]
{
let _ = requested;
false
}
})
}
#[no_mangle]
pub extern "C" fn rw_theme_names(out: *mut c_char, cap: c_uint) -> c_uint {
c_try!({
#[cfg(device_profile)]
let names = {
let manager = crate::theme::global_theme_manager();
manager.theme_names().iter().map(|name| name.to_string()).collect::<Vec<_>>()
};
#[cfg(not(device_profile))]
let names: Vec<alloc::string::String> = Vec::new();
let refs: Vec<&str> = names.iter().map(alloc::string::String::as_str).collect();
write_space_separated(&refs, out, cap)
})
}
#[no_mangle]
pub extern "C" fn rw_set_high_contrast(mode: c_int) {
c_try_void!({
let enabled = mode != 0;
let mode = if enabled {
crate::style::HighContrastMode::WhiteOnBlack
} else {
crate::style::HighContrastMode::None
};
#[cfg(device_profile)]
{
crate::theme::set_global_high_contrast(mode);
crate::reapply_active_theme();
}
#[cfg(not(device_profile))]
{
let _ = mode;
}
})
}
pub const RW_SCROLL_TO_TOP: c_int = 0;
pub const RW_SCROLL_TO_BOTTOM: c_int = 1;
pub const RW_SCROLL_TO_LEFT: c_int = 2;
pub const RW_SCROLL_TO_RIGHT: c_int = 3;
#[no_mangle]
pub extern "C" fn rw_widget_set_scroll_position(widget_id: u64, x: c_int, y: c_int) -> CBool {
c_try!({
let applied = crate::widget::runtime::with_widget_mut(widget_id, |widget| {
match crate::widget::capability::coercion::widget_as_mut::<crate::widget::ScrollArea>(
widget,
) {
Some(area) => {
area.set_scroll_position(x, y);
true
}
None => false,
}
})
.unwrap_or(false);
if applied {
crate::widget::runtime::request_repaint(widget_id);
}
applied
})
}
#[no_mangle]
pub extern "C" fn rw_widget_scroll_to(widget_id: u64, where_: c_int) -> CBool {
c_try!({
let applied = crate::widget::runtime::with_widget_mut(widget_id, |widget| {
let Some(area) = crate::widget::capability::coercion::widget_as_mut::<
crate::widget::ScrollArea,
>(widget) else {
return false;
};
match where_ {
RW_SCROLL_TO_TOP => area.scroll_to_top(),
RW_SCROLL_TO_BOTTOM => area.scroll_to_bottom(),
RW_SCROLL_TO_LEFT => area.scroll_to_left(),
RW_SCROLL_TO_RIGHT => area.scroll_to_right(),
_ => return false,
}
true
})
.unwrap_or(false);
if applied {
crate::widget::runtime::request_repaint(widget_id);
}
applied
})
}
#[no_mangle]
pub extern "C" fn rw_widget_list_add(widget_id: u64, text: *const c_char) -> c_uint {
c_try!({
let item = c_str_or_default(text);
let added = crate::widget::runtime::with_widget_mut(widget_id, |widget| {
crate::widget::capability::append_widget_list_item(widget, item.clone())
})
.unwrap_or(false);
if !added {
return 0;
}
crate::widget::runtime::request_repaint(widget_id);
rw_widget_list_count(widget_id)
})
}
#[no_mangle]
pub extern "C" fn rw_widget_list_clear(widget_id: u64) -> CBool {
c_try!({
let cleared = crate::widget::runtime::with_widget_mut(widget_id, |widget| {
crate::widget::capability::clear_widget_list_items(widget)
})
.unwrap_or(false);
if cleared {
crate::widget::runtime::request_repaint(widget_id);
}
cleared
})
}
#[no_mangle]
pub extern "C" fn rw_widget_list_count(widget_id: u64) -> c_uint {
c_try!({
crate::widget::runtime::with_widget(widget_id, |widget| {
crate::widget::capability::widget_list_item_count(widget)
})
.unwrap_or(0) as c_uint
})
}
#[no_mangle]
pub extern "C" fn rw_widget_list_item(
widget_id: u64,
index: c_uint,
out: *mut c_char,
cap: c_uint,
) -> c_uint {
c_try!({
let text = crate::widget::runtime::with_widget(widget_id, |widget| {
crate::widget::capability::widget_list_item(widget, index as usize)
})
.flatten();
match text {
Some(text) => write_c_string(&text, out, cap),
None => 0,
}
})
}
fn write_c_string(text: &str, out: *mut c_char, cap: c_uint) -> c_uint {
let bytes = text.as_bytes();
let required = bytes.len() as c_uint;
if out.is_null() || cap == 0 {
return required;
}
let writable = (cap as usize).saturating_sub(1).min(bytes.len());
unsafe {
core::ptr::copy_nonoverlapping(bytes.as_ptr(), out.cast::<u8>(), writable);
*out.add(writable) = 0;
}
required
}
#[no_mangle]
pub unsafe extern "C" fn rw_widget_set_layout(
parent: u64,
kind_name: *const c_char,
spacing: c_int,
margin: c_int,
) -> CBool {
c_try!({
let name = c_str_or_default(kind_name);
if !crate::widget::runtime::is_mounted(parent) {
crate::error::ffi::record_capability_error(
crate::widget::capability::CapabilityAccessError::UnknownWidget,
);
return false;
}
let spec = serde_json::json!({
"type": name,
"spacing": spacing,
"margin": margin,
});
#[cfg(all(device_profile, feature = "serde_json"))]
{
match crate::json::parse_layout_kind(&spec)
.map(|kind| crate::json::create_layout_from_kind(&kind))
{
Ok(layout) => {
crate::layout::declarative::store_layout(parent, layout);
true
}
Err(message) => {
crate::error::ffi::record_message_error(&message);
false
}
}
}
#[cfg(not(all(device_profile, feature = "serde_json")))]
{
let _ = spec;
crate::error::ffi::record_message_error(
"this build has no JSON layout engine, so a declarative layout kind cannot be \
created here",
);
false
}
})
}
#[no_mangle]
pub extern "C" fn rw_widget_layout_add(parent: u64, child: u64, stretch: c_uint) -> CBool {
c_try!({
let added = crate::layout::declarative::add_widget_to_layout(child, stretch.max(1), parent);
if !added {
crate::error::ffi::record_message_error(&format!(
"no layout is stored for widget {parent}; call rw_widget_set_layout first"
));
}
added
})
}
#[no_mangle]
pub extern "C" fn rw_widget_layout_add_spacer(parent: u64, stretch: c_uint) -> CBool {
c_try!(crate::layout::declarative::add_spacer_to_layout(stretch.max(1), parent))
}
#[no_mangle]
pub extern "C" fn rw_widget_layout_remove(parent: u64, child: u64) -> CBool {
c_try!(crate::layout::declarative::remove_widget_from_layout(child, parent))
}
#[no_mangle]
pub extern "C" fn rw_widget_layout_clear(parent: u64) -> CBool {
c_try!(crate::layout::declarative::forget_layout(parent))
}
#[no_mangle]
pub extern "C" fn rw_widget_layout_apply(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> c_uint {
c_try!({
let rect = crate::core::Rect::new(x, y, width, height);
let applied = crate::layout::declarative::apply_layout(parent, rect);
if !applied.is_empty() {
crate::widget::runtime::request_repaint(parent);
}
applied.len() as c_uint
})
}
#[no_mangle]
pub extern "C" fn rw_widget_layout_child_count(parent: u64) -> c_uint {
c_try!({
crate::layout::declarative::preview_layout(parent, crate::core::Rect::new(0, 0, 0, 0)).len()
as c_uint
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_widget_set_style(widget_id: u64, declaration: *const c_char) -> CBool {
c_try!({
let text = c_str_or_default(declaration);
let applied = crate::widget::runtime::with_widget_mut(widget_id, |widget| {
let property = text.split(':').next().unwrap_or("").trim();
if !property.is_empty() && !crate::style::CssParser::is_known_property(property) {
return Err(format!(
"unknown style property {property:?}; see the styling chapter of the cookbook \
for the accepted names"
));
}
let mut style = widget.style().clone();
match crate::style::CssParser::apply_declaration_text(&text, &mut style) {
Ok(()) => {
widget.set_style(style);
Ok(())
}
Err(message) => Err(message),
}
});
match applied {
Some(Ok(())) => {
crate::widget::runtime::request_repaint(widget_id);
true
}
Some(Err(message)) => {
crate::error::ffi::record_message_error(&message);
false
}
None => {
crate::error::ffi::record_capability_error(
crate::widget::capability::CapabilityAccessError::UnknownWidget,
);
false
}
}
})
}
#[cfg(not(stripped_widgets))]
fn encode_capability_value(
value: crate::widget::capability::CapabilityValue,
) -> (c_int, i64, Option<*mut c_char>) {
use crate::widget::capability::CapabilityValue;
match value {
CapabilityValue::Null => (RW_VALUE_NULL, 0, None),
CapabilityValue::Bool(flag) => (RW_VALUE_BOOL, i64::from(flag), None),
CapabilityValue::Int(number) => (RW_VALUE_INT, number, None),
CapabilityValue::UInt(number) => (RW_VALUE_UINT, number as i64, None),
CapabilityValue::Float(number) => {
(RW_VALUE_FLOAT, number.to_bits() as i64, None)
}
CapabilityValue::String(text) => {
let c_text = CString::new(text).unwrap_or_default().into_raw();
(RW_VALUE_STRING, 0, Some(c_text))
}
CapabilityValue::Color(color) => {
let c_text = CString::new(color.to_hex_rgba()).unwrap_or_default();
(RW_VALUE_COLOR, 0, Some(c_text.into_raw()))
}
CapabilityValue::Rect(rect) => {
let text = alloc::format!(
"{},{},{},{}",
rect.x,
rect.y,
rect.width as i32,
rect.height as i32
);
let c_text = CString::new(text).unwrap_or_default();
(RW_VALUE_RECT, 0, Some(c_text.into_raw()))
}
}
}
#[cfg(not(stripped_widgets))]
fn decode_capability_value(
kind: c_int,
num: i64,
str_value: *const c_char,
) -> Option<crate::widget::capability::CapabilityValue> {
use crate::widget::capability::CapabilityValue;
match kind {
RW_VALUE_NULL => Some(CapabilityValue::Null),
RW_VALUE_BOOL => Some(CapabilityValue::Bool(num != 0)),
RW_VALUE_INT => Some(CapabilityValue::Int(num)),
RW_VALUE_UINT => Some(CapabilityValue::UInt(u64::try_from(num).ok()?)),
RW_VALUE_FLOAT => Some(CapabilityValue::Float(f64::from_bits(num as u64))),
RW_VALUE_STRING => Some(CapabilityValue::String(unsafe {
if str_value.is_null() {
String::new()
} else {
CStr::from_ptr(str_value).to_string_lossy().into_owned()
}
})),
RW_VALUE_COLOR => Some(CapabilityValue::Color(
crate::style::CssParser::parse_color(&unsafe { read_c_string(str_value) }).ok()?,
)),
RW_VALUE_RECT => {
Some(CapabilityValue::Rect(parse_rect_string(&unsafe { read_c_string(str_value) })?))
}
_ => None,
}
}
unsafe fn read_c_string(value: *const c_char) -> String {
if value.is_null() {
String::new()
} else {
CStr::from_ptr(value).to_string_lossy().into_owned()
}
}
fn parse_rect_string(text: &str) -> Option<crate::core::Rect> {
let mut parts = text.split(',');
let x: i32 = parts.next()?.trim().parse().ok()?;
let y: i32 = parts.next()?.trim().parse().ok()?;
let width: u32 = parts.next()?.trim().parse().ok()?;
let height: u32 = parts.next()?.trim().parse().ok()?;
if parts.next().is_some() {
return None;
}
Some(crate::core::Rect::new(x, y, width, height))
}
const RW_VALUE_NULL: c_int = 0;
const RW_VALUE_BOOL: c_int = 1;
const RW_VALUE_INT: c_int = 2;
const RW_VALUE_UINT: c_int = 3;
const RW_VALUE_FLOAT: c_int = 4;
const RW_VALUE_STRING: c_int = 5;
const RW_VALUE_COLOR: c_int = 6;
const RW_VALUE_RECT: c_int = 7;
#[no_mangle]
pub extern "C" fn rw_combo_box_add_item(combo_box: u64, text: *const c_char) -> CBool {
c_try!({ get_control_backend().combo_box_add_item(combo_box, &c_str_or_default(text)) })
}
#[no_mangle]
pub extern "C" fn rw_combo_box_clear_items(combo_box: u64) -> CBool {
c_try!({ get_control_backend().combo_box_clear_items(combo_box) })
}
#[no_mangle]
pub extern "C" fn rw_combo_box_set_current_index(combo_box: u64, index: c_uint) -> CBool {
c_try!({
crate::platform::get_platform().combo_box_set_current_index(combo_box, index as usize)
})
}
#[no_mangle]
pub extern "C" fn rw_combo_box_current_index(combo_box: u64) -> c_int {
c_try!({
match crate::platform::get_platform().combo_box_current_index(combo_box) {
Some(idx) => idx as c_int,
None => -1,
}
})
}
#[no_mangle]
pub extern "C" fn rw_combo_box_item_count(combo_box: u64) -> c_uint {
c_try!({ crate::platform::get_platform().combo_box_item_count(combo_box) as c_uint })
}
#[no_mangle]
pub extern "C" fn rw_combo_box_item_text(combo_box: u64, index: c_uint) -> *const c_char {
c_try!({
let text = crate::platform::get_platform().combo_box_item_text(combo_box, index as usize);
to_c_string_or_empty(text.unwrap_or_default())
})
}
#[no_mangle]
pub extern "C" fn rw_list_box_add_item(list_box: u64, text: *const c_char) -> CBool {
c_try!({ get_control_backend().list_box_add_item(list_box, &c_str_or_default(text)) })
}
#[no_mangle]
pub extern "C" fn rw_list_box_remove_item(list_box: u64, index: c_uint) -> CBool {
c_try!({ get_control_backend().list_box_remove_item(list_box, index as usize) })
}
#[no_mangle]
pub extern "C" fn rw_list_box_clear_items(list_box: u64) -> CBool {
c_try!({ get_control_backend().list_box_clear_items(list_box) })
}
#[no_mangle]
pub extern "C" fn rw_list_box_set_current_index(list_box: u64, index: c_uint) -> CBool {
c_try!({ crate::platform::get_platform().list_box_set_current_index(list_box, index as usize) })
}
#[no_mangle]
pub extern "C" fn rw_list_box_current_index(list_box: u64) -> c_int {
c_try!({
match crate::platform::get_platform().list_box_current_index(list_box) {
Some(idx) => idx as c_int,
None => -1,
}
})
}
#[no_mangle]
pub extern "C" fn rw_list_box_item_count(list_box: u64) -> c_uint {
c_try!({ crate::platform::get_platform().list_box_item_count(list_box) as c_uint })
}
#[no_mangle]
pub extern "C" fn rw_list_box_item_text(list_box: u64, index: c_uint) -> *const c_char {
c_try!({
let text = crate::platform::get_platform().list_box_item_text(list_box, index as usize);
to_c_string_or_empty(text.unwrap_or_default())
})
}
#[no_mangle]
pub extern "C" fn rw_set_clipboard_text(text: *const c_char) -> CBool {
c_try!({ get_control_backend().set_clipboard_text(&c_str_or_default(text)) })
}
#[no_mangle]
pub extern "C" fn rw_get_clipboard_text() -> *const c_char {
c_try!({
let text = get_control_backend().get_clipboard_text();
to_c_string_or_empty(text)
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_begin_drag(
source: u64,
mime_type: *const c_char,
payload: *const u8,
payload_len: c_uint,
) -> CBool {
c_try!({
let slice = if payload.is_null() || payload_len == 0 {
&[]
} else {
unsafe { core::slice::from_raw_parts(payload, payload_len as usize) }
};
get_control_backend().begin_drag(source, &c_str_or_default(mime_type), slice)
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_poll_drop_event(
source_out: *mut u64,
target_out: *mut u64,
mime_out: *mut *mut c_char,
payload_out: *mut *mut u8,
payload_len_out: *mut c_uint,
) -> CBool {
c_try!({
let Some(event) = get_control_backend().poll_drop_event() else {
return false;
};
unsafe {
if !source_out.is_null() {
*source_out = event.source_widget_id;
}
if !target_out.is_null() {
*target_out = event.target_widget_id;
}
if !mime_out.is_null() {
let cs = CString::new(event.mime).unwrap_or_else(|_| CString::new("").unwrap());
*mime_out = cs.into_raw();
}
if !payload_out.is_null() && !payload_len_out.is_null() && !event.payload.is_empty() {
let len = event.payload.len();
let boxed: Box<[u8]> = event.payload.into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut u8;
*payload_out = ptr;
*payload_len_out = len as c_uint;
} else {
if !payload_out.is_null() {
*payload_out = std::ptr::null_mut();
}
if !payload_len_out.is_null() {
*payload_len_out = 0;
}
}
}
true
})
}
#[no_mangle]
pub extern "C" fn rw_create_menu_bar(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_menu_bar(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_menu(
parent: u64,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_menu(parent, &c_str_or_default(text), x, y, width, height)
})
}
#[no_mangle]
pub extern "C" fn rw_attach_menu_bar_to_window(window: u64, menu_bar: u64) -> CBool {
c_try!({ get_control_backend().attach_menu_bar_to_window(window, menu_bar) })
}
#[no_mangle]
pub extern "C" fn rw_menu_add_item(
parent_menu: u64,
text: *const c_char,
shortcut: *const c_char,
) -> u64 {
c_try!({
let shortcut_text =
if shortcut.is_null() { None } else { Some(c_str_or_default(shortcut)) };
get_control_backend().menu_add_item(
parent_menu,
&c_str_or_default(text),
shortcut_text.as_deref(),
)
})
}
#[no_mangle]
pub extern "C" fn rw_poll_menu_triggered() -> u64 {
c_try!({ get_control_backend().poll_menu_triggered().unwrap_or(0) })
}
#[no_mangle]
pub extern "C" fn rw_poll_widget_triggered() -> u64 {
c_try!({ get_control_backend().poll_widget_triggered().unwrap_or(0) })
}
#[no_mangle]
pub unsafe extern "C" fn rw_poll_widget_trigger_event(widget_id_out: *mut u64) -> c_uint {
c_try!({
let Some(event) = get_control_backend().poll_widget_trigger_event() else {
return 0;
};
if !widget_id_out.is_null() {
*widget_id_out = event.widget_id;
}
event.kind as c_uint
})
}
#[no_mangle]
pub extern "C" fn rw_inject_menu_trigger(menu_item_id: u64) -> CBool {
c_try!({ get_control_backend().inject_menu_trigger(menu_item_id) })
}
#[no_mangle]
pub extern "C" fn rw_inject_widget_trigger_event(widget_id: u64, kind_code: c_uint) -> CBool {
c_try!({
get_control_backend()
.inject_widget_trigger_event(widget_id, trigger_kind_from_code(kind_code))
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_menu_item(menu_item_id: u64) -> CBool {
c_try!({ get_control_backend().inject_menu_trigger(menu_item_id) })
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_click(widget_id: u64) -> CBool {
c_try!({
get_control_backend()
.inject_widget_trigger_event(widget_id, crate::platform::WidgetTriggerKind::Clicked)
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_value_changed(widget_id: u64) -> CBool {
c_try!({
get_control_backend().inject_widget_trigger_event(
widget_id,
crate::platform::WidgetTriggerKind::ValueChanged,
)
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_widget_event(widget_id: u64, kind_code: c_uint) -> CBool {
c_try!({
get_control_backend()
.inject_widget_trigger_event(widget_id, trigger_kind_from_code(kind_code))
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_bind_node(node_handle: u64, widget_id: u64) -> CBool {
c_try!({
if node_handle == 0 || widget_id == 0 {
return false;
}
harmony_node_registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.insert(node_handle, widget_id);
true
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_unbind_node(node_handle: u64) -> CBool {
c_try!({
if node_handle == 0 {
return false;
}
harmony_node_registry()
.lock()
.unwrap_or_else(|e| e.into_inner())
.remove(&node_handle)
.is_some()
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_lookup_widget_id(node_handle: u64) -> u64 {
c_try!({ harmony_lookup_widget(node_handle).unwrap_or(0) })
}
#[no_mangle]
pub extern "C" fn rw_harmony_clear_node_bindings() {
c_try_void!({
harmony_node_registry().lock().unwrap_or_else(|e| e.into_inner()).clear();
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_node_menu_item(node_handle: u64) -> CBool {
c_try!({
let Some(widget_id) = harmony_lookup_widget(node_handle) else {
return false;
};
get_control_backend().inject_menu_trigger(widget_id)
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_node_click(node_handle: u64) -> CBool {
c_try!({
let Some(widget_id) = harmony_lookup_widget(node_handle) else {
return false;
};
get_control_backend()
.inject_widget_trigger_event(widget_id, crate::platform::WidgetTriggerKind::Clicked)
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_node_value_changed(node_handle: u64) -> CBool {
c_try!({
let Some(widget_id) = harmony_lookup_widget(node_handle) else {
return false;
};
get_control_backend().inject_widget_trigger_event(
widget_id,
crate::platform::WidgetTriggerKind::ValueChanged,
)
})
}
#[no_mangle]
pub extern "C" fn rw_harmony_on_node_widget_event(node_handle: u64, kind_code: c_uint) -> CBool {
c_try!({
let Some(widget_id) = harmony_lookup_widget(node_handle) else {
return false;
};
get_control_backend()
.inject_widget_trigger_event(widget_id, trigger_kind_from_code(kind_code))
})
}
#[no_mangle]
pub extern "C" fn rw_create_tool_bar(
parent: u64,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({ get_control_backend().create_tool_bar(parent, x, y, width, height) })
}
#[no_mangle]
pub extern "C" fn rw_create_status_bar(
parent: u64,
text: *const c_char,
x: c_int,
y: c_int,
width: c_uint,
height: c_uint,
) -> u64 {
c_try!({
get_control_backend().create_status_bar(
parent,
&c_str_or_default(text),
x,
y,
width,
height,
)
})
}
#[no_mangle]
pub extern "C" fn rw_show_widget(widget_id: u64) {
c_try_void!({
get_control_backend().show_widget(widget_id);
})
}
#[no_mangle]
pub extern "C" fn rw_hide_widget(widget_id: u64) {
c_try_void!({
get_control_backend().hide_widget(widget_id);
})
}
#[no_mangle]
pub extern "C" fn rw_set_widget_text(widget_id: u64, text: *const c_char) {
c_try_void!({
get_control_backend().set_widget_text(widget_id, &c_str_or_default(text));
})
}
#[no_mangle]
pub extern "C" fn rw_get_widget_text(widget_id: u64) -> *const c_char {
c_try!({
let text = get_control_backend().get_widget_text(widget_id);
to_c_string_or_empty(text)
})
}
#[no_mangle]
pub extern "C" fn rw_set_widget_enabled(widget_id: u64, enabled: CBool) {
c_try_void!({
get_control_backend().set_widget_enabled(widget_id, enabled);
})
}
#[no_mangle]
pub extern "C" fn rw_is_widget_enabled(widget_id: u64) -> CBool {
c_try!({ get_control_backend().is_widget_enabled(widget_id) })
}
#[no_mangle]
pub extern "C" fn rw_set_widget_visible(widget_id: u64, visible: CBool) {
c_try_void!({
get_control_backend().set_widget_visible(widget_id, visible);
})
}
#[no_mangle]
pub extern "C" fn rw_is_widget_visible(widget_id: u64) -> CBool {
c_try!({ get_control_backend().is_widget_visible(widget_id) })
}
#[no_mangle]
pub extern "C" fn rw_set_widget_ime_enabled(widget_id: u64, enabled: CBool) -> CBool {
c_try!({ crate::platform::get_platform().set_widget_ime_enabled(widget_id, enabled) })
}
#[no_mangle]
pub extern "C" fn rw_is_widget_ime_enabled(widget_id: u64) -> CBool {
c_try!({ crate::platform::get_platform().is_widget_ime_enabled(widget_id) })
}
#[no_mangle]
pub extern "C" fn rw_set_widget_accessibility_name(widget_id: u64, name: *const c_char) -> CBool {
c_try!({
crate::platform::get_platform()
.set_widget_accessibility_name(widget_id, &c_str_or_default(name))
})
}
#[no_mangle]
pub extern "C" fn rw_get_widget_accessibility_name(widget_id: u64) -> *const c_char {
c_try!({
let name = crate::platform::get_platform().get_widget_accessibility_name(widget_id);
to_c_string_or_empty(name)
})
}
#[no_mangle]
pub extern "C" fn rw_backend_name() -> *const c_char {
c_try!({ to_c_string_or_empty(get_control_backend().backend_name()) })
}
#[no_mangle]
pub extern "C" fn rw_platform_capabilities() -> c_uint {
c_try!({
let caps = crate::platform::capabilities();
let mut mask: c_uint = 0;
if caps.dpi_scaling {
mask |= 1 << 0;
}
if caps.ime {
mask |= 1 << 1;
}
if caps.accessibility {
mask |= 1 << 2;
}
if caps.native_menu {
mask |= 1 << 3;
}
if caps.typed_widget_trigger {
mask |= 1 << 4;
}
mask
})
}
#[no_mangle]
pub extern "C" fn rw_platform_dpi_scale_factor() -> c_float {
c_try!({ crate::platform::dpi_scale_factor() })
}
#[no_mangle]
pub extern "C" fn rw_set_render_aa_samples_per_axis(samples: c_uint) -> c_uint {
c_try!({
let samples = samples.clamp(1, 8) as u8;
let config =
crate::render::SoftwareRenderConfig { aa_samples_per_axis: samples }.normalized();
crate::render::set_default_software_render_config(config);
crate::render::default_software_render_config().aa_samples_per_axis as c_uint
})
}
#[no_mangle]
pub extern "C" fn rw_get_render_aa_samples_per_axis() -> c_uint {
c_try!({ crate::render::default_software_render_config().aa_samples_per_axis as c_uint })
}
#[no_mangle]
pub extern "C" fn rw_set_embedded_target_fps(fps: c_uint) -> c_uint {
c_try!({ crate::render_engine::set_embedded_target_fps(fps) as c_uint })
}
#[no_mangle]
pub extern "C" fn rw_get_embedded_target_fps() -> c_uint {
c_try!({ crate::render_engine::embedded_target_fps() as c_uint })
}
#[no_mangle]
pub extern "C" fn rw_submit_embedded_noop_task(label: *const c_char) -> u64 {
c_try!({ crate::render_engine::submit_embedded_task(c_str_or_default(label), |_| {}) })
}
#[no_mangle]
pub extern "C" fn rw_embedded_engine_is_initialized() -> CBool {
c_try!({ crate::render_engine::embedded_engine_stats().initialized })
}
#[no_mangle]
pub extern "C" fn rw_embedded_engine_is_running() -> CBool {
c_try!({ crate::render_engine::embedded_engine_stats().running })
}
#[no_mangle]
pub extern "C" fn rw_embedded_engine_frame_count() -> u64 {
c_try!({ crate::render_engine::embedded_engine_stats().frame_count })
}
#[no_mangle]
pub extern "C" fn rw_embedded_engine_pending_task_count() -> u64 {
c_try!({ crate::render_engine::embedded_engine_stats().pending_task_count as u64 })
}
#[no_mangle]
pub extern "C" fn rw_embedded_engine_window_count() -> u64 {
c_try!({ crate::render_engine::embedded_engine_stats().window_count as u64 })
}
#[no_mangle]
pub extern "C" fn rw_embedded_engine_button_count() -> u64 {
c_try!({ crate::render_engine::embedded_engine_stats().button_count as u64 })
}
#[no_mangle]
pub extern "C" fn rw_platform_capability_contract(profile_code: c_uint) -> c_uint {
c_try!({
let profile = if profile_code == 1 {
crate::core::RuntimeProfile::Embedded
} else {
crate::core::RuntimeProfile::Full
};
let contract = crate::platform::negotiate_capability_contract(profile);
capability_contract_mask(contract)
})
}
#[no_mangle]
pub extern "C" fn rw_mobile_backend_name() -> *const c_char {
c_try!({
#[cfg(feature = "mobile-api")]
{
to_c_string_or_empty(crate::platform::mobile_backend_name())
}
#[cfg(not(feature = "mobile-api"))]
{
CString::new("").unwrap().into_raw()
}
})
}
#[no_mangle]
pub extern "C" fn rw_mobile_attach_native_view(native_handle: u64) -> CBool {
c_try!({
#[cfg(feature = "mobile-api")]
{
crate::platform::mobile_attach_to_native_view(native_handle as usize)
}
#[cfg(not(feature = "mobile-api"))]
{
let _ = native_handle;
false
}
})
}
#[no_mangle]
pub extern "C" fn rw_bindings_api_version() -> c_uint {
c_try!({ 8 })
}
#[no_mangle]
pub extern "C" fn rw_nodejs_binding_status() -> c_uint {
c_try!({ (1 << 0) | (1 << 1) })
}
#[no_mangle]
pub extern "C" fn rw_python_binding_status() -> c_uint {
c_try!({ (1 << 0) | (1 << 1) | (1 << 2) })
}
#[no_mangle]
pub extern "C" fn rw_cpp_binding_status() -> c_uint {
c_try!({ (1 << 0) | (1 << 1) })
}
#[no_mangle]
pub extern "C" fn rw_java_binding_status() -> c_uint {
c_try!({ (1 << 0) | (1 << 1) | (1 << 2) })
}
#[no_mangle]
pub extern "C" fn rw_java_jni_skeleton_version() -> c_uint {
c_try!({ 1 })
}
#[no_mangle]
pub extern "C" fn rw_cpp_reserved() -> c_uint {
c_try!({ 1 })
}
#[no_mangle]
pub extern "C" fn rw_java_reserved() -> c_uint {
c_try!({ 1 })
}
#[no_mangle]
pub extern "C" fn rw_python_reserved() -> c_uint {
c_try!({ 1 })
}
#[no_mangle]
pub extern "C" fn rw_error_code(_handle: u64) -> c_int {
c_try!({ crate::error::ffi::last_ffi_error().map(|e| e.id.0 as c_int).unwrap_or(0) })
}
#[no_mangle]
pub extern "C" fn rw_error_message(_handle: u64) -> *mut c_char {
c_try!({
let message = crate::error::ffi::last_ffi_error().map(|e| e.message).unwrap_or_default();
CString::new(message).unwrap_or_default().into_raw()
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_free_string(s: *mut c_char) {
c_try_void!({
if s.is_null() {
return;
}
unsafe {
let _ = CString::from_raw(s);
}
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_free_bytes(ptr: *mut u8, len: c_uint) {
c_try_void!({
if ptr.is_null() {
return;
}
unsafe {
let _ = Vec::from_raw_parts(ptr, len as usize, len as usize);
}
})
}
#[no_mangle]
pub unsafe extern "C" fn rw_free_rust_string(s: *mut c_char) {
rw_free_string(s);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn drop_event_payload_is_freed_through_rw_free_bytes() {
use crate::platform::{DropEvent, Platform, StubPlatform};
use core::ffi::c_uint;
let stub = StubPlatform::new("test-desktop", crate::core::PlatformFamily::Desktop);
let target = stub.create_window("drop-target", 0, 0, 320, 240);
let source = stub.create_window("drop-source", 0, 0, 320, 240);
let payload_bytes: Vec<u8> = (1u8..=32).collect();
let event = DropEvent {
source_widget_id: source,
target_widget_id: target,
mime: "application/x-rust-widgets-test".to_string(),
payload: payload_bytes.clone(),
};
assert!(
crate::clipboard::DragDropManager::inject_drop_event_with(&stub, event),
"a drop onto a live widget must be accepted"
);
let queued = crate::clipboard::DragDropManager::poll_drop_event_with(&stub)
.expect("the injected event must be polled back");
assert_eq!(queued.payload, payload_bytes);
let mut mime_out: *mut c_char = std::ptr::null_mut();
let mut payload_out: *mut u8 = std::ptr::null_mut();
let mut payload_len_out: c_uint = 0;
let mut source_out: u64 = 0;
let mut target_out: u64 = 0;
let had_event = unsafe {
rw_poll_drop_event(
&mut source_out,
&mut target_out,
&mut mime_out,
&mut payload_out,
&mut payload_len_out,
)
};
if !had_event {
assert!(mime_out.is_null());
assert!(payload_out.is_null());
assert_eq!(payload_len_out, 0);
unsafe { rw_free_bytes(std::ptr::null_mut(), 0) };
unsafe { rw_free_string(std::ptr::null_mut()) };
return;
}
if !mime_out.is_null() {
let mime = unsafe { CStr::from_ptr(mime_out) }.to_string_lossy().into_owned();
assert!(!mime.is_empty(), "a delivered event carries its mime type");
unsafe { rw_free_string(mime_out) };
}
if !payload_out.is_null() {
let read_back =
unsafe { core::slice::from_raw_parts(payload_out, payload_len_out as usize) }
.to_vec();
assert_eq!(read_back.len(), payload_len_out as usize);
unsafe { rw_free_bytes(payload_out, payload_len_out) };
} else {
assert_eq!(payload_len_out, 0, "a null payload must report zero length");
}
unsafe { rw_free_bytes(std::ptr::null_mut(), 0) };
unsafe { rw_free_string(std::ptr::null_mut()) };
}
#[test]
fn the_free_functions_accept_a_null_pointer() {
unsafe {
rw_free_bytes(std::ptr::null_mut(), 0);
rw_free_bytes(std::ptr::null_mut(), 4096);
rw_free_string(std::ptr::null_mut());
}
}
#[test]
fn a_released_byte_buffer_is_reclaimed_by_rw_free_bytes() {
use core::ffi::c_uint;
let bytes: Vec<u8> = (0u8..=63).collect();
let len = bytes.len();
let boxed: Box<[u8]> = bytes.clone().into_boxed_slice();
let ptr = Box::into_raw(boxed) as *mut u8;
let read_back = unsafe { core::slice::from_raw_parts(ptr, len) }.to_vec();
assert_eq!(read_back, bytes);
unsafe { rw_free_bytes(ptr, len as c_uint) };
}
#[test]
fn capability_values_round_trip_through_the_abi() {
use crate::widget::capability::CapabilityValue;
let cases = [
CapabilityValue::Null,
CapabilityValue::Bool(true),
CapabilityValue::Bool(false),
CapabilityValue::Int(-42),
CapabilityValue::UInt(7),
CapabilityValue::Float(core::f64::consts::PI),
CapabilityValue::String("hello".to_string()),
CapabilityValue::Color(crate::core::Color::rgba(0x0A, 0x1B, 0x2C, 0x7D)),
CapabilityValue::Rect(crate::core::Rect::new(1, 2, 300, 400)),
];
for original in cases {
let (kind, num, text) = encode_capability_value(original.clone());
let raw = text.map_or(core::ptr::null(), |owned| owned as *const c_char);
let decoded = decode_capability_value(kind, num, raw)
.unwrap_or_else(|| panic!("kind {kind} must decode, but did not"));
assert_eq!(
decoded, original,
"a {original:?} must survive the ABI round-trip, got {decoded:?}"
);
if !raw.is_null() {
unsafe { drop(CString::from_raw(raw as *mut c_char)) };
}
}
}
#[test]
fn malformed_color_and_rect_are_refused() {
for bad in ["not-a-color", "#12", "rgb(1,2)", ""] {
let text = CString::new(bad).expect("no interior NUL");
assert!(
decode_capability_value(RW_VALUE_COLOR, 0, text.as_ptr()).is_none(),
"{bad:?} is not a colour and must not decode to one"
);
}
for bad in ["1,2,3", "a,b,c,d", "1,2,3,4,5", "", "1,2,-3,-4"] {
let text = CString::new(bad).expect("no interior NUL");
assert!(
decode_capability_value(RW_VALUE_RECT, 0, text.as_ptr()).is_none(),
"{bad:?} is not a rectangle and must not decode to one"
);
}
}
#[test]
fn c_abi_widget_lifecycle_roundtrip() {
use std::ffi::{CStr, CString};
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
let title = c("abi-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0, "window creation must return a non-zero handle");
let label = c("hello");
let button = rw_create_button(window, label.as_ptr(), 10, 10, 80, 30);
assert_ne!(button, 0, "button creation must return a non-zero handle");
assert_eq!(
rw_create_button(9999, label.as_ptr(), 0, 0, 10, 10),
0,
"an unknown parent must be rejected"
);
let updated = c("updated");
rw_set_widget_text(button, updated.as_ptr());
let ptr = rw_get_widget_text(button);
assert!(!ptr.is_null(), "rw_get_widget_text must never return null");
let read_back = CStr::from_ptr(ptr).to_string_lossy().into_owned();
assert_eq!(read_back, "updated");
rw_free_string(ptr as *mut c_char);
rw_set_widget_geometry(button, 20, 20, 120, 40);
rw_hide_widget(button);
assert!(!rw_is_widget_visible(button), "hidden widget reports not visible");
rw_show_widget(button);
assert!(rw_is_widget_visible(button), "shown widget reports visible");
rw_set_widget_enabled(button, false);
assert!(!rw_is_widget_enabled(button), "disabled widget reports disabled");
rw_set_widget_enabled(button, true);
assert!(rw_is_widget_enabled(button), "enabled widget reports enabled");
}
}
#[test]
fn c_abi_unknown_widget_text_is_empty_not_null() {
use std::ffi::CStr;
unsafe {
let ptr = rw_get_widget_text(0xDEAD_BEEF);
assert!(!ptr.is_null(), "unknown widget must still return a valid pointer");
let text = CStr::from_ptr(ptr).to_string_lossy().into_owned();
assert!(text.is_empty(), "unknown widget text should be empty, got {text:?}");
rw_free_string(ptr as *mut c_char);
}
}
#[test]
fn c_abi_widget_kind_names_enumerates_the_registry() {
use std::ffi::CStr;
unsafe {
let required = rw_widget_kind_names(core::ptr::null_mut(), 0);
assert!(required > 0, "the registry must publish names");
let mut buffer = vec![0u8; required as usize + 1];
let written = rw_widget_kind_names(buffer.as_mut_ptr() as *mut c_char, required + 1);
assert_eq!(written, required, "the required size must be stable across calls");
let text =
CStr::from_ptr(buffer.as_ptr() as *const c_char).to_string_lossy().into_owned();
let names: Vec<&str> = text.split(' ').collect();
for expected in ["button", "tree_view", "timeline_widget", "grid_table"] {
assert!(names.contains(&expected), "{expected} must appear in the kind list");
}
}
}
#[test]
fn c_abi_name_enumeration_size_query_is_side_effect_free() {
let first = rw_widget_kind_names(core::ptr::null_mut(), 0);
let second = rw_widget_kind_names(core::ptr::null_mut(), 0);
assert_eq!(first, second, "a size query must be repeatable");
assert!(first > 0);
}
#[test]
fn c_abi_create_widget_of_kind_reaches_registered_controls() {
use std::ffi::CString;
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
let title = c("by-kind-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
for (name, expected_kind) in [
("tree_view", ""),
("timeline_widget", ""),
("command_palette", ""),
("diff_viewer", ""),
("markdown_editor", ""),
("toast_stack", ""),
("grid_table", ""),
] {
let kind = c(name);
let text = c("");
let id =
rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 100, 40);
assert_ne!(id, 0, "{name} must be creatable by name");
let enabled = c("enabled");
let mut out_kind: c_int = -1;
let mut out_num: i64 = 0;
let mut out_str: *mut c_char = core::ptr::null_mut();
let ok = rw_get_widget_property(
id,
enabled.as_ptr(),
&mut out_kind,
&mut out_num,
&mut out_str,
);
assert!(ok, "{name} must answer a base property");
assert_eq!(out_kind, RW_VALUE_BOOL, "{name}::enabled must be a bool");
assert_eq!(out_num, 1, "{name} must start enabled");
let _ = expected_kind;
}
let bogus = c("definitely_not_a_control");
let empty = c("");
assert_eq!(
rw_create_widget_of_kind(window, bogus.as_ptr(), empty.as_ptr(), 0, 0, 10, 10),
0,
"an unknown name must be rejected with 0"
);
}
}
#[test]
fn c_abi_set_and_get_widget_property_round_trip() {
use std::ffi::{CStr, CString};
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
let title = c("prop-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let kind = c("slider");
let text = c("");
let slider =
rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 120, 24);
assert_ne!(slider, 0);
let tooltip = c("tooltip");
let value = c("drag me");
assert!(
rw_set_widget_property(
slider,
tooltip.as_ptr(),
RW_VALUE_STRING,
0,
value.as_ptr()
),
"tooltip must be writable through the ABI"
);
let mut out_kind: c_int = -1;
let mut out_num: i64 = 0;
let mut out_str: *mut c_char = core::ptr::null_mut();
assert!(rw_get_widget_property(
slider,
tooltip.as_ptr(),
&mut out_kind,
&mut out_num,
&mut out_str
));
assert_eq!(out_kind, RW_VALUE_STRING);
assert!(!out_str.is_null());
assert_eq!(CStr::from_ptr(out_str).to_string_lossy(), "drag me");
rw_free_string(out_str);
let maximum = c("maximum");
assert!(rw_set_widget_property(
slider,
maximum.as_ptr(),
RW_VALUE_INT,
42,
core::ptr::null()
));
assert!(rw_get_widget_property(
slider,
maximum.as_ptr(),
&mut out_kind,
&mut out_num,
&mut out_str
));
assert_eq!(out_kind, RW_VALUE_INT);
assert_eq!(out_num, 42);
let geometry = c("geometry");
assert!(rw_get_widget_property(
slider,
geometry.as_ptr(),
&mut out_kind,
&mut out_num,
&mut out_str
));
assert_eq!(out_kind, RW_VALUE_STRING, "geometry is published as a string");
if !out_str.is_null() {
rw_free_string(out_str);
}
assert!(
!rw_set_widget_property(
slider,
geometry.as_ptr(),
RW_VALUE_STRING,
0,
value.as_ptr()
),
"geometry must stay read-only"
);
assert_ne!(rw_error_code(0), 0, "the refusal must set the error code");
}
}
#[test]
fn c_abi_widget_list_entry_points_change_the_real_collection() {
use std::ffi::CString;
let c = |s: &str| CString::new(s).expect("no interior NUL");
let title = c("list-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let kind = c("list_box");
let text = c("");
let list = rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 160, 120);
assert_ne!(list, 0);
assert_eq!(rw_widget_list_count(list), 0, "a fresh list holds nothing");
for (index, item) in ["alpha", "beta", "gamma"].iter().enumerate() {
let payload = c(item);
let count = rw_widget_list_add(list, payload.as_ptr());
assert_eq!(count as usize, index + 1, "adding {item} must grow the real count");
}
let fourth = c("delta");
assert_eq!(rw_widget_list_add(list, fourth.as_ptr()), 4);
assert_eq!(rw_widget_list_count(list), 4);
assert!(rw_widget_list_clear(list), "clearing a list_box must succeed");
assert_eq!(
rw_widget_list_count(list),
0,
"the clear must reach the control, not just report success"
);
let button_kind = c("button");
let button =
rw_create_widget_of_kind(window, button_kind.as_ptr(), text.as_ptr(), 0, 0, 80, 24);
assert_ne!(button, 0);
let item = c("nope");
assert_eq!(
rw_widget_list_add(button, item.as_ptr()),
0,
"a button holds no items, so the add must be refused"
);
assert!(!rw_widget_list_clear(button), "a button has no collection to clear");
assert_eq!(rw_widget_list_count(button), 0);
}
#[test]
fn c_abi_widget_list_items_can_be_read_back() {
use std::ffi::{CStr, CString};
let c = |s: &str| CString::new(s).expect("no interior NUL");
let title = c("list-read-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let kind = c("list_box");
let text = c("");
let list = rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 160, 120);
assert_ne!(list, 0);
let expected = ["alpha", "beta", "gamma"];
for item in expected {
let payload = c(item);
rw_widget_list_add(list, payload.as_ptr());
}
for (index, want) in expected.iter().enumerate() {
let required = rw_widget_list_item(list, index as c_uint, std::ptr::null_mut(), 0);
assert_eq!(
required as usize,
want.len(),
"a null buffer must report the byte length of item {index}"
);
let mut buffer = vec![0 as c_char; want.len() + 1];
let written = rw_widget_list_item(
list,
index as c_uint,
buffer.as_mut_ptr(),
buffer.len() as c_uint,
);
assert_eq!(written, required, "the writing call must report the same length");
let got = unsafe { CStr::from_ptr(buffer.as_ptr()) }.to_str().expect("ASCII fixtures");
assert_eq!(got, *want, "item {index} must read back as it was added");
}
assert_eq!(
rw_widget_list_item(list, expected.len() as c_uint, std::ptr::null_mut(), 0),
0,
"an index past the last item must report no item"
);
let button_kind = c("button");
let button =
rw_create_widget_of_kind(window, button_kind.as_ptr(), text.as_ptr(), 0, 0, 80, 24);
assert_ne!(button, 0);
assert_eq!(
rw_widget_list_item(button, 0, std::ptr::null_mut(), 0),
0,
"a button holds no items to read"
);
}
#[test]
fn c_abi_scroll_entry_points_move_the_real_offset() {
use std::ffi::CString;
let c = |s: &str| CString::new(s).expect("no interior NUL");
let title = c("scroll-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let kind = c("scroll_area");
let text = c("");
let area = rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 100, 100);
assert_ne!(area, 0);
crate::widget::runtime::with_widget_mut(area, |widget| {
if let Some(area) = crate::widget::capability::coercion::widget_as_mut::<
crate::widget::ScrollArea,
>(widget)
{
area.set_viewport(crate::core::Rect::new(0, 0, 100, 100));
area.set_content_size(crate::core::Size::new(300, 400));
}
});
let offset_of = || {
crate::widget::runtime::with_widget(area, |widget| {
crate::widget::capability::coercion::widget_as::<crate::widget::ScrollArea>(widget)
.map(|area| area.scroll_position())
})
.flatten()
};
assert!(
rw_widget_set_scroll_position(area, 50, 70),
"scroll_area must accept a scroll offset"
);
assert_eq!(offset_of(), Some((50, 70)), "the offset must be stored");
assert!(rw_widget_scroll_to(area, RW_SCROLL_TO_BOTTOM));
assert_eq!(
offset_of().map(|(_, y)| y),
Some(300),
"bottom is content height minus viewport height"
);
assert!(rw_widget_scroll_to(area, RW_SCROLL_TO_TOP));
assert_eq!(offset_of().map(|(_, y)| y), Some(0));
assert!(
!rw_widget_scroll_to(area, 99),
"an unknown destination must be rejected rather than defaulting"
);
let button_kind = c("button");
let button =
rw_create_widget_of_kind(window, button_kind.as_ptr(), text.as_ptr(), 0, 0, 80, 24);
assert_ne!(button, 0);
assert!(!rw_widget_set_scroll_position(button, 10, 10));
assert!(!rw_widget_scroll_to(button, RW_SCROLL_TO_TOP));
}
#[test]
fn c_abi_set_style_reaches_the_widget_style_record() {
use std::ffi::CString;
let c = |s: &str| CString::new(s).expect("no interior NUL");
let title = c("style-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let kind = c("button");
let text = c("Styled");
let button = rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 120, 32);
assert_ne!(button, 0);
let read_background = || {
crate::widget::runtime::with_widget(button, |widget| {
widget.style().background_color.map(|color| color.to_hex_rgba())
})
.flatten()
};
let read_radius = || {
crate::widget::runtime::with_widget(button, |widget| widget.style().border_radius)
.flatten()
};
let red = c("background-color: #FF0000");
unsafe {
assert!(
rw_widget_set_style(button, red.as_ptr()),
"a valid declaration must be accepted"
);
}
assert_eq!(
read_background(),
Some("#FF0000FF".to_string()),
"the colour must reach the widget's style, not just be parsed"
);
let radius = c("border-radius: 6");
unsafe {
assert!(rw_widget_set_style(button, radius.as_ptr()));
}
assert_eq!(read_radius(), Some(6), "the radius must be stored");
let malformed = c("not-a-declaration");
let ok = unsafe { rw_widget_set_style(button, malformed.as_ptr()) };
assert!(!ok, "a declaration with no ':' must be refused");
assert_ne!(rw_error_code(0), 0, "the refusal must set the error code");
let unknown = c("backgrond-color: #00FF00");
unsafe {
assert!(
!rw_widget_set_style(button, unknown.as_ptr()),
"a misspelled property must be refused rather than skipped"
);
}
assert_eq!(read_background(), Some("#FF0000FF".to_string()));
let valid = c("background-color: #0000FF");
unsafe {
assert!(!rw_widget_set_style(0xDEAD_BEEF, valid.as_ptr()));
}
}
#[test]
fn c_abi_layout_entry_points_move_the_widgets() {
use std::ffi::CString;
let c = |s: &str| CString::new(s).expect("no interior NUL");
let title = c("layout-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 400, 300);
assert_ne!(window, 0);
let kind = c("group_box");
let text = c("");
let parent = rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 400, 300);
assert_ne!(parent, 0);
let one = rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 50, 50);
let two = rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 50, 50);
assert_ne!(one, 0);
assert_ne!(two, 0);
let vbox = c("vbox");
unsafe {
assert!(
rw_widget_set_layout(parent, vbox.as_ptr(), 4, 0),
"a vbox layout must be accepted on a mounted parent"
);
}
assert!(rw_widget_layout_add(parent, one, 1));
assert!(rw_widget_layout_add(parent, two, 1));
assert_eq!(rw_widget_layout_child_count(parent), 2, "both children must be registered");
let read_rect = |id| {
crate::widget::runtime::with_widget(id, |widget| widget.geometry())
.expect("the child must be mounted")
};
let before = (read_rect(one), read_rect(two));
let applied = rw_widget_layout_apply(parent, 0, 0, 400, 300);
assert_eq!(applied, 2, "both children must be positioned");
let after = (read_rect(one), read_rect(two));
assert_ne!(
(before.0.x, before.0.y, before.0.width, before.0.height),
(after.0.x, after.0.y, after.0.width, after.0.height),
"the first child's geometry must actually change: before={:?} after={:?}",
before.0,
after.0
);
assert!(
after.1.y > after.0.y,
"a vbox must place the second child below the first: {:?} then {:?}",
after.0,
after.1
);
assert!(rw_widget_layout_add_spacer(parent, 1));
assert_eq!(rw_widget_layout_child_count(parent), 2, "a spacer is not a child");
let bogus = c("definitely_not_a_layout");
unsafe {
assert!(
!rw_widget_set_layout(parent, bogus.as_ptr(), 0, 0),
"an unknown layout kind must be refused"
);
}
assert!(
!rw_widget_layout_add(window, one, 1),
"a parent without a layout must refuse the child"
);
assert!(rw_widget_layout_remove(parent, one));
assert!(rw_widget_layout_clear(parent));
assert!(
!rw_widget_layout_clear(parent),
"clearing twice must report there was nothing left"
);
}
#[test]
fn c_abi_property_errors_are_distinguishable() {
use std::ffi::CString;
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
crate::error::ffi::clear_last_ffi_error();
let title = c("err-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let bogus = c("__no_such_property__");
let mut out_kind: c_int = -1;
let mut out_num: i64 = 0;
let mut out_str: *mut c_char = core::ptr::null_mut();
crate::error::ffi::clear_last_ffi_error();
assert!(!rw_get_widget_property(
window,
bogus.as_ptr(),
&mut out_kind,
&mut out_num,
&mut out_str
));
let unknown_property_code = rw_error_code(0);
assert_ne!(unknown_property_code, 0);
crate::error::ffi::clear_last_ffi_error();
assert!(!rw_get_widget_property(
0xDEAD_BEEF,
bogus.as_ptr(),
&mut out_kind,
&mut out_num,
&mut out_str
));
assert_ne!(rw_error_code(0), 0, "an unknown widget is also an error");
}
}
#[test]
fn c_abi_set_widget_property_rejects_unknown_value_kind() {
use std::ffi::CString;
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
let title = c("kind-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let tooltip = c("tooltip");
crate::error::ffi::clear_last_ffi_error();
assert!(
!rw_set_widget_property(window, tooltip.as_ptr(), 99, 0, core::ptr::null()),
"an unknown value kind must be refused"
);
assert_ne!(rw_error_code(0), 0);
}
}
#[test]
fn c_abi_widget_property_names_lists_the_controls_contract() {
use std::ffi::{CStr, CString};
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
let title = c("names-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let kind = c("timeline_widget");
let text = c("");
let timeline =
rw_create_widget_of_kind(window, kind.as_ptr(), text.as_ptr(), 0, 0, 200, 80);
assert_ne!(timeline, 0);
let required = rw_widget_property_names(timeline, core::ptr::null_mut(), 0);
assert!(required > 0);
let mut buffer = vec![0u8; required as usize + 1];
rw_widget_property_names(timeline, buffer.as_mut_ptr() as *mut c_char, required + 1);
let listed =
CStr::from_ptr(buffer.as_ptr() as *const c_char).to_string_lossy().into_owned();
for expected in ["item_count", "row_height", "enabled"] {
assert!(listed.contains(expected), "{expected} must be published: {listed}");
}
assert_eq!(rw_widget_property_names(0xDEAD_BEEF, core::ptr::null_mut(), 0), 0);
}
}
#[test]
fn c_abi_theme_entry_points_round_trip() {
use std::ffi::{CStr, CString};
let _guard = crate::theme::theme_test_guard();
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
let required = rw_theme_names(core::ptr::null_mut(), 0);
assert!(required > 0, "at least one theme must be registered");
let mut buffer = vec![0u8; required as usize + 1];
rw_theme_names(buffer.as_mut_ptr() as *mut c_char, required + 1);
let names =
CStr::from_ptr(buffer.as_ptr() as *const c_char).to_string_lossy().into_owned();
let first = names.split(' ').next().expect("a name").to_string();
let known = c(&first);
assert!(rw_set_theme(known.as_ptr()), "{first} must be selectable");
assert_eq!(
crate::theme::global_theme_manager().current_theme_name(),
first,
"the manager must report the theme that was selected"
);
let bogus = c("__no_such_theme__");
assert!(!rw_set_theme(bogus.as_ptr()), "an unknown theme must be refused");
assert_eq!(
crate::theme::global_theme_manager().current_theme_name(),
first,
"a refused switch must not change the active theme"
);
}
}
#[test]
fn c_abi_high_contrast_reaches_a_new_control() {
use std::ffi::CString;
let _guard = crate::theme::theme_test_guard();
crate::theme::set_global_high_contrast(crate::style::HighContrastMode::None);
let c = |s: &str| CString::new(s).expect("no interior NUL");
unsafe {
let title = c("hc-window");
let window = rw_create_window(title.as_ptr(), 0, 0, 320, 240);
assert_ne!(window, 0);
let label = c("hi");
let before = rw_create_label(window, label.as_ptr(), 0, 0, 60, 20);
assert_ne!(before, 0);
rw_set_high_contrast(1);
assert_eq!(
crate::theme::global_high_contrast(),
crate::style::HighContrastMode::WhiteOnBlack,
"a non-zero mode must enable the override"
);
let after = rw_create_label(window, label.as_ptr(), 0, 30, 60, 20);
assert_ne!(after, 0);
let background_of = |id: u64| -> Option<String> {
let name = c("enabled");
let mut out_kind: c_int = -1;
let mut out_num: i64 = 0;
let mut out_str: *mut c_char = core::ptr::null_mut();
let ok = rw_get_widget_property(
id,
name.as_ptr(),
&mut out_kind,
&mut out_num,
&mut out_str,
);
assert!(ok, "a live control must answer `enabled`");
Some(format!("{out_kind}:{out_num}"))
};
assert_eq!(background_of(before), background_of(after));
rw_set_high_contrast(0);
assert_eq!(
crate::theme::global_high_contrast(),
crate::style::HighContrastMode::None,
"mode 0 must clear the override"
);
}
}
#[cfg(all(feature = "desktop", widgets_unstripped))]
#[test]
fn render_aa_sample_abi_roundtrip_clamps_values() {
let _guard = crate::render::software_render_config_test_lock()
.lock()
.expect("software render config test lock poisoned");
let original = rw_get_render_aa_samples_per_axis();
let low = rw_set_render_aa_samples_per_axis(0);
assert_eq!(low, 1);
assert_eq!(rw_get_render_aa_samples_per_axis(), 1);
let high = rw_set_render_aa_samples_per_axis(100);
assert_eq!(high, 8);
assert_eq!(rw_get_render_aa_samples_per_axis(), 8);
rw_set_render_aa_samples_per_axis(original);
assert_eq!(rw_get_render_aa_samples_per_axis(), original.clamp(1, 8));
}
#[test]
fn embedded_target_fps_abi_roundtrip_clamps_values() {
let _guard = crate::render_engine::embedded::embedded_test_guard();
let original = rw_get_embedded_target_fps();
let low = rw_set_embedded_target_fps(0);
assert_eq!(low, 1);
assert_eq!(rw_get_embedded_target_fps(), 1);
let high = rw_set_embedded_target_fps(1000);
assert_eq!(high, 240);
assert_eq!(rw_get_embedded_target_fps(), 240);
rw_set_embedded_target_fps(original);
assert_eq!(rw_get_embedded_target_fps(), original.clamp(1, 240));
}
}