use glam::Vec2; use crate::{ draw::{ ImageHandle, TextureFormat, UiDrawCall, UiDrawCommandList, atlas::{TextureAtlasManager, TextureAtlasMeta}, }, element::{MeasureContext, ProcessContext, UiElement}, event::{EventQueue, UiEvent}, input::UiInputState, layout::{Direction, LayoutInfo}, signal::{SignalStore, Signal}, state::StateRepo, text::{FontHandle, TextRenderer} }; /// 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 { //mouse_position: Vec2, stateful_state: StateRepo, //event_queue: VecDeque, 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::default(), //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) -> Result { 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 `max_size` parameter to specify the maximum 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: T, max_size: Vec2) { assert!(self.state, "must call UiInstance::begin before adding elements"); let layout = LayoutInfo { position: Vec2::ZERO, max_size, direction: Direction::Vertical, }; 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(&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(&mut self, f: impl FnMut(T)) { self.signal.drain::().for_each(f); } } impl Default for UiInstance { fn default() -> Self { Self::new() } }