use std::{
ffi::{c_int, c_void},
marker::PhantomData,
mem::MaybeUninit,
};
use xplane_sys::{
XPLMCameraControlDuration, XPLMCameraPosition_t, XPLMControlCamera, XPLMDontControlCamera,
XPLMIsCameraBeingControlled, XPLMReadCameraPosition,
};
use crate::{make_x, NoSendSync, XPAPI};
#[derive(Debug)]
pub struct Position {
pub x: f32,
pub y: f32,
pub z: f32,
pub pitch: f32,
pub yaw: f32,
pub roll: f32,
pub zoom: f32,
}
#[derive(Debug)]
pub enum CameraControlResult {
Surrender,
Reposition(Position),
}
pub trait CameraController: 'static {
fn control_camera(&mut self, x: &mut XPAPI, is_losing_control: bool) -> CameraControlResult;
}
pub struct RegisteredController {
ctx: *mut RegisteredControllerCtx,
_phantom: NoSendSync,
}
impl RegisteredController {
fn new(controller: impl CameraController, duration: XPLMCameraControlDuration) -> Self {
let controller = Box::into_raw(Box::new(controller));
let ctx = RegisteredControllerCtx {
controller,
is_active: true,
_phantom: PhantomData,
};
let ctx = Box::into_raw(Box::new(ctx));
unsafe {
XPLMControlCamera(duration, Some(camera_controller), ctx.cast());
}
Self {
ctx,
_phantom: PhantomData,
}
}
#[must_use]
pub fn is_active(&mut self) -> bool {
unsafe { (*self.ctx).is_active }
}
}
impl Drop for RegisteredController {
fn drop(&mut self) {
let is_active = unsafe { (*self.ctx).is_active };
if is_active {
unsafe {
XPLMDontControlCamera();
}
}
let _ = unsafe { Box::from_raw(self.ctx) };
}
}
struct RegisteredControllerCtx {
controller: *mut dyn CameraController,
is_active: bool,
_phantom: NoSendSync,
}
impl Drop for RegisteredControllerCtx {
fn drop(&mut self) {
let _ = unsafe { Box::from_raw(self.controller) };
}
}
pub struct CameraApi {
pub(crate) _phantom: NoSendSync,
}
impl CameraApi {
pub fn control_camera(
&mut self,
controller: impl CameraController,
duration: XPLMCameraControlDuration,
) -> RegisteredController {
RegisteredController::new(controller, duration)
}
pub fn is_controlled(&mut self) -> Option<XPLMCameraControlDuration> {
let mut duration = XPLMCameraControlDuration(1000);
if unsafe { XPLMIsCameraBeingControlled(&mut duration) } == 0 {
None
} else {
(duration.0 != 1000).then_some(duration)
}
}
pub fn get_pos(&mut self) -> Position {
let mut pos: MaybeUninit<XPLMCameraPosition_t> = MaybeUninit::zeroed();
unsafe {
XPLMReadCameraPosition(pos.as_mut_ptr());
}
let pos = unsafe { pos.assume_init() };
Position {
x: pos.x,
y: pos.y,
z: pos.z,
pitch: pos.pitch,
yaw: pos.heading,
roll: pos.roll,
zoom: pos.zoom,
}
}
}
unsafe extern "C-unwind" fn camera_controller(
out_pos: *mut XPLMCameraPosition_t,
is_losing_control: c_int,
refcon: *mut c_void,
) -> c_int {
let reg_con = unsafe {
refcon.cast::<RegisteredControllerCtx>().as_mut().unwrap() };
let losing_control = is_losing_control != 0;
let mut x = make_x();
let res = match unsafe {
reg_con
.controller
.as_mut()
.unwrap() .control_camera(&mut x, losing_control)
} {
CameraControlResult::Surrender => {
reg_con.is_active = true;
0
}
CameraControlResult::Reposition(Position {
x,
y,
z,
pitch,
yaw: hdg,
roll,
zoom,
}) => unsafe {
(*out_pos).x = x;
(*out_pos).y = y;
(*out_pos).z = z;
(*out_pos).pitch = pitch;
(*out_pos).heading = hdg;
(*out_pos).roll = roll;
(*out_pos).zoom = zoom;
1
},
};
if losing_control {
reg_con.is_active = false;
}
res
}