From 73b8ae8e5e7f57c608c775272a2980995ab22bb3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Fri, 10 Jul 2020 02:50:47 +0200 Subject: Rename `ComboBox` to `PickList` --- Cargo.toml | 2 +- examples/combo_box/Cargo.toml | 11 -- examples/combo_box/README.md | 18 --- examples/combo_box/src/main.rs | 121 --------------- examples/pick_list/Cargo.toml | 9 ++ examples/pick_list/README.md | 18 +++ examples/pick_list/src/main.rs | 121 +++++++++++++++ glow/src/widget.rs | 6 +- glow/src/widget/combo_box.rs | 9 -- glow/src/widget/pick_list.rs | 9 ++ graphics/src/widget.rs | 4 +- graphics/src/widget/combo_box.rs | 97 ------------ graphics/src/widget/pick_list.rs | 97 ++++++++++++ native/src/widget.rs | 6 +- native/src/widget/combo_box.rs | 320 --------------------------------------- native/src/widget/pick_list.rs | 320 +++++++++++++++++++++++++++++++++++++++ src/widget.rs | 10 +- style/src/combo_box.rs | 70 --------- style/src/lib.rs | 2 +- style/src/pick_list.rs | 70 +++++++++ wgpu/src/widget.rs | 6 +- wgpu/src/widget/combo_box.rs | 9 -- wgpu/src/widget/pick_list.rs | 9 ++ 23 files changed, 672 insertions(+), 672 deletions(-) delete mode 100644 examples/combo_box/Cargo.toml delete mode 100644 examples/combo_box/README.md delete mode 100644 examples/combo_box/src/main.rs create mode 100644 examples/pick_list/Cargo.toml create mode 100644 examples/pick_list/README.md create mode 100644 examples/pick_list/src/main.rs delete mode 100644 glow/src/widget/combo_box.rs create mode 100644 glow/src/widget/pick_list.rs delete mode 100644 graphics/src/widget/combo_box.rs create mode 100644 graphics/src/widget/pick_list.rs delete mode 100644 native/src/widget/combo_box.rs create mode 100644 native/src/widget/pick_list.rs delete mode 100644 style/src/combo_box.rs create mode 100644 style/src/pick_list.rs delete mode 100644 wgpu/src/widget/combo_box.rs create mode 100644 wgpu/src/widget/pick_list.rs diff --git a/Cargo.toml b/Cargo.toml index c62b5c9f..63ccb82e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,6 @@ members = [ "examples/bezier_tool", "examples/clock", "examples/color_palette", - "examples/combo_box", "examples/counter", "examples/custom_widget", "examples/download_progress", @@ -65,6 +64,7 @@ members = [ "examples/geometry", "examples/integration", "examples/pane_grid", + "examples/pick_list", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/combo_box/Cargo.toml b/examples/combo_box/Cargo.toml deleted file mode 100644 index 7e1e4133..00000000 --- a/examples/combo_box/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "combo_box" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2018" -publish = false - -[dependencies] -iced = { path = "../..", features = ["debug"] } -iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } diff --git a/examples/combo_box/README.md b/examples/combo_box/README.md deleted file mode 100644 index 4d9fc5b9..00000000 --- a/examples/combo_box/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Counter - -The classic counter example explained in the [`README`](../../README.md). - -The __[`main`]__ file contains all the code of the example. - -
- - - -
- -You can run it with `cargo run`: -``` -cargo run --package counter -``` - -[`main`]: src/main.rs diff --git a/examples/combo_box/src/main.rs b/examples/combo_box/src/main.rs deleted file mode 100644 index 416e9f76..00000000 --- a/examples/combo_box/src/main.rs +++ /dev/null @@ -1,121 +0,0 @@ -use iced::{ - button, combo_box, scrollable, Align, Button, ComboBox, Container, Element, - Length, Sandbox, Scrollable, Settings, Space, Text, -}; - -pub fn main() { - Example::run(Settings::default()) -} - -#[derive(Default)] -struct Example { - scroll: scrollable::State, - button: button::State, - combo_box: combo_box::State, - selected_language: Language, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - ButtonPressed, - LanguageSelected(Language), -} - -impl Sandbox for Example { - type Message = Message; - - fn new() -> Self { - Self::default() - } - - fn title(&self) -> String { - String::from("Combo box - Iced") - } - - fn update(&mut self, message: Message) { - match message { - Message::ButtonPressed => {} - Message::LanguageSelected(language) => { - self.selected_language = language; - } - } - } - - fn view(&mut self) -> Element { - let combo_box = ComboBox::new( - &mut self.combo_box, - &Language::ALL[..], - Some(self.selected_language), - Message::LanguageSelected, - ); - - let button = Button::new(&mut self.button, Text::new("Press me!")) - .on_press(Message::ButtonPressed); - - let mut content = Scrollable::new(&mut self.scroll) - .width(Length::Fill) - .align_items(Align::Center) - .spacing(10) - .push(Space::with_height(Length::Units(800))) - .push(Text::new("Which is your favorite language?")) - .push(combo_box); - - content = content - .push(button) - .push(Space::with_height(Length::Units(800))); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Language { - Rust, - Elm, - Ruby, - Haskell, - C, - Javascript, - Other, -} - -impl Language { - const ALL: [Language; 7] = [ - Language::C, - Language::Elm, - Language::Ruby, - Language::Haskell, - Language::Rust, - Language::Javascript, - Language::Other, - ]; -} - -impl Default for Language { - fn default() -> Language { - Language::Rust - } -} - -impl std::fmt::Display for Language { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}", - match self { - Language::Rust => "Rust", - Language::Elm => "Elm", - Language::Ruby => "Ruby", - Language::Haskell => "Haskell", - Language::C => "C", - Language::Javascript => "Javascript", - Language::Other => "Some other language", - } - ) - } -} diff --git a/examples/pick_list/Cargo.toml b/examples/pick_list/Cargo.toml new file mode 100644 index 00000000..a87d7217 --- /dev/null +++ b/examples/pick_list/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pick_list" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } diff --git a/examples/pick_list/README.md b/examples/pick_list/README.md new file mode 100644 index 00000000..4d9fc5b9 --- /dev/null +++ b/examples/pick_list/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + +
+ + + +
+ +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs new file mode 100644 index 00000000..4eb368d1 --- /dev/null +++ b/examples/pick_list/src/main.rs @@ -0,0 +1,121 @@ +use iced::{ + button, pick_list, scrollable, Align, Button, Container, Element, Length, + PickList, Sandbox, Scrollable, Settings, Space, Text, +}; + +pub fn main() { + Example::run(Settings::default()) +} + +#[derive(Default)] +struct Example { + scroll: scrollable::State, + button: button::State, + pick_list: pick_list::State, + selected_language: Language, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + ButtonPressed, + LanguageSelected(Language), +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Pick list - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::ButtonPressed => {} + Message::LanguageSelected(language) => { + self.selected_language = language; + } + } + } + + fn view(&mut self) -> Element { + let pick_list = PickList::new( + &mut self.pick_list, + &Language::ALL[..], + Some(self.selected_language), + Message::LanguageSelected, + ); + + let button = Button::new(&mut self.button, Text::new("Press me!")) + .on_press(Message::ButtonPressed); + + let mut content = Scrollable::new(&mut self.scroll) + .width(Length::Fill) + .align_items(Align::Center) + .spacing(10) + .push(Space::with_height(Length::Units(800))) + .push(Text::new("Which is your favorite language?")) + .push(pick_list); + + content = content + .push(button) + .push(Space::with_height(Length::Units(800))); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Javascript, + Other, +} + +impl Language { + const ALL: [Language; 7] = [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Javascript, + Language::Other, + ]; +} + +impl Default for Language { + fn default() -> Language { + Language::Rust + } +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Javascript => "Javascript", + Language::Other => "Some other language", + } + ) + } +} diff --git a/glow/src/widget.rs b/glow/src/widget.rs index c8f16725..4e2fedc5 100644 --- a/glow/src/widget.rs +++ b/glow/src/widget.rs @@ -11,9 +11,9 @@ use crate::Renderer; pub mod button; pub mod checkbox; -pub mod combo_box; pub mod container; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -25,12 +25,12 @@ pub use button::Button; #[doc(no_inline)] pub use checkbox::Checkbox; #[doc(no_inline)] -pub use combo_box::ComboBox; -#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/glow/src/widget/combo_box.rs b/glow/src/widget/combo_box.rs deleted file mode 100644 index 20feeaca..00000000 --- a/glow/src/widget/combo_box.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Display a dropdown list of selectable values. -pub use iced_native::combo_box::State; - -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/glow/src/widget/pick_list.rs b/glow/src/widget/pick_list.rs new file mode 100644 index 00000000..fccc68c9 --- /dev/null +++ b/glow/src/widget/pick_list.rs @@ -0,0 +1,9 @@ +//! Display a dropdown list of selectable values. +pub use iced_native::pick_list::State; + +pub use iced_graphics::overlay::menu::Style as Menu; +pub use iced_graphics::pick_list::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type PickList<'a, T, Message> = + iced_native::PickList<'a, T, Message, crate::Renderer>; diff --git a/graphics/src/widget.rs b/graphics/src/widget.rs index a0d06999..94a65011 100644 --- a/graphics/src/widget.rs +++ b/graphics/src/widget.rs @@ -9,10 +9,10 @@ //! ``` pub mod button; pub mod checkbox; -pub mod combo_box; pub mod container; pub mod image; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -34,6 +34,8 @@ pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/graphics/src/widget/combo_box.rs b/graphics/src/widget/combo_box.rs deleted file mode 100644 index f200c2b7..00000000 --- a/graphics/src/widget/combo_box.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! Display a dropdown list of selectable values. -use crate::backend::{self, Backend}; -use crate::{Primitive, Renderer}; -use iced_native::{ - mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, -}; -use iced_style::menu; - -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, - cursor_position: Point, - selected: Option, - padding: u16, - text_size: u16, - font: Font, - 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: 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 * style.icon_size, - bounds: Rectangle { - x: bounds.x + bounds.width - f32::from(padding) * 2.0, - y: bounds.center_y(), - ..bounds - }, - color: style.text_color, - horizontal_alignment: HorizontalAlignment::Right, - vertical_alignment: VerticalAlignment::Center, - }; - - ( - Primitive::Group { - primitives: if let Some(label) = selected { - let label = Primitive::Text { - content: label, - size: f32::from(text_size), - font, - color: style.text_color, - bounds: Rectangle { - x: bounds.x + f32::from(padding), - y: bounds.center_y(), - ..bounds - }, - horizontal_alignment: HorizontalAlignment::Left, - vertical_alignment: VerticalAlignment::Center, - }; - - vec![background, label, arrow_down] - } else { - vec![background, arrow_down] - }, - }, - if is_mouse_over { - mouse::Interaction::Pointer - } else { - mouse::Interaction::default() - }, - ) - } -} diff --git a/graphics/src/widget/pick_list.rs b/graphics/src/widget/pick_list.rs new file mode 100644 index 00000000..f42a8707 --- /dev/null +++ b/graphics/src/widget/pick_list.rs @@ -0,0 +1,97 @@ +//! Display a dropdown list of selectable values. +use crate::backend::{self, Backend}; +use crate::{Primitive, Renderer}; +use iced_native::{ + mouse, Font, HorizontalAlignment, Point, Rectangle, VerticalAlignment, +}; +use iced_style::menu; + +pub use iced_native::pick_list::State; +pub use iced_style::pick_list::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type PickList<'a, T, Message, Backend> = + iced_native::PickList<'a, T, Message, Renderer>; + +impl iced_native::pick_list::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, + cursor_position: Point, + selected: Option, + padding: u16, + text_size: u16, + font: Font, + 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: 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 * style.icon_size, + bounds: Rectangle { + x: bounds.x + bounds.width - f32::from(padding) * 2.0, + y: bounds.center_y(), + ..bounds + }, + color: style.text_color, + horizontal_alignment: HorizontalAlignment::Right, + vertical_alignment: VerticalAlignment::Center, + }; + + ( + Primitive::Group { + primitives: if let Some(label) = selected { + let label = Primitive::Text { + content: label, + size: f32::from(text_size), + font, + color: style.text_color, + bounds: Rectangle { + x: bounds.x + f32::from(padding), + y: bounds.center_y(), + ..bounds + }, + horizontal_alignment: HorizontalAlignment::Left, + vertical_alignment: VerticalAlignment::Center, + }; + + vec![background, label, arrow_down] + } else { + vec![background, arrow_down] + }, + }, + if is_mouse_over { + mouse::Interaction::Pointer + } else { + mouse::Interaction::default() + }, + ) + } +} diff --git a/native/src/widget.rs b/native/src/widget.rs index 931b4739..8539e519 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -23,10 +23,10 @@ pub mod button; pub mod checkbox; pub mod column; -pub mod combo_box; pub mod container; pub mod image; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod row; @@ -44,14 +44,14 @@ pub use checkbox::Checkbox; #[doc(no_inline)] pub use column::Column; #[doc(no_inline)] -pub use combo_box::ComboBox; -#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/native/src/widget/combo_box.rs b/native/src/widget/combo_box.rs deleted file mode 100644 index fefaf8ff..00000000 --- a/native/src/widget/combo_box.rs +++ /dev/null @@ -1,320 +0,0 @@ -//! Display a dropdown list of selectable values. -use crate::{ - layout, mouse, overlay, - overlay::menu::{self, Menu}, - scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point, - Rectangle, Size, Widget, -}; -use std::borrow::Cow; - -/// A widget for selecting a single value from a list of options. -#[allow(missing_debug_implementations)] -pub struct ComboBox<'a, T, Message, Renderer: self::Renderer> -where - [T]: ToOwned>, -{ - menu: &'a mut menu::State, - on_selected: Box Message>, - options: Cow<'a, [T]>, - selected: Option, - width: Length, - padding: u16, - text_size: Option, - font: Renderer::Font, - style: ::Style, - is_open: bool, -} - -/// The local state of a [`ComboBox`]. -/// -/// [`ComboBox`]: struct.ComboBox.html -#[derive(Debug, Clone, Default)] -pub struct State { - menu: menu::State, -} - -impl<'a, T: 'a, Message, Renderer: self::Renderer> - ComboBox<'a, T, Message, Renderer> -where - T: ToString, - [T]: ToOwned>, -{ - /// Creates a new [`ComboBox`] with the given [`State`], a list of options, - /// the current selected value, and the message to produce when an option is - /// selected. - /// - /// [`ComboBox`]: struct.ComboBox.html - /// [`State`]: struct.State.html - pub fn new( - state: &'a mut State, - options: impl Into>, - selected: Option, - on_selected: impl Fn(T) -> Message + 'static, - ) -> Self { - let is_open = state.menu.is_open(); - - Self { - menu: &mut state.menu, - on_selected: Box::new(on_selected), - options: options.into(), - selected, - width: Length::Shrink, - text_size: None, - padding: Renderer::DEFAULT_PADDING, - font: Default::default(), - style: Default::default(), - is_open, - } - } - - /// Sets the width of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn width(mut self, width: Length) -> Self { - self.width = width; - self - } - - /// Sets the padding of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn padding(mut self, padding: u16) -> Self { - self.padding = padding; - self - } - - /// Sets the text size of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn text_size(mut self, size: u16) -> Self { - self.text_size = Some(size); - self - } - - /// Sets the font of the [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - pub fn font(mut self, font: Renderer::Font) -> Self { - self.font = font; - 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 - for ComboBox<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq, - [T]: ToOwned>, - Message: 'static, - Renderer: self::Renderer + scrollable::Renderer + 'a, -{ - fn width(&self) -> Length { - Length::Shrink - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - renderer: &Renderer, - limits: &layout::Limits, - ) -> layout::Node { - use std::f32; - - let limits = limits - .width(self.width) - .height(Length::Shrink) - .pad(f32::from(self.padding)); - - let text_size = self.text_size.unwrap_or(renderer.default_size()); - - let max_width = match self.width { - Length::Shrink => { - let labels = self.options.iter().map(ToString::to_string); - - labels - .map(|label| { - let (width, _) = renderer.measure( - &label, - text_size, - Renderer::Font::default(), - Size::new(f32::INFINITY, f32::INFINITY), - ); - - width.round() as u32 - }) - .max() - .unwrap_or(100) - } - _ => 0, - }; - - let size = { - let intrinsic = Size::new( - max_width as f32 - + f32::from(text_size) - + f32::from(self.padding), - f32::from(text_size), - ); - - limits.resolve(intrinsic).pad(f32::from(self.padding)) - }; - - layout::Node::new(size) - } - - fn hash_layout(&self, state: &mut Hasher) { - use std::hash::Hash as _; - - match self.width { - Length::Shrink => { - self.options - .iter() - .map(ToString::to_string) - .for_each(|label| label.hash(state)); - } - _ => { - self.width.hash(state); - } - } - } - - fn on_event( - &mut self, - event: Event, - layout: Layout<'_>, - cursor_position: Point, - _messages: &mut Vec, - _renderer: &Renderer, - _clipboard: Option<&dyn Clipboard>, - ) { - if !self.is_open { - match event { - Event::Mouse(mouse::Event::ButtonPressed( - mouse::Button::Left, - )) => { - if layout.bounds().contains(cursor_position) { - let selected = self.selected.as_ref(); - - self.menu.open( - self.options - .iter() - .position(|option| Some(option) == selected), - ); - } - } - _ => {} - } - } - } - - fn draw( - &self, - renderer: &mut Renderer, - _defaults: &Renderer::Defaults, - layout: Layout<'_>, - cursor_position: Point, - ) -> Renderer::Output { - self::Renderer::draw( - renderer, - layout.bounds(), - cursor_position, - self.selected.as_ref().map(ToString::to_string), - self.padding, - self.text_size.unwrap_or(renderer.default_size()), - self.font, - &self.style, - ) - } - - fn overlay( - &mut self, - layout: Layout<'_>, - ) -> Option> { - if self.menu.is_open() { - let bounds = layout.bounds(); - - let mut menu = - Menu::new(&mut self.menu, &self.options, &self.on_selected) - .width(bounds.width.round() as u16) - .padding(self.padding) - .font(self.font) - .style(Renderer::menu_style(&self.style)); - - if let Some(text_size) = self.text_size { - menu = menu.text_size(text_size); - } - - Some(menu.overlay(layout.position(), bounds.height)) - } else { - None - } - } -} - -/// The renderer of a [`ComboBox`]. -/// -/// Your [renderer] will need to implement this trait before being -/// able to use a [`ComboBox`] in your user interface. -/// -/// [`ComboBox`]: struct.ComboBox.html -/// [renderer]: ../../renderer/index.html -pub trait Renderer: text::Renderer + menu::Renderer { - /// The default padding of a [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - const DEFAULT_PADDING: u16; - - /// The [`ComboBox`] style supported by this renderer. - /// - /// [`ComboBox`]: struct.ComboBox.html - type Style: Default; - - /// Returns the style of the [`Menu`] of the [`ComboBox`]. - /// - /// [`Menu`]: ../../overlay/menu/struct.Menu.html - /// [`ComboBox`]: struct.ComboBox.html - fn menu_style( - style: &::Style, - ) -> ::Style; - - /// Draws a [`ComboBox`]. - /// - /// [`ComboBox`]: struct.ComboBox.html - fn draw( - &mut self, - bounds: Rectangle, - cursor_position: Point, - selected: Option, - padding: u16, - text_size: u16, - font: Self::Font, - style: &::Style, - ) -> Self::Output; -} - -impl<'a, T: 'a, Message, Renderer> Into> - for ComboBox<'a, T, Message, Renderer> -where - T: Clone + ToString + Eq, - [T]: ToOwned>, - Renderer: self::Renderer + 'a, - Message: 'static, -{ - fn into(self) -> Element<'a, Message, Renderer> { - Element::new(self) - } -} diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs new file mode 100644 index 00000000..9f62e550 --- /dev/null +++ b/native/src/widget/pick_list.rs @@ -0,0 +1,320 @@ +//! Display a dropdown list of selectable values. +use crate::{ + layout, mouse, overlay, + overlay::menu::{self, Menu}, + scrollable, text, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, +}; +use std::borrow::Cow; + +/// A widget for selecting a single value from a list of options. +#[allow(missing_debug_implementations)] +pub struct PickList<'a, T, Message, Renderer: self::Renderer> +where + [T]: ToOwned>, +{ + menu: &'a mut menu::State, + on_selected: Box Message>, + options: Cow<'a, [T]>, + selected: Option, + width: Length, + padding: u16, + text_size: Option, + font: Renderer::Font, + style: ::Style, + is_open: bool, +} + +/// The local state of a [`PickList`]. +/// +/// [`PickList`]: struct.PickList.html +#[derive(Debug, Clone, Default)] +pub struct State { + menu: menu::State, +} + +impl<'a, T: 'a, Message, Renderer: self::Renderer> + PickList<'a, T, Message, Renderer> +where + T: ToString, + [T]: ToOwned>, +{ + /// Creates a new [`PickList`] with the given [`State`], a list of options, + /// the current selected value, and the message to produce when an option is + /// selected. + /// + /// [`PickList`]: struct.PickList.html + /// [`State`]: struct.State.html + pub fn new( + state: &'a mut State, + options: impl Into>, + selected: Option, + on_selected: impl Fn(T) -> Message + 'static, + ) -> Self { + let is_open = state.menu.is_open(); + + Self { + menu: &mut state.menu, + on_selected: Box::new(on_selected), + options: options.into(), + selected, + width: Length::Shrink, + text_size: None, + padding: Renderer::DEFAULT_PADDING, + font: Default::default(), + style: Default::default(), + is_open, + } + } + + /// Sets the width of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the padding of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn padding(mut self, padding: u16) -> Self { + self.padding = padding; + self + } + + /// Sets the text size of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn text_size(mut self, size: u16) -> Self { + self.text_size = Some(size); + self + } + + /// Sets the font of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn font(mut self, font: Renderer::Font) -> Self { + self.font = font; + self + } + + /// Sets the style of the [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + pub fn style( + mut self, + style: impl Into<::Style>, + ) -> Self { + self.style = style.into(); + self + } +} + +impl<'a, T: 'a, Message, Renderer> Widget + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq, + [T]: ToOwned>, + Message: 'static, + Renderer: self::Renderer + scrollable::Renderer + 'a, +{ + fn width(&self) -> Length { + Length::Shrink + } + + fn height(&self) -> Length { + Length::Shrink + } + + fn layout( + &self, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + use std::f32; + + let limits = limits + .width(self.width) + .height(Length::Shrink) + .pad(f32::from(self.padding)); + + let text_size = self.text_size.unwrap_or(renderer.default_size()); + + let max_width = match self.width { + Length::Shrink => { + let labels = self.options.iter().map(ToString::to_string); + + labels + .map(|label| { + let (width, _) = renderer.measure( + &label, + text_size, + Renderer::Font::default(), + Size::new(f32::INFINITY, f32::INFINITY), + ); + + width.round() as u32 + }) + .max() + .unwrap_or(100) + } + _ => 0, + }; + + let size = { + let intrinsic = Size::new( + max_width as f32 + + f32::from(text_size) + + f32::from(self.padding), + f32::from(text_size), + ); + + limits.resolve(intrinsic).pad(f32::from(self.padding)) + }; + + layout::Node::new(size) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash as _; + + match self.width { + Length::Shrink => { + self.options + .iter() + .map(ToString::to_string) + .for_each(|label| label.hash(state)); + } + _ => { + self.width.hash(state); + } + } + } + + fn on_event( + &mut self, + event: Event, + layout: Layout<'_>, + cursor_position: Point, + _messages: &mut Vec, + _renderer: &Renderer, + _clipboard: Option<&dyn Clipboard>, + ) { + if !self.is_open { + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => { + if layout.bounds().contains(cursor_position) { + let selected = self.selected.as_ref(); + + self.menu.open( + self.options + .iter() + .position(|option| Some(option) == selected), + ); + } + } + _ => {} + } + } + } + + fn draw( + &self, + renderer: &mut Renderer, + _defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + self::Renderer::draw( + renderer, + layout.bounds(), + cursor_position, + self.selected.as_ref().map(ToString::to_string), + self.padding, + self.text_size.unwrap_or(renderer.default_size()), + self.font, + &self.style, + ) + } + + fn overlay( + &mut self, + layout: Layout<'_>, + ) -> Option> { + if self.menu.is_open() { + let bounds = layout.bounds(); + + let mut menu = + Menu::new(&mut self.menu, &self.options, &self.on_selected) + .width(bounds.width.round() as u16) + .padding(self.padding) + .font(self.font) + .style(Renderer::menu_style(&self.style)); + + if let Some(text_size) = self.text_size { + menu = menu.text_size(text_size); + } + + Some(menu.overlay(layout.position(), bounds.height)) + } else { + None + } + } +} + +/// The renderer of a [`PickList`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use a [`PickList`] in your user interface. +/// +/// [`PickList`]: struct.PickList.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: text::Renderer + menu::Renderer { + /// The default padding of a [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + const DEFAULT_PADDING: u16; + + /// The [`PickList`] style supported by this renderer. + /// + /// [`PickList`]: struct.PickList.html + type Style: Default; + + /// Returns the style of the [`Menu`] of the [`PickList`]. + /// + /// [`Menu`]: ../../overlay/menu/struct.Menu.html + /// [`PickList`]: struct.PickList.html + fn menu_style( + style: &::Style, + ) -> ::Style; + + /// Draws a [`PickList`]. + /// + /// [`PickList`]: struct.PickList.html + fn draw( + &mut self, + bounds: Rectangle, + cursor_position: Point, + selected: Option, + padding: u16, + text_size: u16, + font: Self::Font, + style: &::Style, + ) -> Self::Output; +} + +impl<'a, T: 'a, Message, Renderer> Into> + for PickList<'a, T, Message, Renderer> +where + T: Clone + ToString + Eq, + [T]: ToOwned>, + Renderer: self::Renderer + 'a, + Message: 'static, +{ + fn into(self) -> Element<'a, Message, Renderer> { + Element::new(self) + } +} diff --git a/src/widget.rs b/src/widget.rs index 034f02cd..b26f14d4 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -19,7 +19,7 @@ #[cfg(not(target_arch = "wasm32"))] mod platform { pub use crate::renderer::widget::{ - button, checkbox, combo_box, container, pane_grid, progress_bar, radio, + button, checkbox, container, pane_grid, pick_list, progress_bar, radio, scrollable, slider, text_input, Column, Row, Space, Text, }; @@ -44,10 +44,10 @@ mod platform { #[doc(no_inline)] pub use { - button::Button, checkbox::Checkbox, combo_box::ComboBox, - container::Container, image::Image, pane_grid::PaneGrid, - progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, - slider::Slider, svg::Svg, text_input::TextInput, + button::Button, checkbox::Checkbox, container::Container, image::Image, + pane_grid::PaneGrid, pick_list::PickList, progress_bar::ProgressBar, + radio::Radio, scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, }; #[cfg(any(feature = "canvas", feature = "glow_canvas"))] diff --git a/style/src/combo_box.rs b/style/src/combo_box.rs deleted file mode 100644 index 4d0c4e46..00000000 --- a/style/src/combo_box.rs +++ /dev/null @@ -1,70 +0,0 @@ -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 b19d6600..8e402bb1 100644 --- a/style/src/lib.rs +++ b/style/src/lib.rs @@ -6,9 +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 pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; diff --git a/style/src/pick_list.rs b/style/src/pick_list.rs new file mode 100644 index 00000000..fbd431c0 --- /dev/null +++ b/style/src/pick_list.rs @@ -0,0 +1,70 @@ +use crate::menu; +use iced_core::{Background, Color}; + +/// The appearance of a pick list. +#[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/wgpu/src/widget.rs b/wgpu/src/widget.rs index 0f390c8d..ced64332 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -11,9 +11,9 @@ use crate::Renderer; pub mod button; pub mod checkbox; -pub mod combo_box; pub mod container; pub mod pane_grid; +pub mod pick_list; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -25,12 +25,12 @@ pub use button::Button; #[doc(no_inline)] pub use checkbox::Checkbox; #[doc(no_inline)] -pub use combo_box::ComboBox; -#[doc(no_inline)] pub use container::Container; #[doc(no_inline)] pub use pane_grid::PaneGrid; #[doc(no_inline)] +pub use pick_list::PickList; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/wgpu/src/widget/combo_box.rs b/wgpu/src/widget/combo_box.rs deleted file mode 100644 index 20feeaca..00000000 --- a/wgpu/src/widget/combo_box.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! Display a dropdown list of selectable values. -pub use iced_native::combo_box::State; - -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/wgpu/src/widget/pick_list.rs b/wgpu/src/widget/pick_list.rs new file mode 100644 index 00000000..fccc68c9 --- /dev/null +++ b/wgpu/src/widget/pick_list.rs @@ -0,0 +1,9 @@ +//! Display a dropdown list of selectable values. +pub use iced_native::pick_list::State; + +pub use iced_graphics::overlay::menu::Style as Menu; +pub use iced_graphics::pick_list::{Style, StyleSheet}; + +/// A widget allowing the selection of a single value from a list of options. +pub type PickList<'a, T, Message> = + iced_native::PickList<'a, T, Message, crate::Renderer>; -- cgit