#![cfg(all(feature = "xcomponent", not(alloc_frugal)))]
use crate::compat::String;
use crate::core::{ObjectId, Point, Rect, Size};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Mutex, OnceLock};
const MAX_TOUCH_POINTS: usize = 10;
#[cfg(target_env = "ohos")]
const XCOMPONENT_ID_LEN_MAX: usize = 128;
pub type NativeXComponent = core::ffi::c_void;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TouchEventType {
Down = 0,
Up = 1,
Move = 2,
Cancel = 3,
Unknown = 4,
}
#[cfg(any(target_env = "ohos", test))]
impl TouchEventType {
fn from_raw(raw: i32) -> Self {
match raw {
0 => Self::Down,
1 => Self::Up,
2 => Self::Move,
3 => Self::Cancel,
_ => Self::Unknown,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MouseEventAction {
None = 0,
Press = 1,
Release = 2,
Move = 3,
Cancel = 4,
}
#[cfg(any(target_env = "ohos", test))]
impl MouseEventAction {
fn from_raw(raw: i32) -> Option<Self> {
match raw {
0 => Some(Self::None),
1 => Some(Self::Press),
2 => Some(Self::Release),
3 => Some(Self::Move),
4 => Some(Self::Cancel),
_ => None,
}
}
}
pub mod mouse_button {
pub const NONE: u32 = 0;
pub const LEFT: u32 = 0x01;
pub const RIGHT: u32 = 0x02;
pub const MIDDLE: u32 = 0x04;
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct TouchPoint {
pub id: i32,
pub screen_x: f32,
pub screen_y: f32,
pub x: f32,
pub y: f32,
pub point_type: i32,
pub size: f64,
pub force: f32,
pub time_stamp: i64,
pub is_pressed: bool,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct TouchEvent {
pub id: i32,
pub screen_x: f32,
pub screen_y: f32,
pub x: f32,
pub y: f32,
pub event_type: i32,
pub size: f64,
pub force: f32,
pub device_id: i64,
pub time_stamp: i64,
pub touch_points: [TouchPoint; MAX_TOUCH_POINTS],
pub num_points: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct MouseEvent {
pub x: f32,
pub y: f32,
pub screen_x: f32,
pub screen_y: f32,
pub time_stamp: i64,
pub action: i32,
pub button: u32,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct NativeXComponentCallback {
pub on_surface_created: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
pub on_surface_changed: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
pub on_surface_destroyed: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
pub dispatch_touch_event: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct NativeXComponentMouseEventCallback {
pub dispatch_mouse_event: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
pub dispatch_hover_event: Option<extern "C" fn(*mut NativeXComponent, bool)>,
}
#[cfg(target_env = "ohos")]
extern "C" {
fn OH_NativeXComponent_GetTouchEvent(
component: *mut NativeXComponent,
window: *mut core::ffi::c_void,
touch_event: *mut TouchEvent,
) -> i32;
fn OH_NativeXComponent_RegisterCallback(
component: *mut NativeXComponent,
callback: *mut NativeXComponentCallback,
) -> i32;
fn OH_NativeXComponent_RegisterMouseEventCallback(
component: *mut NativeXComponent,
callback: *mut NativeXComponentMouseEventCallback,
) -> i32;
fn OH_NativeXComponent_RegisterKeyEventCallback(
component: *mut NativeXComponent,
callback: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
) -> i32;
fn OH_NativeXComponent_RegisterFocusEventCallback(
component: *mut NativeXComponent,
callback: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
) -> i32;
fn OH_NativeXComponent_RegisterBlurEventCallback(
component: *mut NativeXComponent,
callback: Option<extern "C" fn(*mut NativeXComponent, *mut core::ffi::c_void)>,
) -> i32;
fn OH_NativeXComponent_GetXComponentSize(
component: *mut NativeXComponent,
window: *mut core::ffi::c_void,
width: *mut u64,
height: *mut u64,
) -> i32;
fn OH_NativeXComponent_GetXComponentOffset(
component: *mut NativeXComponent,
window: *mut core::ffi::c_void,
x: *mut f64,
y: *mut f64,
) -> i32;
fn OH_NativeXComponent_GetXComponentId(
component: *mut NativeXComponent,
id: *mut core::ffi::c_char,
size: *mut u64,
) -> i32;
}
#[cfg(target_env = "ohos")]
const RESULT_SUCCESS: i32 = 0;
struct SurfaceState {
mounted_widget: Option<ObjectId>,
size: Size,
offset: Point,
#[cfg(target_env = "ohos")]
alive: bool,
}
impl SurfaceState {
const fn new() -> Self {
Self {
mounted_widget: None,
size: Size::new(0, 0),
offset: Point::new(0, 0),
#[cfg(target_env = "ohos")]
alive: false,
}
}
}
fn surface() -> &'static Mutex<SurfaceState> {
static SURFACE: OnceLock<Mutex<SurfaceState>> = OnceLock::new();
SURFACE.get_or_init(|| Mutex::new(SurfaceState::new()))
}
static BOUND: AtomicBool = AtomicBool::new(false);
#[cfg(any(target_env = "ohos", test))]
static UI_THREAD: AtomicUsize = AtomicUsize::new(0);
#[cfg(any(target_env = "ohos", test))]
fn is_ui_thread() -> bool {
let bound_on = UI_THREAD.load(Ordering::Acquire);
bound_on != 0 && current_thread_id() == bound_on
}
#[cfg(any(target_env = "ohos", test))]
fn current_thread_id() -> usize {
thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static ID: u8 = const { 0 };
}
ID.with(|cell| cell as *const u8 as usize)
}
pub unsafe fn bind(component: *mut NativeXComponent) -> bool {
if component.is_null() {
log::error!("[harmony] xcomponent: bind called with a null OH_NativeXComponent");
return false;
}
let already = BOUND_COMPONENT.load(Ordering::Acquire);
if already != 0 && already != component as usize {
log::error!(
"[harmony] xcomponent: bind refused — the bridge is already bound to a different \
component. SDK 20 exposes no callback unregister, so the previous component's \
callbacks cannot be detached and would corrupt this one's state; unbind/finish the \
first component before binding another"
);
return false;
}
#[cfg(not(target_env = "ohos"))]
{
let _ = component;
log::warn!(
"[harmony] xcomponent: this build is not for OpenHarmony, so it has no libace_ndk \
to register against; the bridge is inert"
);
#[allow(clippy::needless_return)]
return false;
}
#[cfg(target_env = "ohos")]
{
UI_THREAD.store(current_thread_id(), Ordering::Release);
static CALLBACKS: OnceLock<NativeXComponentCallback> = OnceLock::new();
let callbacks = CALLBACKS.get_or_init(|| NativeXComponentCallback {
on_surface_created: Some(on_surface_created),
on_surface_changed: Some(on_surface_changed),
on_surface_destroyed: Some(on_surface_destroyed),
dispatch_touch_event: Some(dispatch_touch_event),
});
let registered = unsafe {
OH_NativeXComponent_RegisterCallback(
component,
callbacks as *const NativeXComponentCallback as *mut NativeXComponentCallback,
)
};
if registered != RESULT_SUCCESS {
log::error!(
"[harmony] xcomponent: OH_NativeXComponent_RegisterCallback failed (status={registered})"
);
return false;
}
static MOUSE_CALLBACKS: OnceLock<NativeXComponentMouseEventCallback> = OnceLock::new();
let mouse = MOUSE_CALLBACKS.get_or_init(|| NativeXComponentMouseEventCallback {
dispatch_mouse_event: Some(dispatch_mouse_event),
dispatch_hover_event: Some(dispatch_hover_event),
});
let mouse_status = unsafe {
OH_NativeXComponent_RegisterMouseEventCallback(
component,
mouse as *const NativeXComponentMouseEventCallback
as *mut NativeXComponentMouseEventCallback,
)
};
if mouse_status != RESULT_SUCCESS {
log::warn!(
"[harmony] xcomponent: mouse callback not registered (status={mouse_status}); \
pointer input will not be delivered to this surface"
);
}
let key_status = unsafe {
OH_NativeXComponent_RegisterKeyEventCallback(component, Some(dispatch_key_event))
};
if key_status != RESULT_SUCCESS {
log::warn!(
"[harmony] xcomponent: key callback not registered (status={key_status}); \
typing will not reach this surface"
);
}
let focus_status = unsafe {
OH_NativeXComponent_RegisterFocusEventCallback(component, Some(on_focus_event))
};
if focus_status != RESULT_SUCCESS {
log::warn!(
"[harmony] xcomponent: focus callback not registered (status={focus_status})"
);
}
let blur_status = unsafe {
OH_NativeXComponent_RegisterBlurEventCallback(component, Some(on_blur_event))
};
if blur_status != RESULT_SUCCESS {
log::warn!("[harmony] xcomponent: blur callback not registered (status={blur_status})");
}
let a11y = unsafe {
super::accessibility::attach_for_component(super::accessibility::bridge(), component)
};
if !a11y {
log::debug!(
"[harmony] xcomponent: no accessibility provider for this component; the surface
and input work, but no accessibility notifications will be posted"
);
}
let keyboard_status = unsafe { OH_NativeXComponent_SetNeedSoftKeyboard(component, true) };
if keyboard_status != RESULT_SUCCESS {
log::warn!(
"[harmony] xcomponent: SetNeedSoftKeyboard failed (status={keyboard_status}); the \
on-screen keyboard may not appear for text input"
);
}
BOUND_COMPONENT.store(component as usize, Ordering::Release);
BOUND.store(true, Ordering::Release);
log::info!(
"[harmony] xcomponent: bound; surface, touch, mouse, key and focus callbacks registered"
);
true
}
}
pub fn component_id() -> Option<String> {
let _ = surface(); let component = BOUND_COMPONENT.load(Ordering::Acquire);
if component == 0 {
return None;
}
#[cfg(not(target_env = "ohos"))]
{
let _ = component;
#[allow(clippy::needless_return)]
return None;
}
#[cfg(target_env = "ohos")]
{
let mut buffer = vec![0u8; XCOMPONENT_ID_LEN_MAX + 1];
let mut size = XCOMPONENT_ID_LEN_MAX as u64;
let status = unsafe {
OH_NativeXComponent_GetXComponentId(
component as *mut NativeXComponent,
buffer.as_mut_ptr() as *mut core::ffi::c_char,
&mut size,
)
};
if status != RESULT_SUCCESS {
log::error!("[harmony] xcomponent: GetXComponentId failed (status={status})");
return None;
}
let end = buffer.iter().position(|byte| *byte == 0).unwrap_or(buffer.len());
buffer.truncate(end);
String::from_utf8(buffer).ok()
}
}
static BOUND_COMPONENT: AtomicUsize = AtomicUsize::new(0);
pub fn set_mounted_widget(widget_id: ObjectId, rect: Rect) -> bool {
if !BOUND.load(Ordering::Acquire) {
log::error!(
"[harmony] xcomponent: mount for widget {widget_id} refused — no XComponent is bound; \
call rw_harmony_bind_xcomponent from the ArkTS onLoad first"
);
return false;
}
let mut state = surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner());
state.mounted_widget = Some(widget_id);
state.size = Size::new(rect.width, rect.height);
true
}
pub fn clear_mounted_widget() {
let mut state = surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner());
state.mounted_widget = None;
}
pub fn mounted_widget() -> Option<ObjectId> {
surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner()).mounted_widget
}
pub fn surface_size() -> Option<Size> {
let state = surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner());
if state.size.width == 0 || state.size.height == 0 {
None
} else {
Some(state.size)
}
}
pub fn surface_offset() -> Point {
surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner()).offset
}
#[cfg(target_env = "ohos")]
unsafe fn record_surface_size(component: *mut NativeXComponent, window: *mut core::ffi::c_void) {
#[cfg(not(target_env = "ohos"))]
{
let _ = (component, window);
log::debug!(
"[harmony] xcomponent: this build is not for OpenHarmony, so the surface size \
cannot be read; no size was recorded"
);
return;
}
#[cfg(target_env = "ohos")]
{
let mut width: u64 = 0;
let mut height: u64 = 0;
let status = unsafe {
OH_NativeXComponent_GetXComponentSize(component, window, &mut width, &mut height)
};
if status != RESULT_SUCCESS {
log::error!("[harmony] xcomponent: GetXComponentSize failed (status={status})");
return;
}
let mut offset_x: f64 = 0.0;
let mut offset_y: f64 = 0.0;
let offset_status = unsafe {
OH_NativeXComponent_GetXComponentOffset(component, window, &mut offset_x, &mut offset_y)
};
if offset_status != RESULT_SUCCESS {
log::warn!("[harmony] xcomponent: GetXComponentOffset failed (status={offset_status})");
}
let mut state = surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner());
state.size = Size::new(width as u32, height as u32);
state.offset = Point::new(offset_x.round() as i32, offset_y.round() as i32);
log::debug!(
"[harmony] xcomponent: surface {}x{} at ({}, {})",
state.size.width,
state.size.height,
state.offset.x,
state.offset.y
);
}
}
#[cfg(target_env = "ohos")]
extern "C" fn on_surface_created(component: *mut NativeXComponent, window: *mut core::ffi::c_void) {
BOUND_COMPONENT.store(component as usize, Ordering::Release);
unsafe { record_surface_size(component, window) };
surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner()).alive = true;
log::info!("[harmony] xcomponent: surface created");
}
#[cfg(target_env = "ohos")]
extern "C" fn on_surface_changed(component: *mut NativeXComponent, window: *mut core::ffi::c_void) {
unsafe { record_surface_size(component, window) };
if let Some(size) = surface_size() {
let widget = mounted_widget();
if let Some(widget_id) = widget {
crate::queue_resize_trigger(widget_id, size.width, size.height);
}
}
}
#[cfg(target_env = "ohos")]
extern "C" fn on_surface_destroyed(
_component: *mut NativeXComponent,
_window: *mut core::ffi::c_void,
) {
let mut state = surface().lock().unwrap_or_else(|poisoned| poisoned.into_inner());
state.alive = false;
log::info!("[harmony] xcomponent: surface destroyed");
}
#[cfg(target_env = "ohos")]
extern "C" fn dispatch_touch_event(
component: *mut NativeXComponent,
window: *mut core::ffi::c_void,
) {
if !is_ui_thread() {
log::error!("[harmony] xcomponent: touch callback off the ArkUI thread; event dropped");
return;
}
let Some(widget_id) = mounted_widget() else {
return;
};
#[cfg(not(target_env = "ohos"))]
{
let _ = (component, window, widget_id);
return;
}
#[cfg(target_env = "ohos")]
{
let mut touch: TouchEvent = unsafe { core::mem::zeroed() };
let status = unsafe { OH_NativeXComponent_GetTouchEvent(component, window, &mut touch) };
if status != RESULT_SUCCESS {
log::error!("[harmony] xcomponent: GetTouchEvent failed (status={status})");
return;
}
let event_type = TouchEventType::from_raw(touch.event_type);
let points = touch.num_points.min(MAX_TOUCH_POINTS as u32) as usize;
if points == 0 {
dispatch_one_touch(widget_id, touch.id as u64, touch.x, touch.y, event_type);
return;
}
for point in touch.touch_points.iter().take(points) {
let point_type = TouchEventType::from_raw(point.point_type);
dispatch_one_touch(widget_id, point.id as u64, point.x, point.y, point_type);
}
}
}
#[cfg(feature = "touch")]
#[cfg(target_env = "ohos")]
fn dispatch_one_touch(
widget_id: ObjectId,
touch_id: u64,
x: f32,
y: f32,
event_type: TouchEventType,
) {
let position = Point::new(x.round() as i32, y.round() as i32);
let event = match event_type {
TouchEventType::Down => crate::event::Event::touch_begin(position.x, position.y, touch_id),
TouchEventType::Up => crate::event::Event::touch_end(position.x, position.y, touch_id),
TouchEventType::Move => crate::event::Event::touch_move(position.x, position.y, touch_id),
TouchEventType::Cancel => crate::event::translator::touch_cancel(position, touch_id),
TouchEventType::Unknown => return,
};
if crate::platform::platform_facts().route_pointer_event(widget_id, &event, position) {
crate::request_repaint_because(widget_id, crate::RepaintReason::State);
}
}
#[cfg(not(feature = "touch"))]
fn dispatch_one_touch(
_widget_id: ObjectId,
_touch_id: u64,
_x: f32,
_y: f32,
_event_type: TouchEventType,
) {
log::debug!(
"[harmony] xcomponent: touch contact dropped — this build has no 'touch' capability, so \
there is no touch event to deliver"
);
}
#[cfg(target_env = "ohos")]
extern "C" fn dispatch_mouse_event(
component: *mut NativeXComponent,
window: *mut core::ffi::c_void,
) {
if !is_ui_thread() {
log::error!("[harmony] xcomponent: mouse callback off the ArkUI thread; event dropped");
return;
}
let Some(widget_id) = mounted_widget() else {
return;
};
#[cfg(not(target_env = "ohos"))]
{
let _ = (component, window, widget_id);
return;
}
#[cfg(target_env = "ohos")]
{
let mut mouse: MouseEvent = unsafe { core::mem::zeroed() };
let status = unsafe {
OH_NativeXComponent_GetMouseEvent(component, window, &mut mouse)
};
if status != RESULT_SUCCESS {
log::error!("[harmony] xcomponent: GetMouseEvent failed (status={status})");
return;
}
let Some(action) = MouseEventAction::from_raw(mouse.action) else {
log::debug!("[harmony] xcomponent: unknown mouse action {}", mouse.action);
return;
};
let button = if mouse.button & mouse_button::RIGHT != 0 {
crate::event::mouse_button::SECONDARY
} else if mouse.button & mouse_button::MIDDLE != 0 {
crate::event::mouse_button::MIDDLE
} else {
crate::event::mouse_button::PRIMARY
};
let position = Point::new(mouse.x.round() as i32, mouse.y.round() as i32);
record_pointer_position(position);
let event = match action {
MouseEventAction::Press => {
crate::event::Event::mouse_press_with(position.x, position.y, button, 0)
}
MouseEventAction::Release => {
crate::event::Event::MouseRelease { pos: position, button }
}
MouseEventAction::Move => crate::event::Event::MouseMove { pos: position },
MouseEventAction::Cancel => crate::event::Event::MouseLeave { pos: position },
MouseEventAction::None => return,
};
if crate::platform::platform_facts().route_pointer_event(widget_id, &event, position) {
crate::request_repaint_because(widget_id, crate::RepaintReason::State);
}
}
}
#[cfg(target_env = "ohos")]
extern "C" fn dispatch_hover_event(_component: *mut NativeXComponent, is_hover: bool) {
if !is_ui_thread() {
return;
}
let Some(widget_id) = mounted_widget() else {
return;
};
if is_hover {
return;
}
let Some(position) = last_pointer_position() else {
return;
};
let event = crate::event::Event::MouseLeave { pos: position };
if crate::platform::platform_facts().route_pointer_event(widget_id, &event, position) {
crate::request_repaint_because(widget_id, crate::RepaintReason::State);
}
}
#[cfg(target_env = "ohos")]
fn last_pointer_position() -> Option<Point> {
LAST_POINTER.with(|cell| cell.get())
}
#[cfg(target_env = "ohos")]
fn record_pointer_position(position: Point) {
LAST_POINTER.with(|cell| cell.set(Some(position)));
}
thread_local! {
static LAST_POINTER: core::cell::Cell<Option<Point>> = const { core::cell::Cell::new(None) };
}
#[cfg(target_env = "ohos")]
extern "C" fn dispatch_key_event(
component: *mut NativeXComponent,
_window: *mut core::ffi::c_void,
) {
if !is_ui_thread() {
log::error!("[harmony] xcomponent: key callback off the ArkUI thread; event dropped");
return;
}
let Some(widget_id) = mounted_widget() else {
return;
};
#[cfg(not(target_env = "ohos"))]
{
let _ = (component, widget_id);
return;
}
#[cfg(target_env = "ohos")]
{
let mut key_event: *mut core::ffi::c_void = core::ptr::null_mut();
let status = unsafe { OH_NativeXComponent_GetKeyEvent(component, &mut key_event) };
if status != RESULT_SUCCESS || key_event.is_null() {
log::error!("[harmony] xcomponent: GetKeyEvent failed (status={status})");
return;
}
let mut action: i32 = 0;
let action_status =
unsafe { OH_NativeXComponent_GetKeyEventAction(key_event, &mut action) };
if action_status != RESULT_SUCCESS {
log::error!("[harmony] xcomponent: GetKeyEventAction failed (status={action_status})");
return;
}
let mut code: i32 = 0;
let code_status = unsafe { OH_NativeXComponent_GetKeyEventCode(key_event, &mut code) };
if code_status != RESULT_SUCCESS {
log::error!("[harmony] xcomponent: GetKeyEventCode failed (status={code_status})");
return;
}
const KEY_ACTION_DOWN: i32 = 0;
const KEY_ACTION_UP: i32 = 1;
let target = crate::widget::runtime::focused_widget().unwrap_or(widget_id);
let modifiers = unsafe { key_modifiers(key_event) };
let event = match action {
KEY_ACTION_DOWN => crate::event::Event::KeyPress { key: code as u32, modifiers },
KEY_ACTION_UP => crate::event::Event::KeyRelease { key: code as u32, modifiers },
other => {
log::debug!("[harmony] xcomponent: key action {other} is neither down nor up");
return;
}
};
if crate::widget::runtime::dispatch_event(target, &event) {
crate::request_repaint_because(widget_id, crate::RepaintReason::State);
}
}
}
#[cfg(target_env = "ohos")]
unsafe fn key_modifiers(key_event: *mut core::ffi::c_void) -> u32 {
const ARKUI_CTRL: u64 = 1 << 0;
const ARKUI_SHIFT: u64 = 1 << 1;
const ARKUI_ALT: u64 = 1 << 2;
const WIDGET_SHIFT: u32 = 1;
const WIDGET_CONTROL: u32 = 2;
const WIDGET_ALT: u32 = 4;
#[cfg(not(target_env = "ohos"))]
{
let _ = key_event;
return 0;
}
#[cfg(target_env = "ohos")]
{
let mut keys: u64 = 0;
let status =
unsafe { OH_NativeXComponent_GetKeyEventModifierKeyStates(key_event, &mut keys) };
if status != RESULT_SUCCESS {
log::debug!(
"[harmony] xcomponent: GetKeyEventModifierKeyStates failed (status={status}); \
the key is delivered with no modifiers"
);
return 0;
}
let mut modifiers = 0u32;
if keys & ARKUI_SHIFT != 0 {
modifiers |= WIDGET_SHIFT;
}
if keys & ARKUI_CTRL != 0 {
modifiers |= WIDGET_CONTROL;
}
if keys & ARKUI_ALT != 0 {
modifiers |= WIDGET_ALT;
}
modifiers
}
}
#[cfg(target_env = "ohos")]
extern "C" fn on_focus_event(_component: *mut NativeXComponent, _window: *mut core::ffi::c_void) {
if !is_ui_thread() {
return;
}
if let Some(widget_id) = mounted_widget() {
crate::widget::runtime::report_state(
widget_id,
crate::widget::runtime::StateFact::Focused(true),
);
}
}
#[cfg(target_env = "ohos")]
extern "C" fn on_blur_event(_component: *mut NativeXComponent, _window: *mut core::ffi::c_void) {
if !is_ui_thread() {
return;
}
if let Some(widget_id) = mounted_widget() {
crate::widget::runtime::report_state(
widget_id,
crate::widget::runtime::StateFact::Focused(false),
);
}
}
#[cfg(target_env = "ohos")]
extern "C" {
fn OH_NativeXComponent_GetMouseEvent(
component: *mut NativeXComponent,
window: *mut core::ffi::c_void,
mouse_event: *mut MouseEvent,
) -> i32;
fn OH_NativeXComponent_GetKeyEvent(
component: *mut NativeXComponent,
key_event: *mut *mut core::ffi::c_void,
) -> i32;
fn OH_NativeXComponent_GetKeyEventAction(
key_event: *mut core::ffi::c_void,
action: *mut i32,
) -> i32;
fn OH_NativeXComponent_GetKeyEventCode(
key_event: *mut core::ffi::c_void,
code: *mut i32,
) -> i32;
fn OH_NativeXComponent_GetKeyEventModifierKeyStates(
key_event: *mut core::ffi::c_void,
keys: *mut u64,
) -> i32;
fn OH_NativeXComponent_SetNeedSoftKeyboard(
component: *mut NativeXComponent,
need_soft_keyboard: bool,
) -> i32;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn touch_event_types_map_explicitly() {
assert_eq!(TouchEventType::from_raw(0), TouchEventType::Down);
assert_eq!(TouchEventType::from_raw(1), TouchEventType::Up);
assert_eq!(TouchEventType::from_raw(2), TouchEventType::Move);
assert_eq!(TouchEventType::from_raw(3), TouchEventType::Cancel);
assert_eq!(TouchEventType::from_raw(4), TouchEventType::Unknown);
assert_eq!(TouchEventType::from_raw(99), TouchEventType::Unknown);
assert_eq!(TouchEventType::from_raw(-1), TouchEventType::Unknown);
}
#[test]
fn mouse_actions_map_explicitly() {
assert_eq!(MouseEventAction::from_raw(0), Some(MouseEventAction::None));
assert_eq!(MouseEventAction::from_raw(1), Some(MouseEventAction::Press));
assert_eq!(MouseEventAction::from_raw(2), Some(MouseEventAction::Release));
assert_eq!(MouseEventAction::from_raw(3), Some(MouseEventAction::Move));
assert_eq!(MouseEventAction::from_raw(4), Some(MouseEventAction::Cancel));
assert_eq!(MouseEventAction::from_raw(99), None);
}
#[test]
fn mounting_without_a_bound_component_is_refused() {
assert!(!BOUND.load(Ordering::Acquire), "a test must not have bound a real component");
assert!(!set_mounted_widget(4242, Rect::new(0, 0, 100, 100)));
assert_eq!(mounted_widget(), None);
}
#[test]
fn surface_size_is_none_before_arkui_reports_one() {
assert_eq!(surface_size(), None, "an unreported size must not be invented");
}
#[test]
fn component_id_is_none_when_unbound() {
assert_eq!(component_id(), None);
}
#[test]
fn the_ui_thread_guard_is_closed_before_a_bind() {
assert!(!is_ui_thread());
}
}