use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use druid_shell::Cursor;
use crate::debug_state::DebugState;
use crate::kurbo::Vec2;
use crate::text::TextStorage;
use crate::widget::prelude::*;
use crate::widget::Axis;
use crate::{
ArcStr, Color, Data, FontDescriptor, KeyOrValue, LocalizedString, Point, TextAlignment,
TextLayout,
};
use tracing::{instrument, trace, warn};
const LABEL_X_PADDING: f64 = 2.0;
pub struct Label<T> {
label: RawLabel<ArcStr>,
current_text: ArcStr,
text: LabelText<T>,
text_should_be_updated: bool,
}
pub struct RawLabel<T> {
layout: TextLayout<T>,
line_break_mode: LineBreaking,
disabled: bool,
default_text_color: KeyOrValue<Color>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Data)]
pub enum LineBreaking {
WordWrap,
Clip,
Overflow,
}
#[derive(Clone)]
pub enum LabelText<T> {
Localized(LocalizedString<T>),
Static(Static),
Dynamic(Dynamic<T>),
}
#[derive(Clone)]
pub struct Dynamic<T> {
f: Arc<dyn Fn(&T, &Env) -> ArcStr>,
resolved: ArcStr,
}
#[derive(Debug, Clone)]
pub struct Static {
string: ArcStr,
resolved: bool,
}
impl<T: TextStorage> RawLabel<T> {
pub fn new() -> Self {
Self {
layout: TextLayout::new(),
line_break_mode: LineBreaking::Overflow,
disabled: false,
default_text_color: crate::theme::TEXT_COLOR.into(),
}
}
pub fn with_text_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
self.set_text_color(color);
self
}
pub fn with_text_size(mut self, size: impl Into<KeyOrValue<f64>>) -> Self {
self.set_text_size(size);
self
}
pub fn with_font(mut self, font: impl Into<KeyOrValue<FontDescriptor>>) -> Self {
self.set_font(font);
self
}
pub fn with_line_break_mode(mut self, mode: LineBreaking) -> Self {
self.set_line_break_mode(mode);
self
}
pub fn with_text_alignment(mut self, alignment: TextAlignment) -> Self {
self.set_text_alignment(alignment);
self
}
pub fn set_text_color(&mut self, color: impl Into<KeyOrValue<Color>>) {
let color = color.into();
if !self.disabled {
self.layout.set_text_color(color.clone());
}
self.default_text_color = color;
}
pub fn set_text_size(&mut self, size: impl Into<KeyOrValue<f64>>) {
self.layout.set_text_size(size);
}
pub fn set_font(&mut self, font: impl Into<KeyOrValue<FontDescriptor>>) {
self.layout.set_font(font);
}
pub fn set_line_break_mode(&mut self, mode: LineBreaking) {
self.line_break_mode = mode;
}
pub fn set_text_alignment(&mut self, alignment: TextAlignment) {
self.layout.set_text_alignment(alignment);
}
pub fn draw_at(&self, ctx: &mut PaintCtx, origin: impl Into<Point>) {
self.layout.draw(ctx, origin)
}
pub fn baseline_offset(&self) -> f64 {
let text_metrics = self.layout.layout_metrics();
text_metrics.size.height - text_metrics.first_baseline
}
}
impl<T: TextStorage> Label<T> {
pub fn raw() -> RawLabel<T> {
RawLabel::new()
}
}
impl<T: Data> Label<T> {
pub fn new(text: impl Into<LabelText<T>>) -> Self {
let text = text.into();
let current_text = text.display_text();
Self {
text,
current_text,
label: RawLabel::new(),
text_should_be_updated: true,
}
}
pub fn dynamic(text: impl Fn(&T, &Env) -> String + 'static) -> Self {
let text: LabelText<T> = text.into();
Label::new(text)
}
pub fn text(&self) -> ArcStr {
self.text.display_text()
}
pub fn set_text(&mut self, text: impl Into<LabelText<T>>) {
self.text = text.into();
self.text_should_be_updated = true;
}
pub fn with_text_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
self.label.set_text_color(color);
self
}
pub fn with_text_size(mut self, size: impl Into<KeyOrValue<f64>>) -> Self {
self.label.set_text_size(size);
self
}
pub fn with_font(mut self, font: impl Into<KeyOrValue<FontDescriptor>>) -> Self {
self.label.set_font(font);
self
}
pub fn with_line_break_mode(mut self, mode: LineBreaking) -> Self {
self.label.set_line_break_mode(mode);
self
}
pub fn with_text_alignment(mut self, alignment: TextAlignment) -> Self {
self.label.set_text_alignment(alignment);
self
}
pub fn draw_at(&self, ctx: &mut PaintCtx, origin: impl Into<Point>) {
self.label.draw_at(ctx, origin)
}
}
impl Static {
fn new(s: ArcStr) -> Self {
Static {
string: s,
resolved: false,
}
}
fn resolve(&mut self) -> bool {
let is_first_call = !self.resolved;
self.resolved = true;
is_first_call
}
}
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::Static(s) => cb(&s.string),
LabelText::Localized(s) => cb(&s.localized_str()),
LabelText::Dynamic(s) => cb(&s.resolved),
}
}
pub fn display_text(&self) -> ArcStr {
match self {
LabelText::Static(s) => s.string.clone(),
LabelText::Localized(s) => s.localized_str(),
LabelText::Dynamic(s) => s.resolved.clone(),
}
}
pub fn resolve(&mut self, data: &T, env: &Env) -> bool {
match self {
LabelText::Static(s) => s.resolve(),
LabelText::Localized(s) => s.resolve(data, env),
LabelText::Dynamic(s) => s.resolve(data, env),
}
}
}
impl<T: Data> Widget<T> for Label<T> {
#[instrument(name = "Label", level = "trace", skip(self, _ctx, _event, _data, _env))]
fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut T, _env: &Env) {}
#[instrument(name = "Label", level = "trace", skip(self, ctx, event, data, env))]
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
if matches!(event, LifeCycle::WidgetAdded) {
self.text.resolve(data, env);
self.text_should_be_updated = false;
}
self.label
.lifecycle(ctx, event, &self.text.display_text(), env);
}
#[instrument(name = "Label", level = "trace", skip(self, ctx, _old_data, data, env))]
fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &T, data: &T, env: &Env) {
let data_changed = self.text.resolve(data, env);
self.text_should_be_updated = false;
if data_changed {
let new_text = self.text.display_text();
self.label.update(ctx, &self.current_text, &new_text, env);
self.current_text = new_text;
} else if ctx.env_changed() {
self.label
.update(ctx, &self.current_text, &self.current_text, env);
}
}
#[instrument(name = "Label", level = "trace", skip(self, ctx, bc, _data, env))]
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, _data: &T, env: &Env) -> Size {
self.label.layout(ctx, bc, &self.current_text, env)
}
#[instrument(name = "Label", level = "trace", skip(self, ctx, _data, env))]
fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, env: &Env) {
if self.text_should_be_updated {
tracing::warn!("Label text changed without call to update. See LabelAdapter::set_text for information.");
}
self.label.paint(ctx, &self.current_text, env)
}
fn debug_state(&self, _data: &T) -> DebugState {
DebugState {
display_name: self.short_type_name().to_string(),
main_value: self.current_text.to_string(),
..Default::default()
}
}
fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
_data: &T,
env: &Env,
) -> f64 {
self.label
.compute_max_intrinsic(axis, ctx, bc, &self.current_text, env)
}
}
impl<T: TextStorage> Widget<T> for RawLabel<T> {
#[instrument(
name = "RawLabel",
level = "trace",
skip(self, ctx, event, _data, _env)
)]
fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut T, _env: &Env) {
match event {
Event::MouseUp(event) => {
let pos = event.pos - Vec2::new(LABEL_X_PADDING, 0.0);
if let Some(link) = self.layout.link_for_pos(pos) {
ctx.submit_command(link.command.clone());
}
}
Event::MouseMove(event) => {
let pos = event.pos - Vec2::new(LABEL_X_PADDING, 0.0);
if self.layout.link_for_pos(pos).is_some() {
ctx.set_cursor(&Cursor::Pointer);
} else {
ctx.clear_cursor();
}
}
_ => {}
}
}
#[instrument(name = "RawLabel", level = "trace", skip(self, ctx, event, data, _env))]
fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, _env: &Env) {
match event {
LifeCycle::WidgetAdded => {
self.layout.set_text(data.to_owned());
}
LifeCycle::DisabledChanged(disabled) => {
let color = if *disabled {
KeyOrValue::Key(crate::theme::DISABLED_TEXT_COLOR)
} else {
self.default_text_color.clone()
};
self.layout.set_text_color(color);
ctx.request_layout();
}
_ => {}
}
}
#[instrument(
name = "RawLabel",
level = "trace",
skip(self, ctx, old_data, data, _env)
)]
fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, _env: &Env) {
if !old_data.same(data) {
self.layout.set_text(data.clone());
ctx.request_layout();
}
if self.layout.needs_rebuild_after_update(ctx) {
ctx.request_layout();
}
}
#[instrument(name = "RawLabel", level = "trace", skip(self, ctx, bc, _data, env))]
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, _data: &T, env: &Env) -> Size {
bc.debug_check("Label");
let width = match self.line_break_mode {
LineBreaking::WordWrap => bc.max().width - LABEL_X_PADDING * 2.0,
_ => f64::INFINITY,
};
self.layout.set_wrap_width(width);
self.layout.rebuild_if_needed(ctx.text(), env);
let text_metrics = self.layout.layout_metrics();
ctx.set_baseline_offset(text_metrics.size.height - text_metrics.first_baseline);
let size = bc.constrain(Size::new(
text_metrics.size.width + 2. * LABEL_X_PADDING,
text_metrics.size.height,
));
trace!("Computed size: {}", size);
size
}
#[instrument(name = "RawLabel", level = "trace", skip(self, ctx, _data, _env))]
fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, _env: &Env) {
let origin = Point::new(LABEL_X_PADDING, 0.0);
let label_size = ctx.size();
if self.line_break_mode == LineBreaking::Clip {
ctx.clip(label_size.to_rect());
}
self.draw_at(ctx, origin)
}
fn compute_max_intrinsic(
&mut self,
axis: Axis,
ctx: &mut LayoutCtx,
bc: &BoxConstraints,
data: &T,
env: &Env,
) -> f64 {
match axis {
Axis::Horizontal => {
match self.line_break_mode {
LineBreaking::WordWrap => {
self.line_break_mode = LineBreaking::Clip;
let s = self.layout(ctx, bc, data, env);
self.line_break_mode = LineBreaking::WordWrap;
s.width
}
_ => self.layout(ctx, bc, data, env).width,
}
}
Axis::Vertical => {
warn!("Max intrinsic height of a label is not implemented.");
0.
}
}
}
}
impl<T: TextStorage> Default for RawLabel<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Deref for Label<T> {
type Target = RawLabel<ArcStr>;
fn deref(&self) -> &Self::Target {
&self.label
}
}
impl<T> DerefMut for Label<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.label
}
}
impl<T> From<String> for LabelText<T> {
fn from(src: String) -> LabelText<T> {
LabelText::Static(Static::new(src.into()))
}
}
impl<T> From<&str> for LabelText<T> {
fn from(src: &str) -> LabelText<T> {
LabelText::Static(Static::new(src.into()))
}
}
impl<T> From<ArcStr> for LabelText<T> {
fn from(string: ArcStr) -> LabelText<T> {
LabelText::Static(Static::new(string))
}
}
impl<T> From<LocalizedString<T>> for LabelText<T> {
fn from(src: LocalizedString<T>) -> LabelText<T> {
LabelText::Localized(src)
}
}
impl<T, S, F> From<F> for LabelText<T>
where
S: Into<Arc<str>>,
F: Fn(&T, &Env) -> S + 'static,
{
fn from(src: F) -> LabelText<T> {
let f = Arc::new(move |state: &T, env: &Env| src(state, env).into());
LabelText::Dynamic(Dynamic {
f,
resolved: ArcStr::from(""),
})
}
}