summaryrefslogtreecommitdiffstats
path: root/wgpu/src/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'wgpu/src/renderer')
-rw-r--r--wgpu/src/renderer/button.rs86
-rw-r--r--wgpu/src/renderer/checkbox.rs106
-rw-r--r--wgpu/src/renderer/column.rs34
-rw-r--r--wgpu/src/renderer/image.rs34
-rw-r--r--wgpu/src/renderer/radio.rs109
-rw-r--r--wgpu/src/renderer/row.rs34
-rw-r--r--wgpu/src/renderer/slider.rs128
-rw-r--r--wgpu/src/renderer/text.rs83
8 files changed, 614 insertions, 0 deletions
diff --git a/wgpu/src/renderer/button.rs b/wgpu/src/renderer/button.rs
new file mode 100644
index 00000000..ad2186d6
--- /dev/null
+++ b/wgpu/src/renderer/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<Message>(&self, button: &Button<Message, Self>) -> 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<Message>(
+ &mut self,
+ button: &Button<Message, Self>,
+ 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
new file mode 100644
index 00000000..fd3f08b1
--- /dev/null
+++ b/wgpu/src/renderer/checkbox.rs
@@ -0,0 +1,106 @@
+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<Message>(&self, checkbox: &Checkbox<Message>) -> 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(&checkbox.label))
+ .node(self)
+ }
+
+ fn draw<Message>(
+ &mut self,
+ checkbox: &Checkbox<Message>,
+ 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
new file mode 100644
index 00000000..cac6da77
--- /dev/null
+++ b/wgpu/src/renderer/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<Message>(
+ &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
new file mode 100644
index 00000000..0e312706
--- /dev/null
+++ b/wgpu/src/renderer/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/radio.rs b/wgpu/src/renderer/radio.rs
new file mode 100644
index 00000000..97b4f70e
--- /dev/null
+++ b/wgpu/src/renderer/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<Message>(&self, radio: &Radio<Message>) -> 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<Message>(
+ &mut self,
+ radio: &Radio<Message>,
+ 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
new file mode 100644
index 00000000..bbfef9a1
--- /dev/null
+++ b/wgpu/src/renderer/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<Message>(
+ &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/slider.rs b/wgpu/src/renderer/slider.rs
new file mode 100644
index 00000000..4ae3abc4
--- /dev/null
+++ b/wgpu/src/renderer/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<Message>(&self, slider: &Slider<Message>) -> 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<Message>(
+ &mut self,
+ slider: &Slider<Message>,
+ 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/text.rs b/wgpu/src/renderer/text.rs
new file mode 100644
index 00000000..8fbade4e
--- /dev/null
+++ b/wgpu/src/renderer/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(), bounds.height())
+ } else {
+ (0.0, 0.0)
+ };
+
+ let size = iced_native::Size { width, height };
+
+ // If the text has no width boundary we avoid caching as the
+ // layout engine may just be measuring text in a row.
+ if bounds.0 == f32::INFINITY {
+ return size;
+ } else {
+ *measure = Some(size);
+ }
+ }
+
+ measure.unwrap()
+ })
+ }
+
+ fn draw(&mut self, text: &Text, layout: Layout<'_>) -> Self::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,
+ )
+ }
+}