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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! An `App` component to create GUI applications with `Ultralight`.
//!
//! The `App` component of `Ultralight` allows you to create a GUI application
//! that uses GPU rendering to render web pages.
//!
//! If you want to have access to the inner parts of the `Ultralight` engine,
//! have access to the textures to integrate into your game/application, check
//! [`Renderer`] where you can implement your own
//! [`GpuDriver`](crate::gpu_driver::GpuDriver) and integrate it with your project.
use crate::config::Config;
use crate::error::CreationError;
use crate::renderer::Renderer;
use crate::window::{Window, WindowFlags};

/// Settings specific for the [`App`].
pub struct Settings {
    internal: ul_sys::ULSettings,
}

impl Settings {
    /// Starts the building process for the [`Settings`] struct. returns a builder
    /// which can be used to configure the settings.
    pub fn start() -> SettingsBuilder {
        SettingsBuilder::default()
    }

    /// Returns the underlying [`ul_sys::ULSettings`] struct, to be used locally for
    /// calling the underlying C API.
    pub(crate) unsafe fn to_ul(&self) -> ul_sys::ULSettings {
        self.internal
    }
}

impl Drop for Settings {
    fn drop(&mut self) {
        unsafe {
            ul_sys::ulDestroySettings(self.internal);
        }
    }
}

#[derive(Default)]
/// Builder for the [`Settings`] struct.
pub struct SettingsBuilder {
    developer_name: Option<String>,
    app_name: Option<String>,
    filesystem_path: Option<String>,
    load_shaders_from_filesystem: Option<bool>,
    force_cpu_renderer: Option<bool>,
}

impl SettingsBuilder {
    /// The name of the developer of this app.
    ///
    /// This is used to generate a unique path to store local application data
    /// on the user's machine.
    pub fn developer_name(mut self, developer_name: &str) -> Self {
        self.developer_name = Some(developer_name.to_string());
        self
    }

    /// The name of this app.
    ///
    /// This is used to generate a unique path to store local application data
    /// on the user's machine.
    pub fn app_name(mut self, app_name: &str) -> Self {
        self.app_name = Some(app_name.to_string());
        self
    }

    /// The root file path for our file system. You should set this to the
    /// relative path where all of your app data is.
    ///
    /// This will be used to resolve all file URLs, eg `file:///page.html`.
    ///
    /// This relative path is resolved using the following logic:
    ///     - Windows: relative to the executable path
    ///     - Linux:   relative to the executable path
    ///     - macOS:   relative to `YourApp.app/Contents/Resources/`
    pub fn filesystem_path(mut self, filesystem_path: &str) -> Self {
        self.filesystem_path = Some(filesystem_path.to_string());
        self
    }

    /// Whether or not we should load and compile shaders from the file system
    /// (eg, from the /shaders/ path, relative to [`filesystem_path`](Self::filesystem_path)).
    ///
    /// If this is false (the default), we will instead load pre-compiled shaders
    /// from memory which speeds up application startup time.
    pub fn load_shaders_from_filesystem(mut self, load_shaders_from_filesystem: bool) -> Self {
        self.load_shaders_from_filesystem = Some(load_shaders_from_filesystem);
        self
    }

    /// We try to use the GPU renderer when a compatible GPU is detected.
    ///
    /// Set this to true to force the engine to always use the CPU renderer.
    pub fn force_cpu_renderer(mut self, force_cpu_renderer: bool) -> Self {
        self.force_cpu_renderer = Some(force_cpu_renderer);
        self
    }

    /// Builds the [`Settings`] struct using the settings configured in this builder.
    ///
    /// Return [`None`] if failed to create [`Settings`].
    pub fn build(self) -> Option<Settings> {
        let internal = unsafe { ul_sys::ulCreateSettings() };

        if internal.is_null() {
            return None;
        }

        set_config_str!(internal, self.developer_name, ulSettingsSetDeveloperName);

        set_config_str!(internal, self.app_name, ulSettingsSetAppName);

        set_config_str!(internal, self.filesystem_path, ulSettingsSetFileSystemPath);

        set_config!(
            internal,
            self.load_shaders_from_filesystem,
            ulSettingsSetLoadShadersFromFileSystem
        );

        set_config!(
            internal,
            self.force_cpu_renderer,
            ulSettingsSetForceCPURenderer
        );

        Some(Settings { internal })
    }
}

/// Monitor struct, represents a platform monitor.
pub struct Monitor {
    // This is managed by the `App`, so we don't need to free it.
    internal: ul_sys::ULMonitor,
}

