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