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/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 +++++++++++++++++++++++++++++++++ 10 files changed, 906 insertions(+) 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/src/renderer/widget') 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