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 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    /// Append a styled [Span] to the text.
94    pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
95        self.contents.push(SelectableContent::Span(span.into()));
96        self
97    }
98
99    /// Append an inline element that flows between the text.
100    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    /// The text the editor selects over. Each inline element becomes a single space
117    /// so selection offsets stay aligned with the paragraph's placeholders.
118    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}