1use std::ops::{Deref, DerefMut};
18use std::sync::Arc;
19
20use druid_shell::Cursor;
21
22use crate::debug_state::DebugState;
23use crate::kurbo::Vec2;
24use crate::text::TextStorage;
25use crate::widget::prelude::*;
26use crate::widget::Axis;
27use crate::{
28 ArcStr, Color, Data, FontDescriptor, KeyOrValue, LocalizedString, Point, TextAlignment,
29 TextLayout,
30};
31use tracing::{instrument, trace, warn};
32
33const LABEL_X_PADDING: f64 = 2.0;
35
36pub struct Label<T> {
77 label: RawLabel<ArcStr>,
78 current_text: ArcStr,
79 text: LabelText<T>,
80 text_should_be_updated: bool,
83}
84
85pub struct RawLabel<T> {
90 layout: TextLayout<T>,
91 line_break_mode: LineBreaking,
92
93 disabled: bool,
94 default_text_color: KeyOrValue<Color>,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Data)]
99pub enum LineBreaking {
100 WordWrap,
102 Clip,
104 Overflow,
106}
107
108#[derive(Clone)]
114pub enum LabelText<T> {
115 Localized(LocalizedString<T>),
117 Static(Static),
119 Dynamic(Dynamic<T>),
122}
123
124#[derive(Clone)]
126pub struct Dynamic<T> {
127 f: Arc<dyn Fn(&T, &Env) -> ArcStr>,
128 resolved: ArcStr,
129}
130
131#[derive(Debug, Clone)]
133pub struct Static {
134 string: ArcStr,
136 resolved: bool,
142}
143
144impl<T: TextStorage> RawLabel<T> {
145 pub fn new() -> Self {
147 Self {
148 layout: TextLayout::new(),
149 line_break_mode: LineBreaking::Overflow,
150 disabled: false,
151 default_text_color: crate::theme::TEXT_COLOR.into(),
152 }
153 }
154
155 pub fn with_text_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
161 self.set_text_color(color);
162 self
163 }
164
165 pub fn with_text_size(mut self, size: impl Into<KeyOrValue<f64>>) -> Self {
171 self.set_text_size(size);
172 self
173 }
174
175 pub fn with_font(mut self, font: impl Into<KeyOrValue<FontDescriptor>>) -> Self {
182 self.set_font(font);
183 self
184 }
185
186 pub fn with_line_break_mode(mut self, mode: LineBreaking) -> Self {
188 self.set_line_break_mode(mode);
189 self
190 }
191
192 pub fn with_text_alignment(mut self, alignment: TextAlignment) -> Self {
194 self.set_text_alignment(alignment);
195 self
196 }
197
198 pub fn set_text_color(&mut self, color: impl Into<KeyOrValue<Color>>) {
208 let color = color.into();
209 if !self.disabled {
210 self.layout.set_text_color(color.clone());
211 }
212 self.default_text_color = color;
213 }
214
215 pub fn set_text_size(&mut self, size: impl Into<KeyOrValue<f64>>) {
225 self.layout.set_text_size(size);
226 }
227
228 pub fn set_font(&mut self, font: impl Into<KeyOrValue<FontDescriptor>>) {
239 self.layout.set_font(font);
240 }
241
242 pub fn set_line_break_mode(&mut self, mode: LineBreaking) {
249 self.line_break_mode = mode;
250 }
251
252 pub fn set_text_alignment(&mut self, alignment: TextAlignment) {
254 self.layout.set_text_alignment(alignment);
255 }
256
257 pub fn draw_at(&self, ctx: &mut PaintCtx, origin: impl Into<Point>) {
263 self.layout.draw(ctx, origin)
264 }
265
266 pub fn baseline_offset(&self) -> f64 {
268 let text_metrics = self.layout.layout_metrics();
269 text_metrics.size.height - text_metrics.first_baseline
270 }
271}
272
273impl<T: TextStorage> Label<T> {
274 pub fn raw() -> RawLabel<T> {
278 RawLabel::new()
279 }
280}
281
282impl<T: Data> Label<T> {
283 pub fn new(text: impl Into<LabelText<T>>) -> Self {
300 let text = text.into();
301 let current_text = text.display_text();
302 Self {
303 text,
304 current_text,
305 label: RawLabel::new(),
306 text_should_be_updated: true,
307 }
308 }
309
310 pub fn dynamic(text: impl Fn(&T, &Env) -> String + 'static) -> Self {
331 let text: LabelText<T> = text.into();
332 Label::new(text)
333 }
334
335 pub fn text(&self) -> ArcStr {
337 self.text.display_text()
338 }
339
340 pub fn set_text(&mut self, text: impl Into<LabelText<T>>) {
351 self.text = text.into();
352 self.text_should_be_updated = true;
353 }
354
355 pub fn with_text_color(mut self, color: impl Into<KeyOrValue<Color>>) -> Self {
361 self.label.set_text_color(color);
362 self
363 }
364
365 pub fn with_text_size(mut self, size: impl Into<KeyOrValue<f64>>) -> Self {
371 self.label.set_text_size(size);
372 self
373 }
374
375 pub fn with_font(mut self, font: impl Into<KeyOrValue<FontDescriptor>>) -> Self {
382 self.label.set_font(font);
383 self
384 }
385
386 pub fn with_line_break_mode(mut self, mode: LineBreaking) -> Self {
388 self.label.set_line_break_mode(mode);
389 self
390 }
391
392 pub fn with_text_alignment(mut self, alignment: TextAlignment) -> Self {
394 self.label.set_text_alignment(alignment);
395 self
396 }
397
398 pub fn draw_at(&self, ctx: &mut PaintCtx, origin: impl Into<Point>) {
404 self.label.draw_at(ctx, origin)
405 }
406}
407
408impl Static {
409 fn new(s: ArcStr) -> Self {
410 Static {
411 string: s,
412 resolved: false,
413 }
414 }
415
416 fn resolve(&mut self) -> bool {
417 let is_first_call = !self.resolved;
418 self.resolved = true;
419 is_first_call
420 }
421}
422
423impl<T> Dynamic<T> {
424 fn resolve(&mut self, data: &T, env: &Env) -> bool {
425 let new = (self.f)(data, env);
426 let changed = new != self.resolved;
427 self.resolved = new;
428 changed
429 }
430}
431
432impl<T: Data> LabelText<T> {
433 pub fn with_display_text<V>(&self, mut cb: impl FnMut(&str) -> V) -> V {
435 match self {
436 LabelText::Static(s) => cb(&s.string),
437 LabelText::Localized(s) => cb(&s.localized_str()),
438 LabelText::Dynamic(s) => cb(&s.resolved),
439 }
440 }
441
442 pub fn display_text(&self) -> ArcStr {
444 match self {
445 LabelText::Static(s) => s.string.clone(),
446 LabelText::Localized(s) => s.localized_str(),
447 LabelText::Dynamic(s) => s.resolved.clone(),
448 }
449 }
450
451 pub fn resolve(&mut self, data: &T, env: &Env) -> bool {
456 match self {
457 LabelText::Static(s) => s.resolve(),
458 LabelText::Localized(s) => s.resolve(data, env),
459 LabelText::Dynamic(s) => s.resolve(data, env),
460 }
461 }
462}
463
464impl<T: Data> Widget<T> for Label<T> {
465 #[instrument(name = "Label", level = "trace", skip(self, _ctx, _event, _data, _env))]
466 fn event(&mut self, _ctx: &mut EventCtx, _event: &Event, _data: &mut T, _env: &Env) {}
467
468 #[instrument(name = "Label", level = "trace", skip(self, ctx, event, data, env))]
469 fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, env: &Env) {
470 if matches!(event, LifeCycle::WidgetAdded) {
471 self.text.resolve(data, env);
472 self.text_should_be_updated = false;
473 }
474 self.label
475 .lifecycle(ctx, event, &self.text.display_text(), env);
476 }
477
478 #[instrument(name = "Label", level = "trace", skip(self, ctx, _old_data, data, env))]
479 fn update(&mut self, ctx: &mut UpdateCtx, _old_data: &T, data: &T, env: &Env) {
480 let data_changed = self.text.resolve(data, env);
481 self.text_should_be_updated = false;
482 if data_changed {
483 let new_text = self.text.display_text();
484 self.label.update(ctx, &self.current_text, &new_text, env);
485 self.current_text = new_text;
486 } else if ctx.env_changed() {
487 self.label
488 .update(ctx, &self.current_text, &self.current_text, env);
489 }
490 }
491
492 #[instrument(name = "Label", level = "trace", skip(self, ctx, bc, _data, env))]
493 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, _data: &T, env: &Env) -> Size {
494 self.label.layout(ctx, bc, &self.current_text, env)
495 }
496
497 #[instrument(name = "Label", level = "trace", skip(self, ctx, _data, env))]
498 fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, env: &Env) {
499 if self.text_should_be_updated {
500 tracing::warn!("Label text changed without call to update. See LabelAdapter::set_text for information.");
501 }
502 self.label.paint(ctx, &self.current_text, env)
503 }
504
505 fn debug_state(&self, _data: &T) -> DebugState {
506 DebugState {
507 display_name: self.short_type_name().to_string(),
508 main_value: self.current_text.to_string(),
509 ..Default::default()
510 }
511 }
512
513 fn compute_max_intrinsic(
514 &mut self,
515 axis: Axis,
516 ctx: &mut LayoutCtx,
517 bc: &BoxConstraints,
518 _data: &T,
519 env: &Env,
520 ) -> f64 {
521 self.label
522 .compute_max_intrinsic(axis, ctx, bc, &self.current_text, env)
523 }
524}
525
526impl<T: TextStorage> Widget<T> for RawLabel<T> {
527 #[instrument(
528 name = "RawLabel",
529 level = "trace",
530 skip(self, ctx, event, _data, _env)
531 )]
532 fn event(&mut self, ctx: &mut EventCtx, event: &Event, _data: &mut T, _env: &Env) {
533 match event {
534 Event::MouseUp(event) => {
535 let pos = event.pos - Vec2::new(LABEL_X_PADDING, 0.0);
537 if let Some(link) = self.layout.link_for_pos(pos) {
538 ctx.submit_command(link.command.clone());
539 }
540 }
541 Event::MouseMove(event) => {
542 let pos = event.pos - Vec2::new(LABEL_X_PADDING, 0.0);
544
545 if self.layout.link_for_pos(pos).is_some() {
546 ctx.set_cursor(&Cursor::Pointer);
547 } else {
548 ctx.clear_cursor();
549 }
550 }
551 _ => {}
552 }
553 }
554
555 #[instrument(name = "RawLabel", level = "trace", skip(self, ctx, event, data, _env))]
556 fn lifecycle(&mut self, ctx: &mut LifeCycleCtx, event: &LifeCycle, data: &T, _env: &Env) {
557 match event {
558 LifeCycle::WidgetAdded => {
559 self.layout.set_text(data.to_owned());
560 }
561 LifeCycle::DisabledChanged(disabled) => {
562 let color = if *disabled {
563 KeyOrValue::Key(crate::theme::DISABLED_TEXT_COLOR)
564 } else {
565 self.default_text_color.clone()
566 };
567 self.layout.set_text_color(color);
568 ctx.request_layout();
569 }
570 _ => {}
571 }
572 }
573
574 #[instrument(
575 name = "RawLabel",
576 level = "trace",
577 skip(self, ctx, old_data, data, _env)
578 )]
579 fn update(&mut self, ctx: &mut UpdateCtx, old_data: &T, data: &T, _env: &Env) {
580 if !old_data.same(data) {
581 self.layout.set_text(data.clone());
582 ctx.request_layout();
583 }
584 if self.layout.needs_rebuild_after_update(ctx) {
585 ctx.request_layout();
586 }
587 }
588
589 #[instrument(name = "RawLabel", level = "trace", skip(self, ctx, bc, _data, env))]
590 fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints, _data: &T, env: &Env) -> Size {
591 bc.debug_check("Label");
592
593 let width = match self.line_break_mode {
594 LineBreaking::WordWrap => bc.max().width - LABEL_X_PADDING * 2.0,
595 _ => f64::INFINITY,
596 };
597
598 self.layout.set_wrap_width(width);
599 self.layout.rebuild_if_needed(ctx.text(), env);
600
601 let text_metrics = self.layout.layout_metrics();
602 ctx.set_baseline_offset(text_metrics.size.height - text_metrics.first_baseline);
603 let size = bc.constrain(Size::new(
604 text_metrics.size.width + 2. * LABEL_X_PADDING,
605 text_metrics.size.height,
606 ));
607 trace!("Computed size: {}", size);
608 size
609 }
610
611 #[instrument(name = "RawLabel", level = "trace", skip(self, ctx, _data, _env))]
612 fn paint(&mut self, ctx: &mut PaintCtx, _data: &T, _env: &Env) {
613 let origin = Point::new(LABEL_X_PADDING, 0.0);
614 let label_size = ctx.size();
615
616 if self.line_break_mode == LineBreaking::Clip {
617 ctx.clip(label_size.to_rect());
618 }
619 self.draw_at(ctx, origin)
620 }
621
622 fn compute_max_intrinsic(
623 &mut self,
624 axis: Axis,
625 ctx: &mut LayoutCtx,
626 bc: &BoxConstraints,
627 data: &T,
628 env: &Env,
629 ) -> f64 {
630 match axis {
631 Axis::Horizontal => {
632 match self.line_break_mode {
633 LineBreaking::WordWrap => {
634 self.line_break_mode = LineBreaking::Clip;
637 let s = self.layout(ctx, bc, data, env);
638 self.line_break_mode = LineBreaking::WordWrap;
639 s.width
640 }
641 _ => self.layout(ctx, bc, data, env).width,
642 }
643 }
644 Axis::Vertical => {
645 warn!("Max intrinsic height of a label is not implemented.");
646 0.
647 }
648 }
649 }
650}
651
652impl<T: TextStorage> Default for RawLabel<T> {
653 fn default() -> Self {
654 Self::new()
655 }
656}
657
658impl<T> Deref for Label<T> {
659 type Target = RawLabel<ArcStr>;
660 fn deref(&self) -> &Self::Target {
661 &self.label
662 }
663}
664
665impl<T> DerefMut for Label<T> {
666 fn deref_mut(&mut self) -> &mut Self::Target {
667 &mut self.label
668 }
669}
670impl<T> From<String> for LabelText<T> {
671 fn from(src: String) -> LabelText<T> {
672 LabelText::Static(Static::new(src.into()))
673 }
674}
675
676impl<T> From<&str> for LabelText<T> {
677 fn from(src: &str) -> LabelText<T> {
678 LabelText::Static(Static::new(src.into()))
679 }
680}
681
682impl<T> From<ArcStr> for LabelText<T> {
683 fn from(string: ArcStr) -> LabelText<T> {
684 LabelText::Static(Static::new(string))
685 }
686}
687
688impl<T> From<LocalizedString<T>> for LabelText<T> {
689 fn from(src: LocalizedString<T>) -> LabelText<T> {
690 LabelText::Localized(src)
691 }
692}
693
694impl<T, S, F> From<F> for LabelText<T>
695where
696 S: Into<Arc<str>>,
697 F: Fn(&T, &Env) -> S + 'static,
698{
699 fn from(src: F) -> LabelText<T> {
700 let f = Arc::new(move |state: &T, env: &Env| src(state, env).into());
701 LabelText::Dynamic(Dynamic {
702 f,
703 resolved: ArcStr::from(""),
704 })
705 }
706}