RichTextField

Represent a text editing control with parser-based, per-character styling.

To create a rich text field, use RichTextField::new (with 3 parameters: text, layout and flags):

let rtx = RichTextField::new("some text", layout!("x:10,y:5,w:20"), richtextfield::Flags::None);

You can also build it with a parser callback via RichTextField::with_parser:

fn my_parser(t: &mut richtextfield::AttributeText, theme: &Theme) {
    let n = t.count();
    for i in 0..n {
        if let Some(c) = t.char(i) {
            if c.is_ascii_digit() {
                t.set_attr(i, CharAttribute::new(Color::Yellow, Color::Transparent, CharFlags::Bold));
            } else {
                t.set_attr(i, theme.text.normal);
            }
        }
    }
}

let rtx = RichTextField::with_parser(
    "v1 build 42",
    layout!("x:1,y:1,w:30,h:1"),
    richtextfield::Flags::None,
    my_parser,
);

Or via macro:

let rtx1 = richtextfield!("text='some text',x:10,y:5,w:20");
let rtx2 = richtextfield!("'some text',x:10,y:5,w:20,parser:my_parser");

A RichTextField supports all common parameters (see Instantiate via Macros). Besides them, the following named parameters are also accepted:

Parameter nameTypePositional parameterPurpose
text or captionStringYes (first positional parameter)Initial text content. If omitted, an empty text is used.
flagsListNoInitialization flags controlling read-only mode and Enter processing.
parserFunctionNoParser callback (fn(&mut AttributeText, &Theme)) applied after edits.

A rich text field supports the following initialization flags:

  • richtextfield::Flags::Readonly or Readonly (macro) - allows viewing/selecting/copying but prevents text edits.
  • richtextfield::Flags::ProcessEnter or ProcessEnter (macro) - captures Enter and triggers RichTextFieldEvents::on_validate(...).
  • richtextfield::Flags::DisableAutoSelectOnFocus or DisableAutoSelectOnFocus (macro) - disables auto-select-all when focus is received.

Events

To intercept events from a rich text field, implement:

pub trait RichTextFieldEvents {
    fn on_validate(&mut self, handle: Handle<RichTextField>, text: &str) -> EventProcessStatus { ... }
    fn on_text_changed(&mut self, handle: Handle<RichTextField>) -> EventProcessStatus { ... }
}

Methods

Besides the Common methods for all Controls, RichTextField has:

MethodPurpose
set_text(...)Replaces the full text and recomputes parser styling.
undo()Undo last action.
redo()Redo last action.
text()Returns current text.
is_readonly()Returns true when the control is in read-only mode.
set_parser(...)Installs/replaces parser callback and reapplies styling immediately.
reset_parser()Removes parser callback and resets character styles to defaults.

Key association

The following keys are processed when a RichTextField has focus:

KeyPurpose
Left, Right, Up, DownMove cursor through text
Shift+{Left,Right,Up,Down}Extend/reduce selection
Ctrl+LeftMove to beginning of previous word
Shift+Ctrl+LeftSelect from current position to beginning of previous word
Ctrl+RightMove to beginning of next word
Shift+Ctrl+RightSelect from current position to beginning of next word
Home / EndMove to start / end of text
Shift + Home / Shift + EndSelect from cursor to start / end
Delete / BackspaceDelete current / previous character (or current selection)
Ctrl+ASelect all text
Ctrl+UConvert selection (or current word) to lowercase
Ctrl+Shift+UConvert selection (or current word) to uppercase
Ctrl+C or Ctrl+InsertCopy selection
Ctrl+V or Shift+InsertPaste clipboard text
Ctrl+X or Shift+DeleteCut selection
Ctrl+ZUndo last action
Ctrl+Shift+ZRedo last action
EnterTriggers on_validate(...) only when ProcessEnter is set

Additionally, printable characters can be inserted directly into the text.

Mouse actions

Mouse cursor can be used for caret positioning and selection. Double click selects the clicked word.

Example

The following example applies markdown-like styling with a parser callback:

use appcui::prelude::*;

fn markdown_like_colors(t: &mut richtextfield::AttributeText, _theme: &Theme) {
    let n = t.count();
    let mut ticks = false;
    let mut bold = false;
    let mut italic = false;
    let mut i = 0;
    while i < n {
        let c = t.char(i).unwrap_or('\0');
        if i + 1 < n && c == '*' && t.char(i + 1).unwrap_or('\0') == '*' {
            bold = !bold;
            i += 2;
            continue;
        }
        if c == '`' {
            ticks = !ticks;
            i += 1;
            continue;
        }
        if c == '*' {
            italic = !italic;
            i += 1;
            continue;
        }

        if ticks {
            t.set_attr(i, CharAttribute::new(Color::Aqua, Color::Transparent, CharFlags::None));
        } else if bold {
            t.set_attr(i, CharAttribute::new(Color::Yellow, Color::Transparent, CharFlags::Bold));
        } else if italic {
            t.set_attr(i, CharAttribute::new(Color::Green, Color::Transparent, CharFlags::Italic));
        }
        i += 1;
    }
}

fn main() -> Result<(), appcui::system::Error> {
    let mut app = App::new().build()?;
    let mut w = window!("'RichTextField example',a:c,w:80,h:12");
    w.add(label!("'Use **, * and ` to toggle styles',l:1,t:1,r:1,h:1"));
    w.add(richtextfield!(
        "'A `code` and **bold** plus *italic* demo',l:1,t:3,r:1,h:2,parser:markdown_like_colors"
    ));
    app.add_window(w);
    app.run();
    Ok(())
}