freya_components/
selectable_text.rs1use freya_core::prelude::*;
2use freya_edit::*;
3
4#[derive(Debug, Default, PartialEq, Clone, Copy)]
6pub enum SelectableTextStatus {
7 #[default]
9 Idle,
10 Hovering,
12}
13
14#[derive(Clone, PartialEq)]
16enum SelectableContent {
17 Span(Span<'static>),
18 Child(Element),
19}
20
21#[derive(Clone, PartialEq)]
22pub struct SelectableText {
23 contents: Vec<SelectableContent>,
24 layout: LayoutData,
25 accessibility: AccessibilityData,
26 text_style_data: TextStyleData,
27 event_handlers: EventHandlers,
28 layer: Layer,
29 max_lines: Option<usize>,
30 line_height: Option<f32>,
31 key: DiffKey,
32}
33
34impl KeyExt for SelectableText {
35 fn write_key(&mut self) -> &mut DiffKey {
36 &mut self.key
37 }
38}
39
40impl LayoutExt for SelectableText {
41 fn get_layout(&mut self) -> &mut LayoutData {
42 &mut self.layout
43 }
44}
45
46impl ContainerExt for SelectableText {}
47
48impl AccessibilityExt for SelectableText {
49 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
50 &mut self.accessibility
51 }
52}
53
54impl TextStyleExt for SelectableText {
55 fn get_text_style_data(&mut self) -> &mut TextStyleData {
56 &mut self.text_style_data
57 }
58}
59
60impl EventHandlersExt for SelectableText {
61 fn get_event_handlers(&mut self) -> &mut EventHandlers {
62 &mut self.event_handlers
63 }
64}
65
66impl LayerExt for SelectableText {
67 fn get_layer(&mut self) -> &mut Layer {
68 &mut self.layer
69 }
70}
71
72impl Default for SelectableText {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78impl SelectableText {
79 pub fn new() -> Self {
80 Self {
81 contents: Vec::new(),
82 layout: LayoutData::default(),
83 accessibility: AccessibilityData::default(),
84 text_style_data: TextStyleData::default(),
85 event_handlers: EventHandlers::default(),
86 layer: Layer::default(),
87 max_lines: None,
88 line_height: None,
89 key: DiffKey::None,
90 }
91 }
92
93 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
95 self.contents.push(SelectableContent::Span(span.into()));
96 self
97 }
98
99 pub fn child(mut self, child: impl IntoElement) -> Self {
101 self.contents
102 .push(SelectableContent::Child(child.into_element()));
103 self
104 }
105
106 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
107 self.max_lines = max_lines.into();
108 self
109 }
110
111 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
112 self.line_height = line_height.into();
113 self
114 }
115
116 fn editor_text(&self) -> String {
119 self.contents
120 .iter()
121 .map(|content| match content {
122 SelectableContent::Span(span) => span.text.as_ref(),
123 SelectableContent::Child(_) => " ",
124 })
125 .collect()
126 }
127}
128
129impl Component for SelectableText {
130 fn render(&self) -> impl IntoElement {
131 let value = self.editor_text();
132 let holder = use_state(ParagraphHolder::default);
133 let mut editable = use_editable(
134 || self.editor_text(),
135 move || EditableConfig::new().with_allow_changes(false),
136 );
137 let mut status = use_state(SelectableTextStatus::default);
138 let a11y_id = use_a11y();
139 let mut drag_origin = use_state(|| None);
140
141 if value.as_str() != editable.editor().read().rope() {
142 editable.editor_mut().write().set(value.as_str());
143 editable.editor_mut().write().editor_history().clear();
144 }
145
146 let highlights = editable
147 .editor()
148 .read()
149 .get_visible_selection(EditorLine::SingleParagraph);
150
151 let on_pointer_down = move |e: Event<PointerEventData>| {
152 if !e.data().is_primary() {
153 return;
154 }
155 e.stop_propagation();
156 drag_origin.set(Some(e.global_location() - e.element_location()));
157 editable.process_event(EditableEvent::Down {
158 location: e.element_location(),
159 editor_line: EditorLine::SingleParagraph,
160 holder: &holder.read(),
161 });
162 a11y_id.request_focus();
163 };
164
165 let on_global_pointer_move = move |e: Event<PointerEventData>| {
166 if a11y_id.is_focused()
167 && let Some(drag_origin) = drag_origin()
168 {
169 let mut element_location = e.element_location();
170 element_location.x -= drag_origin.x;
171 element_location.y -= drag_origin.y;
172 editable.process_event(EditableEvent::Move {
173 location: element_location,
174 editor_line: EditorLine::SingleParagraph,
175 holder: &holder.read(),
176 });
177 }
178 };
179
180 let on_global_pointer_down = move |_: Event<PointerEventData>| {
181 if *status.read() == SelectableTextStatus::Idle {
182 editable.editor_mut().write().clear_selection();
183 }
184 };
185
186 let on_pointer_enter = move |_| {
187 *status.write() = SelectableTextStatus::Hovering;
188 };
189
190 let on_pointer_leave = move |_| {
191 *status.write() = SelectableTextStatus::default();
192 };
193
194 let on_mouse_up = move |_| {
195 editable.process_event(EditableEvent::Release);
196 };
197
198 let on_key_down = move |e: Event<KeyboardEventData>| {
199 editable.process_event(EditableEvent::KeyDown {
200 key: &e.key,
201 modifiers: e.modifiers,
202 });
203 };
204
205 let on_key_up = move |e: Event<KeyboardEventData>| {
206 editable.process_event(EditableEvent::KeyUp { key: &e.key });
207 };
208
209 let on_global_pointer_press = move |_: Event<PointerEventData>| {
210 match *status.read() {
211 SelectableTextStatus::Idle if a11y_id.is_focused() => {
212 editable.process_event(EditableEvent::Release);
213 }
214 SelectableTextStatus::Hovering => {
215 editable.process_event(EditableEvent::Release);
216 }
217 _ => {}
218 };
219
220 if drag_origin.read().is_some() {
221 drag_origin.set(None);
222 } else if a11y_id.is_focused() {
223 a11y_id.request_unfocus();
224 }
225 };
226
227 let mut paragraph = paragraph()
228 .layout(self.layout.clone())
229 .accessibility(self.accessibility.clone())
230 .text_style(self.text_style_data.clone())
231 .event_handlers(self.event_handlers.clone())
232 .layer(self.layer)
233 .max_lines(self.max_lines)
234 .line_height(self.line_height)
235 .a11y_id(a11y_id)
236 .a11y_focusable(true)
237 .holder(holder.read().clone())
238 .cursor_color(Color::BLACK)
239 .highlights(highlights.map(|h| vec![h]))
240 .on_mouse_up(on_mouse_up)
241 .on_global_pointer_move(on_global_pointer_move)
242 .on_global_pointer_down(on_global_pointer_down)
243 .on_pointer_down(on_pointer_down)
244 .on_pointer_enter(on_pointer_enter)
245 .on_pointer_leave(on_pointer_leave)
246 .on_global_pointer_press(on_global_pointer_press)
247 .on_key_down(on_key_down)
248 .on_key_up(on_key_up);
249
250 for content in &self.contents {
251 paragraph = match content {
252 SelectableContent::Span(span) => paragraph.span(span.clone()),
253 SelectableContent::Child(child) => paragraph.child(child.clone()),
254 };
255 }
256
257 paragraph
258 }
259
260 fn render_key(&self) -> DiffKey {
261 self.key.clone().or(self.default_key())
262 }
263}