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
11/// Basic text font properties.
12///
13/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
14///
15/// See also [`FontFeaturesMix<P>`] for the other font properties.
16///
17/// [`Text!`]: struct@crate::Text
18#[widget_mixin]
19pub struct FontMix<P>(P);
20
21context_var! {
22    /// Font family of [`Text!`] spans.
23    ///
24    /// [`Text!`]: struct@crate::Text
25    pub static FONT_FAMILY_VAR: FontNames = FontNames::default();
26
27    /// Font size of [`Text!`] spans.
28    ///
29    /// [`Text!`]: struct@crate::Text
30    pub static FONT_SIZE_VAR: FontSize = FontSize::Pt(11.0);
31
32    /// Font weight of [`Text!`] spans.
33    ///
34    /// [`Text!`]: struct@crate::Text
35    pub static FONT_WEIGHT_VAR: FontWeight = FontWeight::NORMAL;
36
37    /// Font style of [`Text!`] spans.
38    ///
39    /// [`Text!`]: struct@crate::Text
40    pub static FONT_STYLE_VAR: FontStyle = FontStyle::Normal;
41
42    /// Font stretch of [`Text!`] spans.
43    ///
44    /// [`Text!`]: struct@crate::Text
45    pub static FONT_STRETCH_VAR: FontStretch = FontStretch::NORMAL;
46
47    /// Font synthesis of [`Text!`] spans.
48    ///
49    /// [`Text!`]: struct@crate::Text
50    pub static FONT_SYNTHESIS_VAR: FontSynthesis = FontSynthesis::ENABLED;
51
52    /// Font anti-aliasing of [`Text!`] spans.
53    ///
54    /// [`Text!`]: struct@crate::Text
55    pub static FONT_AA_VAR: FontAntiAliasing = FontAntiAliasing::Default;
56}
57
58impl FontMix<()> {
59    /// Insert context variables used by properties in this mix-in.
60    pub fn context_vars_set(set: &mut ContextValueSet) {
61        set.insert(&FONT_FAMILY_VAR);
62        set.insert(&FONT_SIZE_VAR);
63        set.insert(&FONT_WEIGHT_VAR);
64        set.insert(&FONT_STYLE_VAR);
65        set.insert(&FONT_STRETCH_VAR);
66        set.insert(&FONT_SYNTHESIS_VAR);
67        set.insert(&FONT_AA_VAR);
68    }
69}
70
71/// Font family name or list of names for texts in this widget or descendants.
72///
73/// All fonts in the list are resolved according to the [`font_style`], [`font_weight`] and [`font_stretch`] config.
74/// During text shaping the first font on the list is preferred, but if the font does not cover a character or word, that
75/// character or word  to the second font in the list and so on.
76///
77/// Sets the [`FONT_FAMILY_VAR`].
78///
79/// [`font_style`]: fn@font_style
80/// [`font_weight`]: fn@font_weight
81/// [`font_stretch`]: fn@font_stretch
82#[property(CONTEXT, default(FONT_FAMILY_VAR), widget_impl(FontMix<P>))]
83pub fn font_family(child: impl UiNode, names: impl IntoVar<FontNames>) -> impl UiNode {
84    with_context_var(child, FONT_FAMILY_VAR, names)
85}
86
87/// Sets the font size for the widget and descendants.
88///
89/// This property affects all texts inside the widget and the [`Length::Em`] unit.
90///
91/// Sets the [`FONT_SIZE_VAR`] context var and the [`LayoutMetrics::font_size`].
92///
93/// [`LayoutMetrics::font_size`]: zng_wgt::prelude::LayoutMetrics::font_size
94/// [`Length::Em`]: zng_wgt::prelude::Length::Em
95#[property(CONTEXT, default(FONT_SIZE_VAR), widget_impl(FontMix<P>))]
96pub fn font_size(child: impl UiNode, size: impl IntoVar<FontSize>) -> impl UiNode {
97    let child = match_node(child, |child, op| match op {
98        UiNodeOp::Init => {
99            WIDGET.sub_var_layout(&FONT_SIZE_VAR);
100        }
101        UiNodeOp::Measure { wm, desired_size } => {
102            let font_size = FONT_SIZE_VAR.get();
103            let font_size_px = font_size.layout_dft_x(LAYOUT.root_font_size());
104            *desired_size = if font_size_px >= 0 {
105                LAYOUT.with_font_size(font_size_px, || child.measure(wm))
106            } else {
107                tracing::error!("invalid font size {font_size:?} => {font_size_px:?}");
108                child.measure(wm)
109            };
110        }
111        UiNodeOp::Layout { wl, final_size } => {
112            let font_size = FONT_SIZE_VAR.get();
113            let font_size_px = font_size.layout_dft_x(LAYOUT.root_font_size());
114            *final_size = if font_size_px >= 0 {
115                LAYOUT.with_font_size(font_size_px, || child.layout(wl))
116            } else {
117                tracing::error!("invalid font size {font_size:?} => {font_size_px:?}");
118                child.layout(wl)
119            };
120        }
121        _ => {}
122    });
123    with_context_var(child, FONT_SIZE_VAR, size)
124}
125
126/// Defines the thickness or boldness the preferred font should have.
127///
128/// This value influences font resolution, the variant within the font family that is closest to this config will be selected.
129///
130/// Sets the [`FONT_WEIGHT_VAR`].
131#[property(CONTEXT, default(FONT_WEIGHT_VAR), widget_impl(FontMix<P>))]
132pub fn font_weight(child: impl UiNode, weight: impl IntoVar<FontWeight>) -> impl UiNode {
133    with_context_var(child, FONT_WEIGHT_VAR, weight)
134}
135
136/// Defines the skew style of the font glyphs.
137///
138/// This value influences font resolution, the variant within the font family that is closest to this config will be selected.
139///
140/// Sets the [`FONT_STYLE_VAR`].
141#[property(CONTEXT, default(FONT_STYLE_VAR), widget_impl(FontMix<P>))]
142pub fn font_style(child: impl UiNode, style: impl IntoVar<FontStyle>) -> impl UiNode {
143    with_context_var(child, FONT_STYLE_VAR, style)
144}
145
146/// Defines how condensed or expanded the preferred font should be.
147///
148/// This value influences font resolution, the variant within the font family that is closest to this config will be selected.
149///
150/// Sets the [`FONT_STRETCH_VAR`].
151#[property(CONTEXT, default(FONT_STRETCH_VAR), widget_impl(FontMix<P>))]
152pub fn font_stretch(child: impl UiNode, stretch: impl IntoVar<FontStretch>) -> impl UiNode {
153    with_context_var(child, FONT_STRETCH_VAR, stretch)
154}
155
156/// Configure if a synthetic font is generated for fonts that do not implement **bold** or *oblique* variants.
157///
158/// Not all fonts implement the requested [`font_weight`] and [`font_style`], this config allows the renderer
159/// to try and generate the style and weight anyway, using transforms and the glyph outlines.
160///
161/// Sets the [`FONT_SYNTHESIS_VAR`].
162///
163/// [`font_weight`]: fn@font_weight
164/// [`font_style`]: fn@font_style
165#[property(CONTEXT, default(FONT_SYNTHESIS_VAR), widget_impl(FontMix<P>))]
166pub fn font_synthesis(child: impl UiNode, enabled: impl IntoVar<FontSynthesis>) -> impl UiNode {
167    with_context_var(child, FONT_SYNTHESIS_VAR, enabled)
168}
169
170/// Configure the anti-aliasing used to render text glyphs inside the widget.
171///
172/// Uses the operating system configuration by default.
173///
174/// Sets the [`FONT_AA_VAR`].
175#[property(CONTEXT, default(FONT_AA_VAR), widget_impl(FontMix<P>))]
176pub fn font_aa(child: impl UiNode, aa: impl IntoVar<FontAntiAliasing>) -> impl UiNode {
177    with_context_var(child, FONT_AA_VAR, aa)
178}
179
180/// Text color properties.
181///
182/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
183///
184/// [`Text!`]: struct@crate::Text
185#[widget_mixin]
186pub struct TextFillMix<P>(P);
187
188context_var! {
189    /// Color of [`Text!`] glyphs that are not colored by palette.
190    ///
191    /// [`Text!`]: struct@crate::Text
192    pub static FONT_COLOR_VAR: Rgba = COLOR_SCHEME_VAR.map(|s| match s {
193        ColorScheme::Light => colors::BLACK,
194        ColorScheme::Dark => colors::WHITE,
195    });
196
197    /// Color of [`Text!`] glyphs that are colored by palette, mostly Emoji.
198    ///
199    /// [`Text!`]: struct@crate::Text
200    pub static FONT_PALETTE_VAR: FontColorPalette = COLOR_SCHEME_VAR.map_into();
201
202    /// Overrides of specific colors in the selected colored glyph palette.
203    pub static FONT_PALETTE_COLORS_VAR: Vec<(u16, Rgba)> = vec![];
204}
205
206impl TextFillMix<()> {
207    /// Insert context variables used by properties in this mix-in.
208    pub fn context_vars_set(set: &mut ContextValueSet) {
209        set.insert(&FONT_COLOR_VAR);
210        set.insert(&FONT_PALETTE_VAR);
211        set.insert(&FONT_PALETTE_COLORS_VAR);
212    }
213}
214
215/// Defines the color the most text glyphs are filled with.
216///
217/// Colored glyphs (Emoji) are not affected by this, you can use [`font_palette`] to modify
218/// Emoji colors.
219///
220/// Sets the [`FONT_COLOR_VAR`].
221///
222/// [`font_palette`]: fn@font_palette
223#[property(CONTEXT, default(FONT_COLOR_VAR), widget_impl(TextFillMix<P>))]
224pub fn font_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
225    with_context_var(child, FONT_COLOR_VAR, color)
226}
227
228/// Defines the palette used to render colored glyphs (Emoji).
229///
230/// This property only affects Emoji from fonts using COLR v0. You can use [`font_color`] to set
231/// the base color, and [`font_palette_colors`] to change specific colors.
232///
233/// Sets the [`FONT_PALETTE_VAR`].
234///
235/// [`font_color`]: fn@font_color
236/// [`font_palette_colors`]: fn@font_palette_colors
237#[property(CONTEXT, default(FONT_PALETTE_VAR), widget_impl(TextFillMix<P>))]
238pub fn font_palette(child: impl UiNode, palette: impl IntoVar<FontColorPalette>) -> impl UiNode {
239    with_context_var(child, FONT_PALETTE_VAR, palette)
240}
241
242/// Set the palette color in the font palette colors.
243///
244/// The `index` is pushed or replaced on the context [`FONT_PALETTE_COLORS_VAR`].
245///
246/// This function is a helper for declaring properties that configure the colors of a specific font, you
247/// can use [`font_palette_colors`] to set all color overrides directly.
248///
249/// [`font_palette_colors`]: fn@font_palette_colors
250pub fn with_font_palette_color(child: impl UiNode, index: u16, color: impl IntoVar<Rgba>) -> impl UiNode {
251    with_context_var(
252        child,
253        FONT_PALETTE_COLORS_VAR,
254        merge_var!(FONT_PALETTE_COLORS_VAR, color.into_var(), move |set, color| {
255            let mut set = set.clone();
256            if let Some(i) = set.iter().position(|(i, _)| *i == index) {
257                set[i].1 = *color;
258            } else {
259                set.push((index, *color));
260            }
261            set
262        }),
263    )
264}
265
266/// Defines custom palette colors that affect Emoji colors.
267///
268/// The palette is selected by [`font_palette`] and then each valid index entry in this property replaces
269/// the selected color.
270///
271/// Sets the [`FONT_PALETTE_COLORS_VAR`].
272///
273/// [`font_palette`]: fn@font_palette
274#[property(CONTEXT, default(FONT_PALETTE_COLORS_VAR), widget_impl(TextFillMix<P>))]
275pub fn font_palette_colors(child: impl UiNode, colors: impl IntoVar<Vec<(u16, Rgba)>>) -> impl UiNode {
276    with_context_var(child, FONT_PALETTE_COLORS_VAR, colors)
277}
278
279/// Text align, justify.
280///
281/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
282///
283/// [`Text!`]: struct@crate::Text
284#[widget_mixin]
285pub struct TextAlignMix<P>(P);
286
287context_var! {
288    /// Text alignment inside the available space.
289    pub static TEXT_ALIGN_VAR: Align = Align::START;
290
291    /// Text alignment inside the available space when it overflows.
292    pub static TEXT_OVERFLOW_ALIGN_VAR: Align = Align::TOP_START;
293
294    /// Text justify mode when text align is fill.
295    pub static JUSTIFY_MODE_VAR: Justify = Justify::Auto;
296}
297
298impl TextAlignMix<()> {
299    /// Insert context variables used by properties in this mix-in.
300    pub fn context_vars_set(set: &mut ContextValueSet) {
301        set.insert(&TEXT_ALIGN_VAR);
302        set.insert(&TEXT_OVERFLOW_ALIGN_VAR);
303        set.insert(&JUSTIFY_MODE_VAR);
304    }
305}
306
307/// Alignment of text inside available space.
308///
309/// Horizontal alignment is applied for each line independently, vertical alignment is applied for the entire
310/// text block together.
311///
312/// Note that the [`Text!`] widget only implements this for text inside each instance in isolation, multiple
313/// text instances in an inline row will not all align together by the [`Text!`] layout implementation alone.
314///
315/// Sets the [`TEXT_ALIGN_VAR`].
316///
317/// See also [`txt_overflow_align`], used when the text overflows.
318///
319/// [`Text!`]: struct@crate::Text
320/// [`txt_overflow_align`]: fn@txt_overflow_align
321#[property(CONTEXT, default(TEXT_ALIGN_VAR), widget_impl(TextAlignMix<P>))]
322pub fn txt_align(child: impl UiNode, mode: impl IntoVar<Align>) -> impl UiNode {
323    with_context_var(child, TEXT_ALIGN_VAR, mode)
324}
325
326/// Alignment of text inside available space when the text overflows.
327///
328/// Note that the [`Text!`] widget only implements this for text inside each instance in isolation, multiple
329/// text instances in an inline row will not all align together. Also note that [`txt_overflow`] truncation
330/// only applies to the end of the text after it is aligned, so unless this is [`Align::TOP_START`] (default) the
331/// start of the text maybe still be clipped after truncation.
332///
333/// Sets the [`TEXT_OVERFLOW_ALIGN_VAR`].
334///
335/// [`Text!`]: struct@crate::Text
336/// [`txt_overflow`]: fn@txt_overflow
337/// [`Align::TOP_START`]: zng_wgt::prelude::Align::TOP_START
338#[property(CONTEXT, default(TEXT_OVERFLOW_ALIGN_VAR), widget_impl(TextAlignMix<P>))]
339pub fn txt_overflow_align(child: impl UiNode, mode: impl IntoVar<Align>) -> impl UiNode {
340    with_context_var(child, TEXT_OVERFLOW_ALIGN_VAR, mode)
341}
342
343/// Config the automatic spacing inserted between words and letters when text is aligned to fill.
344///
345/// Text alignment can be set to [`Align::FILL_X`], if that is the case this config is defines how
346/// the glyphs are spaced to *fill* the text block.
347///
348/// Sets the [`JUSTIFY_MODE_VAR`].
349///
350/// [`Align::FILL_X`]: zng_wgt::prelude::Align::FILL_X
351#[property(CONTEXT, default(JUSTIFY_MODE_VAR), widget_impl(TextAlignMix<P>))]
352pub fn justify_mode(child: impl UiNode, mode: impl IntoVar<Justify>) -> impl UiNode {
353    with_context_var(child, JUSTIFY_MODE_VAR, mode)
354}
355
356/// Text wrap, hyphenation.
357///
358/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
359///
360/// [`Text!`]: struct@crate::Text
361#[widget_mixin]
362pub struct TextWrapMix<P>(P);
363
364context_var! {
365    /// If line breaks are automatically inserted to fill the available space.
366    ///
367    /// The [`LINE_BREAK_VAR`], [`WORD_BREAK_VAR`] and [`HYPHENS_VAR`] configure how the text is split.
368    ///
369    /// Is `true` by default.
370    pub static TEXT_WRAP_VAR: bool = true;
371
372    /// Configuration of line breaks inside words during text wrap.
373    pub static WORD_BREAK_VAR: WordBreak = WordBreak::Normal;
374
375    /// Configuration of line breaks in Chinese, Japanese, or Korean text.
376    pub static LINE_BREAK_VAR: LineBreak = LineBreak::Auto;
377
378    /// Text hyphenation config.
379    pub static HYPHENS_VAR: Hyphens = Hyphens::default();
380
381    /// Hyphen text rendered when auto-hyphenating.
382    pub static HYPHEN_CHAR_VAR: Txt = Txt::from_char('-');
383
384    /// Text overflow handling.
385    pub static TEXT_OVERFLOW_VAR: TextOverflow = TextOverflow::Ignore;
386}
387
388impl TextWrapMix<()> {
389    /// Insert context variables used by properties in this mix-in.
390    pub fn context_vars_set(set: &mut ContextValueSet) {
391        set.insert(&TEXT_WRAP_VAR);
392        set.insert(&WORD_BREAK_VAR);
393        set.insert(&LINE_BREAK_VAR);
394        set.insert(&HYPHENS_VAR);
395        set.insert(&HYPHEN_CHAR_VAR);
396        set.insert(&TEXT_OVERFLOW_VAR);
397    }
398}
399
400/// Defines how text overflow is handled by the text widgets.
401#[derive(Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
402pub enum TextOverflow {
403    /// Text is allowed to overflow.
404    ///
405    /// Note that the text widget can still [`clip_to_bounds`], and text widgets also clip any text
406    /// that overflows over one line-height in any direction. Text overflow is tracked even if `Ignore`
407    /// is set, so custom properties may also implement some form of overflow handling.
408    ///
409    /// [`clip_to_bounds`]: fn@zng_wgt::clip_to_bounds
410    Ignore,
411    /// Truncate the text so it will fit, the associated `Txt` is a suffix appended to the truncated text.
412    ///
413    /// Note that if the suffix is not empty the text will truncate more to reserve space for the suffix. If
414    /// the suffix itself is too wide it will overflow.
415    Truncate(Txt),
416}
417impl fmt::Debug for TextOverflow {
418    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419        if f.alternate() {
420            write!(f, "TextOverflow")?
421        }
422        match self {
423            Self::Ignore => write!(f, "Ignore"),
424            Self::Truncate(arg0) => f.debug_tuple("Truncate").field(arg0).finish(),
425        }
426    }
427}
428impl TextOverflow {
429    /// Truncate without suffix.
430    pub fn truncate() -> Self {
431        Self::Truncate(Txt::from_static(""))
432    }
433
434    /// Truncate with the ellipses `'…'` char as suffix.
435    pub fn ellipses() -> Self {
436        Self::Truncate(Txt::from_char('…'))
437    }
438}
439impl_from_and_into_var! {
440    /// Truncate (no suffix), or ignore.
441    fn from(truncate: bool) -> TextOverflow {
442        if truncate {
443            TextOverflow::truncate()
444        } else {
445            TextOverflow::Ignore
446        }
447    }
448
449    fn from(truncate: Txt) -> TextOverflow {
450        TextOverflow::Truncate(truncate)
451    }
452    fn from(s: &'static str) -> TextOverflow {
453        Txt::from(s).into()
454    }
455    fn from(s: String) -> TextOverflow {
456        Txt::from(s).into()
457    }
458    fn from(c: char) -> TextOverflow {
459        Txt::from(c).into()
460    }
461}
462
463/// Enables or disables text wrap.
464///
465/// If enabled, line-breaks and hyphens are automatically inserted to flow the text to fill the available width. Wrap
466/// can be configured using the [`line_break`], [`word_break`] and [`hyphens`] properties.
467///
468/// Sets the [`TEXT_WRAP_VAR`].
469///
470/// [`line_break`]: fn@line_break
471/// [`word_break`]: fn@word_break
472/// [`hyphens`]: fn@hyphens
473#[property(CONTEXT, default(TEXT_WRAP_VAR), widget_impl(TextWrapMix<P>))]
474pub fn txt_wrap(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
475    with_context_var(child, TEXT_WRAP_VAR, enabled)
476}
477
478/// Configure line breaks inside words during text wrap.
479///
480/// This value is only considered if it is impossible to fit a full word to a line.
481///
482/// Hyphens can be inserted in word breaks using the [`hyphens`] configuration.
483///
484/// Sets the [`WORD_BREAK_VAR`].
485///
486/// [`hyphens`]: fn@hyphens
487#[property(CONTEXT, default(WORD_BREAK_VAR), widget_impl(TextWrapMix<P>))]
488pub fn word_break(child: impl UiNode, mode: impl IntoVar<WordBreak>) -> impl UiNode {
489    with_context_var(child, WORD_BREAK_VAR, mode)
490}
491
492/// Configuration of text wrapping for Chinese, Japanese, or Korean text.
493///
494/// Sets the [`LINE_BREAK_VAR`].
495#[property(CONTEXT, default(LINE_BREAK_VAR), widget_impl(TextWrapMix<P>))]
496pub fn line_break(child: impl UiNode, mode: impl IntoVar<LineBreak>) -> impl UiNode {
497    with_context_var(child, LINE_BREAK_VAR, mode)
498}
499
500/// Configure hyphenation.
501///
502/// Note that for automatic hyphenation to work the [`lang`] must also be set and the [`HYPHENATION`] service must support it.
503///
504/// The auto hyphenation char can be defined using [`hyphen_char`].
505///
506/// [`HYPHENATION`]: zng_ext_font::HYPHENATION
507/// [`lang`]: fn@lang
508/// [`hyphen_char`]: fn@hyphen_char
509#[property(CONTEXT, default(HYPHENS_VAR), widget_impl(TextWrapMix<P>))]
510pub fn hyphens(child: impl UiNode, hyphens: impl IntoVar<Hyphens>) -> impl UiNode {
511    with_context_var(child, HYPHENS_VAR, hyphens)
512}
513
514/// The char or small string that is rendered when text is auto-hyphenated.
515///
516/// Note that hyphenation is enabled by the [`hyphens`] property.
517///
518/// [`hyphens`]: fn@hyphens
519#[property(CONTEXT, default(HYPHEN_CHAR_VAR), widget_impl(TextWrapMix<P>))]
520pub fn hyphen_char(child: impl UiNode, hyphen: impl IntoVar<Txt>) -> impl UiNode {
521    with_context_var(child, HYPHEN_CHAR_VAR, hyphen)
522}
523
524/// Defines if text overflow is truncated, with optional suffix append.
525///
526/// When enabled overflow is truncated by character or by the wrap rules if [`txt_wrap`] is enabled (it is by default).
527///
528/// Overflow is always ignored when the text is editable.
529///
530/// [`txt_wrap`]: fn@txt_wrap
531#[property(CONTEXT, default(TEXT_OVERFLOW_VAR), widget_impl(TextWrapMix<P>))]
532pub fn txt_overflow(child: impl UiNode, overflow: impl IntoVar<TextOverflow>) -> impl UiNode {
533    with_context_var(child, TEXT_OVERFLOW_VAR, overflow)
534}
535
536/// Gets if the text is overflown.
537#[property(CHILD_LAYOUT+100, widget_impl(TextWrapMix<P>))]
538pub fn is_overflown(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
539    let state = state.into_var();
540    match_node(child, move |_, op| match op {
541        UiNodeOp::Deinit => {
542            let _ = state.set(false);
543        }
544        UiNodeOp::Layout { .. } => {
545            let is_o = super::node::TEXT.laidout().overflow.is_some();
546            if is_o != state.get() {
547                let _ = state.set(is_o);
548            }
549        }
550        _ => {}
551    })
552}
553
554/// Gets if the text has an entire line overflown.
555///
556/// This is `true` when the text has multiple lines, either due to line-break or wrap, and at
557/// least one line overflows the allowed height, partially or fully.
558#[property(CHILD_LAYOUT+100, widget_impl(TextWrapMix<P>))]
559pub fn is_line_overflown(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
560    let state = state.into_var();
561    match_node(child, move |_, op| match op {
562        UiNodeOp::Deinit => {
563            let _ = state.set(false);
564        }
565        UiNodeOp::Layout { .. } => {
566            let txt = super::node::TEXT.laidout();
567            let is_o = if let Some(info) = &txt.overflow {
568                info.line < txt.shaped_text.lines_len().saturating_sub(1)
569            } else {
570                false
571            };
572            if is_o != state.get() {
573                let _ = state.set(is_o);
574            }
575        }
576        _ => {}
577    })
578}
579
580/// Gets the overflow text, that is a clone of the text starting from the first overflow character.
581///
582/// Note that overflow is tracked even if [`txt_overflow`] is set to [`TextOverflow::Ignore`].
583///
584/// [`txt_overflow`]: fn@txt_overflow
585#[property(CHILD_LAYOUT+100, widget_impl(TextWrapMix<P>))]
586pub fn get_overflow(child: impl UiNode, txt: impl IntoVar<Txt>) -> impl UiNode {
587    let txt = txt.into_var();
588    match_node(child, move |_, op| {
589        if let UiNodeOp::Layout { .. } = op {
590            let l_txt = super::node::TEXT.laidout();
591            if let Some(info) = &l_txt.overflow {
592                let r = super::node::TEXT.resolved();
593                let tail = &r.segmented_text.text()[info.text_char..];
594                if txt.with(|t| t != tail) {
595                    let _ = txt.set(Txt::from_str(tail));
596                }
597            } else if txt.with(|t| !t.is_empty()) {
598                let _ = txt.set(Txt::from_static(""));
599            }
600        }
601    })
602}
603
604/// Text underline, overline and strikethrough lines.
605///
606/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
607///
608/// [`Text!`]: struct@crate::Text
609#[widget_mixin]
610pub struct TextDecorationMix<P>(P);
611
612bitflags! {
613    /// Represents what parts of a text the underline must skip over.
614    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
615    #[serde(transparent)]
616    pub struct UnderlineSkip: u8 {
617        /// Underline spans the entire text length.
618        const NONE = 0;
619
620        /// Skip white space.
621        const SPACES = 0b0001;
622
623        /// Skip over glyph descenders that intersect with the underline.
624        const GLYPHS = 0b0010;
625
626        /// Default value, skip glyphs.
627        const DEFAULT = Self::GLYPHS.bits();
628    }
629}
630impl Default for UnderlineSkip {
631    fn default() -> Self {
632        Self::DEFAULT
633    }
634}
635
636/// Defines what line gets traced by the text underline decoration.
637#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
638pub enum UnderlinePosition {
639    /// Underline is positioned using the offset defined in the font file.
640    #[default]
641    Font,
642    /// Underline is positioned after the text *descent*, avoiding crossover with all glyph descenders.
643    Descent,
644}
645
646context_var! {
647    /// Underline thickness.
648    pub static UNDERLINE_THICKNESS_VAR: UnderlineThickness = 0;
649    /// Underline style.
650    pub static UNDERLINE_STYLE_VAR: LineStyle = LineStyle::Hidden;
651    /// Underline color, inherits from [`FONT_COLOR_VAR`].
652    pub static UNDERLINE_COLOR_VAR: Rgba = FONT_COLOR_VAR;
653    /// Parts of text skipped by underline.
654    pub static UNDERLINE_SKIP_VAR: UnderlineSkip = UnderlineSkip::DEFAULT;
655    /// Position of the underline.
656    pub static UNDERLINE_POSITION_VAR: UnderlinePosition = UnderlinePosition::Font;
657
658    /// Overline thickness.
659    pub static OVERLINE_THICKNESS_VAR: TextLineThickness = 0;
660    /// Overline style.
661    pub static OVERLINE_STYLE_VAR: LineStyle = LineStyle::Hidden;
662    /// Overline color, inherits from [`FONT_COLOR_VAR`].
663    pub static OVERLINE_COLOR_VAR: Rgba = FONT_COLOR_VAR;
664
665    /// Strikethrough thickness.
666    pub static STRIKETHROUGH_THICKNESS_VAR: TextLineThickness = 0;
667    /// Strikethrough style.
668    pub static STRIKETHROUGH_STYLE_VAR: LineStyle = LineStyle::Hidden;
669    /// Strikethrough color, inherits from [`FONT_COLOR_VAR`].
670    pub static STRIKETHROUGH_COLOR_VAR: Rgba = FONT_COLOR_VAR;
671
672    /// Underline thickness for the IME preview underline.
673    pub static IME_UNDERLINE_THICKNESS_VAR: UnderlineThickness = 1;
674    /// Underline style for the IME preview underline.
675    pub static IME_UNDERLINE_STYLE_VAR: LineStyle = LineStyle::Dotted;
676}
677
678impl TextDecorationMix<()> {
679    /// Insert context variables used by properties in this mix-in.
680    pub fn context_vars_set(set: &mut ContextValueSet) {
681        set.insert(&UNDERLINE_THICKNESS_VAR);
682        set.insert(&UNDERLINE_STYLE_VAR);
683        set.insert(&UNDERLINE_COLOR_VAR);
684        set.insert(&UNDERLINE_SKIP_VAR);
685        set.insert(&UNDERLINE_POSITION_VAR);
686        set.insert(&OVERLINE_THICKNESS_VAR);
687        set.insert(&OVERLINE_STYLE_VAR);
688        set.insert(&OVERLINE_COLOR_VAR);
689        set.insert(&STRIKETHROUGH_THICKNESS_VAR);
690        set.insert(&STRIKETHROUGH_STYLE_VAR);
691        set.insert(&STRIKETHROUGH_COLOR_VAR);
692        set.insert(&IME_UNDERLINE_THICKNESS_VAR);
693        set.insert(&IME_UNDERLINE_STYLE_VAR);
694    }
695}
696
697/// Draw lines *under* each text line.
698///
699/// Sets the [`UNDERLINE_THICKNESS_VAR`] and [`UNDERLINE_STYLE_VAR`].
700#[property(CONTEXT, default(UNDERLINE_THICKNESS_VAR, UNDERLINE_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
701pub fn underline(child: impl UiNode, thickness: impl IntoVar<UnderlineThickness>, style: impl IntoVar<LineStyle>) -> impl UiNode {
702    let child = with_context_var(child, UNDERLINE_THICKNESS_VAR, thickness);
703    with_context_var(child, UNDERLINE_STYLE_VAR, style)
704}
705/// Custom [`underline`](fn@underline) color, if not set
706/// the [`font_color`](fn@font_color) is used.
707///
708/// Sets the [`UNDERLINE_COLOR_VAR`].
709#[property(CONTEXT, default(UNDERLINE_COLOR_VAR), widget_impl(TextDecorationMix<P>))]
710pub fn underline_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
711    with_context_var(child, UNDERLINE_COLOR_VAR, color)
712}
713/// Defines what segments of each text line are skipped when tracing the [`underline`](fn@underline).
714///
715/// By default skips glyphs that intercept the underline.
716///
717/// Sets the [`UNDERLINE_SKIP_VAR`].
718#[property(CONTEXT, default(UNDERLINE_SKIP_VAR), widget_impl(TextDecorationMix<P>))]
719pub fn underline_skip(child: impl UiNode, skip: impl IntoVar<UnderlineSkip>) -> impl UiNode {
720    with_context_var(child, UNDERLINE_SKIP_VAR, skip)
721}
722/// Defines what font line gets traced by the underline.
723///
724/// By default uses the font configuration, but it usually crosses over glyph *descents* causing skips on
725/// the line, you can set this [`UnderlinePosition::Descent`] to fully clear all glyph *descents*.
726///
727/// Sets the [`UNDERLINE_POSITION_VAR`].
728#[property(CONTEXT, default(UNDERLINE_POSITION_VAR), widget_impl(TextDecorationMix<P>))]
729pub fn underline_position(child: impl UiNode, position: impl IntoVar<UnderlinePosition>) -> impl UiNode {
730    with_context_var(child, UNDERLINE_POSITION_VAR, position)
731}
732
733/// Draw lines *above* each text line.
734///
735/// Sets the [`OVERLINE_THICKNESS_VAR`] and [`OVERLINE_STYLE_VAR`].
736#[property(CONTEXT, default(OVERLINE_THICKNESS_VAR, OVERLINE_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
737pub fn overline(child: impl UiNode, thickness: impl IntoVar<TextLineThickness>, style: impl IntoVar<LineStyle>) -> impl UiNode {
738    let child = with_context_var(child, OVERLINE_THICKNESS_VAR, thickness);
739    with_context_var(child, OVERLINE_STYLE_VAR, style)
740}
741/// Custom [`overline`](fn@overline) color, if not set
742/// the [`font_color`](fn@font_color) is used.
743///
744/// Sets the [`OVERLINE_COLOR_VAR`].
745#[property(CONTEXT, default(OVERLINE_COLOR_VAR), widget_impl(TextDecorationMix<P>))]
746pub fn overline_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
747    with_context_var(child, OVERLINE_COLOR_VAR, color)
748}
749
750/// Draw lines across each text line.
751///
752/// Sets the [`STRIKETHROUGH_THICKNESS_VAR`] and [`STRIKETHROUGH_STYLE_VAR`].
753#[property(CONTEXT, default(STRIKETHROUGH_THICKNESS_VAR, STRIKETHROUGH_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
754pub fn strikethrough(child: impl UiNode, thickness: impl IntoVar<TextLineThickness>, style: impl IntoVar<LineStyle>) -> impl UiNode {
755    let child = with_context_var(child, STRIKETHROUGH_THICKNESS_VAR, thickness);
756    with_context_var(child, STRIKETHROUGH_STYLE_VAR, style)
757}
758/// Custom [`strikethrough`](fn@strikethrough) color, if not set
759/// the [`font_color`](fn@font_color) is used.
760///
761/// Sets the [`STRIKETHROUGH_COLOR_VAR`].
762#[property(CONTEXT, default(STRIKETHROUGH_COLOR_VAR), widget_impl(TextDecorationMix<P>))]
763pub fn strikethrough_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
764    with_context_var(child, STRIKETHROUGH_COLOR_VAR, color)
765}
766
767/// Style and thickness of the line drawn *under* the IME preview text.
768///
769/// Sets the [`IME_UNDERLINE_THICKNESS_VAR`] and [`IME_UNDERLINE_STYLE_VAR`].
770#[property(CONTEXT, default(IME_UNDERLINE_THICKNESS_VAR, IME_UNDERLINE_STYLE_VAR), widget_impl(TextDecorationMix<P>))]
771pub fn ime_underline(child: impl UiNode, thickness: impl IntoVar<UnderlineThickness>, style: impl IntoVar<LineStyle>) -> impl UiNode {
772    let child = with_context_var(child, IME_UNDERLINE_THICKNESS_VAR, thickness);
773    with_context_var(child, IME_UNDERLINE_STYLE_VAR, style)
774}
775
776/// Text spacing properties.
777///
778/// All properties in this mixin affects [`Text!`] nodes inside the widget where they are set.
779///
780/// See also [`ParagraphMix<P>`] for paragraph spacing.
781///
782/// [`Text!`]: struct@crate::Text
783#[widget_mixin]
784pub struct TextSpacingMix<P>(P);
785
786context_var! {
787    /// Text line height of [`Text!`] spans.
788    ///
789    /// [`Text!`]: struct@crate::Text
790    pub static LINE_HEIGHT_VAR: LineHeight = LineHeight::Default;
791
792    /// Extra spacing in between lines of [`Text!`] spans.
793    ///
794    /// [`Text!`]: struct@crate::Text
795    pub static LINE_SPACING_VAR: LineSpacing = LineSpacing::Default;
796
797    /// Extra letter spacing of [`Text!`] spans.
798    ///
799    /// [`Text!`]: struct@crate::Text
800    pub static LETTER_SPACING_VAR: LetterSpacing = LetterSpacing::Default;
801
802    /// Extra word spacing of [`Text!`] spans.
803    ///
804    /// [`Text!`]: struct@crate::Text
805    pub static WORD_SPACING_VAR: WordSpacing = WordSpacing::Default;
806
807    /// Length of the `TAB` space.
808    pub static TAB_LENGTH_VAR: TabLength = 400.pct();
809}
810
811impl TextSpacingMix<()> {
812    /// Insert context variables used by properties in this mix-in.
813    pub fn context_vars_set(set: &mut ContextValueSet) {
814        set.insert(&LINE_HEIGHT_VAR);
815        set.insert(&LINE_SPACING_VAR);
816        set.insert(&LETTER_SPACING_VAR);
817        set.insert(&WORD_SPACING_VAR);
818        set.insert(&TAB_LENGTH_VAR);
819    }
820}
821
822/// Height of each text line. If not set inherits the `line_height` from the parent widget.
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 UiNode, height: impl IntoVar<LineHeight>) -> impl UiNode {
835    with_context_var(child, LINE_HEIGHT_VAR, height)
836}
837
838/// Extra spacing added in between text letters. If not set inherits the `letter_spacing` from the parent widget.
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 UiNode, extra: impl IntoVar<LetterSpacing>) -> impl UiNode {
855    with_context_var(child, LETTER_SPACING_VAR, extra)
856}
857
858/// Extra spacing in-between text lines. If not set inherits the `line_spacing` from the parent widget.
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 UiNode, extra: impl IntoVar<LineSpacing>) -> impl UiNode {
870    with_context_var(child, LINE_SPACING_VAR, extra)
871}
872
873/// Extra spacing added to the Unicode `U+0020 SPACE` character. If not set inherits the `word_spacing` from the parent widget.
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 UiNode, extra: impl IntoVar<WordSpacing>) -> impl 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 UiNode, length: impl IntoVar<TabLength>) -> impl 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 mix-in.
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 UiNode, transform: impl IntoVar<WhiteSpace>) -> impl 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 UiNode, transform: impl IntoVar<TextTransformFn>) -> impl 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 UiNode, lang: impl IntoVar<Langs>) -> impl 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 UiNode, direction: impl IntoVar<LayoutDirection>) -> impl 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 mix-in.
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 mix-in.
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 UiNode, name: FontVariationName, value: impl IntoVar<f32>) -> impl 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) -> impl UiNode
1072where
1073    C: UiNode,
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 UiNode, variations: impl IntoVar<FontVariations>) -> impl 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 UiNode, features: impl IntoVar<FontFeatures>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<CapsVariant>) -> impl 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 UiNode, state: impl IntoVar<NumVariant>) -> impl 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 UiNode, state: impl IntoVar<NumSpacing>) -> impl 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 UiNode, state: impl IntoVar<NumFraction>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontFeatureState>) -> impl 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 UiNode, state: impl IntoVar<FontStyleSet>) -> impl 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 UiNode, state: impl IntoVar<CharVariant>) -> impl 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 UiNode, state: impl IntoVar<FontPosition>) -> impl 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 UiNode, state: impl IntoVar<JpVariant>) -> impl 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 UiNode, state: impl IntoVar<CnVariant>) -> impl 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 UiNode, state: impl IntoVar<EastAsianWidth>) -> impl 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
1243    /// Accepts `'\t'` input when editable.
1244    pub static ACCEPTS_TAB_VAR: bool = false;
1245
1246    /// Accepts `'\n'` input when editable.
1247    pub static ACCEPTS_ENTER_VAR: bool = false;
1248
1249    /// Caret color, inherits from [`FONT_COLOR_VAR`].
1250    pub static CARET_COLOR_VAR: Rgba = FONT_COLOR_VAR;
1251
1252    /// Interactive caret visual.
1253    pub static INTERACTIVE_CARET_VISUAL_VAR: WidgetFn<CaretShape> = wgt_fn!(|s| super::node::default_interactive_caret_visual(s));
1254
1255    /// Interactive caret mode.
1256    pub static INTERACTIVE_CARET_VAR: InteractiveCaretMode = InteractiveCaretMode::default();
1257
1258    /// Selection background color.
1259    pub static SELECTION_COLOR_VAR: Rgba = colors::AZURE.with_alpha(30.pct());
1260
1261    /// If text parse updated for every text change.
1262    pub static TXT_PARSE_LIVE_VAR: bool = true;
1263
1264    /// Debounce time for change stop.
1265    pub static CHANGE_STOP_DELAY_VAR: Duration = 1.secs();
1266
1267    /// Auto selection on keyboard focus.
1268    pub static AUTO_SELECTION_VAR: AutoSelection = AutoSelection::default();
1269
1270    /// Maximum number of characters that can be input.
1271    ///
1272    /// Zero means no limit. Is zero by default.
1273    pub static MAX_CHARS_COUNT_VAR: usize = 0;
1274
1275    /// Replacement character used when obscuring text.
1276    pub static OBSCURING_CHAR_VAR: char = '•';
1277
1278    /// If text characters are replaced with [`OBSCURING_CHAR_VAR`] for rendering.
1279    pub static OBSCURE_TXT_VAR: bool = false;
1280
1281    pub(super) static TXT_PARSE_PENDING_VAR: bool = false;
1282}
1283
1284impl TextEditMix<()> {
1285    /// Insert context variables used by properties in this mix-in.
1286    pub fn context_vars_set(set: &mut ContextValueSet) {
1287        set.insert(&TEXT_EDITABLE_VAR);
1288        set.insert(&TEXT_SELECTABLE_VAR);
1289        set.insert(&ACCEPTS_ENTER_VAR);
1290        set.insert(&CARET_COLOR_VAR);
1291        set.insert(&INTERACTIVE_CARET_VISUAL_VAR);
1292        set.insert(&INTERACTIVE_CARET_VAR);
1293        set.insert(&SELECTION_COLOR_VAR);
1294        set.insert(&TXT_PARSE_LIVE_VAR);
1295        set.insert(&CHANGE_STOP_DELAY_VAR);
1296        set.insert(&AUTO_SELECTION_VAR);
1297        set.insert(&MAX_CHARS_COUNT_VAR);
1298        set.insert(&OBSCURING_CHAR_VAR);
1299        set.insert(&OBSCURE_TXT_VAR);
1300    }
1301}
1302
1303/// Defines the position of an interactive caret in relation to the selection.
1304///
1305/// See [`interactive_caret_visual`](fn@interactive_caret_visual) for more details.
1306#[derive(Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1307pub enum CaretShape {
1308    /// Caret defines the selection start in LTR and end in RTL text.
1309    SelectionLeft,
1310    /// Caret defines the selection end in LTR and start in RTL text.
1311    SelectionRight,
1312    /// Caret defines the insert point, when there is no selection.
1313    Insert,
1314}
1315impl fmt::Debug for CaretShape {
1316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1317        if f.alternate() {
1318            write!(f, "CaretShape::")?;
1319        }
1320        match self {
1321            Self::SelectionLeft => write!(f, "SelectionLeft"),
1322            Self::SelectionRight => write!(f, "SelectionRight"),
1323            Self::Insert => write!(f, "Insert"),
1324        }
1325    }
1326}
1327
1328/// Defines when the interactive carets are used.
1329#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1330pub enum InteractiveCaretMode {
1331    /// Uses interactive carets only for touch selections, uses non-interactive caret for other selections.
1332    #[default]
1333    TouchOnly,
1334    /// Uses interactive carets for all selections.
1335    Enabled,
1336    /// Uses non-interactive carets for all selections.
1337    Disabled,
1338}
1339impl fmt::Debug for InteractiveCaretMode {
1340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1341        if f.alternate() {
1342            write!(f, "InteractiveCaretMode::")?;
1343        }
1344        match self {
1345            Self::TouchOnly => write!(f, "TouchOnly"),
1346            Self::Enabled => write!(f, "Enabled"),
1347            Self::Disabled => write!(f, "Disabled"),
1348        }
1349    }
1350}
1351impl_from_and_into_var! {
1352    fn from(enabled: bool) -> InteractiveCaretMode {
1353        if enabled {
1354            InteractiveCaretMode::Enabled
1355        } else {
1356            InteractiveCaretMode::Disabled
1357        }
1358    }
1359}
1360
1361/// Enable text caret, input and makes the widget focusable.
1362///
1363/// If the `txt` variable is read-only, this is ignored, if the var is writeable this
1364/// enables text input and modifies the variable.
1365///
1366/// Sets the [`TEXT_EDITABLE_VAR`].
1367#[property(CONTEXT, default(TEXT_EDITABLE_VAR), widget_impl(TextEditMix<P>))]
1368pub fn txt_editable(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
1369    with_context_var(child, TEXT_EDITABLE_VAR, enabled)
1370}
1371
1372/// Enable text selection, copy and makes the widget focusable.
1373///
1374/// Sets the [`TEXT_SELECTABLE_VAR`].
1375#[property(CONTEXT, default(TEXT_SELECTABLE_VAR), widget_impl(TextEditMix<P>))]
1376pub fn txt_selectable(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
1377    with_context_var(child, TEXT_SELECTABLE_VAR, enabled)
1378}
1379
1380/// If the `'\t'` character is inserted when tab is pressed and the text is editable.
1381///
1382/// If not enabled or the text is not editable, then pressing tab moves the focus like normal.
1383///
1384/// Sets the [`ACCEPTS_TAB_VAR`].
1385#[property(CONTEXT, default(ACCEPTS_TAB_VAR), widget_impl(TextEditMix<P>))]
1386pub fn accepts_tab(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
1387    with_context_var(child, ACCEPTS_TAB_VAR, enabled)
1388}
1389
1390/// If the `'\n'` character is inserted when enter is pressed and the text is editable.
1391///
1392/// Sets the [`ACCEPTS_ENTER_VAR`].
1393#[property(CONTEXT, default(ACCEPTS_ENTER_VAR), widget_impl(TextEditMix<P>))]
1394pub fn accepts_enter(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
1395    with_context_var(child, ACCEPTS_ENTER_VAR, enabled)
1396}
1397
1398/// Defines the color of the non-interactive caret.
1399///
1400/// Sets the [`CARET_COLOR_VAR`].
1401#[property(CONTEXT, default(CARET_COLOR_VAR), widget_impl(TextEditMix<P>))]
1402pub fn caret_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
1403    with_context_var(child, CARET_COLOR_VAR, color)
1404}
1405
1406/// Defines custom caret visual for interactive caret.
1407///
1408/// The `visual` node becomes the content of a [layered widget] at the `ADORNER+1` layer, the text widget context is
1409/// propagated so contextual variables and value work seamless inside the node.
1410///
1411/// The `visual` node must set one special value during layout, the [`set_interactive_caret_spot`] must be called to
1412/// set the offset to the middle of the caret line in the visual inner-bounds, this is used to position the caret.
1413///
1414/// Sets the [`INTERACTIVE_CARET_VISUAL_VAR`].
1415///
1416/// [layered widget]: zng_wgt_layer
1417/// [`set_interactive_caret_spot`]: super::node::set_interactive_caret_spot
1418#[property(CONTEXT, default(INTERACTIVE_CARET_VISUAL_VAR), widget_impl(TextEditMix<P>))]
1419pub fn interactive_caret_visual(child: impl UiNode, visual: impl IntoVar<WidgetFn<CaretShape>>) -> impl UiNode {
1420    with_context_var(child, INTERACTIVE_CARET_VISUAL_VAR, visual)
1421}
1422
1423/// Defines when the interactive carets are used.
1424///
1425/// By default only uses interactive carets for touch selections.
1426#[property(CONTEXT, default(INTERACTIVE_CARET_VAR), widget_impl(TextEditMix<P>))]
1427pub fn interactive_caret(child: impl UiNode, mode: impl IntoVar<InteractiveCaretMode>) -> impl UiNode {
1428    with_context_var(child, INTERACTIVE_CARET_VAR, mode)
1429}
1430
1431/// Sets the [`SELECTION_COLOR_VAR`].
1432#[property(CONTEXT, default(SELECTION_COLOR_VAR), widget_impl(TextEditMix<P>))]
1433pub fn selection_color(child: impl UiNode, color: impl IntoVar<Rgba>) -> impl UiNode {
1434    with_context_var(child, SELECTION_COLOR_VAR, color)
1435}
1436
1437/// If [`txt_parse`] tries to parse after any text change immediately.
1438///
1439/// This is enabled by default, if disabled the [`PARSE_CMD`] can be used to update pending parse.
1440///
1441/// This property sets the [`TXT_PARSE_LIVE_VAR`].
1442///
1443/// [`txt_parse`]: fn@super::txt_parse
1444/// [`PARSE_CMD`]: super::cmd::PARSE_CMD
1445#[property(CONTEXT, default(TXT_PARSE_LIVE_VAR), widget_impl(TextEditMix<P>))]
1446pub fn txt_parse_live(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
1447    with_context_var(child, TXT_PARSE_LIVE_VAR, enabled)
1448}
1449
1450/// Shorthand property, disables live parsing and parse on change stop.
1451///
1452/// This property sets [`txt_parse_live`] and [`on_change_stop`] on the widget.
1453///
1454/// [`txt_parse_live`]: fn@txt_parse_live
1455/// [`on_change_stop`]: fn@on_change_stop
1456#[property(EVENT, widget_impl(TextEditMix<P>))]
1457pub fn txt_parse_on_stop(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
1458    let enabled = enabled.into_var();
1459    let child = txt_parse_live(child, enabled.map(|&b| !b));
1460    on_change_stop(
1461        child,
1462        hn!(|_| {
1463            if enabled.get() {
1464                super::cmd::PARSE_CMD.scoped(WIDGET.id()).notify();
1465            }
1466        }),
1467    )
1468}
1469
1470/// Maximum number of characters that can be input.
1471///
1472/// Zero means no limit. Is zero by default.
1473///
1474/// This property sets the [`MAX_CHARS_COUNT_VAR`].
1475#[property(CONTEXT, default(MAX_CHARS_COUNT_VAR), widget_impl(TextEditMix<P>))]
1476pub fn max_chars_count(child: impl UiNode, max: impl IntoVar<usize>) -> impl UiNode {
1477    with_context_var(child, MAX_CHARS_COUNT_VAR, max)
1478}
1479
1480/// If text has changed but [`txt_parse`] has not tried to parse the new text yet.
1481///
1482/// This can only be `true` if [`txt_parse_live`] is `false`.
1483///
1484/// [`txt_parse`]: fn@super::txt_parse
1485/// [`txt_parse_live`]: fn@txt_parse_live
1486#[property(CONTEXT, default(false), widget_impl(TextEditMix<P>))]
1487pub fn is_parse_pending(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
1488    // reverse context, `txt_parse` sets `TXT_PARSE_PENDING_VAR`
1489    with_context_var(child, TXT_PARSE_PENDING_VAR, state)
1490}
1491
1492/// Called after the text changed and interaction has stopped.
1493///
1494/// The `handler` will be called after change and [`change_stop_delay`] elapses, or the widget loses focus,
1495/// or the [`Key::Enter`] is pressed and [`accepts_enter`] is `false`.
1496///
1497/// [`change_stop_delay`]: fn@change_stop_delay
1498/// [`accepts_enter`]: fn@accepts_enter
1499/// [`Key::Enter`]: zng_ext_input::keyboard::Key::Enter
1500#[property(EVENT, widget_impl(TextEditMix<P>))]
1501pub fn on_change_stop(child: impl UiNode, handler: impl WidgetHandler<ChangeStopArgs>) -> impl UiNode {
1502    super::node::on_change_stop(child, handler)
1503}
1504
1505/// Debounce time for [`on_change_stop`].
1506///
1507/// After the text stops changing and `delay` is elapsed the change stop handled is called, even
1508/// if the widget is still focused.
1509///
1510/// Is `1.secs()` by default.
1511///
1512/// Sets [`CHANGE_STOP_DELAY_VAR`].
1513///
1514/// [`on_change_stop`]: fn@on_change_stop
1515#[property(CONTEXT, default(CHANGE_STOP_DELAY_VAR), widget_impl(TextEditMix<P>))]
1516pub fn change_stop_delay(child: impl UiNode, delay: impl IntoVar<Duration>) -> impl UiNode {
1517    with_context_var(child, CHANGE_STOP_DELAY_VAR, delay)
1518}
1519
1520/// Auto-selection on focus when the text is selectable.
1521///
1522/// If enabled on keyboard focus all text is selected and on blur any selection is cleared.
1523#[property(CONTEXT, default(AUTO_SELECTION_VAR), widget_impl(TextEditMix<P>))]
1524pub fn auto_selection(child: impl UiNode, mode: impl IntoVar<AutoSelection>) -> impl UiNode {
1525    with_context_var(child, AUTO_SELECTION_VAR, mode)
1526}
1527
1528/// Replacement character used when obscuring text.
1529///
1530/// When [`obscure_txt`] is enabled the text characters are replaced by this one.
1531///
1532/// [`obscure_txt`]: fn@obscure_txt
1533#[property(CONTEXT, default(OBSCURING_CHAR_VAR), widget_impl(TextEditMix<P>))]
1534pub fn obscuring_char(child: impl UiNode, character: impl IntoVar<char>) -> impl UiNode {
1535    with_context_var(child, OBSCURING_CHAR_VAR, character)
1536}
1537
1538/// If the typed text is obscured in render.
1539///
1540/// When enabled each text character is replaced with [`obscuring_char`], cut, copy and undo commands are disabled.
1541///
1542/// Note that the text variable is still **plain text** in memory, a memory dump while the widget is filled can leak
1543/// the password, this is a potential security problem shared by apps that accept typed passwords. To mitigate the problem
1544/// don't use automatic crash reports with memory dump, drop the widget and the text variable as soon as possible,
1545/// design the app to show the password widget last to minimize its lifetime.
1546///
1547/// [`obscuring_char`]: fn@obscuring_char
1548#[property(CONTEXT, default(OBSCURE_TXT_VAR), widget_impl(TextEditMix<P>))]
1549pub fn obscure_txt(child: impl UiNode, enabled: impl IntoVar<bool>) -> impl UiNode {
1550    with_context_var(child, OBSCURE_TXT_VAR, enabled)
1551}
1552
1553bitflags! {
1554    /// Defines when text is auto-selected on focus.
1555    #[derive(Clone, Copy, PartialEq, Eq, Debug, serde::Serialize, serde::Deserialize)]
1556    pub struct AutoSelection: u8 {
1557        /// No auto-selection.
1558        const DISABLED = 0;
1559        /// Clear selection on blur if the widget is not the ALT return focus and is not the parent scope return focus.
1560        ///
1561        /// This is the default behavior.
1562        const CLEAR_ON_BLUR = 0b0000_0001;
1563        /// Select all on keyboard initiated focus.
1564        const ALL_ON_FOCUS_KEYBOARD = 0b0000_00010;
1565        /// Select all on pointer release if the pointer press event caused the focus to happen and it did not change
1566        /// the selection and there is no selection on release.
1567        const ALL_ON_FOCUS_POINTER = 0b0000_00100;
1568        /// All auto-selection features enabled.
1569        const ENABLED = 0b1111_1111;
1570    }
1571}
1572impl_from_and_into_var! {
1573    fn from(enabled: bool) -> AutoSelection {
1574        if enabled {
1575            AutoSelection::ENABLED
1576        } else {
1577            AutoSelection::DISABLED
1578        }
1579    }
1580}
1581impl Default for AutoSelection {
1582    fn default() -> Self {
1583        Self::CLEAR_ON_BLUR
1584    }
1585}
1586
1587/// Arguments for [`on_change_stop`].
1588///
1589/// [`on_change_stop`]: fn@on_change_stop
1590#[derive(Debug, Clone)]
1591pub struct ChangeStopArgs {
1592    /// Event cause.
1593    pub cause: ChangeStopCause,
1594}
1595
1596/// Cause of an [`on_change_stop`].
1597///
1598/// [`on_change_stop`]: fn@on_change_stop
1599#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1600pub enum ChangeStopCause {
1601    /// The [`change_stop_delay`] elapsed.
1602    ///
1603    /// [`change_stop_delay`]: fn@change_stop_delay
1604    DelayElapsed,
1605    /// The [`Key::Enter`] was pressed and [`accepts_enter`] is `false`.
1606    ///
1607    /// [`Key::Enter`]: zng_ext_input::keyboard::Key::Enter
1608    /// [`accepts_enter`]: fn@accepts_enter
1609    Enter,
1610    /// The widget lost keyboard focus.
1611    Blur,
1612}
1613
1614/// Display info of edit caret position.
1615#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, serde::Serialize, serde::Deserialize)]
1616pub struct CaretStatus {
1617    index: usize,
1618    line: u32,
1619    column: u32,
1620}
1621impl CaretStatus {
1622    /// Status for text without caret.
1623    pub fn none() -> Self {
1624        Self {
1625            index: usize::MAX,
1626            line: 0,
1627            column: 0,
1628        }
1629    }
1630
1631    /// New position from char index and text.
1632    ///
1633    /// # Panics
1634    ///
1635    /// Panics if `index` is greater then `text` length.
1636    pub fn new(index: usize, text: &SegmentedText) -> Self {
1637        assert!(index <= text.text().len());
1638
1639        if text.text().is_empty() {
1640            Self { line: 1, column: 1, index }
1641        } else {
1642            let mut line = 1;
1643            let mut line_start = 0;
1644            for seg in text.segs() {
1645                if seg.end > index {
1646                    break;
1647                }
1648                if let TextSegmentKind::LineBreak = seg.kind {
1649                    line += 1;
1650                    line_start = seg.end;
1651                }
1652            }
1653
1654            let column = text.text()[line_start..index].chars().count() + 1;
1655
1656            Self {
1657                line,
1658                column: column as _,
1659                index,
1660            }
1661        }
1662    }
1663
1664    /// Char index on the text string, starts a 0, can be the length of the text.
1665    pub fn index(&self) -> Option<usize> {
1666        match self.index {
1667            usize::MAX => None,
1668            i => Some(i),
1669        }
1670    }
1671
1672    /// Display line, starts at 1.
1673    ///
1674    /// Note that this does not count soft line breaks (wrapped lines), this is the actual text line.
1675    pub fn line(&self) -> Option<NonZeroU32> {
1676        NonZeroU32::new(self.line)
1677    }
1678
1679    /// Display column, starts at 1.
1680    ///
1681    /// This is the char count from the start of the text line to the index.
1682    pub fn column(&self) -> Option<NonZeroU32> {
1683        NonZeroU32::new(self.column)
1684    }
1685}
1686impl fmt::Display for CaretStatus {
1687    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1688        if self.index().is_some() {
1689            write!(f, "Ln {}, Col {}", self.line, self.column)
1690        } else {
1691            Ok(())
1692        }
1693    }
1694}
1695
1696/// Represents the number of lines and number of wrap lines in a text.
1697#[derive(Debug, Clone, PartialEq, Eq)]
1698pub enum LinesWrapCount {
1699    /// No line wrap.
1700    ///
1701    /// The associated value is the number of lines.
1702    NoWrap(usize),
1703    /// Some text lines have more than one wrap line.
1704    ///
1705    /// The associated value is a vec of wrap-line count for each text line, is `1` for lines that don't wrap.
1706    Wrap(Vec<u32>),
1707}
1708impl LinesWrapCount {
1709    /// Gets the number of text lines.
1710    pub fn lines_len(&self) -> usize {
1711        match self {
1712            Self::NoWrap(l) => *l,
1713            Self::Wrap(lns) => lns.len(),
1714        }
1715    }
1716}
1717
1718/// Text paragraph properties.
1719///
1720/// Note that the [`Text!`] widget does not include this mix-in, as raw text does not encode
1721/// paragraph breaks, other rich text widgets can include it to configure paragraphs.
1722///
1723/// [`Text!`]: struct@crate::Text
1724#[widget_mixin]
1725pub struct ParagraphMix<P>(P);
1726
1727context_var! {
1728    /// Extra paragraph spacing of text blocks.
1729    pub static PARAGRAPH_SPACING_VAR: ParagraphSpacing = 1.em();
1730}
1731
1732impl ParagraphMix<()> {
1733    /// Insert context variables used by properties in this mix-in.
1734    pub fn context_vars_set(set: &mut ContextValueSet) {
1735        set.insert(&PARAGRAPH_SPACING_VAR);
1736    }
1737}
1738
1739/// Extra spacing in-between paragraphs.
1740///
1741/// The default value is `1.em()`. Note that the [`Text!`] widget does not implement this property, as raw text does not encode
1742/// paragraph breaks, this property and context var exists to configure *rich-text* widgets, like the `Markdown!` widget.
1743///
1744/// Sets the [`PARAGRAPH_SPACING_VAR`].
1745///
1746/// [`Text!`]: struct@crate::Text
1747#[property(CONTEXT, default(PARAGRAPH_SPACING_VAR), widget_impl(ParagraphMix<P>))]
1748pub fn paragraph_spacing(child: impl UiNode, extra: impl IntoVar<ParagraphSpacing>) -> impl UiNode {
1749    with_context_var(child, PARAGRAPH_SPACING_VAR, extra)
1750}
1751
1752/// Selection toolbar properties.
1753#[widget_mixin]
1754pub struct SelectionToolbarMix<P>(P);
1755
1756context_var! {
1757    /// Selection toolbar function.
1758    pub static SELECTION_TOOLBAR_FN_VAR: WidgetFn<SelectionToolbarArgs> = WidgetFn::nil();
1759    /// Position the selection toolbar in relation to the bounding box of all selection rectangles.
1760    pub static SELECTION_TOOLBAR_ANCHOR_VAR: AnchorOffset = AnchorOffset::out_top();
1761}
1762
1763impl SelectionToolbarMix<()> {
1764    /// Insert context variables used by properties in this mix-in.
1765    pub fn context_vars_set(set: &mut ContextValueSet) {
1766        set.insert(&SELECTION_TOOLBAR_FN_VAR);
1767        set.insert(&SELECTION_TOOLBAR_ANCHOR_VAR);
1768    }
1769}
1770
1771/// Defines the floating mini-toolbar that shows near a new text selection.
1772///
1773/// The `toolbar` is used
1774#[property(CONTEXT, widget_impl(SelectionToolbarMix<P>))]
1775pub fn selection_toolbar(child: impl UiNode, toolbar: impl UiNode) -> impl UiNode {
1776    selection_toolbar_fn(child, WidgetFn::singleton(toolbar))
1777}
1778
1779/// Defines the floating mini-toolbar that shows near a new text selection.
1780///
1781/// Sets the [`SELECTION_TOOLBAR_FN_VAR`].
1782#[property(CONTEXT, default(SELECTION_TOOLBAR_FN_VAR), widget_impl(SelectionToolbarMix<P>))]
1783pub fn selection_toolbar_fn(child: impl UiNode, toolbar: impl IntoVar<WidgetFn<SelectionToolbarArgs>>) -> impl UiNode {
1784    with_context_var(child, SELECTION_TOOLBAR_FN_VAR, toolbar)
1785}
1786
1787/// Arguments for [`selection_toolbar_fn`].
1788///
1789/// [`selection_toolbar_fn`]: fn@selection_toolbar_fn
1790pub struct SelectionToolbarArgs {
1791    /// ID of the widget the toolbar is anchored to.
1792    pub anchor_id: WidgetId,
1793    /// Text was selected through touch interaction.
1794    pub is_touch: bool,
1795}
1796
1797/// Position the selection toolbar in relation to the bounding box of all selection rectangles.
1798///
1799/// See [`selection_toolbar_fn`](fn@selection_toolbar_fn).
1800///
1801/// Sets the [`SELECTION_TOOLBAR_ANCHOR_VAR`].
1802#[property(CONTEXT, default(SELECTION_TOOLBAR_ANCHOR_VAR), widget_impl(SelectionToolbarMix<P>))]
1803pub fn selection_toolbar_anchor(child: impl UiNode, offset: impl IntoVar<AnchorOffset>) -> impl UiNode {
1804    with_context_var(child, SELECTION_TOOLBAR_ANCHOR_VAR, offset)
1805}
1806
1807/// Properties that probes various state from the text widget.
1808#[widget_mixin]
1809pub struct TextInspectMix<P>(P);
1810
1811impl TextInspectMix<()> {
1812    /// Insert context variables used by properties in this mix-in.
1813    pub fn context_vars_set(set: &mut ContextValueSet) {
1814        let _ = set;
1815    }
1816}
1817
1818/// Gets the caret char index, if the text is editable.
1819#[property(EVENT, default(None), widget_impl(TextInspectMix<P>))]
1820pub fn get_caret_index(child: impl UiNode, index: impl IntoVar<Option<CaretIndex>>) -> impl UiNode {
1821    super::node::get_caret_index(child, index)
1822}
1823
1824/// Gets the caret display status, if the text is editable.
1825#[property(EVENT, default(CaretStatus::none()), widget_impl(TextInspectMix<P>))]
1826pub fn get_caret_status(child: impl UiNode, status: impl IntoVar<CaretStatus>) -> impl UiNode {
1827    super::node::get_caret_status(child, status)
1828}
1829
1830/// Gets the number of lines in the text, including wrap lines.
1831///
1832/// This is very cheap, the text widget already has the length, but it does include wrapped lines. You
1833/// can use [`get_lines_wrap_count`] to get text lines and a count of wrapped lines for each.
1834///
1835/// [`get_lines_wrap_count`]: fn@get_lines_wrap_count
1836#[property(CHILD_LAYOUT+100, default(0), widget_impl(TextInspectMix<P>))]
1837pub fn get_lines_len(child: impl UiNode, len: impl IntoVar<usize>) -> impl UiNode {
1838    super::node::get_lines_len(child, len)
1839}
1840
1841/// Gets the number of wrap lines per text lines.
1842#[property(CHILD_LAYOUT+100, default(LinesWrapCount::NoWrap(0)), widget_impl(TextInspectMix<P>))]
1843pub fn get_lines_wrap_count(child: impl UiNode, lines: impl IntoVar<LinesWrapCount>) -> impl UiNode {
1844    super::node::get_lines_wrap_count(child, lines)
1845}
1846
1847/// Gets the number of character in the text.
1848#[property(EVENT, default(0), widget_impl(TextInspectMix<P>))]
1849pub fn get_chars_count(child: impl UiNode, chars: impl IntoVar<usize>) -> impl UiNode {
1850    let chars = chars.into_var();
1851    match_node(child, move |_, op| {
1852        if let UiNodeOp::Init = op {
1853            let ctx = super::node::TEXT.resolved();
1854            let _ = chars.set_from_map(&ctx.txt, |t| t.chars().count());
1855            let handle = ctx.txt.bind_map(&chars, |t| t.chars().count());
1856            WIDGET.push_var_handle(handle);
1857        }
1858    })
1859}
1860
1861/// Highlight a text range.
1862///
1863/// This property must be set in the text widget.
1864#[property(CHILD_LAYOUT+100, widget_impl(TextInspectMix<P>))]
1865pub fn txt_highlight(child: impl UiNode, range: impl IntoVar<std::ops::Range<CaretIndex>>, color: impl IntoVar<Rgba>) -> impl UiNode {
1866    let range = range.into_var();
1867    let color = color.into_var();
1868    let color_key = FrameValueKey::new_unique();
1869    match_node(child, move |_, op| match op {
1870        UiNodeOp::Init => {
1871            WIDGET.sub_var_render(&range).sub_var_render_update(&color);
1872        }
1873        UiNodeOp::Render { frame } => {
1874            let l_txt = super::node::TEXT.laidout();
1875            let r_txt = super::node::TEXT.resolved();
1876            let r_txt = r_txt.segmented_text.text();
1877
1878            for line_rect in l_txt.shaped_text.highlight_rects(range.get(), r_txt) {
1879                frame.push_color(line_rect, color_key.bind_var(&color, |c| *c));
1880            }
1881        }
1882        UiNodeOp::RenderUpdate { update } => {
1883            if let Some(color_update) = color_key.update_var(&color, |c| *c) {
1884                update.update_color(color_update)
1885            }
1886        }
1887        _ => {}
1888    })
1889}
1890
1891/// Gets a vector of font and ranges.
1892///
1893/// This property must be set in the text widget.
1894#[property(CHILD_LAYOUT+100, widget_impl(TextInspectMix<P>))]
1895pub fn get_font_use(child: impl UiNode, font_use: impl IntoVar<Vec<(Font, std::ops::Range<usize>)>>) -> impl UiNode {
1896    let font_use = font_use.into_var();
1897    let mut shaped_text_version = u32::MAX;
1898    match_node(child, move |c, op| {
1899        if let UiNodeOp::Layout { wl, final_size } = op {
1900            *final_size = c.layout(wl);
1901
1902            let ctx = crate::node::TEXT.laidout();
1903
1904            if ctx.shaped_text_version != shaped_text_version && font_use.capabilities().can_modify() {
1905                shaped_text_version = ctx.shaped_text_version;
1906
1907                let mut r = vec![];
1908
1909                for seg in ctx.shaped_text.lines().flat_map(|l| l.segs()) {
1910                    let mut seg_glyph_i = 0;
1911                    let seg_range = seg.text_range();
1912
1913                    for (font, glyphs) in seg.glyphs() {
1914                        if r.is_empty() {
1915                            r.push((font.clone(), 0..seg_range.end));
1916                        } else {
1917                            let last_i = r.len() - 1;
1918                            if &r[last_i].0 != font {
1919                                let seg_char_i = seg.clusters()[seg_glyph_i] as usize;
1920
1921                                let char_i = seg_range.start + seg_char_i;
1922                                r[last_i].1.end = char_i;
1923                                r.push((font.clone(), char_i..seg_range.end));
1924                            }
1925                        }
1926                        seg_glyph_i += glyphs.len();
1927                    }
1928                }
1929
1930                let _ = font_use.set(r);
1931            }
1932        }
1933    })
1934}