freya_components/
selectable_text.rs1use std::borrow::Cow;
2
3use freya_core::prelude::*;
4use freya_edit::*;
5
6#[derive(Debug, Default, PartialEq, Clone, Copy)]
8pub enum SelectableTextStatus {
9 #[default]
11 Idle,
12 Hovering,
14}
15
16#[derive(Clone, PartialEq)]
17pub struct SelectableText {
18 value: Cow<'static, str>,
19 layout: LayoutData,
20 accessibility: AccessibilityData,
21 text_style_data: TextStyleData,
22 max_lines: Option<usize>,
23 line_height: Option<f32>,
24 key: DiffKey,
25}
26
27impl KeyExt for SelectableText {
28 fn write_key(&mut self) -> &mut DiffKey {
29 &mut self.key
30 }
31}
32
33impl LayoutExt for SelectableText {
34 fn get_layout(&mut self) -> &mut LayoutData {
35 &mut self.layout
36 }
37}
38
39impl ContainerExt for SelectableText {}
40
41impl AccessibilityExt for SelectableText {
42 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
43 &mut self.accessibility
44 }
45}
46
47impl TextStyleExt for SelectableText {
48 fn get_text_style_data(&mut self) -> &mut TextStyleData {
49 &mut self.text_style_data
50 }
51}
52
53impl SelectableText {
54 pub fn new(value: impl Into<Cow<'static, str>>) -> Self {
55 Self {
56 value: value.into(),
57 layout: LayoutData::default(),
58 accessibility: AccessibilityData::default(),
59 text_style_data: TextStyleData::default(),
60 max_lines: None,
61 line_height: None,
62 key: DiffKey::None,
63 }
64 }
65
66 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
67 self.max_lines = max_lines.into();
68 self
69 }
70
71 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
72 self.line_height = line_height.into();
73 self
74 }
75}
76
77impl Component for SelectableText {
78 fn render(&self) -> impl IntoElement {
79 let holder = use_state(ParagraphHolder::default);
80 let mut editable = use_editable(
81 || self.value.to_string(),
82 move || EditableConfig::new().with_allow_changes(false),
83 );
84 let mut status = use_state(SelectableTextStatus::default);
85 let focus = use_focus();
86 let mut drag_origin = use_state(|| None);
87
88 if self.value.as_ref() != editable.editor().read().rope() {
89 editable.editor_mut().write().set(self.value.as_ref());
90 editable.editor_mut().write().editor_history().clear();
91 }
92
93 let highlights = editable
94 .editor()
95 .read()
96 .get_visible_selection(EditorLine::SingleParagraph);
97
98 let on_pointer_down = move |e: Event<PointerEventData>| {
99 e.stop_propagation();
100 drag_origin.set(Some(e.global_location() - e.element_location()));
101 editable.process_event(EditableEvent::Down {
102 location: e.element_location(),
103 editor_line: EditorLine::SingleParagraph,
104 holder: &holder.read(),
105 });
106 focus.request_focus();
107 };
108
109 let on_global_pointer_move = move |e: Event<PointerEventData>| {
110 if focus.is_focused()
111 && let Some(drag_origin) = drag_origin()
112 {
113 let mut element_location = e.element_location();
114 element_location.x -= drag_origin.x;
115 element_location.y -= drag_origin.y;
116 editable.process_event(EditableEvent::Move {
117 location: element_location,
118 editor_line: EditorLine::SingleParagraph,
119 holder: &holder.read(),
120 });
121 }
122 };
123
124 let on_global_pointer_down = move |_: Event<PointerEventData>| {
125 if *status.read() == SelectableTextStatus::Idle {
126 editable.editor_mut().write().clear_selection();
127 }
128 };
129
130 let on_pointer_enter = move |_| {
131 *status.write() = SelectableTextStatus::Hovering;
132 };
133
134 let on_pointer_leave = move |_| {
135 *status.write() = SelectableTextStatus::default();
136 };
137
138 let on_mouse_up = move |_| {
139 editable.process_event(EditableEvent::Release);
140 };
141
142 let on_key_down = move |e: Event<KeyboardEventData>| {
143 editable.process_event(EditableEvent::KeyDown {
144 key: &e.key,
145 modifiers: e.modifiers,
146 });
147 };
148
149 let on_key_up = move |e: Event<KeyboardEventData>| {
150 editable.process_event(EditableEvent::KeyUp { key: &e.key });
151 };
152
153 let on_global_pointer_press = move |_: Event<PointerEventData>| {
154 match *status.read() {
155 SelectableTextStatus::Idle if focus.is_focused() => {
156 editable.process_event(EditableEvent::Release);
157 }
158 SelectableTextStatus::Hovering => {
159 editable.process_event(EditableEvent::Release);
160 }
161 _ => {}
162 };
163
164 if drag_origin.read().is_some() {
165 drag_origin.set(None);
166 } else if focus.is_focused() {
167 focus.request_unfocus();
168 }
169 };
170
171 paragraph()
172 .layout(self.layout.clone())
173 .accessibility(self.accessibility.clone())
174 .text_style(self.text_style_data.clone())
175 .max_lines(self.max_lines)
176 .line_height(self.line_height)
177 .a11y_id(focus.a11y_id())
178 .a11y_focusable(true)
179 .holder(holder.read().clone())
180 .cursor_color(Color::BLACK)
181 .highlights(highlights.map(|h| vec![h]))
182 .on_mouse_up(on_mouse_up)
183 .on_global_pointer_move(on_global_pointer_move)
184 .on_global_pointer_down(on_global_pointer_down)
185 .on_pointer_down(on_pointer_down)
186 .on_pointer_enter(on_pointer_enter)
187 .on_pointer_leave(on_pointer_leave)
188 .on_global_pointer_press(on_global_pointer_press)
189 .on_key_down(on_key_down)
190 .on_key_up(on_key_up)
191 .span(Span::new(editable.editor().read().to_string()))
192 }
193
194 fn render_key(&self) -> DiffKey {
195 self.key.clone().or(self.default_key())
196 }
197}