ul_next/
app.rs

1//! An `App` component to create GUI applications with `Ultralight`.
2//!
3//! The `App` component of `Ultralight` allows you to create a GUI application
4//! that uses GPU rendering to render web pages.
5//!
6//! If you want to have access to the inner parts of the `Ultralight` engine,
7//! have access to the textures to integrate into your game/application, check
8//! [`Renderer`] where you can implement your own
9//! [`GpuDriver`](crate::gpu_driver::GpuDriver) and integrate it with your project.
10use std::sync::Arc;
11
12use crate::{
13    config::Config,
14    error::CreationError,
15    renderer::Renderer,
16    window::{Window, WindowFlags},
17    Library,
18};
19
20/// Settings specific for the [`App`].
21pub struct Settings {
22    lib: Arc<Library>,
23    internal: ul_sys::ULSettings,
24}
25
26impl Settings {
27    /// Starts the building process for the [`Settings`] struct. returns a builder
28    /// which can be used to configure the settings.
29    pub fn start() -> SettingsBuilder {
30        SettingsBuilder::default()
31    }
32
33    /// Returns the underlying [`ul_sys::ULSettings`] struct, to be used locally for
34    /// calling the underlying C API.
35    pub(crate) unsafe fn to_ul(&self) -> ul_sys::ULSettings {
36        self.internal
37    }
38}
39
40impl Drop for Settings {
41    fn drop(&mut self) {
42        unsafe {
43            self.lib.appcore().ulDestroySettings(self.internal);
44        }
45    }
46}
47
48#[derive(Default)]
49/// Builder for the [`Settings`] struct.
50pub struct SettingsBuilder {
51    developer_name: Option<String>,
52    app_name: Option<String>,
53    filesystem_path: Option<String>,
54    load_shaders_from_filesystem: Option<bool>,
55    force_cpu_renderer: Option<bool>,
56}
57
58impl SettingsBuilder {
59    /// The name of the developer of this app.
60    ///
61    /// This is used to generate a unique path to store local application data
62    /// on the user's machine.
63    pub fn developer_name(mut self, developer_name: &str) -> Self {
64        self.developer_name = Some(developer_name.to_string());
65        self
66    }
67
68    /// The name of this app.
69    ///
70    /// This is used to generate a unique path to store local application data
71    /// on the user's machine.
72    pub fn app_name(mut self, app_name: &str) -> Self {
73        self.app_name = Some(app_name.to_string());
74        self
75    }
76
77    /// The root file path for our file system. You should set this to the
78    /// relative path where all of your app data is.
79    ///
80    /// This will be used to resolve all file URLs, eg `file:///page.html`.
81    ///
82    /// This relative path is resolved using the following logic:
83    ///     - Windows: relative to the executable path
84    ///     - Linux:   relative to the executable path
85    ///     - macOS:   relative to `YourApp.app/Contents/Resources/`
86    pub fn filesystem_path(mut self, filesystem_path: &str) -> Self {
87        self.filesystem_path = Some(filesystem_path.to_string());
88        self
89    }
90
91    /// Whether or not we should load and compile shaders from the file system
92    /// (eg, from the /shaders/ path, relative to [`filesystem_path`](Self::filesystem_path)).
93    ///
94    /// If this is false (the default), we will instead load pre-compiled shaders
95    /// from memory which speeds up application startup time.
96    pub fn load_shaders_from_filesystem(mut self, load_shaders_from_filesystem: bool) -> Self {
97        self.load_shaders_from_filesystem = Some(load_shaders_from_filesystem);
98        self
99    }
100
101    /// We try to use the GPU renderer when a compatible GPU is detected.
102    ///
103    /// Set this to true to force the engine to always use the CPU renderer.
104    pub fn force_cpu_renderer(mut self, force_cpu_renderer: bool) -> Self {
105        self.force_cpu_renderer = Some(force_cpu_renderer);
106        self
107    }
108
109    /// Builds the [`Settings`] struct using the settings configured in this builder.
110    ///
111    /// Return [`None`] if failed to create [`Settings`].
112    pub fn build(self, lib: Arc<Library>) -> Option<Settings> {
113        let internal = unsafe { lib.appcore().ulCreateSettings() };
114
115        if internal.is_null() {
116            return None;
117        }
118
119        set_config_str!(
120            internal,
121            self.developer_name,
122            lib.appcore().ulSettingsSetDeveloperName
123        );
124
125        set_config_str!(internal, self.app_name, lib.appcore().ulSettingsSetAppName);
126
127        set_config_str!(
128            internal,
129            self.filesystem_path,
130            lib.appcore().ulSettingsSetFileSystemPath
131        );
132
133        set_config!(
134            internal,
135            self.load_shaders_from_filesystem,
136            lib.appcore().ulSettingsSetLoadShadersFromFileSystem
137        );
138
139        set_config!(
140            internal,
141            self.force_cpu_renderer,
142            lib.appcore().ulSettingsSetForceCPURenderer
143        );
144
145        Some(Settings { lib, internal })
146    }
147}
148
149/// Monitor struct, represents a platform monitor.
150pub struct Monitor {
151    lib: Arc<Library>,
152    // This is managed by the `App`, so we don't need to free it.
153    internal: ul_sys::ULMonitor,
154}
155
156impl Monitor {
157    /// Get the DPI scale (1.0 = 100%)
158    pub fn get_scale(&self) -> f64 {
159        unsafe { self.lib.appcore().ulMonitorGetScale(self.internal) }
160    }
161
162    /// Get the width of the monitor.
163    pub fn get_width(&self) -> u32 {
164        unsafe { self.lib.appcore().ulMonitorGetWidth(self.internal) }
165    }
166
167    /// Get the height of the monitor.
168    pub fn get_height(&self) -> u32 {
169        unsafe { self.lib.appcore().ulMonitorGetHeight(self.internal) }
170    }
171}
172
173/// Main application struct.
174pub struct App {
175    lib: Arc<Library>,
176    settings: Settings,
177
178    monitor: Monitor,
179    renderer: Renderer,
180
181    internal: ul_sys::ULApp,
182}
183
184impl App {
185    // TODO: the C++ library creates a singleton and stores the object globaly
186    //       should we do the same and return a reference only?
187    /// Creates a new application instance.
188    ///
189    /// # Arguments
190    /// * `settings` - The settings to customize the app runtime behaviour.
191    /// * `config` - Options for `Ultralight` [`Renderer`].
192    ///
193    /// Leaving `settings` or `config` as `None` will use the default settings/
194    /// config.
195    ///
196    /// Returns [`None`] if the application could not be created.
197    pub fn new(
198        lib: Arc<Library>,
199        settings: Option<Settings>,
200        config: Option<Config>,
201    ) -> Result<Self, CreationError> {
202        let config = match config {
203            Some(config) => config,
204            None => Config::start()
205                .build(lib.clone())
206                .ok_or(CreationError::NullReference)?,
207        };
208
209        let settings = match settings {
210            Some(settings) => settings,
211            None => Settings::start()
212                .build(lib.clone())
213                .ok_or(CreationError::NullReference)?,
214        };
215
216        unsafe {
217            let app_internal = lib.appcore().ulCreateApp(settings.to_ul(), config.to_ul());
218            if app_internal.is_null() {
219                return Err(CreationError::NullReference);
220            }
221
222            let monitor = Monitor {
223                lib: lib.clone(),
224                internal: lib.appcore().ulAppGetMainMonitor(app_internal),
225            };
226            if monitor.internal.is_null() {
227                lib.appcore().ulDestroyApp(app_internal);
228                return Err(CreationError::NullReference);
229            }
230            let renderer_raw = lib.appcore().ulAppGetRenderer(app_internal);
231            if let Ok(renderer) = Renderer::from_raw(lib.clone(), renderer_raw) {
232                Ok(Self {
233                    lib,
234                    settings,
235                    internal: app_internal,
236                    monitor,
237                    renderer,
238                })
239            } else {
240                lib.appcore().ulDestroyApp(app_internal);
241                Err(CreationError::NullReference)
242            }
243        }
244    }
245
246    // TODO: the `Settings` struct is useless since we can't access the settings
247    //       fields from the CAPI. so either remove this or find a solution.
248    /// Get the settings of the app.
249    pub fn settings(&self) -> &Settings {
250        &self.settings
251    }
252
253    /// Get the main monitor of the app.
254    pub fn main_monitor(&self) -> &Monitor {
255        &self.monitor
256    }
257
258    /// Whether or not the app is running.
259    pub fn is_running(&self) -> bool {
260        unsafe { self.lib.appcore().ulAppIsRunning(self.internal) }
261    }
262
263    /// Get the underlying [`Renderer`] instance.
264    pub fn renderer(&self) -> &Renderer {
265        &self.renderer
266    }
267
268    set_callback! {
269        /// Set a callback to be called whenever the App updates.
270        /// You should update all app logic here.
271        ///
272        /// This event is fired right before the run loop calls
273        /// [`Renderer::update`](crate::renderer::Renderer::update) and
274        /// [`Renderer::render`](crate::renderer::Renderer::render).
275        pub fn set_update_callback(&self, callback: FnMut()) :
276            [App::lib.appcore()] ulAppSetUpdateCallback() {}
277    }
278
279    /// Start the main loop.
280    pub fn run(&self) {
281        unsafe { self.lib.appcore().ulAppRun(self.internal) }
282    }
283
284    /// Stop the main loop.
285    pub fn quit(&self) {
286        unsafe { self.lib.appcore().ulAppQuit(self.internal) }
287    }
288
289    /// Create a new window.
290    ///
291    /// # Arguments
292    /// * `width` - The width of the window.
293    /// * `height` - The height of the window.
294    /// * `fullscreen` - Whether or not the window should be fullscreen.
295    /// * `window_flags` - Various [`WindowFlags`].
296    ///
297    /// The window will be shown by default unless [`WindowFlags::hidden`] was set.
298    ///
299    /// The window will be closed automatically if the object is dropped.
300    ///
301    /// Returns [`None`] if the window could not be created.
302    pub fn create_window(
303        &self,
304        width: u32,
305        height: u32,
306        fullscreen: bool,
307        window_flags: WindowFlags,
308    ) -> Option<Window> {
309        unsafe {
310            Window::create(
311                self.lib.clone(),
312                self.monitor.internal,
313                width,
314                height,
315                fullscreen,
316                window_flags,
317            )
318        }
319    }
320}
321
322impl Drop for App {
323    fn drop(&mut self) {
324        unsafe {
325            self.lib.appcore().ulDestroyApp(self.internal);
326        }
327    }
328}