freya_components/
selectable_text.rs1use std::rc::Rc;
2
3use dioxus::prelude::*;
4use freya_core::platform::CursorIcon;
5use freya_elements::{
6 self as dioxus_elements,
7 events::KeyboardEvent,
8 MouseEvent,
9};
10use freya_hooks::{
11 use_editable,
12 use_focus,
13 use_platform,
14 EditableConfig,
15 EditableEvent,
16 EditableMode,
17 TextEditor,
18};
19
20#[derive(Debug, Default, PartialEq, Clone, Copy)]
22pub enum SelectableTextStatus {
23 #[default]
25 Idle,
26 Hovering,
28}
29
30#[component]
43pub fn SelectableText(value: ReadOnlySignal<String>) -> Element {
44 let platform = use_platform();
45 let mut editable = use_editable(
46 move || EditableConfig::new(value()).with_allow_changes(false),
47 EditableMode::MultipleLinesSingleEditor,
48 );
49 let mut status = use_signal(SelectableTextStatus::default);
50 let mut focus = use_focus();
51 let mut drag_origin = use_signal(|| None);
52
53 if &*value.read() != editable.editor().read().rope() {
54 editable.editor_mut().write().set(&value.read());
55 editable.editor_mut().write().editor_history().clear();
56 }
57
58 use_drop(move || {
59 if *status.peek() == SelectableTextStatus::Hovering {
60 platform.set_cursor(CursorIcon::default());
61 }
62 });
63
64 let a11y_id = focus.attribute();
65 let cursor_reference = editable.cursor_attr();
66 let highlights = editable.highlights_attr(0);
67
68 let onmousedown = move |e: MouseEvent| {
69 e.stop_propagation();
70 drag_origin.set(Some(e.get_screen_coordinates() - e.element_coordinates));
71 editable.process_event(&EditableEvent::MouseDown(e.data, 0));
72 focus.request_focus();
73 };
74
75 let onglobalmousemove = move |mut e: MouseEvent| {
76 if focus.is_focused() {
77 if let Some(drag_origin) = drag_origin() {
78 let data = Rc::get_mut(&mut e.data).unwrap();
79 data.element_coordinates.x -= drag_origin.x;
80 data.element_coordinates.y -= drag_origin.y;
81 editable.process_event(&EditableEvent::MouseMove(e.data, 0));
82 }
83 }
84 };
85
86 let onmouseenter = move |_| {
87 platform.set_cursor(CursorIcon::Text);
88 *status.write() = SelectableTextStatus::Hovering;
89 };
90
91 let onmouseleave = move |_| {
92 platform.set_cursor(CursorIcon::default());
93 *status.write() = SelectableTextStatus::default();
94 };
95
96 let onclick = move |_: MouseEvent| {
97 editable.process_event(&EditableEvent::Click);
98 };
99
100 let onkeydown = move |e: KeyboardEvent| {
101 editable.process_event(&EditableEvent::KeyDown(e.data));
102 };
103
104 let onkeyup = move |e: KeyboardEvent| {
105 editable.process_event(&EditableEvent::KeyUp(e.data));
106 };
107
108 let onglobalclick = move |_| {
109 match *status.read() {
110 SelectableTextStatus::Idle if focus.is_focused() => {
111 editable.process_event(&EditableEvent::Click);
112 }
113 SelectableTextStatus::Hovering => {
114 editable.process_event(&EditableEvent::Click);
115 }
116 _ => {}
117 };
118
119 if focus.is_focused() {
124 if drag_origin.read().is_some() {
125 drag_origin.set(None);
126 } else {
127 editable.editor_mut().write().clear_selection();
128 focus.request_unfocus();
129 }
130 }
131 };
132
133 rsx!(
134 paragraph {
135 a11y_focusable: "true",
136 a11y_id,
137 cursor_id: "0",
138 cursor_mode: "editable",
139 cursor_color: "black",
140 highlights,
141 cursor_reference,
142 onclick,
143 onglobalmousemove,
144 onmousedown,
145 onmouseenter,
146 onmouseleave,
147 onglobalclick,
148 onkeydown,
149 onkeyup,
150 text {
151 "{editable.editor()}"
152 }
153 }
154 )
155}
156
157#[cfg(test)]
158mod test {
159 use freya::prelude::*;
160 use freya_testing::prelude::*;
161
162 #[tokio::test]
163 pub async fn selectable_text() {
164 fn selectable_text_app() -> Element {
165 rsx!(SelectableText {
166 value: "Hello, World!"
167 })
168 }
169
170 let mut utils = launch_test(selectable_text_app);
171
172 let root = utils.root().get(0);
174 assert_eq!(root.get(0).get(0).text(), Some("Hello, World!"));
175 utils.wait_for_update().await;
176 utils.wait_for_update().await;
177
178 utils.push_event(TestEvent::Mouse {
179 name: EventName::MouseDown,
180 cursor: (3.0, 3.0).into(),
181 button: Some(MouseButton::Left),
182 });
183 utils.wait_for_update().await;
184 utils.wait_for_update().await;
185 utils.push_event(TestEvent::Mouse {
186 name: EventName::MouseMove,
187 cursor: (55.0, 3.0).into(),
188 button: Some(MouseButton::Left),
189 });
190 utils.wait_for_update().await;
191 utils.wait_for_update().await;
192
193 let root = utils.root().get(0);
194 let highlights = root.state().cursor.highlights.clone();
195 #[cfg(not(target_os = "macos"))]
196 assert_eq!(highlights, Some(vec![(0, 8)]));
197
198 #[cfg(target_os = "macos")]
199 assert_eq!(highlights, Some(vec![(0, 7)]));
200 }
201}