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
use crate::{
element::{MeasureContext, ProcessContext, UiElement},
layout::{Direction, LayoutInfo},
text::{FontHandle, TextRenderer},
draw::{
ImageHandle,
TextureFormat,
UiDrawCall,
UiDrawCommandList,
atlas::{TextureAtlasManager, TextureAtlasMeta},
},
signal::{Signal, SignalStore},
event::{EventQueue, UiEvent},
input::UiInputState,
rect::Rect,
state::StateRepo,
};
/// The main instance of the UI system.
///
/// In most cases, you should only have one instance of this struct, but multiple instances are allowed\
/// (Please note that it's possible to render multiple UI "roots" using a single instance)
pub struct UiInstance {
stateful_state: StateRepo,
prev_draw_commands: UiDrawCommandList,
draw_commands: UiDrawCommandList,
draw_call: UiDrawCall,
draw_call_modified: bool,
text_renderer: TextRenderer,
atlas: TextureAtlasManager,
events: EventQueue,
input: UiInputState,
signal: SignalStore,
/// True if in the middle of a laying out a frame
state: bool,
}
impl UiInstance {
/// Crate and initialize a new instance of the UI
///
/// In most cases, you should only do this *once*, during the initialization of your application
pub fn new() -> Self {
UiInstance {
//mouse_position: Vec2::ZERO,
stateful_state: StateRepo::new(),
//event_queue: VecDeque::new(),
// root_elements: Vec::new(),
prev_draw_commands: UiDrawCommandList::default(),
draw_commands: UiDrawCommandList::default(),
draw_call: UiDrawCall::default(),
draw_call_modified: false,
// ftm: FontTextureManager::default(),
text_renderer: TextRenderer::new(),
atlas: {
let mut atlas = TextureAtlasManager::default();
atlas.add_dummy();
atlas
},
events: EventQueue::new(),
input: UiInputState::new(),
signal: SignalStore::new(),
state: false,
}
}
/// Parse and add a font from a raw byte slice to the UI\
/// TrueType (`.ttf`/`.ttc`) and OpenType (`.otf`) fonts are supported\
///
/// Returns a font handle ([`FontHandle`]).
///
/// ## Panics:
/// If the font data is invalid or corrupt
pub fn add_font(&mut self, font: &[u8]) -> FontHandle {
self.text_renderer.add_font_from_bytes(font)
}
/// Add an image to the texture atlas\
/// Accepted texture formats are `Rgba` and `Grayscale`
///
/// Returns an image handle ([`ImageHandle`])\
/// This handle can be used to reference the texture in draw commands\
/// It's a light reference and can be cloned/copied freely, but will not be cleaned up even when dropped
pub fn add_image(&mut self, format: TextureFormat, data: &[u8], width: usize) -> ImageHandle {
self.atlas.add(width, data, format)
}
//TODO better error handling
/// Add an image from a file to the texture atlas\
/// (experimental, may be removed in the future)
///
/// Requires the `image` feature
///
/// # Panics:
/// - If the file exists but contains invalid image data\
/// (this will change to a soft error in the future)
#[cfg(feature = "image")]
pub fn add_image_file_path(&mut self, path: impl AsRef<std::path::Path>) -> Result<ImageHandle, std::io::Error> {
use std::io::{Read, Seek};
// Open the file (and wrap it in a bufreader)
let mut file = std::io::BufReader::new(std::fs::File::open(path)?);
//Guess the image format from the magic bytes
//Read like 64 bytes, which should be enough for magic byte detection
//well this would fail if the image is somehow smaller than 64 bytes, but who the fvck cares...
let mut magic = [0; 64];
file.read_exact(&mut magic)?;
let format = image::guess_format(&magic).expect("Invalid image data (FORMAT)");
file.seek(std::io::SeekFrom::Start(0))?;
//Parse the image and read the raw uncompressed rgba data
let image = image::load(file, format).expect("Invalid image data");
let image_rgba = image.as_rgba8().unwrap();
//Add the image to the atlas
let handle = self.add_image(
TextureFormat::Rgba,
image_rgba,
image.width() as usize
);
Ok(handle)
}
/// Push a font to the font stack\
/// The font will be used for all text rendering until it is popped
///
/// This function is useful for replacing the default font, use sparingly\
/// (This library attempts to be stateless, however passing the font to every text element is not very practical)
pub fn push_font(&mut self, font: FontHandle) {
self.text_renderer.push_font(font);
}
/// Pop a font from the font stack\
///
/// ## Panics:
/// If the font stack is empty
pub fn pop_font(&mut self) {
self.text_renderer.pop_font();
}
/// Get the current default font
pub fn current_font(&self) -> FontHandle {
self.text_renderer.current_font()
}
/// Add an element or an element tree to the UI
///
/// Use the `rect` parameter to specify the position and size of the element\
/// (usually, the size of the window/screen)
///
/// ## Panics:
/// If called while the UI is not active (call [`UiInstance::begin`] first)
pub fn add(&mut self, element: impl UiElement, rect: impl Into<Rect>) {
assert!(self.state, "must call UiInstance::begin before adding elements");
let rect: Rect = rect.into();
let layout = LayoutInfo {
position: rect.position,
max_size: rect.size,
direction: Direction::Vertical,
remaining_space: None,
};
let measure = element.measure(MeasureContext {
state: &self.stateful_state,
layout: &layout,
text_measure: self.text_renderer.to_measure(),
current_font: self.text_renderer.current_font(),
images: self.atlas.context(),
});
element.process(ProcessContext {
measure: &measure,
state: &mut self.stateful_state,
layout: &layout,
draw: &mut self.draw_commands,
text_measure: self.text_renderer.to_measure(),
current_font: self.text_renderer.current_font(),
images: self.atlas.context(),
input: self.input.ctx(),
signal: &mut self.signal,
});
}
/// Prepare the UI for layout and processing\
/// You must call this function at the beginning of the frame, before adding any elements\
///
/// ## Panics:
/// If called twice in a row (for example, if you forget to call [`UiInstance::end`])\
/// This is an indication of a bug in your code and should be fixed.
pub fn begin(&mut self) {
//check and update current state
assert!(!self.state, "must call UiInstance::end before calling UiInstance::begin again");
self.state = true;
//first, drain and process the event queue
self.input.update_state(&mut self.events);
//then, reset the (remaining) signals
self.signal.clear();
//then, reset the draw commands
std::mem::swap(&mut self.prev_draw_commands, &mut self.draw_commands);
self.draw_commands.commands.clear();
self.draw_call_modified = false;
//reset atlas modification flag
self.atlas.reset_modified();
}
/// End the frame and prepare the UI for rendering\
/// You must call this function at the end of the frame, before rendering the UI
///
/// ## Panics:
/// If called without calling [`UiInstance::begin`] first. (or if called twice)\
/// This is an indication of a bug in your code and should be fixed.
pub fn end(&mut self) {
//check and update current state
assert!(self.state, "must call UiInstance::begin before calling UiInstance::end");
self.state = false;
//check if the draw commands have been modified
if self.draw_commands.commands == self.prev_draw_commands.commands {
return
}
//if they have, rebuild the draw call and set the modified flag
self.draw_call = UiDrawCall::build(&self.draw_commands, &mut self.atlas, &mut self.text_renderer);
self.draw_call_modified = true;
}
/// Get the draw call information for the current frame
///
/// This function should only be used by the render backend.\
/// You should not call this directly unless you're implementing a custom render backend
///
/// Returns a tuple with a boolean indicating if the buffers have been modified since the last frame
///
/// You should only call this function *after* [`UiInstance::end`]\
/// Calling it in the middle of a frame will result in a warning but will not cause a panic\
/// (please note that doing so is probably a mistake and should be fixed in your code)\
/// Doing so anyway will return draw call data for the previous frame, but the `modified` flag will *always* be incorrect until [`UiInstance::end`] is called
///
pub fn draw_call(&self) -> (bool, &UiDrawCall) {
if self.state {
log::warn!("UiInstance::draw_call called while in the middle of a frame, this is probably a mistake");
}
(self.draw_call_modified, &self.draw_call)
}
/// Get the texture atlas size and data for the current frame
///
/// This function should only be used by the render backend.\
/// You should not call this directly unless you're implementing a custom render backend
///
/// You should only call this function *after* [`UiInstance::end`]\
/// Calling it in the middle of a frame will result in a warning but will not cause a panic\
/// (please note that doing so is probably a mistake and should be fixed in your code)\
/// Using this function in the middle of a frame will return partially modified atlas data that may be outdated or incomplete\
/// This will lead to rendering artifacts, 1-frame delays and flashes and is probably not what you want
///
/// Make sure to check [`TextureAtlasMeta::modified`] to see if the texture has been modified
/// since the beginning of the current frame before uploading it to the GPU
pub fn atlas(&self) -> TextureAtlasMeta {
if self.state {
log::warn!("UiInstance::atlas called while in the middle of a frame, this is probably a mistake");
}
self.atlas.meta()
}
/// Push a platform event to the UI event queue
///
/// You should call this function *before* calling [`UiInstance::begin`] or after calling [`UiInstance::end`]\
/// Calling it in the middle of a frame will result in a warning but will not cause a panic\
/// (please note that doing so is probably a mistake and should be fixed in your code)\
/// In this case, the event will be processed in the next frame, but in some cases may affect the current frame.\
/// (The exact behavior is not guaranteed and you should avoid doing this if possible)
///
/// This function should only be used by the platform backend.\
/// You should not call this directly unless you're implementing a custom platform backend
/// or have a very specific usecase
pub fn push_event(&mut self, event: UiEvent) {
if self.state {
log::warn!("UiInstance::push_event called while in the middle of a frame, this is probably a mistake");
}
self.events.push(event);
}
/// Push a "fake" signal to the UI signal queue
pub fn push_signal<T: Signal + 'static>(&mut self, signal: T) {
self.signal.add(signal);
}
//TODO: offer a non-consuming version of this function for T: Clone
/// Process all signals of a given type
///
/// This clears the signal queue for the given type and iterates over all signals
pub fn process_signals<T: Signal + 'static>(&mut self, f: impl FnMut(T)) {
self.signal.drain::<T>().for_each(f);
}
}
impl Default for UiInstance {
fn default() -> Self {
Self::new()
}
}