impl Monitor {
    /// Get the DPI scale (1.0 = 100%)
    pub fn get_scale(&self) -> f64 {
        unsafe { ul_sys::ulMonitorGetScale(self.internal) }
    }

    /// Get the width of the monitor.
    pub fn get_width(&self) -> u32 {
        unsafe { ul_sys::ulMonitorGetWidth(self.internal) }
    }

    /// Get the height of the monitor.
    pub fn get_height(&self) -> u32 {
        unsafe { ul_sys::ulMonitorGetHeight(self.internal) }
    }
}

/// Main application struct.
pub struct App {
    settings: Settings,

    monitor: Monitor,
    renderer: Renderer,

    internal: ul_sys::ULApp,
}

impl App {
    // TODO: the C++ library creates a singleton and stores the object globaly
    //       should we do the same and return a reference only?
    /// Creates a new application instance.
    ///
    /// # Arguments
    /// * `settings` - The settings to customize the app runtime behaviour.
    /// * `config` - Options for `Ultralight` [`Renderer`].
    ///
    /// Leaving `settings` or `config` as `None` will use the default settings/
    /// config.
    ///
    /// Returns [`None`] if the application could not be created.
    pub fn new(settings: Option<Settings>, config: Option<Config>) -> Result<Self, CreationError> {
        let config = match config {
            Some(config) => config,
            None => Config::start()
                .build()
                .ok_or(CreationError::NullReference)?,
        };

        let settings = match settings {
            Some(settings) => settings,
            None => Settings::start()
                .build()
                .ok_or(CreationError::NullReference)?,
        };

        unsafe {
            let app_internal = ul_sys::ulCreateApp(settings.to_ul(), config.to_ul());
            if app_internal.is_null() {
                return Err(CreationError::NullReference);
            }

            let monitor = Monitor {
                internal: ul_sys::ulAppGetMainMonitor(app_internal),
            };
            if monitor.internal.is_null() {
                ul_sys::ulDestroyApp(app_internal);
                return Err(CreationError::NullReference);
            }
            let renderer_raw = ul_sys::ulAppGetRenderer(app_internal);
            if let Ok(renderer) = Renderer::from_raw(renderer_raw) {
                Ok(Self {
                    settings,
                    internal: app_internal,
                    monitor,
                    renderer,
                })
            } else {
                ul_sys::ulDestroyApp(app_internal);
                Err(CreationError::NullReference)
            }
        }
    }

    // TODO: the `Settings` struct is useless since we can't access the settings
    //       fields from the CAPI. so either remove this or find a solution.
    /// Get the settings of the app.
    pub fn settings(&self) -> &Settings {
        &self.settings
    }

    /// Get the main monitor of the app.
    pub fn main_monitor(&self) -> &Monitor {
        &self.monitor
    }

    /// Whether or not the app is running.
    pub fn is_running(&self) -> bool {
        unsafe { ul_sys::ulAppIsRunning(self.internal) }
    }

    /// Get the underlying [`Renderer`] instance.
    pub fn renderer(&self) -> &Renderer {
        &self.renderer
    }

    set_callback! {
        /// Set a callback to be called whenever the App updates.
        /// You should update all app logic here.
        ///
        /// This event is fired right before the run loop calls
        /// [`Renderer::update`](crate::renderer::Renderer::update) and
        /// [`Renderer::render`](crate::renderer::Renderer::render).
        pub fn set_update_callback(&self, callback: FnMut()) :
            ulAppSetUpdateCallback() {
        }
    }

    /// Start the main loop.
    pub fn run(&self) {
        unsafe { ul_sys::ulAppRun(self.internal) }
    }

    /// Stop the main loop.
    pub fn quit(&self) {
        unsafe { ul_sys::ulAppQuit(self.internal) }
    }

    /// Create a new window.
    ///
    /// # Arguments
    /// * `width` - The width of the window.
    /// * `height` - The height of the window.
    /// * `fullscreen` - Whether or not the window should be fullscreen.
    /// * `window_flags` - Various [`WindowFlags`].
    ///
    /// The window will be shown by default unless [`WindowFlags::hidden`] was set.
    ///
    /// The window will be closed automatically if the object is dropped.
    ///
    /// Returns [`None`] if the window could not be created.
    pub fn create_window(
        &self,
        width: u32,
        height: u32,
        fullscreen: bool,
        window_flags: WindowFlags,
    ) -> Option<Window> {
        unsafe {
            Window::create(
                self.monitor.internal,
                width,
                height,
                fullscreen,
                window_flags,
            )
        }
    }
}

impl Drop for App {
    fn drop(&mut self) {
        unsafe {
            ul_sys::ulDestroyApp(self.internal);
        }
    }
}