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