use std::cell::RefCell;
use std::ffi::CString;
use std::os::raw::{c_char, c_int};
use xplm_sys::{
xpMsg_AcceptChild, xpMsg_AcceptParent, xpMsg_Create, xpMsg_CursorAdjust,
xpMsg_DescriptorChanged, xpMsg_Destroy, xpMsg_Draw, xpMsg_ExposedChanged, xpMsg_Hidden,
xpMsg_KeyLoseFocus, xpMsg_KeyPress, xpMsg_KeyTakeFocus, xpMsg_LoseChild, xpMsg_MouseDown,
xpMsg_MouseDrag, xpMsg_MouseUp, xpMsg_MouseWheel, xpMsg_None as xp_msg_none, xpMsg_Paint,
xpMsg_PropertyChanged, xpMsg_Reshape, xpMsg_Shown, xpProperty_Clip, xpProperty_DragXOff,
xpProperty_DragYOff, xpProperty_Dragging, xpProperty_Enabled, xpProperty_Hilited,
xpProperty_Object, xpProperty_Refcon, xpWidgetClass_Button, xpWidgetClass_Caption,
xpWidgetClass_GeneralGraphics, xpWidgetClass_MainWindow, xpWidgetClass_Progress,
xpWidgetClass_ScrollBar, xpWidgetClass_SubWindow, xpWidgetClass_TextField, XPAddWidgetCallback,
XPBringRootWidgetToFront, XPCountChildWidgets, XPCreateCustomWidget, XPCreateWidget,
XPDestroyWidget, XPDispatchMode, XPFindRootWidget, XPGetNthChildWidget, XPGetParentWidget,
XPGetWidgetDescriptor, XPGetWidgetForLocation, XPGetWidgetGeometry, XPGetWidgetProperty,
XPGetWidgetUnderlyingWindow, XPGetWidgetWithFocus, XPHideWidget, XPIsWidgetInFront,
XPIsWidgetVisible, XPLoseKeyboardFocus, XPPlaceWidgetWithin, XPSendMessageToWidget,
XPSetKeyboardFocus, XPSetWidgetDescriptor, XPSetWidgetGeometry, XPSetWidgetProperty,
XPShowWidget, XPWidgetClass, XPWidgetID, XPWidgetMessage, XPWidgetPropertyID,
};
use crate::window::WindowRef;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WidgetClass {
MainWindow,
SubWindow,
Button,
TextField,
ScrollBar,
Caption,
GeneralGraphics,
Progress,
}
impl From<WidgetClass> for XPWidgetClass {
fn from(c: WidgetClass) -> Self {
(match c {
WidgetClass::MainWindow => xpWidgetClass_MainWindow,
WidgetClass::SubWindow => xpWidgetClass_SubWindow,
WidgetClass::Button => xpWidgetClass_Button,
WidgetClass::TextField => xpWidgetClass_TextField,
WidgetClass::ScrollBar => xpWidgetClass_ScrollBar,
WidgetClass::Caption => xpWidgetClass_Caption,
WidgetClass::GeneralGraphics => xpWidgetClass_GeneralGraphics,
WidgetClass::Progress => xpWidgetClass_Progress,
}) as XPWidgetClass
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DispatchMode {
Direct,
UpChain,
Recursive,
DirectAllCallbacks,
Once,
}
impl From<DispatchMode> for XPDispatchMode {
fn from(m: DispatchMode) -> Self {
(match m {
DispatchMode::Direct => xplm_sys::xpMode_Direct,
DispatchMode::UpChain => xplm_sys::xpMode_UpChain,
DispatchMode::Recursive => xplm_sys::xpMode_Recursive,
DispatchMode::DirectAllCallbacks => xplm_sys::xpMode_DirectAllCallbacks,
DispatchMode::Once => xplm_sys::xpMode_Once,
}) as XPDispatchMode
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WidgetMessage {
None,
Create,
Destroy,
Paint,
Draw,
KeyPress,
KeyTakeFocus,
KeyLoseFocus,
MouseDown,
MouseDrag,
MouseUp,
Reshape,
ExposedChanged,
AcceptChild,
LoseChild,
AcceptParent,
Shown,
Hidden,
DescriptorChanged,
PropertyChanged,
MouseWheel,
CursorAdjust,
Other(XPWidgetMessage),
}
impl From<XPWidgetMessage> for WidgetMessage {
fn from(raw: XPWidgetMessage) -> Self {
match raw {
v if v == xp_msg_none => WidgetMessage::None,
v if v == xpMsg_Create => WidgetMessage::Create,
v if v == xpMsg_Destroy => WidgetMessage::Destroy,
v if v == xpMsg_Paint => WidgetMessage::Paint,
v if v == xpMsg_Draw => WidgetMessage::Draw,
v if v == xpMsg_KeyPress => WidgetMessage::KeyPress,
v if v == xpMsg_KeyTakeFocus => WidgetMessage::KeyTakeFocus,
v if v == xpMsg_KeyLoseFocus => WidgetMessage::KeyLoseFocus,
v if v == xpMsg_MouseDown => WidgetMessage::MouseDown,
v if v == xpMsg_MouseDrag => WidgetMessage::MouseDrag,
v if v == xpMsg_MouseUp => WidgetMessage::MouseUp,
v if v == xpMsg_Reshape => WidgetMessage::Reshape,
v if v == xpMsg_ExposedChanged => WidgetMessage::ExposedChanged,
v if v == xpMsg_AcceptChild => WidgetMessage::AcceptChild,
v if v == xpMsg_LoseChild => WidgetMessage::LoseChild,
v if v == xpMsg_AcceptParent => WidgetMessage::AcceptParent,
v if v == xpMsg_Shown => WidgetMessage::Shown,
v if v == xpMsg_Hidden => WidgetMessage::Hidden,
v if v == xpMsg_DescriptorChanged => WidgetMessage::DescriptorChanged,
v if v == xpMsg_PropertyChanged => WidgetMessage::PropertyChanged,
v if v == xpMsg_MouseWheel => WidgetMessage::MouseWheel,
v if v == xpMsg_CursorAdjust => WidgetMessage::CursorAdjust,
_ => WidgetMessage::Other(raw),
}
}
}
impl From<WidgetMessage> for XPWidgetMessage {
fn from(m: WidgetMessage) -> Self {
(match m {
WidgetMessage::None => xp_msg_none,
WidgetMessage::Create => xpMsg_Create,
WidgetMessage::Destroy => xpMsg_Destroy,
WidgetMessage::Paint => xpMsg_Paint,
WidgetMessage::Draw => xpMsg_Draw,
WidgetMessage::KeyPress => xpMsg_KeyPress,
WidgetMessage::KeyTakeFocus => xpMsg_KeyTakeFocus,
WidgetMessage::KeyLoseFocus => xpMsg_KeyLoseFocus,
WidgetMessage::MouseDown => xpMsg_MouseDown,
WidgetMessage::MouseDrag => xpMsg_MouseDrag,
WidgetMessage::MouseUp => xpMsg_MouseUp,
WidgetMessage::Reshape => xpMsg_Reshape,
WidgetMessage::ExposedChanged => xpMsg_ExposedChanged,
WidgetMessage::AcceptChild => xpMsg_AcceptChild,
WidgetMessage::LoseChild => xpMsg_LoseChild,
WidgetMessage::AcceptParent => xpMsg_AcceptParent,
WidgetMessage::Shown => xpMsg_Shown,
WidgetMessage::Hidden => xpMsg_Hidden,
WidgetMessage::DescriptorChanged => xpMsg_DescriptorChanged,
WidgetMessage::PropertyChanged => xpMsg_PropertyChanged,
WidgetMessage::MouseWheel => xpMsg_MouseWheel,
WidgetMessage::CursorAdjust => xpMsg_CursorAdjust,
WidgetMessage::Other(raw) => return raw,
}) as XPWidgetMessage
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WidgetPropertyId(XPWidgetPropertyID);
impl WidgetPropertyId {
pub const DRAGGING: Self = Self(xpProperty_Dragging as XPWidgetPropertyID);
pub const DRAG_X_OFF: Self = Self(xpProperty_DragXOff as XPWidgetPropertyID);
pub const DRAG_Y_OFF: Self = Self(xpProperty_DragYOff as XPWidgetPropertyID);
pub const HILITED: Self = Self(xpProperty_Hilited as XPWidgetPropertyID);
pub const OBJECT: Self = Self(xpProperty_Object as XPWidgetPropertyID);
pub const CLIP: Self = Self(xpProperty_Clip as XPWidgetPropertyID);
pub const ENABLED: Self = Self(xpProperty_Enabled as XPWidgetPropertyID);
pub fn new(raw: XPWidgetPropertyID) -> Self {
Self(raw)
}
}
const REFCON_PROPERTY: XPWidgetPropertyID = xpProperty_Refcon as XPWidgetPropertyID;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WidgetRef(XPWidgetID);
impl WidgetRef {
pub fn geometry(&self) -> (i32, i32, i32, i32) {
let (mut left, mut top, mut right, mut bottom) = (0, 0, 0, 0);
unsafe { XPGetWidgetGeometry(self.0, &mut left, &mut top, &mut right, &mut bottom) };
(left, top, right, bottom)
}
pub fn set_geometry(&self, left: i32, top: i32, right: i32, bottom: i32) {
unsafe { XPSetWidgetGeometry(self.0, left, top, right, bottom) }
}
pub fn is_visible(&self) -> bool {
unsafe { XPIsWidgetVisible(self.0) != 0 }
}
pub fn show(&self) {
unsafe { XPShowWidget(self.0) }
}
pub fn hide(&self) {
unsafe { XPHideWidget(self.0) }
}
pub fn descriptor(&self) -> String {
let mut buf = [0u8; 256];
unsafe {
XPGetWidgetDescriptor(self.0, buf.as_mut_ptr() as *mut c_char, buf.len() as c_int)
};
let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
String::from_utf8_lossy(&buf[..end]).into_owned()
}
pub fn set_descriptor(&self, descriptor: &str) {
let Ok(c_descriptor) = CString::new(descriptor) else {
return;
};
unsafe { XPSetWidgetDescriptor(self.0, c_descriptor.as_ptr()) }
}
pub fn property(&self, property: WidgetPropertyId) -> Option<isize> {
let mut exists: c_int = 0;
let value = unsafe { XPGetWidgetProperty(self.0, property.0, &mut exists) };
(exists != 0).then_some(value)
}
pub fn set_property(&self, property: WidgetPropertyId, value: isize) {
unsafe { XPSetWidgetProperty(self.0, property.0, value) }
}
pub fn parent(&self) -> Option<WidgetRef> {
let id = unsafe { XPGetParentWidget(self.0) };
(!id.is_null()).then_some(WidgetRef(id))
}
pub fn place_within(&self, container: Option<WidgetRef>) {
unsafe { XPPlaceWidgetWithin(self.0, container.map_or(std::ptr::null_mut(), |c| c.0)) }
}
pub fn child_count(&self) -> i32 {
unsafe { XPCountChildWidgets(self.0) }
}
pub fn nth_child(&self, index: i32) -> Option<WidgetRef> {
let id = unsafe { XPGetNthChildWidget(self.0, index) };
(!id.is_null()).then_some(WidgetRef(id))
}
pub fn children(&self) -> impl Iterator<Item = WidgetRef> + '_ {
(0..self.child_count()).filter_map(move |i| self.nth_child(i))
}
pub fn find_root(&self) -> WidgetRef {
WidgetRef(unsafe { XPFindRootWidget(self.0) })
}
pub fn bring_root_to_front(&self) {
unsafe { XPBringRootWidgetToFront(self.0) }
}
pub fn is_in_front(&self) -> bool {
unsafe { XPIsWidgetInFront(self.0) != 0 }
}
pub fn underlying_window(&self) -> WindowRef {
WindowRef::from_raw(unsafe { XPGetWidgetUnderlyingWindow(self.0) })
}
pub fn set_keyboard_focus(&self) -> Option<WidgetRef> {
let id = unsafe { XPSetKeyboardFocus(self.0) };
(!id.is_null()).then_some(WidgetRef(id))
}
pub fn lose_keyboard_focus(&self) {
unsafe { XPLoseKeyboardFocus(self.0) }
}
pub fn send_message(
&self,
message: WidgetMessage,
mode: DispatchMode,
param1: isize,
param2: isize,
) -> bool {
unsafe { XPSendMessageToWidget(self.0, message.into(), mode.into(), param1, param2) != 0 }
}
}
pub fn widget_with_focus() -> Option<WidgetRef> {
let id = unsafe { XPGetWidgetWithFocus() };
(!id.is_null()).then_some(WidgetRef(id))
}
pub fn widget_for_location(
container: WidgetRef,
x: i32,
y: i32,
recursive: bool,
visible_only: bool,
) -> Option<WidgetRef> {
let id = unsafe {
XPGetWidgetForLocation(container.0, x, y, recursive as c_int, visible_only as c_int)
};
(!id.is_null()).then_some(WidgetRef(id))
}
type WidgetCallback = dyn FnMut(WidgetMessage, WidgetRef, isize, isize) -> bool + 'static;
pub struct Widget {
id: XPWidgetID,
}
unsafe impl Send for Widget {}
impl Widget {
pub fn handle(&self) -> WidgetRef {
WidgetRef(self.id)
}
}
impl Drop for Widget {
fn drop(&mut self) {
unsafe { XPDestroyWidget(self.id, 1) }
}
}
pub fn create_widget(
left: i32,
top: i32,
right: i32,
bottom: i32,
visible: bool,
descriptor: &str,
is_root: bool,
container: Option<WidgetRef>,
class: WidgetClass,
) -> Option<Widget> {
let c_descriptor = CString::new(descriptor).ok()?;
let id = unsafe {
XPCreateWidget(
left,
top,
right,
bottom,
visible as c_int,
c_descriptor.as_ptr(),
is_root as c_int,
container.map_or(std::ptr::null_mut(), |c| c.0),
class.into(),
)
};
(!id.is_null()).then_some(Widget { id })
}
pub fn create_custom_widget(
left: i32,
top: i32,
right: i32,
bottom: i32,
visible: bool,
descriptor: &str,
is_root: bool,
container: Option<WidgetRef>,
callback: impl FnMut(WidgetMessage, WidgetRef, isize, isize) -> bool + 'static,
) -> Option<Widget> {
let c_descriptor = CString::new(descriptor).ok()?;
let state: Box<RefCell<Box<WidgetCallback>>> =
Box::new(RefCell::new(Box::new(callback) as Box<WidgetCallback>));
let refcon = Box::into_raw(state) as isize;
let id = unsafe {
XPCreateCustomWidget(
left,
top,
right,
bottom,
visible as c_int,
c_descriptor.as_ptr(),
is_root as c_int,
container.map_or(std::ptr::null_mut(), |c| c.0),
Some(custom_widget_trampoline),
)
};
if id.is_null() {
unsafe { drop(Box::from_raw(refcon as *mut RefCell<Box<WidgetCallback>>)) };
return None;
}
unsafe { XPSetWidgetProperty(id, REFCON_PROPERTY, refcon) };
Some(Widget { id })
}
pub fn add_widget_callback(
widget: WidgetRef,
callback: impl FnMut(WidgetMessage, WidgetRef, isize, isize) -> bool + 'static,
) {
let state: Box<RefCell<Box<WidgetCallback>>> =
Box::new(RefCell::new(Box::new(callback) as Box<WidgetCallback>));
let refcon = Box::into_raw(state);
unsafe {
XPSetWidgetProperty(widget.0, REFCON_PROPERTY, refcon as isize);
XPAddWidgetCallback(widget.0, Some(custom_widget_trampoline));
}
}
unsafe extern "C" fn custom_widget_trampoline(
message: XPWidgetMessage,
widget: XPWidgetID,
param1: isize,
param2: isize,
) -> c_int {
crate::guard(|| {
let mut exists: c_int = 0;
let refcon = unsafe { XPGetWidgetProperty(widget, REFCON_PROPERTY, &mut exists) };
if exists == 0 || refcon == 0 {
return false;
}
let state = refcon as *const RefCell<Box<WidgetCallback>>;
let wrapped_message = WidgetMessage::from(message);
let handled =
(unsafe { &*state }.borrow_mut())(wrapped_message, WidgetRef(widget), param1, param2);
if wrapped_message == WidgetMessage::Destroy {
drop(unsafe { Box::from_raw(state as *mut RefCell<Box<WidgetCallback>>) });
}
handled
})
.unwrap_or(false) as c_int
}