From 58e04af824a64d9f712a2d6691d4283888d271d3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 2 Nov 2019 19:58:49 +0100 Subject: Draft `Metrics` and improve `Target` abstraction --- native/src/lib.rs | 2 + native/src/metrics.rs | 11 ++++++ native/src/renderer.rs | 2 +- native/src/renderer/windowed.rs | 24 +++++++++--- wgpu/src/renderer.rs | 84 ++++++++++++++++++++++++++--------------- winit/src/application.rs | 18 ++++++--- 6 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 native/src/metrics.rs diff --git a/native/src/lib.rs b/native/src/lib.rs index fa72a553..86bfb3ca 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -206,6 +206,7 @@ mod element; mod event; mod hasher; mod layout; +mod metrics; mod mouse_cursor; mod node; mod style; @@ -224,6 +225,7 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; +pub use metrics::Metrics; pub use mouse_cursor::MouseCursor; pub use node::Node; pub use renderer::Renderer; diff --git a/native/src/metrics.rs b/native/src/metrics.rs new file mode 100644 index 00000000..7c344b9b --- /dev/null +++ b/native/src/metrics.rs @@ -0,0 +1,11 @@ +use std::time; + +/// A bunch of metrics about an Iced application. +#[derive(Debug, Clone, Copy)] +pub struct Metrics { + pub startup_time: time::Duration, + pub update_time: time::Duration, + pub view_time: time::Duration, + pub renderer_output_time: time::Duration, + pub message_count: usize, +} diff --git a/native/src/renderer.rs b/native/src/renderer.rs index afe1b09a..5963d577 100644 --- a/native/src/renderer.rs +++ b/native/src/renderer.rs @@ -24,7 +24,7 @@ mod debugger; mod windowed; pub use debugger::Debugger; -pub use windowed::Windowed; +pub use windowed::{Target, Windowed}; pub trait Renderer { type Output; diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs index bcf37964..0499ca70 100644 --- a/native/src/renderer/windowed.rs +++ b/native/src/renderer/windowed.rs @@ -1,17 +1,29 @@ -use crate::MouseCursor; +use crate::{Metrics, MouseCursor}; use raw_window_handle::HasRawWindowHandle; -pub trait Windowed: super::Renderer { - type Target; +pub trait Windowed: super::Renderer + Sized { + type Target: Target; - fn new(window: &W) -> Self; - - fn target(&self, width: u16, height: u16) -> Self::Target; + fn new() -> Self; fn draw( &mut self, output: &Self::Output, + metrics: Option, target: &mut Self::Target, ) -> MouseCursor; } + +pub trait Target { + type Renderer; + + fn new( + window: &W, + width: u16, + height: u16, + renderer: &Self::Renderer, + ) -> Self; + + fn resize(&mut self, width: u16, height: u16, renderer: &Self::Renderer); +} diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index fbc39327..6e90c129 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,6 +1,6 @@ use crate::{quad, Image, Primitive, Quad, Transformation}; use iced_native::{ - renderer::Debugger, renderer::Windowed, Background, Color, Layout, + renderer::Debugger, renderer::Windowed, Background, Color, Layout, Metrics, MouseCursor, Point, Rectangle, Widget, }; @@ -26,7 +26,6 @@ mod text; mod text_input; pub struct Renderer { - surface: Surface, device: Device, queue: Queue, quad_pipeline: quad::Pipeline, @@ -36,12 +35,61 @@ pub struct Renderer { } pub struct Target { + surface: Surface, width: u16, height: u16, transformation: Transformation, swap_chain: SwapChain, } +impl iced_native::renderer::Target for Target { + type Renderer = Renderer; + + fn new( + window: &W, + width: u16, + height: u16, + renderer: &Renderer, + ) -> Target { + let surface = Surface::create(window); + + let swap_chain = renderer.device.create_swap_chain( + &surface, + &SwapChainDescriptor { + usage: TextureUsage::OUTPUT_ATTACHMENT, + format: TextureFormat::Bgra8UnormSrgb, + width: u32::from(width), + height: u32::from(height), + present_mode: wgpu::PresentMode::Vsync, + }, + ); + + Target { + surface, + width, + height, + transformation: Transformation::orthographic(width, height), + swap_chain, + } + } + + fn resize(&mut self, width: u16, height: u16, renderer: &Renderer) { + self.width = width; + self.height = height; + self.transformation = Transformation::orthographic(width, height); + self.swap_chain = renderer.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 struct Layer<'a> { bounds: Rectangle, y_offset: u32, @@ -63,7 +111,7 @@ impl<'a> Layer<'a> { } impl Renderer { - fn new(window: &W) -> Self { + fn new() -> Self { let adapter = Adapter::request(&RequestAdapterOptions { power_preference: PowerPreference::LowPower, backends: BackendBit::all(), @@ -77,8 +125,6 @@ impl Renderer { limits: Limits { max_bind_groups: 2 }, }); - let surface = Surface::create(window); - // TODO: Think about font loading strategy // Loading system fonts with fallback may be a good idea let font: &[u8] = @@ -91,7 +137,6 @@ impl Renderer { let image_pipeline = crate::image::Pipeline::new(&mut device); Self { - surface, device, queue, quad_pipeline, @@ -101,24 +146,6 @@ impl Renderer { } } - fn target(&self, width: u16, height: u16) -> Target { - Target { - width, - height, - transformation: Transformation::orthographic(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, - }, - ), - } - } - fn draw( &mut self, (primitive, mouse_cursor): &(Primitive, MouseCursor), @@ -363,17 +390,14 @@ impl iced_native::Renderer for Renderer { impl Windowed for Renderer { type Target = Target; - fn new(window: &W) -> Self { - Self::new(window) - } - - fn target(&self, width: u16, height: u16) -> Target { - self.target(width, height) + fn new() -> Self { + Self::new() } fn draw( &mut self, output: &Self::Output, + metrics: Option, target: &mut Target, ) -> MouseCursor { self.draw(output, target) diff --git a/winit/src/application.rs b/winit/src/application.rs index b90b5eef..8c7d8c37 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -1,7 +1,7 @@ use crate::{ column, conversion, input::{keyboard, mouse}, - renderer::Windowed, + renderer::{Target, Windowed}, Cache, Column, Element, Event, Length, MouseCursor, UserInterface, }; @@ -41,8 +41,14 @@ pub trait Application { .into(); let mut new_size: Option = None; - let mut renderer = Self::Renderer::new(&window); - let mut target = renderer.target(size.width, size.height); + let mut renderer = Self::Renderer::new(); + + let mut target = ::Target::new( + &window, + size.width, + size.height, + &renderer, + ); let user_interface = UserInterface::build( document(&mut self, size), @@ -103,11 +109,13 @@ pub trait Application { } event::Event::RedrawRequested(_) => { if let Some(new_size) = new_size.take() { - target = renderer.target(new_size.width, new_size.height); + target.resize(new_size.width, new_size.height, &renderer); + size = new_size; } - let new_mouse_cursor = renderer.draw(&primitive, &mut target); + let new_mouse_cursor = + renderer.draw(&primitive, None, &mut target); if new_mouse_cursor != mouse_cursor { window.set_cursor_icon(conversion::mouse_cursor( -- cgit From ef056d84890e745010675e70f734f882f89356c2 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 2 Nov 2019 20:20:35 +0100 Subject: Move `Target` to its own module --- wgpu/src/renderer.rs | 90 +++--------------- wgpu/src/renderer/button.rs | 86 ----------------- wgpu/src/renderer/checkbox.rs | 107 ---------------------- wgpu/src/renderer/column.rs | 34 ------- wgpu/src/renderer/image.rs | 34 ------- wgpu/src/renderer/radio.rs | 109 ---------------------- wgpu/src/renderer/row.rs | 34 ------- wgpu/src/renderer/scrollable.rs | 129 -------------------------- wgpu/src/renderer/slider.rs | 128 -------------------------- wgpu/src/renderer/target.rs | 74 +++++++++++++++ wgpu/src/renderer/text.rs | 83 ----------------- wgpu/src/renderer/text_input.rs | 162 --------------------------------- wgpu/src/renderer/widget.rs | 10 ++ wgpu/src/renderer/widget/button.rs | 86 +++++++++++++++++ wgpu/src/renderer/widget/checkbox.rs | 107 ++++++++++++++++++++++ wgpu/src/renderer/widget/column.rs | 34 +++++++ wgpu/src/renderer/widget/image.rs | 34 +++++++ wgpu/src/renderer/widget/radio.rs | 109 ++++++++++++++++++++++ wgpu/src/renderer/widget/row.rs | 34 +++++++ wgpu/src/renderer/widget/scrollable.rs | 129 ++++++++++++++++++++++++++ wgpu/src/renderer/widget/slider.rs | 128 ++++++++++++++++++++++++++ wgpu/src/renderer/widget/text.rs | 83 +++++++++++++++++ wgpu/src/renderer/widget/text_input.rs | 162 +++++++++++++++++++++++++++++++++ 23 files changed, 1002 insertions(+), 984 deletions(-) delete mode 100644 wgpu/src/renderer/button.rs delete mode 100644 wgpu/src/renderer/checkbox.rs delete mode 100644 wgpu/src/renderer/column.rs delete mode 100644 wgpu/src/renderer/image.rs delete mode 100644 wgpu/src/renderer/radio.rs delete mode 100644 wgpu/src/renderer/row.rs delete mode 100644 wgpu/src/renderer/scrollable.rs delete mode 100644 wgpu/src/renderer/slider.rs create mode 100644 wgpu/src/renderer/target.rs delete mode 100644 wgpu/src/renderer/text.rs delete mode 100644 wgpu/src/renderer/text_input.rs create mode 100644 wgpu/src/renderer/widget.rs create mode 100644 wgpu/src/renderer/widget/button.rs create mode 100644 wgpu/src/renderer/widget/checkbox.rs create mode 100644 wgpu/src/renderer/widget/column.rs create mode 100644 wgpu/src/renderer/widget/image.rs create mode 100644 wgpu/src/renderer/widget/radio.rs create mode 100644 wgpu/src/renderer/widget/row.rs create mode 100644 wgpu/src/renderer/widget/scrollable.rs create mode 100644 wgpu/src/renderer/widget/slider.rs create mode 100644 wgpu/src/renderer/widget/text.rs create mode 100644 wgpu/src/renderer/widget/text_input.rs diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 6e90c129..1988dfae 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -4,26 +4,19 @@ use iced_native::{ MouseCursor, Point, Rectangle, Widget, }; -use raw_window_handle::HasRawWindowHandle; use wgpu::{ Adapter, BackendBit, CommandEncoderDescriptor, Device, DeviceDescriptor, - Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, Surface, - SwapChain, SwapChainDescriptor, TextureFormat, TextureUsage, + Extensions, Limits, PowerPreference, Queue, RequestAdapterOptions, + TextureFormat, }; use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, Section}; use std::{cell::RefCell, rc::Rc}; -mod button; -mod checkbox; -mod column; -mod image; -mod radio; -mod row; -mod scrollable; -mod slider; -mod text; -mod text_input; +mod target; +mod widget; + +pub use target::Target; pub struct Renderer { device: Device, @@ -34,62 +27,6 @@ pub struct Renderer { glyph_brush: Rc>>, } -pub struct Target { - surface: Surface, - width: u16, - height: u16, - transformation: Transformation, - swap_chain: SwapChain, -} - -impl iced_native::renderer::Target for Target { - type Renderer = Renderer; - - fn new( - window: &W, - width: u16, - height: u16, - renderer: &Renderer, - ) -> Target { - let surface = Surface::create(window); - - let swap_chain = renderer.device.create_swap_chain( - &surface, - &SwapChainDescriptor { - usage: TextureUsage::OUTPUT_ATTACHMENT, - format: TextureFormat::Bgra8UnormSrgb, - width: u32::from(width), - height: u32::from(height), - present_mode: wgpu::PresentMode::Vsync, - }, - ); - - Target { - surface, - width, - height, - transformation: Transformation::orthographic(width, height), - swap_chain, - } - } - - fn resize(&mut self, width: u16, height: u16, renderer: &Renderer) { - self.width = width; - self.height = height; - self.transformation = Transformation::orthographic(width, height); - self.swap_chain = renderer.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 struct Layer<'a> { bounds: Rectangle, y_offset: u32, @@ -153,7 +90,9 @@ impl Renderer { ) -> MouseCursor { log::debug!("Drawing"); - let frame = target.swap_chain.get_next_texture(); + let (width, height) = target.dimensions(); + let transformation = target.transformation(); + let frame = target.next_frame(); let mut encoder = self .device @@ -181,8 +120,8 @@ impl Renderer { Rectangle { x: 0, y: 0, - width: u32::from(target.width), - height: u32::from(target.height), + width: u32::from(width), + height: u32::from(height), }, 0, )); @@ -190,12 +129,7 @@ impl Renderer { self.draw_primitive(primitive, &mut layers); for layer in layers { - self.flush( - target.transformation, - &layer, - &mut encoder, - &frame.view, - ); + self.flush(transformation, &layer, &mut encoder, &frame.view); } self.queue.submit(&[encoder.finish()]); diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs deleted file mode 100644 index ad2186d6..00000000 --- a/wgpu/src/renderer/button.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - button, Align, Background, Button, Color, Layout, Length, MouseCursor, - Node, Point, Rectangle, Style, -}; - -impl button::Renderer for Renderer { - fn node(&self, button: &Button) -> Node { - let style = Style::default() - .width(button.width) - .padding(button.padding) - .min_width(Length::Units(100)) - .align_self(button.align_self) - .align_items(Align::Stretch); - - Node::with_children(style, vec![button.content.node(self)]) - } - - fn draw( - &mut self, - button: &Button, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let bounds = layout.bounds(); - - let (content, _) = button.content.draw( - self, - layout.children().next().unwrap(), - cursor_position, - ); - - let is_mouse_over = bounds.contains(cursor_position); - - // TODO: Render proper shadows - // TODO: Make hovering and pressed styles configurable - let shadow_offset = if is_mouse_over { - if button.state.is_pressed { - 0.0 - } else { - 2.0 - } - } else { - 1.0 - }; - - ( - Primitive::Group { - primitives: vec![ - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + 1.0, - y: bounds.y + shadow_offset, - ..bounds - }, - background: Background::Color(Color { - r: 0.0, - b: 0.0, - g: 0.0, - a: 0.5, - }), - border_radius: button.border_radius, - }, - Primitive::Quad { - bounds, - background: button.background.unwrap_or( - Background::Color(Color { - r: 0.8, - b: 0.8, - g: 0.8, - a: 1.0, - }), - ), - border_radius: button.border_radius, - }, - content, - ], - }, - if is_mouse_over { - MouseCursor::Pointer - } else { - MouseCursor::OutOfBounds - }, - ) - } -} diff --git a/wgpu/src/renderer/checkbox.rs b/wgpu/src/renderer/checkbox.rs deleted file mode 100644 index ea7a4c0b..00000000 --- a/wgpu/src/renderer/checkbox.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - checkbox, text, text::HorizontalAlignment, text::VerticalAlignment, Align, - Background, Checkbox, Color, Column, Layout, Length, MouseCursor, Node, - Point, Rectangle, Row, Text, Widget, -}; - -const SIZE: f32 = 28.0; - -impl checkbox::Renderer for Renderer { - fn node(&self, checkbox: &Checkbox) -> Node { - Row::<(), Self>::new() - .width(Length::Fill) - .spacing(15) - .align_items(Align::Center) - .push( - Column::new() - .width(Length::Units(SIZE as u16)) - .height(Length::Units(SIZE as u16)), - ) - .push(Text::new(&checkbox.label)) - .node(self) - } - - fn draw( - &mut self, - checkbox: &Checkbox, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let bounds = layout.bounds(); - let mut children = layout.children(); - - let checkbox_layout = children.next().unwrap(); - let label_layout = children.next().unwrap(); - let checkbox_bounds = checkbox_layout.bounds(); - - let (label, _) = text::Renderer::draw( - self, - &Text::new(&checkbox.label), - label_layout, - ); - - let is_mouse_over = bounds.contains(cursor_position); - - let (checkbox_border, checkbox_box) = ( - Primitive::Quad { - bounds: checkbox_bounds, - background: Background::Color(Color { - r: 0.6, - g: 0.6, - b: 0.6, - a: 1.0, - }), - border_radius: 6, - }, - Primitive::Quad { - bounds: Rectangle { - x: checkbox_bounds.x + 1.0, - y: checkbox_bounds.y + 1.0, - width: checkbox_bounds.width - 2.0, - height: checkbox_bounds.height - 2.0, - }, - background: Background::Color(if is_mouse_over { - Color { - r: 0.90, - g: 0.90, - b: 0.90, - a: 1.0, - } - } else { - Color { - r: 0.95, - g: 0.95, - b: 0.95, - a: 1.0, - } - }), - border_radius: 6, - }, - ); - - ( - Primitive::Group { - primitives: if checkbox.is_checked { - // TODO: Draw an actual icon - let (check, _) = text::Renderer::draw( - self, - &Text::new("X") - .horizontal_alignment(HorizontalAlignment::Center) - .vertical_alignment(VerticalAlignment::Center), - checkbox_layout, - ); - - vec![checkbox_border, checkbox_box, check, label] - } else { - vec![checkbox_border, checkbox_box, label] - }, - }, - if is_mouse_over { - MouseCursor::Pointer - } else { - MouseCursor::OutOfBounds - }, - ) - } -} diff --git a/wgpu/src/renderer/column.rs b/wgpu/src/renderer/column.rs deleted file mode 100644 index cac6da77..00000000 --- a/wgpu/src/renderer/column.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{column, Column, Layout, MouseCursor, Point}; - -impl column::Renderer for Renderer { - fn draw( - &mut self, - column: &Column<'_, Message, Self>, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; - - ( - Primitive::Group { - primitives: column - .children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - let (primitive, new_mouse_cursor) = - child.draw(self, layout, cursor_position); - - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; - } - - primitive - }) - .collect(), - }, - mouse_cursor, - ) - } -} diff --git a/wgpu/src/renderer/image.rs b/wgpu/src/renderer/image.rs deleted file mode 100644 index 0e312706..00000000 --- a/wgpu/src/renderer/image.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{image, Image, Layout, Length, MouseCursor, Node, Style}; - -impl image::Renderer for Renderer { - fn node(&self, image: &Image) -> Node { - let (width, height) = self.image_pipeline.dimensions(&image.path); - - let aspect_ratio = width as f32 / height as f32; - - let mut style = Style::default().align_self(image.align_self); - - // TODO: Deal with additional cases - style = match (image.width, image.height) { - (Length::Units(width), _) => style.width(image.width).height( - Length::Units((width as f32 / aspect_ratio).round() as u16), - ), - (_, _) => style - .width(Length::Units(width as u16)) - .height(Length::Units(height as u16)), - }; - - Node::new(style) - } - - fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output { - ( - Primitive::Image { - path: image.path.clone(), - bounds: layout.bounds(), - }, - MouseCursor::OutOfBounds, - ) - } -} diff --git a/wgpu/src/renderer/radio.rs b/wgpu/src/renderer/radio.rs deleted file mode 100644 index 97b4f70e..00000000 --- a/wgpu/src/renderer/radio.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - radio, text, Align, Background, Color, Column, Layout, Length, MouseCursor, - Node, Point, Radio, Rectangle, Row, Text, Widget, -}; - -const SIZE: f32 = 28.0; -const DOT_SIZE: f32 = SIZE / 2.0; - -impl radio::Renderer for Renderer { - fn node(&self, radio: &Radio) -> Node { - Row::<(), Self>::new() - .spacing(15) - .align_items(Align::Center) - .push( - Column::new() - .width(Length::Units(SIZE as u16)) - .height(Length::Units(SIZE as u16)), - ) - .push(Text::new(&radio.label)) - .node(self) - } - - fn draw( - &mut self, - radio: &Radio, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let bounds = layout.bounds(); - let mut children = layout.children(); - - let radio_bounds = children.next().unwrap().bounds(); - let label_layout = children.next().unwrap(); - - let (label, _) = - text::Renderer::draw(self, &Text::new(&radio.label), label_layout); - - let is_mouse_over = bounds.contains(cursor_position); - - let (radio_border, radio_box) = ( - Primitive::Quad { - bounds: radio_bounds, - background: Background::Color(Color { - r: 0.6, - g: 0.6, - b: 0.6, - a: 1.0, - }), - border_radius: (SIZE / 2.0) as u16, - }, - Primitive::Quad { - bounds: Rectangle { - x: radio_bounds.x + 1.0, - y: radio_bounds.y + 1.0, - width: radio_bounds.width - 2.0, - height: radio_bounds.height - 2.0, - }, - background: Background::Color(if is_mouse_over { - Color { - r: 0.90, - g: 0.90, - b: 0.90, - a: 1.0, - } - } else { - Color { - r: 0.95, - g: 0.95, - b: 0.95, - a: 1.0, - } - }), - border_radius: (SIZE / 2.0 - 1.0) as u16, - }, - ); - - ( - Primitive::Group { - primitives: if radio.is_selected { - let radio_circle = Primitive::Quad { - bounds: Rectangle { - x: radio_bounds.x + DOT_SIZE / 2.0, - y: radio_bounds.y + DOT_SIZE / 2.0, - width: radio_bounds.width - DOT_SIZE, - height: radio_bounds.height - DOT_SIZE, - }, - background: Background::Color(Color { - r: 0.30, - g: 0.30, - b: 0.30, - a: 1.0, - }), - border_radius: (DOT_SIZE / 2.0) as u16, - }; - - vec![radio_border, radio_box, radio_circle, label] - } else { - vec![radio_border, radio_box, label] - }, - }, - if is_mouse_over { - MouseCursor::Pointer - } else { - MouseCursor::OutOfBounds - }, - ) - } -} diff --git a/wgpu/src/renderer/row.rs b/wgpu/src/renderer/row.rs deleted file mode 100644 index bbfef9a1..00000000 --- a/wgpu/src/renderer/row.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{row, Layout, MouseCursor, Point, Row}; - -impl row::Renderer for Renderer { - fn draw( - &mut self, - row: &Row<'_, Message, Self>, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let mut mouse_cursor = MouseCursor::OutOfBounds; - - ( - Primitive::Group { - primitives: row - .children - .iter() - .zip(layout.children()) - .map(|(child, layout)| { - let (primitive, new_mouse_cursor) = - child.draw(self, layout, cursor_position); - - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; - } - - primitive - }) - .collect(), - }, - mouse_cursor, - ) - } -} diff --git a/wgpu/src/renderer/scrollable.rs b/wgpu/src/renderer/scrollable.rs deleted file mode 100644 index 72d77cc8..00000000 --- a/wgpu/src/renderer/scrollable.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - scrollable, Background, Color, Layout, MouseCursor, Point, Rectangle, - Scrollable, Widget, -}; - -const SCROLLBAR_WIDTH: u16 = 10; -const SCROLLBAR_MARGIN: u16 = 2; - -fn scrollbar_bounds(bounds: Rectangle) -> Rectangle { - Rectangle { - x: bounds.x + bounds.width - - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - y: bounds.y, - width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), - height: bounds.height, - } -} - -impl scrollable::Renderer for Renderer { - fn is_mouse_over_scrollbar( - &self, - bounds: Rectangle, - content_bounds: Rectangle, - cursor_position: Point, - ) -> bool { - content_bounds.height > bounds.height - && scrollbar_bounds(bounds).contains(cursor_position) - } - - fn draw( - &mut self, - scrollable: &Scrollable<'_, Message, Self>, - bounds: Rectangle, - content: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - let content_bounds = content.bounds(); - - let offset = scrollable.state.offset(bounds, content_bounds); - let is_content_overflowing = content_bounds.height > bounds.height; - let scrollbar_bounds = scrollbar_bounds(bounds); - let is_mouse_over_scrollbar = self.is_mouse_over_scrollbar( - bounds, - content_bounds, - cursor_position, - ); - - let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { - Point::new(cursor_position.x, cursor_position.y + offset as f32) - } else { - Point::new(cursor_position.x, -1.0) - }; - - let (content, mouse_cursor) = - scrollable.content.draw(self, content, cursor_position); - - let clip = Primitive::Clip { - bounds, - offset, - content: Box::new(content), - }; - - ( - if is_content_overflowing - && (is_mouse_over || scrollable.state.is_scrollbar_grabbed()) - { - let ratio = bounds.height / content_bounds.height; - let scrollbar_height = bounds.height * ratio; - let y_offset = offset as f32 * ratio; - - let scrollbar = Primitive::Quad { - bounds: Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - y: scrollbar_bounds.y + y_offset, - width: scrollbar_bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - height: scrollbar_height, - }, - background: Background::Color(Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.7, - }), - border_radius: 5, - }; - - if is_mouse_over_scrollbar - || scrollable.state.is_scrollbar_grabbed() - { - let scrollbar_background = Primitive::Quad { - bounds: Rectangle { - x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), - width: scrollbar_bounds.width - - f32::from(2 * SCROLLBAR_MARGIN), - ..scrollbar_bounds - }, - background: Background::Color(Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 0.3, - }), - border_radius: 5, - }; - - Primitive::Group { - primitives: vec![clip, scrollbar_background, scrollbar], - } - } else { - Primitive::Group { - primitives: vec![clip, scrollbar], - } - } - } else { - clip - }, - if is_mouse_over_scrollbar - || scrollable.state.is_scrollbar_grabbed() - { - MouseCursor::Idle - } else { - mouse_cursor - }, - ) - } -} diff --git a/wgpu/src/renderer/slider.rs b/wgpu/src/renderer/slider.rs deleted file mode 100644 index 4ae3abc4..00000000 --- a/wgpu/src/renderer/slider.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{ - slider, Background, Color, Layout, Length, MouseCursor, Node, Point, - Rectangle, Slider, Style, -}; - -const HANDLE_WIDTH: f32 = 8.0; -const HANDLE_HEIGHT: f32 = 22.0; - -impl slider::Renderer for Renderer { - fn node(&self, slider: &Slider) -> Node { - let style = Style::default() - .width(slider.width) - .height(Length::Units(HANDLE_HEIGHT as u16)) - .min_width(Length::Units(100)); - - Node::new(style) - } - - fn draw( - &mut self, - slider: &Slider, - layout: Layout<'_>, - cursor_position: Point, - ) -> Self::Output { - let bounds = layout.bounds(); - - let is_mouse_over = bounds.contains(cursor_position); - - let rail_y = bounds.y + (bounds.height / 2.0).round(); - - let (rail_top, rail_bottom) = ( - Primitive::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y, - width: bounds.width, - height: 2.0, - }, - background: Background::Color(Color { - r: 0.6, - g: 0.6, - b: 0.6, - a: 1.0, - }), - border_radius: 0, - }, - Primitive::Quad { - bounds: Rectangle { - x: bounds.x, - y: rail_y + 2.0, - width: bounds.width, - height: 2.0, - }, - background: Background::Color(Color::WHITE), - border_radius: 0, - }, - ); - - let (range_start, range_end) = slider.range.clone().into_inner(); - - let handle_offset = (bounds.width - HANDLE_WIDTH) - * ((slider.value - range_start) - / (range_end - range_start).max(1.0)); - - let (handle_border, handle) = ( - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + handle_offset.round() - 1.0, - y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0, - width: HANDLE_WIDTH + 2.0, - height: HANDLE_HEIGHT + 2.0, - }, - background: Background::Color(Color { - r: 0.6, - g: 0.6, - b: 0.6, - a: 1.0, - }), - border_radius: 5, - }, - Primitive::Quad { - bounds: Rectangle { - x: bounds.x + handle_offset.round(), - y: rail_y - HANDLE_HEIGHT / 2.0, - width: HANDLE_WIDTH, - height: HANDLE_HEIGHT, - }, - background: Background::Color(if slider.state.is_dragging() { - Color { - r: 0.85, - g: 0.85, - b: 0.85, - a: 1.0, - } - } else if is_mouse_over { - Color { - r: 0.9, - g: 0.9, - b: 0.9, - a: 1.0, - } - } else { - Color { - r: 0.95, - g: 0.95, - b: 0.95, - a: 1.0, - } - }), - border_radius: 4, - }, - ); - - ( - Primitive::Group { - primitives: vec![rail_top, rail_bottom, handle_border, handle], - }, - if slider.state.is_dragging() { - MouseCursor::Grabbing - } else if is_mouse_over { - MouseCursor::Grab - } else { - MouseCursor::OutOfBounds - }, - ) - } -} diff --git a/wgpu/src/renderer/target.rs b/wgpu/src/renderer/target.rs new file mode 100644 index 00000000..d9d05bf0 --- /dev/null +++ b/wgpu/src/renderer/target.rs @@ -0,0 +1,74 @@ +use crate::{Renderer, Transformation}; + +use raw_window_handle::HasRawWindowHandle; + +pub struct Target { + surface: wgpu::Surface, + width: u16, + height: u16, + transformation: Transformation, + swap_chain: wgpu::SwapChain, +} + +impl Target { + pub fn dimensions(&self) -> (u16, u16) { + (self.width, self.height) + } + + pub fn transformation(&self) -> Transformation { + self.transformation + } + + pub fn next_frame(&mut self) -> wgpu::SwapChainOutput { + self.swap_chain.get_next_texture() + } +} + +impl iced_native::renderer::Target for Target { + type Renderer = Renderer; + + fn new( + window: &W, + width: u16, + height: u16, + renderer: &Renderer, + ) -> Target { + let surface = wgpu::Surface::create(window); + let swap_chain = + new_swap_chain(&surface, width, height, &renderer.device); + + Target { + surface, + width, + height, + transformation: Transformation::orthographic(width, height), + swap_chain, + } + } + + fn resize(&mut self, width: u16, height: u16, renderer: &Renderer) { + self.width = width; + self.height = height; + self.transformation = Transformation::orthographic(width, height); + self.swap_chain = + new_swap_chain(&self.surface, width, height, &renderer.device); + } +} + +fn new_swap_chain( + surface: &wgpu::Surface, + width: u16, + height: u16, + device: &wgpu::Device, +) -> wgpu::SwapChain { + device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: u32::from(width), + height: u32::from(height), + present_mode: wgpu::PresentMode::Vsync, + }, + ) +} diff --git a/wgpu/src/renderer/text.rs b/wgpu/src/renderer/text.rs deleted file mode 100644 index 82f75f09..00000000 --- a/wgpu/src/renderer/text.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{Primitive, Renderer}; -use iced_native::{text, Color, Layout, MouseCursor, Node, Style, Text}; - -use wgpu_glyph::{GlyphCruncher, Section}; - -use std::cell::RefCell; -use std::f32; - -impl text::Renderer for Renderer { - 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().ceil(), bounds.height().ceil()) - } 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::Output { - ( - Primitive::Text { - content: text.content.clone(), - size: f32::from(text.size.unwrap_or(20)), - bounds: layout.bounds(), - color: text.color.unwrap_or(Color::BLACK), - horizontal_alignment: text.horizontal_alignment, - vertical_alignment: text.vertical_alignment, - }, - MouseCursor::OutOfBounds, - ) - } -} diff --git a/wgpu/src/renderer/text_input.rs b/wgpu/src/renderer/text_input.rs deleted file mode 100644 index deb8eae7..00000000 --- a/wgpu/src/renderer/text_input.rs +++ /dev/null @@ -1,162 +0,0 @@ -use crate::{Primitive, Renderer}; - -use iced_native::{ - text::HorizontalAlignment, text::VerticalAlignment, text_input, Background, - Color, MouseCursor, Point, Rectangle, TextInput, -}; -use std::f32; - -impl text_input::Renderer for Renderer { - fn default_size(&self) -> u16 { - // TODO: Make this configurable - 20 - } - - fn draw( - &mut self, - text_input: &TextInput, - bounds: Rectangle, - text_bounds: Rectangle, - cursor_position: Point, - ) -> Self::Output { - let is_mouse_over = bounds.contains(cursor_position); - - let border = Primitive::Quad { - bounds, - background: Background::Color( - if is_mouse_over || text_input.state.is_focused { - Color { - r: 0.5, - g: 0.5, - b: 0.5, - a: 1.0, - } - } else { - Color { - r: 0.7, - g: 0.7, - b: 0.7, - a: 1.0, - } - }, - ), - border_radius: 5, - }; - - let input = Primitive::Quad { - bounds: Rectangle { - x: bounds.x + 1.0, - y: bounds.y + 1.0, - width: bounds.width - 2.0, - height: bounds.height - 2.0, - }, - background: Background::Color(Color::WHITE), - border_radius: 5, - }; - - let size = f32::from(text_input.size.unwrap_or(self.default_size())); - let text = text_input.value.to_string(); - - let value = Primitive::Text { - content: if text.is_empty() { - text_input.placeholder.clone() - } else { - text.clone() - }, - color: if text.is_empty() { - Color { - r: 0.7, - g: 0.7, - b: 0.7, - a: 1.0, - } - } else { - Color { - r: 0.3, - g: 0.3, - b: 0.3, - a: 1.0, - } - }, - bounds: Rectangle { - width: f32::INFINITY, - ..text_bounds - }, - size, - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Center, - }; - - let content = Primitive::Clip { - bounds: text_bounds, - offset: 0, - content: Box::new(if text_input.state.is_focused { - use wgpu_glyph::{GlyphCruncher, Scale, Section}; - - let text_before_cursor = &text_input - .value - .until(text_input.state.cursor_position(&text_input.value)) - .to_string(); - - let mut text_value_width = self - .glyph_brush - .borrow_mut() - .glyph_bounds(Section { - text: text_before_cursor, - bounds: (f32::INFINITY, text_bounds.height), - scale: Scale { x: size, y: size }, - ..Default::default() - }) - .map(|bounds| bounds.width().round()) - .unwrap_or(0.0); - - let spaces_at_the_end = text_before_cursor.len() - - text_before_cursor.trim_end().len(); - - if spaces_at_the_end > 0 { - let space_width = { - let glyph_brush = self.glyph_brush.borrow(); - - // TODO: Select appropriate font - let font = &glyph_brush.fonts()[0]; - - font.glyph(' ') - .scaled(Scale { x: size, y: size }) - .h_metrics() - .advance_width - }; - - text_value_width += spaces_at_the_end as f32 * space_width; - } - - let cursor = Primitive::Quad { - bounds: Rectangle { - x: text_bounds.x + text_value_width, - y: text_bounds.y, - width: 1.0, - height: text_bounds.height, - }, - background: Background::Color(Color::BLACK), - border_radius: 0, - }; - - Primitive::Group { - primitives: vec![value, cursor], - } - } else { - value - }), - }; - - ( - Primitive::Group { - primitives: vec![border, input, content], - }, - if is_mouse_over { - MouseCursor::Text - } else { - MouseCursor::OutOfBounds - }, - ) - } -} diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs new file mode 100644 index 00000000..52410bee --- /dev/null +++ b/wgpu/src/renderer/widget.rs @@ -0,0 +1,10 @@ +mod button; +mod checkbox; +mod column; +mod image; +mod radio; +mod row; +mod scrollable; +mod slider; +mod text; +mod text_input; diff --git a/wgpu/src/renderer/widget/button.rs b/wgpu/src/renderer/widget/button.rs new file mode 100644 index 00000000..ad2186d6 --- /dev/null +++ b/wgpu/src/renderer/widget/button.rs @@ -0,0 +1,86 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + button, Align, Background, Button, Color, Layout, Length, MouseCursor, + Node, Point, Rectangle, Style, +}; + +impl button::Renderer for Renderer { + fn node(&self, button: &Button) -> Node { + let style = Style::default() + .width(button.width) + .padding(button.padding) + .min_width(Length::Units(100)) + .align_self(button.align_self) + .align_items(Align::Stretch); + + Node::with_children(style, vec![button.content.node(self)]) + } + + fn draw( + &mut self, + button: &Button, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let bounds = layout.bounds(); + + let (content, _) = button.content.draw( + self, + layout.children().next().unwrap(), + cursor_position, + ); + + let is_mouse_over = bounds.contains(cursor_position); + + // TODO: Render proper shadows + // TODO: Make hovering and pressed styles configurable + let shadow_offset = if is_mouse_over { + if button.state.is_pressed { + 0.0 + } else { + 2.0 + } + } else { + 1.0 + }; + + ( + Primitive::Group { + primitives: vec![ + Primitive::Quad { + bounds: Rectangle { + x: bounds.x + 1.0, + y: bounds.y + shadow_offset, + ..bounds + }, + background: Background::Color(Color { + r: 0.0, + b: 0.0, + g: 0.0, + a: 0.5, + }), + border_radius: button.border_radius, + }, + Primitive::Quad { + bounds, + background: button.background.unwrap_or( + Background::Color(Color { + r: 0.8, + b: 0.8, + g: 0.8, + a: 1.0, + }), + ), + border_radius: button.border_radius, + }, + content, + ], + }, + if is_mouse_over { + MouseCursor::Pointer + } else { + MouseCursor::OutOfBounds + }, + ) + } +} diff --git a/wgpu/src/renderer/widget/checkbox.rs b/wgpu/src/renderer/widget/checkbox.rs new file mode 100644 index 00000000..ea7a4c0b --- /dev/null +++ b/wgpu/src/renderer/widget/checkbox.rs @@ -0,0 +1,107 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + checkbox, text, text::HorizontalAlignment, text::VerticalAlignment, Align, + Background, Checkbox, Color, Column, Layout, Length, MouseCursor, Node, + Point, Rectangle, Row, Text, Widget, +}; + +const SIZE: f32 = 28.0; + +impl checkbox::Renderer for Renderer { + fn node(&self, checkbox: &Checkbox) -> Node { + Row::<(), Self>::new() + .width(Length::Fill) + .spacing(15) + .align_items(Align::Center) + .push( + Column::new() + .width(Length::Units(SIZE as u16)) + .height(Length::Units(SIZE as u16)), + ) + .push(Text::new(&checkbox.label)) + .node(self) + } + + fn draw( + &mut self, + checkbox: &Checkbox, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let bounds = layout.bounds(); + let mut children = layout.children(); + + let checkbox_layout = children.next().unwrap(); + let label_layout = children.next().unwrap(); + let checkbox_bounds = checkbox_layout.bounds(); + + let (label, _) = text::Renderer::draw( + self, + &Text::new(&checkbox.label), + label_layout, + ); + + let is_mouse_over = bounds.contains(cursor_position); + + let (checkbox_border, checkbox_box) = ( + Primitive::Quad { + bounds: checkbox_bounds, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: 6, + }, + Primitive::Quad { + bounds: Rectangle { + x: checkbox_bounds.x + 1.0, + y: checkbox_bounds.y + 1.0, + width: checkbox_bounds.width - 2.0, + height: checkbox_bounds.height - 2.0, + }, + background: Background::Color(if is_mouse_over { + Color { + r: 0.90, + g: 0.90, + b: 0.90, + a: 1.0, + } + } else { + Color { + r: 0.95, + g: 0.95, + b: 0.95, + a: 1.0, + } + }), + border_radius: 6, + }, + ); + + ( + Primitive::Group { + primitives: if checkbox.is_checked { + // TODO: Draw an actual icon + let (check, _) = text::Renderer::draw( + self, + &Text::new("X") + .horizontal_alignment(HorizontalAlignment::Center) + .vertical_alignment(VerticalAlignment::Center), + checkbox_layout, + ); + + vec![checkbox_border, checkbox_box, check, label] + } else { + vec![checkbox_border, checkbox_box, label] + }, + }, + if is_mouse_over { + MouseCursor::Pointer + } else { + MouseCursor::OutOfBounds + }, + ) + } +} diff --git a/wgpu/src/renderer/widget/column.rs b/wgpu/src/renderer/widget/column.rs new file mode 100644 index 00000000..cac6da77 --- /dev/null +++ b/wgpu/src/renderer/widget/column.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{column, Column, Layout, MouseCursor, Point}; + +impl column::Renderer for Renderer { + fn draw( + &mut self, + column: &Column<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: column + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_cursor) = + child.draw(self, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) + } +} diff --git a/wgpu/src/renderer/widget/image.rs b/wgpu/src/renderer/widget/image.rs new file mode 100644 index 00000000..0e312706 --- /dev/null +++ b/wgpu/src/renderer/widget/image.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{image, Image, Layout, Length, MouseCursor, Node, Style}; + +impl image::Renderer for Renderer { + fn node(&self, image: &Image) -> Node { + let (width, height) = self.image_pipeline.dimensions(&image.path); + + let aspect_ratio = width as f32 / height as f32; + + let mut style = Style::default().align_self(image.align_self); + + // TODO: Deal with additional cases + style = match (image.width, image.height) { + (Length::Units(width), _) => style.width(image.width).height( + Length::Units((width as f32 / aspect_ratio).round() as u16), + ), + (_, _) => style + .width(Length::Units(width as u16)) + .height(Length::Units(height as u16)), + }; + + Node::new(style) + } + + fn draw(&mut self, image: &Image, layout: Layout<'_>) -> Self::Output { + ( + Primitive::Image { + path: image.path.clone(), + bounds: layout.bounds(), + }, + MouseCursor::OutOfBounds, + ) + } +} diff --git a/wgpu/src/renderer/widget/radio.rs b/wgpu/src/renderer/widget/radio.rs new file mode 100644 index 00000000..97b4f70e --- /dev/null +++ b/wgpu/src/renderer/widget/radio.rs @@ -0,0 +1,109 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + radio, text, Align, Background, Color, Column, Layout, Length, MouseCursor, + Node, Point, Radio, Rectangle, Row, Text, Widget, +}; + +const SIZE: f32 = 28.0; +const DOT_SIZE: f32 = SIZE / 2.0; + +impl radio::Renderer for Renderer { + fn node(&self, radio: &Radio) -> Node { + Row::<(), Self>::new() + .spacing(15) + .align_items(Align::Center) + .push( + Column::new() + .width(Length::Units(SIZE as u16)) + .height(Length::Units(SIZE as u16)), + ) + .push(Text::new(&radio.label)) + .node(self) + } + + fn draw( + &mut self, + radio: &Radio, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let bounds = layout.bounds(); + let mut children = layout.children(); + + let radio_bounds = children.next().unwrap().bounds(); + let label_layout = children.next().unwrap(); + + let (label, _) = + text::Renderer::draw(self, &Text::new(&radio.label), label_layout); + + let is_mouse_over = bounds.contains(cursor_position); + + let (radio_border, radio_box) = ( + Primitive::Quad { + bounds: radio_bounds, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: (SIZE / 2.0) as u16, + }, + Primitive::Quad { + bounds: Rectangle { + x: radio_bounds.x + 1.0, + y: radio_bounds.y + 1.0, + width: radio_bounds.width - 2.0, + height: radio_bounds.height - 2.0, + }, + background: Background::Color(if is_mouse_over { + Color { + r: 0.90, + g: 0.90, + b: 0.90, + a: 1.0, + } + } else { + Color { + r: 0.95, + g: 0.95, + b: 0.95, + a: 1.0, + } + }), + border_radius: (SIZE / 2.0 - 1.0) as u16, + }, + ); + + ( + Primitive::Group { + primitives: if radio.is_selected { + let radio_circle = Primitive::Quad { + bounds: Rectangle { + x: radio_bounds.x + DOT_SIZE / 2.0, + y: radio_bounds.y + DOT_SIZE / 2.0, + width: radio_bounds.width - DOT_SIZE, + height: radio_bounds.height - DOT_SIZE, + }, + background: Background::Color(Color { + r: 0.30, + g: 0.30, + b: 0.30, + a: 1.0, + }), + border_radius: (DOT_SIZE / 2.0) as u16, + }; + + vec![radio_border, radio_box, radio_circle, label] + } else { + vec![radio_border, radio_box, label] + }, + }, + if is_mouse_over { + MouseCursor::Pointer + } else { + MouseCursor::OutOfBounds + }, + ) + } +} diff --git a/wgpu/src/renderer/widget/row.rs b/wgpu/src/renderer/widget/row.rs new file mode 100644 index 00000000..bbfef9a1 --- /dev/null +++ b/wgpu/src/renderer/widget/row.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{row, Layout, MouseCursor, Point, Row}; + +impl row::Renderer for Renderer { + fn draw( + &mut self, + row: &Row<'_, Message, Self>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: row + .children + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_cursor) = + child.draw(self, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) + } +} diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs new file mode 100644 index 00000000..72d77cc8 --- /dev/null +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -0,0 +1,129 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + scrollable, Background, Color, Layout, MouseCursor, Point, Rectangle, + Scrollable, Widget, +}; + +const SCROLLBAR_WIDTH: u16 = 10; +const SCROLLBAR_MARGIN: u16 = 2; + +fn scrollbar_bounds(bounds: Rectangle) -> Rectangle { + Rectangle { + x: bounds.x + bounds.width + - f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + y: bounds.y, + width: f32::from(SCROLLBAR_WIDTH + 2 * SCROLLBAR_MARGIN), + height: bounds.height, + } +} + +impl scrollable::Renderer for Renderer { + fn is_mouse_over_scrollbar( + &self, + bounds: Rectangle, + content_bounds: Rectangle, + cursor_position: Point, + ) -> bool { + content_bounds.height > bounds.height + && scrollbar_bounds(bounds).contains(cursor_position) + } + + fn draw( + &mut self, + scrollable: &Scrollable<'_, Message, Self>, + bounds: Rectangle, + content: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + let content_bounds = content.bounds(); + + let offset = scrollable.state.offset(bounds, content_bounds); + let is_content_overflowing = content_bounds.height > bounds.height; + let scrollbar_bounds = scrollbar_bounds(bounds); + let is_mouse_over_scrollbar = self.is_mouse_over_scrollbar( + bounds, + content_bounds, + cursor_position, + ); + + let cursor_position = if is_mouse_over && !is_mouse_over_scrollbar { + Point::new(cursor_position.x, cursor_position.y + offset as f32) + } else { + Point::new(cursor_position.x, -1.0) + }; + + let (content, mouse_cursor) = + scrollable.content.draw(self, content, cursor_position); + + let clip = Primitive::Clip { + bounds, + offset, + content: Box::new(content), + }; + + ( + if is_content_overflowing + && (is_mouse_over || scrollable.state.is_scrollbar_grabbed()) + { + let ratio = bounds.height / content_bounds.height; + let scrollbar_height = bounds.height * ratio; + let y_offset = offset as f32 * ratio; + + let scrollbar = Primitive::Quad { + bounds: Rectangle { + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + y: scrollbar_bounds.y + y_offset, + width: scrollbar_bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + height: scrollbar_height, + }, + background: Background::Color(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.7, + }), + border_radius: 5, + }; + + if is_mouse_over_scrollbar + || scrollable.state.is_scrollbar_grabbed() + { + let scrollbar_background = Primitive::Quad { + bounds: Rectangle { + x: scrollbar_bounds.x + f32::from(SCROLLBAR_MARGIN), + width: scrollbar_bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar_bounds + }, + background: Background::Color(Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 0.3, + }), + border_radius: 5, + }; + + Primitive::Group { + primitives: vec![clip, scrollbar_background, scrollbar], + } + } else { + Primitive::Group { + primitives: vec![clip, scrollbar], + } + } + } else { + clip + }, + if is_mouse_over_scrollbar + || scrollable.state.is_scrollbar_grabbed() + { + MouseCursor::Idle + } else { + mouse_cursor + }, + ) + } +} diff --git a/wgpu/src/renderer/widget/slider.rs b/wgpu/src/renderer/widget/slider.rs new file mode 100644 index 00000000..4ae3abc4 --- /dev/null +++ b/wgpu/src/renderer/widget/slider.rs @@ -0,0 +1,128 @@ +use crate::{Primitive, Renderer}; +use iced_native::{ + slider, Background, Color, Layout, Length, MouseCursor, Node, Point, + Rectangle, Slider, Style, +}; + +const HANDLE_WIDTH: f32 = 8.0; +const HANDLE_HEIGHT: f32 = 22.0; + +impl slider::Renderer for Renderer { + fn node(&self, slider: &Slider) -> Node { + let style = Style::default() + .width(slider.width) + .height(Length::Units(HANDLE_HEIGHT as u16)) + .min_width(Length::Units(100)); + + Node::new(style) + } + + fn draw( + &mut self, + slider: &Slider, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let bounds = layout.bounds(); + + let is_mouse_over = bounds.contains(cursor_position); + + let rail_y = bounds.y + (bounds.height / 2.0).round(); + + let (rail_top, rail_bottom) = ( + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: 0, + }, + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y + 2.0, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(Color::WHITE), + border_radius: 0, + }, + ); + + let (range_start, range_end) = slider.range.clone().into_inner(); + + let handle_offset = (bounds.width - HANDLE_WIDTH) + * ((slider.value - range_start) + / (range_end - range_start).max(1.0)); + + let (handle_border, handle) = ( + Primitive::Quad { + bounds: Rectangle { + x: bounds.x + handle_offset.round() - 1.0, + y: rail_y - HANDLE_HEIGHT / 2.0 - 1.0, + width: HANDLE_WIDTH + 2.0, + height: HANDLE_HEIGHT + 2.0, + }, + background: Background::Color(Color { + r: 0.6, + g: 0.6, + b: 0.6, + a: 1.0, + }), + border_radius: 5, + }, + Primitive::Quad { + bounds: Rectangle { + x: bounds.x + handle_offset.round(), + y: rail_y - HANDLE_HEIGHT / 2.0, + width: HANDLE_WIDTH, + height: HANDLE_HEIGHT, + }, + background: Background::Color(if slider.state.is_dragging() { + Color { + r: 0.85, + g: 0.85, + b: 0.85, + a: 1.0, + } + } else if is_mouse_over { + Color { + r: 0.9, + g: 0.9, + b: 0.9, + a: 1.0, + } + } else { + Color { + r: 0.95, + g: 0.95, + b: 0.95, + a: 1.0, + } + }), + border_radius: 4, + }, + ); + + ( + Primitive::Group { + primitives: vec![rail_top, rail_bottom, handle_border, handle], + }, + if slider.state.is_dragging() { + MouseCursor::Grabbing + } else if is_mouse_over { + MouseCursor::Grab + } else { + MouseCursor::OutOfBounds + }, + ) + } +} diff --git a/wgpu/src/renderer/widget/text.rs b/wgpu/src/renderer/widget/text.rs new file mode 100644 index 00000000..82f75f09 --- /dev/null +++ b/wgpu/src/renderer/widget/text.rs @@ -0,0 +1,83 @@ +use crate::{Primitive, Renderer}; +use iced_native::{text, Color, Layout, MouseCursor, Node, Style, Text}; + +use wgpu_glyph::{GlyphCruncher, Section}; + +use std::cell::RefCell; +use std::f32; + +impl text::Renderer for Renderer { + 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().ceil(), bounds.height().ceil()) + } 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::Output { + ( + Primitive::Text { + content: text.content.clone(), + size: f32::from(text.size.unwrap_or(20)), + bounds: layout.bounds(), + color: text.color.unwrap_or(Color::BLACK), + horizontal_alignment: text.horizontal_alignment, + vertical_alignment: text.vertical_alignment, + }, + MouseCursor::OutOfBounds, + ) + } +} diff --git a/wgpu/src/renderer/widget/text_input.rs b/wgpu/src/renderer/widget/text_input.rs new file mode 100644 index 00000000..deb8eae7 --- /dev/null +++ b/wgpu/src/renderer/widget/text_input.rs @@ -0,0 +1,162 @@ +use crate::{Primitive, Renderer}; + +use iced_native::{ + text::HorizontalAlignment, text::VerticalAlignment, text_input, Background, + Color, MouseCursor, Point, Rectangle, TextInput, +}; +use std::f32; + +impl text_input::Renderer for Renderer { + fn default_size(&self) -> u16 { + // TODO: Make this configurable + 20 + } + + fn draw( + &mut self, + text_input: &TextInput, + bounds: Rectangle, + text_bounds: Rectangle, + cursor_position: Point, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let border = Primitive::Quad { + bounds, + background: Background::Color( + if is_mouse_over || text_input.state.is_focused { + Color { + r: 0.5, + g: 0.5, + b: 0.5, + a: 1.0, + } + } else { + Color { + r: 0.7, + g: 0.7, + b: 0.7, + a: 1.0, + } + }, + ), + border_radius: 5, + }; + + let input = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + 1.0, + y: bounds.y + 1.0, + width: bounds.width - 2.0, + height: bounds.height - 2.0, + }, + background: Background::Color(Color::WHITE), + border_radius: 5, + }; + + let size = f32::from(text_input.size.unwrap_or(self.default_size())); + let text = text_input.value.to_string(); + + let value = Primitive::Text { + content: if text.is_empty() { + text_input.placeholder.clone() + } else { + text.clone() + }, + color: if text.is_empty() { + Color { + r: 0.7, + g: 0.7, + b: 0.7, + a: 1.0, + } + } else { + Color { + r: 0.3, + g: 0.3, + b: 0.3, + a: 1.0, + } + }, + bounds: Rectangle { + width: f32::INFINITY, + ..text_bounds + }, + size, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; + + let content = Primitive::Clip { + bounds: text_bounds, + offset: 0, + content: Box::new(if text_input.state.is_focused { + use wgpu_glyph::{GlyphCruncher, Scale, Section}; + + let text_before_cursor = &text_input + .value + .until(text_input.state.cursor_position(&text_input.value)) + .to_string(); + + let mut text_value_width = self + .glyph_brush + .borrow_mut() + .glyph_bounds(Section { + text: text_before_cursor, + bounds: (f32::INFINITY, text_bounds.height), + scale: Scale { x: size, y: size }, + ..Default::default() + }) + .map(|bounds| bounds.width().round()) + .unwrap_or(0.0); + + let spaces_at_the_end = text_before_cursor.len() + - text_before_cursor.trim_end().len(); + + if spaces_at_the_end > 0 { + let space_width = { + let glyph_brush = self.glyph_brush.borrow(); + + // TODO: Select appropriate font + let font = &glyph_brush.fonts()[0]; + + font.glyph(' ') + .scaled(Scale { x: size, y: size }) + .h_metrics() + .advance_width + }; + + text_value_width += spaces_at_the_end as f32 * space_width; + } + + let cursor = Primitive::Quad { + bounds: Rectangle { + x: text_bounds.x + text_value_width, + y: text_bounds.y, + width: 1.0, + height: text_bounds.height, + }, + background: Background::Color(Color::BLACK), + border_radius: 0, + }; + + Primitive::Group { + primitives: vec![value, cursor], + } + } else { + value + }), + }; + + ( + Primitive::Group { + primitives: vec![border, input, content], + }, + if is_mouse_over { + MouseCursor::Text + } else { + MouseCursor::OutOfBounds + }, + ) + } +} -- cgit From 2c6bfdbc8c2262c3550fa16d4472e29d73077956 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Nov 2019 04:39:11 +0100 Subject: Implement debug view and load system fonts --- Cargo.toml | 4 + examples/resources/Roboto-LICENSE | 202 --------------------------------- examples/resources/Roboto-Regular.ttf | Bin 171272 -> 0 bytes examples/resources/ui.png | Bin 16691 -> 0 bytes native/src/lib.rs | 2 - native/src/metrics.rs | 11 -- native/src/renderer/windowed.rs | 6 +- src/lib.rs | 2 +- wgpu/Cargo.toml | 1 + wgpu/src/font.rs | 38 +++++++ wgpu/src/lib.rs | 1 + wgpu/src/renderer.rs | 64 +++++++++-- winit/Cargo.toml | 3 + winit/src/application.rs | 60 ++++++++-- winit/src/debug/basic.rs | 206 ++++++++++++++++++++++++++++++++++ winit/src/debug/null.rs | 48 ++++++++ winit/src/lib.rs | 11 ++ 17 files changed, 418 insertions(+), 241 deletions(-) delete mode 100644 examples/resources/Roboto-LICENSE delete mode 100644 examples/resources/Roboto-Regular.ttf delete mode 100644 examples/resources/ui.png delete mode 100644 native/src/metrics.rs create mode 100644 wgpu/src/font.rs create mode 100644 winit/src/debug/basic.rs create mode 100644 winit/src/debug/null.rs diff --git a/Cargo.toml b/Cargo.toml index e8b53066..8f9769b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,10 @@ readme = "README.md" keywords = ["gui", "ui", "graphics", "interface", "widgets"] categories = ["gui"] +[features] +# Enables a debug view in native platforms (press F12) +debug = ["iced_winit/debug"] + [badges] maintenance = { status = "actively-developed" } diff --git a/examples/resources/Roboto-LICENSE b/examples/resources/Roboto-LICENSE deleted file mode 100644 index 75b52484..00000000 --- a/examples/resources/Roboto-LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/examples/resources/Roboto-Regular.ttf b/examples/resources/Roboto-Regular.ttf deleted file mode 100644 index 2b6392ff..00000000 Binary files a/examples/resources/Roboto-Regular.ttf and /dev/null differ diff --git a/examples/resources/ui.png b/examples/resources/ui.png deleted file mode 100644 index 4fd3beb3..00000000 Binary files a/examples/resources/ui.png and /dev/null differ diff --git a/native/src/lib.rs b/native/src/lib.rs index 86bfb3ca..fa72a553 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -206,7 +206,6 @@ mod element; mod event; mod hasher; mod layout; -mod metrics; mod mouse_cursor; mod node; mod style; @@ -225,7 +224,6 @@ pub use element::Element; pub use event::Event; pub use hasher::Hasher; pub use layout::Layout; -pub use metrics::Metrics; pub use mouse_cursor::MouseCursor; pub use node::Node; pub use renderer::Renderer; diff --git a/native/src/metrics.rs b/native/src/metrics.rs deleted file mode 100644 index 7c344b9b..00000000 --- a/native/src/metrics.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::time; - -/// A bunch of metrics about an Iced application. -#[derive(Debug, Clone, Copy)] -pub struct Metrics { - pub startup_time: time::Duration, - pub update_time: time::Duration, - pub view_time: time::Duration, - pub renderer_output_time: time::Duration, - pub message_count: usize, -} diff --git a/native/src/renderer/windowed.rs b/native/src/renderer/windowed.rs index 0499ca70..6e4ae611 100644 --- a/native/src/renderer/windowed.rs +++ b/native/src/renderer/windowed.rs @@ -1,4 +1,4 @@ -use crate::{Metrics, MouseCursor}; +use crate::MouseCursor; use raw_window_handle::HasRawWindowHandle; @@ -7,10 +7,10 @@ pub trait Windowed: super::Renderer + Sized { fn new() -> Self; - fn draw( + fn draw>( &mut self, output: &Self::Output, - metrics: Option, + overlay: &[T], target: &mut Self::Target, ) -> MouseCursor; } diff --git a/src/lib.rs b/src/lib.rs index 1bcdada2..da108804 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ mod platform; pub use platform::*; pub trait Application { - type Message; + type Message: std::fmt::Debug; fn update(&mut self, message: Self::Message); diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 04fae248..2c212286 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -14,4 +14,5 @@ wgpu_glyph = { version = "0.4", git = "https://github.com/hecrj/wgpu_glyph", rev raw-window-handle = "0.3" image = "0.22" glam = "0.8" +font-kit = "0.4" log = "0.4" diff --git a/wgpu/src/font.rs b/wgpu/src/font.rs new file mode 100644 index 00000000..e2838268 --- /dev/null +++ b/wgpu/src/font.rs @@ -0,0 +1,38 @@ +pub use font_kit::family_name::FamilyName as Family; + +pub struct Source { + raw: font_kit::sources::fontconfig::FontconfigSource, +} + +impl Source { + pub fn new() -> Self { + Source { + raw: font_kit::sources::fontconfig::FontconfigSource::new(), + } + } + + pub fn load(&self, families: &[Family]) -> Vec { + let font = self + .raw + .select_best_match( + families, + &font_kit::properties::Properties::default(), + ) + .expect("Find font"); + + match font { + font_kit::handle::Handle::Path { path, .. } => { + use std::io::Read; + + let mut buf = Vec::new(); + let mut reader = std::fs::File::open(path).expect("Read font"); + let _ = reader.read_to_end(&mut buf); + + buf + } + font_kit::handle::Handle::Memory { bytes, .. } => { + bytes.as_ref().clone() + } + } + } +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 01dc4c20..f504897d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1,3 +1,4 @@ +mod font; mod image; mod primitive; mod quad; diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index 1988dfae..770430ad 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -1,6 +1,6 @@ -use crate::{quad, Image, Primitive, Quad, Transformation}; +use crate::{font, quad, Image, Primitive, Quad, Transformation}; use iced_native::{ - renderer::Debugger, renderer::Windowed, Background, Color, Layout, Metrics, + renderer::Debugger, renderer::Windowed, Background, Color, Layout, MouseCursor, Point, Rectangle, Widget, }; @@ -62,13 +62,16 @@ impl Renderer { limits: Limits { max_bind_groups: 2 }, }); - // TODO: Think about font loading strategy - // Loading system fonts with fallback may be a good idea - let font: &[u8] = - include_bytes!("../../examples/resources/Roboto-Regular.ttf"); + // TODO: Font customization + let font_source = font::Source::new(); + let sans_serif_font = font_source.load(&[font::Family::SansSerif]); + let mono_font = font_source.load(&[font::Family::Monospace]); - let glyph_brush = GlyphBrushBuilder::using_font_bytes(font) - .build(&mut device, TextureFormat::Bgra8UnormSrgb); + let glyph_brush = GlyphBrushBuilder::using_fonts_bytes(vec![ + sans_serif_font, + mono_font, + ]) + .build(&mut device, TextureFormat::Bgra8UnormSrgb); let quad_pipeline = quad::Pipeline::new(&mut device); let image_pipeline = crate::image::Pipeline::new(&mut device); @@ -83,9 +86,10 @@ impl Renderer { } } - fn draw( + fn draw>( &mut self, (primitive, mouse_cursor): &(Primitive, MouseCursor), + overlay: &[T], target: &mut Target, ) -> MouseCursor { log::debug!("Drawing"); @@ -127,6 +131,7 @@ impl Renderer { )); self.draw_primitive(primitive, &mut layers); + self.draw_overlay(overlay, &mut layers); for layer in layers { self.flush(transformation, &layer, &mut encoder, &frame.view); @@ -260,6 +265,41 @@ impl Renderer { } } + fn draw_overlay<'a, T: AsRef>( + &mut self, + lines: &'a [T], + layers: &mut Vec>, + ) { + let first = layers.first().unwrap(); + let mut overlay = Layer::new(first.bounds, 0); + + let font_id = + wgpu_glyph::FontId(self.glyph_brush.borrow().fonts().len() - 1); + let scale = wgpu_glyph::Scale { x: 20.0, y: 20.0 }; + + for (i, line) in lines.iter().enumerate() { + overlay.text.push(Section { + text: line.as_ref(), + screen_position: (11.0, 11.0 + 25.0 * i as f32), + color: [0.9, 0.9, 0.9, 1.0], + scale, + font_id, + ..Section::default() + }); + + overlay.text.push(Section { + text: line.as_ref(), + screen_position: (10.0, 10.0 + 25.0 * i as f32), + color: [0.0, 0.0, 0.0, 1.0], + scale, + font_id, + ..Section::default() + }); + } + + layers.push(overlay); + } + fn flush( &mut self, transformation: Transformation, @@ -328,13 +368,13 @@ impl Windowed for Renderer { Self::new() } - fn draw( + fn draw>( &mut self, output: &Self::Output, - metrics: Option, + overlay: &[T], target: &mut Target, ) -> MouseCursor { - self.draw(output, target) + self.draw(output, overlay, target) } } diff --git a/winit/Cargo.toml b/winit/Cargo.toml index c8227ac4..2831ba2f 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -7,6 +7,9 @@ description = "A winit runtime for Iced" license = "MIT" repository = "https://github.com/hecrj/iced" +[features] +debug = [] + [dependencies] iced_native = { version = "0.1.0-alpha", path = "../native" } winit = { version = "0.20.0-alpha3", git = "https://github.com/rust-windowing/winit", rev = "709808eb4e69044705fcb214bcc30556db761405"} diff --git a/winit/src/application.rs b/winit/src/application.rs index 8c7d8c37..5d1aae38 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -2,13 +2,13 @@ use crate::{ column, conversion, input::{keyboard, mouse}, renderer::{Target, Windowed}, - Cache, Column, Element, Event, Length, MouseCursor, UserInterface, + Cache, Column, Debug, Element, Event, Length, MouseCursor, UserInterface, }; pub trait Application { type Renderer: Windowed + column::Renderer; - type Message; + type Message: std::fmt::Debug; fn update(&mut self, message: Self::Message); @@ -24,6 +24,9 @@ pub trait Application { window::WindowBuilder, }; + let mut debug = Debug::new(); + + debug.startup_started(); let event_loop = EventLoop::new(); // TODO: Ask for window settings and configure this properly @@ -50,16 +53,22 @@ pub trait Application { &renderer, ); + debug.layout_started(); let user_interface = UserInterface::build( - document(&mut self, size), + document(&mut self, size, &mut debug), Cache::default(), &renderer, ); + debug.layout_finished(); + debug.draw_started(); let mut primitive = user_interface.draw(&mut renderer); + debug.draw_finished(); + let mut cache = Some(user_interface.into_cache()); let mut events = Vec::new(); let mut mouse_cursor = MouseCursor::OutOfBounds; + debug.startup_finished(); window.request_redraw(); @@ -70,17 +79,23 @@ pub trait Application { // // This will allow us to rebuild it only when a message is // handled. + debug.layout_started(); let mut user_interface = UserInterface::build( - document(&mut self, size), + document(&mut self, size, &mut debug), cache.take().unwrap(), &renderer, ); + debug.layout_finished(); + debug.event_processing_started(); let messages = user_interface.update(&renderer, events.drain(..)); + debug.event_processing_finished(); if messages.is_empty() { + debug.draw_started(); primitive = user_interface.draw(&mut renderer); + debug.draw_finished(); cache = Some(user_interface.into_cache()); } else { @@ -91,16 +106,24 @@ pub trait Application { for message in messages { log::debug!("Updating"); + debug.log_message(&message); + + debug.update_started(); self.update(message); + debug.update_finished(); } + debug.layout_started(); let user_interface = UserInterface::build( - document(&mut self, size), + document(&mut self, size, &mut debug), temp_cache, &renderer, ); + debug.layout_finished(); + debug.draw_started(); primitive = user_interface.draw(&mut renderer); + debug.draw_finished(); cache = Some(user_interface.into_cache()); } @@ -108,6 +131,8 @@ pub trait Application { window.request_redraw(); } event::Event::RedrawRequested(_) => { + debug.render_started(); + if let Some(new_size) = new_size.take() { target.resize(new_size.width, new_size.height, &renderer); @@ -115,7 +140,9 @@ pub trait Application { } let new_mouse_cursor = - renderer.draw(&primitive, None, &mut target); + renderer.draw(&primitive, &debug.overlay(), &mut target); + + debug.render_finished(); if new_mouse_cursor != mouse_cursor { window.set_cursor_icon(conversion::mouse_cursor( @@ -191,6 +218,14 @@ pub trait Application { }, .. } => { + match (virtual_keycode, state) { + ( + winit::event::VirtualKeyCode::F12, + winit::event::ElementState::Pressed, + ) => debug.toggle(), + _ => {} + } + events.push(Event::Keyboard(keyboard::Event::Input { key_code: conversion::key_code(virtual_keycode), state: conversion::button_state(state), @@ -229,17 +264,22 @@ impl From for Size { } } -fn document( - application: &mut Application, +fn document<'a, Application>( + application: &'a mut Application, size: Size, -) -> Element + debug: &mut Debug, +) -> Element<'a, Application::Message, Application::Renderer> where Application: self::Application, Application::Message: 'static, { + debug.view_started(); + let view = application.view(); + debug.view_finished(); + Column::new() .width(Length::Units(size.width)) .height(Length::Units(size.height)) - .push(application.view()) + .push(view) .into() } diff --git a/winit/src/debug/basic.rs b/winit/src/debug/basic.rs new file mode 100644 index 00000000..09bb5ae1 --- /dev/null +++ b/winit/src/debug/basic.rs @@ -0,0 +1,206 @@ +use std::collections::VecDeque; +use std::time; + +#[derive(Debug)] +pub struct Debug { + is_enabled: bool, + + startup_start: time::Instant, + startup_duration: time::Duration, + + update_start: time::Instant, + update_durations: TimeBuffer, + + view_start: time::Instant, + view_durations: TimeBuffer, + + layout_start: time::Instant, + layout_durations: TimeBuffer, + + event_start: time::Instant, + event_durations: TimeBuffer, + + draw_start: time::Instant, + draw_durations: TimeBuffer, + + render_start: time::Instant, + render_durations: TimeBuffer, + + message_count: usize, + last_messages: VecDeque, +} + +impl Debug { + pub fn new() -> Self { + let now = time::Instant::now(); + + Self { + is_enabled: false, + startup_start: now, + startup_duration: time::Duration::from_secs(0), + + update_start: now, + update_durations: TimeBuffer::new(200), + + view_start: now, + view_durations: TimeBuffer::new(200), + + layout_start: now, + layout_durations: TimeBuffer::new(200), + + event_start: now, + event_durations: TimeBuffer::new(200), + + draw_start: now, + draw_durations: TimeBuffer::new(200), + + render_start: now, + render_durations: TimeBuffer::new(50), + + message_count: 0, + last_messages: VecDeque::new(), + } + } + + pub fn toggle(&mut self) { + self.is_enabled = !self.is_enabled; + } + + pub fn startup_started(&mut self) { + self.startup_start = time::Instant::now(); + } + + pub fn startup_finished(&mut self) { + self.startup_duration = time::Instant::now() - self.startup_start; + } + + pub fn update_started(&mut self) { + self.update_start = time::Instant::now(); + } + + pub fn update_finished(&mut self) { + self.update_durations + .push(time::Instant::now() - self.update_start); + } + + pub fn view_started(&mut self) { + self.view_start = time::Instant::now(); + } + + pub fn view_finished(&mut self) { + self.view_durations + .push(time::Instant::now() - self.view_start); + } + + pub fn layout_started(&mut self) { + self.layout_start = time::Instant::now(); + } + + pub fn layout_finished(&mut self) { + self.layout_durations + .push(time::Instant::now() - self.layout_start); + } + + pub fn event_processing_started(&mut self) { + self.event_start = time::Instant::now(); + } + + pub fn event_processing_finished(&mut self) { + self.event_durations + .push(time::Instant::now() - self.event_start); + } + + pub fn draw_started(&mut self) { + self.draw_start = time::Instant::now(); + } + + pub fn draw_finished(&mut self) { + self.draw_durations + .push(time::Instant::now() - self.draw_start); + } + + pub fn render_started(&mut self) { + self.render_start = time::Instant::now(); + } + + pub fn render_finished(&mut self) { + self.render_durations + .push(time::Instant::now() - self.render_start); + } + + pub fn log_message(&mut self, message: &Message) { + self.last_messages.push_back(format!("{:?}", message)); + + if self.last_messages.len() > 10 { + let _ = self.last_messages.pop_front(); + } + + self.message_count += 1; + } + + pub fn overlay(&self) -> Vec { + if !self.is_enabled { + return Vec::new(); + } + + let mut lines = Vec::new(); + + fn key_value(key: &str, value: T) -> String { + format!("{: <30} {:?}", key, value) + } + + lines.push(key_value("Startup:", self.startup_duration)); + lines.push(key_value("Update:", self.update_durations.average())); + lines.push(key_value("View:", self.view_durations.average())); + lines.push(key_value("Layout:", self.layout_durations.average())); + lines.push(key_value( + "Event processing:", + self.event_durations.average(), + )); + lines.push(key_value( + "Primitive generation:", + self.draw_durations.average(), + )); + lines.push(key_value("Render:", self.render_durations.average())); + lines.push(key_value("Message count:", self.message_count)); + lines.push(String::from("Last messages:")); + lines.extend( + self.last_messages.iter().map(|msg| format!(" {}", msg)), + ); + + lines + } +} + +#[derive(Debug)] +struct TimeBuffer { + head: usize, + size: usize, + contents: Vec, +} + +impl TimeBuffer { + fn new(capacity: usize) -> TimeBuffer { + TimeBuffer { + head: 0, + size: 0, + contents: vec![time::Duration::from_secs(0); capacity], + } + } + + fn push(&mut self, duration: time::Duration) { + self.head = (self.head + 1) % self.contents.len(); + self.contents[self.head] = duration; + self.size = (self.size + 1).min(self.contents.len()); + } + + fn average(&self) -> time::Duration { + let sum: time::Duration = if self.size == self.contents.len() { + self.contents[..].iter().sum() + } else { + self.contents[..self.size].iter().sum() + }; + + sum / self.size.max(1) as u32 + } +} diff --git a/winit/src/debug/null.rs b/winit/src/debug/null.rs new file mode 100644 index 00000000..9c809dd4 --- /dev/null +++ b/winit/src/debug/null.rs @@ -0,0 +1,48 @@ +#[derive(Debug)] +pub struct Debug; + +impl Debug { + pub fn new() -> Self { + Self + } + + pub fn toggle(&mut self) {} + + pub fn startup_started(&mut self) {} + + pub fn startup_finished(&mut self) {} + + pub fn update_started(&mut self) {} + + pub fn update_finished(&mut self) {} + + pub fn view_started(&mut self) {} + + pub fn view_finished(&mut self) {} + + pub fn layout_started(&mut self) {} + + pub fn layout_finished(&mut self) {} + + pub fn event_processing_started(&mut self) {} + + pub fn event_processing_finished(&mut self) {} + + pub fn draw_started(&mut self) {} + + pub fn draw_finished(&mut self) {} + + pub fn render_started(&mut self) {} + + pub fn render_finished(&mut self) {} + + pub fn log_message( + &mut self, + _message: &Message, + ) { + } + + pub fn overlay(&self) -> Vec { + Vec::new() + } +} diff --git a/winit/src/lib.rs b/winit/src/lib.rs index b08fcb6c..f66c0553 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -6,3 +6,14 @@ pub mod conversion; mod application; pub use application::Application; + +// We disable debug capabilities on release builds unless the `debug` feature +// is explicitly enabled. +#[cfg_attr(any(debug_assertions, feature = "debug"), path = "debug/basic.rs")] +#[cfg_attr( + not(any(debug_assertions, feature = "debug")), + path = "debug/null.rs" +)] +mod debug; + +use debug::Debug; -- cgit From 24e53d92c5808bb47b316de632f51db69173d1c5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Nov 2019 04:42:31 +0100 Subject: Remove Wasm target from CI for now --- .github/workflows/integration.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 54ca2e33..5e73f3cb 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -7,10 +7,6 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] rust: [stable, beta] - include: - - os: ubuntu-latest - rust: stable - targets: 'wasm32-unknown-unknown' steps: - uses: hecrj/setup-rust-action@v1 with: -- cgit From 1a2e512686caa5b644644155185cc5efe2087bee Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Nov 2019 04:49:57 +0100 Subject: Add crate information to debug view --- winit/src/debug/basic.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/winit/src/debug/basic.rs b/winit/src/debug/basic.rs index 09bb5ae1..67c6d8a2 100644 --- a/winit/src/debug/basic.rs +++ b/winit/src/debug/basic.rs @@ -149,6 +149,12 @@ impl Debug { format!("{: <30} {:?}", key, value) } + lines.push(format!( + "{} {} - {}", + env!("CARGO_PKG_NAME"), + env!("CARGO_PKG_VERSION"), + env!("CARGO_PKG_REPOSITORY"), + )); lines.push(key_value("Startup:", self.startup_duration)); lines.push(key_value("Update:", self.update_durations.average())); lines.push(key_value("View:", self.view_durations.average())); -- cgit From 494b0681f831c76b28e0bcd02cd1b83378416e01 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Nov 2019 05:06:53 +0100 Subject: Enable debug view explicitly and test it in CI --- .github/workflows/integration.yml | 4 +++- winit/src/lib.rs | 7 ++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 5e73f3cb..16d60ea7 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -19,4 +19,6 @@ jobs: sudo apt-get install -y libasound2-dev libudev-dev - uses: actions/checkout@master - name: Run tests - run: cargo test --verbose --all --all-features + run: | + cargo test --verbose --all + cargo test --verbose --all --all-features diff --git a/winit/src/lib.rs b/winit/src/lib.rs index f66c0553..d9482fe4 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -9,11 +9,8 @@ pub use application::Application; // We disable debug capabilities on release builds unless the `debug` feature // is explicitly enabled. -#[cfg_attr(any(debug_assertions, feature = "debug"), path = "debug/basic.rs")] -#[cfg_attr( - not(any(debug_assertions, feature = "debug")), - path = "debug/null.rs" -)] +#[cfg_attr(feature = "debug", path = "debug/basic.rs")] +#[cfg_attr(not(feature = "debug"), path = "debug/null.rs")] mod debug; use debug::Debug; -- cgit From 81cfb863ab676204aee5f5d1ed0a2bd953833081 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sun, 3 Nov 2019 05:16:27 +0100 Subject: Use `SystemSource` instead of `FontconfigSource` --- wgpu/src/font.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wgpu/src/font.rs b/wgpu/src/font.rs index e2838268..9bba9b22 100644 --- a/wgpu/src/font.rs +++ b/wgpu/src/font.rs @@ -1,13 +1,13 @@ pub use font_kit::family_name::FamilyName as Family; pub struct Source { - raw: font_kit::sources::fontconfig::FontconfigSource, + raw: font_kit::source::SystemSource, } impl Source { pub fn new() -> Self { Source { - raw: font_kit::sources::fontconfig::FontconfigSource::new(), + raw: font_kit::source::SystemSource::new(), } } -- cgit From 40e9a2f6ae8a9d8576531c79fb57bc53fe5a0acf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Nov 2019 03:49:10 +0100 Subject: Use new `Layer` in `draw_overlay` --- wgpu/src/renderer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index f8b546b3..c167f2bf 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -274,7 +274,7 @@ impl Renderer { layers: &mut Vec>, ) { let first = layers.first().unwrap(); - let mut overlay = Layer::new(first.bounds, 0); + let mut overlay = Layer::new(first.bounds, Vector::new(0, 0)); let font_id = wgpu_glyph::FontId(self.glyph_brush.borrow().fonts().len() - 1); -- cgit From 0157121038987feb6c2ea3066a21ce25e689888e Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 5 Nov 2019 03:57:13 +0100 Subject: Improve default font loading --- wgpu/src/font.rs | 18 ++++++++---------- wgpu/src/renderer.rs | 19 +++++++++++-------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/wgpu/src/font.rs b/wgpu/src/font.rs index 9bba9b22..31df5bf4 100644 --- a/wgpu/src/font.rs +++ b/wgpu/src/font.rs @@ -1,3 +1,4 @@ +pub use font_kit::error::SelectionError as LoadError; pub use font_kit::family_name::FamilyName as Family; pub struct Source { @@ -11,14 +12,11 @@ impl Source { } } - pub fn load(&self, families: &[Family]) -> Vec { - let font = self - .raw - .select_best_match( - families, - &font_kit::properties::Properties::default(), - ) - .expect("Find font"); + pub fn load(&self, families: &[Family]) -> Result, LoadError> { + let font = self.raw.select_best_match( + families, + &font_kit::properties::Properties::default(), + )?; match font { font_kit::handle::Handle::Path { path, .. } => { @@ -28,10 +26,10 @@ impl Source { let mut reader = std::fs::File::open(path).expect("Read font"); let _ = reader.read_to_end(&mut buf); - buf + Ok(buf) } font_kit::handle::Handle::Memory { bytes, .. } => { - bytes.as_ref().clone() + Ok(bytes.as_ref().clone()) } } } diff --git a/wgpu/src/renderer.rs b/wgpu/src/renderer.rs index c167f2bf..060f07a3 100644 --- a/wgpu/src/renderer.rs +++ b/wgpu/src/renderer.rs @@ -64,14 +64,17 @@ impl Renderer { // TODO: Font customization let font_source = font::Source::new(); - let sans_serif_font = font_source.load(&[font::Family::SansSerif]); - let mono_font = font_source.load(&[font::Family::Monospace]); - - let glyph_brush = GlyphBrushBuilder::using_fonts_bytes(vec![ - sans_serif_font, - mono_font, - ]) - .build(&mut device, TextureFormat::Bgra8UnormSrgb); + let default_font = font_source + .load(&[font::Family::SansSerif, font::Family::Serif]) + .expect("Find sans-serif or serif font"); + + let mono_font = font_source + .load(&[font::Family::Monospace]) + .expect("Find monospace font"); + + let glyph_brush = + GlyphBrushBuilder::using_fonts_bytes(vec![default_font, mono_font]) + .build(&mut device, TextureFormat::Bgra8UnormSrgb); let quad_pipeline = quad::Pipeline::new(&mut device); let image_pipeline = crate::image::Pipeline::new(&mut device); -- cgit