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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
mod priority;

use core::mem;

pub use self::priority::Priority;
use crate::{
    bindings::{cc_string, Gui_Add, Gui_Remove, InputDevice, Screen, ScreenVTABLE},
    std_types::{c_char, c_int, c_void, Box},
};

pub struct OwnedScreen {
    pub screen: Box<Screen>,
    vtable: Box<ScreenVTABLE>,
    added: bool,
}

impl OwnedScreen {
    pub fn new() -> Self {
        let mut vtable = Box::new(ScreenVTABLE {
            Init: Some(Init),
            Update: Some(Update),
            Free: Some(Free),
            Render: Some(Render),
            BuildMesh: Some(BuildMesh),
            HandlesInputDown: Some(HandlesInputDown),
            OnInputUp: Some(OnInputUp),
            HandlesKeyPress: Some(HandlesKeyPress),
            HandlesTextChanged: Some(HandlesTextChanged),
            HandlesPointerDown: Some(HandlesPointerDown),
            OnPointerUp: Some(OnPointerUp),
            HandlesPointerMove: Some(HandlesPointerMove),
            HandlesMouseScroll: Some(HandlesMouseScroll),
            Layout: Some(Layout),
            ContextLost: Some(ContextLost),
            ContextRecreated: Some(ContextRecreated),
            HandlesPadAxis: Some(HandlesPadAxis),
        });

        let screen = Box::new(unsafe {
            let mut screen: Screen = mem::zeroed();
            screen.VTABLE = vtable.as_mut();
            screen
        });

        Self {
            screen,
            vtable,
            added: false,
        }
    }

    pub fn add<T: Into<Priority>>(&mut self, priority: T) {
        if self.added {
            return;
        }
        unsafe {
            // priority is stored as a u8 even though api is c_int
            Gui_Add(self.screen.as_mut(), priority.into().to_u8() as _);
        }
        self.added = true;
    }

    pub fn remove(&mut self) {
        if self.added {
            unsafe {
                Gui_Remove(self.screen.as_mut());
            }
            self.added = false;
        }
    }

    /// Initialises persistent state.
    pub fn on_init(&mut self, f: unsafe extern "C" fn(elem: *mut c_void)) -> &mut Self {
        self.vtable.as_mut().Init = Some(f);
        self
    }

    /// Updates this screen, called every frame just before Render().
    pub fn on_update(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, delta: f32),
    ) -> &mut Self {
        self.vtable.as_mut().Update = Some(f);
        self
    }

    /// Frees/releases persistent state.
    pub fn on_free(&mut self, f: unsafe extern "C" fn(elem: *mut c_void)) -> &mut Self {
        self.vtable.as_mut().Free = Some(f);
        self
    }

    /// Draws this screen and its widgets on screen.
    pub fn on_render(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, delta: f32),
    ) -> &mut Self {
        self.vtable.as_mut().Render = Some(f);
        self
    }

    /// Builds the vertex mesh for all the widgets in the screen.
    pub fn on_build_mesh(&mut self, f: unsafe extern "C" fn(elem: *mut c_void)) -> &mut Self {
        self.vtable.as_mut().BuildMesh = Some(f);
        self
    }

    /// Returns non-zero if an input press is handled.
    pub fn on_handles_input_down(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, key: c_int, device: *mut InputDevice) -> c_int,
    ) -> &mut Self {
        self.vtable.as_mut().HandlesInputDown = Some(f);
        self
    }

    /// Returns non-zero if an input release is handled.
    pub fn on_on_input_up(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, key: c_int, device: *mut InputDevice),
    ) -> &mut Self {
        self.vtable.as_mut().OnInputUp = Some(f);
        self
    }

    /// Returns non-zero if a key character press is handled.
    pub fn on_handles_key_press(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, keyChar: c_char) -> c_int,
    ) -> &mut Self {
        self.vtable.as_mut().HandlesKeyPress = Some(f);
        self
    }

    /// Returns non-zero if a key character press is handled.
    /// Currently only raised by on-screen keyboard in web client.
    pub fn on_handles_text_changed(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, str: *const cc_string) -> c_int,
    ) -> &mut Self {
        self.vtable.as_mut().HandlesTextChanged = Some(f);
        self
    }

    /// Returns non-zero if a pointer press is handled.
    pub fn on_handles_pointer_down(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, id: c_int, x: c_int, y: c_int) -> c_int,
    ) -> &mut Self {
        self.vtable.as_mut().HandlesPointerDown = Some(f);
        self
    }

    /// Returns non-zero if a pointer release is handled.
    pub fn on_on_pointer_up(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, id: c_int, x: c_int, y: c_int),
    ) -> &mut Self {
        self.vtable.as_mut().OnPointerUp = Some(f);
        self
    }

    /// Returns non-zero if a pointer movement is handled.
    pub fn on_handles_pointer_move(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, id: c_int, x: c_int, y: c_int) -> c_int,
    ) -> &mut Self {
        self.vtable.as_mut().HandlesPointerMove = Some(f);
        self
    }

    /// Returns non-zero if a mouse wheel scroll is handled.
    pub fn on_handles_mouse_scroll(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, delta: f32) -> c_int,
    ) -> &mut Self {
        self.vtable.as_mut().HandlesMouseScroll = Some(f);
        self
    }

    /// Positions widgets on screen. Typically called on window resize.
    pub fn on_layout(&mut self, f: unsafe extern "C" fn(elem: *mut c_void)) -> &mut Self {
        self.vtable.as_mut().Layout = Some(f);
        self
    }

    /// Destroys graphics resources. (textures, vertex buffers, etc)
    pub fn on_context_lost(&mut self, f: unsafe extern "C" fn(elem: *mut c_void)) -> &mut Self {
        self.vtable.as_mut().ContextLost = Some(f);
        self
    }

    /// Allocates graphics resources. (textures, vertex buffers, etc)
    pub fn on_context_recreated(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void),
    ) -> &mut Self {
        self.vtable.as_mut().ContextRecreated = Some(f);
        self
    }

    /// Returns non-zero if a pad axis update is handled.
    pub fn on_handles_pad_axis(
        &mut self,
        f: unsafe extern "C" fn(elem: *mut c_void, axis: c_int, x: f32, y: f32) -> c_int,
    ) -> &mut Self {
        self.vtable.as_mut().HandlesPadAxis = Some(f);
        self
    }
}

