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}