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 (limited to 'wgpu') 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