use alloc::string::{String, ToString};
use denise::Pen;
use denise::Role;
use denise_text::TextStyle;
use crate::widget::{MeasureCtx, Measured, Offer, PaintCtx, Widget};
use crate::widgets::describe::{
ALIGNMENTS, Describe, DynDescribe, Group, Mismatch, Property, PropertyKind, ROLES, Value,
};
use crate::widgets::style::{Align, draw_aligned};
#[derive(Clone, Debug)]
pub struct Label {
text: String,
role: Role,
align: (Align, Align),
style: TextStyle,
}
impl Label {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
role: Role::BaseContent,
align: (Align::Start, Align::Center),
style: TextStyle::built_in(16),
}
}
pub fn with_role(mut self, role: Role) -> Self {
self.role = role;
self
}
pub fn with_align(mut self, horizontal: Align, vertical: Align) -> Self {
self.align = (horizontal, vertical);
self
}
pub fn with_style(mut self, style: TextStyle) -> Self {
self.style = style;
self
}
pub fn with_size(mut self, size_px: u16) -> Self {
self.style.size_px = size_px;
self
}
#[inline]
pub const fn style(&self) -> TextStyle {
self.style
}
#[inline]
pub fn text(&self) -> &str {
&self.text
}
pub fn set_text(&mut self, text: impl Into<String>) {
self.text = text.into();
}
pub fn set_style(&mut self, style: TextStyle) {
self.style = style;
}
pub fn set_role(&mut self, role: Role) {
self.role = role;
}
pub fn update(&mut self, text: &str) -> bool {
let changed = self.text != text;
if changed {
self.text = text.to_string();
}
changed
}
}
impl<M: 'static> Widget<M> for Label {
fn describe(&self) -> Option<&dyn DynDescribe> {
Some(self)
}
fn describe_mut(&mut self) -> Option<&mut dyn DynDescribe> {
Some(self)
}
fn measure(&self, ctx: &mut MeasureCtx<'_>, _offered: Offer) -> Measured {
Measured::both(
ctx.text.measure_line(self.style, &self.text),
ctx.text.line_height(self.style),
)
}
fn paint(&self, ctx: &mut PaintCtx<'_>, canvas: &mut Pen<'_>) {
let color = ctx.theme.color(self.role);
draw_aligned(
canvas, ctx.text, self.style, ctx.bounds, self.align, &self.text, color,
);
}
}
impl Describe for Label {
const KIND: &'static str = "label";
const DOC: &'static str = "Text that is read and not touched.";
const GROUP: Group = Group::Display;
const ICON: &'static denise::icon::Icon = &super::icons::LABEL;
const PROPERTIES: &'static [Property] = &[
Property::new("text", PropertyKind::Text, "The text drawn."),
Property::new(
"role",
PropertyKind::Enum(ROLES),
"Colour role. A `*Content` role draws on a surface; a surface role draws the text in that colour.",
),
Property::new(
"align",
PropertyKind::Enum(ALIGNMENTS),
"Where the text sits horizontally in its box.",
),
Property::new(
"valign",
PropertyKind::Enum(ALIGNMENTS),
"Where the text sits vertically in its box.",
),
Property::new(
"size",
PropertyKind::Int { min: 6, max: 96 },
"Text size in logical pixels.",
)
.in_pixels(),
];
fn get(&self, name: &str) -> Option<Value> {
Some(match name {
"text" => Value::text(self.text.as_str()),
"role" => Value::role(self.role),
"align" => Value::align(self.align.0),
"valign" => Value::align(self.align.1),
"size" => Value::Int(i32::from(self.style.size_px)),
_ => return None,
})
}
fn apply(&mut self, name: &str, value: Value) -> Result<(), Mismatch> {
match name {
"text" => self.text = value.as_text()?,
"role" => self.role = value.as_role()?,
"align" => self.align.0 = value.as_align()?,
"valign" => self.align.1 = value.as_align()?,
"size" => self.style.size_px = value.as_size()?,
_ => return Err(Mismatch::Unknown),
}
Ok(())
}
}