Add styling support for ComboBox and Menu

This commit is contained in:
Héctor Ramón Jiménez 2020-06-11 20:41:11 +02:00
parent 0ff5a02550
commit 61f22b1db2
11 changed files with 206 additions and 41 deletions

View File

@ -1,3 +1,8 @@
pub use iced_native::combo_box::State; 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>;

View File

@ -9,18 +9,18 @@
#![forbid(rust_2018_idioms)] #![forbid(rust_2018_idioms)]
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
mod antialiasing; mod antialiasing;
mod overlay;
mod primitive; mod primitive;
mod renderer; mod renderer;
mod transformation; mod transformation;
mod viewport; mod viewport;
mod widget;
pub mod backend; pub mod backend;
pub mod defaults; pub mod defaults;
pub mod font; pub mod font;
pub mod layer; pub mod layer;
pub mod overlay;
pub mod triangle; pub mod triangle;
pub mod widget;
pub mod window; pub mod window;
#[doc(no_inline)] #[doc(no_inline)]

View File

@ -1 +1 @@
mod menu; pub mod menu;

View File

@ -1,18 +1,23 @@
use crate::backend::Backend; use crate::backend::Backend;
use crate::{Primitive, Renderer}; use crate::{Primitive, Renderer};
use iced_native::{ use iced_native::{
mouse, overlay, Background, Color, Font, HorizontalAlignment, Point, mouse, overlay, Color, Font, HorizontalAlignment, Point, Rectangle,
Rectangle, VerticalAlignment, VerticalAlignment,
}; };
pub use iced_style::menu::Style;
impl<B> overlay::menu::Renderer for Renderer<B> impl<B> overlay::menu::Renderer for Renderer<B>
where where
B: Backend, B: Backend,
{ {
type Style = Style;
fn decorate( fn decorate(
&mut self, &mut self,
bounds: Rectangle, bounds: Rectangle,
_cursor_position: Point, _cursor_position: Point,
style: &Style,
(primitives, mouse_cursor): Self::Output, (primitives, mouse_cursor): Self::Output,
) -> Self::Output { ) -> Self::Output {
( (
@ -20,11 +25,9 @@ where
primitives: vec![ primitives: vec![
Primitive::Quad { Primitive::Quad {
bounds, bounds,
background: Background::Color( background: style.background,
[0.87, 0.87, 0.87].into(), border_color: style.border_color,
), border_width: style.border_width,
border_color: [0.7, 0.7, 0.7].into(),
border_width: 1,
border_radius: 0, border_radius: 0,
}, },
primitives, primitives,
@ -42,6 +45,7 @@ where
hovered_option: Option<usize>, hovered_option: Option<usize>,
text_size: u16, text_size: u16,
padding: u16, padding: u16,
style: &Style,
) -> Self::Output { ) -> Self::Output {
use std::f32; use std::f32;
@ -63,7 +67,7 @@ where
if is_selected { if is_selected {
primitives.push(Primitive::Quad { primitives.push(Primitive::Quad {
bounds, bounds,
background: Background::Color([0.4, 0.4, 1.0].into()), background: style.selected_background,
border_color: Color::TRANSPARENT, border_color: Color::TRANSPARENT,
border_width: 0, border_width: 0,
border_radius: 0, border_radius: 0,
@ -81,9 +85,9 @@ where
size: f32::from(text_size), size: f32::from(text_size),
font: Font::Default, font: Font::Default,
color: if is_selected { color: if is_selected {
Color::WHITE style.selected_text_color
} else { } else {
Color::BLACK style.text_color
}, },
horizontal_alignment: HorizontalAlignment::Left, horizontal_alignment: HorizontalAlignment::Left,
vertical_alignment: VerticalAlignment::Center, vertical_alignment: VerticalAlignment::Center,

View File

@ -1,18 +1,29 @@
use crate::backend::{self, Backend}; use crate::backend::{self, Backend};
use crate::{Primitive, Renderer}; use crate::{Primitive, Renderer};
use iced_native::{ use iced_native::{
mouse, Background, Color, Font, HorizontalAlignment, Point, Rectangle, mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment,
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<Backend>>;
impl<B> iced_native::combo_box::Renderer for Renderer<B> impl<B> iced_native::combo_box::Renderer for Renderer<B>
where where
B: Backend + backend::Text, B: Backend + backend::Text,
{ {
type Style = Box<dyn StyleSheet>;
const DEFAULT_PADDING: u16 = 5; const DEFAULT_PADDING: u16 = 5;
fn menu_style(style: &Box<dyn StyleSheet>) -> menu::Style {
style.menu()
}
fn draw( fn draw(
&mut self, &mut self,
bounds: Rectangle, bounds: Rectangle,
@ -20,31 +31,34 @@ where
selected: Option<String>, selected: Option<String>,
text_size: u16, text_size: u16,
padding: u16, padding: u16,
style: &Box<dyn StyleSheet>,
) -> Self::Output { ) -> Self::Output {
let is_mouse_over = bounds.contains(cursor_position); let is_mouse_over = bounds.contains(cursor_position);
let style = if is_mouse_over {
style.hovered()
} else {
style.active()
};
let background = Primitive::Quad { let background = Primitive::Quad {
bounds, bounds,
background: Background::Color([0.87, 0.87, 0.87].into()), background: style.background,
border_color: if is_mouse_over { border_color: style.border_color,
Color::BLACK border_width: style.border_width,
} else { border_radius: style.border_radius,
[0.7, 0.7, 0.7].into()
},
border_width: 1,
border_radius: 0,
}; };
let arrow_down = Primitive::Text { let arrow_down = Primitive::Text {
content: B::ARROW_DOWN_ICON.to_string(), content: B::ARROW_DOWN_ICON.to_string(),
font: B::ICON_FONT, font: B::ICON_FONT,
size: bounds.height * 0.7, size: bounds.height * style.icon_size,
bounds: Rectangle { bounds: Rectangle {
x: bounds.x + bounds.width - f32::from(padding) * 2.0, x: bounds.x + bounds.width - f32::from(padding) * 2.0,
y: bounds.center_y(), y: bounds.center_y(),
..bounds ..bounds
}, },
color: Color::BLACK, color: style.text_color,
horizontal_alignment: HorizontalAlignment::Right, horizontal_alignment: HorizontalAlignment::Right,
vertical_alignment: VerticalAlignment::Center, vertical_alignment: VerticalAlignment::Center,
}; };
@ -56,7 +70,7 @@ where
content: label, content: label,
size: f32::from(text_size), size: f32::from(text_size),
font: Font::Default, font: Font::Default,
color: Color::BLACK, color: style.text_color,
bounds: Rectangle { bounds: Rectangle {
x: bounds.x + f32::from(padding), x: bounds.x + f32::from(padding),
y: bounds.center_y(), y: bounds.center_y(),

View File

@ -10,6 +10,7 @@ pub struct Menu<'a, Message, Renderer: self::Renderer> {
is_open: &'a mut bool, is_open: &'a mut bool,
width: u16, width: u16,
target_height: f32, target_height: f32,
style: <Renderer as self::Renderer>::Style,
} }
#[derive(Default)] #[derive(Default)]
@ -43,6 +44,7 @@ where
target_height: f32, target_height: f32,
text_size: u16, text_size: u16,
padding: u16, padding: u16,
style: <Renderer as self::Renderer>::Style,
) -> Self ) -> Self
where where
T: Clone + ToString, T: Clone + ToString,
@ -55,6 +57,7 @@ where
on_selected, on_selected,
text_size, text_size,
padding, padding,
style.clone(),
)), )),
) )
.padding(1); .padding(1);
@ -64,6 +67,7 @@ where
is_open: &mut state.is_open, is_open: &mut state.is_open,
width, width,
target_height, target_height,
style,
} }
} }
} }
@ -156,11 +160,16 @@ where
self.container self.container
.draw(renderer, defaults, layout, cursor_position); .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 where
[T]: ToOwned, [T]: ToOwned,
{ {
@ -169,9 +178,10 @@ where
on_selected: Box<dyn Fn(T) -> Message>, on_selected: Box<dyn Fn(T) -> Message>,
text_size: u16, text_size: u16,
padding: u16, padding: u16,
style: <Renderer as self::Renderer>::Style,
} }
impl<'a, T, Message> List<'a, T, Message> impl<'a, T, Message, Renderer: self::Renderer> List<'a, T, Message, Renderer>
where where
[T]: ToOwned, [T]: ToOwned,
{ {
@ -181,6 +191,7 @@ where
on_selected: Box<dyn Fn(T) -> Message>, on_selected: Box<dyn Fn(T) -> Message>,
text_size: u16, text_size: u16,
padding: u16, padding: u16,
style: <Renderer as self::Renderer>::Style,
) -> Self { ) -> Self {
List { List {
hovered_option, hovered_option,
@ -188,12 +199,13 @@ where
on_selected, on_selected,
text_size, text_size,
padding, padding,
style,
} }
} }
} }
impl<'a, T, Message, Renderer> Widget<'a, Message, Renderer> impl<'a, T, Message, Renderer: self::Renderer> Widget<'a, Message, Renderer>
for List<'a, T, Message> for List<'a, T, Message, Renderer>
where where
T: ToString + Clone, T: ToString + Clone,
[T]: ToOwned, [T]: ToOwned,
@ -286,15 +298,19 @@ where
*self.hovered_option, *self.hovered_option,
self.text_size, self.text_size,
self.padding, self.padding,
&self.style,
) )
} }
} }
pub trait Renderer: scrollable::Renderer + container::Renderer { pub trait Renderer: scrollable::Renderer + container::Renderer {
type Style: Default + Clone;
fn decorate( fn decorate(
&mut self, &mut self,
bounds: Rectangle, bounds: Rectangle,
cursor_position: Point, cursor_position: Point,
style: &<Self as Renderer>::Style,
primitive: Self::Output, primitive: Self::Output,
) -> Self::Output; ) -> Self::Output;
@ -306,16 +322,17 @@ pub trait Renderer: scrollable::Renderer + container::Renderer {
hovered_option: Option<usize>, hovered_option: Option<usize>,
text_size: u16, text_size: u16,
padding: u16, padding: u16,
style: &<Self as Renderer>::Style,
) -> Self::Output; ) -> Self::Output;
} }
impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>> impl<'a, T, Message, Renderer> Into<Element<'a, Message, Renderer>>
for List<'a, T, Message> for List<'a, T, Message, Renderer>
where where
T: ToString + Clone, T: ToString + Clone,
[T]: ToOwned, [T]: ToOwned,
Message: 'static, Message: 'static,
Renderer: self::Renderer, Renderer: 'a + self::Renderer,
{ {
fn into(self) -> Element<'a, Message, Renderer> { fn into(self) -> Element<'a, Message, Renderer> {
Element::new(self) Element::new(self)

View File

@ -6,7 +6,7 @@ use crate::{
}; };
use std::borrow::Cow; use std::borrow::Cow;
pub struct ComboBox<'a, T, Message> pub struct ComboBox<'a, T, Message, Renderer: self::Renderer>
where where
[T]: ToOwned<Owned = Vec<T>>, [T]: ToOwned<Owned = Vec<T>>,
{ {
@ -16,6 +16,7 @@ where
width: Length, width: Length,
padding: u16, padding: u16,
text_size: Option<u16>, text_size: Option<u16>,
style: <Renderer as self::Renderer>::Style,
} }
#[derive(Default)] #[derive(Default)]
@ -28,7 +29,8 @@ pub struct Internal<'a, T, Message> {
on_selected: Box<dyn Fn(T) -> Message>, on_selected: Box<dyn Fn(T) -> Message>,
} }
impl<'a, T: 'a, Message> ComboBox<'a, T, Message> impl<'a, T: 'a, Message, Renderer: self::Renderer>
ComboBox<'a, T, Message, Renderer>
where where
T: ToString, T: ToString,
[T]: ToOwned<Owned = Vec<T>>, [T]: ToOwned<Owned = Vec<T>>,
@ -48,7 +50,8 @@ where
selected, selected,
width: Length::Shrink, width: Length::Shrink,
text_size: None, text_size: None,
padding: 5, padding: Renderer::DEFAULT_PADDING,
style: <Renderer as self::Renderer>::Style::default(),
} }
} }
@ -72,10 +75,21 @@ where
self.text_size = Some(size); self.text_size = Some(size);
self self
} }
/// Sets the style of the [`ComboBox`].
///
/// [`ComboBox`]: struct.ComboBox.html
pub fn style(
mut self,
style: impl Into<<Renderer as self::Renderer>::Style>,
) -> Self {
self.style = style.into();
self
}
} }
impl<'a, T: 'a, Message, Renderer> Widget<'a, Message, Renderer> impl<'a, T: 'a, Message, Renderer> Widget<'a, Message, Renderer>
for ComboBox<'a, T, Message> for ComboBox<'a, T, Message, Renderer>
where where
T: Clone + ToString + Eq, T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>, [T]: ToOwned<Owned = Vec<T>>,
@ -196,6 +210,7 @@ where
self.selected.as_ref().map(ToString::to_string), self.selected.as_ref().map(ToString::to_string),
self.text_size.unwrap_or(renderer.default_size()), self.text_size.unwrap_or(renderer.default_size()),
self.padding, self.padding,
&self.style,
) )
} }
@ -223,6 +238,7 @@ where
bounds.height, bounds.height,
self.text_size.unwrap_or(20), self.text_size.unwrap_or(20),
self.padding, self.padding,
Renderer::menu_style(&self.style),
)), )),
)) ))
} else { } else {
@ -235,8 +251,14 @@ where
} }
pub trait Renderer: text::Renderer + menu::Renderer { pub trait Renderer: text::Renderer + menu::Renderer {
type Style: Default;
const DEFAULT_PADDING: u16; const DEFAULT_PADDING: u16;
fn menu_style(
style: &<Self as Renderer>::Style,
) -> <Self as menu::Renderer>::Style;
fn draw( fn draw(
&mut self, &mut self,
bounds: Rectangle, bounds: Rectangle,
@ -244,11 +266,12 @@ pub trait Renderer: text::Renderer + menu::Renderer {
selected: Option<String>, selected: Option<String>,
text_size: u16, text_size: u16,
padding: u16, padding: u16,
style: &<Self as Renderer>::Style,
) -> Self::Output; ) -> Self::Output;
} }
impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>> impl<'a, T: 'a, Message, Renderer> Into<Element<'a, Message, Renderer>>
for ComboBox<'a, T, Message> for ComboBox<'a, T, Message, Renderer>
where where
T: Clone + ToString + Eq, T: Clone + ToString + Eq,
[T]: ToOwned<Owned = Vec<T>>, [T]: ToOwned<Owned = Vec<T>>,

70
style/src/combo_box.rs Normal file
View File

@ -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<dyn StyleSheet> {
fn default() -> Self {
Box::new(Default)
}
}
impl<T> From<T> for Box<dyn StyleSheet>
where
T: 'static + StyleSheet,
{
fn from(style: T) -> Self {
Box::new(style)
}
}

View File

@ -6,7 +6,9 @@ pub use iced_core::{Background, Color};
pub mod button; pub mod button;
pub mod checkbox; pub mod checkbox;
pub mod combo_box;
pub mod container; pub mod container;
pub mod menu;
pub mod progress_bar; pub mod progress_bar;
pub mod radio; pub mod radio;
pub mod scrollable; pub mod scrollable;

25
style/src/menu.rs Normal file
View File

@ -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()),
}
}
}

View File

@ -1,3 +1,8 @@
pub use iced_native::combo_box::State; 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>;