Skip to main content

sequoia_elements/
text_field.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3
4use sequoia_core::{Color, Edges, Length, Rect, Size};
5use sequoia_input::{
6    CursorIcon, Event, EventPhase, ImeEvent, Key, KeyEvent, Modifiers, PointerButton, PointerEvent,
7};
8use sequoia_layout::{LayoutCtx, LayoutStyle};
9use sequoia_reactive::Signal;
10use sequoia_style::{TextFieldStyle, Theme, VisualState};
11use sequoia_view::{AnyView, EventCtx, FontWeight, PaintCtx, Unit, View};
12
13use crate::buffer::BufferEngine;
14use crate::clipboard::SharedClipboard;
15use crate::underline;
16
17const HEIGHT: f32 = 26.0;
18
19const CARET_W: f32 = 1.5;
20
21#[derive(Clone, Default)]
22struct FieldState {
23    caret: usize,
24
25    anchor: Option<usize>,
26
27    bounds: Vec<f32>,
28
29    preedit: String,
30
31    was_focused: bool,
32
33    scroll: f32,
34
35    session_text: Option<String>,
36
37    engine: BufferEngine,
38}
39
40pub struct TextField {
41    value: Signal<String>,
42    placeholder: String,
43    max_length: Option<usize>,
44    font_size: Option<f32>,
45    weight: Option<FontWeight>,
46    text_color: Option<Color>,
47    line_color: Option<Color>,
48    line_width: Option<f32>,
49    appearance: Option<TextFieldStyle>,
50    placeholder_color: Option<Color>,
51    background: Option<Color>,
52    border_color: Option<Color>,
53    border_width: Option<f32>,
54    radius: Option<f32>,
55    padding: Option<(f32, f32)>,
56    style: LayoutStyle,
57    margin: Edges,
58
59    revision: Signal<u32>,
60
61    state: Rc<RefCell<FieldState>>,
62
63    on_edit_start: Option<Rc<dyn Fn()>>,
64
65    on_edit_end: Option<Rc<dyn Fn(String)>>,
66
67    on_submit: Option<Rc<dyn Fn(String)>>,
68}
69
70impl TextField {
71    pub fn new(value: Signal<String>) -> Self {
72        Self {
73            value,
74            placeholder: String::new(),
75            max_length: None,
76            font_size: None,
77            weight: None,
78            text_color: None,
79            line_color: None,
80            line_width: None,
81            appearance: None,
82            placeholder_color: None,
83            background: None,
84            border_color: None,
85            border_width: None,
86            radius: None,
87            padding: None,
88            style: LayoutStyle {
89                width: Length::Fill(1.0),
90                height: Length::Px(HEIGHT),
91                ..LayoutStyle::default()
92            },
93            margin: Edges::ZERO,
94            revision: Signal::new(0),
95            state: Rc::new(RefCell::new(FieldState::default())),
96            on_edit_start: None,
97            on_edit_end: None,
98            on_submit: None,
99        }
100    }
101
102    pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
103        self.placeholder = placeholder.into();
104        self
105    }
106
107    pub fn max_length(mut self, max: usize) -> Self {
108        self.max_length = Some(max);
109        self
110    }
111
112    pub fn on_edit_start(mut self, f: impl Fn() + 'static) -> Self {
113        self.on_edit_start = Some(Rc::new(f));
114        self
115    }
116
117    pub fn on_edit_end(mut self, f: impl Fn(String) + 'static) -> Self {
118        self.on_edit_end = Some(Rc::new(f));
119        self
120    }
121
122    pub fn on_submit(mut self, f: impl Fn(String) + 'static) -> Self {
123        self.on_submit = Some(Rc::new(f));
124        self
125    }
126
127    pub fn font_size(mut self, size: f32) -> Self {
128        self.font_size = Some(size);
129        self
130    }
131
132    pub fn weight(mut self, weight: FontWeight) -> Self {
133        self.weight = Some(weight);
134        self
135    }
136
137    pub fn bold(mut self, bold: bool) -> Self {
138        self.weight = Some(if bold {
139            FontWeight::BOLD
140        } else {
141            FontWeight::NORMAL
142        });
143        self
144    }
145
146    pub fn text_color(mut self, color: Color) -> Self {
147        self.text_color = Some(color);
148        self
149    }
150
151    pub fn line_color(mut self, color: Color) -> Self {
152        self.line_color = Some(color);
153        self
154    }
155
156    pub fn appearance(mut self, style: TextFieldStyle) -> Self {
157        self.appearance = Some(style);
158        self
159    }
160
161    pub fn placeholder_color(mut self, color: Color) -> Self {
162        self.placeholder_color = Some(color);
163        self
164    }
165
166    pub fn line_width(mut self, width: f32) -> Self {
167        self.line_width = Some(width.max(0.0));
168        self
169    }
170
171    pub fn background(mut self, color: Color) -> Self {
172        self.background = Some(color);
173        self
174    }
175
176    pub fn border_color(mut self, color: Color) -> Self {
177        self.border_color = Some(color);
178        self
179    }
180
181    pub fn border_width(mut self, width: f32) -> Self {
182        self.border_width = Some(width.max(0.0));
183        self
184    }
185
186    pub fn border(mut self, width: f32, color: Color) -> Self {
187        self.border_width = Some(width.max(0.0));
188        self.border_color = Some(color);
189        self
190    }
191
192    pub fn radius(mut self, radius: f32) -> Self {
193        self.radius = Some(radius.max(0.0));
194        self
195    }
196
197    pub fn padding(mut self, padding: impl Into<Edges>) -> Self {
198        let p = padding.into();
199        self.padding = Some(((p.left + p.right) * 0.5, (p.top + p.bottom) * 0.5));
200        self
201    }
202
203    pub fn padding_xy(mut self, x: f32, y: f32) -> Self {
204        self.padding = Some((x.max(0.0), y.max(0.0)));
205        self
206    }
207
208    pub fn width(mut self, width: impl Into<Length>) -> Self {
209        self.style = self.style.width(width.into());
210        self
211    }
212
213    pub fn height(mut self, height: impl Into<Length>) -> Self {
214        self.style = self.style.height(height.into());
215        self
216    }
217
218    pub fn min_width(mut self, v: f32) -> Self {
219        self.style = self.style.min_width(v);
220        self
221    }
222
223    pub fn max_width(mut self, v: f32) -> Self {
224        self.style = self.style.max_width(v);
225        self
226    }
227
228    pub fn min_height(mut self, v: f32) -> Self {
229        self.style = self.style.min_height(v);
230        self
231    }
232
233    pub fn max_height(mut self, v: f32) -> Self {
234        self.style = self.style.max_height(v);
235        self
236    }
237
238    pub fn flex(mut self, weight: f32) -> Self {
239        self.style = self.style.flex(weight);
240        self
241    }
242
243    pub fn expand(self) -> Self {
244        self.flex(1.0)
245    }
246
247    pub fn margin(mut self, margin: impl Into<Edges>) -> Self {
248        let m = margin.into();
249        self.margin = Edges::only(
250            sanitize_margin(m.left),
251            sanitize_margin(m.top),
252            sanitize_margin(m.right),
253            sanitize_margin(m.bottom),
254        );
255        self
256    }
257
258    pub fn margin_xy(self, x: f32, y: f32) -> Self {
259        self.margin(Edges::xy(x, y))
260    }
261
262    pub fn margin_x(mut self, x: f32) -> Self {
263        let x = sanitize_margin(x);
264        self.margin.left = x;
265        self.margin.right = x;
266        self
267    }
268
269    pub fn margin_y(mut self, y: f32) -> Self {
270        let y = sanitize_margin(y);
271        self.margin.top = y;
272        self.margin.bottom = y;
273        self
274    }
275
276    pub fn margin_left(mut self, v: f32) -> Self {
277        self.margin.left = sanitize_margin(v);
278        self
279    }
280
281    pub fn margin_right(mut self, v: f32) -> Self {
282        self.margin.right = sanitize_margin(v);
283        self
284    }
285
286    pub fn margin_top(mut self, v: f32) -> Self {
287        self.margin.top = sanitize_margin(v);
288        self
289    }
290
291    pub fn margin_bottom(mut self, v: f32) -> Self {
292        self.margin.bottom = sanitize_margin(v);
293        self
294    }
295
296    fn with_state<R>(&self, f: impl FnOnce(&mut FieldState) -> R) -> R {
297        f(&mut self.state.borrow_mut())
298    }
299
300    fn chars(&self) -> Vec<char> {
301        self.value.get_untracked().chars().collect()
302    }
303
304    pub fn caret(&self) -> usize {
305        self.with_state(|s| s.caret)
306    }
307
308    pub fn selection(&self) -> Option<(usize, usize)> {
309        self.with_state(|s| {
310            s.anchor.map(|a| {
311                let c = s.caret;
312                (a.min(c), a.max(c))
313            })
314        })
315    }
316
317    fn begin_session(&self) {
318        let cur = self.value.get_untracked();
319        self.with_state(|s| {
320            s.session_text = Some(cur);
321            s.was_focused = true;
322        });
323        if let Some(f) = &self.on_edit_start {
324            f();
325        }
326    }
327
328    fn note_focus(&self, focused: bool) {
329        enum Trans {
330            Start,
331            End(String),
332            None,
333        }
334        let cur = self.value.get_untracked();
335        let trans = self.with_state(|s| {
336            let was = s.was_focused;
337            s.was_focused = focused;
338            if !was && focused {
339                s.session_text = Some(cur.clone());
340                Trans::Start
341            } else if was && !focused {
342                match s.session_text.take() {
343                    Some(before) if before != cur => Trans::End(cur.clone()),
344                    _ => Trans::None,
345                }
346            } else {
347                Trans::None
348            }
349        });
350        match trans {
351            Trans::Start => {
352                if let Some(f) = &self.on_edit_start {
353                    f();
354                }
355            }
356            Trans::End(text) => {
357                if let Some(f) = &self.on_edit_end {
358                    f(text);
359                }
360            }
361            Trans::None => {}
362        }
363    }
364
365    fn stored_bounds(&self) -> Vec<f32> {
366        self.with_state(|s| s.bounds.clone())
367    }
368
369    fn store_bounds(&self, bounds: Vec<f32>) {
370        self.with_state(|s| s.bounds = bounds);
371    }
372
373    fn set_caret(&self, caret: usize) {
374        let value = self.value.clone();
375        self.with_state(|s| {
376            s.engine.ensure(&value);
377            s.caret = caret.min(s.engine.len_chars());
378            s.anchor = None;
379        });
380    }
381
382    fn set_caret_extend(&self, caret: usize, extend: bool) {
383        let value = self.value.clone();
384        self.with_state(|s| {
385            s.engine.ensure(&value);
386            let caret = caret.min(s.engine.len_chars());
387            if extend {
388                let anchor = s.anchor.unwrap_or(s.caret);
389                s.anchor = Some(anchor);
390                s.caret = caret;
391                if s.anchor == Some(s.caret) {
392                    s.anchor = None;
393                }
394            } else {
395                s.caret = caret;
396                s.anchor = None;
397            }
398        });
399    }
400
401    fn bump(&self) {
402        self.revision
403            .set(self.revision.get_untracked().wrapping_add(1));
404    }
405
406    fn font_weight(&self) -> FontWeight {
407        self.weight
408            .or_else(|| {
409                self.appearance
410                    .and_then(|s| s.font_weight)
411                    .map(FontWeight::new)
412            })
413            .unwrap_or_default()
414    }
415
416    fn resolved_font_size(&self, theme: &Theme) -> f32 {
417        self.font_size
418            .or_else(|| self.appearance.and_then(|s| s.font_size))
419            .unwrap_or(theme.font_size)
420    }
421
422    fn resolved_line_width(&self) -> f32 {
423        if let Some(w) = self
424            .line_width
425            .or_else(|| self.appearance.and_then(|s| s.line_width))
426        {
427            return w;
428        }
429        let has_line_color =
430            self.line_color.is_some() || self.appearance.and_then(|s| s.line).is_some();
431        if has_line_color {
432            return underline::LINE_W;
433        }
434        0.0
435    }
436
437    fn resolved_text_color(&self) -> Option<Color> {
438        self.text_color
439            .or_else(|| self.appearance.and_then(|s| s.text))
440    }
441
442    fn resolved_line_color(&self) -> Option<Color> {
443        self.line_color
444            .or_else(|| self.appearance.and_then(|s| s.line))
445    }
446
447    fn resolved_placeholder_color(&self, theme: &Theme) -> Color {
448        self.placeholder_color
449            .or_else(|| self.appearance.and_then(|s| s.placeholder))
450            .unwrap_or(theme.text_secondary)
451    }
452
453    fn resolved_pad(&self) -> (f32, f32) {
454        self.padding
455            .or_else(|| self.appearance.and_then(|s| s.padding))
456            .unwrap_or((underline::PAD_X, 0.0))
457    }
458
459    fn resolved_background(&self) -> Color {
460        self.background
461            .or_else(|| self.appearance.and_then(|s| s.background))
462            .unwrap_or(Color::TRANSPARENT)
463    }
464
465    fn resolved_border(&self, theme: &Theme) -> Option<(f32, Color)> {
466        let width_opt = self
467            .border_width
468            .or_else(|| self.appearance.and_then(|s| s.border_width));
469        let color_opt = self
470            .border_color
471            .or_else(|| self.appearance.and_then(|s| s.border));
472        match (width_opt, color_opt) {
473            (None, None) => None,
474            (Some(w), Some(c)) => {
475                let w = w.max(0.0);
476                if w <= 0.0 || c.a <= 0.0 {
477                    None
478                } else {
479                    Some((w, c))
480                }
481            }
482            (Some(w), None) => {
483                let w = w.max(0.0);
484                if w <= 0.0 || theme.border.a <= 0.0 {
485                    None
486                } else {
487                    Some((w, theme.border))
488                }
489            }
490            (None, Some(c)) => {
491                if c.a <= 0.0 {
492                    None
493                } else {
494                    Some((1.0, c))
495                }
496            }
497        }
498    }
499
500    fn resolved_radius(&self, theme: &Theme) -> f32 {
501        self.radius
502            .or_else(|| self.appearance.and_then(|s| s.radius))
503            .unwrap_or(theme.radius)
504            .max(0.0)
505    }
506
507    fn approx_border_w(&self) -> f32 {
508        if let Some(w) = self
509            .border_width
510            .or_else(|| self.appearance.and_then(|s| s.border_width))
511        {
512            return w.max(0.0);
513        }
514        let has_color =
515            self.border_color.is_some() || self.appearance.and_then(|s| s.border).is_some();
516        if has_color { 1.0 } else { 0.0 }
517    }
518
519    fn caret_at(&self, x: f32, len: usize) -> usize {
520        let bounds = self.stored_bounds();
521        let Some(last) = bounds.last() else {
522            return len;
523        };
524        if x >= *last {
525            return len;
526        }
527        if x <= 0.0 {
528            return 0;
529        }
530        let mut best = 0;
531        let mut best_distance = f32::MAX;
532        for (i, offset) in bounds.iter().enumerate() {
533            let distance = (offset - x).abs();
534            if distance < best_distance {
535                best_distance = distance;
536                best = i;
537            }
538        }
539        best.min(len)
540    }
541
542    fn delete_selection(&self) -> bool {
543        let sel = self.selection();
544        let Some((a, b)) = sel else { return false };
545        if a == b {
546            self.with_state(|s| s.anchor = None);
547            return false;
548        }
549        let value = self.value.clone();
550        self.with_state(|s| {
551            s.engine.ensure(&value);
552            let len = s.engine.len_chars();
553            let (a, b) = (a.min(len), b.min(len));
554            let (ab, bb) = (s.engine.to_bytes(a), s.engine.to_bytes(b));
555            s.engine.delete(ab..bb);
556            s.engine.commit(&value);
557            s.caret = s.engine.to_chars(ab);
558            s.anchor = None;
559        });
560        self.bump();
561        true
562    }
563
564    pub fn selected_text(&self) -> Option<String> {
565        let (a, b) = self.selection()?;
566        if a == b {
567            return None;
568        }
569        let value = self.value.clone();
570        self.with_state(|s| {
571            s.engine.ensure(&value);
572            let len = s.engine.len_chars();
573            let (a, b) = (a.min(len), b.min(len));
574            let text = s
575                .engine
576                .buffer()
577                .substring(s.engine.to_bytes(a)..s.engine.to_bytes(b));
578            if text.is_empty() { None } else { Some(text) }
579        })
580    }
581
582    pub fn select_range(&self, a: usize, b: usize) {
583        let value = self.value.clone();
584        self.with_state(|s| {
585            s.engine.ensure(&value);
586            let len = s.engine.len_chars();
587            let (a, b) = (a.min(len), b.min(len));
588            s.caret = b;
589            s.anchor = if a == b { None } else { Some(a) };
590        });
591        self.bump();
592    }
593
594    pub fn clear_selection(&self) -> bool {
595        let had = self.with_state(|s| s.anchor.take().is_some());
596        if had {
597            self.bump();
598        }
599        had
600    }
601
602    fn insert(&self, c: char) {
603        self.insert_str(&c.to_string());
604    }
605
606    fn insert_str(&self, text: &str) {
607        if text.is_empty() {
608            return;
609        }
610
611        let value = self.value.clone();
612        self.with_state(|st| {
613            st.engine.ensure(&value);
614            let total = st.engine.len_chars();
615
616            let sel_bytes = match st.anchor.map(|a| (a.min(st.caret), a.max(st.caret))) {
617                None => None,
618                Some((a, b)) if a == b => {
619                    st.anchor = None;
620                    None
621                }
622                Some((a, b)) => {
623                    let (a, b) = (a.min(total), b.min(total));
624                    Some((st.engine.to_bytes(a), st.engine.to_bytes(b)))
625                }
626            };
627            let allowed = match self.max_length {
628                Some(max) => max.saturating_sub(total),
629                None => usize::MAX,
630            };
631            let ins = BufferEngine::fit_prefix(text, allowed);
632            let caret_b = match sel_bytes {
633                Some((ab, bb)) => {
634                    st.engine.replace(ab..bb, ins);
635                    ab + ins.len()
636                }
637                None => {
638                    let caret_b = st.engine.to_bytes(st.caret.min(total));
639                    st.engine.insert(caret_b, ins);
640                    caret_b + ins.len()
641                }
642            };
643            st.engine.commit(&value);
644            st.caret = st.engine.to_chars(caret_b).min(st.engine.len_chars());
645            st.anchor = None;
646        });
647        self.bump();
648    }
649
650    fn backspace(&self) {
651        if self.delete_selection() {
652            return;
653        }
654        let value = self.value.clone();
655        self.with_state(|s| {
656            s.engine.ensure(&value);
657            let caret = s.caret.min(s.engine.len_chars());
658            if caret == 0 {
659                return;
660            }
661            let caret_b = s.engine.to_bytes(caret);
662            let from_b = sequoia_buffer::prev_grapheme(s.engine.buffer(), caret_b);
663            s.engine.delete(from_b..caret_b);
664            s.engine.commit(&value);
665            s.caret = s.engine.to_chars(from_b);
666            s.anchor = None;
667        });
668        self.bump();
669    }
670
671    fn delete(&self) {
672        if self.delete_selection() {
673            return;
674        }
675        let value = self.value.clone();
676        self.with_state(|s| {
677            s.engine.ensure(&value);
678            let len = s.engine.len_chars();
679            let caret = s.caret.min(len);
680            if caret >= len {
681                return;
682            }
683            let caret_b = s.engine.to_bytes(caret);
684            let to_b = sequoia_buffer::next_grapheme(s.engine.buffer(), caret_b);
685            s.engine.delete(caret_b..to_b);
686            s.engine.commit(&value);
687            s.caret = s.engine.to_chars(caret_b);
688            s.anchor = None;
689        });
690        self.bump();
691    }
692
693    fn move_caret(&self, delta: isize, extend: bool) {
694        let value = self.value.clone();
695        let next = self.with_state(|s| {
696            s.engine.ensure(&value);
697            let mut caret_b = s.engine.to_bytes(s.caret.min(s.engine.len_chars()));
698            let steps = delta.unsigned_abs().max(1);
699            for _ in 0..steps {
700                let b = if delta < 0 {
701                    sequoia_buffer::prev_grapheme(s.engine.buffer(), caret_b)
702                } else {
703                    sequoia_buffer::next_grapheme(s.engine.buffer(), caret_b)
704                };
705                if b == caret_b {
706                    break;
707                }
708                caret_b = b;
709            }
710            s.engine.to_chars(caret_b)
711        });
712        self.set_caret_extend(next, extend);
713        self.bump();
714    }
715
716    fn move_home(&self, extend: bool) {
717        self.set_caret_extend(0, extend);
718        self.bump();
719    }
720
721    fn move_end(&self, extend: bool) {
722        let value = self.value.clone();
723        let end = self.with_state(|s| {
724            s.engine.ensure(&value);
725            s.engine.len_chars()
726        });
727        self.set_caret_extend(end, extend);
728        self.bump();
729    }
730
731    pub fn select_all(&self) {
732        let value = self.value.clone();
733        self.with_state(|s| {
734            s.engine.ensure(&value);
735            let len = s.engine.len_chars();
736            s.anchor = Some(0);
737            s.caret = len;
738        });
739        self.bump();
740    }
741
742    pub fn submit(&self) {
743        let Some(cb) = self.on_submit.clone() else {
744            return;
745        };
746        let has_preedit = self.with_state(|s| !s.preedit.is_empty());
747        if has_preedit {
748            return;
749        }
750        let cur = self.value.get_untracked();
751        self.with_state(|s| {
752            s.session_text = Some(cur.clone());
753        });
754        cb(cur);
755    }
756
757    fn undo(&self) {
758        let value = self.value.clone();
759        let caret = self.with_state(|s| {
760            s.engine.ensure(&value);
761            match s.engine.undo() {
762                Some((a, _)) => {
763                    s.engine.commit(&value);
764                    Some(s.engine.to_chars(a))
765                }
766                None => None,
767            }
768        });
769        if let Some(caret) = caret {
770            self.with_state(|s| {
771                s.caret = caret;
772                s.anchor = None;
773            });
774            self.bump();
775        }
776    }
777
778    fn redo(&self) {
779        let value = self.value.clone();
780        let caret = self.with_state(|s| {
781            s.engine.ensure(&value);
782            match s.engine.redo() {
783                Some((_, b)) => {
784                    s.engine.commit(&value);
785                    Some(s.engine.to_chars(b))
786                }
787                None => None,
788            }
789        });
790        if let Some(caret) = caret {
791            self.with_state(|s| {
792                s.caret = caret;
793                s.anchor = None;
794            });
795            self.bump();
796        }
797    }
798
799    fn on_key(&self, key: &KeyEvent) {
800        let extend = key.modifiers.contains(Modifiers::SHIFT);
801        let len = self.with_state(|s| {
802            s.engine.ensure(&self.value);
803            s.engine.len_chars()
804        });
805        match key.key {
806            Key::ENTER => self.submit(),
807            Key::BACKSPACE => self.backspace(),
808            Key::DELETE => self.delete(),
809            Key::ARROW_LEFT => self.move_caret(-1, extend),
810            Key::ARROW_RIGHT => self.move_caret(1, extend),
811            Key::HOME | Key::PAGE_UP => {
812                if extend {
813                    self.move_home(true);
814                } else {
815                    self.set_caret(0);
816                    self.bump();
817                }
818            }
819            Key::END | Key::PAGE_DOWN => {
820                if extend {
821                    self.move_end(true);
822                } else {
823                    self.set_caret(len);
824                    self.bump();
825                }
826            }
827            Key::SPACE => {
828                if extend {
829                    self.move_caret(1, true);
830                } else {
831                    self.insert(' ');
832                }
833            }
834
835            Key::ESCAPE | Key::TAB => {
836                let had = self.with_state(|s| {
837                    let had = s.anchor.is_some() || !s.preedit.is_empty();
838                    s.anchor = None;
839                    s.preedit.clear();
840                    had
841                });
842                if had {
843                    self.bump();
844                }
845            }
846            other => {
847                if let Some(c) = other.char()
848                    && !c.is_control()
849                {
850                    let cmd = key.modifiers.contains(Modifiers::CONTROL)
851                        || key.modifiers.contains(Modifiers::SUPER);
852                    if cmd {
853                        return;
854                    }
855                    self.insert(c);
856                }
857            }
858        }
859    }
860
861    fn on_ime(&self, ime: &ImeEvent) {
862        match ime {
863            ImeEvent::Enabled => {}
864            ImeEvent::Preedit { value, .. } => {
865                self.with_state(|s| s.preedit = value.clone());
866                self.bump();
867            }
868            ImeEvent::Commit(value) => {
869                self.with_state(|s| s.preedit.clear());
870                self.insert_str(value);
871            }
872            ImeEvent::Disabled => {
873                let had = self.with_state(|s| {
874                    let had = !s.preedit.is_empty();
875                    s.preedit.clear();
876                    had
877                });
878                if had {
879                    self.bump();
880                }
881            }
882
883            _ => {}
884        }
885    }
886
887    fn on_clipboard_key(&self, key: &KeyEvent, ctx: &EventCtx) -> bool {
888        let cmd =
889            key.modifiers.contains(Modifiers::CONTROL) || key.modifiers.contains(Modifiers::SUPER);
890        if !cmd {
891            return false;
892        }
893        let Some(c) = key.key.char().map(|c| c.to_ascii_lowercase()) else {
894            return false;
895        };
896
897        if c == 'z' && !key.modifiers.contains(Modifiers::SHIFT) {
898            self.undo();
899            return true;
900        }
901        if c == 'y' || (c == 'z' && key.modifiers.contains(Modifiers::SHIFT)) {
902            self.redo();
903            return true;
904        }
905        if !matches!(c, 'c' | 'x' | 'v' | 'a') {
906            return false;
907        }
908        if c == 'a' {
909            self.select_all();
910            return true;
911        }
912        let Some(clip) = ctx.environment.get::<SharedClipboard>() else {
913            return true;
914        };
915        match c {
916            'c' => {
917                if let Some(sel) = self.selected_text() {
918                    clip.0.set_text(&sel);
919                } else {
920                    clip.0.set_text(&self.value.get_untracked());
921                }
922            }
923            'x' => {
924                if let Some(sel) = self.selected_text() {
925                    clip.0.set_text(&sel);
926                    self.delete_selection();
927                } else {
928                    clip.0.set_text(&self.value.get_untracked());
929                    self.value.set(String::new());
930                    self.set_caret(0);
931                    self.bump();
932                }
933            }
934            _ => {
935                if let Some(paste) = clip.0.get_text() {
936                    let clean: String = paste
937                        .chars()
938                        .filter_map(|ch| {
939                            if ch == '\n' || ch == '\r' {
940                                Some(' ')
941                            } else if ch.is_control() {
942                                None
943                            } else {
944                                Some(ch)
945                            }
946                        })
947                        .collect();
948                    self.insert_str(&clean);
949                }
950            }
951        }
952        true
953    }
954}
955
956impl View for TextField {
957    fn body(&self) -> impl View + 'static {
958        Unit
959    }
960
961    fn is_leaf(&self) -> bool {
962        true
963    }
964
965    fn accessibility(&self) -> Option<sequoia_view::Accessible> {
966        Some(sequoia_view::Accessible::new(
967            sequoia_view::AccessibleRole::TextField,
968            if self.placeholder.is_empty() {
969                "pole tekstowe".to_string()
970            } else {
971                self.placeholder.clone()
972            },
973        ))
974    }
975
976    fn focusable(&self) -> bool {
977        true
978    }
979
980    fn accepts_text_input(&self) -> bool {
981        true
982    }
983
984    fn has_visual_state(&self) -> bool {
985        true
986    }
987
988    fn reuse_state(&self, old: &Self) {
989        if !Rc::ptr_eq(&self.state, &old.state) {
990            *self.state.borrow_mut() = old.state.borrow().clone();
991        }
992        self.revision.set(old.revision.get_untracked());
993    }
994
995    fn keyed_children(&self) -> Option<Vec<(u64, AnyView)>> {
996        let _ = self.value.get();
997        let _ = self.revision.get();
998
999        Some(Vec::new())
1000    }
1001
1002    fn cursor_icon(&self, _state: VisualState) -> CursorIcon {
1003        CursorIcon::Text
1004    }
1005
1006    fn layout_style(&self) -> LayoutStyle {
1007        let mut style = self.style;
1008        style.margin = self.margin;
1009        style
1010    }
1011
1012    fn intrinsic_size(&self, ctx: &mut LayoutCtx) -> Option<Size> {
1013        let appearance = self.appearance.unwrap_or_default();
1014        let size = self.font_size.or(appearance.font_size).unwrap_or(14.0);
1015        let weight = self.font_weight();
1016        let line_width = self.resolved_line_width();
1017        let (pad_x, pad_y) = self.resolved_pad();
1018        let bw = self.approx_border_w();
1019        let w = ctx
1020            .text_width_weighted(&self.value.get_untracked(), size, weight)
1021            .max(ctx.text_width_weighted(&self.placeholder, size, weight));
1022        let h = if line_width > 0.0 {
1023            underline::content_height(size, line_width)
1024        } else {
1025            LayoutCtx::text_line_height(size)
1026        };
1027        Some(Size::new(
1028            w + pad_x * 2.0 + bw * 2.0,
1029            h + pad_y * 2.0 + bw * 2.0,
1030        ))
1031    }
1032
1033    fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
1034        if ctx.phase != EventPhase::Target {
1035            return;
1036        }
1037        match event {
1038            Event::Pointer(PointerEvent::Down {
1039                button: PointerButton::Left,
1040                pos,
1041                modifiers,
1042                ..
1043            }) => {
1044                self.begin_session();
1045                let len = self.chars().len();
1046                let (pad_x, _) = self.resolved_pad();
1047                let ix = pad_x + self.approx_border_w();
1048                let scroll = self.with_state(|s| s.scroll);
1049                let x = pos.x - (ctx.rect.left() + ix) + scroll;
1050                let caret = self.caret_at(x, len);
1051                let extend = modifiers.contains(Modifiers::SHIFT);
1052                self.set_caret_extend(caret, extend);
1053                self.bump();
1054                ctx.request_focus();
1055
1056                ctx.capture_pointer();
1057                ctx.handled();
1058            }
1059            Event::Pointer(PointerEvent::Move { pos, .. }) => {
1060                if ctx.state.pressed {
1061                    let len = self.chars().len();
1062                    let (pad_x, _) = self.resolved_pad();
1063                    let ix = pad_x + self.approx_border_w();
1064                    let scroll = self.with_state(|s| s.scroll);
1065                    let x = pos.x - (ctx.rect.left() + ix) + scroll;
1066                    let caret = self.caret_at(x, len);
1067                    self.set_caret_extend(caret, true);
1068                    self.bump();
1069                    ctx.handled();
1070                }
1071            }
1072            Event::Key(key) => {
1073                if !self.on_clipboard_key(key, ctx) {
1074                    self.on_key(key);
1075                }
1076                ctx.handled();
1077            }
1078            Event::Ime(ime) => {
1079                self.on_ime(ime);
1080                ctx.handled();
1081            }
1082            _ => {}
1083        }
1084    }
1085
1086    fn paint_self(&self, ctx: &mut PaintCtx) {
1087        self.note_focus(ctx.state.focused);
1088        let rect = ctx.rect;
1089        if rect.size.width <= 0.0 || rect.size.height <= 0.0 {
1090            return;
1091        }
1092        let theme = ctx.theme();
1093        let size = self.resolved_font_size(&theme);
1094        let weight = self.font_weight();
1095        let line_width = self.resolved_line_width();
1096        let placeholder_color = self.resolved_placeholder_color(&theme);
1097        let (pad_x, pad_y) = self.resolved_pad();
1098        let background = self.resolved_background();
1099        let radius = self.resolved_radius(&theme);
1100        let border = self.resolved_border(&theme);
1101        let colors = underline::field_colors(
1102            &theme,
1103            ctx.state.hovered || ctx.state.focused,
1104            ctx.state.focused,
1105            self.resolved_text_color(),
1106            self.resolved_line_color(),
1107        );
1108
1109        if background.a > 0.0 || border.is_some() {
1110            match border {
1111                Some((w, color)) => {
1112                    ctx.bordered_quad(rect, background, radius, w, color);
1113                }
1114                None => {
1115                    ctx.quad(rect, background, radius);
1116                }
1117            }
1118        }
1119
1120        let bw = border.map(|(w, _)| w).unwrap_or(0.0);
1121        let content_h = if line_width > 0.0 {
1122            underline::content_height(size, line_width)
1123        } else {
1124            LayoutCtx::text_line_height(size)
1125        };
1126        let inner_h = (rect.size.height - bw * 2.0 - pad_y * 2.0).max(0.0);
1127        let yoff = if inner_h > content_h {
1128            (inner_h - content_h) * 0.5
1129        } else {
1130            0.0
1131        };
1132        let (ipx, ipy) = (pad_x + bw, pad_y + bw + yoff);
1133
1134        let line_w = (rect.size.width - ipx * 2.0).max(0.0);
1135        if line_width > 0.0 && colors.line.a > 0.0 {
1136            let line = underline::line_rect_padded(rect, size, line_w, line_width, ipx, ipy);
1137            ctx.quad(line, colors.line, line_width * 0.5);
1138        }
1139
1140        let text = self.value.get_untracked();
1141        let chars: Vec<char> = text.chars().collect();
1142        let (caret, anchor, preedit) = self.with_state(|s| (s.caret, s.anchor, s.preedit.clone()));
1143        let caret = caret.min(chars.len());
1144        let vis_w = (rect.size.width - ipx * 2.0).max(0.0);
1145
1146        let mut caret_x = 0.0;
1147        let mut bounds: Vec<f32>;
1148        if ctx.state.hovered || ctx.state.focused || anchor.is_some() {
1149            bounds = ctx.caret_positions(&text, size, weight);
1150
1151            if !preedit.is_empty() {
1152                let pw = ctx.text_width_weighted(&preedit, size, weight);
1153
1154                for b in bounds.iter_mut().skip(caret) {
1155                    *b += pw;
1156                }
1157            }
1158            caret_x = bounds.get(caret).copied().unwrap_or(0.0);
1159
1160            if !preedit.is_empty() {
1161                caret_x += ctx.text_width_weighted(&preedit, size, weight);
1162            }
1163
1164            let text_w = bounds.last().copied().unwrap_or(0.0);
1165            self.with_state(|s| {
1166                if caret_x + CARET_W - s.scroll > vis_w {
1167                    s.scroll = caret_x + CARET_W - vis_w;
1168                }
1169                if caret_x - s.scroll < 0.0 {
1170                    s.scroll = caret_x;
1171                }
1172                s.scroll = s.scroll.clamp(0.0, (text_w + CARET_W - vis_w).max(0.0));
1173            });
1174            self.store_bounds(bounds);
1175        }
1176        let scroll = self.with_state(|s| s.scroll);
1177        let x = rect.left() + ipx - scroll;
1178        let y = underline::baseline_padded(rect, size, ipy);
1179
1180        let clip_box = Rect::from_xywh(
1181            rect.left() + ipx,
1182            rect.top() + bw,
1183            vis_w,
1184            (rect.size.height - bw * 2.0).max(0.0),
1185        );
1186        ctx.push_clip(clip_box);
1187
1188        if let Some(a) = anchor {
1189            let (s0, s1) = (a.min(caret).min(chars.len()), a.max(caret).min(chars.len()));
1190            if s1 > s0 {
1191                let stored = self.stored_bounds();
1192
1193                let x0 = stored.get(s0).copied().unwrap_or(0.0);
1194                let x1 = stored.get(s1).copied().unwrap_or(x0);
1195                if x1 > x0 {
1196                    let top = y - size * 0.8;
1197                    let sel = Rect::from_xywh(x + x0, top, x1 - x0, size * 1.05);
1198                    ctx.quad(sel, theme.accent_pressed, 0.0);
1199                }
1200            }
1201        }
1202
1203        if chars.is_empty() && preedit.is_empty() && !self.placeholder.is_empty() {
1204            ctx.draw_text_weighted(&self.placeholder, (x, y), size, weight, placeholder_color);
1205        } else if preedit.is_empty() {
1206            ctx.draw_text_weighted(&text, (x, y), size, weight, colors.text);
1207        } else {
1208            let before: String = chars[..caret.min(chars.len())].iter().collect();
1209            let after: String = chars[caret.min(chars.len())..].iter().collect();
1210            let composed = format!("{before}{preedit}{after}");
1211            ctx.draw_text_weighted(&composed, (x, y), size, weight, colors.text);
1212
1213            let stored = self.stored_bounds();
1214            let x0 = stored.get(caret).copied().unwrap_or(0.0);
1215            let pw = ctx.text_width_weighted(&preedit, size, weight);
1216            let line_y =
1217                underline::line_rect_padded(rect, size, line_w, line_width, ipx, ipy).top();
1218            ctx.quad(
1219                Rect::from_xywh(x + x0, line_y - 2.0, pw.max(1.0), 1.5),
1220                colors.text,
1221                0.0,
1222            );
1223        }
1224
1225        if ctx.state.focused {
1226            let top = y - size * 0.8;
1227            ctx.quad(
1228                Rect::from_xywh(x + caret_x, top, CARET_W, size * 1.05),
1229                colors.text,
1230                0.0,
1231            );
1232        }
1233
1234        ctx.pop_clip();
1235    }
1236}
1237
1238fn sanitize_margin(v: f32) -> f32 {
1239    if v.is_finite() { v.max(0.0) } else { 0.0 }
1240}