Skip to main content

freya_components/
selectable_text.rs

1use freya_core::prelude::*;
2use freya_edit::*;
3
4/// Current status of the SelectableText.
5#[derive(Debug, Default, PartialEq, Clone, Copy)]
6pub enum SelectableTextStatus {
7    /// Default state.
8    #[default]
9    Idle,
10    /// Mouse is hovering the text.
11    Hovering,
12}
13
14/// A piece of a [SelectableText]: styled text or an inline element flowing between the text.
15#[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    /// Append a styled [Span] to the text.
96    pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
97        self.contents.push(SelectableContent::Span(span.into()));
98        self
99    }
100
101    /// Append an inline element that flows between the text.
102    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    /// The text the editor selects over. Each inline element becomes a single space
119    /// so selection offsets stay aligned with the paragraph's placeholders.
120    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}