use crate::piet::{
Color, FontBuilder, PietText, PietTextLayout, RenderContext, Text, TextLayout,
TextLayoutBuilder, UnitPoint,
};
use crate::{
theme, BoxConstraints, Data, Env, Event, EventCtx, KeyOrValue, LayoutCtx, LifeCycle,
LifeCycleCtx, LocalizedString, PaintCtx, Point, Size, UpdateCtx, Widget,
};
const LINE_HEIGHT_FACTOR: f64 = 1.2;
const BASELINE_GUESS_FACTOR: f64 = 0.8;
const LABEL_X_PADDING: f64 = 2.0;
pub enum LabelText<T> {
Localized(LocalizedString<T>),
Specific(String),
Dynamic(Dynamic<T>),
}
#[doc(hidden)]
pub struct Dynamic<T> {
f: Box<dyn Fn(&T, &Env) -> String>,
resolved: String,
}
pub struct Label<T> {
text: LabelText<T>,
color: KeyOrValue<Color>,
size: KeyOrValue<f64>,
}
impl<T: Data> Label<T> {
pub fn new(text: impl Into<LabelText<T>>) -> Self {
let text = text.into();
Self {
text,
color: theme::LABEL_COLOR.into(),
size: theme::TEXT_SIZE_NORMAL.into(),
}
}
pub fn dynamic(text: impl Fn(&T, &Env) -> String + 'static) -> Self {
let text: LabelText<T> = text.into();
Label::new(text)
}
#[deprecated(since = "0.5.0", note = "Use an Align widget instead")]
pub fn text_align(self, _align: UnitPoint) -> Self {
self
}
pub fn with_text_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
self.color = color.into();
self
}
pub fn with_text_size(mut self, size: impl Into<KeyOrValue<f64>>) -> Self {
self.size = size.into();
self
}
pub fn set_text(&mut self, text: impl Into<String>) {
self.text = LabelText::Specific(text.into());
}
pub fn set_text_color(&mut self, color: impl Into<KeyOrValue<Color>>) {
self.color = color.into();
}
pub fn set_text_size(&mut self, size: impl Into<KeyOrValue<f64>>) {
self.size = size.into();
}
fn get_layout(&mut self, t: &mut PietText, env: &Env) -> PietTextLayout {
let font_name = env.get(theme::FONT_NAME);
let font_size = self.size.resolve(env);
let font = t.new_font_by_name(font_name, font_size).build().unwrap();
self.text
.with_display_text(|text| t.new_text_layout(&font, &text).build().unwrap())
}
}
impl<T> Dynamic<T> {
fn resolve(&mut self, data: &T, env: &Env) -> bool {
let new = (self.f)(data, env);
let changed = new != self.resolved;
self.resolved = new;
changed
}
}
impl<T: Data> LabelText<T> {
pub fn with_display_text<V>(&self, mut cb: impl FnMut(&str) -> V) -> V {
match self {
LabelText::Specific(s) => cb(s.as_str()),
LabelText::Localized(s) => cb(s.localized_str()),
LabelText::Dynamic(s) => cb(s.resolved.as_str()),
}
}
pub fn resolve(&mut self, data: &T, env: &Env) -> bool {
match self {
LabelText::Specific(_) => false,
LabelText::Localized(s) => s.resolve(data, env),
LabelText::Dynamic(s) => s.resolve(data, env),
}
}
}
impl<T: Data> Widget<T> for Label<T> {
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut T, _env: &Env) {}
fn lifecycle(&mut self, _ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
if let LifeCycle::WidgetAdded = event {
self.text.resolve(data, env);
}
}
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, env: &Env) {
if !old_data.same(data) && self.text.resolve(data, env) {
ctx.request_layout();
}
}
fn layout(
&mut self,
layout_ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &T,
env: &Env,
) -> Size {
bc.debug_check("Label");
let font_size = self.size.resolve(env);
let text_layout = self.get_layout(layout_ctx.text(), env);
bc.constrain(Size::new(
text_layout.width() + 2. * LABEL_X_PADDING,
font_size * LINE_HEIGHT_FACTOR,
))
}
fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, env: &Env) {
let font_size = self.size.resolve(env);
let text_layout = self.get_layout(ctx.text(), env);
let line_height = font_size * LINE_HEIGHT_FACTOR;
let origin = Point::new(LABEL_X_PADDING, line_height * BASELINE_GUESS_FACTOR);
let color = self.color.resolve(env);
ctx.draw_text(&text_layout, origin, &color);
}
}
impl<T> From<String> for LabelText<T> {
fn from(src: String) -> LabelText<T> {
LabelText::Specific(src)
}
}
impl<T> From<&str> for LabelText<T> {
fn from(src: &str) -> LabelText<T> {
LabelText::Specific(src.to_string())
}
}
impl<T> From<LocalizedString<T>> for LabelText<T> {
fn from(src: LocalizedString<T>) -> LabelText<T> {
LabelText::Localized(src)
}
}
impl<T, F: Fn(&T, &Env) -> String + 'static> From<F> for LabelText<T> {
fn from(src: F) -> LabelText<T> {
let f = Box::new(src);
LabelText::Dynamic(Dynamic {
f,
resolved: String::default(),
})
}
}