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,
7 render::FontSynthesis,
8 update::UpdateOp,
9 widget::{
10 WIDGET,
11 info::WIDGET_TREE_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::Update { .. } => {
117 if FONT_CHANGED_EVENT.has_update(true) | FONT_FAMILY_VAR.is_new()
118 || FONT_STYLE_VAR.is_new()
119 || FONT_WEIGHT_VAR.is_new()
120 || FONT_STRETCH_VAR.is_new()
121 {
122 state = State::Reload;
123 } else if let State::Loading { response, .. } = &state {
124 if let Some(f) = response.rsp() {
125 tracing::trace!("text {:?} fonts finished loading", WIDGET.id());
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 tracing::trace!("text {:?} begin load font list", WIDGET.id());
153 FONTS.list(
154 family,
155 FONT_STYLE_VAR.get(),
156 FONT_WEIGHT_VAR.get(),
157 FONT_STRETCH_VAR.get(),
158 lang.best(),
159 )
160 })
161 });
162
163 if let Some(f) = font_list.rsp() {
164 tracing::trace!("font list already loaded");
165 let mut txt = TEXT.resolve();
166 txt.synthesis = FONT_SYNTHESIS_VAR.get() & f.best().synthesis_for(FONT_STYLE_VAR.get(), FONT_WEIGHT_VAR.get());
167 txt.faces = f;
168 state = State::Loaded;
169
170 WIDGET.layout();
171 } else {
172 tracing::trace!("font list loading");
173 state = State::Loading {
174 _update_handle: font_list.subscribe(UpdateOp::Update, WIDGET.id()),
175 response: font_list,
176 _window_load_handle: WINDOW.loading_handle(1.secs(), "font-list"),
177 };
178 }
179 }
180 })
181}
182fn resolve_text_access(child: impl IntoUiNode) -> UiNode {
183 match_node(child, |child, op| match op {
184 UiNodeOp::Init => {
185 WIDGET
186 .sub_var_info(&TEXT.resolved().txt)
187 .sub_var_info(&TEXT_EDITABLE_VAR)
188 .sub_var_info(&TEXT_SELECTABLE_VAR)
189 .sub_var_info(&OBSCURE_TXT_VAR);
190 }
191 UiNodeOp::Info { info } => {
192 let editable = TEXT_EDITABLE_VAR.get();
193 if editable || TEXT_SELECTABLE_VAR.get() {
194 FocusInfoBuilder::new(info).focusable_passive(true);
195 }
196
197 child.info(info);
198
199 if !editable
200 && !OBSCURE_TXT_VAR.get()
201 && let Some(mut a) = info.access()
202 {
203 a.set_label(TEXT.resolved().segmented_text.text().clone());
204 }
205 }
206 _ => {}
207 })
208}
209fn resolve_text_segments(child: impl IntoUiNode) -> UiNode {
210 match_node(child, |_, op| {
211 let mut segment = false;
212 match op {
213 UiNodeOp::Init => {
214 WIDGET
215 .sub_var(&TEXT.resolved().txt)
216 .sub_var(&TEXT_TRANSFORM_VAR)
217 .sub_var(&WHITE_SPACE_VAR)
218 .sub_var(&DIRECTION_VAR)
219 .sub_var(&TEXT_EDITABLE_VAR);
220
221 segment = true;
222 }
223 UiNodeOp::Update { .. } => {
224 segment = TEXT.resolved().txt.is_new()
225 || TEXT_TRANSFORM_VAR.is_new()
226 || WHITE_SPACE_VAR.is_new()
227 || DIRECTION_VAR.is_new()
228 || TEXT_EDITABLE_VAR.is_new();
229 }
230 _ => {}
231 }
232 if segment {
233 let mut ctx = TEXT.resolve();
234
235 let mut txt = ctx.txt.get();
236
237 if !TEXT_EDITABLE_VAR.get() {
238 TEXT_TRANSFORM_VAR.with(|t| {
239 if let Cow::Owned(t) = t.transform(&txt) {
240 txt = t;
241 }
242 });
243 WHITE_SPACE_VAR.with(|t| {
244 if let Cow::Owned(t) = t.transform(&txt) {
245 txt = t;
246 }
247 });
248 }
249
250 let direction = DIRECTION_VAR.get();
251 if ctx.segmented_text.text() != &txt || ctx.segmented_text.base_direction() != direction {
252 ctx.segmented_text = SegmentedText::new(txt, direction);
253
254 ctx.pending_layout = PendingLayout::RESHAPE;
255 WIDGET.layout();
256 }
257 }
258 })
259}
260fn resolve_text_edit(child: impl IntoUiNode) -> UiNode {
261 let mut edit = None::<Box<ResolveTextEdit>>;
263
264 match_node(child, move |child, op| {
265 let mut enable = false;
266 match op {
267 UiNodeOp::Init => {
268 WIDGET
269 .sub_var(&TEXT_EDITABLE_VAR)
270 .sub_var(&TEXT_SELECTABLE_VAR)
271 .sub_var(&MAX_CHARS_COUNT_VAR);
272 enable = TEXT_EDITABLE_VAR.get() || TEXT_SELECTABLE_VAR.get();
273 }
274 UiNodeOp::Deinit => {
275 edit = None;
276 }
277 UiNodeOp::Update { updates } => {
278 if TEXT_EDITABLE_VAR.is_new() || TEXT_SELECTABLE_VAR.is_new() {
279 enable = TEXT_EDITABLE_VAR.get() || TEXT_SELECTABLE_VAR.get();
280 if !enable && edit.is_some() {
281 edit = None;
282 TEXT.resolve().caret.opacity = var(0.fct()).read_only();
283 }
284 }
285
286 if let Some(edit) = &mut edit {
287 child.update(updates);
288
289 resolve_text_edit_update(edit);
290
291 if TEXT_EDITABLE_VAR.get() && TEXT.resolved().txt.capabilities().can_modify() {
292 resolve_text_edit_events(edit);
293 }
294 if TEXT_EDITABLE_VAR.get() || TEXT_SELECTABLE_VAR.get() {
295 resolve_text_edit_or_select_events(edit);
296 }
297
298 let enable = !OBSCURE_TXT_VAR.get() && TEXT.resolved().caret.selection_range().is_some();
299 edit.cut.enabled().set(enable);
300 edit.copy.enabled().set(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.focus_changed = FOCUS_CHANGED_EVENT.subscribe(UpdateOp::Update, id);
313 edit.key_input = KEY_INPUT_EVENT.subscribe(UpdateOp::Update, id);
314 edit.access_text = ACCESS_TEXT_EVENT.subscribe(UpdateOp::Update, id);
315 edit.ime = IME_EVENT.subscribe(UpdateOp::Update, id);
316
317 let win_id = WINDOW.id();
318 let i = WIDGET_TREE_CHANGED_EVENT.var_map(
319 move |args| {
320 if args.tree.window_id() == win_id
321 && let Some(wgt) = args.tree.get(id)
322 {
323 Some(wgt.interactivity())
324 } else {
325 None
326 }
327 },
328 Interactivity::empty,
329 );
330 i.subscribe(UpdateOp::Update, id).perm();
331 edit.interactivity = Some(i);
332
333 edit.paste = PASTE_CMD.scoped(id).subscribe(true);
334 edit.edit = EDIT_CMD.scoped(id).subscribe(true);
335
336 edit.max_count = MAX_CHARS_COUNT_VAR.subscribe(UpdateOp::Update, id);
337
338 let mut ctx = TEXT.resolve();
339
340 enforce_max_count(&ctx.txt);
341
342 if FOCUS.is_focused(WIDGET.id()).get() {
343 ctx.caret.opacity = KEYBOARD.caret_animation();
344 edit.caret_animation = ctx.caret.opacity.subscribe(UpdateOp::Update, WIDGET.id());
345 }
346 }
347
348 if TEXT_SELECTABLE_VAR.get() {
349 let id = WIDGET.id();
350
351 edit.access_selection = ACCESS_SELECTION_EVENT.subscribe(UpdateOp::Update, id);
352
353 let enabled = !OBSCURE_TXT_VAR.get() && TEXT.resolved().caret.selection_range().is_some();
354 edit.copy = COPY_CMD.scoped(id).subscribe(enabled);
355 if editable {
356 edit.cut = CUT_CMD.scoped(id).subscribe(enabled);
357 } else {
358 edit.focus_changed = FOCUS_CHANGED_EVENT.subscribe(UpdateOp::Update, id);
360
361 edit.key_input = KEY_INPUT_EVENT.subscribe(UpdateOp::Update, id);
362 }
363 }
364 }
365 })
366}
367#[derive(Default)]
369struct ResolveTextEdit {
370 focus_changed: VarHandle,
371 key_input: VarHandle,
372 access_text: VarHandle,
373 access_selection: VarHandle,
374 ime: VarHandle,
375
376 interactivity: Option<Var<Interactivity>>,
377 caret_animation: VarHandle,
378
379 max_count: VarHandle,
380 cut: CommandHandle,
381 copy: CommandHandle,
382 paste: CommandHandle,
383 edit: CommandHandle,
384}
385impl ResolveTextEdit {
386 fn get(edit_data: &mut Option<Box<Self>>) -> &mut Self {
387 &mut *edit_data.get_or_insert_with(Default::default)
388 }
389}
390fn enforce_max_count(text: &Var<Txt>) {
391 let max_count = MAX_CHARS_COUNT_VAR.get();
392 if max_count > 0 {
393 let count = text.with(|t| t.chars().count());
394 if count > max_count {
395 tracing::debug!("txt var set to text longer than can be typed");
396 text.modify(move |t| {
397 if let Some((i, _)) = t.as_str().char_indices().nth(max_count) {
398 t.to_mut().truncate(i);
399 }
400 });
401 }
402 }
403}
404fn resolve_text_edit_events(edit: &mut ResolveTextEdit) {
405 if let Some(i) = edit.interactivity.as_ref().unwrap().get_new()
406 && i.is_disabled()
407 {
408 edit.caret_animation = VarHandle::dummy();
409 TEXT.resolve().caret.opacity = var(0.fct()).read_only();
410 }
411
412 if TEXT.resolved().pending_edit {
413 return;
414 }
415 let widget = WIDGET.info();
416 if !widget.interactivity().is_enabled() {
417 return;
418 }
419
420 let prev_caret = {
421 let r = TEXT.resolved();
422 (r.caret.index, r.caret.index_version, r.caret.selection_index)
423 };
424
425 KEY_INPUT_EVENT.each_update(false, |args| {
426 let mut ctx = TEXT.resolve();
427 if args.state == KeyState::Pressed && args.target.widget_id() == widget.id() {
428 match &args.key {
429 Key::Backspace => {
430 let caret = &mut ctx.caret;
431 if caret.selection_index.is_some() || caret.index.unwrap_or(CaretIndex::ZERO).index > 0 {
432 if args.modifiers.is_only_ctrl() {
433 args.propagation.stop();
434 ctx.selection_by = SelectionBy::Keyboard;
435 drop(ctx);
436 TextEditOp::backspace_word().call_edit_op();
437 } else if args.modifiers.is_empty() {
438 args.propagation.stop();
439 ctx.selection_by = SelectionBy::Keyboard;
440 drop(ctx);
441 TextEditOp::backspace().call_edit_op();
442 }
443 }
444 }
445 Key::Delete => {
446 let caret = &mut ctx.caret;
447 let caret_idx = caret.index.unwrap_or(CaretIndex::ZERO);
448 if caret.selection_index.is_some() || caret_idx.index < ctx.segmented_text.text().len() {
449 if args.modifiers.is_only_ctrl() {
450 args.propagation.stop();
451 ctx.selection_by = SelectionBy::Keyboard;
452 drop(ctx);
453 TextEditOp::delete_word().call_edit_op();
454 } else if args.modifiers.is_empty() {
455 args.propagation.stop();
456 ctx.selection_by = SelectionBy::Keyboard;
457 drop(ctx);
458 TextEditOp::delete().call_edit_op();
459 }
460 }
461 }
462 _ => {
463 let insert = args.insert_str();
464 if !insert.is_empty() {
465 let skip = (args.is_tab() && !ACCEPTS_TAB_VAR.get()) || (args.is_line_break() && !ACCEPTS_ENTER_VAR.get());
466 if !skip {
467 args.propagation.stop();
468 ctx.selection_by = SelectionBy::Keyboard;
469 drop(ctx);
470 TextEditOp::insert(Txt::from_str(insert)).call_edit_op();
471 }
472 }
473 }
474 }
475 }
476 });
477 FOCUS_CHANGED_EVENT.each_update(true, |args| {
478 let mut ctx = TEXT.resolve();
479 let caret = &mut ctx.caret;
480 let caret_index = &mut caret.index;
481
482 if args.is_focused(widget.id()) {
483 if caret_index.is_none() {
484 *caret_index = Some(CaretIndex::ZERO);
485 } else {
486 caret.opacity = KEYBOARD.caret_animation();
488 edit.caret_animation = caret.opacity.subscribe(UpdateOp::RenderUpdate, widget.id());
489 }
490 } else {
491 edit.caret_animation = VarHandle::dummy();
492 caret.opacity = var(0.fct()).read_only();
493 }
494
495 let auto_select = AUTO_SELECTION_VAR.get();
496 if auto_select != AutoSelection::DISABLED && caret.selection_index.is_some() && TEXT_SELECTABLE_VAR.get() {
497 if auto_select.contains(AutoSelection::CLEAR_ON_BLUR) {
498 if let Some(rich) = TEXT.try_rich() {
499 if args.is_focus_leave(rich.root_id) {
500 if let Some(rich_root) = rich.root_info() {
503 let alt_return = FOCUS.alt_return().with(|p| p.as_ref().map(|p| p.widget_id()));
504 if alt_return.is_none() || rich_root.descendants().all(|d| d.id() != alt_return.unwrap()) {
505 if let Some(info) = WIDGET.info().into_focusable(true, true)
507 && let Some(scope) = info.scope()
508 {
509 let parent_return = FOCUS.return_focused(scope.info().id()).with(|p| p.as_ref().map(|p| p.widget_id()));
510 if parent_return.is_none() || rich_root.descendants().all(|d| d.id() != alt_return.unwrap()) {
511 SELECT_CMD.scoped(widget.id()).notify_param(TextSelectOp::next());
513 }
514 }
515 }
516 }
517 }
518 } else if args.is_blur(widget.id()) {
519 let us = Some(widget.id());
522 let alt_return = FOCUS.alt_return().with(|p| p.as_ref().map(|p| p.widget_id()));
523 if alt_return != us {
524 if let Some(info) = WIDGET.info().into_focusable(true, true)
526 && let Some(scope) = info.scope()
527 {
528 let parent_return = FOCUS.return_focused(scope.info().id()).with(|p| p.as_ref().map(|p| p.widget_id()));
529 if parent_return != us {
530 SELECT_CMD.scoped(widget.id()).notify_param(TextSelectOp::next());
532 }
533 }
534 }
535 }
536 }
537
538 if auto_select.contains(AutoSelection::ALL_ON_FOCUS_KEYBOARD) && args.highlight && args.is_focus(widget.id()) {
539 SELECT_ALL_CMD.scoped(widget.id()).notify();
541 }
542
543 }
545 });
546
547 CUT_CMD.scoped(widget.id()).each_update(true, false, |args| {
548 let mut ctx = TEXT.resolve();
549 if let Some(range) = ctx.caret.selection_char_range() {
550 args.propagation.stop();
551 ctx.selection_by = SelectionBy::Command;
552 CLIPBOARD.set_text(Txt::from_str(&ctx.segmented_text.text()[range]));
553 drop(ctx);
554 TextEditOp::delete().call_edit_op();
555 }
556 });
557 PASTE_CMD.scoped(widget.id()).each_update(true, false, |args| {
558 if let Some(paste) = CLIPBOARD.text().ok().flatten()
559 && !paste.is_empty()
560 {
561 args.propagation.stop();
562 TEXT.resolve().selection_by = SelectionBy::Command;
563 TextEditOp::insert(paste).call_edit_op();
564 }
565 });
566 EDIT_CMD.scoped(widget.id()).each_update(true, false, |args| {
567 if let Some(op) = args.param::<UndoTextEditOp>() {
568 args.propagation.stop();
569
570 op.call();
571 if !TEXT.resolved().pending_edit {
572 TEXT.resolve().pending_edit = true;
573 WIDGET.update();
574 }
575 } else if let Some(op) = args.param::<TextEditOp>() {
576 args.propagation.stop();
577
578 op.clone().call_edit_op();
579 }
580 });
581 ACCESS_TEXT_EVENT.each_update(false, |args| {
582 if args.target.widget_id() == widget.id() {
583 args.propagation.stop();
584
585 if args.selection_only {
586 TextEditOp::insert(args.txt.clone())
587 } else {
588 let current_len = TEXT.resolved().txt.with(|t| t.len());
589 let new_len = args.txt.len();
590 TextEditOp::replace(0..current_len, args.txt.clone(), new_len..new_len)
591 }
592 .call_edit_op();
593 }
594 });
595 IME_EVENT.each_update(false, |args| {
596 let mut resegment = false;
597
598 if let Some((start, end)) = args.preview_caret {
599 let mut ctx = TEXT.resolve();
602 let ctx = &mut *ctx;
603
604 if args.txt.is_empty() {
605 if let Some(preview) = ctx.ime_preview.take() {
606 resegment = true;
607 let caret = &mut ctx.caret;
608 caret.set_index(preview.prev_caret);
609 caret.selection_index = preview.prev_selection;
610 }
611 } else if let Some(preview) = &mut ctx.ime_preview {
612 resegment = preview.txt != args.txt;
613 if resegment {
614 preview.txt = args.txt.clone();
615 }
616 } else {
617 resegment = true;
618 let caret = &mut ctx.caret;
619 ctx.ime_preview = Some(ImePreview {
620 txt: args.txt.clone(),
621 prev_caret: caret.index.unwrap_or(CaretIndex::ZERO),
622 prev_selection: caret.selection_index,
623 });
624 }
625
626 if let Some(preview) = &ctx.ime_preview {
628 let caret = &mut ctx.caret;
629 let ime_start = if let Some(s) = preview.prev_selection {
630 preview.prev_caret.index.min(s.index)
631 } else {
632 preview.prev_caret.index
633 };
634 if start != end {
635 let start = ime_start + start;
636 let end = ime_start + end;
637 resegment |= caret.selection_char_range() != Some(start..end);
638 caret.set_char_selection(start, end);
639 } else {
640 let start = ime_start + start;
641 resegment |= caret.selection_index.is_some() || caret.index.map(|c| c.index) != Some(start);
642 caret.set_char_index(start);
643 caret.selection_index = None;
644 }
645 }
646 } else {
647 args.propagation.stop();
650 {
651 let mut ctx = TEXT.resolve();
652 if let Some(preview) = ctx.ime_preview.take() {
653 let caret = &mut ctx.caret;
655 caret.set_index(preview.prev_caret);
656 caret.selection_index = preview.prev_selection;
657
658 if args.txt.is_empty() {
659 resegment = true;
662 }
663 }
664 }
665
666 if !args.txt.is_empty() {
667 let mut ctx = TEXT.resolve();
669 ctx.selection_by = SelectionBy::Keyboard;
670
671 ctx.pending_layout |= PendingLayout::UNDERLINE;
674 WIDGET.layout();
675
676 drop(ctx);
677 TextEditOp::insert(args.txt.clone()).call_edit_op();
678 }
679 }
680
681 if resegment {
682 let mut ctx = TEXT.resolve();
683
684 let mut text = ctx.txt.get();
686 if let Some(preview) = &ctx.ime_preview {
687 if let Some(s) = preview.prev_selection {
688 let range = if preview.prev_caret.index < s.index {
689 preview.prev_caret.index..s.index
690 } else {
691 s.index..preview.prev_caret.index
692 };
693 text.to_mut().replace_range(range, preview.txt.as_str());
694 } else {
695 text.to_mut().insert_str(preview.prev_caret.index, preview.txt.as_str());
696 }
697 text.end_mut();
698 }
699 ctx.segmented_text = SegmentedText::new(text, DIRECTION_VAR.get());
700
701 ctx.pending_layout |= PendingLayout::RESHAPE;
702 WIDGET.layout();
703 }
704 });
705
706 let mut ctx = TEXT.resolve();
707 let caret = &mut ctx.caret;
708
709 if (caret.index, caret.index_version, caret.selection_index) != prev_caret {
710 caret.used_retained_x = false;
711 if caret.index.is_none() || !FOCUS.is_focused(widget.id()).get() {
712 edit.caret_animation = VarHandle::dummy();
713 caret.opacity = var(0.fct()).read_only();
714 } else {
715 caret.opacity = KEYBOARD.caret_animation();
716 edit.caret_animation = caret.opacity.subscribe(UpdateOp::RenderUpdate, widget.id());
717 }
718 ctx.pending_layout |= PendingLayout::CARET;
719 WIDGET.layout(); }
721}
722fn resolve_text_edit_or_select_events(_: &mut ResolveTextEdit) {
723 let widget_id = WIDGET.id();
724
725 COPY_CMD.scoped(widget_id).each_update(true, false, |args| {
726 let ctx = TEXT.resolved();
727 if let Some(range) = ctx.caret.selection_char_range() {
728 args.propagation.stop();
729 let txt = Txt::from_str(&ctx.segmented_text.text()[range]);
730 if let Some(rt) = args.param::<RichTextCopyParam>() {
731 rt.set_text(txt);
732 } else {
733 let _ = CLIPBOARD.set_text(txt);
734 }
735 }
736 });
737 ACCESS_SELECTION_EVENT.each_update(false, |args| {
738 if args.start.0.widget_id() == widget_id && args.caret.0.widget_id() == widget_id {
739 args.propagation.stop();
740
741 let mut ctx = TEXT.resolve();
742
743 ctx.caret.set_char_selection(args.start.1, args.caret.1);
744
745 ctx.pending_layout |= PendingLayout::CARET;
746 WIDGET.layout();
747 }
748 });
749}
750fn resolve_text_edit_update(_: &mut ResolveTextEdit) {
751 let mut ctx = TEXT.resolve();
752 let ctx = &mut *ctx;
753 if ctx.txt.is_new() {
754 if !ctx.pending_edit && UNDO.scope() == Some(WIDGET.id()) {
755 UNDO.clear();
756 }
757
758 if let Some(p) = ctx.ime_preview.take() {
759 ctx.caret.index = Some(p.prev_caret);
760 ctx.caret.selection_index = p.prev_selection;
761
762 CANCEL_IME_CMD.scoped(WINDOW.id()).notify();
763 }
764
765 enforce_max_count(&ctx.txt);
766
767 let caret = &mut ctx.caret;
769 if let Some(i) = &mut caret.index {
770 i.index = ctx.segmented_text.snap_grapheme_boundary(i.index);
771 }
772 if let Some(i) = &mut caret.selection_index {
773 i.index = ctx.segmented_text.snap_grapheme_boundary(i.index);
774 }
775 if let Some((cr, _)) = &mut caret.initial_selection {
776 cr.start.index = ctx.segmented_text.snap_grapheme_boundary(cr.start.index);
777 cr.end.index = ctx.segmented_text.snap_grapheme_boundary(cr.end.index);
778 }
779 }
780
781 if TEXT_EDITABLE_VAR.get() && MAX_CHARS_COUNT_VAR.is_new() {
782 enforce_max_count(&TEXT.resolved().txt);
783 }
784
785 ctx.pending_edit = false;
787}