From 61f22b1db23f3495145a9a4f7255311fe8381998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 11 Jun 2020 20:41:11 +0200 Subject: [PATCH] Add styling support for `ComboBox` and `Menu` --- glow/src/widget/combo_box.rs | 7 +++- graphics/src/lib.rs | 4 +- graphics/src/overlay.rs | 2 +- graphics/src/overlay/menu.rs | 24 ++++++----- graphics/src/widget/combo_box.rs | 42 ++++++++++++------- native/src/overlay/menu.rs | 31 ++++++++++---- native/src/widget/combo_box.rs | 33 ++++++++++++--- style/src/combo_box.rs | 70 ++++++++++++++++++++++++++++++++ style/src/lib.rs | 2 + style/src/menu.rs | 25 ++++++++++++ wgpu/src/widget/combo_box.rs | 7 +++- 11 files changed, 206 insertions(+), 41 deletions(-) create mode 100644 style/src/combo_box.rs create mode 100644 style/src/menu.rs diff --git a/glow/src/widget/combo_box.rs b/glow/src/widget/combo_box.rs index bb3931ef..bfface29 100644 --- a/glow/src/widget/combo_box.rs +++ b/glow/src/widget/combo_box.rs @@ -1,3 +1,8 @@ pub use iced_native::combo_box::State; -pub type ComboBox<'a, T, Message> = iced_native::ComboBox<'a, T, Message>; +pub use iced_graphics::combo_box::{Style, StyleSheet}; +pub use iced_graphics::overlay::menu::Style as Menu; + +/// A widget allowing the selection of a single value from a list of options. +pub type ComboBox<'a, T, Message> = + iced_native::ComboBox<'a, T, Message, crate::Renderer>; diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index 92e8432e..0c427634 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -9,18 +9,18 @@ #![forbid(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] mod antialiasing; -mod overlay; mod primitive; mod renderer; mod transformation; mod viewport; -mod widget; pub mod backend; pub mod defaults; pub mod font; pub mod layer; +pub mod overlay; pub mod triangle; +pub mod widget; pub mod window; #[doc(no_inline)] diff --git a/graphics/src/overlay.rs b/graphics/src/overlay.rs index c57668d4..b9a0e3e0 100644 --- a/graphics/src/overlay.rs +++ b/graphics/src/overlay.rs @@ -1 +1 @@ -mod menu; +pub mod menu; diff --git a/graphics/src/overlay/menu.rs b/graphics/src/overlay/menu.rs index f4204f25..13065645 100644 --- a/graphics/src/overlay/menu.rs +++ b/graphics/src/overlay/menu.rs @@ -1,18 +1,23 @@ use crate::backend::Backend; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, overlay, Background, Color, Font, HorizontalAlignment, Point, - Rectangle, VerticalAlignment, + mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle, + VerticalAlignment, }; +pub use iced_style::menu::Style; + impl overlay::menu::Renderer for Renderer where B: Backend, { + type Style = Style; + fn decorate( &mut self, bounds: Rectangle, _cursor_position: Point, + style: &Style, (primitives, mouse_cursor): Self::Output, ) -> Self::Output { ( @@ -20,11 +25,9 @@ where primitives: vec![ Primitive::Quad { bounds, - background: Background::Color( - [0.87, 0.87, 0.87].into(), - ), - border_color: [0.7, 0.7, 0.7].into(), - border_width: 1, + background: style.background, + border_color: style.border_color, + border_width: style.border_width, border_radius: 0, }, primitives, @@ -42,6 +45,7 @@ where hovered_option: Option, text_size: u16, padding: u16, + style: &Style, ) -> Self::Output { use std::f32; @@ -63,7 +67,7 @@ where if is_selected { primitives.push(Primitive::Quad { bounds, - background: Background::Color([0.4, 0.4, 1.0].into()), + background: style.selected_background, border_color: Color::TRANSPARENT, border_width: 0, border_radius: 0, @@ -81,9 +85,9 @@ where size: f32::from(text_size), font: Font::Default, color: if is_selected { - Color::WHITE + style.selected_text_color } else { - Color::BLACK + style.text_color }, horizontal_alignment: HorizontalAlignment::Left, vertical_alignment: VerticalAlignment::Center, diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs index 92024c6c..078b5def 100644 --- a/graphics/src/widget/combo_box.rs +++ b/graphics/src/widget/combo_box.rs @@ -1,18 +1,29 @@ use crate::backend::{self, Backend}; use crate::{Primitive, Renderer}; use iced_native::{ - mouse, Background, Color, Font, HorizontalAlignment, Point, Rectangle, - VerticalAlignment, + mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, }; +use iced_style::menu; -pub use iced_native::ComboBox; +pub use iced_native::combo_box::State; +pub use iced_style::combo_box::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type ComboBox<'a, T, Message, Backend> = + iced_native::ComboBox<'a, T, Message, Renderer>; impl iced_native::combo_box::Renderer for Renderer where B: Backend + backend::Text, { + type Style = Box; + const DEFAULT_PADDING: u16 = 5; + fn menu_style(style: &Box) -> menu::Style { + style.menu() + } + fn draw( &mut self, bounds: Rectangle, @@ -20,31 +31,34 @@ where selected: Option, text_size: u16, padding: u16, + style: &Box, ) -> Self::Output { let is_mouse_over = bounds.contains(cursor_position); + let style = if is_mouse_over { + style.hovered() + } else { + style.active() + }; + let background = Primitive::Quad { bounds, - background: Background::Color([0.87, 0.87, 0.87].into()), - border_color: if is_mouse_over { - Color::BLACK - } else { - [0.7, 0.7, 0.7].into() - }, - border_width: 1, - border_radius: 0, + background: style.background, + border_color: style.border_color, + border_width: style.border_width, + border_radius: style.border_radius, }; let arrow_down = Primitive::Text { content: B::ARROW_DOWN_ICON.to_string(), font: B::ICON_FONT, - size: bounds.height * 0.7, + size: bounds.height * style.icon_size, bounds: Rectangle { x: bounds.x + bounds.width - f32::from(padding) * 2.0, y: bounds.center_y(), ..bounds }, - color: Color::BLACK, + color: style.text_color, horizontal_alignment: HorizontalAlignment::Right, vertical_alignment: VerticalAlignment::Center, }; @@ -56,7 +70,7 @@ where content: label, size: f32::from(text_size), font: Font::Default, - color: Color::BLACK, + color: style.text_color, bounds: Rectangle { x: bounds.x + f32::from(padding), y: bounds.center_y(), diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs index 05c41181..9c180671 100644 --- a/native/src/overlay/menu.rs +++ b/native/src/overlay/menu.rs @@ -10,6 +10,7 @@ pub struct Menu<'a, Message, Renderer: self::Renderer> { is_open: &'a mut bool, width: u16, target_height: f32, + style: ::Style, } #[derive(Default)] @@ -43,6 +44,7 @@ where target_height: f32, text_size: u16, padding: u16, + style: ::Style, ) -> Self where T: Clone + ToString, @@ -55,6 +57,7 @@ where on_selected, text_size, padding, + style.clone(), )), ) .padding(1); @@ -64,6 +67,7 @@ where is_open: &mut state.is_open, width, target_height, + style, } } } @@ -156,11 +160,16 @@ where self.container .draw(renderer, defaults, layout, cursor_position); - renderer.decorate(layout.bounds(), cursor_position, primitives) + renderer.decorate( + layout.bounds(), + cursor_position, + &self.style, + primitives, + ) } } -struct List<'a, T, Message> +struct List<'a, T, Message, Renderer: self::Renderer> where [T]: ToOwned, { @@ -169,9 +178,10 @@ where on_selected: Box Message>, text_size: u16, padding: u16, + style: ::Style, } -impl<'a, T, Message> List<'a, T, Message> +impl<'a, T, Message, Renderer: self::Renderer> List<'a, T, Message, Renderer> where [T]: ToOwned, { @@ -181,6 +191,7 @@ where on_selected: Box Message>, text_size: u16, padding: u16, + style: ::Style, ) -> Self { List { hovered_option, @@ -188,12 +199,13 @@ where on_selected, text_size, padding, + style, } } } -impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> - for List<'a, T, Message> +impl<'a, T, Message, Renderer: self::Renderer> Widget<'a, Message, Renderer> + for List<'a, T, Message, Renderer> where T: ToString + Clone, [T]: ToOwned, @@ -286,15 +298,19 @@ where *self.hovered_option, self.text_size, self.padding, + &self.style, ) } } pub trait Renderer: scrollable::Renderer + container::Renderer { + type Style: Default + Clone; + fn decorate( &mut self, bounds: Rectangle, cursor_position: Point, + style: &::Style, primitive: Self::Output, ) -> Self::Output; @@ -306,16 +322,17 @@ pub trait Renderer: scrollable::Renderer + container::Renderer { hovered_option: Option, text_size: u16, padding: u16, + style: &::Style, ) -> Self::Output; } impl<'a, T, Message, Renderer> Into> - for List<'a, T, Message> + for List<'a, T, Message, Renderer> where T: ToString + Clone, [T]: ToOwned, Message: 'static, - Renderer: self::Renderer, + Renderer: 'a + self::Renderer, { fn into(self) -> Element<'a, Message, Renderer> { Element::new(self) diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs index 2adee884..df2a530a 100644 --- a/native/src/widget/combo_box.rs +++ b/native/src/widget/combo_box.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::borrow::Cow; -pub struct ComboBox<'a, T, Message> +pub struct ComboBox<'a, T, Message, Renderer: self::Renderer> where [T]: ToOwned>, { @@ -16,6 +16,7 @@ where width: Length, padding: u16, text_size: Option, + style: ::Style, } #[derive(Default)] @@ -28,7 +29,8 @@ pub struct Internal<'a, T, Message> { on_selected: Box Message>, } -impl<'a, T: 'a, Message> ComboBox<'a, T, Message> +impl<'a, T: 'a, Message, Renderer: self::Renderer> + ComboBox<'a, T, Message, Renderer> where T: ToString, [T]: ToOwned>, @@ -48,7 +50,8 @@ where selected, width: Length::Shrink, text_size: None, - padding: 5, + padding: Renderer::DEFAULT_PADDING, + style: ::Style::default(), } } @@ -72,10 +75,21 @@ where self.text_size = Some(size); self } + + /// Sets the style of the [`ComboBox`]. + /// + /// [`ComboBox`]: struct.ComboBox.html + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } } impl<'a, T: 'a, Message, Renderer> Widget<'a, Message, Renderer> - for ComboBox<'a, T, Message> + for ComboBox<'a, T, Message, Renderer> where T: Clone + ToString + Eq, [T]: ToOwned>, @@ -196,6 +210,7 @@ where self.selected.as_ref().map(ToString::to_string), self.text_size.unwrap_or(renderer.default_size()), self.padding, + &self.style, ) } @@ -223,6 +238,7 @@ where bounds.height, self.text_size.unwrap_or(20), self.padding, + Renderer::menu_style(&self.style), )), )) } else { @@ -235,8 +251,14 @@ where } pub trait Renderer: text::Renderer + menu::Renderer { + type Style: Default; + const DEFAULT_PADDING: u16; + fn menu_style( + style: &::Style, + ) -> ::Style; + fn draw( &mut self, bounds: Rectangle, @@ -244,11 +266,12 @@ pub trait Renderer: text::Renderer + menu::Renderer { selected: Option, text_size: u16, padding: u16, + style: &::Style, ) -> Self::Output; } impl<'a, T: 'a, Message, Renderer> Into> - for ComboBox<'a, T, Message> + for ComboBox<'a, T, Message, Renderer> where T: Clone + ToString + Eq, [T]: ToOwned>, diff --git a/style/src/combo_box.rs b/style/src/combo_box.rs new file mode 100644 index 00000000..4d0c4e46 --- /dev/null +++ b/style/src/combo_box.rs @@ -0,0 +1,70 @@ +use crate::menu; +use iced_core::{Background, Color}; + +/// The appearance of a combo box. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub text_color: Color, + pub background: Background, + pub border_radius: u16, + pub border_width: u16, + pub border_color: Color, + pub icon_size: f32, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + text_color: Color::BLACK, + background: Background::Color([0.87, 0.87, 0.87].into()), + border_radius: 0, + border_width: 1, + border_color: [0.7, 0.7, 0.7].into(), + icon_size: 0.7, + } + } +} + +/// A set of rules that dictate the style of a container. +pub trait StyleSheet { + fn menu(&self) -> menu::Style; + + fn active(&self) -> Style; + + /// Produces the style of a container. + fn hovered(&self) -> Style; +} + +struct Default; + +impl StyleSheet for Default { + fn menu(&self) -> menu::Style { + menu::Style::default() + } + + fn active(&self) -> Style { + Style::default() + } + + fn hovered(&self) -> Style { + Style { + border_color: Color::BLACK, + ..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/style/src/lib.rs b/style/src/lib.rs index 72d83aec..b19d6600 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -6,7 +6,9 @@ pub use iced_core::{Background, Color}; pub mod button; pub mod checkbox; +pub mod combo_box; pub mod container; +pub mod menu; pub mod progress_bar; pub mod radio; pub mod scrollable; diff --git a/style/src/menu.rs b/style/src/menu.rs new file mode 100644 index 00000000..e8321dc7 --- /dev/null +++ b/style/src/menu.rs @@ -0,0 +1,25 @@ +use iced_core::{Background, Color}; + +/// The appearance of a menu. +#[derive(Debug, Clone, Copy)] +pub struct Style { + pub text_color: Color, + pub background: Background, + pub border_width: u16, + pub border_color: Color, + pub selected_text_color: Color, + pub selected_background: Background, +} + +impl std::default::Default for Style { + fn default() -> Self { + Self { + text_color: Color::BLACK, + background: Background::Color([0.87, 0.87, 0.87].into()), + border_width: 1, + border_color: [0.7, 0.7, 0.7].into(), + selected_text_color: Color::WHITE, + selected_background: Background::Color([0.4, 0.4, 1.0].into()), + } + } +} diff --git a/wgpu/src/widget/combo_box.rs b/wgpu/src/widget/combo_box.rs index bb3931ef..bfface29 100644 --- a/wgpu/src/widget/combo_box.rs +++ b/wgpu/src/widget/combo_box.rs @@ -1,3 +1,8 @@ pub use iced_native::combo_box::State; -pub type ComboBox<'a, T, Message> = iced_native::ComboBox<'a, T, Message>; +pub use iced_graphics::combo_box::{Style, StyleSheet}; +pub use iced_graphics::overlay::menu::Style as Menu; + +/// A widget allowing the selection of a single value from a list of options. +pub type ComboBox<'a, T, Message> = + iced_native::ComboBox<'a, T, Message, crate::Renderer>;