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