zng_wgt_text/
text_properties.rs

1use std::{fmt, num::NonZeroU32, time::Duration};
2
3use zng_app::render::FontSynthesis;
4use zng_color::COLOR_SCHEME_VAR;
5use zng_ext_font::{font_features::*, *};
6use zng_ext_l10n::{LANG_VAR, Langs};
7use zng_view_api::config::FontAntiAliasing;
8use zng_wgt::prelude::*;
9use zng_wgt_layer::AnchorOffset;
10
11use crate::node::TEXT;
12
13/// Basic text font properties.
14///
15/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
16///
17/// See also [`FontFeaturesMix<P>`] for the other font properties.
18///
19/// [`Text!`]: struct@crate::Text
20#[widget_mixin]
21pub struct FontMix<P>(P);
22
23context_var! {
24    /// Font family of [`Text!`] spans.
25    ///
26    /// [`Text!`]: struct@crate::Text
27    pub static FONT_FAMILY_VAR: FontNames = FontNames::default();
28
29    /// Font size of [`Text!`] spans.
30    ///
31    /// [`Text!`]: struct@crate::Text
32    pub static FONT_SIZE_VAR: FontSize = FontSize::Pt(11.0);
33
34    /// Font weight of [`Text!`] spans.
35    ///
36    /// [`Text!`]: struct@crate::Text
37    pub static FONT_WEIGHT_VAR: FontWeight = FontWeight::NORMAL;
38
39    /// Font style of [`Text!`] spans.
40    ///
41    /// [`Text!`]: struct@crate::Text
42    pub static FONT_STYLE_VAR: FontStyle = FontStyle::Normal;
43
44    /// Font stretch of [`Text!`] spans.
45    ///
46    /// [`Text!`]: struct@crate::Text
47    pub static FONT_STRETCH_VAR: FontStretch = FontStretch::NORMAL;
48
49    /// Font synthesis of [`Text!`] spans.
50    ///
51    /// [`Text!`]: struct@crate::Text
52    pub static FONT_SYNTHESIS_VAR: FontSynthesis = FontSynthesis::ENABLED;
53
54    /// Font anti-aliasing of [`Text!`] spans.
55    ///
56    /// [`Text!`]: struct@crate::Text
57    pub static FONT_AA_VAR: FontAntiAliasing = FontAntiAliasing::Default;
58}
59
60impl FontMix<()> {
61    /// Insert context variables used by properties in this mixin.
62    pub fn context_vars_set(set: &mut ContextValueSet) {
63        set.insert(&FONT_FAMILY_VAR);
64        set.insert(&FONT_SIZE_VAR);
65        set.insert(&FONT_WEIGHT_VAR);
66        set.insert(&FONT_STYLE_VAR);
67        set.insert(&FONT_STRETCH_VAR);
68        set.insert(&FONT_SYNTHESIS_VAR);
69        set.insert(&FONT_AA_VAR);
70    }
71}
72
73/// Font family name or list of names for texts in this widget or descendants.
74///
75/// All fonts in the list are resolved according to the [`font_style`], [`font_weight`] and [`font_stretch`] config.
76/// During text shaping the first font on the list is preferred, but if the font does not cover a character or word, that
77/// character or word  to the second font in the list and so on.
78///
79/// Sets the [`FONT_FAMILY_VAR`].
80///
81/// [`font_style`]: fn@font_style
82/// [`font_weight`]: fn@font_weight
83/// [`font_stretch`]: fn@font_stretch
84#[property(CONTEXT, default(FONT_FAMILY_VAR), widget_impl(FontMix<P>))]
85pub fn font_family(child: impl IntoUiNode, names: impl IntoVar<FontNames>) -> UiNode {
86    with_context_var(child, FONT_FAMILY_VAR, names)
87}
88
89/// Sets the font size for the widget and descendants.
90///
91/// This property affects all texts inside the widget and the [`Length::Em`] unit.
92///
93/// Sets the [`FONT_SIZE_VAR`] context var and the [`LayoutMetrics::font_size`].
94///
95/// [`LayoutMetrics::font_size`]: zng_wgt::prelude::LayoutMetrics::font_size
96/// [`Length::Em`]: zng_wgt::prelude::Length::Em
97#[property(CONTEXT, default(FONT_SIZE_VAR), widget_impl(FontMix<P>))]
98pub fn font_size(child: impl IntoUiNode, size: impl IntoVar<FontSize>) -> UiNode {
99    let child = match_node(child, |child, op| match op {
100        UiNodeOp::Init => {
101            WIDGET.sub_var_layout(&FONT_SIZE_VAR);
102        }
103        UiNodeOp::Measure { wm, desired_size } => {
104            let font_size = FONT_SIZE_VAR.get();
105            let font_size_px = font_size.layout_dft_x(LAYOUT.root_font_size());
106            *desired_size = if font_size_px >= 0 {
107                LAYOUT.with_font_size(font_size_px, || child.measure(wm))
108            } else {
109                tracing::error!("invalid font size {font_size:?} => {font_size_px:?}");
110                child.measure(wm)
111            };
112        }
113        UiNodeOp::Layout { wl, final_size } => {
114            let font_size = FONT_SIZE_VAR.get();
115            let font_size_px = font_size.layout_dft_x(LAYOUT.root_font_size());
116            *final_size = if font_size_px >= 0 {
117                LAYOUT.with_font_size(font_size_px, || child.layout(wl))
118            } else {
119                tracing::error!("invalid font size {font_size:?} => {font_size_px:?}");
120                child.layout(wl)
121            };
122        }
123        _ => {}
124    });
125    with_context_var(child, FONT_SIZE_VAR, size)
126}
127
128/// Defines the thickness or boldness the preferred font should have.
129///
130/// This value influences font resolution, the variant within the font family that is closest to this config will be selected.
131///
132/// Sets the [`FONT_WEIGHT_VAR`].
133#[property(CONTEXT, default(FONT_WEIGHT_VAR), widget_impl(FontMix<P>))]
134pub fn font_weight(child: impl IntoUiNode, weight: impl IntoVar<FontWeight>) -> UiNode {
135    with_context_var(child, FONT_WEIGHT_VAR, weight)
136}
137
138/// Defines the skew style of the font glyphs.
139///
140/// This value influences font resolution, the variant within the font family that is closest to this config will be selected.
141///
142/// Sets the [`FONT_STYLE_VAR`].
143#[property(CONTEXT, default(FONT_STYLE_VAR), widget_impl(FontMix<P>))]
144pub fn font_style(child: impl IntoUiNode, style: impl IntoVar<FontStyle>) -> UiNode {
145    with_context_var(child, FONT_STYLE_VAR, style)
146}
147
148/// Defines how condensed or expanded the preferred font should be.
149///
150/// This value influences font resolution, the variant within the font family that is closest to this config will be selected.
151///
152/// Sets the [`FONT_STRETCH_VAR`].
153#[property(CONTEXT, default(FONT_STRETCH_VAR), widget_impl(FontMix<P>))]
154pub fn font_stretch(child: impl IntoUiNode, stretch: impl IntoVar<FontStretch>) -> UiNode {
155    with_context_var(child, FONT_STRETCH_VAR, stretch)
156}
157
158/// Configure if a synthetic font is generated for fonts that do not implement **bold** or *oblique* variants.
159///
160/// Not all fonts implement the requested [`font_weight`] and [`font_style`], this config allows the renderer
161/// to try and generate the style and weight anyway, using transforms and the glyph outlines.
162///
163/// Sets the [`FONT_SYNTHESIS_VAR`].
164///
165/// [`font_weight`]: fn@font_weight
166/// [`font_style`]: fn@font_style
167#[property(CONTEXT, default(FONT_SYNTHESIS_VAR), widget_impl(FontMix<P>))]
168pub fn font_synthesis(child: impl IntoUiNode, enabled: impl IntoVar<FontSynthesis>) -> UiNode {
169    with_context_var(child, FONT_SYNTHESIS_VAR, enabled)
170}
171
172/// Configure the anti-aliasing used to render text glyphs inside the widget.
173///
174/// Uses the operating system configuration by default.
175///
176/// Sets the [`FONT_AA_VAR`].
177#[property(CONTEXT, default(FONT_AA_VAR), widget_impl(FontMix<P>))]
178pub fn font_aa(child: impl IntoUiNode, aa: impl IntoVar<FontAntiAliasing>) -> UiNode {
179    with_context_var(child, FONT_AA_VAR, aa)
180}
181
182/// Text color properties.
183///
184/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
185///
186/// [`Text!`]: struct@crate::Text
187#[widget_mixin]
188pub struct TextFillMix<P>(P);
189
190context_var! {
191    /// Color of [`Text!`] glyphs that are not colored by palette.
192    ///
193    /// [`Text!`]: struct@crate::Text
194    pub static FONT_COLOR_VAR: Rgba = COLOR_SCHEME_VAR.map(|s| match s {
195        ColorScheme::Light => colors::BLACK,
196        ColorScheme::Dark => colors::WHITE,
197        _ => colors::BLACK,
198    });
199
200    /// Color of [`Text!`] glyphs that are colored by palette, mostly Emoji.
201    ///
202    /// [`Text!`]: struct@crate::Text
203    pub static FONT_PALETTE_VAR: FontColorPalette = COLOR_SCHEME_VAR.map_into();
204
205    /// Overrides of specific colors in the selected colored glyph palette.
206    pub static FONT_PALETTE_COLORS_VAR: Vec<(u16, Rgba)> = vec![];
207}
208
209impl TextFillMix<()> {
210    /// Insert context variables used by properties in this mixin.
211    pub fn context_vars_set(set: &mut ContextValueSet) {
212        set.insert(&FONT_COLOR_VAR);
213        set.insert(&FONT_PALETTE_VAR);
214        set.insert(&FONT_PALETTE_COLORS_VAR);
215    }
216}
217
218/// Defines the color the most text glyphs are filled with.
219///
220/// Colored glyphs (Emoji) are not affected by this, you can use [`font_palette`] to modify
221/// Emoji colors.
222///
223/// Sets the [`FONT_COLOR_VAR`].
224///
225/// [`font_palette`]: fn@font_palette
226#[property(CONTEXT, default(FONT_COLOR_VAR), widget_impl(TextFillMix<P>))]
227pub fn font_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
228    with_context_var(child, FONT_COLOR_VAR, color)
229}
230
231/// Defines the palette used to render colored glyphs (Emoji).
232///
233/// This property only affects Emoji from fonts using COLR v0. You can use [`font_color`] to set
234/// the base color, and [`font_palette_colors`] to change specific colors.
235///
236/// Sets the [`FONT_PALETTE_VAR`].
237///
238/// [`font_color`]: fn@font_color
239/// [`font_palette_colors`]: fn@font_palette_colors
240#[property(CONTEXT, default(FONT_PALETTE_VAR), widget_impl(TextFillMix<P>))]
241pub fn font_palette(child: impl IntoUiNode, palette: impl IntoVar<FontColorPalette>) -> UiNode {
242    with_context_var(child, FONT_PALETTE_VAR, palette)
243}
244
245/// Set the palette color in the font palette colors.
246///
247/// The `index` is pushed or replaced on the context [`FONT_PALETTE_COLORS_VAR`].
248///
249/// This function is a helper for declaring properties that configure the colors of a specific font, you
250/// can use [`font_palette_colors`] to set all color overrides directly.
251///
252/// [`font_palette_colors`]: fn@font_palette_colors
253pub fn with_font_palette_color(child: impl IntoUiNode, index: u16, color: impl IntoVar<Rgba>) -> UiNode {
254    with_context_var(
255        child,
256        FONT_PALETTE_COLORS_VAR,
257        merge_var!(FONT_PALETTE_COLORS_VAR, color.into_var(), move |set, color| {
258            let mut set = set.clone();
259            if let Some(i) = set.iter().position(|(i, _)| *i == index) {
260                set[i].1 = *color;
261            } else {
262                set.push((index, *color));
263            }
264            set
265        }),
266    )
267}
268
269/// Defines custom palette colors that affect Emoji colors.
270///
271/// The palette is selected by [`font_palette`] and then each valid index entry in this property replaces
272/// the selected color.
273///
274/// Sets the [`FONT_PALETTE_COLORS_VAR`].
275///
276/// [`font_palette`]: fn@font_palette
277#[property(CONTEXT, default(FONT_PALETTE_COLORS_VAR), widget_impl(TextFillMix<P>))]
278pub fn font_palette_colors(child: impl IntoUiNode, colors: impl IntoVar<Vec<(u16, Rgba)>>) -> UiNode {
279    with_context_var(child, FONT_PALETTE_COLORS_VAR, colors)
280}
281
282/// Text align, justify.
283///
284/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
285///
286/// [`Text!`]: struct@crate::Text
287#[widget_mixin]
288pub struct TextAlignMix<P>(P);
289
290context_var! {
291    /// Text alignment inside the available space.
292    pub static TEXT_ALIGN_VAR: Align = Align::START;
293
294    /// Text alignment inside the available space when it overflows.
295    pub static TEXT_OVERFLOW_ALIGN_VAR: Align = Align::TOP_START;
296
297    /// Text justify mode when text align is fill.
298    pub static JUSTIFY_MODE_VAR: Justify = Justify::Auto;
299}
300
301impl TextAlignMix<()> {
302    /// Insert context variables used by properties in this mixin.
303    pub fn context_vars_set(set: &mut ContextValueSet) {
304        set.insert(&TEXT_ALIGN_VAR);
305        set.insert(&TEXT_OVERFLOW_ALIGN_VAR);
306        set.insert(&JUSTIFY_MODE_VAR);
307    }
308}
309
310/// Alignment of text inside available space.
311///
312/// Horizontal alignment is applied for each line independently, vertical alignment is applied for the entire
313/// text block together.
314///
315/// Note that the [`Text!`] widget only implements this for text inside each instance in isolation, multiple
316/// text instances in an inline row will not all align together by the [`Text!`] layout implementation alone.
317///
318/// Sets the [`TEXT_ALIGN_VAR`].
319///
320/// See also [`txt_overflow_align`], used when the text overflows.
321///
322/// [`Text!`]: struct@crate::Text
323/// [`txt_overflow_align`]: fn@txt_overflow_align
324#[property(CONTEXT, default(TEXT_ALIGN_VAR), widget_impl(TextAlignMix<P>))]
325pub fn txt_align(child: impl IntoUiNode, mode: impl IntoVar<Align>) -> UiNode {
326    with_context_var(child, TEXT_ALIGN_VAR, mode)
327}
328
329/// Alignment of text inside available space when the text overflows.
330///
331/// Note that the [`Text!`] widget only implements this for text inside each instance in isolation, multiple
332/// text instances in an inline row will not all align together. Also note that [`txt_overflow`] truncation
333/// only applies to the end of the text after it is aligned, so unless this is [`Align::TOP_START`] (default) the
334/// start of the text maybe still be clipped after truncation.
335///
336/// Sets the [`TEXT_OVERFLOW_ALIGN_VAR`].
337///
338/// [`Text!`]: struct@crate::Text
339/// [`txt_overflow`]: fn@txt_overflow
340/// [`Align::TOP_START`]: zng_wgt::prelude::Align::TOP_START
341#[property(CONTEXT, default(TEXT_OVERFLOW_ALIGN_VAR), widget_impl(TextAlignMix<P>))]
342pub fn txt_overflow_align(child: impl IntoUiNode, mode: impl IntoVar<Align>) -> UiNode {
343    with_context_var(child, TEXT_OVERFLOW_ALIGN_VAR, mode)
344}
345
346/// Config the automatic spacing inserted between words and letters when text is aligned to fill.
347///
348/// Text alignment can be set to [`Align::FILL_X`], if that is the case this config is defines how
349/// the glyphs are spaced to *fill* the text block.
350///
351/// Sets the [`JUSTIFY_MODE_VAR`].
352///
353/// [`Align::FILL_X`]: zng_wgt::prelude::Align::FILL_X
354#[property(CONTEXT, default(JUSTIFY_MODE_VAR), widget_impl(TextAlignMix<P>))]
355pub fn justify_mode(child: impl IntoUiNode, mode: impl IntoVar<Justify>) -> UiNode {
356    with_context_var(child, JUSTIFY_MODE_VAR, mode)
357}
358
359/// Text wrap, hyphenation.
360///
361/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
362///
363/// [`Text!`]: struct@crate::Text
364#[widget_mixin]
365pub struct TextWrapMix<P>(P);
366
367context_var! {
368    /// If line breaks are automatically inserted to fill the available space.
369    ///
370    /// The [`LINE_BREAK_VAR`], [`WORD_BREAK_VAR`] and [`HYPHENS_VAR`] configure how the text is split.
371    ///
372    /// Is `true` by default.
373    pub static TEXT_WRAP_VAR: bool = true;
374
375    /// Configuration of line breaks inside words during text wrap.
376    pub static WORD_BREAK_VAR: WordBreak = WordBreak::Normal;
377
378    /// Configuration of line breaks in Chinese, Japanese, or Korean text.
379    pub static LINE_BREAK_VAR: LineBreak = LineBreak::Auto;
380
381    /// Text hyphenation config.
382    pub static HYPHENS_VAR: Hyphens = Hyphens::default();
383
384    /// Hyphen text rendered when auto-hyphenating.
385    pub static HYPHEN_CHAR_VAR: Txt = Txt::from_char('-');
386
387    /// Text overflow handling.
388    pub static TEXT_OVERFLOW_VAR: TextOverflow = TextOverflow::Ignore;
389}
390
391impl TextWrapMix<()> {
392    /// Insert context variables used by properties in this mixin.
393    pub fn context_vars_set(set: &mut ContextValueSet) {
394        set.insert(&TEXT_WRAP_VAR);
395        set.insert(&WORD_BREAK_VAR);
396        set.insert(&LINE_BREAK_VAR);
397        set.insert(&HYPHENS_VAR);
398        set.insert(&HYPHEN_CHAR_VAR);
399        set.insert(&TEXT_OVERFLOW_VAR);
400    }
401}
402
403/// Defines how text overflow is handled by the text widgets.
404#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
405pub enum TextOverflow {
406    /// Text is allowed to overflow.
407    ///
408    /// Note that the text widget can still [`clip_to_bounds`], and text widgets also clip any text
409    /// that overflows over one line-height in any direction. Text overflow is tracked even if `Ignore`
410    /// is set, so custom properties may also implement some form of overflow handling.
411    ///
412    /// [`clip_to_bounds`]: fn@zng_wgt::clip_to_bounds
413    Ignore,
414    /// Truncate the text so it will fit, the associated `Txt` is a suffix appended to the truncated text.
415    ///
416    /// Note that if the suffix is not empty the text will truncate more to reserve space for the suffix. If
417    /// the suffix itself is too wide it will overflow.
418    Truncate(Txt),
419}
420impl fmt::Debug for TextOverflow {
421    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
422        if f.alternate() {
423            write!(f, "TextOverflow")?
424        }
425        match self {
426            Self::Ignore => write!(f, "Ignore"),
427            Self::Truncate(arg0) => f.debug_tuple("Truncate").field(arg0).finish(),
428        }
429    }
430}
431impl TextOverflow {
432    /// Truncate without suffix.
433    pub fn truncate() -> Self {
434        Self::Truncate(Txt::from_static(""))
435    }
436
437    /// Truncate with the ellipses `'…'` char as suffix.
438    pub fn ellipses() -> Self {
439        Self::Truncate(Txt::from_char('…'))
440    }
441}
442impl_from_and_into_var! {
443    /// Truncate (no suffix), or ignore.
444    fn from(truncate: bool) -> TextOverflow {
445        if truncate { TextOverflow::truncate() } else { TextOverflow::Ignore }
446    }
447
448    fn from(truncate: Txt) -> TextOverflow {
449        TextOverflow::Truncate(truncate)
450    }
451    fn from(s: &'static str) -> TextOverflow {
452        Txt::from(s).into()
453    }
454    fn from(s: String) -> TextOverflow {
455        Txt::from(s).into()
456    }
457    fn from(c: char) -> TextOverflow {
458        Txt::from(c).into()
459    }
460}
461
462/// Enables or disables text wrap.
463///
464/// If enabled, line-breaks and hyphens are automatically inserted to flow the text to fill the available width. Wrap
465/// can be configured using the [`line_break`], [`word_break`] and [`hyphens`] properties.
466///
467/// Sets the [`TEXT_WRAP_VAR`].
468///
469/// [`line_break`]: fn@line_break
470/// [`word_break`]: fn@word_break
471/// [`hyphens`]: fn@hyphens
472#[property(CONTEXT, default(TEXT_WRAP_VAR), widget_impl(TextWrapMix<P>))]
473pub fn txt_wrap(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
474    with_context_var(child, TEXT_WRAP_VAR, enabled)
475}
476
477/// Configure line breaks inside words during text wrap.
478///
479/// This value is only considered if it is impossible to fit a full word to a line.
480///
481/// Hyphens can be inserted in word breaks using the [`hyphens`] configuration.
482///
483/// Sets the [`WORD_BREAK_VAR`].
484///
485/// [`hyphens`]: fn@hyphens
486#[property(CONTEXT, default(WORD_BREAK_VAR), widget_impl(TextWrapMix<P>))]
487pub fn word_break(child: impl IntoUiNode, mode: impl IntoVar<WordBreak>) -> UiNode {
488    with_context_var(child, WORD_BREAK_VAR, mode)
489}
490
491/// Configuration of text wrapping for Chinese, Japanese, or Korean text.
492///
493/// Sets the [`LINE_BREAK_VAR`].
494#[property(CONTEXT, default(LINE_BREAK_VAR), widget_impl(TextWrapMix<P>))]
495pub fn line_break(child: impl IntoUiNode, mode: impl IntoVar<LineBreak>) -> UiNode {
496    with_context_var(child, LINE_BREAK_VAR, mode)
497}
498
499/// Configure hyphenation.
500///
501/// Note that for automatic hyphenation to work the [`lang`] must also be set and the [`HYPHENATION`] service must support it.
502///
503/// The auto hyphenation char can be defined using [`hyphen_char`].
504///
505/// [`HYPHENATION`]: zng_ext_font::HYPHENATION
506/// [`lang`]: fn@lang
507/// [`hyphen_char`]: fn@hyphen_char
508#[property(CONTEXT, default(HYPHENS_VAR), widget_impl(TextWrapMix<P>))]
509pub fn hyphens(child: impl IntoUiNode, hyphens: impl IntoVar<Hyphens>) -> UiNode {
510    with_context_var(child, HYPHENS_VAR, hyphens)
511}
512
513/// The char or small string that is rendered when text is auto-hyphenated.
514///
515/// Note that hyphenation is enabled by the [`hyphens`] property.
516///
517/// [`hyphens`]: fn@hyphens
518#[property(CONTEXT, default(HYPHEN_CHAR_VAR), widget_impl(TextWrapMix<P>))]
519pub fn hyphen_char(child: impl IntoUiNode, hyphen: impl IntoVar<Txt>) -> UiNode {
520    with_context_var(child, HYPHEN_CHAR_VAR, hyphen)
521}
522
523/// Defines if text overflow is truncated, with optional suffix append.
524///
525/// When enabled overflow is truncated by character or by the wrap rules if [`txt_wrap`] is enabled (it is by default).
526///
527/// Overflow is always ignored when the text is editable.
528///
529/// [`txt_wrap`]: fn@txt_wrap
530#[property(CONTEXT, default(TEXT_OVERFLOW_VAR), widget_impl(TextWrapMix<P>))]
531pub fn txt_overflow(child: impl IntoUiNode, overflow: impl IntoVar<TextOverflow>) -> UiNode {
532    with_context_var(child, TEXT_OVERFLOW_VAR, overflow)
533}
534
535/// Gets if the text is overflown.
536#[property(CHILD_LAYOUT+100, widget_impl(TextWrapMix<P>))]
537pub fn is_overflown(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
538    let state = state.into_var();
539    match_node(child, move |_, op| match op {
540        UiNodeOp::Deinit => {
541            state.set(false);
542        }
543        UiNodeOp::Layout { .. } => {
544            let is_o = super::node::TEXT.laidout().overflow.is_some();
545            if is_o != state.get() {
546                state.set(is_o);
547            }
548        }
549        _ => {}
550    })
551}
552
553/// Gets if the text has an entire line overflown.
554///
555/// This is `true` when the text has multiple lines, either due to line-break or wrap, and at
556/// least one line overflows the allowed height, partially or fully.
557#[property(CHILD_LAYOUT+100, widget_impl(TextWrapMix<P>))]
558pub fn is_line_overflown(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
559    let state = state.into_var();
560    match_node(child, move |_, op| match op {
561        UiNodeOp::Deinit => {
562            state.set(false);
563        }
564        UiNodeOp::Layout { .. } => {
565            let txt = super::node::TEXT.laidout();
566            let is_o = if let Some(info) = &txt.overflow {
567                info.line < txt.shaped_text.lines_len().saturating_sub(1)
568            } else {
569                false
570            };
571            if is_o != state.get() {
572                state.set(is_o);
573            }
574        }
575        _ => {}
576    })
577}
578
579/// Gets the overflow text, that is a clone of the text starting from the first overflow character.
580///
581/// Note that overflow is tracked even if [`txt_overflow`] is set to [`TextOverflow::Ignore`].
582///
583/// [`txt_overflow`]: fn@txt_overflow
584#[property(CHILD_LAYOUT+100, widget_impl(TextWrapMix<P>))]
585pub fn get_overflow(child: impl IntoUiNode, txt: impl IntoVar<Txt>) -> UiNode {
586    let txt = txt.into_var();
587    match_node(child, move |_, op| {
588        if let UiNodeOp::Layout { .. } = op {
589            let l_txt = super::node::TEXT.laidout();
590            if let Some(info) = &l_txt.overflow {
591                let r = super::node::TEXT.resolved();
592                let tail = &r.segmented_text.text()[info.text_char..];
593                if txt.with(|t| t != tail) {
594                    txt.set(Txt::from_str(tail));
595                }
596            } else if txt.with(|t| !t.is_empty()) {
597                txt.set(Txt::from_static(""));
598            }
599        }
600    })
601}
602
603/// Text underline, overline and strikethrough lines.
604///
605/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
606///
607/// [`Text!`]: struct@crate::Text
608#[widget_mixin]
609pub struct TextDecorationMix<P>(P);
610
611bitflags! {
612    /// Represents what parts of a text the underline must skip over.
613    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
614    #[serde(transparent)]
615    pub struct UnderlineSkip: u8 {
616        /// Underline spans the entire text length.
617        const NONE = 0;
618
619        /// Skip white space.
620        const SPACES = 0b0001;
621
622        /// Skip over glyph descenders that intersect with the underline.
623        const GLYPHS = 0b0010;
624
625        /// Default value, skip glyphs.
626        const DEFAULT = Self::GLYPHS.bits();
627    }
628}
629impl Default for UnderlineSkip {
630    fn default() -> Self {
631        Self::DEFAULT
632    }
633}
634
635/// Defines what line gets traced by the text underline decoration.
636#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
637pub enum UnderlinePosition {
638    /// Underline is positioned using the offset defined in the font file.
639    #[default]
640    Font,
641    /// Underline is positioned after the text *descent*, avoiding crossover with all glyph descenders.
642    Descent,
643}
644
645context_var! {
646    /// Underline thickness.
647    pub static UNDERLINE_THICKNESS_VAR: UnderlineThickness = 0;
648    /// Underline style.
649    pub static UNDERLINE_STYLE_VAR: LineStyle = LineStyle::Hidden;
650    /// Underline color, inherits from [`FONT_COLOR_VAR`].
651    pub static UNDERLINE_COLOR_VAR: Rgba = FONT_COLOR_VAR;
652    /// Parts of text skipped by underline.
653    pub static UNDERLINE_SKIP_VAR: UnderlineSkip = UnderlineSkip::DEFAULT;
654    /// Position of the underline.
655    pub static UNDERLINE_POSITION_VAR: UnderlinePosition = UnderlinePosition::Font;
656
657    /// Overline thickness.
658    pub static OVERLINE_THICKNESS_VAR: TextLineThickness = 0;
659    /// Overline style.
660    pub static OVERLINE_STYLE_VAR: LineStyle = LineStyle::Hidden;
661    /// Overline color, inherits from [`FONT_COLOR_VAR`].
662    pub static OVERLINE_COLOR_VAR: Rgba = FONT_COLOR_VAR;
663
664    /// Strikethrough thickness.
665    pub static STRIKETHROUGH_THICKNESS_VAR: TextLineThickness = 0;
666    /// Strikethrough style.
667    pub static STRIKETHROUGH_STYLE_VAR: LineStyle = LineStyle::Hidden;
668    /// Strikethrough color, inherits from [`FONT_COLOR_VAR`].
669    pub static STRIKETHROUGH_COLOR_VAR: Rgba = FONT_COLOR_VAR;
670
671    /// Underline thickness for the IME preview underline.
672    pub static IME_UNDERLINE_THICKNESS_VAR: UnderlineThickness = 1;
673    /// Underline style for the IME preview underline.
674    pub static IME_UNDERLINE_STYLE_VAR: LineStyle = LineStyle::Dotted;
675}
676
677impl TextDecorationMix<()> {
678    /// Insert context variables used by properties in this mixin.
679    pub fn context_vars_set(set: &mut ContextValueSet) {
680        set.insert(&UNDERLINE_THICKNESS_VAR);
681        set.insert(&UNDERLINE_STYLE_VAR);
682        set.insert(&UNDERLINE_COLOR_VAR);
683        set.insert(&UNDERLINE_SKIP_VAR);
684        set.insert(&UNDERLINE_POSITION_VAR);
685        set.insert(&OVERLINE_THICKNESS_VAR);
686        set.insert(&OVERLINE_STYLE_VAR);
687        set.insert(&OVERLINE_COLOR_VAR);
688        set.insert(&STRIKETHROUGH_THICKNESS_VAR);
689        set.insert(&STRIKETHROUGH_STYLE_VAR);
690        set.insert(&STRIKETHROUGH_COLOR_VAR);
691        set.insert(&IME_UNDERLINE_THICKNESS_VAR);
692        set.insert(&IME_UNDERLINE_STYLE_VAR);
693    }
694}
695
696/// Draw lines *under* each text line.
697///
698/// Sets the [`UNDERLINE_THICKNESS_VAR`] and [`UNDERLINE_STYLE_VAR`].
699#[property(CONTEXT, default(UNDERLINE_THICKNESS_VAR, UNDERLINE_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
700pub fn underline(child: impl IntoUiNode, thickness: impl IntoVar<UnderlineThickness>, style: impl IntoVar<LineStyle>) -> UiNode {
701    let child = with_context_var(child, UNDERLINE_THICKNESS_VAR, thickness);
702    with_context_var(child, UNDERLINE_STYLE_VAR, style)
703}
704/// Custom [`underline`](fn@underline) color, if not set
705/// the [`font_color`](fn@font_color) is used.
706///
707/// Sets the [`UNDERLINE_COLOR_VAR`].
708#[property(CONTEXT, default(UNDERLINE_COLOR_VAR), widget_impl(TextDecorationMix<P>))]
709pub fn underline_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
710    with_context_var(child, UNDERLINE_COLOR_VAR, color)
711}
712/// Defines what segments of each text line are skipped when tracing the [`underline`](fn@underline).
713///
714/// By default skips glyphs that intercept the underline.
715///
716/// Sets the [`UNDERLINE_SKIP_VAR`].
717#[property(CONTEXT, default(UNDERLINE_SKIP_VAR), widget_impl(TextDecorationMix<P>))]
718pub fn underline_skip(child: impl IntoUiNode, skip: impl IntoVar<UnderlineSkip>) -> UiNode {
719    with_context_var(child, UNDERLINE_SKIP_VAR, skip)
720}
721/// Defines what font line gets traced by the underline.
722///
723/// By default uses the font configuration, but it usually crosses over glyph *descents* causing skips on
724/// the line, you can set this [`UnderlinePosition::Descent`] to fully clear all glyph *descents*.
725///
726/// Sets the [`UNDERLINE_POSITION_VAR`].
727#[property(CONTEXT, default(UNDERLINE_POSITION_VAR), widget_impl(TextDecorationMix<P>))]
728pub fn underline_position(child: impl IntoUiNode, position: impl IntoVar<UnderlinePosition>) -> UiNode {
729    with_context_var(child, UNDERLINE_POSITION_VAR, position)
730}
731
732/// Draw lines *above* each text line.
733///
734/// Sets the [`OVERLINE_THICKNESS_VAR`] and [`OVERLINE_STYLE_VAR`].
735#[property(CONTEXT, default(OVERLINE_THICKNESS_VAR, OVERLINE_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
736pub fn overline(child: impl IntoUiNode, thickness: impl IntoVar<TextLineThickness>, style: impl IntoVar<LineStyle>) -> UiNode {
737    let child = with_context_var(child, OVERLINE_THICKNESS_VAR, thickness);
738    with_context_var(child, OVERLINE_STYLE_VAR, style)
739}
740/// Custom [`overline`](fn@overline) color, if not set
741/// the [`font_color`](fn@font_color) is used.
742///
743/// Sets the [`OVERLINE_COLOR_VAR`].
744#[property(CONTEXT, default(OVERLINE_COLOR_VAR), widget_impl(TextDecorationMix<P>))]
745pub fn overline_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
746    with_context_var(child, OVERLINE_COLOR_VAR, color)
747}
748
749/// Draw lines across each text line.
750///
751/// Sets the [`STRIKETHROUGH_THICKNESS_VAR`] and [`STRIKETHROUGH_STYLE_VAR`].
752#[property(CONTEXT, default(STRIKETHROUGH_THICKNESS_VAR, STRIKETHROUGH_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
753pub fn strikethrough(child: impl IntoUiNode, thickness: impl IntoVar<TextLineThickness>, style: impl IntoVar<LineStyle>) -> UiNode {
754    let child = with_context_var(child, STRIKETHROUGH_THICKNESS_VAR, thickness);
755    with_context_var(child, STRIKETHROUGH_STYLE_VAR, style)
756}
757/// Custom [`strikethrough`](fn@strikethrough) color, if not set
758/// the [`font_color`](fn@font_color) is used.
759///
760/// Sets the [`STRIKETHROUGH_COLOR_VAR`].
761#[property(CONTEXT, default(STRIKETHROUGH_COLOR_VAR), widget_impl(TextDecorationMix<P>))]
762pub fn strikethrough_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
763    with_context_var(child, STRIKETHROUGH_COLOR_VAR, color)
764}
765
766/// Style and thickness of the line drawn *under* the IME preview text.
767///
768/// Sets the [`IME_UNDERLINE_THICKNESS_VAR`] and [`IME_UNDERLINE_STYLE_VAR`].
769#[property(CONTEXT, default(IME_UNDERLINE_THICKNESS_VAR, IME_UNDERLINE_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
770pub fn ime_underline(child: impl IntoUiNode, thickness: impl IntoVar<UnderlineThickness>, style: impl IntoVar<LineStyle>) -> UiNode {
771    let child = with_context_var(child, IME_UNDERLINE_THICKNESS_VAR, thickness);
772    with_context_var(child, IME_UNDERLINE_STYLE_VAR, style)
773}
774
775/// Text spacing properties.
776///
777/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
778///
779/// See also [`ParagraphMix<P>`] for paragraph spacing.
780///
781/// [`Text!`]: struct@crate::Text
782#[widget_mixin]
783pub struct TextSpacingMix<P>(P);
784
785context_var! {
786    /// Text line height of [`Text!`] spans.
787    ///
788    /// [`Text!`]: struct@crate::Text
789    pub static LINE_HEIGHT_VAR: LineHeight = LineHeight::Default;
790
791    /// Extra spacing in between lines of [`Text!`] spans.
792    ///
793    /// [`Text!`]: struct@crate::Text
794    pub static LINE_SPACING_VAR: LineSpacing = LineSpacing::Default;
795
796    /// Extra letter spacing of [`Text!`] spans.
797    ///
798    /// [`Text!`]: struct@crate::Text
799    pub static LETTER_SPACING_VAR: LetterSpacing = LetterSpacing::Default;
800
801    /// Extra word spacing of [`Text!`] spans.
802    ///
803    /// [`Text!`]: struct@crate::Text
804    pub static WORD_SPACING_VAR: WordSpacing = WordSpacing::Default;
805
806    /// Length of the `TAB` space.
807    pub static TAB_LENGTH_VAR: TabLength = 400.pct();
808}
809
810impl TextSpacingMix<()> {
811    /// Insert context variables used by properties in this mixin.
812    pub fn context_vars_set(set: &mut ContextValueSet) {
813        set.insert(&LINE_HEIGHT_VAR);
814        set.insert(&LINE_SPACING_VAR);
815        set.insert(&LETTER_SPACING_VAR);
816        set.insert(&WORD_SPACING_VAR);
817        set.insert(&TAB_LENGTH_VAR);
818        set.insert(&PARAGRAPH_INDENT_VAR);
819    }
820}
821
822/// Height of each text line.
823///
824/// The [`Default`] value is computed from the font metrics, `ascent - descent + line_gap`, this is
825/// usually similar to `1.2.em()`. Relative values are computed from the default value, so `200.pct()` is double
826/// the default line height.
827///
828/// The text is vertically centered inside the height.
829///
830/// [`Default`]: Length::Default
831///
832/// Sets the [`LINE_HEIGHT_VAR`].
833#[property(CONTEXT, default(LINE_HEIGHT_VAR), widget_impl(TextSpacingMix<P>))]
834pub fn line_height(child: impl IntoUiNode, height: impl IntoVar<LineHeight>) -> UiNode {
835    with_context_var(child, LINE_HEIGHT_VAR, height)
836}
837
838/// Extra spacing added in between text letters.
839///
840/// Letter spacing is computed using the font data, this unit represents
841/// extra space added to the computed spacing.
842///
843/// A "letter" is a character glyph cluster, e.g.: `a`, `â`, `1`, `-`, `漢`.
844///
845/// The [`Default`] value signals that letter spacing can be tweaked when text *justification* is enabled, all other
846/// values disable automatic adjustments for justification inside words.
847///
848/// Relative values are computed from the length of the space `' '` character.
849///
850/// [`Default`]: Length::Default
851///
852/// This property sets the [`LETTER_SPACING_VAR`] context var that affects all inner texts.
853#[property(CONTEXT, default(LETTER_SPACING_VAR), widget_impl(TextSpacingMix<P>))]
854pub fn letter_spacing(child: impl IntoUiNode, extra: impl IntoVar<LetterSpacing>) -> UiNode {
855    with_context_var(child, LETTER_SPACING_VAR, extra)
856}
857
858/// Extra spacing in-between text lines.
859///
860/// The [`Default`] value is zero. Relative values are calculated from the [`LineHeight`], so `50.pct()` is half
861/// the computed line height. If the text only has one line this property is not used.
862///
863/// [`Default`]: Length::Default
864///
865/// Sets the [`LINE_SPACING_VAR`].
866///
867/// [`LineHeight`]: zng_ext_font::LineHeight
868#[property(CONTEXT, default(LINE_SPACING_VAR), widget_impl(TextSpacingMix<P>))]
869pub fn line_spacing(child: impl IntoUiNode, extra: impl IntoVar<LineSpacing>) -> UiNode {
870    with_context_var(child, LINE_SPACING_VAR, extra)
871}
872
873/// Extra spacing added to the Unicode `U+0020 SPACE` character.
874///
875/// Word spacing is done using the space character "advance" as defined in the font,
876/// this unit represents extra spacing added to that default spacing.
877///
878/// A "word" is the sequence of characters in-between space characters. This extra
879/// spacing is applied per space character not per word, if there are three spaces between words
880/// the extra spacing is applied thrice. Usually the number of spaces between words is collapsed to one,
881/// see [`WhiteSpace`], resulting in only one extra spacing.
882///
883/// The [`Default`] value signals that word spacing can be tweaked when text *justification* is enabled, all other
884/// values disable automatic adjustments for justification. Relative values are computed from the length of the space `' '` character,
885/// so a word spacing of `100.pct()` visually adds *another* space in between words.
886///
887/// [`Default`]: Length::Default
888///
889/// This property sets the [`WORD_SPACING_VAR`] context var that affects all inner widgets.
890///
891/// [`WhiteSpace`]: zng_ext_font::WhiteSpace
892#[property(CONTEXT, default(WORD_SPACING_VAR), widget_impl(TextSpacingMix<P>))]
893pub fn word_spacing(child: impl IntoUiNode, extra: impl IntoVar<WordSpacing>) -> UiNode {
894    with_context_var(child, WORD_SPACING_VAR, extra)
895}
896
897/// Length of the TAB character space, relative to the normal space advance.
898///
899/// Is set to `400.pct()` by default, so 4 times a space.
900///
901/// Sets the [`TAB_LENGTH_VAR`].
902#[property(CONTEXT, default(TAB_LENGTH_VAR), widget_impl(TextSpacingMix<P>))]
903pub fn tab_length(child: impl IntoUiNode, length: impl IntoVar<TabLength>) -> UiNode {
904    with_context_var(child, TAB_LENGTH_VAR, length)
905}
906
907/// Text transform properties.
908///
909/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
910///
911/// [`Text!`]: struct@crate::Text
912#[widget_mixin]
913pub struct TextTransformMix<P>(P);
914
915context_var! {
916    /// Text white space transform of [`Text!`] spans.
917    ///
918    /// [`Text!`]: struct@crate::Text
919    pub static WHITE_SPACE_VAR: WhiteSpace = WhiteSpace::Preserve;
920
921    /// Text transformation function applied to [`Text!`] spans.
922    ///
923    /// [`Text!`]: struct@crate::Text
924    pub static TEXT_TRANSFORM_VAR: TextTransformFn = TextTransformFn::None;
925}
926
927impl TextTransformMix<()> {
928    /// Insert context variables used by properties in this mixin.
929    pub fn context_vars_set(set: &mut ContextValueSet) {
930        set.insert(&WHITE_SPACE_VAR);
931        set.insert(&TEXT_TRANSFORM_VAR);
932    }
933}
934
935/// Text white space transform.
936///
937/// Can be used to collapse a sequence of spaces into a single one, or to ignore line-breaks.
938/// Is [`WhiteSpace::Preserve`] by default.
939///
940/// This property is not applied when the text is [`txt_editable`].
941///
942/// Sets the [`WHITE_SPACE_VAR`].
943///
944/// [`txt_editable`]: fn@txt_editable
945/// [`WhiteSpace::Preserve`]: zng_ext_font::WhiteSpace::Preserve
946#[property(CONTEXT, default(WHITE_SPACE_VAR), widget_impl(TextTransformMix<P>))]
947pub fn white_space(child: impl IntoUiNode, transform: impl IntoVar<WhiteSpace>) -> UiNode {
948    with_context_var(child, WHITE_SPACE_VAR, transform)
949}
950
951/// Text transform, character replacement applied to the text before it is processed by the text widget.
952///
953/// This property is not applied when the text is [`txt_editable`].
954///
955/// Sets the [`TEXT_TRANSFORM_VAR`].
956///  
957/// [`txt_editable`]: fn@txt_editable
958#[property(CONTEXT, default(TEXT_TRANSFORM_VAR), widget_impl(TextTransformMix<P>))]
959pub fn txt_transform(child: impl IntoUiNode, transform: impl IntoVar<TextTransformFn>) -> UiNode {
960    with_context_var(child, TEXT_TRANSFORM_VAR, transform)
961}
962
963/// Language and text direction properties.
964///
965/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
966///
967/// [`Text!`]: struct@crate::Text
968#[widget_mixin]
969pub struct LangMix<P>(P);
970
971/// Sets the text language and script for the widget and descendants.
972///
973/// This property affects all texts inside the widget and the layout direction.
974///
975/// Sets the [`LANG_VAR`] and [`DIRECTION_VAR`] context vars and the [`LayoutMetrics::direction`].
976/// Also sets the [`access::lang`] when accessibility is enabled.
977///
978/// [`access::lang`]: fn@zng_wgt_access::lang
979/// [`LANG_VAR`]: zng_ext_l10n::LANG_VAR
980/// [`DIRECTION_VAR`]: zng_wgt::prelude::DIRECTION_VAR
981/// [`LayoutMetrics::direction`]: zng_wgt::prelude::LayoutMetrics::direction
982#[property(CONTEXT, default(LANG_VAR), widget_impl(LangMix<P>))]
983pub fn lang(child: impl IntoUiNode, lang: impl IntoVar<Langs>) -> UiNode {
984    let lang = lang.into_var();
985    let child = direction(child, lang.map(|l| l.best().direction()));
986    let child = zng_wgt_access::lang(child, lang.map(|l| l.best().clone()));
987    with_context_var(child, LANG_VAR, lang)
988}
989
990/// Sets the layout direction used in the layout of the widget and descendants.
991///
992/// Note that the [`lang`] property already sets the direction, this property can be used to directly override the direction.
993///
994/// Sets the [`DIRECTION_VAR`] context var and the [`LayoutMetrics::direction`].
995///
996/// [`lang`]: fn@lang
997///
998/// [`DIRECTION_VAR`]: zng_wgt::prelude::DIRECTION_VAR
999/// [`LayoutMetrics::direction`]: zng_wgt::prelude::LayoutMetrics::direction
1000#[property(CONTEXT+1, default(DIRECTION_VAR), widget_impl(LangMix<P>))]
1001pub fn direction(child: impl IntoUiNode, direction: impl IntoVar<LayoutDirection>) -> UiNode {
1002    let child = match_node(child, |child, op| match op {
1003        UiNodeOp::Init => {
1004            WIDGET.sub_var_layout(&DIRECTION_VAR);
1005        }
1006        UiNodeOp::Measure { wm, desired_size } => {
1007            *desired_size = LAYOUT.with_direction(DIRECTION_VAR.get(), || child.measure(wm));
1008        }
1009        UiNodeOp::Layout { wl, final_size } => *final_size = LAYOUT.with_direction(DIRECTION_VAR.get(), || child.layout(wl)),
1010        _ => {}
1011    });
1012    with_context_var(child, DIRECTION_VAR, direction)
1013}
1014
1015impl LangMix<()> {
1016    /// Insert context variables used by properties in this mixin.
1017    pub fn context_vars_set(set: &mut ContextValueSet) {
1018        set.insert(&LANG_VAR);
1019        set.insert(&DIRECTION_VAR);
1020    }
1021}
1022
1023/// Advanced font config, features, kerning, variations and more.
1024///
1025/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
1026///
1027/// [`Text!`]: struct@crate::Text
1028#[widget_mixin]
1029pub struct FontFeaturesMix<P>(P);
1030
1031context_var! {
1032    /// Font features of [`Text!`] spans.
1033    ///
1034    /// [`Text!`]: struct@crate::Text
1035    pub static FONT_FEATURES_VAR: FontFeatures = FontFeatures::new();
1036
1037    /// Font variations of [`Text!`] spans.
1038    ///
1039    /// [`Text!`]: struct@crate::Text
1040    pub static FONT_VARIATIONS_VAR: FontVariations = FontVariations::new();
1041}
1042
1043impl FontFeaturesMix<()> {
1044    /// Insert context variables used by properties in this mixin.
1045    pub fn context_vars_set(set: &mut ContextValueSet) {
1046        set.insert(&FONT_FEATURES_VAR);
1047        set.insert(&FONT_VARIATIONS_VAR);
1048    }
1049}
1050
1051/// Includes the font variation config in the widget context.
1052///
1053/// The variation `name` is set for the [`FONT_VARIATIONS_VAR`] in this context, variations already set in the parent
1054/// context that are not the same `name` are also included.
1055pub fn with_font_variation(child: impl IntoUiNode, name: FontVariationName, value: impl IntoVar<f32>) -> UiNode {
1056    with_context_var(
1057        child,
1058        FONT_VARIATIONS_VAR,
1059        merge_var!(FONT_VARIATIONS_VAR, value.into_var(), move |variations, value| {
1060            let mut variations = variations.clone();
1061            variations.insert(name, *value);
1062            variations
1063        }),
1064    )
1065}
1066
1067/// Include the font feature config in the widget context.
1068///
1069/// The modifications done in `set_feature` are visible only in the [`FONT_FEATURES_VAR`] in this context, and features
1070/// already set in a parent context are included.
1071pub fn with_font_feature<C, S, V, D>(child: C, state: V, set_feature: D) -> UiNode
1072where
1073    C: IntoUiNode,
1074    S: VarValue,
1075    V: IntoVar<S>,
1076    D: FnMut(&mut FontFeatures, S) -> S + Send + 'static,
1077{
1078    let mut set_feature = set_feature;
1079    with_context_var(
1080        child,
1081        FONT_FEATURES_VAR,
1082        merge_var!(FONT_FEATURES_VAR, state.into_var(), move |features, state| {
1083            let mut features = features.clone();
1084            set_feature(&mut features, state.clone());
1085            features
1086        }),
1087    )
1088}
1089
1090/// Sets font variations.
1091///
1092/// **Note:** This property fully replaces the font variations for the widget and descendants, use [`with_font_variation`]
1093/// to create a property that sets a variation but retains others from the context.
1094#[property(CONTEXT, default(FONT_VARIATIONS_VAR), widget_impl(FontFeaturesMix<P>))]
1095pub fn font_variations(child: impl IntoUiNode, variations: impl IntoVar<FontVariations>) -> UiNode {
1096    with_context_var(child, FONT_VARIATIONS_VAR, variations)
1097}
1098
1099/// Sets font features.
1100///
1101/// **Note:** This property fully replaces the font variations for the widget and descendants, use [`with_font_variation`]
1102/// to create a property that sets a variation but retains others from the context.
1103#[property(CONTEXT, default(FONT_FEATURES_VAR), widget_impl(FontFeaturesMix<P>))]
1104pub fn font_features(child: impl IntoUiNode, features: impl IntoVar<FontFeatures>) -> UiNode {
1105    with_context_var(child, FONT_FEATURES_VAR, features)
1106}
1107
1108/// Sets the font kerning feature.
1109#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1110pub fn font_kerning(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1111    with_font_feature(child, state, |f, s| f.kerning().set(s))
1112}
1113
1114/// Sets the font common ligatures features.
1115#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1116pub fn font_common_lig(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1117    with_font_feature(child, state, |f, s| f.common_lig().set(s))
1118}
1119
1120/// Sets the font discretionary ligatures feature.
1121#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1122pub fn font_discretionary_lig(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1123    with_font_feature(child, state, |f, s| f.discretionary_lig().set(s))
1124}
1125
1126/// Sets the font historical ligatures feature.
1127#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1128pub fn font_historical_lig(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1129    with_font_feature(child, state, |f, s| f.historical_lig().set(s))
1130}
1131
1132/// Sets the font contextual alternatives feature.
1133#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1134pub fn font_contextual_alt(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1135    with_font_feature(child, state, |f, s| f.contextual_alt().set(s))
1136}
1137
1138/// Sets the font capital variant features.
1139#[property(CONTEXT, default(CapsVariant::Auto), widget_impl(FontFeaturesMix<P>))]
1140pub fn font_caps(child: impl IntoUiNode, state: impl IntoVar<CapsVariant>) -> UiNode {
1141    with_font_feature(child, state, |f, s| f.caps().set(s))
1142}
1143
1144/// Sets the font numeric variant features.
1145#[property(CONTEXT, default(NumVariant::Auto), widget_impl(FontFeaturesMix<P>))]
1146pub fn font_numeric(child: impl IntoUiNode, state: impl IntoVar<NumVariant>) -> UiNode {
1147    with_font_feature(child, state, |f, s| f.numeric().set(s))
1148}
1149
1150/// Sets the font numeric spacing features.
1151#[property(CONTEXT, default(NumSpacing::Auto), widget_impl(FontFeaturesMix<P>))]
1152pub fn font_num_spacing(child: impl IntoUiNode, state: impl IntoVar<NumSpacing>) -> UiNode {
1153    with_font_feature(child, state, |f, s| f.num_spacing().set(s))
1154}
1155
1156/// Sets the font numeric fraction features.
1157#[property(CONTEXT, default(NumFraction::Auto), widget_impl(FontFeaturesMix<P>))]
1158pub fn font_num_fraction(child: impl IntoUiNode, state: impl IntoVar<NumFraction>) -> UiNode {
1159    with_font_feature(child, state, |f, s| f.num_fraction().set(s))
1160}
1161
1162/// Sets the font swash features.
1163#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1164pub fn font_swash(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1165    with_font_feature(child, state, |f, s| f.swash().set(s))
1166}
1167
1168/// Sets the font stylistic alternative feature.
1169#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1170pub fn font_stylistic(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1171    with_font_feature(child, state, |f, s| f.stylistic().set(s))
1172}
1173
1174/// Sets the font historical forms alternative feature.
1175#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1176pub fn font_historical_forms(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1177    with_font_feature(child, state, |f, s| f.historical_forms().set(s))
1178}
1179
1180/// Sets the font ornaments alternative feature.
1181#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1182pub fn font_ornaments(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1183    with_font_feature(child, state, |f, s| f.ornaments().set(s))
1184}
1185
1186/// Sets the font annotation alternative feature.
1187#[property(CONTEXT, default(FontFeatureState::auto()), widget_impl(FontFeaturesMix<P>))]
1188pub fn font_annotation(child: impl IntoUiNode, state: impl IntoVar<FontFeatureState>) -> UiNode {
1189    with_font_feature(child, state, |f, s| f.annotation().set(s))
1190}
1191
1192/// Sets the font stylistic set alternative feature.
1193#[property(CONTEXT, default(FontStyleSet::auto()), widget_impl(FontFeaturesMix<P>))]
1194pub fn font_style_set(child: impl IntoUiNode, state: impl IntoVar<FontStyleSet>) -> UiNode {
1195    with_font_feature(child, state, |f, s| f.style_set().set(s))
1196}
1197
1198/// Sets the font character variant alternative feature.
1199#[property(CONTEXT, default(CharVariant::auto()), widget_impl(FontFeaturesMix<P>))]
1200pub fn font_char_variant(child: impl IntoUiNode, state: impl IntoVar<CharVariant>) -> UiNode {
1201    with_font_feature(child, state, |f, s| f.char_variant().set(s))
1202}
1203
1204/// Sets the font sub/super script position alternative feature.
1205#[property(CONTEXT, default(FontPosition::Auto), widget_impl(FontFeaturesMix<P>))]
1206pub fn font_position(child: impl IntoUiNode, state: impl IntoVar<FontPosition>) -> UiNode {
1207    with_font_feature(child, state, |f, s| f.position().set(s))
1208}
1209
1210/// Sets the Japanese logographic set.
1211#[property(CONTEXT, default(JpVariant::Auto), widget_impl(FontFeaturesMix<P>))]
1212pub fn font_jp_variant(child: impl IntoUiNode, state: impl IntoVar<JpVariant>) -> UiNode {
1213    with_font_feature(child, state, |f, s| f.jp_variant().set(s))
1214}
1215
1216/// Sets the Chinese logographic set.
1217#[property(CONTEXT, default(CnVariant::Auto), widget_impl(FontFeaturesMix<P>))]
1218pub fn font_cn_variant(child: impl IntoUiNode, state: impl IntoVar<CnVariant>) -> UiNode {
1219    with_font_feature(child, state, |f, s| f.cn_variant().set(s))
1220}
1221
1222/// Sets the East Asian figure width.
1223#[property(CONTEXT, default(EastAsianWidth::Auto), widget_impl(FontFeaturesMix<P>))]
1224pub fn font_ea_width(child: impl IntoUiNode, state: impl IntoVar<EastAsianWidth>) -> UiNode {
1225    with_font_feature(child, state, |f, s| f.ea_width().set(s))
1226}
1227
1228/// Text edit properties.
1229///
1230/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
1231///
1232/// [`Text!`]: struct@crate::Text
1233#[widget_mixin]
1234pub struct TextEditMix<P>(P);
1235
1236context_var! {
1237    /// Text is editable.
1238    pub static TEXT_EDITABLE_VAR: bool = false;
1239
1240    /// Text is selectable.
1241    pub static TEXT_SELECTABLE_VAR: bool = false;
1242    /// Text only starts selection from mouse or touch if the Alt modifier is pressed.
1243    pub static TEXT_SELECTABLE_ALT_ONLY_VAR: bool = false;
1244
1245    /// Accepts `'\t'` input when editable.
1246    pub static ACCEPTS_TAB_VAR: bool = false;
1247
1248    /// Accepts `'\n'` input when editable.
1249    pub static ACCEPTS_ENTER_VAR: bool = false;
1250
1251    /// Caret color, inherits from [`FONT_COLOR_VAR`].
1252    pub static CARET_COLOR_VAR: Rgba = FONT_COLOR_VAR;
1253
1254    /// Interactive caret visual.
1255    pub static INTERACTIVE_CARET_VISUAL_VAR: WidgetFn<CaretShape> = wgt_fn!(|s| super::node::default_interactive_caret_visual(s));
1256
1257    /// Interactive caret mode.
1258    pub static INTERACTIVE_CARET_VAR: InteractiveCaretMode = InteractiveCaretMode::default();
1259
1260    /// Selection background color.
1261    pub static SELECTION_COLOR_VAR: Rgba = colors::AZURE.with_alpha(30.pct());
1262
1263    /// If text parse updated for every text change.
1264    pub static TXT_PARSE_LIVE_VAR: bool = true;
1265
1266    /// Debounce time for change stop.
1267    pub static CHANGE_STOP_DELAY_VAR: Duration = 1.secs();
1268
1269    /// Auto selection on keyboard focus.
1270    pub static AUTO_SELECTION_VAR: AutoSelection = AutoSelection::default();
1271
1272    /// Maximum number of characters that can be input.
1273    ///
1274    /// Zero means no limit. Is zero by default.
1275    pub static MAX_CHARS_COUNT_VAR: usize = 0;
1276
1277    /// Replacement character used when obscuring text.
1278    pub static OBSCURING_CHAR_VAR: char = '•';
1279
1280    /// If text characters are replaced with [`OBSCURING_CHAR_VAR`] for rendering.
1281    pub static OBSCURE_TXT_VAR: bool = false;
1282
1283    pub(super) static TXT_PARSE_PENDING_VAR: bool = false;
1284}
1285
1286impl TextEditMix<()> {
1287    /// Insert context variables used by properties in this mixin.
1288    pub fn context_vars_set(set: &mut ContextValueSet) {
1289        set.insert(&TEXT_EDITABLE_VAR);
1290        set.insert(&TEXT_SELECTABLE_VAR);
1291        set.insert(&TEXT_SELECTABLE_ALT_ONLY_VAR);
1292        set.insert(&ACCEPTS_ENTER_VAR);
1293        set.insert(&CARET_COLOR_VAR);
1294        set.insert(&INTERACTIVE_CARET_VISUAL_VAR);
1295        set.insert(&INTERACTIVE_CARET_VAR);
1296        set.insert(&SELECTION_COLOR_VAR);
1297        set.insert(&TXT_PARSE_LIVE_VAR);
1298        set.insert(&CHANGE_STOP_DELAY_VAR);
1299        set.insert(&AUTO_SELECTION_VAR);
1300        set.insert(&MAX_CHARS_COUNT_VAR);
1301        set.insert(&OBSCURING_CHAR_VAR);
1302        set.insert(&OBSCURE_TXT_VAR);
1303    }
1304}
1305
1306/// Defines the position of an interactive caret in relation to the selection.
1307///
1308/// See [`interactive_caret_visual`](fn@interactive_caret_visual) for more details.
1309#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1310pub enum CaretShape {
1311    /// Caret defines the selection start in LTR and end in RTL text.
1312    SelectionLeft,
1313    /// Caret defines the selection end in LTR and start in RTL text.
1314    SelectionRight,
1315    /// Caret defines the insert point, when there is no selection.
1316    Insert,
1317}
1318impl fmt::Debug for CaretShape {
1319    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1320        if f.alternate() {
1321            write!(f, "CaretShape::")?;
1322        }
1323        match self {
1324            Self::SelectionLeft => write!(f, "SelectionLeft"),
1325            Self::SelectionRight => write!(f, "SelectionRight"),
1326            Self::Insert => write!(f, "Insert"),
1327        }
1328    }
1329}
1330
1331/// Defines when the interactive carets are used.
1332#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1333pub enum InteractiveCaretMode {
1334    /// Uses interactive carets only for touch selections, uses non-interactive caret for other selections.
1335    #[default]
1336    TouchOnly,
1337    /// Uses interactive carets for all selections.
1338    Enabled,
1339    /// Uses non-interactive carets for all selections.
1340    Disabled,
1341}
1342impl fmt::Debug for InteractiveCaretMode {
1343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1344        if f.alternate() {
1345            write!(f, "InteractiveCaretMode::")?;
1346        }
1347        match self {
1348            Self::TouchOnly => write!(f, "TouchOnly"),
1349            Self::Enabled => write!(f, "Enabled"),
1350            Self::Disabled => write!(f, "Disabled"),
1351        }
1352    }
1353}
1354impl_from_and_into_var! {
1355    fn from(enabled: bool) -> InteractiveCaretMode {
1356        if enabled {
1357            InteractiveCaretMode::Enabled
1358        } else {
1359            InteractiveCaretMode::Disabled
1360        }
1361    }
1362}
1363
1364/// Enable text caret, input and makes the widget focusable.
1365///
1366/// If the `txt` variable is read-only, this is ignored, if the var is writeable this
1367/// enables text input and modifies the variable.
1368///
1369/// Sets the [`TEXT_EDITABLE_VAR`].
1370#[property(CONTEXT, default(TEXT_EDITABLE_VAR), widget_impl(TextEditMix<P>))]
1371pub fn txt_editable(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1372    with_context_var(child, TEXT_EDITABLE_VAR, enabled)
1373}
1374
1375/// Enable text selection, copy and makes the widget focusable.
1376///
1377/// Note that if the text widget subscribes to mouse or touch events the selection gestures will interfere with those events,
1378/// you can enable [`txt_selectable_alt_only`] so that pointer selection gestures only start when the Alt keyboard modifier is pressed.
1379///
1380/// Sets the [`TEXT_SELECTABLE_VAR`].
1381///
1382/// [`txt_selectable_alt_only`]: fn@txt_selectable_alt_only
1383#[property(CONTEXT, default(TEXT_SELECTABLE_VAR), widget_impl(TextEditMix<P>))]
1384pub fn txt_selectable(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1385    with_context_var(child, TEXT_SELECTABLE_VAR, enabled)
1386}
1387
1388/// Only start mouse and touch selections from this widget when the Alt keyboard modifier is pressed.
1389///
1390/// Note that this property does not enable text selection, [`txt_selectable`] must be enabled on the widget or parent.
1391///
1392/// This property is ignored if the text is also editable. Selections started from sibling widgets in rich text also expand
1393/// inside this widget normally, this property only applies to selection started from this widget.
1394///
1395/// Sets the [`TEXT_SELECTABLE_ALT_ONLY_VAR`].
1396///
1397/// [`txt_selectable`]: fn@txt_selectable
1398#[property(CONTEXT, default(TEXT_SELECTABLE_ALT_ONLY_VAR), widget_impl(TextEditMix<P>))]
1399pub fn txt_selectable_alt_only(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1400    with_context_var(child, TEXT_SELECTABLE_ALT_ONLY_VAR, enabled)
1401}
1402
1403/// If the `'\t'` character is inserted when tab is pressed and the text is editable.
1404///
1405/// If not enabled or the text is not editable, then pressing tab moves the focus like normal.
1406///
1407/// Sets the [`ACCEPTS_TAB_VAR`].
1408#[property(CONTEXT, default(ACCEPTS_TAB_VAR), widget_impl(TextEditMix<P>))]
1409pub fn accepts_tab(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1410    with_context_var(child, ACCEPTS_TAB_VAR, enabled)
1411}
1412
1413/// If the `'\n'` character is inserted when enter is pressed and the text is editable.
1414///
1415/// Sets the [`ACCEPTS_ENTER_VAR`].
1416#[property(CONTEXT, default(ACCEPTS_ENTER_VAR), widget_impl(TextEditMix<P>))]
1417pub fn accepts_enter(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1418    with_context_var(child, ACCEPTS_ENTER_VAR, enabled)
1419}
1420
1421/// Defines the color of the non-interactive caret.
1422///
1423/// Sets the [`CARET_COLOR_VAR`].
1424#[property(CONTEXT, default(CARET_COLOR_VAR), widget_impl(TextEditMix<P>))]
1425pub fn caret_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
1426    with_context_var(child, CARET_COLOR_VAR, color)
1427}
1428
1429/// Defines custom caret visual for interactive caret.
1430///
1431/// The `visual` node becomes the content of a [layered widget] at the `ADORNER+1` layer, the text widget context is
1432/// propagated so contextual variables and value work seamless inside the node.
1433///
1434/// The `visual` node must set one special value during layout, the [`set_interactive_caret_spot`] must be called to
1435/// set the offset to the middle of the caret line in the visual inner-bounds, this is used to position the caret.
1436///
1437/// Sets the [`INTERACTIVE_CARET_VISUAL_VAR`].
1438///
1439/// [layered widget]: zng_wgt_layer
1440/// [`set_interactive_caret_spot`]: super::node::set_interactive_caret_spot
1441#[property(CONTEXT, default(INTERACTIVE_CARET_VISUAL_VAR), widget_impl(TextEditMix<P>))]
1442pub fn interactive_caret_visual(child: impl IntoUiNode, visual: impl IntoVar<WidgetFn<CaretShape>>) -> UiNode {
1443    with_context_var(child, INTERACTIVE_CARET_VISUAL_VAR, visual)
1444}
1445
1446/// Defines when the interactive carets are used.
1447///
1448/// By default only uses interactive carets for touch selections.
1449#[property(CONTEXT, default(INTERACTIVE_CARET_VAR), widget_impl(TextEditMix<P>))]
1450pub fn interactive_caret(child: impl IntoUiNode, mode: impl IntoVar<InteractiveCaretMode>) -> UiNode {
1451    with_context_var(child, INTERACTIVE_CARET_VAR, mode)
1452}
1453
1454/// Sets the [`SELECTION_COLOR_VAR`].
1455#[property(CONTEXT, default(SELECTION_COLOR_VAR), widget_impl(TextEditMix<P>))]
1456pub fn selection_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
1457    with_context_var(child, SELECTION_COLOR_VAR, color)
1458}
1459
1460/// If [`txt_parse`] tries to parse after any text change immediately.
1461///
1462/// This is enabled by default, if disabled the [`PARSE_CMD`] can be used to update pending parse.
1463///
1464/// This property sets the [`TXT_PARSE_LIVE_VAR`].
1465///
1466/// [`txt_parse`]: fn@super::txt_parse
1467/// [`PARSE_CMD`]: super::cmd::PARSE_CMD
1468#[property(CONTEXT, default(TXT_PARSE_LIVE_VAR), widget_impl(TextEditMix<P>))]
1469pub fn txt_parse_live(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1470    with_context_var(child, TXT_PARSE_LIVE_VAR, enabled)
1471}
1472
1473/// Disable live parsing and parse on change stop.
1474///
1475/// This property sets [`txt_parse_live`] and [`on_change_stop`] on the widget.
1476///
1477/// Consider increasing the [`change_stop_delay`] if the text can change after parse.
1478///
1479/// [`txt_parse_live`]: fn@txt_parse_live
1480/// [`on_change_stop`]: fn@on_change_stop
1481/// [`change_stop_delay`]: fn@change_stop_delay
1482#[property(EVENT, widget_impl(TextEditMix<P>))]
1483pub fn txt_parse_on_stop(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1484    let enabled = enabled.into_var();
1485    let child = txt_parse_live(child, enabled.map(|&b| !b));
1486    on_change_stop(
1487        child,
1488        hn!(|_| {
1489            if enabled.get() {
1490                super::cmd::PARSE_CMD.scoped(WIDGET.id()).notify();
1491            }
1492        }),
1493    )
1494}
1495
1496/// Maximum number of characters that can be input.
1497///
1498/// Zero means no limit. Is zero by default.
1499///
1500/// This property sets the [`MAX_CHARS_COUNT_VAR`].
1501#[property(CONTEXT, default(MAX_CHARS_COUNT_VAR), widget_impl(TextEditMix<P>))]
1502pub fn max_chars_count(child: impl IntoUiNode, max: impl IntoVar<usize>) -> UiNode {
1503    with_context_var(child, MAX_CHARS_COUNT_VAR, max)
1504}
1505
1506/// If text has changed but [`txt_parse`] has not tried to parse the new text yet.
1507///
1508/// This can only be `true` if [`txt_parse_live`] is `false`.
1509///
1510/// [`txt_parse`]: fn@super::txt_parse
1511/// [`txt_parse_live`]: fn@txt_parse_live
1512#[property(CONTEXT, default(false), widget_impl(TextEditMix<P>))]
1513pub fn is_parse_pending(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
1514    // reverse context, `txt_parse` sets `TXT_PARSE_PENDING_VAR`
1515    with_context_var(child, TXT_PARSE_PENDING_VAR, state)
1516}
1517
1518/// Called after the text changed and interaction has stopped.
1519///
1520/// The `handler` will be called after change and [`change_stop_delay`] elapses, or the widget loses focus,
1521/// or the [`Key::Enter`] is pressed and [`accepts_enter`] is `false`.
1522///
1523/// [`change_stop_delay`]: fn@change_stop_delay
1524/// [`accepts_enter`]: fn@accepts_enter
1525/// [`Key::Enter`]: zng_ext_input::keyboard::Key::Enter
1526#[property(EVENT, widget_impl(TextEditMix<P>))]
1527pub fn on_change_stop(child: impl IntoUiNode, handler: Handler<ChangeStopArgs>) -> UiNode {
1528    super::node::on_change_stop(child, handler)
1529}
1530
1531/// Debounce time for [`on_change_stop`].
1532///
1533/// After the text stops changing and `delay` is elapsed the change stop handled is called, even
1534/// if the widget is still focused.
1535///
1536/// Is `1.secs()` by default.
1537///
1538/// Sets [`CHANGE_STOP_DELAY_VAR`].
1539///
1540/// [`on_change_stop`]: fn@on_change_stop
1541#[property(CONTEXT, default(CHANGE_STOP_DELAY_VAR), widget_impl(TextEditMix<P>))]
1542pub fn change_stop_delay(child: impl IntoUiNode, delay: impl IntoVar<Duration>) -> UiNode {
1543    with_context_var(child, CHANGE_STOP_DELAY_VAR, delay)
1544}
1545
1546/// Auto-selection on focus change when the text is selectable.
1547///
1548/// If enabled on keyboard focus all text is selected and on blur any selection is cleared.
1549///
1550/// In rich text contexts applies to all sub-component texts.
1551#[property(CONTEXT, default(AUTO_SELECTION_VAR), widget_impl(TextEditMix<P>))]
1552pub fn auto_selection(child: impl IntoUiNode, mode: impl IntoVar<AutoSelection>) -> UiNode {
1553    with_context_var(child, AUTO_SELECTION_VAR, mode)
1554}
1555
1556/// Replacement character used when obscuring text.
1557///
1558/// When [`obscure_txt`] is enabled the text characters are replaced by this one.
1559///
1560/// [`obscure_txt`]: fn@obscure_txt
1561#[property(CONTEXT, default(OBSCURING_CHAR_VAR), widget_impl(TextEditMix<P>))]
1562pub fn obscuring_char(child: impl IntoUiNode, character: impl IntoVar<char>) -> UiNode {
1563    with_context_var(child, OBSCURING_CHAR_VAR, character)
1564}
1565
1566/// Enable typed text obscuring in render.
1567///
1568/// When enabled each text character is replaced with [`obscuring_char`], cut, copy and undo commands are disabled.
1569///
1570/// Note that the text variable is still **plain text** in memory, a memory dump while the widget is filled can leak
1571/// the password, this is a potential security problem shared by apps that accept typed passwords. To mitigate the problem
1572/// don't use automatic crash reports with memory dump, drop the widget and the text variable as soon as possible,
1573/// design the app to show the password widget last to minimize its lifetime.
1574///
1575/// [`obscuring_char`]: fn@obscuring_char
1576#[property(CONTEXT, default(OBSCURE_TXT_VAR), widget_impl(TextEditMix<P>))]
1577pub fn obscure_txt(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
1578    with_context_var(child, OBSCURE_TXT_VAR, enabled)
1579}
1580
1581bitflags! {
1582    /// Defines when text is auto-selected on focus change.
1583    ///
1584    /// In a rich text context applies across all sub component texts.
1585    #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
1586    pub struct AutoSelection: u8 {
1587        /// No auto-selection.
1588        const DISABLED = 0;
1589        /// Clear selection on blur if the widget is not the ALT return focus and is not the parent scope return focus.
1590        ///
1591        /// This is the default behavior.
1592        const CLEAR_ON_BLUR = 0b0000_0001;
1593        /// Select all on keyboard initiated focus.
1594        const ALL_ON_FOCUS_KEYBOARD = 0b0000_0010;
1595        /// Select all on pointer release if the pointer press event caused the focus to happen and it did not change
1596        /// the selection and there is no selection on release.
1597        const ALL_ON_FOCUS_POINTER = 0b0000_0100;
1598        /// All auto-selection features enabled.
1599        const ENABLED = 0b0000_0111;
1600    }
1601}
1602impl_from_and_into_var! {
1603    fn from(enabled: bool) -> AutoSelection {
1604        if enabled { AutoSelection::ENABLED } else { AutoSelection::DISABLED }
1605    }
1606}
1607impl Default for AutoSelection {
1608    fn default() -> Self {
1609        Self::CLEAR_ON_BLUR
1610    }
1611}
1612
1613/// Arguments for [`on_change_stop`].
1614///
1615/// [`on_change_stop`]: fn@on_change_stop
1616#[derive(Debug, Clone)]
1617#[non_exhaustive]
1618pub struct ChangeStopArgs {
1619    /// Event cause.
1620    pub cause: ChangeStopCause,
1621}
1622impl ChangeStopArgs {
1623    /// New args.
1624    pub fn new(cause: ChangeStopCause) -> Self {
1625        Self { cause }
1626    }
1627}
1628
1629/// Cause of an [`on_change_stop`].
1630///
1631/// [`on_change_stop`]: fn@on_change_stop
1632#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1633pub enum ChangeStopCause {
1634    /// The [`change_stop_delay`] elapsed.
1635    ///
1636    /// [`change_stop_delay`]: fn@change_stop_delay
1637    DelayElapsed,
1638    /// The [`Key::Enter`] was pressed and [`accepts_enter`] is `false`.
1639    ///
1640    /// [`Key::Enter`]: zng_ext_input::keyboard::Key::Enter
1641    /// [`accepts_enter`]: fn@accepts_enter
1642    Enter,
1643    /// The widget lost keyboard focus.
1644    Blur,
1645}
1646
1647/// Display info of edit caret position.
1648#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, serde::Serialize, serde::Deserialize)]
1649pub struct CaretStatus {
1650    index: usize,
1651    line: u32,
1652    column: u32,
1653}
1654impl CaretStatus {
1655    /// Status for text without caret.
1656    pub fn none() -> Self {
1657        Self {
1658            index: usize::MAX,
1659            line: 0,
1660            column: 0,
1661        }
1662    }
1663
1664    /// New position from char index and text.
1665    ///
1666    /// # Panics
1667    ///
1668    /// Panics if `index` is greater then `text` length.
1669    pub fn new(index: usize, text: &SegmentedText) -> Self {
1670        assert!(index <= text.text().len());
1671
1672        if text.text().is_empty() {
1673            Self { line: 1, column: 1, index }
1674        } else {
1675            let mut line = 1;
1676            let mut line_start = 0;
1677            for seg in text.segs() {
1678                if seg.end > index {
1679                    break;
1680                }
1681                if let TextSegmentKind::LineBreak = seg.kind {
1682                    line += 1;
1683                    line_start = seg.end;
1684                }
1685            }
1686
1687            let column = text.text()[line_start..index].chars().count() + 1;
1688
1689            Self {
1690                line,
1691                column: column as _,
1692                index,
1693            }
1694        }
1695    }
1696
1697    /// Char index on the text string, starts a 0, can be the length of the text.
1698    pub fn index(&self) -> Option<usize> {
1699        match self.index {
1700            usize::MAX => None,
1701            i => Some(i),
1702        }
1703    }
1704
1705    /// Display line, starts at 1.
1706    ///
1707    /// Note that this does not count soft line breaks (wrapped lines), this is the actual text line.
1708    pub fn line(&self) -> Option<NonZeroU32> {
1709        NonZeroU32::new(self.line)
1710    }
1711
1712    /// Display column, starts at 1.
1713    ///
1714    /// This is the char count from the start of the text line to the index.
1715    pub fn column(&self) -> Option<NonZeroU32> {
1716        NonZeroU32::new(self.column)
1717    }
1718}
1719impl fmt::Display for CaretStatus {
1720    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1721        if self.index().is_some() {
1722            write!(f, "Ln {}, Col {}", self.line, self.column)
1723        } else {
1724            Ok(())
1725        }
1726    }
1727}
1728
1729/// Represents the number of lines and number of wrap lines in a text.
1730#[derive(Debug, Clone, PartialEq, Eq)]
1731pub enum LinesWrapCount {
1732    /// No line wrap.
1733    ///
1734    /// The associated value is the number of lines.
1735    NoWrap(usize),
1736    /// Some text lines have more than one wrap line.
1737    ///
1738    /// The associated value is a vec of wrap-line count for each text line, is `1` for lines that don't wrap.
1739    Wrap(Vec<u32>),
1740}
1741impl LinesWrapCount {
1742    /// Gets the number of text lines.
1743    pub fn lines_len(&self) -> usize {
1744        match self {
1745            Self::NoWrap(l) => *l,
1746            Self::Wrap(lns) => lns.len(),
1747        }
1748    }
1749}
1750
1751/// Text paragraph properties.
1752///
1753/// The base [`Text!`] widget only implements minimal support for paragraphs, rich text widgets will probably
1754/// segment paragraphs into sub widgets and may ignore [`PARAGRAPH_BREAK_VAR`] depending on the source text format.
1755///
1756/// [`Text!`]: struct@crate::Text
1757#[widget_mixin]
1758pub struct ParagraphMix<P>(P);
1759
1760context_var! {
1761    /// Defines how the text is split in paragraphs.
1762    ///
1763    /// Is `None` by default.
1764    pub static PARAGRAPH_BREAK_VAR: ParagraphBreak = ParagraphBreak::default();
1765
1766    /// Extra paragraph spacing of text blocks.
1767    pub static PARAGRAPH_SPACING_VAR: ParagraphSpacing = 1.em();
1768
1769    /// Spacing applied at start of paragraphs.
1770    pub static PARAGRAPH_INDENT_VAR: Indentation = Indentation::default();
1771}
1772
1773impl ParagraphMix<()> {
1774    /// Insert context variables used by properties in this mixin.
1775    pub fn context_vars_set(set: &mut ContextValueSet) {
1776        set.insert(&PARAGRAPH_BREAK_VAR);
1777        set.insert(&PARAGRAPH_SPACING_VAR);
1778        set.insert(&PARAGRAPH_INDENT_VAR);
1779    }
1780}
1781
1782/// Defines paragraphs in the text.
1783///
1784/// In the base `Text!` widget this defines how [`paragraph_spacing`] and [`paragraph_indent`] are applied.
1785///
1786/// Other rich text widgets usually segment paragraphs into widgets and may ignore this property, depending on
1787/// the source text format.
1788///
1789/// Sets the [`PARAGRAPH_BREAK_VAR`].
1790#[property(CONTEXT, default(PARAGRAPH_BREAK_VAR), widget_impl(ParagraphMix<P>))]
1791pub fn paragraph_break(child: impl IntoUiNode, mode: impl IntoVar<ParagraphBreak>) -> UiNode {
1792    with_context_var(child, PARAGRAPH_BREAK_VAR, mode)
1793}
1794
1795/// Extra spacing in-between paragraphs.
1796///
1797/// The default value is `1.em()`. Note that [`paragraph_break`] defines the entire text as a single paragraph
1798/// by default, so this will not be applied by default on the base [`Text!`] widget.
1799///
1800/// Sets the [`PARAGRAPH_SPACING_VAR`].
1801///
1802/// [`Text!`]: struct@crate::Text
1803/// [`paragraph_break`]: fn@paragraph_break
1804#[property(CONTEXT, default(PARAGRAPH_SPACING_VAR), widget_impl(ParagraphMix<P>))]
1805pub fn paragraph_spacing(child: impl IntoUiNode, extra: impl IntoVar<ParagraphSpacing>) -> UiNode {
1806    with_context_var(child, PARAGRAPH_SPACING_VAR, extra)
1807}
1808
1809/// Extra spacing added at the start of lines in a paragraph.
1810///
1811/// This can be set to a width `Length` to insert spacing at the start of each first line,
1812/// or it can be set to `(Length, true)` to hang all lines except the first.
1813///
1814/// See [`paragraph_break`] for how to define paragraphs.
1815///
1816/// Sets the [`PARAGRAPH_INDENT_VAR`].
1817///
1818/// [`paragraph_break`]: fn@paragraph_break
1819#[property(CONTEXT, default(PARAGRAPH_INDENT_VAR), widget_impl(ParagraphMix<P>))]
1820pub fn paragraph_indent(child: impl IntoUiNode, indent: impl IntoVar<Indentation>) -> UiNode {
1821    with_context_var(child, PARAGRAPH_INDENT_VAR, indent)
1822}
1823
1824/// Selection toolbar properties.
1825#[widget_mixin]
1826pub struct SelectionToolbarMix<P>(P);
1827
1828context_var! {
1829    /// Selection toolbar function.
1830    pub static SELECTION_TOOLBAR_FN_VAR: WidgetFn<SelectionToolbarArgs> = WidgetFn::nil();
1831    /// Position the selection toolbar in relation to the bounding box of all selection rectangles.
1832    pub static SELECTION_TOOLBAR_ANCHOR_VAR: AnchorOffset = AnchorOffset::out_top();
1833}
1834
1835impl SelectionToolbarMix<()> {
1836    /// Insert context variables used by properties in this mixin.
1837    pub fn context_vars_set(set: &mut ContextValueSet) {
1838        set.insert(&SELECTION_TOOLBAR_FN_VAR);
1839        set.insert(&SELECTION_TOOLBAR_ANCHOR_VAR);
1840    }
1841}
1842
1843/// Defines the floating mini-toolbar that shows near a new text selection.
1844///
1845/// The `toolbar` is used
1846#[property(CONTEXT, widget_impl(SelectionToolbarMix<P>))]
1847pub fn selection_toolbar(child: impl IntoUiNode, toolbar: impl IntoUiNode) -> UiNode {
1848    selection_toolbar_fn(child, WidgetFn::singleton(toolbar))
1849}
1850
1851/// Defines the floating mini-toolbar that shows near a new text selection.
1852///
1853/// Sets the [`SELECTION_TOOLBAR_FN_VAR`].
1854#[property(CONTEXT, default(SELECTION_TOOLBAR_FN_VAR), widget_impl(SelectionToolbarMix<P>))]
1855pub fn selection_toolbar_fn(child: impl IntoUiNode, toolbar: impl IntoVar<WidgetFn<SelectionToolbarArgs>>) -> UiNode {
1856    with_context_var(child, SELECTION_TOOLBAR_FN_VAR, toolbar)
1857}
1858
1859/// Arguments for [`selection_toolbar_fn`].
1860///
1861/// [`selection_toolbar_fn`]: fn@selection_toolbar_fn
1862#[non_exhaustive]
1863pub struct SelectionToolbarArgs {
1864    /// ID of the widget the toolbar is anchored to.
1865    pub anchor_id: WidgetId,
1866    /// Text was selected through touch interaction.
1867    pub is_touch: bool,
1868}
1869impl SelectionToolbarArgs {
1870    /// New args.
1871    pub fn new(anchor_id: impl Into<WidgetId>, is_touch: bool) -> Self {
1872        Self {
1873            anchor_id: anchor_id.into(),
1874            is_touch,
1875        }
1876    }
1877}
1878
1879/// Position the selection toolbar in relation to the bounding box of all selection rectangles.
1880///
1881/// See [`selection_toolbar_fn`](fn@selection_toolbar_fn).
1882///
1883/// Sets the [`SELECTION_TOOLBAR_ANCHOR_VAR`].
1884#[property(CONTEXT, default(SELECTION_TOOLBAR_ANCHOR_VAR), widget_impl(SelectionToolbarMix<P>))]
1885pub fn selection_toolbar_anchor(child: impl IntoUiNode, offset: impl IntoVar<AnchorOffset>) -> UiNode {
1886    with_context_var(child, SELECTION_TOOLBAR_ANCHOR_VAR, offset)
1887}
1888
1889/// Properties that probes various state from the text widget.
1890#[widget_mixin]
1891pub struct TextInspectMix<P>(P);
1892
1893impl TextInspectMix<()> {
1894    /// Insert context variables used by properties in this mixin.
1895    pub fn context_vars_set(set: &mut ContextValueSet) {
1896        let _ = set;
1897    }
1898}
1899
1900/// Gets the caret char index, if the text is editable.
1901#[property(EVENT, default(None), widget_impl(TextInspectMix<P>))]
1902pub fn get_caret_index(child: impl IntoUiNode, index: impl IntoVar<Option<CaretIndex>>) -> UiNode {
1903    super::node::get_caret_index(child, index)
1904}
1905
1906/// Gets the caret display status, if the text is editable.
1907#[property(EVENT, default(CaretStatus::none()), widget_impl(TextInspectMix<P>))]
1908pub fn get_caret_status(child: impl IntoUiNode, status: impl IntoVar<CaretStatus>) -> UiNode {
1909    super::node::get_caret_status(child, status)
1910}
1911
1912/// Gets the number of lines in the text, including wrap lines.
1913///
1914/// This is very cheap, the text widget already has the length, but it does include wrapped lines. You
1915/// can use [`get_lines_wrap_count`] to get text lines and a count of wrapped lines for each.
1916///
1917/// [`get_lines_wrap_count`]: fn@get_lines_wrap_count
1918#[property(CHILD_LAYOUT+100, default(0), widget_impl(TextInspectMix<P>))]
1919pub fn get_lines_len(child: impl IntoUiNode, len: impl IntoVar<usize>) -> UiNode {
1920    super::node::get_lines_len(child, len)
1921}
1922
1923/// Gets the number of wrap lines per text lines.
1924#[property(CHILD_LAYOUT+100, default(LinesWrapCount::NoWrap(0)), widget_impl(TextInspectMix<P>))]
1925pub fn get_lines_wrap_count(child: impl IntoUiNode, lines: impl IntoVar<LinesWrapCount>) -> UiNode {
1926    super::node::get_lines_wrap_count(child, lines)
1927}
1928
1929/// Gets the number of character in the text.
1930#[property(EVENT, default(0), widget_impl(TextInspectMix<P>))]
1931pub fn get_chars_count(child: impl IntoUiNode, chars: impl IntoVar<usize>) -> UiNode {
1932    let chars = chars.into_var();
1933    match_node(child, move |_, op| {
1934        if let UiNodeOp::Init = op {
1935            let ctx = super::node::TEXT.resolved();
1936            chars.set_from_map(&ctx.txt, |t| t.chars().count());
1937            let handle = ctx.txt.bind_map(&chars, |t| t.chars().count());
1938            WIDGET.push_var_handle(handle);
1939        }
1940    })
1941}
1942
1943/// Highlight a text range.
1944///
1945/// This property must be set in the text widget.
1946#[property(CHILD_LAYOUT+100, widget_impl(TextInspectMix<P>))]
1947pub fn txt_highlight(child: impl IntoUiNode, range: impl IntoVar<std::ops::Range<CaretIndex>>, color: impl IntoVar<Rgba>) -> UiNode {
1948    let range = range.into_var();
1949    let color = color.into_var();
1950    let color_key = FrameValueKey::new_unique();
1951    match_node(child, move |_, op| match op {
1952        UiNodeOp::Init => {
1953            WIDGET.sub_var_render(&range).sub_var_render_update(&color);
1954        }
1955        UiNodeOp::Render { frame } => {
1956            let l_txt = super::node::TEXT.laidout();
1957            let r_txt = super::node::TEXT.resolved();
1958            let r_txt = r_txt.segmented_text.text();
1959
1960            for line_rect in l_txt.shaped_text.highlight_rects(range.get(), r_txt) {
1961                frame.push_color(line_rect, color_key.bind_var(&color, |c| *c));
1962            }
1963        }
1964        UiNodeOp::RenderUpdate { update } => {
1965            if let Some(color_update) = color_key.update_var(&color, |c| *c) {
1966                update.update_color(color_update)
1967            }
1968        }
1969        _ => {}
1970    })
1971}
1972
1973/// Gets a vector of font and ranges.
1974///
1975/// This property must be set in the text widget.
1976#[property(CHILD_LAYOUT+100, widget_impl(TextInspectMix<P>))]
1977pub fn get_font_use(child: impl IntoUiNode, font_use: impl IntoVar<Vec<(Font, std::ops::Range<usize>)>>) -> UiNode {
1978    let font_use = font_use.into_var();
1979    let mut shaped_text_version = u32::MAX;
1980    match_node(child, move |c, op| {
1981        if let UiNodeOp::Layout { wl, final_size } = op {
1982            *final_size = c.layout(wl);
1983
1984            let ctx = crate::node::TEXT.laidout();
1985
1986            if ctx.shaped_text_version != shaped_text_version && font_use.capabilities().can_modify() {
1987                shaped_text_version = ctx.shaped_text_version;
1988
1989                let mut r = vec![];
1990
1991                for seg in ctx.shaped_text.lines().flat_map(|l| l.segs()) {
1992                    let mut seg_glyph_i = 0;
1993                    let seg_range = seg.text_range();
1994
1995                    for (font, glyphs) in seg.glyphs() {
1996                        if r.is_empty() {
1997                            r.push((font.clone(), 0..seg_range.end));
1998                        } else {
1999                            let last_i = r.len() - 1;
2000                            if &r[last_i].0 != font {
2001                                let seg_char_i = seg.clusters()[seg_glyph_i] as usize;
2002
2003                                let char_i = seg_range.start + seg_char_i;
2004                                r[last_i].1.end = char_i;
2005                                r.push((font.clone(), char_i..seg_range.end));
2006                            }
2007                        }
2008                        seg_glyph_i += glyphs.len();
2009                    }
2010                }
2011
2012                font_use.set(r);
2013            }
2014        }
2015    })
2016}
2017
2018/// If widget or rich context has selection.
2019///
2020/// If set on a text widget gets if the local text has a selection, otherwise gets if the rich text context has a selection.
2021#[property(EVENT, widget_impl(TextInspectMix<P>))]
2022pub fn has_selection(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
2023    let state = state.into_var();
2024    match_node(child, move |c, op| {
2025        let mut update = false;
2026        match &op {
2027            UiNodeOp::Init => {
2028                update = true;
2029            }
2030            UiNodeOp::Deinit => {
2031                state.set(false);
2032            }
2033            UiNodeOp::Event { .. } => {
2034                update = true;
2035            }
2036            UiNodeOp::Update { .. } => {
2037                update = true;
2038            }
2039            _ => {}
2040        }
2041
2042        c.op(op);
2043
2044        if update {
2045            let new = if let Some(ctx) = TEXT.try_rich() {
2046                ctx.caret.selection_index.is_some()
2047            } else if let Some(ctx) = TEXT.try_resolved() {
2048                ctx.caret.selection_index.is_some()
2049            } else {
2050                false
2051            };
2052            if new != state.get() {
2053                state.set(new);
2054            }
2055        }
2056    })
2057}
2058
2059/// Gets the selected text in the widget.
2060#[property(EVENT, default(Txt::from_static("")), widget_impl(TextInspectMix<P>))]
2061pub fn get_selection(child: impl IntoUiNode, state: impl IntoVar<Txt>) -> UiNode {
2062    let state = state.into_var();
2063    let mut last_sel = (CaretIndex::ZERO, None::<CaretIndex>);
2064    match_node(child, move |c, op| {
2065        let mut update = false;
2066        match &op {
2067            UiNodeOp::Init => {
2068                update = true;
2069            }
2070            UiNodeOp::Deinit => {
2071                state.set(Txt::from_static(""));
2072            }
2073            UiNodeOp::Event { .. } => {
2074                update = true;
2075            }
2076            UiNodeOp::Update { .. } => {
2077                update = true;
2078            }
2079            _ => {}
2080        }
2081
2082        c.op(op);
2083
2084        if update && let Some(ctx) = TEXT.try_resolved() {
2085            let new_sel = (ctx.caret.index.unwrap_or(CaretIndex::ZERO), ctx.caret.selection_index);
2086
2087            if last_sel != new_sel {
2088                last_sel = new_sel;
2089
2090                let txt = if let Some(range) = ctx.caret.selection_char_range() {
2091                    Txt::from_str(&ctx.segmented_text.text()[range])
2092                } else {
2093                    Txt::from_static("")
2094                };
2095
2096                state.set(txt);
2097            }
2098        }
2099    })
2100}
2101
2102/// Rich text properties.
2103///
2104/// Note that these properties are usually set in parent panels of text widgets.
2105#[widget_mixin]
2106pub struct RichTextMix<P>(P);
2107
2108context_var! {
2109    /// Selection toolbar function.
2110    pub static RICH_TEXT_FOCUSED_Z_VAR: Option<ZIndex> = ZIndex::FRONT;
2111}
2112
2113impl RichTextMix<()> {
2114    /// Insert context variables used by properties in this mixin.
2115    pub fn context_vars_set(set: &mut ContextValueSet) {
2116        set.insert(&RICH_TEXT_FOCUSED_Z_VAR);
2117    }
2118}
2119
2120/// Defines a *rich text* context.
2121///
2122/// When enabled text widget descendants of this widget edit the text across the widgets,
2123/// for example, the caret position moves to the next text widget when it reaches the start/end of the first text widget.
2124///
2125/// Nested rich text contexts are allowed and are all part of the outermost context, enabling this property in all nested
2126/// wrap panels is recommended as it enables some rich text behavior like bringing the focused widget to front to avoid
2127/// clipping the caret.
2128///
2129/// When enabled and not nested the widget will handle text commands, it also broadcasts focus change events to all text descendants.
2130#[property(CONTEXT, default(false), widget_impl(RichTextMix<P>))]
2131pub fn rich_text(child: impl IntoUiNode, enabled: impl IntoVar<bool>) -> UiNode {
2132    crate::node::rich_text_node(child, enabled)
2133}
2134
2135/// Defines the Z-index set on inner text (and nested rich text) widgets when focus is within.
2136///
2137/// Is [`ZIndex::FRONT`] by default, so that the caret visual is not clipped when the insert point is at
2138/// an edge between texts. Set to `None` to not change the Z-index on focus.
2139///
2140/// Sets the [`RICH_TEXT_FOCUSED_Z_VAR`].
2141#[property(CONTEXT, default(RICH_TEXT_FOCUSED_Z_VAR), widget_impl(RichTextMix<P>))]
2142pub fn rich_text_focused_z(child: impl IntoUiNode, z_index: impl IntoVar<Option<ZIndex>>) -> UiNode {
2143    with_context_var(child, RICH_TEXT_FOCUSED_Z_VAR, z_index)
2144}