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