This repository has been archived by the owner on Sep 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
lib.rs
353 lines (335 loc) · 18.1 KB
/
lib.rs
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
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
//! `egui_backend` crate primarily provides traits to abstract away Window and Rendering parts of egui backends.
//! this allows us to use any window backend with any gfx backend crate.
//!
//! egui is an immediate mode gui library. The lifecycle of egui in every frame goes like this:
//! 1. takes input from the window backend. eg: mouse position, keyboard events, resize..
//! 2. constructs gui objects like windows / panels / buttons etc.. and deals with any input interactions.
//! 3. outputs those gui objects as gpu friendly data to be drawn by a gfx backend.
//!
//! So, we need a WindowBackend to provide input to egui and a GfxBackend to draw egui's output.
//! egui already provides an official backends for wgpu, winit and glow, along with a higher level wrapper crate called `eframe`
//! eframe uses `winit` on desktop, custom backend on web and `wgpu`/`glow` for rendering.
//! If that serves your usecase, then it is recommended to keep using that.
//!
//! `egui_backend` crate instead tries to enable separation of window + gfx concerns using traits.
//!
//! this crate provides 3 traits:
//! 1. [`WindowBackend`]: implemented by window backends like [winit](https://docs.rs/winit), [glfw](https://docs.rs/glfw), [sdl2](https://docs.rs/sdl2) etc..
//! 2. [`GfxBackend`]: implemented by rendering backends like [wgpu](https://docs.rs/wgpu), [glow](https://docs.rs/glow), [three-d](https://docs.rs/three-d),
//! 3. [`UserApp`]: implemented by end user's struct which holds the app data as well as egui context and the renderer.
//!
//! This crate will also try to provide functions or structs which are useful across all backends.
//! 1. [`BackendConfig`]: has some configuration which needs to be provided at startup.
//!
//! look at the docs of the relevant trait to learn more.
// #[cfg(target_feature = "egui")]
pub use egui;
// #[cfg(target_feature = "egui")]
use egui::{ClippedPrimitive, FullOutput, PlatformOutput, RawInput, TexturesDelta};
pub use raw_window_handle;
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use std::time::Duration;
/// Intended to provide a common struct which all window backends accept as their configuration.
/// To set size/position/title etc.. just use the windowbackend trait functions after you created the window.
/// This struct is primarily intended for settings which are to be specified *before* creating a window like opengl or transparency etc..
#[derive(Debug, Clone)]
pub struct BackendConfig {
/// true by default
pub is_opengl: bool,
pub opengl_config: Option<OpenGlConfig>,
pub transparent: Option<bool>,
}
impl Default for BackendConfig {
fn default() -> Self {
// let is_opengl = cfg!(target_arch = "wasm32");
let is_opengl = true;
Self {
is_opengl,
transparent: None,
opengl_config: Default::default(),
}
}
}
/// Implement this trait for your windowing backend. the main responsibility of a
/// Windowing Backend is to
/// 1. run event loop and call the necessary functions of Gfx and UserApp every frame.
/// 2. poll and gather events.
/// 3. convert the events to egui's event types before submitting raw input.
/// 4. provide details like window size or window handle to Gfx Backend for creation/update of surface / swapchain.
pub trait WindowBackend: Sized {
/// This will be WindowBackend's configuration. if necessary, just add Boxed closures as its
/// fields and run them before window creation, after window creation etc.. to provide maximum
/// configurability to users
type Configuration: Default + Sized;
/// This type is used by GfxBackend to create/manage swapchain/surfaces. We use an associated type,
/// because impl Trait is not yet supported in return positions of trait functions.
/// For now, we only support a single window.
type WindowType: HasRawDisplayHandle + HasRawWindowHandle + Sized;
/// Create a new window backend.
/// config is the custom configuration of a specific window backend
/// while backend_config is a general config struct for common enough settings like window title.
fn new(config: Self::Configuration, backend_config: BackendConfig) -> Self;
/// extracts all the events of this frame.
fn take_raw_input(&mut self) -> RawInput;
/// This gives us the "Window" struct of this particular backend. should implement raw window handle apis.
/// if this is None, it means window hasn't been created, or has been destroyed for some reason.
/// usually on android, this means the app is suspended. Other platforms always return a live window.
fn get_window(&mut self) -> Option<&mut Self::WindowType>;
/// sometimes, the frame buffer size might have changed and the resize event is still not received.
/// in those cases, wgpu / vulkan like render apis will throw an error if you try to acquire swapchain image
/// with an outdated size. you will need to provide the *latest* size.
/// if the return value is `None`, the window doesn't exist yet. eg: on android, after suspend but before resume event.
fn get_live_physical_size_framebuffer(&mut self) -> Option<[u32; 2]>;
/// Run the event loop
/// Window Backend must call the relevant UserApp functions at the right time.
fn run_event_loop<U: UserApp<UserWindowBackend = Self> + 'static>(user_app: U);
/// config if GfxBackend needs them. usually tells the GfxBackend whether we have an opengl or non-opengl window.
/// for example, if a vulkan backend gets a window with opengl, it can gracefully panic instead of segfaulting.
/// this also serves as an indicator for opengl gfx backends, on whether this backend supports `swap_buffers` or `get_proc_address` functions.
fn get_config(&self) -> &BackendConfig;
/// optional. only implemented by gl windowing libraries like glfw/sdl2 which hold the gl context with Window
/// gfx backends like glow (or raw opengl) will call this if needed.
/// panic! if your WindowBackend doesn't implemented this functionality (eg: winit)
fn swap_buffers(&mut self) {
unimplemented!("swap buffers is not implemented for this window backend");
}
/// A direct helper function to tell us if the window is backed by opengl or non-opengl (vk/dx/mtl).
fn is_opengl(&self) -> bool;
/// get openGL function addresses. optional, just like `Self::swap_buffers`.
/// panic! if it doesn't apply to your WindowBackend. eg: winit.
fn get_proc_address(&mut self, symbol: &str) -> *const core::ffi::c_void {
unimplemented!(
"get_proc_address is not implemented for this window backend. called with {symbol}"
);
}
/// To change the title of the window
fn set_window_title(&mut self, title: &str);
/// The position of the window relative to top left of the monitor/screen/workspace.
fn get_window_position(&mut self) -> Option<[f32; 2]>;
/// set the position of the window relative to top left of the monitor/screen/workspace.
fn set_window_position(&mut self, pos: [f32; 2]);
/// get the size of the window in logical pixels. Use `Self::get_live_physical_framebuffer_size` for physical surface size.
fn get_window_size(&mut self) -> Option<[f32; 2]>;
/// set the window size in logical pixels.
fn set_window_size(&mut self, size: [f32; 2]);
/// check if window is minimized.
/// Warn: On some platforms, size of a minimized window might be returned as "zero"
fn get_window_minimized(&mut self) -> Option<bool>;
/// To minimize/restore a window.
fn set_minimize_window(&mut self, min: bool);
/// If window is maximized.
fn get_window_maximized(&mut self) -> Option<bool>;
/// To maximize/restore the window.
fn set_maximize_window(&mut self, max: bool);
/// If the window is visible on screen.
fn get_window_visibility(&mut self) -> Option<bool>;
/// To show/hide the window. Usually, you will want to hide the window until you have prepared/drawn
/// to the surface atleast once, and then show the window. Otherwise, Users might see garbage until the first frame.
fn set_window_visibility(&mut self, vis: bool);
/// If the window will always stay on top of other windows
fn get_always_on_top(&mut self) -> Option<bool>;
/// To make the window always stay on top of other windows. Usually used for Overlays.
fn set_always_on_top(&mut self, always_on_top: bool);
/// If the window is "passthrough".
/// Passthrough simply means that the window is only visually visible, but input will go to whatever is behind/below the window.
fn get_passthrough(&mut self) -> Option<bool>;
/// To make the window passthrough or non-passthrough. used by overlays.
/// By checking if you application gui (egui) requires the input or not, you can set this to act as an overlay.
fn set_passthrough(&mut self, passthrough: bool);
}
/// Trait for Gfx backends. these could be Gfx APIs like opengl or vulkan or wgpu etc..
/// or higher level renderers like three-d or rend3 or custom renderers etc..
pub trait GfxBackend {
/// similar to WindowBackendConfig. A custom config struct for the creation of GfxBackend
type Configuration: Default;
/// create a new GfxBackend using info from window backend and custom config struct
/// `WindowBackend` trait provides the backend config, which can be used by the renderer to check
/// for compatibility.
///
/// for example, a glow renderer might want an opengl context. but if the window was created without one,
/// the glow renderer should panic.
fn new(window_backend: &mut impl WindowBackend, config: Self::Configuration) -> Self;
/// Android only. callend on app suspension, which destroys the window.
/// so, will need to destroy the `Surface` and recreate during resume event.
/// ### Panic
/// Panic on other platforms
fn suspend(&mut self, _window_backend: &mut impl WindowBackend) {
unimplemented!("This window backend doesn't implement suspend event");
}
/// Android Only. called when app is resumed after suspension.
/// On Android, window can only be created on resume event. so, you cannot create a `Surface` before entering the event loop.
/// when this fn is called, we can create a new surface (swapchain) for the window.
/// doesn't apply on other platforms.
fn resume(&mut self, _window_backend: &mut impl WindowBackend) {}
/// called if framebuffer has been resized. use this to reconfigure your swapchain/surface/viewport..
fn resize_framebuffer(&mut self, window_backend: &mut impl WindowBackend);
/// prepare the surface / swapchain etc.. by acquiring an image for the current frame.
/// use `WindowBackend::get_live_physical_size_framebuffer` fn to resize your swapchain if it is out of date.
fn prepare_frame(&mut self, window_backend: &mut impl WindowBackend);
/// This is where the renderers will start creating renderpasses, issue draw calls etc.. using the data previously prepared.
fn render_egui(
&mut self,
meshes: Vec<ClippedPrimitive>,
textures_delta: TexturesDelta,
logical_screen_size: [f32; 2],
);
/// This is called at the end of the frame. after everything is drawn, you can now present
/// on opengl, renderer might call `WindowBackend::swap_buffers`.
/// on wgpu / vulkan, renderer might submit commands to queues, present swapchain image etc..
fn present(&mut self, window_backend: &mut impl WindowBackend);
}
/// This is the trait most users care about.
/// Just have a struct with WindowBackend, GfxBackend and egui context as fields.
/// and implmenet the `get_all` and `gui_run` fn for a simple app.
/// or you can overload the `run` fn for more advanced stuff like filtering input events etc..
pub trait UserApp {
type UserGfxBackend: GfxBackend;
type UserWindowBackend: WindowBackend;
/// A shortcut function to get windodw, gfx backends as well as egui context.
/// Primarily used to provide default implementations of `resize_framebuffer`, `resume`, `suspend` and `run` fns.
fn get_all(
&mut self,
) -> (
&mut Self::UserWindowBackend,
&mut Self::UserGfxBackend,
&egui::Context,
);
fn resize_framebuffer(&mut self) {
let (wb, gb, _) = self.get_all();
gb.resize_framebuffer(wb);
}
fn resume(&mut self) {
let (wb, gb, _) = self.get_all();
gb.resume(wb);
}
fn suspend(&mut self) {
let (wb, gb, _) = self.get_all();
gb.suspend(wb);
}
fn run(&mut self, logical_size: [f32; 2]) -> Option<(PlatformOutput, Duration)> {
let (wb, gb, egui_context) = self.get_all();
let egui_context = egui_context.clone();
// don't bother doing anything if there's no window
if let Some(full_output) = if wb.get_window().is_some() {
let input = wb.take_raw_input();
gb.prepare_frame(wb);
egui_context.begin_frame(input);
self.gui_run();
Some(egui_context.end_frame())
} else {
None
} {
let FullOutput {
platform_output,
repaint_after,
textures_delta,
shapes,
} = full_output;
let (wb, gb, egui_context) = self.get_all();
let egui_context = egui_context.clone();
gb.render_egui(
egui_context.tessellate(shapes),
textures_delta,
logical_size,
);
gb.present(wb);
return Some((platform_output, repaint_after));
}
None
}
/// This is the only function user needs to implement. this function will be called every frame by the default implementation of `run` fn.
/// Just use the egui context to build the user interface, and after this function is called, `run` fn default impl will take care of drawing egui.
fn gui_run(&mut self);
}
/// Some nice util functions commonly used by egui backends.
pub mod util {
/// input: clip rectangle in logical pixels, scale and framebuffer size in physical pixels
/// we will get [x, y, width, height] of the scissor rectangle.
///
/// internally, it will
/// 1. multiply clip rect and scale to convert the logical rectangle to a physical rectangle in framebuffer space.
/// 2. clamp the rectangle between 0..width and 0..height of the frambuffer. make sure that width/height are positive/zero.
/// 3. return Some only if width/height of scissor region are not zero.
///
/// This fn is for wgpu/metal/directx.
/// For opengl, use [`scissor_from_clip_rect_opengl`].
pub fn scissor_from_clip_rect(
clip_rect: &egui::Rect,
scale: f32,
physical_framebuffer_size: [u32; 2],
) -> Option<[u32; 4]> {
// copy paste from official egui impl because i have no idea what this is :D
// first, we turn the clip rectangle into physical framebuffer coordinates
// clip_min is top left point and clip_max is bottom right.
let clip_min_x = scale * clip_rect.min.x;
let clip_min_y = scale * clip_rect.min.y;
let clip_max_x = scale * clip_rect.max.x;
let clip_max_y = scale * clip_rect.max.y;
// round to integers
let clip_min_x = clip_min_x.round() as i32;
let clip_min_y = clip_min_y.round() as i32;
let clip_max_x = clip_max_x.round() as i32;
let clip_max_y = clip_max_y.round() as i32;
// clamp top_left of clip rect to be within framebuffer bounds
let clip_min_x = clip_min_x.clamp(0, physical_framebuffer_size[0] as i32);
let clip_min_y = clip_min_y.clamp(0, physical_framebuffer_size[1] as i32);
// clamp bottom right of clip rect to be between top_left of clip rect and framebuffer bottom right bounds
let clip_max_x = clip_max_x.clamp(clip_min_x, physical_framebuffer_size[0] as i32);
let clip_max_y = clip_max_y.clamp(clip_min_y, physical_framebuffer_size[1] as i32);
// x,y are simply top left coords
let x = clip_min_x as u32;
let y = clip_min_y as u32;
// width height by subtracting bottom right with top left coords.
let width = (clip_max_x - clip_min_x) as u32;
let height = (clip_max_y - clip_min_y) as u32;
// return only if scissor width/height are not zero. otherwise, no need for a scissor rect at all
(width != 0 && height != 0).then_some([x, y, width, height])
}
/// For wgpu, dx, metal, use [`scissor_from_clip_rect`]..
///
/// **NOTE**:
/// egui coordinates are in logical window space with top left being [0, 0].
/// In opengl, bottom left is [0, 0].
/// so, we need to use bottom left clip-rect coordinate as x,y instead of top left.
/// 1. bottom left corner's y coordinate is simply top left corner's y added with clip rect height
/// 2. but this `y` is represents top border + y units. in opengl, we need units from bottom border
/// 3. we know that for any point y, distance between top and y + distance between bottom and y gives us total height
/// 4. so, height - y units from top gives us y units from bottom.
/// math is suprisingly hard to write down.. just draw it on a paper, it makes sense.
pub fn scissor_from_clip_rect_opengl(
clip_rect: &egui::Rect,
scale: f32,
physical_framebuffer_size: [u32; 2],
) -> Option<[u32; 4]> {
scissor_from_clip_rect(clip_rect, scale, physical_framebuffer_size).map(|mut arr| {
arr[1] = physical_framebuffer_size[1] - (arr[1] + arr[3]);
arr
})
}
}
#[derive(Debug, Clone, Default)]
pub struct OpenGlConfig {
/// minimum major opengl version
/// 2 or 3 is common
pub major: Option<u8>,
/// minor version.
pub minor: Option<u8>,
/// If we want an ES context
/// false is default
pub es: Option<bool>,
/// try creating srgb surface for window
pub srgb: Option<bool>,
/// depth bits
pub depth: Option<u8>,
/// stencil bits
pub stencil: Option<u8>,
/// The number of bits per each color channel.
/// default should be rgba with 8 bits each.
pub color_bits: Option<[u8; 4]>,
/// Must be a power of 2
pub multi_samples: Option<u8>,
/// If false, we request a compatible context.
/// If true, core context.
/// true is default.
pub core: Option<bool>,
}