1use std::{borrow::Cow, num::Wrapping, sync::Arc};
2
3use parking_lot::RwLock;
4use zng_app::{
5 access::{ACCESS_SELECTION_EVENT, ACCESS_TEXT_EVENT},
6 event::{CommandHandle, EventHandle},
7 render::FontSynthesis,
8 update::{EventUpdate, UpdateOp},
9 widget::{
10 WIDGET,
11 info::INTERACTIVITY_CHANGED_EVENT,
12 node::{UiNode, UiNodeOp, match_node},
13 },
14 window::WINDOW,
15};
16use zng_ext_clipboard::{CLIPBOARD, COPY_CMD, CUT_CMD, PASTE_CMD};
17use zng_ext_font::{CaretIndex, FONT_CHANGED_EVENT, FONTS, FontFaceList, SegmentedText};
18use zng_ext_input::{
19 focus::{FOCUS, FOCUS_CHANGED_EVENT, FocusInfoBuilder, WidgetInfoFocusExt as _},
20 keyboard::{KEY_INPUT_EVENT, KEYBOARD},
21};
22use zng_ext_l10n::LANG_VAR;
23use zng_ext_undo::UNDO;
24use zng_ext_window::{IME_EVENT, WINDOW_Ext as _, WindowLoadingHandle, cmd::CANCEL_IME_CMD};
25use zng_layout::context::{DIRECTION_VAR, LayoutDirection};
26use zng_view_api::keyboard::{Key, KeyState};
27use zng_wgt::prelude::*;
28
29use crate::{
30 ACCEPTS_ENTER_VAR, ACCEPTS_TAB_VAR, AUTO_SELECTION_VAR, AutoSelection, FONT_FAMILY_VAR, FONT_STRETCH_VAR, FONT_STYLE_VAR,
31 FONT_SYNTHESIS_VAR, FONT_WEIGHT_VAR, MAX_CHARS_COUNT_VAR, OBSCURE_TXT_VAR, TEXT_EDITABLE_VAR, TEXT_SELECTABLE_VAR, TEXT_TRANSFORM_VAR,
32 WHITE_SPACE_VAR,
33 cmd::{EDIT_CMD, SELECT_ALL_CMD, SELECT_CMD, TextEditOp, TextSelectOp, UndoTextEditOp},
34};
35
36use super::{CaretInfo, ImePreview, PendingLayout, RESOLVED_TEXT, ResolvedText, RichTextCopyParam, SelectionBy, TEXT};
37
38pub fn resolve_text(child: impl IntoUiNode, text: impl IntoVar<Txt>) -> UiNode {
48 let child = resolve_text_font(child);
49 let child = resolve_text_access(child);
50 let child = resolve_text_edit(child);
51 let child = resolve_text_segments(child);
52 resolve_text_context(child, text.into_var())
53}
54fn resolve_text_context(child: impl IntoUiNode, text: Var<Txt>) -> UiNode {
55 let mut resolved = None;
56 match_node(child, move |child, op| match op {
57 UiNodeOp::Init => {
58 resolved = Some(Arc::new(RwLock::new(ResolvedText {
59 txt: text.clone(),
60 ime_preview: None,
61 synthesis: FontSynthesis::empty(),
62 faces: FontFaceList::empty(),
63 segmented_text: SegmentedText::new(Txt::from_static(""), LayoutDirection::LTR),
64 pending_layout: PendingLayout::empty(),
65 pending_edit: false,
66 caret: CaretInfo {
67 opacity: var(0.fct()).read_only(),
68 index: None,
69 selection_index: None,
70 initial_selection: None,
71 index_version: Wrapping(0),
72 used_retained_x: false,
73 skip_next_scroll: false,
74 },
75 selection_by: SelectionBy::Command,
76 selection_toolbar_is_open: false,
77 })));
78
79 RESOLVED_TEXT.with_context(&mut resolved, || child.init());
80 }
81 UiNodeOp::Deinit => {
82 RESOLVED_TEXT.with_context(&mut resolved, || child.deinit());
83
84 resolved = None;
85 }
86 UiNodeOp::Layout { wl, final_size } => RESOLVED_TEXT.with_context(&mut resolved, || {
87 *final_size = child.layout(wl);
88 TEXT.resolve().pending_layout = PendingLayout::empty();
89 }),
90 op => RESOLVED_TEXT.with_context(&mut resolved, || child.op(op)),
91 })
92}
93fn resolve_text_font(child: impl IntoUiNode) -> UiNode {
94 enum State {
95 Reload,
96 Loading {
97 response: ResponseVar<FontFaceList>,
98 _update_handle: VarHandle,
99 _window_load_handle: Option<WindowLoadingHandle>,
100 },
101 Loaded,
102 }
103 let mut state = State::Reload;
104
105 match_node(child, move |_, op| {
106 match op {
107 UiNodeOp::Init => {
108 WIDGET
109 .sub_var(&FONT_FAMILY_VAR)
110 .sub_var(&FONT_STYLE_VAR)
111 .sub_var(&FONT_WEIGHT_VAR)
112 .sub_var(&FONT_STRETCH_VAR)
113 .sub_event(&FONT_CHANGED_EVENT)
114 .sub_var(&FONT_SYNTHESIS_VAR);
115 }
116 UiNodeOp::Event { update } => {
117 if FONT_CHANGED_EVENT.has(update) {
118 state = State::Reload;
119 }
120 }
121 UiNodeOp::Update { .. } => {
122 if FONT_FAMILY_VAR.is_new() || FONT_STYLE_VAR.is_new() || FONT_WEIGHT_VAR.is_new() || FONT_STRETCH_VAR.is_new() {
123 state = State::Reload;
124 } else if let State::Loading { response, .. } = &state {
125 if let Some(f) = response.rsp() {
126 let mut txt = TEXT.resolve();
127 txt.synthesis = FONT_SYNTHESIS_VAR.get() & f.best().synthesis_for(FONT_STYLE_VAR.get(), FONT_WEIGHT_VAR.get());
128 txt.faces = f;
129 state = State::Loaded;
130
131 WIDGET.layout();
132 }
133 } else if let State::Loaded = &state
134 && FONT_SYNTHESIS_VAR.is_new()
135 {
136 let mut txt = TEXT.resolve();
137 txt.synthesis = FONT_SYNTHESIS_VAR.get() & txt.faces.best().synthesis_for(FONT_STYLE_VAR.get(), FONT_WEIGHT_VAR.get());
138
139 WIDGET.render();
140 }
141 }
142 UiNodeOp::Deinit => {
143 state = State::Reload;
144 return;
145 }
146 _ => {}
147 }
148
149 if let State::Reload = &state {
150 let font_list = FONT_FAMILY_VAR.with(|family| {
151 LANG_VAR.with(|lang| {
152 FONTS.list(
153 family,
154 FONT_STYLE_VAR.get(),
155 FONT_WEIGHT_VAR.get(),
156 FONT_STRETCH_VAR.get(),
157 lang.best(),
158 )
159 })
160 });
161
162 if let Some(f) = font_list.rsp() {
163 let mut txt = TEXT.resolve();
164 txt.synthesis = FONT_SYNTHESIS_VAR.get() & f.best().synthesis_for(FONT_STYLE_VAR.get(), FONT_WEIGHT_VAR.get());
165 txt.faces = f;
166 state = State::Loaded;
167
168 WIDGET.layout();
169 } else {
170 state = State::Loading {
171 _update_handle: font_list.subscribe(UpdateOp::Update, WIDGET.id()),
172 response: font_list,
173 _window_load_handle: WINDOW.loading_handle(1.secs()),
174 };
175 }
176 }
177 })
178}
179fn resolve_text_access(child: impl IntoUiNode) -> UiNode {
180 match_node(child, |child, op| match op {
181 UiNodeOp::Init => {
182 WIDGET
183 .sub_var_info(&TEXT.resolved().txt)
184 .sub_var_info(&TEXT_EDITABLE_VAR)
185 .sub_var_info(&TEXT_SELECTABLE_VAR)
186 .sub_var_info(&OBSCURE_TXT_VAR);
187 }
188 UiNodeOp::Info { info } => {
189 let editable = TEXT_EDITABLE_VAR.get();
190 if editable || TEXT_SELECTABLE_VAR.get() {
191 FocusInfoBuilder::new(info).focusable_passive(true);
192 }
193
194 child.info(info);
195
196 if !editable
197 && !OBSCURE_TXT_VAR.get()
198 && let Some(mut a) = info.access()
199 {
200 a.set_label(TEXT.resolved().segmented_text.text().clone());
201 }
202 }
203 _ => {}
204 })
205}
206fn resolve_text_segments(child: impl IntoUiNode) -> UiNode {
207 match_node(child, |_, op| {
208 let mut segment = false;
209 match op {
210 UiNodeOp::Init => {
211 WIDGET
212 .sub_var(&TEXT.resolved().txt)
213 .sub_var(&TEXT_TRANSFORM_VAR)
214 .sub_var(&WHITE_SPACE_VAR)
215 .sub_var(&DIRECTION_VAR)
216 .sub_var(&TEXT_EDITABLE_VAR);
217
218 segment = true;
219 }
220 UiNodeOp::Update { .. } => {
221 segment = TEXT.resolved().txt.is_new()
222 || TEXT_TRANSFORM_VAR.is_new()
223 || WHITE_SPACE_VAR.is_new()
224 || DIRECTION_VAR.is_new()
225 || TEXT_EDITABLE_VAR.is_new();
226 }
227 _ => {}
228 }
229 if segment {
230 let mut ctx = TEXT.resolve();
231
232 let mut txt = ctx.txt.get();
233
234 if !TEXT_EDITABLE_VAR.get() {
235 TEXT_TRANSFORM_VAR.with(|t| {
236 if let Cow::Owned(t) = t.transform(&txt) {
237 txt = t;
238 }
239 });
240 WHITE_SPACE_VAR.with(|t| {
241 if let Cow::Owned(t) = t.transform(&txt) {
242 txt = t;
243 }
244 });
245 }
246
247 let direction = DIRECTION_VAR.get();
248 if ctx.segmented_text.text() != &txt || ctx.segmented_text.base_direction() != direction {
249 ctx.segmented_text = SegmentedText::new(txt, direction);
250
251 ctx.pending_layout = PendingLayout::RESHAPE;
252 WIDGET.layout();
253 }
254 }
255 })
256}
257fn resolve_text_edit(child: impl IntoUiNode) -> UiNode {
258 let mut edit = None::<Box<ResolveTextEdit>>;
260
261 match_node(child, move |child, op| {
262 let mut enable = false;
263 match op {
264 UiNodeOp::Init => {
265 WIDGET
266 .sub_var(&TEXT_EDITABLE_VAR)
267 .sub_var(&TEXT_SELECTABLE_VAR)
268 .sub_var(&MAX_CHARS_COUNT_VAR);
269 enable = TEXT_EDITABLE_VAR.get() || TEXT_SELECTABLE_VAR.get();
270 }
271 UiNodeOp::Deinit => {
272 edit = None;
273 }
274 UiNodeOp::Update { .. } => {
275 if TEXT_EDITABLE_VAR.is_new() || TEXT_SELECTABLE_VAR.is_new() {
276 enable = TEXT_EDITABLE_VAR.get() || TEXT_SELECTABLE_VAR.get();
277 if !enable && edit.is_some() {
278 edit = None;
279 TEXT.resolve().caret.opacity = var(0.fct()).read_only();
280 }
281 }
282
283 if let Some(edit) = &mut edit {
284 resolve_text_edit_update(edit);
285 }
286 }
287 UiNodeOp::Event { update } => {
288 child.event(update);
289
290 if let Some(edit) = &mut edit {
291 if TEXT_EDITABLE_VAR.get() && TEXT.resolved().txt.capabilities().can_modify() {
292 resolve_text_edit_events(update, edit);
293 }
294 if TEXT_EDITABLE_VAR.get() || TEXT_SELECTABLE_VAR.get() {
295 resolve_text_edit_or_select_events(update, edit);
296 }
297
298 let enable = !OBSCURE_TXT_VAR.get() && TEXT.resolved().caret.selection_range().is_some();
299 edit.cut.set_enabled(enable);
300 edit.copy.set_enabled(enable);
301 }
302 }
303 _ => {}
304 }
305 if enable {
306 let edit = ResolveTextEdit::get(&mut edit);
307
308 let editable = TEXT_EDITABLE_VAR.get();
309 if editable {
310 let id = WIDGET.id();
311
312 edit.events[0] = FOCUS_CHANGED_EVENT.subscribe(id);
313 edit.events[1] = INTERACTIVITY_CHANGED_EVENT.subscribe(id);
314 edit.events[2] = KEY_INPUT_EVENT.subscribe(id);
315 edit.events[3] = ACCESS_TEXT_EVENT.subscribe(id);
316 edit.events[5] = IME_EVENT.subscribe(id);
317
318 edit.paste = PASTE_CMD.scoped(id).subscribe(true);
319 edit.edit = EDIT_CMD.scoped(id).subscribe(true);
320
321 edit.max_count = MAX_CHARS_COUNT_VAR.subscribe(UpdateOp::Update, id);
322
323 let mut ctx = TEXT.resolve();
324
325 enforce_max_count(&ctx.txt);
326
327 if FOCUS.is_focused(WIDGET.id()).get() {
328 ctx.caret.opacity = KEYBOARD.caret_animation();
329 edit.caret_animation = ctx.caret.opacity.subscribe(UpdateOp::Update, WIDGET.id());
330 }
331 }
332
333 if TEXT_SELECTABLE_VAR.get() {
334 let id = WIDGET.id();
335
336 edit.events[4] = ACCESS_SELECTION_EVENT.subscribe(id);
337
338 let enabled = !OBSCURE_TXT_VAR.get() && TEXT.resolved().caret.selection_range().is_some();
339 edit.copy = COPY_CMD.scoped(id).subscribe(enabled);
340 if editable {
341 edit.cut = CUT_CMD.scoped(id).subscribe(enabled);
342 } else {
343 edit.events[0] = FOCUS_CHANGED_EVENT.subscribe(id);
345
346 edit.events[2] = KEY_INPUT_EVENT.subscribe(id);
347 }
348 }
349 }
350 })
351}
352#[derive(Default)]
354struct ResolveTextEdit {
355 events: [EventHandle; 6],
356 caret_animation: VarHandle,
357 max_count: VarHandle,
358 cut: CommandHandle,
359 copy: CommandHandle,
360 paste: CommandHandle,
361 edit: CommandHandle,
362}
363impl ResolveTextEdit {
364 fn get(edit_data: &mut Option<Box<Self>>) -> &mut Self {
365 &mut *edit_data.get_or_insert_with(Default::default)
366 }
367}
368fn enforce_max_count(text: &Var<Txt>) {
369 let max_count = MAX_CHARS_COUNT_VAR.get();
370 if max_count > 0 {
371 let count = text.with(|t| t.chars().count());
372 if count > max_count {
373 tracing::debug!("txt var set to text longer than can be typed");
374 text.modify(move |t| {
375 if let Some((i, _)) = t.as_str().char_indices().nth(max_count) {
376 t.to_mut().truncate(i);
377 }
378 });
379 }
380 }
381}
382fn resolve_text_edit_events(update: &EventUpdate, edit: &mut ResolveTextEdit) {
383 if let Some(args) = INTERACTIVITY_CHANGED_EVENT.on(update)
384 && args.is_disable(WIDGET.id())
385 {
386 edit.caret_animation = VarHandle::dummy();
387 TEXT.resolve().caret.opacity = var(0.fct()).read_only();
388 }
389
390 if TEXT.resolved().pending_edit {
391 return;
392 }
393 let widget = WIDGET.info();
394 if !widget.interactivity().is_enabled() {
395 return;
396 }
397
398 let prev_caret = {
399 let r = TEXT.resolved();
400 (r.caret.index, r.caret.index_version, r.caret.selection_index)
401 };
402
403 if let Some(args) = KEY_INPUT_EVENT.on_unhandled(update) {
404 let mut ctx = TEXT.resolve();
405 if args.state == KeyState::Pressed && args.target.widget_id() == widget.id() {
406 match &args.key {
407 Key::Backspace => {
408 let caret = &mut ctx.caret;
409 if caret.selection_index.is_some() || caret.index.unwrap_or(CaretIndex::ZERO).index > 0 {
410 if args.modifiers.is_only_ctrl() {
411 args.propagation().stop();
412 ctx.selection_by = SelectionBy::Keyboard;
413 drop(ctx);
414 TextEditOp::backspace_word().call_edit_op();
415 } else if args.modifiers.is_empty() {
416 args.propagation().stop();
417 ctx.selection_by = SelectionBy::Keyboard;
418 drop(ctx);
419 TextEditOp::backspace().call_edit_op();
420 }
421 }
422 }
423 Key::Delete => {
424 let caret = &mut ctx.caret;
425 let caret_idx = caret.index.unwrap_or(CaretIndex::ZERO);
426 if caret.selection_index.is_some() || caret_idx.index < ctx.segmented_text.text().len() {
427 if args.modifiers.is_only_ctrl() {
428 args.propagation().stop();
429 ctx.selection_by = SelectionBy::Keyboard;
430 drop(ctx);
431 TextEditOp::delete_word().call_edit_op();
432 } else if args.modifiers.is_empty() {
433 args.propagation().stop();
434 ctx.selection_by = SelectionBy::Keyboard;
435 drop(ctx);
436 TextEditOp::delete().call_edit_op();
437 }
438 }
439 }
440 _ => {
441 let insert = args.insert_str();
442 if !insert.is_empty() {
443 let skip = (args.is_tab() && !ACCEPTS_TAB_VAR.get()) || (args.is_line_break() && !ACCEPTS_ENTER_VAR.get());
444 if !skip {
445 args.propagation().stop();
446 ctx.selection_by = SelectionBy::Keyboard;
447 drop(ctx);
448 TextEditOp::insert(Txt::from_str(insert)).call_edit_op();
449 }
450 }
451 }
452 }
453 }
454 } else if let Some(args) = FOCUS_CHANGED_EVENT.on(update) {
455 let mut ctx = TEXT.resolve();
456 let caret = &mut ctx.caret;
457 let caret_index = &mut caret.index;
458
459 if args.is_focused(widget.id()) {
460 if caret_index.is_none() {
461 *caret_index = Some(CaretIndex::ZERO);
462 } else {
463 caret.opacity = KEYBOARD.caret_animation();
465 edit.caret_animation = caret.opacity.subscribe(UpdateOp::RenderUpdate, widget.id());
466 }
467 } else {
468 edit.caret_animation = VarHandle::dummy();
469 caret.opacity = var(0.fct()).read_only();
470 }
471
472 let auto_select = AUTO_SELECTION_VAR.get();
473 if auto_select != AutoSelection::DISABLED && caret.selection_index.is_some() && TEXT_SELECTABLE_VAR.get() {
474 if auto_select.contains(AutoSelection::CLEAR_ON_BLUR) {
475 if let Some(rich) = TEXT.try_rich() {
476 if args.is_focus_leave(rich.root_id) {
477 if let Some(rich_root) = rich.root_info() {
480 let alt_return = FOCUS.alt_return().with(|p| p.as_ref().map(|p| p.widget_id()));
481 if alt_return.is_none() || rich_root.descendants().all(|d| d.id() != alt_return.unwrap()) {
482 if let Some(info) = WIDGET.info().into_focusable(true, true)
484 && let Some(scope) = info.scope()
485 {
486 let parent_return = FOCUS.return_focused(scope.info().id()).with(|p| p.as_ref().map(|p| p.widget_id()));
487 if parent_return.is_none() || rich_root.descendants().all(|d| d.id() != alt_return.unwrap()) {
488 SELECT_CMD.scoped(widget.id()).notify_param(TextSelectOp::next());
490 }
491 }
492 }
493 }
494 }
495 } else if args.is_blur(widget.id()) {
496 let us = Some(widget.id());
499 let alt_return = FOCUS.alt_return().with(|p| p.as_ref().map(|p| p.widget_id()));
500 if alt_return != us {
501 if let Some(info) = WIDGET.info().into_focusable(true, true)
503 && let Some(scope) = info.scope()
504 {
505 let parent_return = FOCUS.return_focused(scope.info().id()).with(|p| p.as_ref().map(|p| p.widget_id()));
506 if parent_return != us {
507 SELECT_CMD.scoped(widget.id()).notify_param(TextSelectOp::next());
509 }
510 }
511 }
512 }
513 }
514
515 if auto_select.contains(AutoSelection::ALL_ON_FOCUS_KEYBOARD) && args.highlight && args.is_focus(widget.id()) {
516 SELECT_ALL_CMD.scoped(widget.id()).notify();
518 }
519
520 }
522 } else if let Some(args) = CUT_CMD.scoped(widget.id()).on_unhandled(update) {
523 let mut ctx = TEXT.resolve();
524 if let Some(range) = ctx.caret.selection_char_range() {
525 args.propagation().stop();
526 ctx.selection_by = SelectionBy::Command;
527 CLIPBOARD.set_text(Txt::from_str(&ctx.segmented_text.text()[range]));
528 drop(ctx);
529 TextEditOp::delete().call_edit_op();
530 }
531 } else if let Some(args) = PASTE_CMD.scoped(widget.id()).on_unhandled(update) {
532 if let Some(paste) = CLIPBOARD.text().ok().flatten()
533 && !paste.is_empty()
534 {
535 args.propagation().stop();
536 TEXT.resolve().selection_by = SelectionBy::Command;
537 TextEditOp::insert(paste).call_edit_op();
538 }
539 } else if let Some(args) = EDIT_CMD.scoped(widget.id()).on_unhandled(update) {
540 if let Some(op) = args.param::<UndoTextEditOp>() {
541 args.propagation().stop();
542
543 op.call();
544 if !TEXT.resolved().pending_edit {
545 TEXT.resolve().pending_edit = true;
546 WIDGET.update();
547 }
548 } else if let Some(op) = args.param::<TextEditOp>() {
549 args.propagation().stop();
550
551 op.clone().call_edit_op();
552 }
553 } else if let Some(args) = ACCESS_TEXT_EVENT.on_unhandled(update) {
554 if args.widget_id == widget.id() {
555 args.propagation().stop();
556
557 if args.selection_only {
558 TextEditOp::insert(args.txt.clone())
559 } else {
560 let current_len = TEXT.resolved().txt.with(|t| t.len());
561 let new_len = args.txt.len();
562 TextEditOp::replace(0..current_len, args.txt.clone(), new_len..new_len)
563 }
564 .call_edit_op();
565 }
566 } else if let Some(args) = IME_EVENT.on_unhandled(update) {
567 let mut resegment = false;
568
569 if let Some((start, end)) = args.preview_caret {
570 let mut ctx = TEXT.resolve();
573 let ctx = &mut *ctx;
574
575 if args.txt.is_empty() {
576 if let Some(preview) = ctx.ime_preview.take() {
577 resegment = true;
578 let caret = &mut ctx.caret;
579 caret.set_index(preview.prev_caret);
580 caret.selection_index = preview.prev_selection;
581 }
582 } else if let Some(preview) = &mut ctx.ime_preview {
583 resegment = preview.txt != args.txt;
584 if resegment {
585 preview.txt = args.txt.clone();
586 }
587 } else {
588 resegment = true;
589 let caret = &mut ctx.caret;
590 ctx.ime_preview = Some(ImePreview {
591 txt: args.txt.clone(),
592 prev_caret: caret.index.unwrap_or(CaretIndex::ZERO),
593 prev_selection: caret.selection_index,
594 });
595 }
596
597 if let Some(preview) = &ctx.ime_preview {
599 let caret = &mut ctx.caret;
600 let ime_start = if let Some(s) = preview.prev_selection {
601 preview.prev_caret.index.min(s.index)
602 } else {
603 preview.prev_caret.index
604 };
605 if start != end {
606 let start = ime_start + start;
607 let end = ime_start + end;
608 resegment |= caret.selection_char_range() != Some(start..end);
609 caret.set_char_selection(start, end);
610 } else {
611 let start = ime_start + start;
612 resegment |= caret.selection_index.is_some() || caret.index.map(|c| c.index) != Some(start);
613 caret.set_char_index(start);
614 caret.selection_index = None;
615 }
616 }
617 } else {
618 args.propagation().stop();
621 {
622 let mut ctx = TEXT.resolve();
623 if let Some(preview) = ctx.ime_preview.take() {
624 let caret = &mut ctx.caret;
626 caret.set_index(preview.prev_caret);
627 caret.selection_index = preview.prev_selection;
628
629 if args.txt.is_empty() {
630 resegment = true;
633 }
634 }
635 }
636
637 if !args.txt.is_empty() {
638 let mut ctx = TEXT.resolve();
640 ctx.selection_by = SelectionBy::Keyboard;
641
642 ctx.pending_layout |= PendingLayout::UNDERLINE;
645 WIDGET.layout();
646
647 drop(ctx);
648 TextEditOp::insert(args.txt.clone()).call_edit_op();
649 }
650 }
651
652 if resegment {
653 let mut ctx = TEXT.resolve();
654
655 let mut text = ctx.txt.get();
657 if let Some(preview) = &ctx.ime_preview {
658 if let Some(s) = preview.prev_selection {
659 let range = if preview.prev_caret.index < s.index {
660 preview.prev_caret.index..s.index
661 } else {
662 s.index..preview.prev_caret.index
663 };
664 text.to_mut().replace_range(range, preview.txt.as_str());
665 } else {
666 text.to_mut().insert_str(preview.prev_caret.index, preview.txt.as_str());
667 }
668 text.end_mut();
669 }
670 ctx.segmented_text = SegmentedText::new(text, DIRECTION_VAR.get());
671
672 ctx.pending_layout |= PendingLayout::RESHAPE;
673 WIDGET.layout();
674 }
675 }
676
677 let mut ctx = TEXT.resolve();
678 let caret = &mut ctx.caret;
679
680 if (caret.index, caret.index_version, caret.selection_index) != prev_caret {
681 caret.used_retained_x = false;
682 if caret.index.is_none() || !FOCUS.is_focused(widget.id()).get() {
683 edit.caret_animation = VarHandle::dummy();
684 caret.opacity = var(0.fct()).read_only();
685 } else {
686 caret.opacity = KEYBOARD.caret_animation();
687 edit.caret_animation = caret.opacity.subscribe(UpdateOp::RenderUpdate, widget.id());
688 }
689 ctx.pending_layout |= PendingLayout::CARET;
690 WIDGET.layout(); }
692}
693fn resolve_text_edit_or_select_events(update: &EventUpdate, _: &mut ResolveTextEdit) {
694 let widget_id = WIDGET.id();
695
696 if let Some(args) = COPY_CMD.scoped(widget_id).on_unhandled(update) {
697 let ctx = TEXT.resolved();
698 if let Some(range) = ctx.caret.selection_char_range() {
699 args.propagation().stop();
700 let txt = Txt::from_str(&ctx.segmented_text.text()[range]);
701 if let Some(rt) = args.param::<RichTextCopyParam>() {
702 rt.set_text(txt);
703 } else {
704 let _ = CLIPBOARD.set_text(txt);
705 }
706 }
707 } else if let Some(args) = ACCESS_SELECTION_EVENT.on_unhandled(update)
708 && args.start.0 == widget_id
709 && args.caret.0 == widget_id
710 {
711 args.propagation().stop();
712
713 let mut ctx = TEXT.resolve();
714
715 ctx.caret.set_char_selection(args.start.1, args.caret.1);
716
717 ctx.pending_layout |= PendingLayout::CARET;
718 WIDGET.layout();
719 }
720}
721fn resolve_text_edit_update(_: &mut ResolveTextEdit) {
722 let mut ctx = TEXT.resolve();
723 let ctx = &mut *ctx;
724 if ctx.txt.is_new() {
725 if !ctx.pending_edit && UNDO.scope() == Some(WIDGET.id()) {
726 UNDO.clear();
727 }
728
729 if let Some(p) = ctx.ime_preview.take() {
730 ctx.caret.index = Some(p.prev_caret);
731 ctx.caret.selection_index = p.prev_selection;
732
733 CANCEL_IME_CMD.scoped(WINDOW.id()).notify();
734 }
735
736 enforce_max_count(&ctx.txt);
737
738 let caret = &mut ctx.caret;
740 if let Some(i) = &mut caret.index {
741 i.index = ctx.segmented_text.snap_grapheme_boundary(i.index);
742 }
743 if let Some(i) = &mut caret.selection_index {
744 i.index = ctx.segmented_text.snap_grapheme_boundary(i.index);
745 }
746 if let Some((cr, _)) = &mut caret.initial_selection {
747 cr.start.index = ctx.segmented_text.snap_grapheme_boundary(cr.start.index);
748 cr.end.index = ctx.segmented_text.snap_grapheme_boundary(cr.end.index);
749 }
750 }
751
752 if TEXT_EDITABLE_VAR.get() && MAX_CHARS_COUNT_VAR.is_new() {
753 enforce_max_count(&TEXT.resolved().txt);
754 }
755
756 ctx.pending_edit = false;
758}