list_item: optional leading icon
Add a leading-icon slot to ListItem so settings-style rows can pair a 24 px symbolic glyph with the label. `.icon( rgba, w, h )` takes the same shape the rest of the toolkit uses for raw pixmaps; the draw path reserves `ICON_SIZE + ICON_GAP` on the left and shifts the label / subtitle text origin so existing icon-less rows render unchanged.
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-only
|
||||
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::types::{ Rect, WidgetId };
|
||||
use crate::render::Canvas;
|
||||
use super::Element;
|
||||
@@ -49,9 +51,14 @@ pub struct ListItem<Msg: Clone>
|
||||
pub id: Option<WidgetId>,
|
||||
/// `true` paints the row with the dark selected surface and white
|
||||
/// text, regardless of hover / press state. Use to indicate the
|
||||
/// active item in a list of choices (combo dropdown, segmented
|
||||
/// picker, settings group with a single active value).
|
||||
/// active item in a list of choices (combo dropdown, settings
|
||||
/// group with a single active value).
|
||||
pub selected: bool,
|
||||
/// Optional leading icon — RGBA bytes + native dimensions. The
|
||||
/// row reserves `theme::ICON_SIZE + theme::ICON_GAP` on the left
|
||||
/// when this is set, and offsets the label / subtitle by the
|
||||
/// same amount. Pass `None` to keep the icon-less layout.
|
||||
pub icon: Option<( Arc<Vec<u8>>, u32, u32 )>,
|
||||
}
|
||||
|
||||
impl<Msg: Clone> ListItem<Msg>
|
||||
@@ -68,9 +75,21 @@ impl<Msg: Clone> ListItem<Msg>
|
||||
on_press: None,
|
||||
id: None,
|
||||
selected: false,
|
||||
icon: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a leading icon. Pass the decoded RGBA buffer alongside
|
||||
/// the image's native width and height; the draw path scales it
|
||||
/// down to [`theme::ICON_SIZE`] on the same row baseline as the
|
||||
/// label. Symbolic icons should be pre-tinted by the caller (see
|
||||
/// [`crate::tint_symbolic`]).
|
||||
pub fn icon( mut self, rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> Self
|
||||
{
|
||||
self.icon = Some( ( rgba, w, h ) );
|
||||
self
|
||||
}
|
||||
|
||||
/// Mark this row as the currently-selected option in its list.
|
||||
/// Selected rows paint with a dark surface and white text and
|
||||
/// override hover / press visuals.
|
||||
@@ -161,12 +180,29 @@ impl<Msg: Clone> ListItem<Msg>
|
||||
rect.y + ( rect.height + theme::LABEL_SIZE ) / 2.0 - 2.0
|
||||
};
|
||||
|
||||
canvas.draw_text( &self.label, rect.x + theme::PAD_H, label_y, theme::LABEL_SIZE, label_color );
|
||||
// Leading-icon column shifts the text right by
|
||||
// `ICON_SIZE + ICON_GAP`. Symbolic icons (single channel
|
||||
// pre-tinted) draw at full opacity; the colour comes from
|
||||
// the bytes the caller passed in, which is why the helper
|
||||
// is in `theme::icon_rgba` rather than baked here.
|
||||
let text_x = if let Some( ( rgba, w, h ) ) = &self.icon
|
||||
{
|
||||
let icon_y = rect.y + ( rect.height - theme::ICON_SIZE ) / 2.0;
|
||||
let icon_rect = Rect { x: rect.x + theme::PAD_H, y: icon_y, width: theme::ICON_SIZE, height: theme::ICON_SIZE };
|
||||
canvas.draw_image_data( rgba, *w, *h, icon_rect, 1.0 );
|
||||
rect.x + theme::PAD_H + theme::ICON_SIZE + theme::ICON_GAP
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.x + theme::PAD_H
|
||||
};
|
||||
|
||||
canvas.draw_text( &self.label, text_x, label_y, theme::LABEL_SIZE, label_color );
|
||||
|
||||
if let Some( ref sub ) = self.subtitle
|
||||
{
|
||||
let sub_y = rect.y + rect.height * 0.62 + theme::SUBTITLE_SIZE * 0.3;
|
||||
canvas.draw_text( sub, rect.x + theme::PAD_H, sub_y, theme::SUBTITLE_SIZE, subtitle_color );
|
||||
canvas.draw_text( sub, text_x, sub_y, theme::SUBTITLE_SIZE, subtitle_color );
|
||||
}
|
||||
|
||||
if let Some( ref trail ) = self.trailing
|
||||
@@ -191,6 +227,7 @@ impl<Msg: Clone> ListItem<Msg>
|
||||
on_press: self.on_press.map( |m| ( *f )( m ) ),
|
||||
id: self.id,
|
||||
selected: self.selected,
|
||||
icon: self.icon,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,3 +31,7 @@ pub const HEIGHT_SUB: f32 = 68.0;
|
||||
pub const PAD_H: f32 = 16.0;
|
||||
pub const RADIUS: f32 = 12.0;
|
||||
pub const FOCUS_W: f32 = 2.0;
|
||||
/// Visible side of the optional leading icon (square).
|
||||
pub const ICON_SIZE: f32 = 24.0;
|
||||
/// Gap between the leading icon's right edge and the label baseline.
|
||||
pub const ICON_GAP: f32 = 12.0;
|
||||
|
||||
Reference in New Issue
Block a user