mod cache; mod event; mod graphic; mod scale; mod widgets; #[macro_use] pub mod img_ids; #[macro_use] pub mod fonts; pub mod ice; pub use event::Event; pub use graphic::{Graphic, Id as GraphicId, Rotation, SampleStrat, Transform}; pub use scale::{Scale, ScaleMode}; pub use widgets::{ image_frame::ImageFrame, image_slider::ImageSlider, ingame::{Ingame, Ingameable}, radio_list::RadioList, slot, toggle_button::ToggleButton, tooltip::{Tooltip, TooltipManager, Tooltipable}, }; use crate::{ render::{ create_ui_quad, create_ui_tri, Consts, DynamicModel, Globals, Mesh, RenderError, Renderer, UiLocals, UiMode, UiPipeline, }, window::Window, Error, }; #[rustfmt::skip] use ::image::GenericImageView; use cache::Cache; use common::{span, util::srgba_to_linear}; use conrod_core::{ event::Input, graph::{self, Graph}, image::{Id as ImageId, Map}, input::{touch::Touch, Motion, Widget}, render::{Primitive, PrimitiveKind}, text::{self, font}, widget::{self, id::Generator}, Rect, Scalar, UiBuilder, UiCell, }; use core::{convert::TryInto, f32, f64, ops::Range}; use graphic::TexId; use hashbrown::hash_map::Entry; use std::{sync::Arc, time::Duration}; use tracing::{error, warn}; use vek::*; #[derive(Debug)] pub enum UiError { RenderError(RenderError), } enum DrawKind { Image(TexId), // Text and non-textured geometry Plain, } enum DrawCommand { Draw { kind: DrawKind, verts: Range }, Scissor(Aabr), WorldPos(Option), } impl DrawCommand { fn image(verts: Range, id: TexId) -> DrawCommand { DrawCommand::Draw { kind: DrawKind::Image(id), verts: verts .start .try_into() .expect("Vertex count for UI rendering does not fit in a u32!") ..verts .end .try_into() .expect("Vertex count for UI rendering does not fit in a u32!"), } } fn plain(verts: Range) -> DrawCommand { DrawCommand::Draw { kind: DrawKind::Plain, verts: verts .start .try_into() .expect("Vertex count for UI rendering does not fit in a u32!") ..verts .end .try_into() .expect("Vertex count for UI rendering does not fit in a u32!"), } } } pub struct Ui { pub ui: conrod_core::Ui, image_map: Map<(graphic::Id, Rotation)>, cache: Cache, // Draw commands for the next render draw_commands: Vec, // Mesh buffer for UI vertices; we reuse its allocation in order to limit vector reallocations // during redrawing. mesh: Mesh, // Model for drawing the ui model: DynamicModel, // Consts for default ui drawing position (ie the interface) interface_locals: Consts, default_globals: Consts, // Consts to specify positions of ingame elements (e.g. Nametags) ingame_locals: Vec>, // Window size for updating scaling window_resized: Option>, // Scale factor changed scale_factor_changed: Option, // Used to delay cache resizing until after current frame is drawn need_cache_resize: bool, // Scaling of the ui scale: Scale, // Tooltips tooltip_manager: TooltipManager, } impl Ui { pub fn new(window: &mut Window) -> Result { let scale = Scale::new(window, ScaleMode::Absolute(1.0), 1.0); let win_dims = scale.scaled_resolution().into_array(); let renderer = window.renderer_mut(); let mut ui = UiBuilder::new(win_dims).build(); // NOTE: Since we redraw the actual frame each time whether or not the UI needs // to be updated, there's no reason to set the redraw count higher than // 1. ui.set_num_redraw_frames(1); let tooltip_manager = TooltipManager::new( ui.widget_id_generator(), Duration::from_millis(1), Duration::from_millis(100), scale.scale_factor_logical(), ); Ok(Self { ui, image_map: Map::new(), cache: Cache::new(renderer)?, draw_commands: Vec::new(), mesh: Mesh::new(), model: renderer.create_dynamic_model(100)?, interface_locals: renderer.create_consts(&[UiLocals::default()])?, default_globals: renderer.create_consts(&[Globals::default()])?, ingame_locals: Vec::new(), window_resized: None, scale_factor_changed: None, need_cache_resize: false, scale, tooltip_manager, }) } // Set the scaling mode of the ui. pub fn set_scaling_mode(&mut self, mode: ScaleMode) { self.scale.set_scaling_mode(mode); // To clear the cache (it won't be resized in this case) self.need_cache_resize = true; // Give conrod the new size. let (w, h) = self.scale.scaled_resolution().into_tuple(); self.ui.handle_event(Input::Resize(w, h)); } pub fn scale_factor_changed(&mut self, scale_factor: f64) { self.scale_factor_changed = Some(scale_factor); } // Get a copy of Scale pub fn scale(&self) -> Scale { self.scale } pub fn add_graphic(&mut self, graphic: Graphic) -> ImageId { self.image_map .insert((self.cache.add_graphic(graphic), Rotation::None)) } pub fn add_graphic_with_rotations(&mut self, graphic: Graphic) -> img_ids::Rotations { let graphic_id = self.cache.add_graphic(graphic); img_ids::Rotations { none: self.image_map.insert((graphic_id, Rotation::None)), cw90: self.image_map.insert((graphic_id, Rotation::Cw90)), cw180: self.image_map.insert((graphic_id, Rotation::Cw180)), cw270: self.image_map.insert((graphic_id, Rotation::Cw270)), // Hacky way to make sure a source rectangle always faces north regardless of player // orientation. // This is an easy way to get around Conrod's lack of rotation data for images (for this // specific use case). source_north: self.image_map.insert((graphic_id, Rotation::SourceNorth)), // Hacky way to make sure a target rectangle always faces north regardless of player // orientation. // This is an easy way to get around Conrod's lack of rotation data for images (for this // specific use case). target_north: self.image_map.insert((graphic_id, Rotation::TargetNorth)), } } pub fn replace_graphic(&mut self, id: ImageId, graphic: Graphic) { let graphic_id = if let Some((graphic_id, _)) = self.image_map.get(&id) { *graphic_id } else { error!("Failed to replace graphic the provided id is not in use"); return; }; self.cache.replace_graphic(graphic_id, graphic); self.image_map.replace(id, (graphic_id, Rotation::None)); } pub fn new_font(&mut self, font: Arc) -> font::Id { let font = text::Font::from_bytes(font.0.clone()).unwrap(); self.ui.fonts.insert(font) } pub fn id_generator(&mut self) -> Generator { self.ui.widget_id_generator() } pub fn set_widgets(&mut self) -> (UiCell, &mut TooltipManager) { (self.ui.set_widgets(), &mut self.tooltip_manager) } // Accepts Option so widget can be unfocused. pub fn focus_widget(&mut self, id: Option) { self.ui.keyboard_capture(match id { Some(id) => id, None => self.ui.window, }); } // Get id of current widget capturing keyboard. pub fn widget_capturing_keyboard(&self) -> Option { self.ui.global_input().current.widget_capturing_keyboard } // Get whether a widget besides the window is capturing the mouse. pub fn no_widget_capturing_mouse(&self) -> bool { self.ui .global_input() .current .widget_capturing_mouse .filter(|id| id != &self.ui.window) .is_none() } // Get the widget graph. pub fn widget_graph(&self) -> &Graph { self.ui.widget_graph() } pub fn handle_event(&mut self, event: Event) { match event.0 { Input::Resize(w, h) => { if w > 1.0 && h > 1.0 { self.window_resized = Some(Vec2::new(w, h)) } }, Input::Touch(touch) => self.ui.handle_event(Input::Touch(Touch { xy: self.scale.scale_point(touch.xy.into()).into_array(), ..touch })), Input::Motion(motion) => self.ui.handle_event(Input::Motion(match motion { Motion::MouseCursor { x, y } => { let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple(); Motion::MouseCursor { x, y } }, Motion::MouseRelative { x, y } => { let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple(); Motion::MouseRelative { x, y } }, Motion::Scroll { x, y } => { let (x, y) = self.scale.scale_point(Vec2::new(x, y)).into_tuple(); Motion::Scroll { x, y } }, _ => motion, })), _ => self.ui.handle_event(event.0), } } pub fn widget_input(&self, id: widget::Id) -> Widget { self.ui.widget_input(id) } #[allow(clippy::float_cmp)] // TODO: Pending review in #587 pub fn maintain(&mut self, renderer: &mut Renderer, view_projection_mat: Option>) { span!(_guard, "maintain", "Ui::maintain"); // Maintain tooltip manager self.tooltip_manager .maintain(self.ui.global_input(), self.scale.scale_factor_logical()); // Handle scale factor changing let need_resize = if let Some(scale_factor) = self.scale_factor_changed.take() { self.scale.scale_factor_changed(scale_factor) } else { false }; // Handle window resizing. let need_resize = if let Some(new_dims) = self.window_resized.take() { let (old_w, old_h) = self.scale.scaled_resolution().into_tuple(); self.scale.window_resized(new_dims); let (w, h) = self.scale.scaled_resolution().into_tuple(); self.ui.handle_event(Input::Resize(w, h)); // Avoid panic in graphic cache when minimizing. // Avoid resetting cache if window size didn't change // Somewhat inefficient for elements that won't change size after a window // resize let res = renderer.get_resolution(); res.x > 0 && res.y > 0 && !(old_w == w && old_h == h) } else { false } || need_resize; if need_resize { self.need_cache_resize = true; } if self.need_cache_resize { // Resize graphic cache // FIXME: Handle errors here. self.cache.resize(renderer).unwrap(); self.need_cache_resize = false; } let mut retry = false; self.maintain_internal(renderer, view_projection_mat, &mut retry); if retry { // Update the glyph cache and try again. self.maintain_internal(renderer, view_projection_mat, &mut retry); } } fn maintain_internal( &mut self, renderer: &mut Renderer, view_projection_mat: Option>, retry: &mut bool, ) { span!(_guard, "internal", "Ui::maintain_internal"); let (graphic_cache, text_cache, glyph_cache, cache_tex) = self.cache.cache_mut_and_tex(); let mut primitives = if *retry { // If this is a retry, always redraw. self.ui.draw() } else { // Otherwise, redraw only if widgets were actually updated. match self.ui.draw_if_changed() { Some(primitives) => primitives, None => return, } }; let (half_res, x_align, y_align) = { let res = renderer.get_resolution(); ( res.map(|e| e as f32 / 2.0), (res.x & 1) as f32 * 0.5, (res.y & 1) as f32 * 0.5, ) }; let ui = &self.ui; let p_scale_factor = self.scale.scale_factor_physical(); // Functions for converting for conrod scalar coords to GL vertex coords (-1.0 // to 1.0). let (ui_win_w, ui_win_h) = (ui.win_w, ui.win_h); let vx = |x: f64| (x / ui_win_w * 2.0) as f32; let vy = |y: f64| (y / ui_win_h * 2.0) as f32; let gl_aabr = |rect: Rect| { let (l, r, b, t) = rect.l_r_b_t(); let min = Vec2::new( ((vx(l) * half_res.x + x_align).round() - x_align) / half_res.x, ((vy(b) * half_res.y + y_align).round() - y_align) / half_res.y, ); let max = Vec2::new( ((vx(r) * half_res.x + x_align).round() - x_align) / half_res.x, ((vy(t) * half_res.y + y_align).round() - y_align) / half_res.y, ); Aabr { min, max } }; // let window_dim = ui.window_dim(); let theme = &ui.theme; let widget_graph = ui.widget_graph(); let fonts = &ui.fonts; let dpi_factor = p_scale_factor as f32; // We can use information about whether a widget was actually updated to more // easily track cache invalidations. let updated_widgets = ui.updated_widgets(); let mut glyph_missing = false; updated_widgets.iter() // Filter out widgets that are either: // - not text primitives, or // - are text primitives, but were both already in the cache, and not updated this // frame. // // The reason the second condition is so complicated is that we want to handle cases // where we cleared the whole cache, which can result in glyphs from text updated in a // previous frame not being present in the text cache. .filter_map(|(&widget_id, updated)| { widget_graph.widget(widget_id) .and_then(|widget| Some((widget.rect, widget.unique_widget_state::()?))) .and_then(|(rect, text)| { // NOTE: This fallback is weird and probably shouldn't exist. let font_id = text.style.font_id(theme)/*.or_else(|| fonts.ids().next())*/?; let font = fonts.get(font_id)?; Some((widget_id, updated, rect, text, font_id, font)) }) }) // Recache the entry. .for_each(|(widget_id, updated, rect, graph::UniqueWidgetState { state, style }, font_id, font)| { let entry = match text_cache.entry(widget_id) { Entry::Occupied(_) if !updated => return, entry => entry, }; // Retrieve styling. let color = style.color(theme); let font_size = style.font_size(theme); let line_spacing = style.line_spacing(theme); let justify = style.justify(theme); let y_align = conrod_core::position::Align::End; let text = &state.string; let line_infos = &state.line_infos; // Convert conrod coordinates to pixel coordinates. let trans_x = |x: Scalar| (x + ui_win_w / 2.0) * dpi_factor as Scalar; let trans_y = |y: Scalar| ((-y) + ui_win_h / 2.0) * dpi_factor as Scalar; // Produce the text layout iterators. let lines = line_infos.iter().map(|info| &text[info.byte_range()]); let line_rects = text::line::rects(line_infos.iter(), font_size, rect, justify, y_align, line_spacing); // Grab the positioned glyphs from the text primitives. let scale = text::f32_pt_to_scale(font_size as f32 * dpi_factor); let positioned_glyphs = lines.zip(line_rects).flat_map(|(line, line_rect)| { let (x, y) = (trans_x(line_rect.left()) as f32, trans_y(line_rect.bottom()) as f32); let point = text::rt::Point { x, y }; font.layout(line, scale, point) }); // Reuse the mesh allocation if possible at this entry if possible; we // then clear it to avoid using stale entries. let mesh = entry.or_insert_with(Mesh::new); mesh.clear(); let color = srgba_to_linear(color.to_fsa().into()); positioned_glyphs.for_each(|g| { match glyph_cache.rect_for(font_id.index(), &g) { Ok(Some((uv_rect, screen_rect))) => { let uv = Aabr { min: Vec2::new(uv_rect.min.x, uv_rect.max.y), max: Vec2::new(uv_rect.max.x, uv_rect.min.y), }; let rect = Aabr { min: Vec2::new( vx(screen_rect.min.x as f64 / p_scale_factor - ui.win_w / 2.0), vy(ui.win_h / 2.0 - screen_rect.max.y as f64 / p_scale_factor), ), max: Vec2::new( vx(screen_rect.max.x as f64 / p_scale_factor - ui.win_w / 2.0), vy(ui.win_h / 2.0 - screen_rect.min.y as f64 / p_scale_factor), ), }; mesh.push_quad(create_ui_quad(rect, uv, color, UiMode::Text)); }, // Glyph not found, no-op. Ok(None) => {}, // Glyph was found, but was not yet cached; we'll need to add it to the // cache and retry. Err(_) => { // Queue the unknown glyph to be cached. glyph_missing = true; } } // NOTE: Important to do this for *all* glyphs to try to make sure that // any that are uncached are part of the graph. Because we always // clear the entire cache whenever a new glyph is encountered, by // adding and checking all glyphs as they come in we guarantee that (1) // any glyphs in the text cache are in the queue, and (2) any glyphs // not in the text cache are either in the glyph cache, or (during our // processing here) we set the retry flag to force a glyph cache // update. Setting the flag causes all glyphs in the current queue to // become part of the glyph cache during the second call to // `maintain_internal`, so as long as the cache refresh succeeded, // during the second call all glyphs will hit this branch as desired. glyph_cache.queue_glyph(font_id.index(), g); }); }); if glyph_missing { if *retry { // If a glyph was missing and this was our second try, we know something was // messed up during the glyph_cache redraw. It is possible that // the queue contained unneeded glyphs, so we don't necessarily // have to give up; a more precise enumeration of the // glyphs required to render this frame might work out. However, this is a // pretty remote edge case, so we opt to not care about this // frame (we skip rendering it, basically), and just clear the // text cache and glyph queue; next frame will then // start out with an empty slate, and therefore will enqueue precisely the // glyphs needed for that frame. If *that* frame fails, we're // in bigger trouble. text_cache.clear(); glyph_cache.clear(); glyph_cache.clear_queue(); self.ui.needs_redraw(); warn!("Could not recache queued glyphs, skipping frame."); } else { // NOTE: If this is the first round after encountering a new glyph, we just // refresh the whole glyph cache. Technically this is not necessary since // positioned_glyphs should still be accurate, but it's just the easiest way // to ensure that all glyph positions get updated. It also helps keep the glyph // cache reasonable by making sure any glyphs that subsequently get rendered are // actually in the cache, including glyphs that were mapped to ids but didn't // happen to be rendered on the frame where the cache was // refreshed. text_cache.clear(); tracing::debug!("Updating glyphs and clearing text cache."); if let Err(err) = glyph_cache.cache_queued(|rect, data| { let offset = [rect.min.x as u16, rect.min.y as u16]; let size = [rect.width() as u16, rect.height() as u16]; let new_data = data .iter() .map(|x| [255, 255, 255, *x]) .collect::>(); if let Err(err) = renderer.update_texture(cache_tex, offset, size, &new_data) { warn!("Failed to update texture: {:?}", err); } }) { // FIXME: If we actually hit this error, it's still possible we could salvage // things in various ways (for instance, the current queue might have extra // stuff in it, so we could try calling maintain_internal a // third time with a fully clean queue; or we could try to // increase the glyph texture size, etc. But hopefully // we will not actually encounter this error. warn!("Failed to cache queued glyphs: {:?}", err); // Clear queued glyphs, so that (hopefully) next time we won't have the // offending glyph or glyph set. We then exit the loop and don't try to // rerender the frame. glyph_cache.clear_queue(); self.ui.needs_redraw(); } else { // Successfully cached, so repeat the loop. *retry = true; } } return; } self.draw_commands.clear(); let mesh = &mut self.mesh; mesh.clear(); enum State { Image(TexId), Plain, }; let mut current_state = State::Plain; let mut start = 0; let window_scissor = default_scissor(renderer); let mut current_scissor = window_scissor; let mut ingame_local_index = 0; enum Placement { Interface, // Number of primitives left to render ingame and visibility InWorld(usize, bool), }; let mut placement = Placement::Interface; // Switches to the `Plain` state and completes the previous `Command` if not // already in the `Plain` state. macro_rules! switch_to_plain_state { () => { if let State::Image(id) = current_state { self.draw_commands .push(DrawCommand::image(start..mesh.vertices().len(), id)); start = mesh.vertices().len(); current_state = State::Plain; } }; } while let Some(prim) = primitives.next() { let Primitive { kind, scizzor, rect, id: widget_id, } = prim; // Check for a change in the scissor. let new_scissor = { let (l, b, w, h) = scizzor.l_b_w_h(); let scale_factor = self.scale.scale_factor_physical(); // Calculate minimum x and y coordinates while // flipping y axis (from +up to +down) and // moving origin to top-left corner (from middle). let min_x = ui.win_w / 2.0 + l; let min_y = ui.win_h / 2.0 - b - h; let intersection = Aabr { min: Vec2 { x: (min_x * scale_factor) as u16, y: (min_y * scale_factor) as u16, }, max: Vec2 { x: ((min_x + w) * scale_factor) as u16, y: ((min_y + h) * scale_factor) as u16, }, } .intersection(window_scissor); if intersection.is_valid() { intersection } else { Aabr::new_empty(Vec2::zero()) } }; if new_scissor != current_scissor { // Finish the current command. self.draw_commands.push(match current_state { State::Plain => DrawCommand::plain(start..mesh.vertices().len()), State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id), }); start = mesh.vertices().len(); // Update the scissor and produce a command. current_scissor = new_scissor; self.draw_commands.push(DrawCommand::Scissor(new_scissor)); } match placement { // No primitives left to place in the world at the current position, go back to // drawing the interface Placement::InWorld(0, _) => { placement = Placement::Interface; // Finish current state self.draw_commands.push(match current_state { State::Plain => DrawCommand::plain(start..mesh.vertices().len()), State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id), }); start = mesh.vertices().len(); // Push new position command self.draw_commands.push(DrawCommand::WorldPos(None)); }, // Primitives still left to draw ingame Placement::InWorld(num_prims, visible) => match kind { // Other types aren't drawn & shouldn't decrement the number of primitives left // to draw ingame PrimitiveKind::Other(_) => {}, // Decrement the number of primitives left _ => { placement = Placement::InWorld(num_prims - 1, visible); // Behind the camera if !visible { continue; } }, }, Placement::Interface => {}, } match kind { PrimitiveKind::Image { image_id, color, source_rect, } => { let (graphic_id, rotation) = self .image_map .get(&image_id) .expect("Image does not exist in image map"); let gl_aabr = gl_aabr(rect); let (source_aabr, gl_size) = { // Transform the source rectangle into uv coordinate. // TODO: Make sure this is right. Especially the conversions. let ((uv_l, uv_r, uv_b, uv_t), gl_size) = match graphic_cache.get_graphic(*graphic_id) { Some(Graphic::Blank) | None => continue, Some(Graphic::Image(image, ..)) => { source_rect.and_then(|src_rect| { let (image_w, image_h) = image.dimensions(); let (source_w, source_h) = src_rect.w_h(); let gl_size = gl_aabr.size(); if image_w == 0 || image_h == 0 || source_w < 1.0 || source_h < 1.0 || gl_size.reduce_partial_min() < f32::EPSILON { None } else { // Multiply drawn image size by ratio of original image // size to // source rectangle size (since as the proportion of the // image gets // smaller, the drawn size should get bigger), up to the // actual // size of the original image. let ratio_x = (image_w as f64 / source_w).min( (image_w as f64 / (gl_size.w * half_res.x) as f64) .max(1.0), ); let ratio_y = (image_h as f64 / source_h).min( (image_h as f64 / (gl_size.h * half_res.y) as f64) .max(1.0), ); let (l, r, b, t) = src_rect.l_r_b_t(); Some(( ( l / image_w as f64, /* * ratio_x*/ r / image_w as f64, /* * ratio_x*/ b / image_h as f64, /* * ratio_y*/ t / image_h as f64, /* * ratio_y*/ ), Extent2::new( (gl_size.w as f64 * ratio_x) as f32, (gl_size.h as f64 * ratio_y) as f32, ), )) /* ((l / image_w as f64), (r / image_w as f64), (b / image_h as f64), (t / image_h as f64)) */ } }) }, // No easy way to interpret source_rect for voxels... Some(Graphic::Voxel(..)) => None, } .unwrap_or_else(|| ((0.0, 1.0, 0.0, 1.0), gl_aabr.size())); ( Aabr { min: Vec2::new(uv_l, uv_b), max: Vec2::new(uv_r, uv_t), }, gl_size, ) }; let resolution = Vec2::new( (gl_size.w * half_res.x).round() as u16, (gl_size.h * half_res.y).round() as u16, ); // Don't do anything if resolution is zero if resolution.map(|e| e == 0).reduce_or() { continue; // TODO: consider logging unneeded elements } let color = srgba_to_linear(color.unwrap_or(conrod_core::color::WHITE).to_fsa().into()); // Cache graphic at particular resolution. let (uv_aabr, tex_id) = match graphic_cache.cache_res( renderer, *graphic_id, resolution, source_aabr, *rotation, ) { // TODO: get dims from graphic_cache (or have it return floats directly) Some((aabr, tex_id)) => { let cache_dims = graphic_cache .get_tex(tex_id) .get_dimensions() .map(|e| e as f32); let min = Vec2::new(aabr.min.x as f32, aabr.max.y as f32) / cache_dims; let max = Vec2::new(aabr.max.x as f32, aabr.min.y as f32) / cache_dims; (Aabr { min, max }, tex_id) }, None => continue, }; match current_state { // Switch to the image state if we are not in it already. State::Plain => { self.draw_commands .push(DrawCommand::plain(start..mesh.vertices().len())); start = mesh.vertices().len(); current_state = State::Image(tex_id); }, // If the image is cached in a different texture switch to the new one State::Image(id) if id != tex_id => { self.draw_commands .push(DrawCommand::image(start..mesh.vertices().len(), id)); start = mesh.vertices().len(); current_state = State::Image(tex_id); }, State::Image(_) => {}, } mesh.push_quad(create_ui_quad(gl_aabr, uv_aabr, color, match *rotation { Rotation::None | Rotation::Cw90 | Rotation::Cw180 | Rotation::Cw270 => { UiMode::Image }, Rotation::SourceNorth => UiMode::ImageSourceNorth, Rotation::TargetNorth => UiMode::ImageTargetNorth, })); }, PrimitiveKind::Text { .. } => { switch_to_plain_state!(); // Mesh should already be cached. mesh.push_mesh( text_cache .get(&widget_id) .as_deref() .unwrap_or(&Mesh::new()), ); }, PrimitiveKind::Rectangle { color } => { let color = srgba_to_linear(color.to_fsa().into()); // Don't draw a transparent rectangle. if color[3] == 0.0 { continue; } switch_to_plain_state!(); mesh.push_quad(create_ui_quad( gl_aabr(rect), Aabr { min: Vec2::zero(), max: Vec2::zero(), }, color, UiMode::Geometry, )); }, PrimitiveKind::TrianglesSingleColor { color, triangles } => { // Don't draw transparent triangle or switch state if there are actually no // triangles. let color = srgba_to_linear(Rgba::from(Into::<[f32; 4]>::into(color))); if triangles.is_empty() || color[3] == 0.0 { continue; } switch_to_plain_state!(); for tri in triangles { let p1 = Vec2::new(vx(tri[0][0]), vy(tri[0][1])); let p2 = Vec2::new(vx(tri[1][0]), vy(tri[1][1])); let p3 = Vec2::new(vx(tri[2][0]), vy(tri[2][1])); // If triangle is clockwise, reverse it. let (v1, v2): (Vec3, Vec3) = ((p2 - p1).into(), (p3 - p1).into()); let triangle = if v1.cross(v2).z > 0.0 { [p1.into_array(), p2.into_array(), p3.into_array()] } else { [p2.into_array(), p1.into_array(), p3.into_array()] }; mesh.push_tri(create_ui_tri( triangle, [[0.0; 2]; 3], color, UiMode::Geometry, )); } }, PrimitiveKind::Other(container) => { if container.type_id == std::any::TypeId::of::() { // Calculate the scale factor to pixels at this 3d point using the camera. if let Some(view_projection_mat) = view_projection_mat { // Retrieve world position let parameters = container .state_and_style::() .unwrap() .state .parameters; let pos_on_screen = (view_projection_mat * Vec4::from_point(parameters.pos)) .homogenized(); let visible = if pos_on_screen.z > -1.0 && pos_on_screen.z < 1.0 { let x = pos_on_screen.x; let y = pos_on_screen.y; let (w, h) = parameters.dims.into_tuple(); let (half_w, half_h) = (w / ui_win_w as f32, h / ui_win_h as f32); (x - half_w < 1.0 && x + half_w > -1.0) && (y - half_h < 1.0 && y + half_h > -1.0) } else { false }; // Don't process ingame elements outside the frustum placement = if visible { // Finish current state self.draw_commands.push(match current_state { State::Plain => { DrawCommand::plain(start..mesh.vertices().len()) }, State::Image(id) => { DrawCommand::image(start..mesh.vertices().len(), id) }, }); start = mesh.vertices().len(); // Push new position command let world_pos = Vec4::from_point(parameters.pos); if self.ingame_locals.len() > ingame_local_index { renderer .update_consts( &mut self.ingame_locals[ingame_local_index], &[world_pos.into()], ) .unwrap(); } else { self.ingame_locals .push(renderer.create_consts(&[world_pos.into()]).unwrap()); } self.draw_commands .push(DrawCommand::WorldPos(Some(ingame_local_index))); ingame_local_index += 1; Placement::InWorld(parameters.num, true) } else { Placement::InWorld(parameters.num, false) }; } } }, _ => {}, /* TODO: Add this. *PrimitiveKind::TrianglesMultiColor {..} => {println!("primitive kind * multicolor with id {:?}", id);} */ } } // Enter the final command. self.draw_commands.push(match current_state { State::Plain => DrawCommand::plain(start..mesh.vertices().len()), State::Image(id) => DrawCommand::image(start..mesh.vertices().len(), id), }); /* // Draw glyph cache (use for debugging). self.draw_commands .push(DrawCommand::Scissor(default_scissor(renderer))); start = mesh.vertices().len(); mesh.push_quad(create_ui_quad( Aabr { min: (-1.0, -1.0).into(), max: (1.0, 1.0).into(), }, Aabr { min: (0.0, 1.0).into(), max: (1.0, 0.0).into(), }, Rgba::new(1.0, 1.0, 1.0, 0.8), UiMode::Text, )); self.draw_commands .push(DrawCommand::plain(start..mesh.vertices().len())); */ // Create a larger dynamic model if the mesh is larger than the current model // size. if self.model.vbuf.len() < self.mesh.vertices().len() { self.model = renderer .create_dynamic_model(self.mesh.vertices().len() * 4 / 3) .unwrap(); } // Update model with new mesh. renderer.update_model(&self.model, &self.mesh, 0).unwrap(); } pub fn render(&self, renderer: &mut Renderer, maybe_globals: Option<&Consts>) { span!(_guard, "render", "Ui::render"); let mut scissor = default_scissor(renderer); let globals = maybe_globals.unwrap_or(&self.default_globals); let mut locals = &self.interface_locals; for draw_command in self.draw_commands.iter() { match draw_command { DrawCommand::Scissor(new_scissor) => { scissor = *new_scissor; }, DrawCommand::WorldPos(index) => { locals = index.map_or(&self.interface_locals, |i| &self.ingame_locals[i]); }, DrawCommand::Draw { kind, verts } => { let tex = match kind { DrawKind::Image(tex_id) => self.cache.graphic_cache().get_tex(*tex_id), DrawKind::Plain => self.cache.glyph_cache_tex(), }; let model = self.model.submodel(verts.clone()); renderer.render_ui_element(model, tex, scissor, globals, locals); }, } } } } fn default_scissor(renderer: &Renderer) -> Aabr { let (screen_w, screen_h) = renderer.get_resolution().into_tuple(); Aabr { min: Vec2 { x: 0, y: 0 }, max: Vec2 { x: screen_w, y: screen_h, }, } }