1use super::{lazy::*, *};
2use u::{Caret, DynamicStr, caret as ca, if_ctrl};
3
4#[derive(Default, Debug)]
5pub struct TextEdit {
6 size: Vec2,
7 scale: f32,
8 a: mAffected,
9 parser: LazyCell<ParseResult<mAffected>>,
10 scrollbar: Slider,
11 pub text: CachedStr,
12}
13#[derive(Default, Debug)]
14struct mAffected {
15 caret: Caret,
16 select: Caret,
17 history: History,
18}
19impl TextEdit {
20 pub fn draw<'s: 'l, 'l>(&'s mut self, r: &mut RenderLock<'l>, t: &'l Theme, layout @ Surf { size, .. }: Surf, scale: f32, readonly: bool) {
21 let SCR_PAD = 0.02;
22 let CUR_PAD = 0.01;
23 let (id, s, font) = (ref_UUID(self), self, &t.font);
24
25 let lazy_parse = async move |text: Astr, font: Arc<Font>| {
26 let lnum_w = {
27 let lnum = f32(1 + stream::iter(text.lines()).count().await);
28 let max_lnum = lnum.log10().max(1.).ceil();
29 let w = font.glyph('0').adv * scale * max_lnum;
30 w.or_def(w * 20. < size.x())
31 };
32 let (lsb, lines, lnums) = u::parse_text(&text, &font, scale, size.x() - lnum_w - SCR_PAD, char::is_whitespace).await;
33 let lines: Vec<_> = stream::iter(lines.into_iter().zip(lnums.into_iter()).map(DynamicStr::new)).collect().await;
34 ((lsb, lnum_w), lines.into())
35 };
36
37 if s.text.changed() || scale != s.scale || size != s.size {
38 let (text, font) = (s.text.clone(), font.clone());
39
40 (s.size, s.scale, (s.a.caret, s.a.select), s.parser) = (
41 size,
42 scale,
43 Def(),
44 LazyCell::with(((Def(), vec![P(text.clone())].into()), Def()), async move |_| (lazy_parse(text, font).await, Def())),
45 );
46 }
47
48 let Self { a, parser, scrollbar, .. } = s;
49 let mut parser = parser.lock();
50 let (((lsb, lnum_w), ref lines), ref reconcile) = *parser;
51 reconcile.apply(a);
52 let mAffected { caret, select, history } = a;
53
54 let (scrollable, p, (start, len)) = {
55 let (start, len) = (1. - scrollbar.pip_pos, lines.len());
56 let (_, l) = u::visible_range(layout, scale, 0., len);
57 let skip = f32(len - l) * start;
58 let p = move |lines, n| u::line_pos(lines, font, scale, layout, skip, n, lsb);
59 (len > l, p, u::visible_range(layout, scale, skip, len))
60 };
61 let ((beg_y, end_y, len_y), (pip_size, adv)) = (vec3((start, start + len, len)), u::visible_norm(layout, len, lines.len()));
62
63 let Surf { pos, size } = layout.w(lnum_w);
64 r.draw(Rect { pos, size, color: t.fg });
65
66 let _c = r.clip(layout);
67
68 let layout @ Surf { pos, size } = layout.x(lnum_w).w_sub(f32(scrollable) * SCR_PAD + lnum_w);
69 r.draw(Rect { pos, size, color: t.bg });
70
71 if !r.focused(id) {
72 } else if caret != select {
73 let (caret @ (_, b), select @ (_, e)) = ca::sort(*caret, *select);
74
75 for i in b.max(beg_y)..=e.min(end_y) {
76 let x = 0.0.or_val(i != b, || ca::adv(lines, font, scale, caret, 0.));
77 let w = size.x().or_val(i != e, || ca::adv(lines, font, scale, select, 0.));
78 let pos = pos.sum(p(lines, usize(i))).sum((x, 0));
79 r.draw(Rect { pos, size: (w - x, scale), color: t.highlight });
80 }
81 } else {
82 let x = ca::adv(lines, font, scale, *caret, CUR_PAD);
83 let Surf { pos, size } = layout.xy(p(lines, usize(caret.y()))).x(x).size((CUR_PAD, scale));
84 r.draw(Rect { pos, size, color: t.highlight });
85 }
86
87 let words = lines
88 .iter()
89 .enumerate()
90 .skip(start)
91 .take(len + 1)
92 .map(|(n, line)| {
93 let p = p(lines, n);
94 if let R(_) = line {
95 let Surf { pos, size } = layout.x_self(1).x(-CUR_PAD).size((CUR_PAD, CUR_PAD));
96 r.draw(Rect { pos: pos.sum(p), size, color: t.highlight });
97 }
98
99 (p, line)
100 })
101 .collect_vec();
102
103 words.into_iter().for_each(|(p, line)| {
104 let (p, text) = (pos.sum(p), line.as_clipped_str(font, scale, size.x()));
105 if !text.is_empty() {
106 r.draw(Text { pos: p, scale, color: t.text, text, font });
107 }
108
109 if 0. < lnum_w
110 && let Some(text) = line.lnum()
111 {
112 let (pos, text) = ((pos.x() - lnum_w, p.y()), &text);
113 r.draw(Text { pos, scale, color: t.highlight, text, font });
114 }
115 });
116
117 let (pos, sc) = (pos.sum(p(lines, start)), Cell::from_mut(scrollbar));
118 r.logic(
119 layout,
120 move |e, focused, mouse_pos| {
121 let parser = Cell::from_mut(&mut parser);
122 let (cs @ (c, s), rw, lines) = ((*caret, *select), !readonly, || (unsafe { &*parser.as_ptr() }).pipe(|((_, l), _)| l));
123
124 let click = |c: Vec2| ca::at_pos(lines(), font, scale, start, c.sub(pos));
125 let max_caret = || ca::set(lines(), vec2(isize::MAX), (0, 0));
126
127 let move_pip = |o: f32| sc.mutate(|s| s.pip_pos = (s.pip_pos + o * adv).clamp(0., 1.).or_val(scrollable, || 1.));
128
129 let center_pip = |sel @ (c, _): (Caret, _)| {
130 f32(beg_y + len_y / 2 - c.y()).pipe(move_pip);
131 sel
132 };
133
134 let clamp_pip = |sel @ (c, _): (Caret, _)| {
135 if c.y() >= end_y {
136 f32(beg_y + len_y - c.y()).pipe(move_pip)
137 } else if c.y() <= beg_y {
138 f32(beg_y + 1 - c.y()).pipe(move_pip)
139 }
140 sel
141 };
142
143 let move_caret = |c, o| ca::set(lines(), c, o);
144
145 let set_caret = |m: Mod, c| (c, s.or_val(m.shift(), || c));
146
147 let collect = |(c, s)| u::collect_range(lines().iter(), c, s).pipe(task::block_on);
148
149 let edit = |pre, post, ins: &str, copy_out: fn(&str)| {
150 let (c, s) = ca::sort(c, s);
151 let s = s.or_val(c != s, || move_caret(c, (pre, 0)));
152 let (c, s) = ca::sort(c, s);
153
154 if c == s && ins.is_empty() {
155 return (c, c);
156 }
157
158 if c != s {
159 let del = &collect(cs);
160 copy_out(del);
161 }
162
163 parser.mutate(|p| p.set(|((_, l), _)| u::replace_range(l, ins, c, s)));
164
165 let c = move_caret(c, (post, 0));
166
167 parser.mutate(|p| {
168 let (caret_was, font) = (c, font.clone());
169 p.update(async move |((_, lines), _)| {
170 let caret = ca::serialise(lines.iter(), caret_was).await;
171
172 let text = String::new()
173 .tap_async(async |t| stream::iter(lines.iter()).for_each(|l| l.write_self(t)).await)
174 .await;
175
176 let (lines, parsed) = lazy_parse(text.into(), font).await.pipe(|(h, l)| (l.clone(), (h, l))); let caret = ca::set_async(&lines, (0, 0), (caret, 0)).await;
179
180 let effect = move |mAffected { caret: c, select: s, history }: &mut _| {
181 if *c == caret_was && c == s {
182 (*c, *s) = (caret, caret);
183 }
184 history.add((caret, lines))
185 };
186
187 (parsed, effect.into())
188 })
189 });
190
191 (c, c)
192 };
193
194 let caret = |cs| (*caret, *select) = cs;
195
196 match *e {
197 OfferFocus => return Accept,
198 Defocus => move_pip(0.),
199 Scroll { at, m } => return move_pip(at.y() * if_ctrl(m, 10., 1.)).pipe(|_| Accept),
200 MouseButton { m, .. } if m.pressed() => set_caret(m, click(mouse_pos)).pipe(caret),
201 MouseMove { at, m } if focused && m.lmb() => set_caret(m, click(at)).pipe(clamp_pip).pipe(caret),
202 Keyboard { key, m } if focused && m.pressed() => {
203 let x = |o| set_caret(m, move_caret(c, (o, 0)));
204 let y = |o| set_caret(m, move_caret(c, (0, o)));
205
206 match key {
207 Key::Escape => return DropFocus,
208 Key::Right => x(if_ctrl(m, 10, 1)).pipe(clamp_pip).pipe(caret),
209 Key::Left => x(-if_ctrl(m, 10, 1)).pipe(clamp_pip).pipe(caret),
210 Key::Up => y(-1).pipe(clamp_pip).pipe(caret),
211 Key::Down => y(1).pipe(clamp_pip).pipe(caret),
212 Key::PageUp => y(-len_y).pipe(center_pip).pipe(caret),
213 Key::PageDown => y(len_y).pipe(center_pip).pipe(caret),
214 Key::A if m.ctrl() => (max_caret(), (0, 0)).pipe(center_pip).pipe(caret),
215 Key::C if m.ctrl() && c != s => collect(cs).pipe(RenderLock::set_clipboard),
216 Key::X if rw && m.ctrl() => edit(0, 0, "", |s| RenderLock::set_clipboard(s)).pipe(clamp_pip).pipe(caret),
217 Key::V if rw && m.ctrl() => edit(0, 0, &RenderLock::clipboard(), noop).pipe(clamp_pip).pipe(caret),
218 Key::Delete if rw => edit(1, 0, "", noop).pipe(clamp_pip).pipe(caret),
219 Key::Backspace if rw => edit(-1, 0, "", noop).pipe(clamp_pip).pipe(caret),
220 Key::Return if rw => edit(0, 1, "\n", noop).pipe(clamp_pip).pipe(caret),
221 Key::Z if rw && m.ctrl() => {
222 let h = 's: {
223 if !m.shift()
224 && let h @ Some(_) = history.undo()
225 {
226 break 's h;
227 }
228 if m.shift()
229 && let h @ Some(_) = history.redo()
230 {
231 break 's h;
232 }
233 None
234 };
235
236 if let Some((c, t)) = h {
237 parser.mutate(|p| p.set(move |((_, l), _)| *l = t));
238 (c, c).pipe(center_pip).pipe(caret)
239 }
240 }
241 _ => (),
242 }
243 }
244 Char { ch } if rw && focused => edit(0, 1, ch.as_str(), noop).pipe(clamp_pip).pipe(caret),
245 _ => (),
246 }
247 Accept.or_def(focused)
248 },
249 id,
250 );
251
252 if scrollable {
253 sc.mutate(|s| s.draw(r, t, layout.x_self(1).w(SCR_PAD), pip_size));
254 }
255 }
256}
257
258impl<'s: 'l, 'l> Lock::TextEdit<'s, 'l, '_> {
259 pub fn draw(self, g: impl Into<Surf>, sc: f32) {
260 let Self { s, r, t } = self;
261 s.draw(r, t, g.into(), sc, false)
262 }
263}
264
265#[derive(Default, Debug)]
266struct History {
267 states: Vec<TextState>,
268 at: usize,
269}
270impl History {
271 fn add(&mut self, (c, v): TextState) {
272 let HIST_SIZE = 100;
273
274 let Self { states, at } = self;
275
276 if *at + 1 < states.len() {
277 states.truncate(*at + 1);
278 }
279 states.push((c, v));
280
281 let len = states.len();
282 if len < HIST_SIZE {
283 *at += 1.or_def(len > 1);
284 } else {
285 states.remove(0);
286 }
287 }
288 fn undo(&mut self) -> Option<TextState> {
289 let Self { states, at } = self;
290
291 if *at < 1 {
292 None?
293 }
294 *at -= 1;
295
296 states.at(*at).clone().pipe(Some)
297 }
298 fn redo(&mut self) -> Option<TextState> {
299 let Self { states, at } = self;
300
301 if *at + 1 >= states.len() {
302 None?
303 }
304 *at += 1;
305
306 states.at(*at).clone().pipe(Some)
307 }
308}
309
310fn noop(_: &str) {}
311type Lines = VerVec<DynamicStr>;
312type ParseResult<T> = ((Vec2, Lines), Effect<T>);
313type TextState = (Caret, VerVec<DynamicStr>);
314use DynamicStr::*;