use std::sync::Arc;
use egui::text::TextWrapping;
use egui::{Align, Align2, NumExt as _, RichText, Ui};
use super::{
ContentContext, DesiredWidth, ListItemContent, ListItemContentButtonsExt, ListVisuals,
};
use crate::list_item::item_buttons::ItemButtons;
use crate::{DesignTokens, Icon, LabelStyle, UiExt as _};
#[expect(clippy::type_complexity)]
pub struct LabelContent<'a> {
text: egui::WidgetText,
subdued: bool,
weak: bool,
italics: bool,
strong: bool,
label_style: LabelStyle,
icon_fn: Option<Box<dyn FnOnce(&mut egui::Ui, egui::Rect, ListVisuals) + 'a>>,
buttons: ItemButtons<'a>,
text_wrap_mode: Option<egui::TextWrapMode>,
min_desired_width: Option<f32>,
}
impl<'a> LabelContent<'a> {
pub fn new(text: impl Into<egui::WidgetText>) -> Self {
Self {
text: text.into(),
subdued: false,
weak: false,
italics: false,
strong: false,
label_style: Default::default(),
icon_fn: None,
buttons: ItemButtons::default(),
text_wrap_mode: None,
min_desired_width: None,
}
}
pub fn header(text: impl Into<RichText>) -> Self {
Self::new(text.into().size(DesignTokens::list_header_font_size())).strong(true)
}
#[inline]
pub fn subdued(mut self, subdued: bool) -> Self {
self.subdued = subdued;
self
}
#[inline]
pub fn weak(mut self, weak: bool) -> Self {
self.weak = weak;
self
}
#[inline]
pub fn italics(mut self, italics: bool) -> Self {
self.italics = italics;
self
}
#[inline]
pub fn strong(mut self, strong: bool) -> Self {
self.strong = strong;
self
}
#[inline]
pub fn label_style(mut self, style: crate::LabelStyle) -> Self {
self.label_style = style;
self
}
#[inline]
pub fn truncate(mut self, truncate: bool) -> Self {
self.text_wrap_mode = Some(if truncate {
egui::TextWrapMode::Truncate
} else {
egui::TextWrapMode::Extend
});
self
}
#[inline]
pub fn min_desired_width(mut self, min_desired_width: f32) -> Self {
self.min_desired_width = Some(min_desired_width);
self
}
#[inline]
pub fn with_icon(self, icon: &'a Icon) -> Self {
self.with_icon_fn(|ui, rect, visuals| {
icon.as_image().tint(visuals.icon_tint()).paint_at(ui, rect);
})
}
#[inline]
pub fn with_icon_fn(
mut self,
icon_fn: impl FnOnce(&mut egui::Ui, egui::Rect, ListVisuals) + 'a,
) -> Self {
self.icon_fn = Some(Box::new(icon_fn));
self
}
fn get_text_wrap_mode(&self, ui: &egui::Ui) -> egui::TextWrapMode {
if let Some(text_wrap_mode) = self.text_wrap_mode {
text_wrap_mode
} else if crate::is_in_resizable_panel(ui) {
egui::TextWrapMode::Truncate } else {
egui::TextWrapMode::Extend }
}
}
impl ListItemContent for LabelContent<'_> {
fn ui(self: Box<Self>, ui: &mut Ui, context: &ContentContext<'_>) {
let text_wrap_mode = self.get_text_wrap_mode(ui);
let Self {
mut text,
subdued,
weak,
italics,
strong,
label_style,
icon_fn,
buttons,
text_wrap_mode: _,
min_desired_width: _,
} = *self;
let tokens = ui.tokens();
let small_icon_size = tokens.small_icon_size;
let icon_rect = egui::Rect::from_center_size(
context.rect.left_center() + egui::vec2(small_icon_size.x / 2., 0.0),
small_icon_size,
);
let mut text_rect = context.rect;
if icon_fn.is_some() {
text_rect.min.x += icon_rect.width() + tokens.text_to_icon_padding();
}
if italics || label_style == LabelStyle::Unnamed {
text = text.italics();
}
let mut visuals = context.visuals;
visuals.strong |= strong;
let mut text_color = visuals.text_color();
if weak {
text_color = ui.style().visuals.gray_out(text_color);
} else if subdued {
text_color = text_color.gamma_multiply(0.5);
}
if let Some(icon_fn) = icon_fn {
icon_fn(ui, icon_rect, visuals);
}
buttons.show_and_shrink_rect(ui, context, &mut text_rect);
let mut layout_job = Arc::unwrap_or_clone(text.into_layout_job(
ui.style(),
egui::FontSelection::Default,
Align::LEFT,
));
layout_job.wrap = TextWrapping::from_wrap_mode_and_width(text_wrap_mode, text_rect.width());
let galley = ui.fonts_mut(|fonts| fonts.layout_job(layout_job));
context.response.widget_info(|| {
egui::WidgetInfo::selected(
egui::WidgetType::SelectableLabel,
ui.is_enabled(),
context.list_item.selected,
galley.text(),
)
});
let text_pos = Align2::LEFT_CENTER
.align_size_within_rect(galley.size(), text_rect)
.min;
ui.painter().galley(text_pos, galley, text_color);
}
fn desired_width(&self, ui: &Ui) -> DesiredWidth {
let tokens = ui.tokens();
let text_wrap_mode = self.get_text_wrap_mode(ui);
let measured_width = {
let mut text = self.text.clone();
if self.italics || self.label_style == LabelStyle::Unnamed {
text = text.italics();
}
let layout_job = Arc::unwrap_or_clone(text.clone().into_layout_job(
ui.style(),
egui::FontSelection::Default,
Align::LEFT,
));
let galley = ui.fonts_mut(|fonts| fonts.layout_job(layout_job));
let mut desired_width = galley.size().x;
if self.icon_fn.is_some() {
desired_width += tokens.small_icon_size.x + tokens.text_to_icon_padding();
}
desired_width.ceil()
};
if text_wrap_mode == egui::TextWrapMode::Extend {
let min_desired_width = self.min_desired_width.unwrap_or(0.0);
DesiredWidth::Exact(measured_width.at_least(min_desired_width))
} else {
let default_min_width = 64.0;
let min_desired_width = self
.min_desired_width
.unwrap_or_else(|| measured_width.min(default_min_width));
DesiredWidth::AtLeast(min_desired_width)
}
}
}
impl<'a> ListItemContentButtonsExt<'a> for LabelContent<'a> {
fn buttons(&self) -> &ItemButtons<'a> {
&self.buttons
}
fn buttons_mut(&mut self) -> &mut ItemButtons<'a> {
&mut self.buttons
}
}