diff options
author | 2019-10-05 05:12:36 +0200 | |
---|---|---|
committer | 2019-10-05 05:12:36 +0200 | |
commit | 0c3f78713d24b263e94cf6aebb8862926feaff23 (patch) | |
tree | fd7536e245228369dfb3300826002459c5a99ff3 | |
parent | a90f7fcb987f667a80038a5e72f379abbd59d932 (diff) | |
download | iced-0c3f78713d24b263e94cf6aebb8862926feaff23.tar.gz iced-0c3f78713d24b263e94cf6aebb8862926feaff23.tar.bz2 iced-0c3f78713d24b263e94cf6aebb8862926feaff23.zip |
Draft basic text rendering using `wgpu_glyph`
-rw-r--r-- | native/src/element.rs | 9 | ||||
-rw-r--r-- | src/lib.rs | 34 | ||||
-rw-r--r-- | wgpu/Cargo.toml | 1 | ||||
-rw-r--r-- | wgpu/src/lib.rs | 226 |
4 files changed, 221 insertions, 49 deletions
diff --git a/native/src/element.rs b/native/src/element.rs index 8d14070a..417e3463 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -41,6 +41,15 @@ where } } + pub fn draw( + &self, + renderer: &mut Renderer, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Primitive { + self.widget.draw(renderer, layout, cursor_position) + } + /// Applies a transformation to the produced message of the [`Element`]. /// /// This method is useful when you want to decouple different parts of your @@ -1,4 +1,4 @@ -pub use iced_wgpu::Renderer; +pub use iced_wgpu::{Primitive, Renderer}; pub use iced_winit::{ button, slider, text, winit, Align, Button, Checkbox, Color, Image, Justify, Length, Radio, Slider, Text, @@ -33,13 +33,15 @@ pub trait UserInterface { .expect("Open window"); let size = window.inner_size().to_physical(window.hidpi_factor());; + let (width, height) = (size.width as u16, size.height as u16); - let mut renderer = - Renderer::new(&window, size.width as u32, size.height as u32); + let mut renderer = Renderer::new(&window); + let mut target = renderer.target(width, height); let mut cache = Some(iced_winit::Cache::default()); let mut events = Vec::new(); let mut redraws = 0; + let mut primitive = Primitive::None; window.request_redraw(); @@ -51,7 +53,7 @@ pub trait UserInterface { // This will allow us to rebuild it only when a message is // handled. let mut user_interface = iced_winit::UserInterface::build( - self.view(), + document(&mut self, width, height), cache.take().unwrap(), &mut renderer, ); @@ -59,7 +61,7 @@ pub trait UserInterface { let messages = user_interface.update(events.drain(..)); if messages.is_empty() { - let _ = user_interface.draw(&mut renderer); + primitive = user_interface.draw(&mut renderer); cache = Some(user_interface.into_cache()); } else { @@ -72,12 +74,12 @@ pub trait UserInterface { } let user_interface = iced_winit::UserInterface::build( - self.view(), + document(&mut self, width, height), temp_cache, &mut renderer, ); - let _ = user_interface.draw(&mut renderer); + primitive = user_interface.draw(&mut renderer); cache = Some(user_interface.into_cache()); } @@ -89,7 +91,7 @@ pub trait UserInterface { .. } => { println!("Redrawing {}", redraws); - renderer.draw(); + renderer.draw(&mut target, &primitive); redraws += 1; @@ -108,3 +110,19 @@ pub trait UserInterface { }) } } + +fn document<UserInterface>( + user_interface: &mut UserInterface, + width: u16, + height: u16, +) -> Element<UserInterface::Message> +where + UserInterface: self::UserInterface, + UserInterface::Message: 'static, +{ + Column::new() + .width(Length::Units(width)) + .height(Length::Units(height)) + .push(user_interface.view()) + .into() +} diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 879def28..79661baa 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -13,4 +13,5 @@ vulkan = ["wgpu/vulkan"] [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } wgpu = "0.3" +wgpu_glyph = "0.4" raw-window-handle = "0.1" diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 28964858..d8f727cd 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,7 +1,7 @@ use iced_native::{ button, checkbox, column, image, radio, renderer::Debugger, row, slider, text, Button, Checkbox, Color, Column, Image, Layout, Node, Point, Radio, - Row, Slider, Style, Text, Widget, + Rectangle, Row, Slider, Style, Text, Widget, }; use raw_window_handle::HasRawWindowHandle; @@ -10,28 +10,34 @@ use wgpu::{ Instance, Limits, PowerPreference, RequestAdapterOptions, Surface, SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, }; +use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, GlyphCruncher, Section}; + +use std::f32; +use std::{cell::RefCell, rc::Rc}; pub struct Renderer { instance: Instance, surface: Surface, adapter: Adapter, device: Device, + glyph_brush: Rc<RefCell<GlyphBrush<'static, ()>>>, +} + +pub struct Target { + width: u16, + height: u16, swap_chain: SwapChain, } impl Renderer { - pub fn new<W: HasRawWindowHandle>( - window: &W, - width: u32, - height: u32, - ) -> Self { + pub fn new<W: HasRawWindowHandle>(window: &W) -> Self { let instance = Instance::new(); let adapter = instance.request_adapter(&RequestAdapterOptions { power_preference: PowerPreference::LowPower, }); - let device = adapter.request_device(&DeviceDescriptor { + let mut device = adapter.request_device(&DeviceDescriptor { extensions: Extensions { anisotropic_filtering: false, }, @@ -40,28 +46,40 @@ impl Renderer { let surface = instance.create_surface(window.raw_window_handle()); - let swap_chain = device.create_swap_chain( - &surface, - &SwapChainDescriptor { - usage: TextureUsage::OUTPUT_ATTACHMENT, - format: TextureFormat::Bgra8UnormSrgb, - width, - height, - present_mode: wgpu::PresentMode::Vsync, - }, - ); + let font: &[u8] = + include_bytes!("../../examples/tour/resources/Roboto-Regular.ttf"); + + let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) + .build(&mut device, TextureFormat::Bgra8UnormSrgb); Self { instance, surface, adapter, device, - swap_chain, + glyph_brush: Rc::new(RefCell::new(glyph_brush)), } } - pub fn draw(&mut self) { - let frame = self.swap_chain.get_next_texture(); + pub fn target(&self, width: u16, height: u16) -> Target { + Target { + width, + height, + swap_chain: self.device.create_swap_chain( + &self.surface, + &SwapChainDescriptor { + usage: TextureUsage::OUTPUT_ATTACHMENT, + format: TextureFormat::Bgra8UnormSrgb, + width: u32::from(width), + height: u32::from(height), + present_mode: wgpu::PresentMode::Vsync, + }, + ), + } + } + + pub fn draw(&mut self, target: &mut Target, primitive: &Primitive) { + let frame = target.swap_chain.get_next_texture(); let mut encoder = self .device @@ -83,39 +101,169 @@ impl Renderer { depth_stencil_attachment: None, }); + self.draw_primitive(primitive); + + self.glyph_brush + .borrow_mut() + .draw_queued( + &mut self.device, + &mut encoder, + &frame.view, + u32::from(target.width), + u32::from(target.height), + ) + .expect("Draw text"); + self.device.get_queue().submit(&[encoder.finish()]); } + + fn draw_primitive(&mut self, primitive: &Primitive) { + match primitive { + Primitive::None => {} + Primitive::Group { primitives } => { + for primitive in primitives { + self.draw_primitive(primitive) + } + } + Primitive::Text { + content, + bounds, + size, + } => self.glyph_brush.borrow_mut().queue(Section { + text: &content, + screen_position: (bounds.x, bounds.y), + bounds: (bounds.width, bounds.height), + scale: wgpu_glyph::Scale { x: *size, y: *size }, + ..Default::default() + }), + } + } +} + +#[derive(Debug, Clone)] +pub enum Primitive { + None, + Group { + primitives: Vec<Primitive>, + }, + Text { + content: String, + bounds: Rectangle, + size: f32, + }, +} + +impl iced_native::Renderer for Renderer { + type Primitive = Primitive; } impl column::Renderer for Renderer { fn draw<Message>( &mut self, - _column: &Column<'_, Message, Self>, - _layout: Layout<'_>, - _cursor_position: Point, + column: &Column<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, ) -> Self::Primitive { - () + Primitive::Group { + primitives: column + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } } } impl row::Renderer for Renderer { fn draw<Message>( &mut self, - _column: &Row<'_, Message, Self>, - _layout: Layout<'_>, - _cursor_position: Point, + row: &Row<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, ) -> Self::Primitive { - () + Primitive::Group { + primitives: row + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + child.draw(self, layout, cursor_position) + }) + .collect(), + } } } impl text::Renderer for Renderer { - fn node(&self, _text: &Text) -> Node { - Node::new(Style::default()) + fn node(&self, text: &Text) -> Node { + let glyph_brush = self.glyph_brush.clone(); + let content = text.content.clone(); + + // TODO: Investigate why stretch tries to measure this MANY times + // with every ancestor's bounds. + // Bug? Using the library wrong? I should probably open an issue on + // the stretch repository. + // I noticed that the first measure is the one that matters in + // practice. Here, we use a RefCell to store the cached measurement. + let measure = RefCell::new(None); + let size = text.size.map(f32::from).unwrap_or(20.0); + + let style = Style::default().width(text.width); + + iced_native::Node::with_measure(style, move |bounds| { + let mut measure = measure.borrow_mut(); + + if measure.is_none() { + let bounds = ( + match bounds.width { + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(w) => w, + }, + match bounds.height { + iced_native::Number::Undefined => f32::INFINITY, + iced_native::Number::Defined(h) => h, + }, + ); + + let text = Section { + text: &content, + scale: wgpu_glyph::Scale { x: size, y: size }, + bounds, + ..Default::default() + }; + + let (width, height) = if let Some(bounds) = + glyph_brush.borrow_mut().glyph_bounds(&text) + { + (bounds.width(), bounds.height()) + } else { + (0.0, 0.0) + }; + + let size = iced_native::Size { width, height }; + + // If the text has no width boundary we avoid caching as the + // layout engine may just be measuring text in a row. + if bounds.0 == f32::INFINITY { + return size; + } else { + *measure = Some(size); + } + } + + measure.unwrap() + }) } - fn draw(&mut self, _text: &Text, _layout: Layout<'_>) -> Self::Primitive { - () + fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::Primitive { + Primitive::Text { + content: text.content.clone(), + size: f32::from(text.size.unwrap_or(20)), + bounds: layout.bounds(), + } } } @@ -130,7 +278,7 @@ impl checkbox::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } @@ -145,7 +293,7 @@ impl radio::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } @@ -160,7 +308,7 @@ impl slider::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } @@ -174,7 +322,7 @@ impl image::Renderer<&str> for Renderer { _checkbox: &Image<&str>, _layout: Layout<'_>, ) -> Self::Primitive { - () + Primitive::None } } @@ -189,14 +337,10 @@ impl button::Renderer for Renderer { _layout: Layout<'_>, _cursor_position: Point, ) -> Self::Primitive { - () + Primitive::None } } -impl iced_native::Renderer for Renderer { - type Primitive = (); -} - impl Debugger for Renderer { fn explain<Message>( &mut self, |