diff options
author | 2020-05-19 21:00:40 +0200 | |
---|---|---|
committer | 2020-05-19 21:00:40 +0200 | |
commit | e6180912488db4d59fbffcb46c5930282306cb92 (patch) | |
tree | f41c7ee830a8765b3795c8b6d012c7c621927cca /graphics/src/widget | |
parent | c2e0c52ce031ffe1c300b3cfa362b0e445ac5afd (diff) | |
download | iced-e6180912488db4d59fbffcb46c5930282306cb92.tar.gz iced-e6180912488db4d59fbffcb46c5930282306cb92.tar.bz2 iced-e6180912488db4d59fbffcb46c5930282306cb92.zip |
Merge unnecessary split widget modules
Diffstat (limited to 'graphics/src/widget')
-rw-r--r-- | graphics/src/widget/button.rs | 99 | ||||
-rw-r--r-- | graphics/src/widget/checkbox.rs | 68 | ||||
-rw-r--r-- | graphics/src/widget/column.rs | 42 | ||||
-rw-r--r-- | graphics/src/widget/container.rs | 54 | ||||
-rw-r--r-- | graphics/src/widget/image.rs | 30 | ||||
-rw-r--r-- | graphics/src/widget/pane_grid.rs | 95 | ||||
-rw-r--r-- | graphics/src/widget/progress_bar.rs | 60 | ||||
-rw-r--r-- | graphics/src/widget/radio.rs | 69 | ||||
-rw-r--r-- | graphics/src/widget/row.rs | 42 | ||||
-rw-r--r-- | graphics/src/widget/scrollable.rs | 131 | ||||
-rw-r--r-- | graphics/src/widget/slider.rs | 109 | ||||
-rw-r--r-- | graphics/src/widget/space.rs | 15 | ||||
-rw-r--r-- | graphics/src/widget/svg.rs | 28 | ||||
-rw-r--r-- | graphics/src/widget/text.rs | 67 | ||||
-rw-r--r-- | graphics/src/widget/text_input.rs | 266 |
15 files changed, 1165 insertions, 10 deletions
diff --git a/graphics/src/widget/button.rs b/graphics/src/widget/button.rs index 9debf5c3..aeb862d5 100644 --- a/graphics/src/widget/button.rs +++ b/graphics/src/widget/button.rs @@ -4,7 +4,12 @@ //! //! [`Button`]: type.Button.html //! [`State`]: struct.State.html -use crate::Renderer; +use crate::defaults::{self, Defaults}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::{ + Background, Color, Element, Layout, Point, Rectangle, Vector, +}; pub use iced_native::button::State; pub use iced_style::button::{Style, StyleSheet}; @@ -14,3 +19,95 @@ pub use iced_style::button::{Style, StyleSheet}; /// This is an alias of an `iced_native` button with an `iced_wgpu::Renderer`. pub type Button<'a, Message, Backend> = iced_native::Button<'a, Message, Renderer<Backend>>; + +impl<B> iced_native::button::Renderer for Renderer<B> +where + B: Backend, +{ + const DEFAULT_PADDING: u16 = 5; + + type Style = Box<dyn StyleSheet>; + + fn draw<Message>( + &mut self, + _defaults: &Defaults, + bounds: Rectangle, + cursor_position: Point, + is_disabled: bool, + is_pressed: bool, + style: &Box<dyn StyleSheet>, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let styling = if is_disabled { + style.disabled() + } else if is_mouse_over { + if is_pressed { + style.pressed() + } else { + style.hovered() + } + } else { + style.active() + }; + + let (content, _) = content.draw( + self, + &Defaults { + text: defaults::Text { + color: styling.text_color, + }, + }, + content_layout, + cursor_position, + ); + + ( + if styling.background.is_some() || styling.border_width > 0 { + let background = Primitive::Quad { + bounds, + background: styling + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: styling.border_radius, + border_width: styling.border_width, + border_color: styling.border_color, + }; + + if styling.shadow_offset == Vector::default() { + Primitive::Group { + primitives: vec![background, content], + } + } else { + // TODO: Implement proper shadow support + let shadow = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + styling.shadow_offset.x, + y: bounds.y + styling.shadow_offset.y, + ..bounds + }, + background: Background::Color( + [0.0, 0.0, 0.0, 0.5].into(), + ), + border_radius: styling.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![shadow, background, content], + } + } + } else { + content + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/widget/checkbox.rs b/graphics/src/widget/checkbox.rs index 3c8b58de..cb7fd2cf 100644 --- a/graphics/src/widget/checkbox.rs +++ b/graphics/src/widget/checkbox.rs @@ -1,5 +1,9 @@ //! Show toggle controls using checkboxes. -use crate::Renderer; +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::checkbox; +use iced_native::mouse; +use iced_native::{HorizontalAlignment, Rectangle, VerticalAlignment}; pub use iced_style::checkbox::{Style, StyleSheet}; @@ -8,3 +12,65 @@ pub use iced_style::checkbox::{Style, StyleSheet}; /// This is an alias of an `iced_native` checkbox with an `iced_wgpu::Renderer`. pub type Checkbox<Message, Backend> = iced_native::Checkbox<Message, Renderer<Backend>>; + +impl<B> checkbox::Renderer for Renderer<B> +where + B: Backend + backend::Text, +{ + type Style = Box<dyn StyleSheet>; + + const DEFAULT_SIZE: u16 = 20; + const DEFAULT_SPACING: u16 = 15; + + fn draw( + &mut self, + bounds: Rectangle, + is_checked: bool, + is_mouse_over: bool, + (label, _): Self::Output, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered(is_checked) + } else { + style_sheet.active(is_checked) + }; + + let checkbox = Primitive::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: if is_checked { + let check = Primitive::Text { + content: B::CHECKMARK_ICON.to_string(), + font: B::ICON_FONT, + size: bounds.height * 0.7, + bounds: Rectangle { + x: bounds.center_x(), + y: bounds.center_y(), + ..bounds + }, + color: style.checkmark_color, + horizontal_alignment: HorizontalAlignment::Center, + vertical_alignment: VerticalAlignment::Center, + }; + + vec![checkbox, check, label] + } else { + vec![checkbox, label] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/widget/column.rs b/graphics/src/widget/column.rs new file mode 100644 index 00000000..9183d2ee --- /dev/null +++ b/graphics/src/widget/column.rs @@ -0,0 +1,42 @@ +use crate::{Backend, Primitive, Renderer}; +use iced_native::column; +use iced_native::mouse; +use iced_native::{Element, Layout, Point}; + +pub type Column<'a, Message, Backend> = + iced_native::Column<'a, Message, Renderer<Backend>>; + +impl<B> column::Renderer for Renderer<B> +where + B: Backend, +{ + fn draw<Message>( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_interaction = mouse::Interaction::default(); + + ( + Primitive::Group { + primitives: content + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_interaction) = + child.draw(self, defaults, layout, cursor_position); + + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; + } + + primitive + }) + .collect(), + }, + mouse_interaction, + ) + } +} diff --git a/graphics/src/widget/container.rs b/graphics/src/widget/container.rs index c4c4e5ba..070cb48b 100644 --- a/graphics/src/widget/container.rs +++ b/graphics/src/widget/container.rs @@ -1,5 +1,8 @@ //! Decorate content and apply alignment. -use crate::Renderer; +use crate::container; +use crate::defaults::{self, Defaults}; +use crate::{Backend, Primitive, Renderer}; +use iced_native::{Background, Color, Element, Layout, Point, Rectangle}; pub use iced_style::container::{Style, StyleSheet}; @@ -9,3 +12,52 @@ pub use iced_style::container::{Style, StyleSheet}; /// `Renderer`. pub type Container<'a, Message, Backend> = iced_native::Container<'a, Message, Renderer<Backend>>; + +impl<B> iced_native::container::Renderer for Renderer<B> +where + B: Backend, +{ + type Style = Box<dyn container::StyleSheet>; + + fn draw<Message>( + &mut self, + defaults: &Defaults, + bounds: Rectangle, + cursor_position: Point, + style_sheet: &Self::Style, + content: &Element<'_, Message, Self>, + content_layout: Layout<'_>, + ) -> Self::Output { + let style = style_sheet.style(); + + let defaults = Defaults { + text: defaults::Text { + color: style.text_color.unwrap_or(defaults.text.color), + }, + }; + + let (content, mouse_interaction) = + content.draw(self, &defaults, content_layout, cursor_position); + + if style.background.is_some() || style.border_width > 0 { + let quad = Primitive::Quad { + bounds, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: vec![quad, content], + }, + mouse_interaction, + ) + } else { + (content, mouse_interaction) + } + } +} diff --git a/graphics/src/widget/image.rs b/graphics/src/widget/image.rs new file mode 100644 index 00000000..79d82cb1 --- /dev/null +++ b/graphics/src/widget/image.rs @@ -0,0 +1,30 @@ +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::image; +use iced_native::mouse; +use iced_native::Layout; + +pub use iced_native::Image; + +impl<B> image::Renderer for Renderer<B> +where + B: Backend + backend::Image, +{ + fn dimensions(&self, handle: &image::Handle) -> (u32, u32) { + self.backend().dimensions(handle) + } + + fn draw( + &mut self, + handle: image::Handle, + layout: Layout<'_>, + ) -> Self::Output { + ( + Primitive::Image { + handle, + bounds: layout.bounds(), + }, + mouse::Interaction::default(), + ) + } +} diff --git a/graphics/src/widget/pane_grid.rs b/graphics/src/widget/pane_grid.rs index 34be8ee7..56af683d 100644 --- a/graphics/src/widget/pane_grid.rs +++ b/graphics/src/widget/pane_grid.rs @@ -8,7 +8,10 @@ //! //! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.1/examples/pane_grid //! [`PaneGrid`]: type.PaneGrid.html -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::pane_grid; +use iced_native::{Element, Layout, Point, Rectangle, Vector}; pub use iced_native::pane_grid::{ Axis, Direction, DragEvent, Focus, KeyPressEvent, Pane, ResizeEvent, Split, @@ -23,3 +26,93 @@ pub use iced_native::pane_grid::{ /// This is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`. pub type PaneGrid<'a, Message, Backend> = iced_native::PaneGrid<'a, Message, Renderer<Backend>>; + +impl<B> pane_grid::Renderer for Renderer<B> +where + B: Backend, +{ + fn draw<Message>( + &mut self, + defaults: &Self::Defaults, + content: &[(Pane, Element<'_, Message, Self>)], + dragging: Option<Pane>, + resizing: Option<Axis>, + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let pane_cursor_position = if dragging.is_some() { + // TODO: Remove once cursor availability is encoded in the type + // system + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + + let mut mouse_interaction = mouse::Interaction::default(); + let mut dragged_pane = None; + + let mut panes: Vec<_> = content + .iter() + .zip(layout.children()) + .enumerate() + .map(|(i, ((id, pane), layout))| { + let (primitive, new_mouse_interaction) = + pane.draw(self, defaults, layout, pane_cursor_position); + + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; + } + + if Some(*id) == dragging { + dragged_pane = Some((i, layout)); + } + + primitive + }) + .collect(); + + let primitives = if let Some((index, layout)) = dragged_pane { + let pane = panes.remove(index); + let bounds = layout.bounds(); + + // TODO: Fix once proper layering is implemented. + // This is a pretty hacky way to achieve layering. + let clip = Primitive::Clip { + bounds: Rectangle { + x: cursor_position.x - bounds.width / 2.0, + y: cursor_position.y - bounds.height / 2.0, + width: bounds.width + 0.5, + height: bounds.height + 0.5, + }, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Translate { + translation: Vector::new( + cursor_position.x - bounds.x - bounds.width / 2.0, + cursor_position.y - bounds.y - bounds.height / 2.0, + ), + content: Box::new(pane), + }), + }; + + panes.push(clip); + + panes + } else { + panes + }; + + ( + Primitive::Group { primitives }, + if dragging.is_some() { + mouse::Interaction::Grabbing + } else if let Some(axis) = resizing { + match axis { + Axis::Horizontal => mouse::Interaction::ResizingVertically, + Axis::Vertical => mouse::Interaction::ResizingHorizontally, + } + } else { + mouse_interaction + }, + ) + } +} diff --git a/graphics/src/widget/progress_bar.rs b/graphics/src/widget/progress_bar.rs index c1e5702e..48acb3c1 100644 --- a/graphics/src/widget/progress_bar.rs +++ b/graphics/src/widget/progress_bar.rs @@ -4,7 +4,10 @@ //! as well as a length, height and style. //! //! [`ProgressBar`]: type.ProgressBar.html -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::progress_bar; +use iced_native::{Color, Rectangle}; pub use iced_style::progress_bar::{Style, StyleSheet}; @@ -13,3 +16,58 @@ pub use iced_style::progress_bar::{Style, StyleSheet}; /// This is an alias of an `iced_native` progress bar with an /// `iced_wgpu::Renderer`. pub type ProgressBar<Backend> = iced_native::ProgressBar<Renderer<Backend>>; + +impl<B> progress_bar::Renderer for Renderer<B> +where + B: Backend, +{ + type Style = Box<dyn StyleSheet>; + + const DEFAULT_HEIGHT: u16 = 30; + + fn draw( + &self, + bounds: Rectangle, + range: std::ops::RangeInclusive<f32>, + value: f32, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = style_sheet.style(); + + let (range_start, range_end) = range.into_inner(); + let active_progress_width = bounds.width + * ((value - range_start) / (range_end - range_start).max(1.0)); + + let background = Primitive::Group { + primitives: vec![Primitive::Quad { + bounds: Rectangle { ..bounds }, + background: style.background, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }], + }; + + ( + if active_progress_width > 0.0 { + let bar = Primitive::Quad { + bounds: Rectangle { + width: active_progress_width, + ..bounds + }, + background: style.bar, + border_radius: style.border_radius, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + Primitive::Group { + primitives: vec![background, bar], + } + } else { + background + }, + mouse::Interaction::default(), + ) + } +} diff --git a/graphics/src/widget/radio.rs b/graphics/src/widget/radio.rs index c621a26a..dd8b5f17 100644 --- a/graphics/src/widget/radio.rs +++ b/graphics/src/widget/radio.rs @@ -1,5 +1,8 @@ //! Create choices using radio buttons. -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::radio; +use iced_native::{Background, Color, Rectangle}; pub use iced_style::radio::{Style, StyleSheet}; @@ -9,3 +12,67 @@ pub use iced_style::radio::{Style, StyleSheet}; /// `iced_wgpu::Renderer`. pub type Radio<Message, Backend> = iced_native::Radio<Message, Renderer<Backend>>; + +const SIZE: f32 = 28.0; +const DOT_SIZE: f32 = SIZE / 2.0; + +impl<B> radio::Renderer for Renderer<B> +where + B: Backend, +{ + type Style = Box<dyn StyleSheet>; + + const DEFAULT_SIZE: u16 = SIZE as u16; + const DEFAULT_SPACING: u16 = 15; + + fn draw( + &mut self, + bounds: Rectangle, + is_selected: bool, + is_mouse_over: bool, + (label, _): Self::Output, + style_sheet: &Self::Style, + ) -> Self::Output { + let style = if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let radio = Primitive::Quad { + bounds, + background: style.background, + border_radius: (SIZE / 2.0) as u16, + border_width: style.border_width, + border_color: style.border_color, + }; + + ( + Primitive::Group { + primitives: if is_selected { + let radio_circle = Primitive::Quad { + bounds: Rectangle { + x: bounds.x + DOT_SIZE / 2.0, + y: bounds.y + DOT_SIZE / 2.0, + width: bounds.width - DOT_SIZE, + height: bounds.height - DOT_SIZE, + }, + background: Background::Color(style.dot_color), + border_radius: (DOT_SIZE / 2.0) as u16, + border_width: 0, + border_color: Color::TRANSPARENT, + }; + + vec![radio, radio_circle, label] + } else { + vec![radio, label] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/widget/row.rs b/graphics/src/widget/row.rs new file mode 100644 index 00000000..9865d0de --- /dev/null +++ b/graphics/src/widget/row.rs @@ -0,0 +1,42 @@ +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::row; +use iced_native::{Element, Layout, Point}; + +pub type Row<'a, Message, Backend> = + iced_native::Row<'a, Message, Renderer<Backend>>; + +impl<B> row::Renderer for Renderer<B> +where + B: Backend, +{ + fn draw<Message>( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_interaction = mouse::Interaction::default(); + + ( + Primitive::Group { + primitives: content + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_interaction) = + child.draw(self, defaults, layout, cursor_position); + + if new_mouse_interaction > mouse_interaction { + mouse_interaction = new_mouse_interaction; + } + + primitive + }) + .collect(), + }, + mouse_interaction, + ) + } +} diff --git a/graphics/src/widget/scrollable.rs b/graphics/src/widget/scrollable.rs index 61eae587..b149db0a 100644 --- a/graphics/src/widget/scrollable.rs +++ b/graphics/src/widget/scrollable.rs @@ -1,5 +1,8 @@ //! Navigate an endless amount of content with a scrollbar. -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::scrollable; +use iced_native::{Background, Color, Rectangle, Vector}; pub use iced_native::scrollable::State; pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; @@ -11,3 +14,129 @@ pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; /// `Renderer`. pub type Scrollable<'a, Message, Backend> = iced_native::Scrollable<'a, Message, Renderer<Backend>>; + +const SCROLLBAR_WIDTH: u16 = 10; +const SCROLLBAR_MARGIN: u16 = 2; + +impl<B> scrollable::Renderer for Renderer<B> +where + B: Backend, +{ + type Style = Box<dyn iced_style::scrollable::StyleSheet>; + + fn scrollbar( + &self, + bounds: Rectangle, + content_bounds: Rectangle, + offset: u32, + ) -> Option<scrollable::Scrollbar> { + if content_bounds.height > bounds.height { + let scrollbar_bounds = 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, + }; + + let ratio = bounds.height / content_bounds.height; + let scrollbar_height = bounds.height * ratio; + let y_offset = offset as f32 * ratio; + + let scroller_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, + }; + + Some(scrollable::Scrollbar { + bounds: scrollbar_bounds, + scroller: scrollable::Scroller { + bounds: scroller_bounds, + }, + }) + } else { + None + } + } + + fn draw( + &mut self, + state: &scrollable::State, + bounds: Rectangle, + _content_bounds: Rectangle, + is_mouse_over: bool, + is_mouse_over_scrollbar: bool, + scrollbar: Option<scrollable::Scrollbar>, + offset: u32, + style_sheet: &Self::Style, + (content, mouse_interaction): Self::Output, + ) -> Self::Output { + ( + if let Some(scrollbar) = scrollbar { + let clip = Primitive::Clip { + bounds, + offset: Vector::new(0, offset), + content: Box::new(content), + }; + + let style = if state.is_scroller_grabbed() { + style_sheet.dragging() + } else if is_mouse_over_scrollbar { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let is_scrollbar_visible = + style.background.is_some() || style.border_width > 0; + + let scroller = if is_mouse_over + || state.is_scroller_grabbed() + || is_scrollbar_visible + { + Primitive::Quad { + bounds: scrollbar.scroller.bounds, + background: Background::Color(style.scroller.color), + border_radius: style.scroller.border_radius, + border_width: style.scroller.border_width, + border_color: style.scroller.border_color, + } + } else { + Primitive::None + }; + + let scrollbar = if is_scrollbar_visible { + Primitive::Quad { + bounds: Rectangle { + x: scrollbar.bounds.x + f32::from(SCROLLBAR_MARGIN), + width: scrollbar.bounds.width + - f32::from(2 * SCROLLBAR_MARGIN), + ..scrollbar.bounds + }, + background: style + .background + .unwrap_or(Background::Color(Color::TRANSPARENT)), + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + } + } else { + Primitive::None + }; + + Primitive::Group { + primitives: vec![clip, scrollbar, scroller], + } + } else { + content + }, + if is_mouse_over_scrollbar || state.is_scroller_grabbed() { + mouse::Interaction::Idle + } else { + mouse_interaction + }, + ) + } +} diff --git a/graphics/src/widget/slider.rs b/graphics/src/widget/slider.rs index 035c7c41..b00cde9a 100644 --- a/graphics/src/widget/slider.rs +++ b/graphics/src/widget/slider.rs @@ -4,7 +4,10 @@ //! //! [`Slider`]: struct.Slider.html //! [`State`]: struct.State.html -use crate::Renderer; +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::slider; +use iced_native::{Background, Color, Point, Rectangle}; pub use iced_native::slider::State; pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; @@ -15,3 +18,107 @@ pub use iced_style::slider::{Handle, HandleShape, Style, StyleSheet}; /// This is an alias of an `iced_native` slider with an `iced_wgpu::Renderer`. pub type Slider<'a, Message, Backend> = iced_native::Slider<'a, Message, Renderer<Backend>>; + +const HANDLE_HEIGHT: f32 = 22.0; + +impl<B> slider::Renderer for Renderer<B> +where + B: Backend, +{ + type Style = Box<dyn StyleSheet>; + + fn height(&self) -> u32 { + 30 + } + + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + range: std::ops::RangeInclusive<f32>, + value: f32, + is_dragging: bool, + style_sheet: &Self::Style, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let style = if is_dragging { + style_sheet.dragging() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + 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(style.rail_colors.0), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + Primitive::Quad { + bounds: Rectangle { + x: bounds.x, + y: rail_y + 2.0, + width: bounds.width, + height: 2.0, + }, + background: Background::Color(style.rail_colors.1), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + ); + + let (range_start, range_end) = range.into_inner(); + + let (handle_width, handle_height, handle_border_radius) = + match style.handle.shape { + HandleShape::Circle { radius } => { + (f32::from(radius * 2), f32::from(radius * 2), radius) + } + HandleShape::Rectangle { + width, + border_radius, + } => (f32::from(width), HANDLE_HEIGHT, border_radius), + }; + + let handle_offset = (bounds.width - handle_width) + * ((value - range_start) / (range_end - range_start).max(1.0)); + + let handle = 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(style.handle.color), + border_radius: handle_border_radius, + border_width: style.handle.border_width, + border_color: style.handle.border_color, + }; + + ( + Primitive::Group { + primitives: vec![rail_top, rail_bottom, handle], + }, + if is_dragging { + mouse::Interaction::Grabbing + } else if is_mouse_over { + mouse::Interaction::Grab + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/graphics/src/widget/space.rs b/graphics/src/widget/space.rs new file mode 100644 index 00000000..1f31eabe --- /dev/null +++ b/graphics/src/widget/space.rs @@ -0,0 +1,15 @@ +use crate::{Backend, Primitive, Renderer}; +use iced_native::mouse; +use iced_native::space; +use iced_native::Rectangle; + +pub use iced_native::Space; + +impl<B> space::Renderer for Renderer<B> +where + B: Backend, +{ + fn draw(&mut self, _bounds: Rectangle) -> Self::Output { + (Primitive::None, mouse::Interaction::default()) + } +} diff --git a/graphics/src/widget/svg.rs b/graphics/src/widget/svg.rs new file mode 100644 index 00000000..b1aa7cea --- /dev/null +++ b/graphics/src/widget/svg.rs @@ -0,0 +1,28 @@ +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::{mouse, svg, Layout}; + +pub use iced_native::Svg; + +impl<B> svg::Renderer for Renderer<B> +where + B: Backend + backend::Svg, +{ + fn dimensions(&self, handle: &svg::Handle) -> (u32, u32) { + self.backend().viewport_dimensions(handle) + } + + fn draw( + &mut self, + handle: svg::Handle, + layout: Layout<'_>, + ) -> Self::Output { + ( + Primitive::Svg { + handle, + bounds: layout.bounds(), + }, + mouse::Interaction::default(), + ) + } +} diff --git a/graphics/src/widget/text.rs b/graphics/src/widget/text.rs index ec0349f9..327f8e29 100644 --- a/graphics/src/widget/text.rs +++ b/graphics/src/widget/text.rs @@ -1,7 +1,72 @@ //! Write some text for your users to read. -use crate::Renderer; +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::mouse; +use iced_native::text; +use iced_native::{ + Color, Font, HorizontalAlignment, Rectangle, Size, VerticalAlignment, +}; /// A paragraph of text. /// /// This is an alias of an `iced_native` text with an `iced_wgpu::Renderer`. pub type Text<Backend> = iced_native::Text<Renderer<Backend>>; + +use std::f32; + +impl<B> text::Renderer for Renderer<B> +where + B: Backend + backend::Text, +{ + type Font = Font; + + const DEFAULT_SIZE: u16 = 20; + + fn measure( + &self, + content: &str, + size: u16, + font: Font, + bounds: Size, + ) -> (f32, f32) { + self.backend() + .measure(content, f32::from(size), font, bounds) + } + + fn draw( + &mut self, + defaults: &Self::Defaults, + bounds: Rectangle, + content: &str, + size: u16, + font: Font, + color: Option<Color>, + horizontal_alignment: HorizontalAlignment, + vertical_alignment: VerticalAlignment, + ) -> Self::Output { + let x = match horizontal_alignment { + iced_native::HorizontalAlignment::Left => bounds.x, + iced_native::HorizontalAlignment::Center => bounds.center_x(), + iced_native::HorizontalAlignment::Right => bounds.x + bounds.width, + }; + + let y = match vertical_alignment { + iced_native::VerticalAlignment::Top => bounds.y, + iced_native::VerticalAlignment::Center => bounds.center_y(), + iced_native::VerticalAlignment::Bottom => bounds.y + bounds.height, + }; + + ( + Primitive::Text { + content: content.to_string(), + size: f32::from(size), + bounds: Rectangle { x, y, ..bounds }, + color: color.unwrap_or(defaults.text.color), + font, + horizontal_alignment, + vertical_alignment, + }, + mouse::Interaction::default(), + ) + } +} diff --git a/graphics/src/widget/text_input.rs b/graphics/src/widget/text_input.rs index 8015626b..023bdd7f 100644 --- a/graphics/src/widget/text_input.rs +++ b/graphics/src/widget/text_input.rs @@ -4,7 +4,15 @@ //! //! [`TextInput`]: struct.TextInput.html //! [`State`]: struct.State.html -use crate::Renderer; +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::mouse; +use iced_native::text_input::{self, cursor}; +use iced_native::{ + Background, Color, Font, HorizontalAlignment, Point, Rectangle, Size, + Vector, VerticalAlignment, +}; +use std::f32; pub use iced_native::text_input::State; pub use iced_style::text_input::{Style, StyleSheet}; @@ -14,3 +22,259 @@ pub use iced_style::text_input::{Style, StyleSheet}; /// This is an alias of an `iced_native` text input with an `iced_wgpu::Renderer`. pub type TextInput<'a, Message, Backend> = iced_native::TextInput<'a, Message, Renderer<Backend>>; + +impl<B> text_input::Renderer for Renderer<B> +where + B: Backend + backend::Text, +{ + type Style = Box<dyn StyleSheet>; + + fn default_size(&self) -> u16 { + // TODO: Make this configurable + 20 + } + + fn measure_value(&self, value: &str, size: u16, font: Font) -> f32 { + let backend = self.backend(); + + let (mut width, _) = + backend.measure(value, f32::from(size), font, Size::INFINITY); + + let spaces_around = value.len() - value.trim().len(); + + if spaces_around > 0 { + let space_width = backend.space_width(size as f32); + width += spaces_around as f32 * space_width; + } + + width + } + + fn offset( + &self, + text_bounds: Rectangle, + font: Font, + size: u16, + value: &text_input::Value, + state: &text_input::State, + ) -> f32 { + if state.is_focused() { + let cursor = state.cursor(); + + let focus_position = match cursor.state(value) { + cursor::State::Index(i) => i, + cursor::State::Selection { end, .. } => end, + }; + + let (_, offset) = measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + focus_position, + font, + ); + + offset + } else { + 0.0 + } + } + + fn draw( + &mut self, + bounds: Rectangle, + text_bounds: Rectangle, + cursor_position: Point, + font: Font, + size: u16, + placeholder: &str, + value: &text_input::Value, + state: &text_input::State, + style_sheet: &Self::Style, + ) -> Self::Output { + let is_mouse_over = bounds.contains(cursor_position); + + let style = if state.is_focused() { + style_sheet.focused() + } else if is_mouse_over { + style_sheet.hovered() + } else { + style_sheet.active() + }; + + let input = Primitive::Quad { + bounds, + background: style.background, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, + }; + + let text = value.to_string(); + + let text_value = Primitive::Text { + content: if text.is_empty() { + placeholder.to_string() + } else { + text.clone() + }, + color: if text.is_empty() { + style_sheet.placeholder_color() + } else { + style_sheet.value_color() + }, + font, + bounds: Rectangle { + y: text_bounds.center_y(), + width: f32::INFINITY, + ..text_bounds + }, + size: f32::from(size), + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; + + let (contents_primitive, offset) = if state.is_focused() { + let cursor = state.cursor(); + + let (cursor_primitive, offset) = match cursor.state(value) { + cursor::State::Index(position) => { + let (text_value_width, offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + position, + font, + ); + + ( + 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( + style_sheet.value_color(), + ), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + offset, + ) + } + cursor::State::Selection { start, end } => { + let left = start.min(end); + let right = end.max(start); + + let (left_position, left_offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + left, + font, + ); + + let (right_position, right_offset) = + measure_cursor_and_scroll_offset( + self, + text_bounds, + value, + size, + right, + font, + ); + + let width = right_position - left_position; + + ( + Primitive::Quad { + bounds: Rectangle { + x: text_bounds.x + left_position, + y: text_bounds.y, + width, + height: text_bounds.height, + }, + background: Background::Color( + style_sheet.selection_color(), + ), + border_radius: 0, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + if end == right { + right_offset + } else { + left_offset + }, + ) + } + }; + + ( + Primitive::Group { + primitives: vec![cursor_primitive, text_value], + }, + Vector::new(offset as u32, 0), + ) + } else { + (text_value, Vector::new(0, 0)) + }; + + let text_width = self.measure_value( + if text.is_empty() { placeholder } else { &text }, + size, + font, + ); + + let contents = if text_width > text_bounds.width { + Primitive::Clip { + bounds: text_bounds, + offset, + content: Box::new(contents_primitive), + } + } else { + contents_primitive + }; + + ( + Primitive::Group { + primitives: vec![input, contents], + }, + if is_mouse_over { + mouse::Interaction::Text + } else { + mouse::Interaction::default() + }, + ) + } +} + +fn measure_cursor_and_scroll_offset<B>( + renderer: &Renderer<B>, + text_bounds: Rectangle, + value: &text_input::Value, + size: u16, + cursor_index: usize, + font: Font, +) -> (f32, f32) +where + B: Backend + backend::Text, +{ + use iced_native::text_input::Renderer; + + let text_before_cursor = value.until(cursor_index).to_string(); + + let text_value_width = + renderer.measure_value(&text_before_cursor, size, font); + let offset = ((text_value_width + 5.0) - text_bounds.width).max(0.0); + + (text_value_width, offset) +} |