impl Default for OwnedScreen {
    fn default() -> Self {
        Self::new()
    }
}

impl Drop for OwnedScreen {
    fn drop(&mut self) {
        #[cfg(not(test))]
        self.remove();
    }
}

// default noop functions
unsafe extern "C" fn Init(_elem: *mut c_void) {}
unsafe extern "C" fn Update(_elem: *mut c_void, _delta: f32) {}
unsafe extern "C" fn Free(_elem: *mut c_void) {}
unsafe extern "C" fn Render(_elem: *mut c_void, _delta: f32) {}
unsafe extern "C" fn BuildMesh(_elem: *mut c_void) {}
unsafe extern "C" fn HandlesInputDown(
    _elem: *mut c_void,
    _key: c_int,
    _device: *mut InputDevice,
) -> c_int {
    0
}
unsafe extern "C" fn OnInputUp(_elem: *mut c_void, _key: c_int, _device: *mut InputDevice) {}
unsafe extern "C" fn HandlesKeyPress(_elem: *mut c_void, _keyChar: c_char) -> c_int {
    0
}
unsafe extern "C" fn HandlesTextChanged(_elem: *mut c_void, _str: *const cc_string) -> c_int {
    0
}
unsafe extern "C" fn HandlesPointerDown(
    _elem: *mut c_void,
    _id: c_int,
    _x: c_int,
    _y: c_int,
) -> c_int {
    0
}
unsafe extern "C" fn OnPointerUp(_elem: *mut c_void, _id: c_int, _x: c_int, _y: c_int) {}
unsafe extern "C" fn HandlesPointerMove(
    _elem: *mut c_void,
    _id: c_int,
    _x: c_int,
    _y: c_int,
) -> c_int {
    0
}
unsafe extern "C" fn HandlesMouseScroll(_elem: *mut c_void, _delta: f32) -> c_int {
    0
}
unsafe extern "C" fn Layout(_elem: *mut c_void) {}
unsafe extern "C" fn ContextLost(_elem: *mut c_void) {}
unsafe extern "C" fn ContextRecreated(_elem: *mut c_void) {}
unsafe extern "C" fn HandlesPadAxis(_elem: *mut c_void, _axis: c_int, _x: f32, _y: f32) -> c_int {
    0
}

#[test]
fn test_screen() {
    extern "C" fn init(_elem: *mut c_void) {
        //
    }

    let _screen = OwnedScreen::new().on_init(init);
    let mut screen = OwnedScreen::new();
    screen.on_init(init);
}