From 9a8a56952da8ad25a7482a6d554dd49041dc6718 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 25 Nov 2024 06:51:16 +0100 Subject: [PATCH] feat(dropdown): optional icons for dropdowns --- src/widget/dropdown/menu/mod.rs | 30 +++++++++++++--- src/widget/dropdown/widget.rs | 62 ++++++++++++++++++--------------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/src/widget/dropdown/menu/mod.rs b/src/widget/dropdown/menu/mod.rs index 73204f3b..66574420 100644 --- a/src/widget/dropdown/menu/mod.rs +++ b/src/widget/dropdown/menu/mod.rs @@ -5,7 +5,7 @@ mod appearance; pub use appearance::{Appearance, StyleSheet}; -use crate::widget::Container; +use crate::widget::{icon, Container}; use iced_core::event::{self, Event}; use iced_core::layout::{self, Layout}; use iced_core::text::{self, Text}; @@ -24,6 +24,7 @@ where { state: &'a mut State, options: &'a [S], + icons: &'a [icon::Handle], hovered_option: &'a mut Option, selected_option: Option, on_selected: Box Message + 'a>, @@ -41,6 +42,7 @@ impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { pub fn new( state: &'a mut State, options: &'a [S], + icons: &'a [icon::Handle], hovered_option: &'a mut Option, selected_option: Option, on_selected: impl FnMut(usize) -> Message + 'a, @@ -49,6 +51,7 @@ impl<'a, S: AsRef, Message: 'a> Menu<'a, S, Message> { Menu { state, options, + icons, hovered_option, selected_option, on_selected: Box::new(on_selected), @@ -141,6 +144,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { let Menu { state, options, + icons, hovered_option, selected_option, on_selected, @@ -154,6 +158,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> { let mut container = Container::new(Scrollable::new(List { options, + icons, hovered_option, selected_option, on_selected, @@ -268,6 +273,7 @@ impl<'a, Message> iced_core::Overlay struct List<'a, S: AsRef, Message> { options: &'a [S], + icons: &'a [icon::Handle], hovered_option: &'a mut Option, selected_option: Option, on_selected: Box Message + 'a>, @@ -395,12 +401,12 @@ impl<'a, S: AsRef, Message> Widget fn draw( &self, - _state: &Tree, + state: &Tree, renderer: &mut crate::Renderer, theme: &crate::Theme, - _style: &renderer::Style, + style: &renderer::Style, layout: Layout<'_>, - _cursor: mouse::Cursor, + cursor: mouse::Cursor, viewport: &Rectangle, ) { let appearance = theme.appearance(&()); @@ -452,6 +458,7 @@ impl<'a, S: AsRef, Message> Widget iced_core::Svg::new(crate::widget::common::object_select().clone()) .color(appearance.selected_text_color) .border_radius(appearance.border_radius); + svg::Renderer::draw_svg( renderer, svg_handle, @@ -489,12 +496,25 @@ impl<'a, S: AsRef, Message> Widget (appearance.text_color, crate::font::default()) }; - let bounds = Rectangle { + let mut bounds = Rectangle { x: bounds.x + self.padding.left, y: bounds.center_y(), width: f32::INFINITY, ..bounds }; + + if let Some(handle) = self.icons.get(i) { + let icon_bounds = Rectangle { + x: bounds.x, + y: bounds.y + 8.0 - (bounds.height / 2.0), + width: 20.0, + height: 20.0, + }; + + bounds.x += 24.0; + icon::draw(renderer, handle, icon_bounds); + } + text::Renderer::fill_text( renderer, Text { diff --git a/src/widget/dropdown/widget.rs b/src/widget/dropdown/widget.rs index 20d46a20..26f69cce 100644 --- a/src/widget/dropdown/widget.rs +++ b/src/widget/dropdown/widget.rs @@ -24,6 +24,8 @@ pub struct Dropdown<'a, S: AsRef, Message> { on_selected: Box Message + 'a>, #[setters(skip)] selections: &'a [S], + #[setters] + icons: &'a [icon::Handle], #[setters(skip)] selected: Option, #[setters(into)] @@ -55,6 +57,7 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { Self { on_selected: Box::new(on_selected), selections, + icons: &[], selected, width: Length::Shrink, gap: Self::DEFAULT_GAP, @@ -64,29 +67,6 @@ impl<'a, S: AsRef, Message> Dropdown<'a, S, Message> { font: None, } } - - fn update_paragraphs(&self, state: &mut tree::State) { - let state = state.downcast_mut::(); - - state - .selections - .resize_with(self.selections.len(), crate::Plain::default); - for (i, selection) in self.selections.iter().enumerate() { - state.selections[i].update(Text { - content: selection.as_ref(), - bounds: Size::INFINITY, - // TODO use the renderer default size - size: iced::Pixels(self.text_size.unwrap_or(14.0)), - - line_height: self.text_line_height, - font: self.font.unwrap_or(crate::font::default()), - horizontal_alignment: alignment::Horizontal::Left, - vertical_alignment: alignment::Vertical::Top, - shaping: text::Shaping::Advanced, - wrapping: text::Wrapping::default(), - }); - } - } } impl<'a, S: AsRef, Message: 'a> Widget @@ -158,6 +138,7 @@ impl<'a, S: AsRef, Message: 'a> Widget().selections.get_mut(id)) }), + !self.icons.is_empty(), ) } @@ -217,6 +198,7 @@ impl<'a, S: AsRef, Message: 'a> Widget(), viewport, ); @@ -241,6 +223,7 @@ impl<'a, S: AsRef, Message: 'a> Widget, selection: Option<(&str, &mut crate::Plain)>, + has_icons: bool, ) -> layout::Node { use std::f32; @@ -346,9 +330,11 @@ pub fn layout( _ => 0.0, }; + let icon_size = if has_icons { 24.0 } else { 0.0 }; + let size = { let intrinsic = Size::new( - max_width + gap + 16.0, + max_width + icon_size + gap + 16.0, f32::from(text_line_height.to_absolute(Pixels(text_size))), ); @@ -449,6 +435,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( _text_line_height: text::LineHeight, _font: Option, selections: &'a [S], + icons: &'a [icon::Handle], selected_option: Option, on_selected: &'a dyn Fn(usize) -> Message, translation: Vector, @@ -459,6 +446,7 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( let menu = Menu::new( &mut state.menu, selections, + icons, &mut state.hovered_option, selected_option, |option| { @@ -473,15 +461,18 @@ pub fn overlay<'a, S: AsRef, Message: 'a>( selection_paragraph.min_width().round() }; + let pad_width = padding.horizontal().mul_add(2.0, 16.0); + + let icon_width = if icons.is_empty() { 0.0 } else { 24.0 }; + selections .iter() .zip(state.selections.iter_mut()) .map(|(label, selection)| measure(label.as_ref(), selection.raw())) .fold(0.0, |next, current| current.max(next)) + gap - + 16.0 - + padding.horizontal() - + padding.horizontal() + + pad_width + + icon_width }) .padding(padding) .text_size(text_size); @@ -509,6 +500,7 @@ pub fn draw<'a, S>( text_line_height: text::LineHeight, font: crate::font::Font, selected: Option<&'a S>, + icon: Option<&'a icon::Handle>, state: &'a State, viewport: &Rectangle, ) where @@ -550,12 +542,26 @@ pub fn draw<'a, S>( if let Some(content) = selected.map(AsRef::as_ref) { let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer).0); - let bounds = Rectangle { + + let mut bounds = Rectangle { x: bounds.x + padding.left, y: bounds.center_y(), width: bounds.width - padding.horizontal(), height: f32::from(text_line_height.to_absolute(Pixels(text_size))), }; + + if let Some(handle) = icon { + let icon_bounds = Rectangle { + x: bounds.x, + y: bounds.y - (bounds.height / 2.0) - 2.0, + width: 20.0, + height: 20.0, + }; + + bounds.x += 24.0; + icon::draw(renderer, handle, icon_bounds); + } + text::Renderer::fill_text( renderer, Text {