1use std::cell::RefCell;
8use std::rc::Rc;
9
10use repose_core::prelude::*;
11use repose_core::{Brush, PointerEvent, Rect, Scene, SceneNode, View};
12
13use crate::Text;
14use crate::textfield::{caret_xy_for_byte, index_for_xy_bytes};
15
16pub fn SelectableText(
17 text: impl Into<String>,
18 font_size_dp: f32,
19 on_selection_change: impl Fn(Option<(usize, usize)>) + 'static,
20) -> View {
21 let text: String = text.into();
22 let text_for_handlers = text.clone();
23 let text_for_paint = text.clone();
24
25 let selection: Rc<RefCell<Option<(usize, usize)>>> =
26 remember_with_key("sel:range", || RefCell::new(None));
27 let anchor: Rc<RefCell<usize>> = remember_with_key("sel:anchor", || RefCell::new(0));
28 let dragging: Rc<RefCell<bool>> = remember_with_key("sel:dragging", || RefCell::new(false));
29 let last_rect: Rc<RefCell<Rect>> =
30 remember_with_key("sel:last_rect", || RefCell::new(Rect::default()));
31
32 let callback = Rc::new(on_selection_change);
33
34 let cb_down = callback.clone();
35 let on_down = {
36 let text = text_for_handlers.clone();
37 let selection = selection.clone();
38 let anchor = anchor.clone();
39 let dragging = dragging.clone();
40 let last_rect = last_rect.clone();
41 move |ev: PointerEvent| {
42 let r = *last_rect.borrow();
43 if r.w <= 0.0 || r.h <= 0.0 {
44 return;
45 }
46 let font_px = dp_to_px(font_size_dp) * text_scale().0;
47 let lx = (ev.position.x - r.x).max(0.0);
48 let ly = (ev.position.y - r.y).max(0.0);
49 let wrap_w = r.w.max(1.0);
50 let byte = index_for_xy_bytes(&text, font_px, wrap_w, lx, ly);
51 *anchor.borrow_mut() = byte;
52 *selection.borrow_mut() = Some((byte, byte));
53 *dragging.borrow_mut() = true;
54 cb_down(Some((byte, byte)));
55 }
56 };
57
58 let cb_move = callback.clone();
59 let text_for_primary = text_for_handlers.clone();
60 let on_move = {
61 let text = text_for_handlers.clone();
62 let selection = selection.clone();
63 let anchor = anchor.clone();
64 let dragging = dragging.clone();
65 let last_rect = last_rect.clone();
66 move |ev: PointerEvent| {
67 if !*dragging.borrow() {
68 return;
69 }
70 let r = *last_rect.borrow();
71 if r.w <= 0.0 || r.h <= 0.0 {
72 return;
73 }
74 let font_px = dp_to_px(font_size_dp) * text_scale().0;
75 let lx = (ev.position.x - r.x).max(0.0);
76 let ly = (ev.position.y - r.y).max(0.0);
77 let wrap_w = r.w.max(1.0);
78 let byte = index_for_xy_bytes(&text, font_px, wrap_w, lx, ly);
79 let a = *anchor.borrow();
80 let sel = Some((a.min(byte), a.max(byte)));
81 *selection.borrow_mut() = sel;
82 cb_move(sel);
83 if let Some((s, e)) = sel
84 && e > s
85 {
86 repose_core::clipboard::set_primary_selection(&text[s..e]);
87 }
88 }
89 };
90
91 let cb_up = callback.clone();
92 let on_up = {
93 let text = text_for_primary.clone();
94 let selection = selection.clone();
95 let dragging = dragging.clone();
96 move |_ev: PointerEvent| {
97 *dragging.borrow_mut() = false;
98 let sel = *selection.borrow();
99 if let Some((a, b)) = sel {
100 let s = a.min(b);
101 let e = a.max(b);
102 if e > s {
103 repose_core::clipboard::set_primary_selection(&text[s..e]);
104 }
105 }
106 cb_up(sel);
107 }
108 };
109
110 let painter = {
111 let text = text_for_paint.clone();
112 let selection = selection.clone();
113 let last_rect = last_rect.clone();
114 move |scene: &mut Scene, rect: Rect, _alpha: f32| {
115 *last_rect.borrow_mut() = rect;
116
117 let (s, e) = match *selection.borrow() {
118 Some((a, b)) if a != b => {
119 if a < b {
120 (a, b)
121 } else {
122 (b, a)
123 }
124 }
125 _ => return,
126 };
127 if e == 0 || e <= s {
128 return;
129 }
130
131 let font_px = dp_to_px(font_size_dp) * text_scale().0;
132 let wrap_w = rect.w.max(1.0);
133 let (sx, sy, sli) = caret_xy_for_byte(&text, font_px, wrap_w, s);
134 let (ex, ey, eli) = caret_xy_for_byte(&text, font_px, wrap_w, e);
135 let th = theme();
136 let brush = Brush::Solid(th.primary.with_alpha(96));
137 let line_h = font_px * 1.2;
138
139 if sli == eli {
140 let x = sx.min(ex);
141 let w = (ex - sx).abs().max(2.0);
142 scene.nodes.push(SceneNode::Rect {
143 rect: Rect {
144 x: rect.x + x,
145 y: rect.y + sy,
146 w,
147 h: line_h,
148 },
149 brush,
150 radius: [0.0; 4],
151 });
152 } else {
153 scene.nodes.push(SceneNode::Rect {
154 rect: Rect {
155 x: rect.x + sx,
156 y: rect.y + sy,
157 w: (rect.w - sx).max(2.0),
158 h: line_h,
159 },
160 brush: brush,
161 radius: [0.0; 4],
162 });
163 if eli > sli + 1 {
164 scene.nodes.push(SceneNode::Rect {
165 rect: Rect {
166 x: rect.x,
167 y: rect.y + (sli as f32 + 1.0) * line_h,
168 w: rect.w,
169 h: (eli as f32 - sli as f32 - 1.0) * line_h,
170 },
171 brush: brush,
172 radius: [0.0; 4],
173 });
174 }
175 scene.nodes.push(SceneNode::Rect {
176 rect: Rect {
177 x: rect.x,
178 y: rect.y + ey,
179 w: ex.max(2.0),
180 h: line_h,
181 },
182 brush,
183 radius: [0.0; 4],
184 });
185 }
186 }
187 };
188
189 let mut v = Text(text_for_paint);
190 v.modifier = v
191 .modifier
192 .on_pointer_down(on_down)
193 .on_pointer_move(on_move)
194 .on_pointer_up(on_up)
195 .painter(painter)
196 .on_action({
197 let selection = selection.clone();
198 let text = text_for_handlers.clone();
199 move |action| match action {
200 repose_core::shortcuts::Action::Copy => {
201 let sel = *selection.borrow();
202 if let Some((a, b)) = sel {
203 let s = a.min(b);
204 let e = a.max(b);
205 if e > s {
206 repose_core::clipboard::copy_to_clipboard(&text[s..e]);
207 return true;
208 }
209 }
210 false
211 }
212 _ => false,
213 }
214 });
215 v
216}