From a7458c099c8e0938aa9b61e57d97ea042cf45e81 Mon Sep 17 00:00:00 2001 From: Imbris Date: Tue, 21 Apr 2020 21:29:59 -0400 Subject: [PATCH] Implement text renderering with glyph_brush (lifetime error) --- Cargo.lock | 32 +++ voxygen/Cargo.toml | 1 + voxygen/src/menu/main/ui.rs | 13 +- voxygen/src/render/mesh.rs | 15 ++ voxygen/src/ui/ice/cache.rs | 153 +++++++++++++ voxygen/src/ui/ice/mod.rs | 40 ++-- voxygen/src/ui/ice/renderer.rs | 210 +++++++++++++++--- .../ui/ice/renderer/background_container.rs | 11 +- voxygen/src/ui/ice/renderer/column.rs | 20 +- .../src/ui/ice/renderer/compound_graphic.rs | 10 +- voxygen/src/ui/ice/renderer/container.rs | 9 +- voxygen/src/ui/ice/renderer/image.rs | 13 +- voxygen/src/ui/ice/renderer/row.rs | 20 +- voxygen/src/ui/ice/renderer/space.rs | 8 +- voxygen/src/ui/ice/renderer/stack.rs | 23 +- voxygen/src/ui/ice/renderer/text.rs | 97 ++++++++ voxygen/src/ui/ice/winit_conversion.rs | 45 ++-- 17 files changed, 598 insertions(+), 122 deletions(-) create mode 100644 voxygen/src/ui/ice/cache.rs create mode 100644 voxygen/src/ui/ice/renderer/text.rs diff --git a/Cargo.lock b/Cargo.lock index e7f7f63ce1..d274d777a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1856,6 +1856,31 @@ dependencies = [ "gl_generator", ] +[[package]] +name = "glyph_brush" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fca6f9d679bff1322c76c9a1ad4b8553b30a94f3f75bea6936e19032c2f2ec3" +dependencies = [ + "glyph_brush_layout", + "log", + "ordered-float", + "rustc-hash", + "rusttype 0.8.3", + "twox-hash", +] + +[[package]] +name = "glyph_brush_layout" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b70adc570f1dc71b6b32e241cbcc2b42175f5aea71951fbf41e68b04aec24c7" +dependencies = [ + "approx", + "rusttype 0.8.3", + "xi-unicode", +] + [[package]] name = "guillotiere" version = "0.5.2" @@ -5083,6 +5108,7 @@ dependencies = [ "git2", "glsl-include", "glutin", + "glyph_brush", "guillotiere", "hashbrown 0.7.2", "iced_native", @@ -5667,6 +5693,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" +[[package]] +name = "xi-unicode" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e71b85d8b1b8bfaf4b5c834187554d201a8cd621c2bbfa33efd41a3ecabd48b2" + [[package]] name = "xml-rs" version = "0.8.3" diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 7dfcd90ea8..c4b3208c1c 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -37,6 +37,7 @@ conrod_winit = {git = "https://gitlab.com/veloren/conrod.git", branch="copypasta euc = {git = "https://github.com/zesterer/euc.git"} iced = {package = "iced_native", git = "https://github.com/hecrj/iced"} window_clipboard = "0.1.1" +glyph_brush = "0.6.3" # ECS specs = {git = "https://github.com/amethyst/specs.git", rev = "7a2e348ab2223818bad487695c66c43db88050a5"} diff --git a/voxygen/src/menu/main/ui.rs b/voxygen/src/menu/main/ui.rs index 4d8fd791eb..a01ff43b15 100644 --- a/voxygen/src/menu/main/ui.rs +++ b/voxygen/src/menu/main/ui.rs @@ -378,7 +378,18 @@ impl<'a> MainMenuUi { let fonts = ConrodVoxygenFonts::load(&voxygen_i18n.fonts, &mut ui) .expect("Impossible to load fonts!"); - let mut ice_ui = IcedUi::new(window).unwrap(); + // TODO: newtype Font + let ice_font = { + use std::io::Read; + let mut buf = Vec::new(); + common::assets::load_file("voxygen.font.OpenSans-Regular", &["ttf"]) + .unwrap() + .read_to_end(&mut buf) + .unwrap(); + glyph_brush::rusttype::Font::from_bytes(buf).unwrap() + }; + + let mut ice_ui = IcedUi::new(window, ice_font).unwrap(); let ice_state = IcedState { imgs: IcedImgs::load(&mut ice_ui).expect("Failed to load images"), }; diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index cd38e88ae4..a8c6b6445e 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -57,6 +57,21 @@ impl Mesh

{ self.verts.push(quad.a); } + /// Overwrite a quad + pub fn replace_quad(&mut self, index: usize, quad: Quad

) { + debug_assert!(index % 3 == 0); + assert!(index + 5 < self.verts.len()); + // Tri 1 + self.verts[index] = quad.a.clone(); + self.verts[index + 1] = quad.b; + self.verts[index + 2] = quad.c.clone(); + + // Tri 2 + self.verts[index + 3] = quad.c; + self.verts[index + 4] = quad.d; + self.verts[index + 5] = quad.a; + } + /// Push the vertices of another mesh onto the end of this mesh. pub fn push_mesh(&mut self, other: &Mesh

) { self.verts.extend_from_slice(other.vertices()); } diff --git a/voxygen/src/ui/ice/cache.rs b/voxygen/src/ui/ice/cache.rs new file mode 100644 index 0000000000..c3d9e70f06 --- /dev/null +++ b/voxygen/src/ui/ice/cache.rs @@ -0,0 +1,153 @@ +use super::{ + graphic::{Graphic, GraphicCache, Id as GraphicId}, + renderer::{IcedRenderer, Primitive}, +}; +use crate::{ + render::{Renderer, Texture}, + Error, +}; +use glyph_brush::{ + GlyphBrushBuilder, GlyphCalculator, GlyphCalculatorBuilder, GlyphCalculatorGuard, +}; +use std::cell::RefCell; +use vek::*; + +// Multiplied by current window size +const GLYPH_CACHE_SIZE: u16 = 1; +// Glyph cache tolerances +const SCALE_TOLERANCE: f32 = 0.1; +const POSITION_TOLERANCE: f32 = 0.1; + +type GlyphBrush = glyph_brush::GlyphBrush<'static, (Aabr, Aabr)>; + +pub type Font = glyph_brush::rusttype::Font<'static>; + +pub struct Cache { + glyph_cache: GlyphBrush, + glyph_cache_tex: Texture, + graphic_cache: GraphicCache, +} + +// TODO: Should functions be returning UiError instead of Error? +impl Cache { + pub fn new(renderer: &mut Renderer, default_font: Font) -> Result { + let (w, h) = renderer.get_resolution().into_tuple(); + + let max_texture_size = renderer.max_texture_size(); + + let glyph_cache_dims = + Vec2::new(w, h).map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size as u16).max(512)); + + Ok(Self { + glyph_cache: GlyphBrushBuilder::using_font(default_font) + .initial_cache_size((glyph_cache_dims.x as u32, glyph_cache_dims.y as u32)) + .gpu_cache_scale_tolerance(SCALE_TOLERANCE) + .gpu_cache_position_tolerance(POSITION_TOLERANCE) + .build(), + glyph_cache_tex: renderer.create_dynamic_texture(glyph_cache_dims.map(|e| e as u16))?, + graphic_cache: GraphicCache::new(renderer), + }) + } + + pub fn glyph_cache_tex(&self) -> &Texture { &self.glyph_cache_tex } + + pub fn glyph_cache_mut_and_tex(&mut self) -> (&mut GlyphBrush, &Texture) { + (&mut self.glyph_cache, &self.glyph_cache_tex) + } + + pub fn glyph_cache_mut(&mut self) -> &mut GlyphBrush { &mut self.glyph_cache } + + // TODO: add font fn + + pub fn graphic_cache(&self) -> &GraphicCache { &self.graphic_cache } + + pub fn graphic_cache_mut(&mut self) -> &mut GraphicCache { &mut self.graphic_cache } + + pub fn add_graphic(&mut self, graphic: Graphic) -> GraphicId { + self.graphic_cache.add_graphic(graphic) + } + + pub fn replace_graphic(&mut self, id: GraphicId, graphic: Graphic) { + self.graphic_cache.replace_graphic(id, graphic) + } + + // Resizes and clears the GraphicCache + pub fn resize_graphic_cache(&mut self, renderer: &mut Renderer) { + self.graphic_cache.clear_cache(renderer); + } + + // Resizes and clears the GlyphCache + pub fn resize_glyph_cache(&mut self, renderer: &mut Renderer) -> Result<(), Error> { + let max_texture_size = renderer.max_texture_size(); + let cache_dims = renderer + .get_resolution() + .map(|e| (e * GLYPH_CACHE_SIZE).min(max_texture_size as u16).max(512)); + self.glyph_cache = self + .glyph_cache + .to_builder() + .initial_cache_size((cache_dims.x as u32, cache_dims.y as u32)) + .build(); + self.glyph_cache_tex = renderer.create_dynamic_texture(cache_dims.map(|e| e as u16))?; + Ok(()) + } +} + +pub struct GlyphCalcCache { + // Hold one of these for adding new fonts + builder: GlyphCalculatorBuilder<'static>, + calculator: GlyphCalculator<'static>, +} + +impl GlyphCalcCache { + pub fn new(default_font: Font) -> Self { + // Multiple copies of the font in memory :/ (using Arc<[u8]> might help) + let builder = GlyphCalculatorBuilder::using_font(default_font); + let calculator = builder.clone().build(); + + Self { + builder, + calculator, + } + } + + pub fn frame_guard<'a>(&'a self) -> GlyphCalculatorGuard<'a, 'static> { + self.calculator.cache_scope() + } + + // add new font fn +} + +pub struct FrameRenderer<'a> { + pub renderer: &'a mut IcedRenderer, + pub glyph_calc: RefCell>, +} + +impl<'a> FrameRenderer<'a> { + pub fn new(renderer: &'a mut IcedRenderer, glyph_calc_cache: &'a mut GlyphCalcCache) -> Self { + Self { + renderer, + glyph_calc: RefCell::new(glyph_calc_cache.frame_guard()), + } + } +} + +impl iced::Renderer for FrameRenderer<'_> { + // Default styling + type Defaults = (); + // TODO: use graph of primitives to enable diffing??? + type Output = (Primitive, iced::mouse::Interaction); + + fn layout<'a, M>( + &mut self, + element: &iced::Element<'a, M, Self>, + limits: &iced::layout::Limits, + ) -> iced::layout::Node { + let node = element.layout(self, limits); + + // Trim text measurements cache? + + node + } +} + +// TODO: impl Debugger diff --git a/voxygen/src/ui/ice/mod.rs b/voxygen/src/ui/ice/mod.rs index ed3ac1418e..4fe0d7cf83 100644 --- a/voxygen/src/ui/ice/mod.rs +++ b/voxygen/src/ui/ice/mod.rs @@ -1,9 +1,11 @@ // tooltip_manager: TooltipManager, +mod cache; mod clipboard; mod renderer; mod widget; mod winit_conversion; +pub use cache::Font; pub use graphic::{Id, Rotation}; pub use iced::Event; pub use renderer::IcedRenderer; @@ -19,14 +21,16 @@ use super::{ scale::{Scale, ScaleMode}, }; use crate::{render::Renderer, window::Window, Error}; +use cache::{FrameRenderer, GlyphCalcCache}; use clipboard::Clipboard; -use iced::{Cache, MouseCursor, Size, UserInterface}; +use iced::{mouse, Cache, Size, UserInterface}; use vek::*; -pub type Element<'a, M> = iced::Element<'a, M, IcedRenderer>; +pub type Element<'a, 'b, M> = iced::Element<'a, M, FrameRenderer<'b>>; pub struct IcedUi { renderer: IcedRenderer, + glyph_calc_cache: GlyphCalcCache, cache: Option, events: Vec, clipboard: Clipboard, @@ -35,14 +39,16 @@ pub struct IcedUi { window_resized: Option>, } impl IcedUi { - pub fn new(window: &mut Window) -> Result { + pub fn new(window: &mut Window, default_font: Font) -> Result { let scale = Scale::new(window, ScaleMode::Absolute(1.0)); let renderer = window.renderer_mut(); let scaled_dims = scale.scaled_window_size().map(|e| e as f32); + // TODO: examine how much mem fonts take up and reduce clones if significant Ok(Self { - renderer: IcedRenderer::new(renderer, scaled_dims)?, + renderer: IcedRenderer::new(renderer, scaled_dims, default_font.clone())?, + glyph_calc_cache: GlyphCalcCache::new(default_font), cache: Some(Cache::new()), events: Vec::new(), // TODO: handle None @@ -58,7 +64,7 @@ impl IcedUi { } pub fn handle_event(&mut self, event: Event) { - use iced::{input::mouse, window}; + use iced::window; match event { // Intercept resizing events Event::Window(window::Event::Resized { width, height }) => { @@ -93,11 +99,12 @@ impl IcedUi { } // TODO: produce root internally??? - pub fn maintain<'a, M, E: Into>>( + // TODO: see if this lifetime soup can be simplified + pub fn maintain<'a, 'b, M, E: Into>>>( &mut self, root: E, renderer: &mut Renderer, - ) -> (Vec, MouseCursor) { + ) -> (Vec, mouse::Interaction) { // Handle window resizing if let Some(new_dims) = self.window_resized.take() { let old_scaled_dims = self.scale.scaled_window_size(); @@ -126,23 +133,28 @@ impl IcedUi { // TODO: convert to f32 at source let window_size = self.scale.scaled_window_size().map(|e| e as f32); + let mut frame_renderer = FrameRenderer::new(&mut self.renderer, &mut self.glyph_calc_cache); + let mut user_interface = UserInterface::build( root, Size::new(window_size.x, window_size.y), self.cache.take().unwrap(), - &mut self.renderer, + &mut frame_renderer, ); - let messages = - user_interface.update(self.events.drain(..), Some(&self.clipboard), &self.renderer); + let messages = user_interface.update( + self.events.drain(..), + Some(&self.clipboard), + &frame_renderer, + ); - let (primitive, mouse_cursor) = user_interface.draw(&mut self.renderer); - - self.renderer.draw(primitive, renderer); + let (primitive, mouse_interaction) = user_interface.draw(&mut frame_renderer); self.cache = Some(user_interface.into_cache()); - (messages, mouse_cursor) + self.renderer.draw(primitive, renderer); + + (messages, mouse_interaction) } pub fn render(&self, renderer: &mut Renderer) { self.renderer.render(renderer, None); } diff --git a/voxygen/src/ui/ice/renderer.rs b/voxygen/src/ui/ice/renderer.rs index d8657de20e..f94581d4d4 100644 --- a/voxygen/src/ui/ice/renderer.rs +++ b/voxygen/src/ui/ice/renderer.rs @@ -7,11 +7,9 @@ mod row; mod space; use super::{ - super::{ - cache::Cache, - graphic::{self, Graphic, TexId}, - }, - widget, Rotation, + super::graphic::{self, Graphic, TexId}, + cache::Cache, + widget, Font, Rotation, }; use crate::{ render::{ @@ -70,10 +68,23 @@ pub enum Primitive { bounds: iced::Rectangle, color: Rgba, }, + Text { + glyphs: Vec<( + glyph_brush::rusttype::PositionedGlyph<'static>, + glyph_brush::Color, + glyph_brush::FontId, + )>, + //size: f32, + bounds: iced::Rectangle, + linear_color: Rgba, + /*font: iced::Font, + *horizontal_alignment: iced::HorizontalAlignment, + *vertical_alignment: iced::VerticalAlignment, */ + }, Nothing, } -// Optimization idea inspired by what I think iced wgpu renderer may be doing +// Optimization idea inspired by what I think iced wgpu renderer may be doing: // Could have layers of things which don't intersect and thus can be reordered // arbitrarily @@ -90,35 +101,51 @@ pub struct IcedRenderer { // Used to delay cache resizing until after current frame is drawn //need_cache_resize: bool, + // Half of physical resolution half_res: Vec2, // Pixel perfection alignment align: Vec2, + // Scale factor between physical and win dims + p_scale: f32, // Pretend dims :) (i.e. scaled) win_dims: Vec2, // Per-frame/update current_state: State, mesh: Mesh, + glyphs: Vec<(usize, usize, Rgba)>, + // Output from glyph_brush in the previous frame + // It can sometimes ask you to redraw with these instead (idk if that is done with + // pre-positioned glyphs) + last_glyph_verts: Vec<(Aabr, Aabr)>, start: usize, // Draw commands for the next render draw_commands: Vec, //current_scissor: Aabr, } impl IcedRenderer { - pub fn new(renderer: &mut Renderer, scaled_dims: Vec2) -> Result { - let (half_res, align) = Self::calculate_resolution_dependents(renderer.get_resolution()); + pub fn new( + renderer: &mut Renderer, + scaled_dims: Vec2, + default_font: Font, + ) -> Result { + let (half_res, align, p_scale) = + Self::calculate_resolution_dependents(renderer.get_resolution(), scaled_dims); Ok(Self { - cache: Cache::new(renderer)?, + cache: Cache::new(renderer, default_font)?, draw_commands: Vec::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(), mesh: Mesh::new(), + glyphs: Vec::new(), + last_glyph_verts: Vec::new(), current_state: State::Plain, half_res, align, + p_scale, win_dims: scaled_dims, start: 0, //current_scissor: default_scissor(renderer), @@ -144,6 +171,7 @@ impl IcedRenderer { // Re-use memory self.draw_commands.clear(); self.mesh.clear(); + self.glyphs.clear(); self.current_state = State::Plain; self.start = 0; @@ -177,6 +205,80 @@ impl IcedRenderer { self.draw_commands .push(DrawCommand::plain(start..mesh.vertices().len()));*/ + // Fill in placeholder glyph quads + let (glyph_cache, cache_tex) = self.cache.glyph_cache_mut_and_tex(); + let half_res = self.half_res; + + let brush_result = glyph_cache.process_queued( + |rect, tex_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 = tex_data + .iter() + .map(|x| [255, 255, 255, *x]) + .collect::>(); + + if let Err(err) = renderer.update_texture(cache_tex, offset, size, &new_data) { + log::warn!("Failed to update glyph cache texture: {:?}", err); + } + }, + // Urgh more allocation we don't need + |vertex_data| { + let uv_rect = vertex_data.tex_coords; + 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 pixel_coords = vertex_data.pixel_coords; + let rect = Aabr { + min: Vec2::new( + pixel_coords.min.x as f32 / half_res.x - 1.0, + pixel_coords.min.y as f32 / half_res.y - 1.0, + ), + max: Vec2::new( + pixel_coords.max.x as f32 / half_res.x - 1.0, + pixel_coords.max.y as f32 / half_res.y - 1.0, + ), + }; + (uv, rect) + }, + ); + + match brush_result { + Ok(brush_action) => { + match brush_action { + glyph_brush::BrushAction::Draw(verts) => self.last_glyph_verts = verts, + glyph_brush::BrushAction::ReDraw => {}, + } + + let glyphs = &self.glyphs; + let mesh = &mut self.mesh; + + glyphs + .iter() + .flat_map(|(mesh_index, glyph_count, linear_color)| { + let mesh_index = *mesh_index; + let linear_color = *linear_color; + (0..*glyph_count).map(move |i| (mesh_index + i * 6, linear_color)) + }) + .zip(self.last_glyph_verts.iter()) + .for_each(|((mesh_index, linear_color), (uv, rect))| { + mesh.replace_quad( + mesh_index, + create_ui_quad(*rect, *uv, linear_color, UiMode::Text), + ) + }); + }, + Err(glyph_brush::BrushError::TextureTooSmall { suggested: (x, y) }) => { + log::error!( + "Texture to small for all glyphs, would need one of the size: ({}, {})", + x, + y + ); + }, + } + // Create a larger dynamic model if the mesh is larger than the current model // size. if self.model.vbuf.len() < self.mesh.vertices().len() { @@ -203,17 +305,23 @@ impl IcedRenderer { } // Returns (half_res, align) - fn calculate_resolution_dependents(res: Vec2) -> (Vec2, Vec2) { + fn calculate_resolution_dependents( + res: Vec2, + win_dims: Vec2, + ) -> (Vec2, Vec2, f32) { let half_res = res.map(|e| e as f32 / 2.0); let align = align(res); + // Assume to be the same in x and y for now... + let p_scale = res.x as f32 / win_dims.x; - (half_res, align) + (half_res, align, p_scale) } fn update_resolution_dependents(&mut self, res: Vec2) { - let (half_res, align) = Self::calculate_resolution_dependents(res); + let (half_res, align, p_scale) = Self::calculate_resolution_dependents(res, self.win_dims); self.half_res = half_res; self.align = align; + self.p_scale = p_scale; } fn gl_aabr(&self, bounds: iced::Rectangle) -> Aabr { @@ -349,6 +457,63 @@ impl IcedRenderer { UiMode::Geometry, )); }, + Primitive::Text { + glyphs, + bounds, // iced::Rectangle + linear_color, + /*font, + *horizontal_alignment, + *vertical_alignment, */ + } => { + self.switch_state(State::Plain); + + // TODO: Scissor? + + // TODO: makes sure we are not doing all this work for hidden text + // e.g. in chat + let glyph_cache = self.cache.glyph_cache_mut(); + + // Count glyphs + let glyph_count = glyphs.len(); + + // Queue the glyphs to be cached. + glyph_cache.queue_pre_positioned( + glyphs, + // Since we already passed in `bounds` to position the glyphs some of this + // seems redundant... + glyph_brush::rusttype::Rect { + min: glyph_brush::rusttype::Point { + x: bounds.x, + y: bounds.y, + }, + max: glyph_brush::rusttype::Point { + x: bounds.x + bounds.width, + y: bounds.y + bounds.height, + }, + }, + 0.0, // z (we don't use this) + ); + + // Leave ui and verts blank to fill in when processing cached glyphs + let zero_aabr = Aabr { + min: Vec2::broadcast(0.0), + max: Vec2::broadcast(0.0), + }; + self.glyphs + .push((self.mesh.vertices().len(), glyph_count, linear_color)); + for _ in 0..glyph_count { + // Push placeholder quad + // Note: moving to some sort of layering / z based system would be an + // alternative to this (and might help with reducing draw + // calls) + self.mesh.push_quad(create_ui_quad( + zero_aabr, + zero_aabr, + linear_color, + UiMode::Text, + )); + } + }, Primitive::Nothing => {}, } } @@ -414,24 +579,3 @@ fn default_scissor(renderer: &Renderer) -> Aabr { }, } } - -impl iced::Renderer for IcedRenderer { - // Default styling - type Defaults = (); - // TODO: use graph of primitives to enable diffing??? - type Output = (Primitive, iced::MouseCursor); - - fn layout<'a, M>( - &mut self, - element: &iced::Element<'a, M, Self>, - limits: &iced::layout::Limits, - ) -> iced::layout::Node { - let node = element.layout(self, limits); - - // Trim text measurements cache? - - node - } -} - -// TODO: impl Debugger diff --git a/voxygen/src/ui/ice/renderer/background_container.rs b/voxygen/src/ui/ice/renderer/background_container.rs index c92f4f6986..3672d8e527 100644 --- a/voxygen/src/ui/ice/renderer/background_container.rs +++ b/voxygen/src/ui/ice/renderer/background_container.rs @@ -1,7 +1,10 @@ -use super::{super::widget::background_container, IcedRenderer, Primitive}; +use super::{ + super::{cache::FrameRenderer, widget::background_container}, + Primitive, +}; use iced::{Element, Layout, Point}; -impl background_container::Renderer for IcedRenderer { +impl background_container::Renderer for FrameRenderer<'_> { fn draw( &mut self, defaults: &Self::Defaults, @@ -17,13 +20,13 @@ impl background_container::Renderer for IcedRenderer { let back_primitive = background .draw(self, defaults, background_layout, cursor_position) .0; - let (content_primitive, mouse_cursor) = + let (content_primitive, mouse_interaction) = content.draw(self, defaults, content_layout, cursor_position); ( Primitive::Group { primitives: vec![back_primitive, content_primitive], }, - mouse_cursor, + mouse_interaction, ) } } diff --git a/voxygen/src/ui/ice/renderer/column.rs b/voxygen/src/ui/ice/renderer/column.rs index ab1a7abbd2..cec3c5cc20 100644 --- a/voxygen/src/ui/ice/renderer/column.rs +++ b/voxygen/src/ui/ice/renderer/column.rs @@ -1,34 +1,34 @@ -use super::{IcedRenderer, Primitive}; -use iced::{column, Element, Layout, MouseCursor, Point}; +use super::{super::cache::FrameRenderer, Primitive}; +use iced::{column, mouse, Element, Layout, Point}; -impl column::Renderer for IcedRenderer { +impl column::Renderer for FrameRenderer<'_> { fn draw( &mut self, defaults: &Self::Defaults, - children: &[Element<'_, M, Self>], + content: &[Element<'_, M, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_interaction = mouse::Interaction::default(); ( Primitive::Group { - primitives: children + primitives: content .iter() .zip(layout.children()) .map(|(child, layout)| { - let (primitive, new_mouse_cursor) = + let (primitive, new_mouse_interaction) = child.draw(self, defaults, layout, cursor_position); - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; } primitive }) .collect(), }, - mouse_cursor, + mouse_interaction, ) } } diff --git a/voxygen/src/ui/ice/renderer/compound_graphic.rs b/voxygen/src/ui/ice/renderer/compound_graphic.rs index 166dbef484..ecd3763541 100644 --- a/voxygen/src/ui/ice/renderer/compound_graphic.rs +++ b/voxygen/src/ui/ice/renderer/compound_graphic.rs @@ -1,11 +1,11 @@ use super::{ - super::{widget::compound_graphic, Rotation}, - IcedRenderer, Primitive, + super::{cache::FrameRenderer, widget::compound_graphic, Rotation}, + Primitive, }; use compound_graphic::GraphicKind; -use iced::{MouseCursor, Rectangle}; +use iced::{mouse, Rectangle}; -impl compound_graphic::Renderer for IcedRenderer { +impl compound_graphic::Renderer for FrameRenderer<'_> { fn draw( &mut self, graphics: I, @@ -28,7 +28,7 @@ impl compound_graphic::Renderer for IcedRenderer { }) .collect(), }, - MouseCursor::OutOfBounds, + mouse::Interaction::default(), ) } } diff --git a/voxygen/src/ui/ice/renderer/container.rs b/voxygen/src/ui/ice/renderer/container.rs index fd144f66ce..c029704c53 100644 --- a/voxygen/src/ui/ice/renderer/container.rs +++ b/voxygen/src/ui/ice/renderer/container.rs @@ -1,7 +1,7 @@ -use super::IcedRenderer; +use super::super::cache::FrameRenderer; use iced::{container, Element, Layout, Point, Rectangle}; -impl container::Renderer for IcedRenderer { +impl container::Renderer for FrameRenderer<'_> { type Style = (); fn draw( @@ -13,10 +13,11 @@ impl container::Renderer for IcedRenderer { content: &Element<'_, M, Self>, content_layout: Layout<'_>, ) -> Self::Output { - let (content, mouse_cursor) = content.draw(self, defaults, content_layout, cursor_position); + let (content, mouse_interaction) = + content.draw(self, defaults, content_layout, cursor_position); // We may have more stuff here if styles are used - (content, mouse_cursor) + (content, mouse_interaction) } } diff --git a/voxygen/src/ui/ice/renderer/image.rs b/voxygen/src/ui/ice/renderer/image.rs index 22b3f8a953..b66a492384 100644 --- a/voxygen/src/ui/ice/renderer/image.rs +++ b/voxygen/src/ui/ice/renderer/image.rs @@ -1,13 +1,14 @@ use super::{ - super::{widget::image, Rotation}, - IcedRenderer, Primitive, + super::{cache::FrameRenderer, widget::image, Rotation}, + Primitive, }; -use iced::MouseCursor; +use iced::mouse; use vek::Rgba; -impl image::Renderer for IcedRenderer { +impl image::Renderer for FrameRenderer<'_> { fn dimensions(&self, handle: image::Handle) -> (u32, u32) { - self.cache + self.renderer + .cache .graphic_cache() .get_graphic_dims((handle, Rotation::None)) // TODO: don't unwrap @@ -26,7 +27,7 @@ impl image::Renderer for IcedRenderer { bounds: layout.bounds(), color, }, - MouseCursor::OutOfBounds, + mouse::Interaction::default(), ) } } diff --git a/voxygen/src/ui/ice/renderer/row.rs b/voxygen/src/ui/ice/renderer/row.rs index 47d22004f4..06f9395193 100644 --- a/voxygen/src/ui/ice/renderer/row.rs +++ b/voxygen/src/ui/ice/renderer/row.rs @@ -1,34 +1,34 @@ -use super::{IcedRenderer, Primitive}; -use iced::{row, Element, Layout, MouseCursor, Point}; +use super::{super::cache::FrameRenderer, Primitive}; +use iced::{mouse, row, Element, Layout, Point}; -impl row::Renderer for IcedRenderer { +impl row::Renderer for FrameRenderer<'_> { fn draw( &mut self, defaults: &Self::Defaults, - children: &[Element<'_, M, Self>], + content: &[Element<'_, M, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_interaction = mouse::Interaction::default(); ( Primitive::Group { - primitives: children + primitives: content .iter() .zip(layout.children()) .map(|(child, layout)| { - let (primitive, new_mouse_cursor) = + let (primitive, new_mouse_interaction) = child.draw(self, defaults, layout, cursor_position); - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; } primitive }) .collect(), }, - mouse_cursor, + mouse_interaction, ) } } diff --git a/voxygen/src/ui/ice/renderer/space.rs b/voxygen/src/ui/ice/renderer/space.rs index 8c1f898e67..bdba27a577 100644 --- a/voxygen/src/ui/ice/renderer/space.rs +++ b/voxygen/src/ui/ice/renderer/space.rs @@ -1,8 +1,8 @@ -use super::{IcedRenderer, Primitive}; -use iced::{space, MouseCursor, Rectangle}; +use super::{super::cache::FrameRenderer, Primitive}; +use iced::{mouse, space, Rectangle}; -impl space::Renderer for IcedRenderer { +impl space::Renderer for FrameRenderer<'_> { fn draw(&mut self, _bounds: Rectangle) -> Self::Output { - (Primitive::Nothing, MouseCursor::OutOfBounds) + (Primitive::Nothing, mouse::Interaction::default()) } } diff --git a/voxygen/src/ui/ice/renderer/stack.rs b/voxygen/src/ui/ice/renderer/stack.rs index 3eb77d5ad7..9f295790ca 100644 --- a/voxygen/src/ui/ice/renderer/stack.rs +++ b/voxygen/src/ui/ice/renderer/stack.rs @@ -1,34 +1,37 @@ -use super::{super::widget::stack, IcedRenderer, Primitive}; -use iced::{Element, Layout, MouseCursor, Point}; +use super::{ + super::{cache::FrameRenderer, widget::stack}, + Primitive, +}; +use iced::{mouse, Element, Layout, Point}; -impl stack::Renderer for IcedRenderer { +impl stack::Renderer for FrameRenderer<'_> { fn draw( &mut self, defaults: &Self::Defaults, - children: &[Element<'_, M, Self>], + content: &[Element<'_, M, Self>], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut mouse_interaction = mouse::Interaction::default(); ( Primitive::Group { - primitives: children + primitives: content .iter() .zip(layout.children()) .map(|(child, layout)| { - let (primitive, new_mouse_cursor) = + let (primitive, new_mouse_interaction) = child.draw(self, defaults, layout, cursor_position); - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; } primitive }) .collect(), }, - mouse_cursor, + mouse_interaction, ) } } diff --git a/voxygen/src/ui/ice/renderer/text.rs b/voxygen/src/ui/ice/renderer/text.rs new file mode 100644 index 0000000000..14532de358 --- /dev/null +++ b/voxygen/src/ui/ice/renderer/text.rs @@ -0,0 +1,97 @@ +use iced::{ + text, Color, Font, HorizontalAlignment, mouse, Rectangle, Size, VerticalAlignment, +}; +use super::{super::cache::FrameRenderer, Primitive}: + +struct FontId(glyph_brush::FontId); + +impl text::Renderer for FrameRenderer<'_> { + type Font = FontId; + const DEFAULT_SIZE: u16 = 20; + + fn measure( + &self, + content: &str, + size: u16, + font: Self::Font, + bounds: Size, + ) -> (f32, f32) { + // Using the physical scale might make these cached info usable below? + // Although we also have a position of the screen so this could be useless + let p_scale = self.p_scale; + // TODO: would be nice if the method was mut + let section = glyph_brush::Section { + text: content, + scale: glyph_brush::rusttype::Scale::uniform(size as f32 * p_scale), + font_id: font.0, + bounds: (size.width, size.height), + ..Default::default() + }; + + let maybe_rect = self.glyph_calc.borrow_mut().glyph_bounds(section); + maybe_rect.map_or((0.0, 0.0), |rect| (rect.width() / p_scale, rect.height() / p_scale)) + } + + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + content: &str, + size: u16, + font: Self::Font, + color: Option, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, + ) -> Self::Output { + use glyph_brush::{HorizontalAlign, VerticalAlign}; + let h_align = match horizontal_alignment { + HorizontalAlignment::Left => HorizontalAlign::Left, + HorizontalAlignment::Center => HorizontalAlign::Center, + HorizontalAlignment::Right => HorizontalAlign::Right, + }; + + let v_align = match vertical_alignment { + VerticalAlignment::Top => VerticalAlign::Top, + VerticalAlignment::Center => VerticalAlign::Center, + VerticalAlignment::Bottom => VerticalAlign::Bottom, + }; + + let p_scale = self.p_scale; + + let section = glyph_brush::Section { + text: content, + // TODO: do snap to pixel thing here IF it is being done down the line + screen_position: (bounds.x * p_scale, bounds.y * p_scale), + bounds: (bounds.width * p_scale, bounds.height * p_scale), + scale: glyph_brush::rusttype::Scale::uniform(size as f32 * p_scale), + layout: glyph_brush::Layout::Wrap { + line_breaker: Default::default(), + h_align, + v_align, + }, + font_id: font.0, + ..Default::default() + }; + + let glyphs = self.glyph_calc.borrow_mut().glyphs(section).map(|positioned_glyph| + ( + positioned_glyph, + [0.0, 0.0, 0.0, 1.0], // Color + font.0, + ) + ).collect(); + + ( + Primitive::Text { + glyphs, + //size: size as f32, + bounds, + color: color.unwrap_or(Color::BLACK).into_linear().into(), + //font, + //horizontal_alignment, + //vertical_alignment, + }, + mouse::Interaction::default, + ) + } +} diff --git a/voxygen/src/ui/ice/winit_conversion.rs b/voxygen/src/ui/ice/winit_conversion.rs index 79538094ff..4b69e6ded1 100644 --- a/voxygen/src/ui/ice/winit_conversion.rs +++ b/voxygen/src/ui/ice/winit_conversion.rs @@ -1,10 +1,7 @@ // Using reference impl: https://github.com/hecrj/iced/blob/e1438774af809c2951c4c7446638500446c81111/winit/src/conversion.rs use iced::{ - input::{ - keyboard::{self, KeyCode, ModifiersState}, - mouse, ButtonState, - }, - window, Event, + keyboard::{self, KeyCode, ModifiersState}, + mouse, window, Event, }; /// Converts a winit event into an iced event. @@ -28,10 +25,14 @@ pub fn window_event(event: winit::WindowEvent) -> Option { y: position.y as f32, })) }, - WindowEvent::MouseInput { button, state, .. } => Some(Event::Mouse(mouse::Event::Input { - button: mouse_button(button), - state: button_state(state), - })), + WindowEvent::MouseInput { button, state, .. } => { + let button = mouse_button(button); + + Some(Event::Mouse(match state { + winit::ElementState::Pressed => mouse::Event::ButtonPressed(button), + winit::ElementState::Released => mouse::Event::ButtonReleased(button), + })) + }, WindowEvent::MouseWheel { delta, .. } => match delta { winit::MouseScrollDelta::LineDelta(delta_x, delta_y) => { Some(Event::Mouse(mouse::Event::WheelScrolled { @@ -63,10 +64,20 @@ pub fn window_event(event: winit::WindowEvent) -> Option { .. }, .. - } => Some(Event::Keyboard(keyboard::Event::Input { - key_code: key_code(virtual_keycode), - state: button_state(state), - modifiers: modifiers_state(modifiers), + } => Some(Event::Keyboard({ + let key_code = key_code(virtual_keycode); + let modifiers = modifiers_state(modifiers); + + match state { + winit::ElementState::Pressed => keyboard::Event::KeyPressed { + key_code, + modifiers, + }, + winit::ElementState::Released => keyboard::Event::KeyReleased { + key_code, + modifiers, + }, + } })), // iced also can use file hovering events but we don't need them right now _ => None, @@ -85,14 +96,6 @@ fn mouse_button(mouse_button: winit::MouseButton) -> mouse::Button { } } -/// Converts winit `ElementState` to an iced button state -fn button_state(element_state: winit::ElementState) -> ButtonState { - match element_state { - winit::ElementState::Pressed => ButtonState::Pressed, - winit::ElementState::Released => ButtonState::Released, - } -} - /// Converts winit `ModifiersState` to iced `ModifiersState` fn modifiers_state(modifiers: winit::ModifiersState) -> ModifiersState { ModifiersState {