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 has_visual_state(&self) -> bool {
981 true
982 }
983
984 fn reuse_state(&self, old: &Self) {
985 if !Rc::ptr_eq(&self.state, &old.state) {
986 *self.state.borrow_mut() = old.state.borrow().clone();
987 }
988 self.revision.set(old.revision.get_untracked());
989 }
990
991 fn keyed_children(&self) -> Option<Vec<(u64, AnyView)>> {
992 let _ = self.value.get();
993 let _ = self.revision.get();
994
995 Some(Vec::new())
996 }
997
998 fn cursor_icon(&self, _state: VisualState) -> CursorIcon {
999 CursorIcon::Text
1000 }
1001
1002 fn layout_style(&self) -> LayoutStyle {
1003 let mut style = self.style;
1004 style.margin = self.margin;
1005 style
1006 }
1007
1008 fn intrinsic_size(&self, ctx: &mut LayoutCtx) -> Option<Size> {
1009 let appearance = self.appearance.unwrap_or_default();
1010 let size = self.font_size.or(appearance.font_size).unwrap_or(14.0);
1011 let weight = self.font_weight();
1012 let line_width = self.resolved_line_width();
1013 let (pad_x, pad_y) = self.resolved_pad();
1014 let bw = self.approx_border_w();
1015 let w = ctx
1016 .text_width_weighted(&self.value.get_untracked(), size, weight)
1017 .max(ctx.text_width_weighted(&self.placeholder, size, weight));
1018 let h = if line_width > 0.0 {
1019 underline::content_height(size, line_width)
1020 } else {
1021 LayoutCtx::text_line_height(size)
1022 };
1023 Some(Size::new(
1024 w + pad_x * 2.0 + bw * 2.0,
1025 h + pad_y * 2.0 + bw * 2.0,
1026 ))
1027 }
1028
1029 fn on_event(&self, event: &Event, ctx: &mut EventCtx) {
1030 if ctx.phase != EventPhase::Target {
1031 return;
1032 }
1033 match event {
1034 Event::Pointer(PointerEvent::Down {
1035 button: PointerButton::Left,
1036 pos,
1037 modifiers,
1038 ..
1039 }) => {
1040 self.begin_session();
1041 let len = self.chars().len();
1042 let (pad_x, _) = self.resolved_pad();
1043 let ix = pad_x + self.approx_border_w();
1044 let scroll = self.with_state(|s| s.scroll);
1045 let x = pos.x - (ctx.rect.left() + ix) + scroll;
1046 let caret = self.caret_at(x, len);
1047 let extend = modifiers.contains(Modifiers::SHIFT);
1048 self.set_caret_extend(caret, extend);
1049 self.bump();
1050 ctx.request_focus();
1051
1052 ctx.capture_pointer();
1053 ctx.handled();
1054 }
1055 Event::Pointer(PointerEvent::Move { pos, .. }) => {
1056 if ctx.state.pressed {
1057 let len = self.chars().len();
1058 let (pad_x, _) = self.resolved_pad();
1059 let ix = pad_x + self.approx_border_w();
1060 let scroll = self.with_state(|s| s.scroll);
1061 let x = pos.x - (ctx.rect.left() + ix) + scroll;
1062 let caret = self.caret_at(x, len);
1063 self.set_caret_extend(caret, true);
1064 self.bump();
1065 ctx.handled();
1066 }
1067 }
1068 Event::Key(key) => {
1069 if !self.on_clipboard_key(key, ctx) {
1070 self.on_key(key);
1071 }
1072 ctx.handled();
1073 }
1074 Event::Ime(ime) => {
1075 self.on_ime(ime);
1076 ctx.handled();
1077 }
1078 _ => {}
1079 }
1080 }
1081
1082 fn paint_self(&self, ctx: &mut PaintCtx) {
1083 self.note_focus(ctx.state.focused);
1084 let rect = ctx.rect;
1085 if rect.size.width <= 0.0 || rect.size.height <= 0.0 {
1086 return;
1087 }
1088 let theme = ctx.theme();
1089 let size = self.resolved_font_size(&theme);
1090 let weight = self.font_weight();
1091 let line_width = self.resolved_line_width();
1092 let placeholder_color = self.resolved_placeholder_color(&theme);
1093 let (pad_x, pad_y) = self.resolved_pad();
1094 let background = self.resolved_background();
1095 let radius = self.resolved_radius(&theme);
1096 let border = self.resolved_border(&theme);
1097 let colors = underline::field_colors(
1098 &theme,
1099 ctx.state.hovered || ctx.state.focused,
1100 ctx.state.focused,
1101 self.resolved_text_color(),
1102 self.resolved_line_color(),
1103 );
1104
1105 if background.a > 0.0 || border.is_some() {
1106 match border {
1107 Some((w, color)) => {
1108 ctx.bordered_quad(rect, background, radius, w, color);
1109 }
1110 None => {
1111 ctx.quad(rect, background, radius);
1112 }
1113 }
1114 }
1115
1116 let bw = border.map(|(w, _)| w).unwrap_or(0.0);
1117 let content_h = if line_width > 0.0 {
1118 underline::content_height(size, line_width)
1119 } else {
1120 LayoutCtx::text_line_height(size)
1121 };
1122 let inner_h = (rect.size.height - bw * 2.0 - pad_y * 2.0).max(0.0);
1123 let yoff = if inner_h > content_h {
1124 (inner_h - content_h) * 0.5
1125 } else {
1126 0.0
1127 };
1128 let (ipx, ipy) = (pad_x + bw, pad_y + bw + yoff);
1129
1130 let line_w = (rect.size.width - ipx * 2.0).max(0.0);
1131 if line_width > 0.0 && colors.line.a > 0.0 {
1132 let line = underline::line_rect_padded(rect, size, line_w, line_width, ipx, ipy);
1133 ctx.quad(line, colors.line, line_width * 0.5);
1134 }
1135
1136 let text = self.value.get_untracked();
1137 let chars: Vec<char> = text.chars().collect();
1138 let (caret, anchor, preedit) = self.with_state(|s| (s.caret, s.anchor, s.preedit.clone()));
1139 let caret = caret.min(chars.len());
1140 let vis_w = (rect.size.width - ipx * 2.0).max(0.0);
1141
1142 let mut caret_x = 0.0;
1143 let mut bounds: Vec<f32>;
1144 if ctx.state.hovered || ctx.state.focused || anchor.is_some() {
1145 bounds = ctx.caret_positions(&text, size, weight);
1146
1147 if !preedit.is_empty() {
1148 let pw = ctx.text_width_weighted(&preedit, size, weight);
1149
1150 for b in bounds.iter_mut().skip(caret) {
1151 *b += pw;
1152 }
1153 }
1154 caret_x = bounds.get(caret).copied().unwrap_or(0.0);
1155
1156 if !preedit.is_empty() {
1157 caret_x += ctx.text_width_weighted(&preedit, size, weight);
1158 }
1159
1160 let text_w = bounds.last().copied().unwrap_or(0.0);
1161 self.with_state(|s| {
1162 if caret_x + CARET_W - s.scroll > vis_w {
1163 s.scroll = caret_x + CARET_W - vis_w;
1164 }
1165 if caret_x - s.scroll < 0.0 {
1166 s.scroll = caret_x;
1167 }
1168 s.scroll = s.scroll.clamp(0.0, (text_w + CARET_W - vis_w).max(0.0));
1169 });
1170 self.store_bounds(bounds);
1171 }
1172 let scroll = self.with_state(|s| s.scroll);
1173 let x = rect.left() + ipx - scroll;
1174 let y = underline::baseline_padded(rect, size, ipy);
1175
1176 let clip_box = Rect::from_xywh(
1177 rect.left() + ipx,
1178 rect.top() + bw,
1179 vis_w,
1180 (rect.size.height - bw * 2.0).max(0.0),
1181 );
1182 ctx.push_clip(clip_box);
1183
1184 if let Some(a) = anchor {
1185 let (s0, s1) = (a.min(caret).min(chars.len()), a.max(caret).min(chars.len()));
1186 if s1 > s0 {
1187 let stored = self.stored_bounds();
1188
1189 let x0 = stored.get(s0).copied().unwrap_or(0.0);
1190 let x1 = stored.get(s1).copied().unwrap_or(x0);
1191 if x1 > x0 {
1192 let top = y - size * 0.8;
1193 let sel = Rect::from_xywh(x + x0, top, x1 - x0, size * 1.05);
1194 ctx.quad(sel, theme.accent_pressed, 0.0);
1195 }
1196 }
1197 }
1198
1199 if chars.is_empty() && preedit.is_empty() && !self.placeholder.is_empty() {
1200 ctx.draw_text_weighted(&self.placeholder, (x, y), size, weight, placeholder_color);
1201 } else if preedit.is_empty() {
1202 ctx.draw_text_weighted(&text, (x, y), size, weight, colors.text);
1203 } else {
1204 let before: String = chars[..caret.min(chars.len())].iter().collect();
1205 let after: String = chars[caret.min(chars.len())..].iter().collect();
1206 let composed = format!("{before}{preedit}{after}");
1207 ctx.draw_text_weighted(&composed, (x, y), size, weight, colors.text);
1208
1209 let stored = self.stored_bounds();
1210 let x0 = stored.get(caret).copied().unwrap_or(0.0);
1211 let pw = ctx.text_width_weighted(&preedit, size, weight);
1212 let line_y =
1213 underline::line_rect_padded(rect, size, line_w, line_width, ipx, ipy).top();
1214 ctx.quad(
1215 Rect::from_xywh(x + x0, line_y - 2.0, pw.max(1.0), 1.5),
1216 colors.text,
1217 0.0,
1218 );
1219 }
1220
1221 if ctx.state.focused {
1222 let top = y - size * 0.8;
1223 ctx.quad(
1224 Rect::from_xywh(x + caret_x, top, CARET_W, size * 1.05),
1225 colors.text,
1226 0.0,
1227 );
1228 }
1229
1230 ctx.pop_clip();
1231 }
1232}
1233
1234fn sanitize_margin(v: f32) -> f32 {
1235 if v.is_finite() { v.max(0.0) } else { 0.0 }
1236}