1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
use std::os::raw::*;
use xplm_sys;

/// A callback that can be called while X-Plane draws graphics
pub trait DrawCallback: 'static {
    /// Draws
    fn draw(&mut self);
}

impl<F> DrawCallback for F
where
    F: 'static + FnMut(),
{
    fn draw(&mut self) {
        self()
    }
}

/// Sets up a draw callback
pub struct Draw {
    /// The callback to execute
    _callback: Box<dyn DrawCallback>,
    /// The draw phase (used when unregistering)
    phase: Phase,
    /// The callback pointer (used when unregistering)
    callback_ptr: *mut c_void,
    /// The C callback (used when unregistering)
    c_callback: xplm_sys::XPLMDrawCallback_f,
}

impl Draw {
    /// Creates a new drawing callback
    pub fn new<C: DrawCallback>(phase: Phase, callback: C) -> Result<Self, Error> {
        let xplm_phase = phase.to_xplm();
        let callback_box = Box::new(callback);
        let callback_ptr: *const _ = &*callback_box;
        let status = unsafe {
            xplm_sys::XPLMRegisterDrawCallback(
                Some(draw_callback::<C>),
                xplm_phase,
                0,
                callback_ptr as *mut _,
            )
        };
        if status == 1 {
            Ok(Draw {
                _callback: callback_box,
                phase,
                callback_ptr: callback_ptr as *mut _,
                c_callback: Some(draw_callback::<C>),
            })
        } else {
            Err(Error::UnsupportedPhase(phase))
        }
    }
}

impl Drop for Draw {
    /// Unregisters this draw callback
    fn drop(&mut self) {
        let phase = self.phase.to_xplm();
        unsafe {
            xplm_sys::XPLMUnregisterDrawCallback(self.c_callback, phase, 0, self.callback_ptr);
        }
    }
}

/// The draw callback provided to X-Plane
///
/// This is instantiated separately for each callback type.
unsafe extern "C" fn draw_callback<C: DrawCallback>(
    _phase: xplm_sys::XPLMDrawingPhase,
    _before: c_int,
    refcon: *mut c_void,
) -> c_int {
    let callback_ptr = refcon as *mut C;
    (*callback_ptr).draw();
    // Always allow X-Plane to draw
    1
}

/// Phases in which drawing can occur
#[derive(Debug, Copy, Clone)]
pub enum Phase {
    // TODO: Some phases have been removed because they were removed from the upstream X-Plane SDK.
    // The replacements should be added back in.
    AfterPanel,
    /// After X-Plane draws panel gauges
    AfterGauges,
    /// After X-Plane draws user interface windows
    AfterWindows,
    /// After X-Plane draws 3D content in the local map window
    AfterLocalMap3D,
    /// After X-Plane draws 2D content in the local map window
    AfterLocalMap2D,
    /// After X-Plane draws 2D content in the local map profile view
    AfterLocalMapProfile,
}

impl Phase {
    /// Converts this phase into an XPLMDrawingPhase and a 0 for after or 1 for before
    fn to_xplm(&self) -> xplm_sys::XPLMDrawingPhase {
        use self::Phase::*;
        let phase = match *self {
            AfterPanel => xplm_sys::xplm_Phase_Panel,
            AfterGauges => xplm_sys::xplm_Phase_Gauges,
            AfterWindows => xplm_sys::xplm_Phase_Window,
            AfterLocalMap2D => xplm_sys::xplm_Phase_LocalMap2D,
            AfterLocalMap3D => xplm_sys::xplm_Phase_LocalMap3D,
            AfterLocalMapProfile => xplm_sys::xplm_Phase_LocalMapProfile,
        };
        phase as xplm_sys::XPLMDrawingPhase
    }
}

/// Errors that can occur when creating a draw callback
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// X-Plane does not support the provided phase
    #[error("Unsupported draw phase: {0:?}")]
    UnsupportedPhase(Phase),
}

/// Stores various flags that can be enabled or disabled
#[derive(Debug, Clone)]
pub struct GraphicsState {
    /// Enable status of fog
    ///
    /// During 3-d rendering fog is set up to cause a fade-to-fog effect at the visibility limit.
    pub fog: bool,
    /// Enable status of 3D lighting
    pub lighting: bool,
    /// Enable status of alpha testing
    ///
    /// Alpha testing stops pixels from being rendered to the frame buffer if their alpha is zero.
    pub alpha_testing: bool,
    /// Enable status of alpha blending
    pub alpha_blending: bool,
    /// Enable status of depth testing
    pub depth_testing: bool,
    /// Enable status of depth writing
    pub depth_writing: bool,
    /// The number of textures that are enabled for use
    pub textures: i32,
}

/// Sets the graphics state
pub fn set_state(state: &GraphicsState) {
    unsafe {
        xplm_sys::XPLMSetGraphicsState(
            state.fog as i32,
            state.textures,
            state.lighting as i32,
            state.alpha_testing as i32,
            state.alpha_blending as i32,
            state.depth_testing as i32,
            state.depth_writing as i32,
        );
    }
}

/// Binds a texture ID to a texture number
///
/// This function should be used instead of glBindTexture
pub fn bind_texture(texture_number: i32, texture_id: i32) {
    unsafe {
        xplm_sys::XPLMBindTexture2d(texture_number, texture_id);
    }
}

/// Generates texture numbers in a range not reserved for X-Plane.
///
/// This function should be used instead of glGenTextures.
///
/// Texture IDs are placed in the provided slice. If the slice contains more than i32::max_value()
/// elements, no more than i32::max_value() texture IDs will be generated.
pub fn generate_texture_numbers(numbers: &mut [i32]) {
    let count = if numbers.len() < (i32::max_value() as usize) {
        numbers.len() as i32
    } else {
        i32::max_value()
    };
    unsafe {
        xplm_sys::XPLMGenerateTextureNumbers(numbers.as_mut_ptr(), count);
    }
}

///
/// Generates a single texture number
///
/// See generate_texture_numbers for more detail.
///
pub fn generate_texture_number() -> i32 {
    let number = 0;
    generate_texture_numbers(&mut [number]);
    number
}