feat(dropdown): optional icons for dropdowns

This commit is contained in:
Michael Aaron Murphy 2024-11-25 06:51:16 +01:00
parent 52ab37c1eb
commit 9a8a56952d
No known key found for this signature in database
GPG key ID: B2732D4240C9212C
2 changed files with 59 additions and 33 deletions

View file

@ -5,7 +5,7 @@
mod appearance; mod appearance;
pub use appearance::{Appearance, StyleSheet}; pub use appearance::{Appearance, StyleSheet};
use crate::widget::Container; use crate::widget::{icon, Container};
use iced_core::event::{self, Event}; use iced_core::event::{self, Event};
use iced_core::layout::{self, Layout}; use iced_core::layout::{self, Layout};
use iced_core::text::{self, Text}; use iced_core::text::{self, Text};
@ -24,6 +24,7 @@ where
{ {
state: &'a mut State, state: &'a mut State,
options: &'a [S], options: &'a [S],
icons: &'a [icon::Handle],
hovered_option: &'a mut Option<usize>, hovered_option: &'a mut Option<usize>,
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: Box<dyn FnMut(usize) -> Message + 'a>, on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
@ -41,6 +42,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
pub fn new( pub fn new(
state: &'a mut State, state: &'a mut State,
options: &'a [S], options: &'a [S],
icons: &'a [icon::Handle],
hovered_option: &'a mut Option<usize>, hovered_option: &'a mut Option<usize>,
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: impl FnMut(usize) -> Message + 'a, on_selected: impl FnMut(usize) -> Message + 'a,
@ -49,6 +51,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Menu<'a, S, Message> {
Menu { Menu {
state, state,
options, options,
icons,
hovered_option, hovered_option,
selected_option, selected_option,
on_selected: Box::new(on_selected), on_selected: Box::new(on_selected),
@ -141,6 +144,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
let Menu { let Menu {
state, state,
options, options,
icons,
hovered_option, hovered_option,
selected_option, selected_option,
on_selected, on_selected,
@ -154,6 +158,7 @@ impl<'a, Message: 'a> Overlay<'a, Message> {
let mut container = Container::new(Scrollable::new(List { let mut container = Container::new(Scrollable::new(List {
options, options,
icons,
hovered_option, hovered_option,
selected_option, selected_option,
on_selected, on_selected,
@ -268,6 +273,7 @@ impl<'a, Message> iced_core::Overlay<Message, crate::Theme, crate::Renderer>
struct List<'a, S: AsRef<str>, Message> { struct List<'a, S: AsRef<str>, Message> {
options: &'a [S], options: &'a [S],
icons: &'a [icon::Handle],
hovered_option: &'a mut Option<usize>, hovered_option: &'a mut Option<usize>,
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: Box<dyn FnMut(usize) -> Message + 'a>, on_selected: Box<dyn FnMut(usize) -> Message + 'a>,
@ -395,12 +401,12 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
fn draw( fn draw(
&self, &self,
_state: &Tree, state: &Tree,
renderer: &mut crate::Renderer, renderer: &mut crate::Renderer,
theme: &crate::Theme, theme: &crate::Theme,
_style: &renderer::Style, style: &renderer::Style,
layout: Layout<'_>, layout: Layout<'_>,
_cursor: mouse::Cursor, cursor: mouse::Cursor,
viewport: &Rectangle, viewport: &Rectangle,
) { ) {
let appearance = theme.appearance(&()); let appearance = theme.appearance(&());
@ -452,6 +458,7 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
iced_core::Svg::new(crate::widget::common::object_select().clone()) iced_core::Svg::new(crate::widget::common::object_select().clone())
.color(appearance.selected_text_color) .color(appearance.selected_text_color)
.border_radius(appearance.border_radius); .border_radius(appearance.border_radius);
svg::Renderer::draw_svg( svg::Renderer::draw_svg(
renderer, renderer,
svg_handle, svg_handle,
@ -489,12 +496,25 @@ impl<'a, S: AsRef<str>, Message> Widget<Message, crate::Theme, crate::Renderer>
(appearance.text_color, crate::font::default()) (appearance.text_color, crate::font::default())
}; };
let bounds = Rectangle { let mut bounds = Rectangle {
x: bounds.x + self.padding.left, x: bounds.x + self.padding.left,
y: bounds.center_y(), y: bounds.center_y(),
width: f32::INFINITY, width: f32::INFINITY,
..bounds ..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( text::Renderer::fill_text(
renderer, renderer,
Text { Text {

View file

@ -24,6 +24,8 @@ pub struct Dropdown<'a, S: AsRef<str>, Message> {
on_selected: Box<dyn Fn(usize) -> Message + 'a>, on_selected: Box<dyn Fn(usize) -> Message + 'a>,
#[setters(skip)] #[setters(skip)]
selections: &'a [S], selections: &'a [S],
#[setters]
icons: &'a [icon::Handle],
#[setters(skip)] #[setters(skip)]
selected: Option<usize>, selected: Option<usize>,
#[setters(into)] #[setters(into)]
@ -55,6 +57,7 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
Self { Self {
on_selected: Box::new(on_selected), on_selected: Box::new(on_selected),
selections, selections,
icons: &[],
selected, selected,
width: Length::Shrink, width: Length::Shrink,
gap: Self::DEFAULT_GAP, gap: Self::DEFAULT_GAP,
@ -64,29 +67,6 @@ impl<'a, S: AsRef<str>, Message> Dropdown<'a, S, Message> {
font: None, font: None,
} }
} }
fn update_paragraphs(&self, state: &mut tree::State) {
let state = state.downcast_mut::<State>();
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<str>, Message: 'a> Widget<Message, crate::Theme, crate::Renderer> impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Renderer>
@ -158,6 +138,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
.map(AsRef::as_ref) .map(AsRef::as_ref)
.zip(tree.state.downcast_mut::<State>().selections.get_mut(id)) .zip(tree.state.downcast_mut::<State>().selections.get_mut(id))
}), }),
!self.icons.is_empty(),
) )
} }
@ -217,6 +198,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
self.text_line_height, self.text_line_height,
font, font,
self.selected.and_then(|id| self.selections.get(id)), self.selected.and_then(|id| self.selections.get(id)),
self.selected.and_then(|id| self.icons.get(id)),
tree.state.downcast_ref::<State>(), tree.state.downcast_ref::<State>(),
viewport, viewport,
); );
@ -241,6 +223,7 @@ impl<'a, S: AsRef<str>, Message: 'a> Widget<Message, crate::Theme, crate::Render
self.text_line_height, self.text_line_height,
self.font, self.font,
self.selections, self.selections,
self.icons,
self.selected, self.selected,
&self.on_selected, &self.on_selected,
translation, translation,
@ -319,6 +302,7 @@ pub fn layout(
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
font: Option<crate::font::Font>, font: Option<crate::font::Font>,
selection: Option<(&str, &mut crate::Plain)>, selection: Option<(&str, &mut crate::Plain)>,
has_icons: bool,
) -> layout::Node { ) -> layout::Node {
use std::f32; use std::f32;
@ -346,9 +330,11 @@ pub fn layout(
_ => 0.0, _ => 0.0,
}; };
let icon_size = if has_icons { 24.0 } else { 0.0 };
let size = { let size = {
let intrinsic = Size::new( 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))), f32::from(text_line_height.to_absolute(Pixels(text_size))),
); );
@ -449,6 +435,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
_text_line_height: text::LineHeight, _text_line_height: text::LineHeight,
_font: Option<crate::font::Font>, _font: Option<crate::font::Font>,
selections: &'a [S], selections: &'a [S],
icons: &'a [icon::Handle],
selected_option: Option<usize>, selected_option: Option<usize>,
on_selected: &'a dyn Fn(usize) -> Message, on_selected: &'a dyn Fn(usize) -> Message,
translation: Vector, translation: Vector,
@ -459,6 +446,7 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
let menu = Menu::new( let menu = Menu::new(
&mut state.menu, &mut state.menu,
selections, selections,
icons,
&mut state.hovered_option, &mut state.hovered_option,
selected_option, selected_option,
|option| { |option| {
@ -473,15 +461,18 @@ pub fn overlay<'a, S: AsRef<str>, Message: 'a>(
selection_paragraph.min_width().round() 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 selections
.iter() .iter()
.zip(state.selections.iter_mut()) .zip(state.selections.iter_mut())
.map(|(label, selection)| measure(label.as_ref(), selection.raw())) .map(|(label, selection)| measure(label.as_ref(), selection.raw()))
.fold(0.0, |next, current| current.max(next)) .fold(0.0, |next, current| current.max(next))
+ gap + gap
+ 16.0 + pad_width
+ padding.horizontal() + icon_width
+ padding.horizontal()
}) })
.padding(padding) .padding(padding)
.text_size(text_size); .text_size(text_size);
@ -509,6 +500,7 @@ pub fn draw<'a, S>(
text_line_height: text::LineHeight, text_line_height: text::LineHeight,
font: crate::font::Font, font: crate::font::Font,
selected: Option<&'a S>, selected: Option<&'a S>,
icon: Option<&'a icon::Handle>,
state: &'a State, state: &'a State,
viewport: &Rectangle, viewport: &Rectangle,
) where ) where
@ -550,12 +542,26 @@ pub fn draw<'a, S>(
if let Some(content) = selected.map(AsRef::as_ref) { if let Some(content) = selected.map(AsRef::as_ref) {
let text_size = text_size.unwrap_or_else(|| text::Renderer::default_size(renderer).0); 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, x: bounds.x + padding.left,
y: bounds.center_y(), y: bounds.center_y(),
width: bounds.width - padding.horizontal(), width: bounds.width - padding.horizontal(),
height: f32::from(text_line_height.to_absolute(Pixels(text_size))), 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( text::Renderer::fill_text(
renderer, renderer,
Text { Text {