cranpose_ui/widgets/
clickable_text.rs1#![allow(non_snake_case)]
7
8use std::rc::Rc;
9
10use cranpose_core::NodeId;
11
12use crate::{
13 modifier::Modifier,
14 text::{AnnotatedString, TextOverflow, TextStyle},
15 widgets::BasicText,
16};
17
18#[doc(hidden)]
19pub trait IntoSharedAnnotatedString {
20 fn into_shared(self) -> Rc<AnnotatedString>;
21}
22
23impl IntoSharedAnnotatedString for AnnotatedString {
24 fn into_shared(self) -> Rc<AnnotatedString> {
25 Rc::new(self)
26 }
27}
28
29impl IntoSharedAnnotatedString for Rc<AnnotatedString> {
30 fn into_shared(self) -> Rc<AnnotatedString> {
31 self
32 }
33}
34
35#[allow(clippy::needless_pass_by_value)]
65pub fn ClickableText<T>(
66 text: T,
67 modifier: Modifier,
68 style: TextStyle,
69 on_click: impl Fn(usize) + 'static,
70) -> NodeId
71where
72 T: IntoSharedAnnotatedString,
73{
74 let text = text.into_shared();
75 let text_for_click = text.clone();
76 let style_for_click = style.clone();
77 let on_click: Rc<dyn Fn(usize)> = Rc::new(on_click);
78
79 let clickable_modifier = modifier.clickable(move |point| {
80 let offset = crate::text::get_offset_for_position(
81 &text_for_click,
82 &style_for_click,
83 point.x,
84 point.y,
85 );
86 on_click(offset);
87 });
88
89 BasicText(
90 text,
91 clickable_modifier,
92 style,
93 TextOverflow::Clip,
94 true,
95 usize::MAX,
96 1,
97 )
98}
99
100#[cfg(test)]
101mod tests {
102 use cranpose_core::{Composition, MemoryApplier, location_key};
103
104 use super::*;
105
106 #[test]
107 fn clickable_text_composes_without_panic() {
108 let _app_context = crate::render_state::app_context_test_scope();
109 let mut comp = Composition::new(MemoryApplier::new());
110 comp.render(location_key(file!(), line!(), column!()), || {
111 ClickableText(
112 AnnotatedString::from("Hello"),
113 Modifier::empty(),
114 TextStyle::default(),
115 |_offset| {},
116 );
117 })
118 .expect("composition succeeds");
119 }
120}