1use std::{any::Any, cell::RefCell, fmt::Display, rc::Rc};
2
3use crate::{
4 Clipboard, ViewId,
5 context::{EventCx, LayoutChangedListener, PaintCx, UpdateCx},
6 event::{Event, EventPropagation, FocusEvent, Phase, listener},
7 prelude::EventListenerTrait,
8 prop_extractor,
9 style::{
10 ContextValue, CustomStylable, CustomStyle, ExprStyle, FontProps, LineHeight, Selectable,
11 SelectionCornerRadius, SelectionStyle, Style, TextAlignProp, TextColor, TextOverflow,
12 TextOverflowProp,
13 },
14 style_class,
15 text::{
16 Attrs, AttrsList, Cursor, FamilyOwned, TextLayout, TextLayoutState, TextSelection,
17 WordBreakStrength,
18 },
19 view::{LayoutNodeCx, View},
20 views::editor::SelectionColor,
21};
22use floem_reactive::UpdaterEffect;
23use floem_renderer::Renderer;
24use peniko::{
25 Brush,
26 color::palette::{self},
27 kurbo::Point,
28};
29use ui_events::{
30 keyboard::{Key, KeyState, KeyboardEvent},
31 pointer::{PointerButtonEvent, PointerEvent},
32};
33
34use super::{Decorators, TextCommand};
35
36prop_extractor! {
37 LabelProps {
38 color: TextColor,
39 text_overflow: TextOverflowProp,
40 line_height: LineHeight,
41 text_selectable: Selectable,
42 text_align: TextAlignProp,
43 }
44}
45
46style_class!(
47 pub LabelClass
49);
50
51#[derive(Debug, Clone, PartialEq)]
86enum SelectionState {
87 None,
88 Ready {
89 origin: Point,
90 selection: TextSelection,
91 },
92 Selecting(TextSelection),
93 Selected(TextSelection),
94}
95
96pub struct Label {
98 id: ViewId,
99 label: String,
100 layout_data: Rc<RefCell<TextLayoutState>>,
102 selection_state: SelectionState,
103 selection_style: SelectionStyle,
104 font_props: FontProps,
105 label_props: LabelProps,
106 text_node: Option<taffy::NodeId>,
107 layout_node: Option<taffy::NodeId>,
108}
109
110impl Label {
111 fn new_internal(id: ViewId, label: String) -> Self {
112 id.register_listener(LayoutChangedListener::listener_key());
113 let layout_data = Rc::new(RefCell::new(TextLayoutState::new(Some(id))));
114 let mut label = Label {
115 id,
116 label,
117 layout_data,
118 text_node: None,
119 layout_node: None,
120 selection_state: SelectionState::None,
121 selection_style: Default::default(),
122 font_props: FontProps::default(),
123 label_props: Default::default(),
124 };
125 label.set_text_layout();
126 label.set_taffy_layout();
127 label.class(LabelClass)
128 }
129
130 pub fn new<S: Display>(label: S) -> Self {
140 Self::new_internal(ViewId::new(), label.to_string())
141 }
142
143 pub fn with_id<S: Display>(id: ViewId, label: S) -> Self {
148 Self::new_internal(id, label.to_string())
149 }
150
151 pub fn derived<S: Display + 'static>(label: impl Fn() -> S + 'static) -> Self {
161 let id = ViewId::new();
162 let initial_label = UpdaterEffect::new(
163 move || label().to_string(),
164 move |new_label| id.update_state(new_label),
165 );
166 Self::new_internal(id, initial_label).on_event_cont(listener::FocusLost, move |_, _| {
167 id.request_layout();
168 })
169 }
170
171 fn with_effective_text_layout<O>(&self, with: impl FnOnce(&TextLayout) -> O) -> O {
172 self.layout_data.borrow().with_effective_text_layout(with)
173 }
174}
175
176#[deprecated(since = "0.2.0", note = "Use Label::new() instead")]
188pub fn text<S: Display>(text: S) -> Label {
189 Label::new(text)
190}
191
192#[deprecated(since = "0.2.0", note = "Use Label::new() instead")]
194pub fn static_label(label: impl Into<String>) -> Label {
195 Label::new(label.into())
196}
197
198#[deprecated(since = "0.2.0", note = "Use Label::derived() instead")]
209pub fn label<S: Display + 'static>(label: impl Fn() -> S + 'static) -> Label {
210 Label::derived(label)
211}
212
213impl Label {
214 fn mark_text_measure_dirty(&self) {
215 if let Some(text_node) = self.text_node {
216 let _ = self.id.taffy().borrow_mut().mark_dirty(text_node);
217 }
218 let _ = self.id.mark_view_layout_dirty();
219 }
220
221 fn get_attrs_list(&self) -> AttrsList {
222 let mut attrs = Attrs::new().color(self.label_props.color().unwrap_or(palette::css::BLACK));
223 let font_size = self.font_props.size();
224 attrs = attrs.font_size(font_size as f32);
225
226 if let Some(font_style) = self.font_props.style() {
227 attrs = attrs.font_style(font_style);
228 }
229 let font_family = self.font_props.family().as_ref().map(|font_family| {
230 let family: Vec<FamilyOwned> = FamilyOwned::parse_list(font_family).collect();
231 family
232 });
233 if let Some(font_family) = font_family.as_ref() {
234 attrs = attrs.family(font_family);
235 }
236 if let Some(font_weight) = self.font_props.weight() {
237 attrs = attrs.weight(font_weight);
238 }
239 attrs = attrs.line_height(self.label_props.line_height());
240 if let TextOverflow::Wrap { word_break, .. } = self.label_props.text_overflow()
241 && word_break != WordBreakStrength::Normal
242 {
243 attrs = attrs.word_break(word_break);
244 }
245 AttrsList::new(attrs)
246 }
247
248 fn set_text_layout(&mut self) {
249 let attrs_list = self.get_attrs_list();
250 let align = self.label_props.text_align();
251 let text_overflow = self.label_props.text_overflow();
252
253 let mut layout_data = self.layout_data.borrow_mut();
254 layout_data.set_text(&self.label, attrs_list, align);
255 layout_data.set_text_overflow(text_overflow);
256
257 self.mark_text_measure_dirty();
258 }
259
260 fn get_hit_point(&self, point: Point) -> Option<Cursor> {
261 let (Some(parent_node), Some(text_node)) = (self.layout_node, self.text_node) else {
262 return None;
263 };
264
265 let text_loc = self.get_text_origin(parent_node, text_node);
266 self.with_effective_text_layout(|l| l.hit_test(point - text_loc.to_vec2()))
267 }
268
269 fn get_text_origin(&self, parent_node: taffy::NodeId, text_node: taffy::NodeId) -> Point {
270 let content_rect = self
271 .id
272 .get_content_rect_relative(text_node, parent_node)
273 .unwrap_or_default();
274 self.layout_data.borrow().centered_text_origin(content_rect)
275 }
276
277 fn update_drag_selection(&mut self, focus_point: Point) {
278 let Some(focus) = self.get_hit_point(focus_point) else {
279 return;
280 };
281
282 match self.selection_state {
283 SelectionState::Ready { selection, .. } => {
284 let next_selection = self
285 .layout_data
286 .borrow()
287 .get_effective_text_layout()
288 .map(|layout| layout.begin_selection(selection.anchor(), focus))
289 .expect("label text layout should be available while selecting");
290 self.selection_state = SelectionState::Selecting(next_selection);
291 }
292 SelectionState::Selecting(selection) | SelectionState::Selected(selection) => {
293 let selection = self
294 .layout_data
295 .borrow()
296 .get_effective_text_layout()
297 .map(|layout| layout.selection(selection.anchor(), focus))
298 .expect("label text layout should be available while selecting");
299 self.selection_state = SelectionState::Selecting(selection);
300 }
301 SelectionState::None => {}
302 }
303 }
304
305 fn selection(&self) -> Option<TextSelection> {
306 match self.selection_state {
307 SelectionState::Selecting(selection) | SelectionState::Selected(selection)
308 if !selection.is_collapsed() =>
309 {
310 Some(selection)
311 }
312 SelectionState::Ready { .. } | SelectionState::None => None,
313 SelectionState::Selecting(_) | SelectionState::Selected(_) => None,
314 }
315 }
316
317 fn handle_modifier_cmd(&mut self, command: &TextCommand) -> bool {
318 match command {
319 TextCommand::Copy => {
320 if let Some(selection) = self.selection() {
321 let layout_data = self.layout_data.borrow();
322 if let Some(text_layout) = layout_data.get_effective_text_layout() {
323 let range = text_layout.selection_text_range(&selection);
324 let selection_txt = self.label[range].into();
325 let _ = Clipboard::set_contents(selection_txt);
326 }
327 }
328 true
329 }
330 _ => false,
331 }
332 }
333 fn handle_key_down(&mut self, event: &KeyboardEvent) -> bool {
334 if event.modifiers.is_empty() {
335 return false;
336 }
337 if !matches!(event.key, Key::Character(_)) {
338 return false;
339 }
340
341 self.handle_modifier_cmd(&event.into())
342 }
343
344 fn paint_selection(&self, text_loc: Point, paint_cx: &mut PaintCx) {
345 if let Some(selection) = self.selection() {
346 let selection_color = self.selection_style.selection_color();
347 self.layout_data
348 .borrow()
349 .selection_rects_for_selection(&selection, text_loc, |rect| {
350 paint_cx.fill(&rect, &selection_color, 0.0);
351 });
352 }
353 }
354
355 fn set_taffy_layout(&mut self) {
356 let taffy_node = self.id.taffy_node();
357 let taffy = self.id.taffy();
358 let mut taffy = taffy.borrow_mut();
359 let text_node = taffy
360 .new_leaf(taffy::Style {
361 ..taffy::Style::DEFAULT
362 })
363 .unwrap();
364
365 let layout_fn = TextLayoutState::create_taffy_layout_fn(self.layout_data.clone());
366 let finalize_fn = TextLayoutState::create_finalize_fn(self.layout_data.clone());
367 self.text_node = Some(text_node);
368 self.layout_node = Some(taffy_node);
369
370 taffy
371 .set_node_context(
372 text_node,
373 Some(LayoutNodeCx::Custom {
374 measure: layout_fn,
375 finalize: Some(finalize_fn),
376 }),
377 )
378 .unwrap();
379 taffy.set_children(taffy_node, &[text_node]).unwrap();
380 }
381}
382
383impl View for Label {
384 fn id(&self) -> ViewId {
385 self.id
386 }
387
388 fn view_style(&self) -> Option<Style> {
389 None
390 }
391
392 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
393 format!("Label: {:?}", self.label).into()
394 }
395
396 fn event(&mut self, cx: &mut EventCx) -> EventPropagation {
397 if let Some(layout) = LayoutChangedListener::extract(&cx.event) {
398 self.layout_data
399 .borrow_mut()
400 .finalize_for_width(layout.new_content_box.width() as f32);
401 }
402 match &cx.event {
403 Event::Focus(FocusEvent::Lost) => {
404 self.selection_state = SelectionState::None;
405 cx.window_state.request_paint(self.id);
406 return EventPropagation::Continue;
407 }
408 Event::Pointer(PointerEvent::Down(PointerButtonEvent { state, pointer, .. }))
409 if self.label_props.text_selectable()
410 && state
411 .buttons
412 .contains(ui_events::pointer::PointerButton::Primary) =>
413 {
414 self.selection_state = self
415 .get_hit_point(state.logical_point())
416 .map(|cursor| SelectionState::Ready {
417 origin: state.logical_point(),
418 selection: self
419 .layout_data
420 .borrow()
421 .get_effective_text_layout()
422 .map(|layout| layout.collapsed_selection(cursor))
423 .expect("label text layout should be available on pointer down"),
424 })
425 .unwrap_or(SelectionState::None);
426 if let Some(pointer_id) = pointer.pointer_id {
427 cx.window_state.set_pointer_capture(pointer_id, self.id);
428 }
429 cx.window_state.request_paint(self.id);
430 }
431 Event::Pointer(PointerEvent::Move(pu)) => {
432 if !self.label_props.text_selectable() {
433 if self.selection_state != SelectionState::None {
434 self.selection_state = SelectionState::None;
435 cx.window_state.request_paint(self.id);
436 }
437 } else {
438 match self.selection_state {
439 SelectionState::Ready { origin, .. } => {
440 if origin.distance(pu.current.logical_point()).abs() > 2. {
442 self.update_drag_selection(pu.current.logical_point());
443 cx.window_state.request_paint(self.id);
444 self.id.request_focus();
445 }
446 }
447 SelectionState::Selecting(_) => {
448 self.update_drag_selection(pu.current.logical_point());
449 cx.window_state.request_paint(self.id);
450 }
451 SelectionState::Selected(_) => return EventPropagation::Continue,
452 SelectionState::None => return EventPropagation::Continue,
453 }
454 }
455 }
456 Event::Pointer(PointerEvent::Up { .. }) => {
457 self.selection_state = match self.selection_state {
458 SelectionState::Selecting(selection) if !selection.is_collapsed() => {
459 SelectionState::Selected(selection)
460 }
461 _ => SelectionState::None,
462 };
463 cx.window_state.request_paint(self.id);
464 }
465 Event::Key(
466 ke @ KeyboardEvent {
467 state: KeyState::Down,
468 ..
469 },
470 ) => {
471 if cx.phase != Phase::Target || !cx.window_state.is_focused(self.id) {
472 return EventPropagation::Continue;
473 }
474 if self.handle_key_down(ke) {
475 return EventPropagation::Stop;
476 }
477 }
478 _ => {}
479 }
480 EventPropagation::Continue
481 }
482
483 fn style_pass(&mut self, cx: &mut crate::context::StyleCx<'_>) {
484 if self.font_props.read(cx) | self.label_props.read(cx) {
485 self.layout_data.borrow_mut().clear_overflow_state();
486 self.set_text_layout();
487 self.id.request_layout();
488 self.id.request_paint();
489 }
490 if self.selection_style.read(cx) {
491 cx.window_state.request_paint(self.id);
492 }
493 }
494
495 fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
496 if state.is::<String>()
497 && let Ok(state) = state.downcast::<String>()
498 {
499 self.label = *state;
500 self.layout_data.borrow_mut().clear_overflow_state();
501 self.set_text_layout();
502 cx.window_state.schedule_layout();
503 }
504 }
505
506 fn paint(&mut self, cx: &mut crate::context::PaintCx) {
507 if self.label.is_empty() {
508 return;
509 }
510
511 let (Some(this_node), Some(text_node)) = (self.layout_node, self.text_node) else {
512 return;
513 };
514
515 let text_loc = self.get_text_origin(this_node, text_node);
516
517 self.with_effective_text_layout(|l| {
518 l.draw(cx, text_loc);
519 if cx.window_state.is_focused(self.id) {
520 self.paint_selection(text_loc, cx);
521 }
522 });
523 }
524}
525
526#[derive(Debug, Clone)]
528pub struct LabelCustomStyle(Style);
529impl From<LabelCustomStyle> for Style {
530 fn from(value: LabelCustomStyle) -> Self {
531 value.0
532 }
533}
534impl From<Style> for LabelCustomStyle {
535 fn from(value: Style) -> Self {
536 Self(value)
537 }
538}
539impl CustomStyle for LabelCustomStyle {
540 type StyleClass = LabelClass;
541}
542
543impl CustomStylable<LabelCustomStyle> for Label {
544 type DV = Self;
545}
546
547impl LabelCustomStyle {
548 pub fn new() -> Self {
549 Self(Style::new())
550 }
551
552 pub fn selectable(mut self, selectable: impl Into<bool>) -> Self {
553 self = Self(self.0.set(Selectable, selectable));
554 self
555 }
556
557 pub fn selection_corner_radius(mut self, corner_radius: impl Into<f64>) -> Self {
558 self = Self(self.0.set(SelectionCornerRadius, corner_radius));
559 self
560 }
561
562 pub fn selection_color(mut self, color: impl Into<Brush>) -> Self {
563 self = Self(self.0.set(SelectionColor, color.into()));
564 self
565 }
566}
567impl Default for LabelCustomStyle {
568 fn default() -> Self {
569 Self::new()
570 }
571}
572
573#[derive(Clone, Default)]
574pub struct LabelCustomExprStyle(Style);
575impl From<LabelCustomExprStyle> for Style {
576 fn from(value: LabelCustomExprStyle) -> Self {
577 value.0
578 }
579}
580impl From<Style> for LabelCustomExprStyle {
581 fn from(value: Style) -> Self {
582 Self(value)
583 }
584}
585impl LabelCustomExprStyle {
586 pub fn new() -> Self {
587 Self(Style::new())
588 }
589
590 pub fn selectable<T>(mut self, selectable: ContextValue<T>) -> Self
591 where
592 T: Into<bool> + 'static,
593 {
594 self = Self(
595 ExprStyle::from(self.0)
596 .set_context(Selectable, selectable.map(Into::into))
597 .into(),
598 );
599 self
600 }
601
602 pub fn selection_corner_radius<T>(mut self, corner_radius: ContextValue<T>) -> Self
603 where
604 T: Into<f64> + 'static,
605 {
606 self = Self(
607 ExprStyle::from(self.0)
608 .set_context(SelectionCornerRadius, corner_radius.map(Into::into))
609 .into(),
610 );
611 self
612 }
613
614 pub fn selection_color<T>(mut self, color: ContextValue<T>) -> Self
615 where
616 T: Into<Brush> + 'static,
617 {
618 self = Self(
619 ExprStyle::from(self.0)
620 .set_context(SelectionColor, color.map(Into::into))
621 .into(),
622 );
623 self
624 }
625}