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) +} | 
