diff --git a/examples/styling.rs b/examples/styling.rs index c368ea07..61c8bcba 100644 --- a/examples/styling.rs +++ b/examples/styling.rs @@ -1,6 +1,6 @@ use iced::{ - button, text_input, Button, Column, Container, Element, Length, Radio, Row, - Sandbox, Settings, Text, TextInput, + button, scrollable, text_input, Button, Column, Container, Element, Length, + Radio, Row, Sandbox, Scrollable, Settings, Text, TextInput, }; pub fn main() { @@ -10,6 +10,7 @@ pub fn main() { #[derive(Default)] struct Styling { theme: style::Theme, + scroll: scrollable::State, input: text_input::State, input_value: String, button: button::State, @@ -76,10 +77,13 @@ impl Sandbox for Styling { .push(choose_theme) .push(Row::new().spacing(10).push(text_input).push(button)); - Container::new(content) + let scrollable = Scrollable::new(&mut self.scroll) + .style(self.theme) + .push(Container::new(content).width(Length::Fill).center_x()); + + Container::new(scrollable) .width(Length::Fill) .height(Length::Fill) - .center_x() .center_y() .style(self.theme) .into() @@ -87,7 +91,7 @@ impl Sandbox for Styling { } mod style { - use iced::{button, container, text_input}; + use iced::{button, container, scrollable, text_input}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Theme { @@ -132,6 +136,15 @@ mod style { } } + impl From for Box { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Scrollable.into(), + } + } + } + mod light { use iced::{button, Background, Color, Vector}; @@ -161,7 +174,9 @@ mod style { } mod dark { - use iced::{button, container, text_input, Background, Color}; + use iced::{ + button, container, scrollable, text_input, Background, Color, + }; pub struct Container; @@ -239,6 +254,42 @@ mod style { ..self.active() } } + + fn pressed(&self) -> button::Style { + button::Style { + border_width: 1, + border_color: Color::WHITE, + ..self.hovered() + } + } + } + + pub struct Scrollable; + + impl scrollable::StyleSheet for Scrollable { + fn active(&self) -> scrollable::Scrollbar { + scrollable::Scrollbar { + background: None, + border_radius: 2, + border_width: 0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: [1.0, 1.0, 1.0, 0.7].into(), + border_radius: 2, + border_width: 1, + border_color: Color::WHITE, + }, + } + } + + fn hovered(&self) -> scrollable::Scrollbar { + scrollable::Scrollbar { + background: Some(Background::Color( + [1.0, 1.0, 1.0, 0.3].into(), + )), + ..self.active() + } + } } } } diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs index 96aa132c..0184ac00 100644 --- a/native/src/renderer/null.rs +++ b/native/src/renderer/null.rs @@ -73,6 +73,8 @@ impl text::Renderer for Null { } impl scrollable::Renderer for Null { + type Style = (); + fn scrollbar( &self, _bounds: Rectangle, @@ -91,6 +93,7 @@ impl scrollable::Renderer for Null { _is_mouse_over_scrollbar: bool, _scrollbar: Option, _offset: u32, + _style: &Self::Style, _content: Self::Output, ) { } diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs index 9df09b14..a062abd0 100644 --- a/native/src/widget/scrollable.rs +++ b/native/src/widget/scrollable.rs @@ -11,14 +11,15 @@ use std::{f32, hash::Hash, u32}; /// A widget that can vertically display an infinite amount of content with a /// scrollbar. #[allow(missing_debug_implementations)] -pub struct Scrollable<'a, Message, Renderer> { +pub struct Scrollable<'a, Message, Renderer: self::Renderer> { state: &'a mut State, height: Length, max_height: u32, content: Column<'a, Message, Renderer>, + style: Renderer::Style, } -impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { +impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> { /// Creates a new [`Scrollable`] with the given [`State`]. /// /// [`Scrollable`]: struct.Scrollable.html @@ -29,6 +30,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { height: Length::Shrink, max_height: u32::MAX, content: Column::new(), + style: Renderer::Style::default(), } } @@ -90,6 +92,14 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { self } + /// Sets the style of the [`Scrollable`] . + /// + /// [`Scrollable`]: struct.Scrollable.html + pub fn style(mut self, style: impl Into) -> Self { + self.style = style.into(); + self + } + /// Adds an element to the [`Scrollable`]. /// /// [`Scrollable`]: struct.Scrollable.html @@ -105,7 +115,7 @@ impl<'a, Message, Renderer> Scrollable<'a, Message, Renderer> { impl<'a, Message, Renderer> Widget for Scrollable<'a, Message, Renderer> where - Renderer: self::Renderer + column::Renderer, + Renderer: 'static + self::Renderer + column::Renderer, { fn width(&self) -> Length { Length::Fill @@ -295,12 +305,13 @@ where is_mouse_over_scrollbar, scrollbar, offset, + &self.style, content, ) } fn hash_layout(&self, state: &mut Hasher) { - std::any::TypeId::of::>().hash(state); + std::any::TypeId::of::>().hash(state); self.height.hash(state); self.max_height.hash(state); @@ -447,6 +458,8 @@ pub struct Scroller { /// [`Scrollable`]: struct.Scrollable.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { + type Style: Default; + /// Returns the [`Scrollbar`] given the bounds and content bounds of a /// [`Scrollable`]. /// @@ -483,6 +496,7 @@ pub trait Renderer: crate::Renderer + Sized { is_mouse_over_scrollbar: bool, scrollbar: Option, offset: u32, + style: &Self::Style, content: Self::Output, ) -> Self::Output; } @@ -490,7 +504,7 @@ pub trait Renderer: crate::Renderer + Sized { impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where - Renderer: 'a + self::Renderer + column::Renderer, + Renderer: 'static + self::Renderer + column::Renderer, Message: 'static, { fn from( diff --git a/src/native.rs b/src/native.rs index 67f85c20..1061a730 100644 --- a/src/native.rs +++ b/src/native.rs @@ -24,20 +24,6 @@ pub mod widget { //! [`text_input::State`]: text_input/struct.State.html pub use iced_wgpu::widget::*; - pub mod scrollable { - //! Navigate an endless amount of content with a scrollbar. - - /// A widget that can vertically display an infinite amount of content - /// with a scrollbar. - /// - /// This is an alias of an `iced_native` scrollable with a default - /// `Renderer`. - pub type Scrollable<'a, Message> = - iced_winit::Scrollable<'a, Message, iced_wgpu::Renderer>; - - pub use iced_winit::scrollable::State; - } - pub mod slider { //! Display an interactive selector of a single value from a range of //! values. diff --git a/style/src/lib.rs b/style/src/lib.rs index 1d7e57c4..fb90b2b5 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -1,3 +1,4 @@ pub mod button; pub mod container; +pub mod scrollable; pub mod text_input; diff --git a/style/src/scrollable.rs b/style/src/scrollable.rs new file mode 100644 index 00000000..690c14a2 --- /dev/null +++ b/style/src/scrollable.rs @@ -0,0 +1,76 @@ +//! Navigate an endless amount of content with a scrollbar. +use iced_core::{Background, Color}; + +/// The appearance of a scrollable. +#[derive(Debug, Clone, Copy)] +pub struct Scrollbar { + pub background: Option, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, + pub scroller: Scroller, +} + +/// The appearance of the scroller of a scrollable. +#[derive(Debug, Clone, Copy)] +pub struct Scroller { + pub color: Color, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, +} + +/// A set of rules that dictate the style of a scrollable. +pub trait StyleSheet { + /// Produces the style of an active scrollbar. + fn active(&self) -> Scrollbar; + + /// Produces the style of an hovered scrollbar. + fn hovered(&self) -> Scrollbar; + + /// Produces the style of a scrollbar that is being dragged. + fn dragging(&self) -> Scrollbar { + self.hovered() + } +} + +struct Default; + +impl StyleSheet for Default { + fn active(&self) -> Scrollbar { + Scrollbar { + background: None, + border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, + scroller: Scroller { + color: [0.0, 0.0, 0.0, 0.7].into(), + border_radius: 5, + border_width: 0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered(&self) -> Scrollbar { + Scrollbar { + background: Some(Background::Color([0.0, 0.0, 0.0, 0.3].into())), + ..self.active() + } + } +} + +impl std::default::Default for Box { + fn default() -> Self { + Box::new(Default) + } +} + +impl From for Box +where + T: 'static + StyleSheet, +{ + fn from(style: T) -> Self { + Box::new(style) + } +} diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index 42a4a743..30d7f337 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -7,6 +7,8 @@ const SCROLLBAR_WIDTH: u16 = 10; const SCROLLBAR_MARGIN: u16 = 2; impl scrollable::Renderer for Renderer { + type Style = Box; + fn scrollbar( &self, bounds: Rectangle, @@ -53,6 +55,7 @@ impl scrollable::Renderer for Renderer { is_mouse_over_scrollbar: bool, scrollbar: Option, offset: u32, + style_sheet: &Self::Style, (content, mouse_cursor): Self::Output, ) -> Self::Output { let clip = Primitive::Clip { @@ -64,17 +67,23 @@ impl scrollable::Renderer for Renderer { ( if let Some(scrollbar) = scrollbar { if is_mouse_over || state.is_scroller_grabbed() { - let scroller = Primitive::Quad { - bounds: scrollbar.scroller.bounds, - background: Background::Color( - [0.0, 0.0, 0.0, 0.7].into(), - ), - border_radius: 5, - border_width: 0, - border_color: Color::TRANSPARENT, + let style = if state.is_scroller_grabbed() { + style_sheet.dragging() + } else if is_mouse_over_scrollbar { + style_sheet.hovered() + } else { + style_sheet.active() }; - if is_mouse_over_scrollbar || state.is_scroller_grabbed() { + let scroller = 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, + }; + + if style.background.is_some() || style.border_width > 0 { let scrollbar = Primitive::Quad { bounds: Rectangle { x: scrollbar.bounds.x @@ -83,12 +92,12 @@ impl scrollable::Renderer for Renderer { - f32::from(2 * SCROLLBAR_MARGIN), ..scrollbar.bounds }, - background: Background::Color( - [0.0, 0.0, 0.0, 0.3].into(), + background: style.background.unwrap_or( + Background::Color(Color::TRANSPARENT), ), - border_radius: 5, - border_width: 0, - border_color: Color::TRANSPARENT, + border_radius: style.border_radius, + border_width: style.border_width, + border_color: style.border_color, }; Primitive::Group { diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 1d7e57c4..fb90b2b5 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -1,3 +1,4 @@ pub mod button; pub mod container; +pub mod scrollable; pub mod text_input; diff --git a/wgpu/src/widget/scrollable.rs b/wgpu/src/widget/scrollable.rs new file mode 100644 index 00000000..1d236105 --- /dev/null +++ b/wgpu/src/widget/scrollable.rs @@ -0,0 +1,13 @@ +//! Navigate an endless amount of content with a scrollbar. +use crate::Renderer; + +pub use iced_native::scrollable::State; +pub use iced_style::scrollable::{Scrollbar, Scroller, StyleSheet}; + +/// A widget that can vertically display an infinite amount of content +/// with a scrollbar. +/// +/// This is an alias of an `iced_native` scrollable with a default +/// `Renderer`. +pub type Scrollable<'a, Message> = + iced_native::Scrollable<'a, Message, Renderer>;