//! A read-only text widget. use figures::units::{Px, UPx}; use figures::{Point, Round, Size}; use kludgine::text::{MeasuredText, Text, TextOrigin}; use kludgine::{CanRenderTo, Color, DrawableExt}; use crate::context::{GraphicsContext, LayoutContext}; use crate::styles::components::TextColor; use crate::value::{Dynamic, Generation, IntoValue, Value}; use crate::widget::{Widget, WidgetInstance}; use crate::ConstraintLimit; /// A read-only text widget. #[derive(Debug)] pub struct Label { /// The contents of the label. pub text: Value, prepared_text: Option<(MeasuredText, Option, Px, Color)>, } impl Label { /// Returns a new label that displays `text`. pub fn new(text: impl IntoValue) -> Self { Self { text: text.into_value(), prepared_text: None, } } fn prepared_text( &mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>, color: Color, width: Px, ) -> &MeasuredText { let check_generation = self.text.generation(); match &self.prepared_text { Some((prepared, prepared_generation, prepared_width, prepared_color)) if prepared.can_render_to(&context.gfx) && *prepared_generation == check_generation && *prepared_color == color && (*prepared_width == width || ((*prepared_width < width || prepared.size.width <= width) && prepared.line_height == prepared.size.height)) => {} _ => { context.apply_current_font_settings(); let measured = self.text.map(|text| { context .gfx .measure_text(Text::new(text, color).wrap_at(width)) }); self.prepared_text = Some((measured, check_generation, width, color)); } } self.prepared_text .as_ref() .map(|(prepared, _, _, _)| prepared) .expect("always initialized") } } impl Widget for Label { fn redraw(&mut self, context: &mut GraphicsContext<'_, '_, '_, '_, '_>) { self.text.invalidate_when_changed(context); let size = context.gfx.region().size; let center = Point::from(size) / 2; let text_color = context.get(&TextColor); let prepared_text = self.prepared_text(context, text_color, size.width); context.gfx.draw_measured_text( prepared_text.translate_by(center.round()), TextOrigin::Center, ); } fn layout( &mut self, available_space: Size, context: &mut LayoutContext<'_, '_, '_, '_, '_>, ) -> Size { let color = context.get(&TextColor); let width = available_space.width.max().try_into().unwrap_or(Px::MAX); let prepared = self.prepared_text(context, color, width); prepared.size.try_cast().unwrap_or_default() } fn summarize(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fmt.debug_tuple("Label").field(&self.text).finish() } } macro_rules! impl_make_widget { ($($type:ty),*) => { $(impl crate::widget::MakeWidgetWithTag for $type { fn make_with_tag(self, id: crate::widget::WidgetTag) -> WidgetInstance { Label::new(self).make_with_tag(id) } })* }; } impl_make_widget!( &'_ str, String, Value, Dynamic, Dynamic<&'static str> );