zng_wgt_ansi_text/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! ANSI text widget, properties and nodes.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14use zng_ext_font::*;
15use zng_wgt::{prelude::*, *};
16use zng_wgt_fill::*;
17use zng_wgt_filter::*;
18use zng_wgt_scroll::{LazyMode, lazy};
19use zng_wgt_stack::{Stack, StackDirection};
20use zng_wgt_text::*;
21
22#[doc(hidden)]
23pub use zng_wgt_text::__formatx;
24
25/// Render text styled using ANSI escape sequences.
26///
27/// Supports color, weight, italic and more, see [`AnsiStyle`] for the full style supported.
28#[widget($crate::AnsiText {
29    ($txt:literal) => {
30        txt = $crate::__formatx!($txt);
31    };
32    ($txt:expr) => {
33        txt = $txt;
34    };
35    ($txt:tt, $($format:tt)*) => {
36        txt = $crate::__formatx!($txt, $($format)*);
37    };
38})]
39#[rustfmt::skip]
40pub struct AnsiText(
41    FontMix<
42    TextSpacingMix<
43    ParagraphMix<
44    LangMix<
45    WidgetBase
46    >>>>
47);
48impl AnsiText {
49    fn widget_intrinsic(&mut self) {
50        widget_set! {
51            self;
52            font_family = ["JetBrains Mono", "Consolas", "monospace"];
53        };
54
55        self.widget_builder().push_build_action(|wgt| {
56            let txt = wgt.capture_var_or_default(property_id!(txt));
57            let child = ansi_node(txt);
58            wgt.set_child(child.boxed());
59        });
60    }
61
62    widget_impl! {
63        /// ANSI text.
64        pub txt(text: impl IntoVar<Txt>);
65    }
66}
67
68pub use ansi_parse::*;
69mod ansi_parse {
70
71    use super::*;
72
73    /// Represents a segment of ANSI styled text that shares the same style.
74    #[derive(Debug)]
75    pub struct AnsiTxt<'a> {
76        /// Text run.
77        pub txt: &'a str,
78        /// Text style.
79        pub style: AnsiStyle,
80    }
81
82    /// Represents the ANSI style of a text run.
83    ///
84    /// See [`AnsiText`](struct@super::AnsiText) for more details.
85    #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
86    pub struct AnsiStyle {
87        /// Background color.
88        pub background_color: AnsiColor,
89        /// Font color.
90        pub color: AnsiColor,
91        /// Font weight.
92        pub weight: AnsiWeight,
93        /// Font italic.
94        pub italic: bool,
95        /// Underline.
96        pub underline: bool,
97        /// Strikethrough.
98        pub strikethrough: bool,
99        /// Negative color.
100        pub invert_color: bool,
101        /// Visibility.
102        pub hidden: bool,
103        /// Blink animation.
104        pub blink: bool,
105    }
106    impl Default for AnsiStyle {
107        fn default() -> Self {
108            Self {
109                background_color: AnsiColor::Black,
110                color: AnsiColor::White,
111                weight: Default::default(),
112                italic: false,
113                underline: false,
114                strikethrough: false,
115                invert_color: false,
116                hidden: false,
117                blink: false,
118            }
119        }
120    }
121
122    /// Named ANSI color.
123    ///
124    /// See [`AnsiStyle`] for more details.
125    #[allow(missing_docs)]
126    #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
127    pub enum AnsiColor {
128        Black,
129        Red,
130        Green,
131        Yellow,
132        Blue,
133        Magenta,
134        Cyan,
135        White,
136        /// Gray
137        BrightBlack,
138        BrightRed,
139        BrightGreen,
140        BrightYellow,
141        BrightBlue,
142        BrightMagenta,
143        BrightCyan,
144        BrightWhite,
145        /// 8-bit lookup.
146        Ansi256(u8),
147        /// RGB
148        TrueColor(u8, u8, u8),
149    }
150    impl_from_and_into_var! {
151        fn from(color: AnsiColor) -> Rgba {
152            match color {
153                AnsiColor::Black => rgb(0, 0, 0),
154                AnsiColor::Red => rgb(205, 49, 49),
155                AnsiColor::Green => rgb(13, 188, 121),
156                AnsiColor::Yellow => rgb(229, 229, 16),
157                AnsiColor::Blue => rgb(36, 114, 200),
158                AnsiColor::Magenta => rgb(188, 63, 188),
159                AnsiColor::Cyan => rgb(17, 168, 205),
160                AnsiColor::White => rgb(229, 229, 229),
161                AnsiColor::BrightBlack => rgb(102, 102, 102),
162                AnsiColor::BrightRed => rgb(241, 76, 76),
163                AnsiColor::BrightGreen => rgb(35, 209, 139),
164                AnsiColor::BrightYellow => rgb(245, 245, 67),
165                AnsiColor::BrightBlue => rgb(59, 142, 234),
166                AnsiColor::BrightMagenta => rgb(214, 112, 214),
167                AnsiColor::BrightCyan => rgb(41, 184, 219),
168                AnsiColor::BrightWhite => rgb(229, 229, 229),
169                AnsiColor::Ansi256(c) => {
170                    let (r, g, b) = X_TERM_256[c as usize];
171                    rgb(r, g, b)
172                }
173                AnsiColor::TrueColor(r, g, b) => rgb(r, g, b),
174            }
175        }
176    }
177
178    /// Font weight defined by ANSI escape codes.
179    ///
180    /// See [`AnsiStyle`] for more details.
181    #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
182    pub enum AnsiWeight {
183        /// Normal.
184        Normal,
185        /// Bold.
186        Bold,
187        /// Light.
188        Faint,
189    }
190    impl Default for AnsiWeight {
191        fn default() -> Self {
192            Self::Normal
193        }
194    }
195    impl_from_and_into_var! {
196        fn from(weight: AnsiWeight) -> FontWeight {
197            match weight {
198                AnsiWeight::Normal => FontWeight::NORMAL,
199                AnsiWeight::Bold => FontWeight::BOLD,
200                AnsiWeight::Faint => FontWeight::LIGHT,
201            }
202        }
203    }
204
205    /// Iterator that parses ANSI escape codes.
206    ///
207    /// This is the pull style parser used internally by the [`AnsiText!`] widget.
208    ///
209    /// [`AnsiText!`]: struct@crate::AnsiText
210    pub struct AnsiTextParser<'a> {
211        source: &'a str,
212        /// Current style.
213        pub style: AnsiStyle,
214    }
215    impl<'a> AnsiTextParser<'a> {
216        /// New parsing iterator.
217        pub fn new(source: &'a str) -> Self {
218            Self {
219                source,
220                style: AnsiStyle::default(),
221            }
222        }
223    }
224    impl<'a> Iterator for AnsiTextParser<'a> {
225        type Item = AnsiTxt<'a>;
226
227        fn next(&mut self) -> Option<Self::Item> {
228            const CSI: &str = "\x1b[";
229
230            fn is_esc_end(byte: u8) -> bool {
231                (0x40..=0x7e).contains(&byte)
232            }
233
234            loop {
235                if self.source.is_empty() {
236                    return None;
237                } else if let Some(source) = self.source.strip_prefix(CSI) {
238                    let mut esc_end = 0;
239                    while esc_end < source.len() && !is_esc_end(source.as_bytes()[esc_end]) {
240                        esc_end += 1;
241                    }
242                    esc_end += 1;
243
244                    let (esc, source) = source.split_at(esc_end);
245
246                    let esc = &esc[..(esc.len() - 1)];
247                    self.style.set(esc);
248
249                    self.source = source;
250                    continue;
251                } else if let Some(i) = self.source.find(CSI) {
252                    let (txt, source) = self.source.split_at(i);
253                    self.source = source;
254                    return Some(AnsiTxt {
255                        txt,
256                        style: self.style.clone(),
257                    });
258                } else {
259                    return Some(AnsiTxt {
260                        txt: std::mem::take(&mut self.source),
261                        style: self.style.clone(),
262                    });
263                }
264            }
265        }
266    }
267
268    impl AnsiStyle {
269        fn set(&mut self, esc_codes: &str) {
270            let mut esc_codes = esc_codes.split(';');
271            while let Some(code) = esc_codes.next() {
272                match code {
273                    "0" => *self = Self::default(),
274                    "1" => self.weight = AnsiWeight::Bold,
275                    "2" => self.weight = AnsiWeight::Faint,
276                    "3" => self.italic = true,
277                    "4" => self.underline = true,
278                    "5" => self.blink = true,
279                    "7" => self.invert_color = true,
280                    "8" => self.hidden = true,
281                    "9" => self.strikethrough = true,
282                    "22" => self.weight = AnsiWeight::Normal,
283                    "23" => self.italic = false,
284                    "24" => self.underline = false,
285                    "25" => self.blink = false,
286                    "27" => self.invert_color = false,
287                    "28" => self.hidden = false,
288                    "29" => self.strikethrough = false,
289                    "30" => self.color = AnsiColor::Black,
290                    "31" => self.color = AnsiColor::Red,
291                    "32" => self.color = AnsiColor::Green,
292                    "33" => self.color = AnsiColor::Yellow,
293                    "34" => self.color = AnsiColor::Blue,
294                    "35" => self.color = AnsiColor::Magenta,
295                    "36" => self.color = AnsiColor::Cyan,
296                    "37" => self.color = AnsiColor::White,
297                    "40" => self.color = AnsiColor::Black,
298                    "41" => self.color = AnsiColor::Red,
299                    "42" => self.color = AnsiColor::Green,
300                    "43" => self.color = AnsiColor::Yellow,
301                    "44" => self.color = AnsiColor::Blue,
302                    "45" => self.color = AnsiColor::Magenta,
303                    "46" => self.color = AnsiColor::Cyan,
304                    "47" => self.color = AnsiColor::White,
305                    "90" => self.color = AnsiColor::BrightBlack,
306                    "91" => self.color = AnsiColor::BrightRed,
307                    "92" => self.color = AnsiColor::BrightGreen,
308                    "93" => self.color = AnsiColor::BrightYellow,
309                    "94" => self.color = AnsiColor::BrightBlue,
310                    "95" => self.color = AnsiColor::BrightMagenta,
311                    "96" => self.color = AnsiColor::BrightCyan,
312                    "97" => self.color = AnsiColor::BrightWhite,
313                    "100" => self.background_color = AnsiColor::BrightBlack,
314                    "101" => self.background_color = AnsiColor::BrightRed,
315                    "102" => self.background_color = AnsiColor::BrightGreen,
316                    "103" => self.background_color = AnsiColor::BrightYellow,
317                    "104" => self.background_color = AnsiColor::BrightBlue,
318                    "105" => self.background_color = AnsiColor::BrightMagenta,
319                    "106" => self.background_color = AnsiColor::BrightCyan,
320                    "107" => self.background_color = AnsiColor::BrightWhite,
321                    "38" | "48" => {
322                        let target = if code == "38" {
323                            &mut self.color
324                        } else {
325                            &mut self.background_color
326                        };
327                        match esc_codes.next() {
328                            Some("5") => {
329                                let c = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
330                                *target = AnsiColor::Ansi256(c)
331                            }
332                            Some("2") => {
333                                let r = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
334                                let g = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
335                                let b = esc_codes.next().and_then(|c| c.parse().ok()).unwrap_or(0);
336
337                                *target = AnsiColor::TrueColor(r, g, b);
338                            }
339                            _ => {}
340                        }
341                    }
342                    _ => (),
343                }
344            }
345        }
346    }
347}
348
349pub use ansi_fn::*;
350mod ansi_fn {
351    use std::time::Duration;
352
353    use super::{AnsiColor, AnsiStyle, AnsiWeight};
354
355    use super::*;
356
357    /// Arguments for a widget function for an ANSI styled text fragment.
358    ///
359    /// See [`TEXT_FN_VAR`] for more details.
360    pub struct TextFnArgs {
361        /// The text.
362        pub txt: Txt,
363        /// The ANSI style.
364        pub style: AnsiStyle,
365    }
366
367    /// Arguments for a widget function for a text line.
368    ///
369    /// See [`LINE_FN_VAR`] for more details.
370    pub struct LineFnArgs {
371        /// Zero-counted global index of this line.
372        pub index: u32,
373        /// Zero-counted index of this line in the parent page.
374        pub page_index: u32,
375        /// Text segment widgets, generated by [`TEXT_FN_VAR`].
376        pub text: UiVec,
377    }
378
379    /// Arguments for a widget function for a stack of lines.
380    ///
381    /// See [`PAGE_FN_VAR`] for more details.
382    pub struct PageFnArgs {
383        /// Zero-counted index of this page.
384        pub index: u32,
385
386        /// Line widgets, generated by [`LINE_FN_VAR`].
387        pub lines: UiVec,
388    }
389
390    /// Arguments for a widget function for a stack of pages.
391    ///
392    /// See [`PANEL_FN_VAR`] for more details.
393    pub struct PanelFnArgs {
394        /// Page widgets, generated by [`PAGE_FN_VAR`].
395        pub pages: UiVec,
396    }
397
398    context_var! {
399        /// Widget function for [`TextFnArgs`].
400        ///
401        /// The returned widgets are layout by the [`LINE_FN_VAR`]. The default view is [`default_text_fn`].
402        pub static TEXT_FN_VAR: WidgetFn<TextFnArgs> = wgt_fn!(|args: TextFnArgs| { default_text_fn(args) });
403
404        /// Widget function for [`LineFnArgs`].
405        ///
406        /// The returned widgets are layout by the [`PAGE_FN_VAR`]. The default view is [`default_line_fn`].
407        pub static LINE_FN_VAR: WidgetFn<LineFnArgs> = wgt_fn!(|args: LineFnArgs| { default_line_fn(args) });
408
409        /// Widget function for [`PageFnArgs`].
410        ///
411        /// The returned widgets are layout by the [`PANEL_FN_VAR`] widget. The default view is [`default_page_fn`].
412        pub static PAGE_FN_VAR: WidgetFn<PageFnArgs> = wgt_fn!(|args: PageFnArgs| { default_page_fn(args) });
413
414        /// Widget function for [`PanelFnArgs`].
415        ///
416        /// The returned view is the [`AnsiText!`] child. The default is [`default_panel_fn`].
417        ///
418        /// [`AnsiText!`]: struct@super::AnsiText
419        pub static PANEL_FN_VAR: WidgetFn<PanelFnArgs> = wgt_fn!(|args: PanelFnArgs| { default_panel_fn(args) });
420
421        /// Duration the ANSI blink animation keeps the text visible for.
422        ///
423        /// Set to `ZERO` or `MAX` to disable animation.
424        pub static BLINK_INTERVAL_VAR: Duration = Duration::ZERO;
425
426        /// Maximum number of lines per [`PAGE_FN_VAR`].
427        ///
428        /// Is `200` by default.
429        pub static LINES_PER_PAGE_VAR: u32 = 200;
430    }
431
432    /// Default [`TEXT_FN_VAR`].
433    ///
434    /// This view is configured by contextual variables like [`BLINK_INTERVAL_VAR`] and all text variables that are
435    /// not overridden by the ANSI style, like the font.
436    ///
437    /// Returns a `Text!` with the text and style.
438    pub fn default_text_fn(args: TextFnArgs) -> impl UiNode {
439        let mut text = Text::widget_new();
440
441        widget_set! {
442            &mut text;
443            txt = args.txt;
444        }
445
446        if args.style.background_color != AnsiColor::Black {
447            widget_set! {
448                &mut text;
449                background_color = args.style.background_color;
450            }
451        }
452        if args.style.color != AnsiColor::White {
453            widget_set! {
454                &mut text;
455                font_color = args.style.color;
456            }
457        }
458
459        if args.style.weight != AnsiWeight::Normal {
460            widget_set! {
461                &mut text;
462                font_weight = args.style.weight;
463            }
464        }
465        if args.style.italic {
466            widget_set! {
467                &mut text;
468                font_style = FontStyle::Italic;
469            }
470        }
471
472        if args.style.underline {
473            widget_set! {
474                &mut text;
475                underline = 1, LineStyle::Solid;
476            }
477        }
478        if args.style.strikethrough {
479            widget_set! {
480                &mut text;
481                strikethrough = 1, LineStyle::Solid;
482            }
483        }
484
485        if args.style.invert_color {
486            widget_set! {
487                &mut text;
488                invert_color = true;
489            }
490        }
491
492        if args.style.hidden {
493            widget_set! {
494                &mut text;
495                visibility = Visibility::Hidden;
496            }
497        }
498        if args.style.blink && !args.style.hidden {
499            let opacity = var(1.fct());
500
501            let interval = BLINK_INTERVAL_VAR.get();
502            if interval != Duration::ZERO && interval != Duration::MAX {
503                opacity.step_oci(0.fct(), interval).perm();
504
505                widget_set! {
506                    &mut text;
507                    opacity;
508                }
509            }
510        }
511
512        text.widget_build()
513    }
514
515    /// Default [`LINE_FN_VAR`].
516    ///
517    /// Returns a `Wrap!` for text with multiple segments, or returns the single segment, or an empty text.
518    pub fn default_line_fn(mut args: LineFnArgs) -> impl UiNode {
519        use crate::prelude::*;
520
521        if args.text.is_empty() {
522            Text!("").boxed()
523        } else if args.text.len() == 1 {
524            args.text.remove(0)
525        } else {
526            Stack! {
527                direction = StackDirection::start_to_end();
528                children = args.text;
529            }
530            .boxed()
531        }
532    }
533
534    /// Default [`PAGE_FN_VAR`].
535    ///
536    /// Returns a `Stack!` for multiple lines, or return the single line, or a nil node.
537    pub fn default_page_fn(mut args: PageFnArgs) -> impl UiNode {
538        use crate::prelude::*;
539
540        if args.lines.is_empty() {
541            NilUiNode.boxed()
542        } else if args.lines.len() == 1 {
543            args.lines.remove(0)
544        } else {
545            let len = args.lines.len();
546            Stack! {
547                direction = StackDirection::top_to_bottom();
548                children = args.lines;
549                lazy = LazyMode::lazy_vertical(wgt_fn!(|_| {
550                    let height_sample = zng_wgt_text::node::line_placeholder(50);
551                    zng_wgt_stack::lazy_sample(len, StackDirection::top_to_bottom(), 0, height_sample)
552                }));
553            }
554            .boxed()
555        }
556    }
557
558    /// Default [`PANEL_FN_VAR`].
559    ///
560    /// Returns a `Stack!` for multiple pages, or returns the single page, or a nil node.
561    pub fn default_panel_fn(mut args: PanelFnArgs) -> impl UiNode {
562        use crate::prelude::*;
563
564        if args.pages.is_empty() {
565            NilUiNode.boxed()
566        } else if args.pages.len() == 1 {
567            args.pages.remove(0)
568        } else {
569            Stack! {
570                direction = StackDirection::top_to_bottom();
571                children = args.pages;
572            }
573            .boxed()
574        }
575    }
576
577    /// ANSI blink animation interval.
578    ///
579    /// Set to `ZERO` to disable the blink animation.
580    ///
581    /// Sets the [`BLINK_INTERVAL_VAR`].
582    #[property(CONTEXT, default(BLINK_INTERVAL_VAR), widget_impl(AnsiText))]
583    pub fn blink_interval(child: impl UiNode, interval: impl IntoVar<Duration>) -> impl UiNode {
584        with_context_var(child, BLINK_INTERVAL_VAR, interval)
585    }
586
587    /// Widget function that converts [`TextFnArgs`] to widgets.
588    ///
589    /// Sets the [`TEXT_FN_VAR`].
590    #[property(CONTEXT, default(TEXT_FN_VAR), widget_impl(AnsiText))]
591    pub fn text_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<TextFnArgs>>) -> impl UiNode {
592        with_context_var(child, TEXT_FN_VAR, wgt_fn)
593    }
594
595    /// Widget function that converts [`LineFnArgs`] to widgets.
596    ///
597    /// Sets the [`LINE_FN_VAR`].
598    #[property(CONTEXT, default(LINE_FN_VAR), widget_impl(AnsiText))]
599    pub fn line_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<LineFnArgs>>) -> impl UiNode {
600        with_context_var(child, LINE_FN_VAR, wgt_fn)
601    }
602
603    /// Widget function that converts [`PageFnArgs`] to widgets.
604    ///
605    /// A *page* is a stack of a maximum of [`lines_per_page`], the text is split in pages mostly for performance reasons.
606    ///
607    /// Sets the [`PAGE_FN_VAR`].
608    ///
609    /// [`lines_per_page`]: fn@lines_per_page
610    #[property(CONTEXT, default(PAGE_FN_VAR), widget_impl(AnsiText))]
611    pub fn page_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<PageFnArgs>>) -> impl UiNode {
612        with_context_var(child, PAGE_FN_VAR, wgt_fn)
613    }
614
615    /// Widget function that converts [`PanelFnArgs`] to widgets.
616    #[property(CONTEXT, default(PANEL_FN_VAR), widget_impl(AnsiText))]
617    pub fn panel_fn(child: impl UiNode, wgt_fn: impl IntoVar<WidgetFn<PanelFnArgs>>) -> impl UiNode {
618        with_context_var(child, PANEL_FN_VAR, wgt_fn)
619    }
620
621    /// Maximum number of lines per page view.
622    ///
623    /// Sets the [`LINES_PER_PAGE_VAR`].
624    #[property(CONTEXT, default(LINES_PER_PAGE_VAR), widget_impl(AnsiText))]
625    pub fn lines_per_page(child: impl UiNode, count: impl IntoVar<u32>) -> impl UiNode {
626        with_context_var(child, LINES_PER_PAGE_VAR, count)
627    }
628}
629
630fn generate_ansi(txt: &impl Var<Txt>) -> BoxedUiNode {
631    use ansi_fn::*;
632    use std::mem;
633
634    txt.with(|txt| {
635        let text_fn = TEXT_FN_VAR.get();
636        let line_fn = LINE_FN_VAR.get();
637        let page_fn = PAGE_FN_VAR.get();
638        let panel_fn = PANEL_FN_VAR.get();
639        let lines_per_page = LINES_PER_PAGE_VAR.get() as usize;
640
641        let mut pages = Vec::with_capacity(4);
642        let mut lines = Vec::with_capacity(50);
643
644        for (i, line) in txt.lines().enumerate() {
645            let text = ansi_parse::AnsiTextParser::new(line)
646                .filter_map(|txt| {
647                    text_fn.call_checked(TextFnArgs {
648                        txt: txt.txt.to_txt(),
649                        style: txt.style,
650                    })
651                })
652                .collect();
653
654            lines.push(line_fn(LineFnArgs {
655                index: i as u32,
656                page_index: lines.len() as u32,
657                text,
658            }));
659
660            if lines.len() == lines_per_page {
661                let lines = mem::replace(&mut lines, Vec::with_capacity(50));
662                pages.push(page_fn(PageFnArgs {
663                    index: pages.len() as u32,
664                    lines: lines.into(),
665                }));
666            }
667        }
668
669        if !lines.is_empty() {
670            pages.push(page_fn(PageFnArgs {
671                index: pages.len() as u32,
672                lines: lines.into(),
673            }));
674        }
675
676        panel_fn(PanelFnArgs { pages: pages.into() })
677    })
678}
679
680/// Implements the ANSI parsing and view generation, configured by contextual properties.
681pub fn ansi_node(txt: impl IntoVar<Txt>) -> impl UiNode {
682    let txt = txt.into_var();
683    match_node(NilUiNode.boxed(), move |c, op| match op {
684        UiNodeOp::Init => {
685            WIDGET
686                .sub_var(&txt)
687                .sub_var(&TEXT_FN_VAR)
688                .sub_var(&LINE_FN_VAR)
689                .sub_var(&PAGE_FN_VAR)
690                .sub_var(&PANEL_FN_VAR)
691                .sub_var(&LINES_PER_PAGE_VAR)
692                .sub_var(&BLINK_INTERVAL_VAR);
693
694            *c.child() = generate_ansi(&txt);
695        }
696        UiNodeOp::Deinit => {
697            c.deinit();
698            *c.child() = NilUiNode.boxed();
699        }
700        UiNodeOp::Update { .. } => {
701            use ansi_fn::*;
702
703            if txt.is_new()
704                || TEXT_FN_VAR.is_new()
705                || LINE_FN_VAR.is_new()
706                || PAGE_FN_VAR.is_new()
707                || PANEL_FN_VAR.is_new()
708                || LINES_PER_PAGE_VAR.is_new()
709                || BLINK_INTERVAL_VAR.is_new()
710            {
711                c.child().deinit();
712                *c.child() = generate_ansi(&txt);
713                c.child().init();
714                WIDGET.update_info().layout().render();
715            }
716        }
717        _ => {}
718    })
719}
720
721static X_TERM_256: [(u8, u8, u8); 256] = [
722    (0, 0, 0),
723    (128, 0, 0),
724    (0, 128, 0),
725    (128, 128, 0),
726    (0, 0, 128),
727    (128, 0, 128),
728    (0, 128, 128),
729    (192, 192, 192),
730    (128, 128, 128),
731    (255, 0, 0),
732    (0, 255, 0),
733    (255, 255, 0),
734    (0, 0, 255),
735    (255, 0, 255),
736    (0, 255, 255),
737    (255, 255, 255),
738    (0, 0, 0),
739    (0, 0, 95),
740    (0, 0, 135),
741    (0, 0, 175),
742    (0, 0, 215),
743    (0, 0, 255),
744    (0, 95, 0),
745    (0, 95, 95),
746    (0, 95, 135),
747    (0, 95, 175),
748    (0, 95, 215),
749    (0, 95, 255),
750    (0, 135, 0),
751    (0, 135, 95),
752    (0, 135, 135),
753    (0, 135, 175),
754    (0, 135, 215),
755    (0, 135, 255),
756    (0, 175, 0),
757    (0, 175, 95),
758    (0, 175, 135),
759    (0, 175, 175),
760    (0, 175, 215),
761    (0, 175, 255),
762    (0, 215, 0),
763    (0, 215, 95),
764    (0, 215, 135),
765    (0, 215, 175),
766    (0, 215, 215),
767    (0, 215, 255),
768    (0, 255, 0),
769    (0, 255, 95),
770    (0, 255, 135),
771    (0, 255, 175),
772    (0, 255, 215),
773    (0, 255, 255),
774    (95, 0, 0),
775    (95, 0, 95),
776    (95, 0, 135),
777    (95, 0, 175),
778    (95, 0, 215),
779    (95, 0, 255),
780    (95, 95, 0),
781    (95, 95, 95),
782    (95, 95, 135),
783    (95, 95, 175),
784    (95, 95, 215),
785    (95, 95, 255),
786    (95, 135, 0),
787    (95, 135, 95),
788    (95, 135, 135),
789    (95, 135, 175),
790    (95, 135, 215),
791    (95, 135, 255),
792    (95, 175, 0),
793    (95, 175, 95),
794    (95, 175, 135),
795    (95, 175, 175),
796    (95, 175, 215),
797    (95, 175, 255),
798    (95, 215, 0),
799    (95, 215, 95),
800    (95, 215, 135),
801    (95, 215, 175),
802    (95, 215, 215),
803    (95, 215, 255),
804    (95, 255, 0),
805    (95, 255, 95),
806    (95, 255, 135),
807    (95, 255, 175),
808    (95, 255, 215),
809    (95, 255, 255),
810    (135, 0, 0),
811    (135, 0, 95),
812    (135, 0, 135),
813    (135, 0, 175),
814    (135, 0, 215),
815    (135, 0, 255),
816    (135, 95, 0),
817    (135, 95, 95),
818    (135, 95, 135),
819    (135, 95, 175),
820    (135, 95, 215),
821    (135, 95, 255),
822    (135, 135, 0),
823    (135, 135, 95),
824    (135, 135, 135),
825    (135, 135, 175),
826    (135, 135, 215),
827    (135, 135, 255),
828    (135, 175, 0),
829    (135, 175, 95),
830    (135, 175, 135),
831    (135, 175, 175),
832    (135, 175, 215),
833    (135, 175, 255),
834    (135, 215, 0),
835    (135, 215, 95),
836    (135, 215, 135),
837    (135, 215, 175),
838    (135, 215, 215),
839    (135, 215, 255),
840    (135, 255, 0),
841    (135, 255, 95),
842    (135, 255, 135),
843    (135, 255, 175),
844    (135, 255, 215),
845    (135, 255, 255),
846    (175, 0, 0),
847    (175, 0, 95),
848    (175, 0, 135),
849    (175, 0, 175),
850    (175, 0, 215),
851    (175, 0, 255),
852    (175, 95, 0),
853    (175, 95, 95),
854    (175, 95, 135),
855    (175, 95, 175),
856    (175, 95, 215),
857    (175, 95, 255),
858    (175, 135, 0),
859    (175, 135, 95),
860    (175, 135, 135),
861    (175, 135, 175),
862    (175, 135, 215),
863    (175, 135, 255),
864    (175, 175, 0),
865    (175, 175, 95),
866    (175, 175, 135),
867    (175, 175, 175),
868    (175, 175, 215),
869    (175, 175, 255),
870    (175, 215, 0),
871    (175, 215, 95),
872    (175, 215, 135),
873    (175, 215, 175),
874    (175, 215, 215),
875    (175, 215, 255),
876    (175, 255, 0),
877    (175, 255, 95),
878    (175, 255, 135),
879    (175, 255, 175),
880    (175, 255, 215),
881    (175, 255, 255),
882    (215, 0, 0),
883    (215, 0, 95),
884    (215, 0, 135),
885    (215, 0, 175),
886    (215, 0, 215),
887    (215, 0, 255),
888    (215, 95, 0),
889    (215, 95, 95),
890    (215, 95, 135),
891    (215, 95, 175),
892    (215, 95, 215),
893    (215, 95, 255),
894    (215, 135, 0),
895    (215, 135, 95),
896    (215, 135, 135),
897    (215, 135, 175),
898    (215, 135, 215),
899    (215, 135, 255),
900    (215, 175, 0),
901    (215, 175, 95),
902    (215, 175, 135),
903    (215, 175, 175),
904    (215, 175, 215),
905    (215, 175, 255),
906    (215, 215, 0),
907    (215, 215, 95),
908    (215, 215, 135),
909    (215, 215, 175),
910    (215, 215, 215),
911    (215, 215, 255),
912    (215, 255, 0),
913    (215, 255, 95),
914    (215, 255, 135),
915    (215, 255, 175),
916    (215, 255, 215),
917    (215, 255, 255),
918    (255, 0, 0),
919    (255, 0, 95),
920    (255, 0, 135),
921    (255, 0, 175),
922    (255, 0, 215),
923    (255, 0, 255),
924    (255, 95, 0),
925    (255, 95, 95),
926    (255, 95, 135),
927    (255, 95, 175),
928    (255, 95, 215),
929    (255, 95, 255),
930    (255, 135, 0),
931    (255, 135, 95),
932    (255, 135, 135),
933    (255, 135, 175),
934    (255, 135, 215),
935    (255, 135, 255),
936    (255, 175, 0),
937    (255, 175, 95),
938    (255, 175, 135),
939    (255, 175, 175),
940    (255, 175, 215),
941    (255, 175, 255),
942    (255, 215, 0),
943    (255, 215, 95),
944    (255, 215, 135),
945    (255, 215, 175),
946    (255, 215, 215),
947    (255, 215, 255),
948    (255, 255, 0),
949    (255, 255, 95),
950    (255, 255, 135),
951    (255, 255, 175),
952    (255, 255, 215),
953    (255, 255, 255),
954    (8, 8, 8),
955    (18, 18, 18),
956    (28, 28, 28),
957    (38, 38, 38),
958    (48, 48, 48),
959    (58, 58, 58),
960    (68, 68, 68),
961    (78, 78, 78),
962    (88, 88, 88),
963    (98, 98, 98),
964    (108, 108, 108),
965    (118, 118, 118),
966    (128, 128, 128),
967    (138, 138, 138),
968    (148, 148, 148),
969    (158, 158, 158),
970    (168, 168, 168),
971    (178, 178, 178),
972    (188, 188, 188),
973    (198, 198, 198),
974    (208, 208, 208),
975    (218, 218, 218),
976    (228, 228, 228),
977    (238, 238, 238),
978];