cranpose_ui/widgets/
clickable_text.rs1#![expect(non_snake_case, reason = "names follow Compose's casing")]
7
8use std::{cell::Cell, rc::Rc};
9
10use cranpose_core::{NodeId, remember};
11use cranpose_ui_graphics::Size;
12
13use crate::{
14 modifier::Modifier,
15 text::{AnnotatedString, TextLayoutOptions, TextOverflow, TextStyle, measure},
16 widgets::BasicTextWithOptions,
17};
18
19#[doc(hidden)]
20pub trait IntoSharedAnnotatedString {
21 fn into_shared(self) -> Rc<AnnotatedString>;
22}
23
24impl IntoSharedAnnotatedString for AnnotatedString {
25 fn into_shared(self) -> Rc<AnnotatedString> {
26 Rc::new(self)
27 }
28}
29
30impl IntoSharedAnnotatedString for Rc<AnnotatedString> {
31 fn into_shared(self) -> Rc<AnnotatedString> {
32 self
33 }
34}
35
36pub fn ClickableText<T>(
55 text: T,
56 modifier: Modifier,
57 style: TextStyle,
58 on_click: impl Fn(usize) + 'static,
59) -> NodeId
60where
61 T: IntoSharedAnnotatedString,
62{
63 let text = text.into_shared();
64 let text_for_click = Rc::clone(&text);
65 let style_for_click = style.clone();
66 let options = TextLayoutOptions {
67 overflow: TextOverflow::Clip,
68 soft_wrap: true,
69 max_lines: usize::MAX,
70 min_lines: 1,
71 };
72 let size = remember(|| Rc::new(Cell::new(Size::default()))).with(Rc::clone);
75 let clickable_modifier = modifier
76 .report_size(Rc::clone(&size))
77 .clickable(move |point| {
78 on_click(measure::offset_in_drawn_text(
79 &text_for_click,
80 &style_for_click,
81 options,
82 size.get().width,
83 point,
84 ));
85 });
86
87 BasicTextWithOptions(text, clickable_modifier, style, options)
88}
89
90#[cfg(test)]
91#[path = "tests/clickable_text_tests.rs"]
92mod tests;