Skip to content

RichText

Header: winui/ui/text/RichText.hpp. Spec: winui/ui/text/RichText.hpp.

Read-only paragraphs of formatted inlines, wrapping by default, selectable, with links and highlighters. Content that doesn't fit can flow into RichTextOverflow containers.

The content is set with set_blocks(), as Paragraphs of Inlines. Offsets, such as the selection and TextHighlighter ranges, count in plain_text(). Selection is on by default. The user can drag, double-click a word, triple-click a paragraph, press Ctrl+A and Ctrl+C, and use a context menu with Copy and Select All. In a spec tree it is built by ui::RichText.

Example:

auto  row      = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);
auto* rich     = row->add(std::make_unique<ui::RichTextControl>());
auto* overflow = row->add(std::make_unique<ui::RichTextOverflowControl>()); // measured after it
rich->set_blocks({
    {.inlines = {{.text = "RichText shows "}, {.text = "formatted", .weight = ui::FontWeight::Bold},
                 {.text = " paragraphs. "}, {.text = "Links", .navigate_uri = "https://example.com/"},
                 {.text = " work too."}}},
    {.inlines = {{.text = "What does not fit the block's height goes on in the overflow."}},
     .margin  = ui::Edges{.top = 12.0f}},
});
rich->set_text_alignment(ui::TextAlignment::Justify);
rich->set_width(200.0f);
rich->set_height(120.0f);
overflow->set_width(200.0f);
rich->set_overflow_target(overflow);
window.set_content(std::move(row));

ui::RichText

Paragraphs of formatted inlines. Builds a ui::RichTextControl.

Read-only, selectable by default and wrapping by default. Content that does not fit can flow on into a ui::RichTextOverflow.

Example:

window.set_content(ui::RichText{
    .blocks = {
        { .inlines = { { .text = "I am a " },
                       { .text = "RichText", .weight = ui::FontWeight::Bold },
                       { .text = " with an ", .italic = true },
                       { .text = "italic run." } } },
        { .inlines = { { .text = "A second paragraph." } }, .margin = ui::Edges{.top = 8} },
    },
    .max_width = 400,
});

Every field, in the order a designated initializer names them:

ui::RichText{
    .blocks, .selection_enabled, .text_highlighters, .overflow, .text_style, .wrapping,
    .text_alignment, .character_spacing, .selection_highlight_color, .foreground, .style,
    .margin, .width, .height, .min_width, .min_height, .max_width, .max_height,
    .horizontal_alignment, .vertical_alignment, .visible, .tool_tip, .accessibility,
    .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus, .on_key_down,
    .on_wheel, .control
}

Fields, in declaration order (designators must follow it):

Field Type Default
blocks ui::Binding<std::vector<ui::Paragraph>> The content: paragraphs of inlines. Space paragraphs apart with each Paragraph's margin.
selection_enabled ui::Binding<bool> {true} The user can select the text and copy it; on by default.
text_highlighters ui::Binding<std::vector<ui::TextHighlighter>> {std::vector<ui::TextHighlighter>{}} Ranges painted with their own background (and foreground).
overflow ui::ControlLink<ui::RichTextOverflow> Where the content that does not fit continues, by the RichTextOverflow's handle (RichTextControl::set_overflow_target()).
text_style ui::Binding<std::optional<ui::TextStyle>> Base style for every run; unset follows the theme's body text.
wrapping ui::Binding<ui::TextWrapping> {ui::TextWrapping::Wrap} Whether lines break at the available width; Wrap by default.
text_alignment ui::Binding<ui::TextAlignment> {ui::TextAlignment::Left} How each line sits between the left and right edges.
character_spacing ui::Binding<int> {0} Extra space between characters, in thousandths of an em.
selection_highlight_color ui::Binding<ui::Paint> The color behind selected text: a fixed color or a theme resource, which follows theme switches. Unset is the accent.
foreground ui::Binding<ui::Paint> The color of runs without a color of their own: a fixed color or a theme resource. Unset uses the theme's text color.
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::RichText> Out: the handle that receives the built control.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::RichTextControl; the caller owns it.

ui::RichTextOverflow

Where a RichText's content continues when it does not fit. Builds a ui::RichTextOverflowControl, named by the RichText's .overflow, or by the previous RichTextOverflow's for a chain. It has no content of its own. It shows whatever of its source's paragraphs the previous container in the chain could not fit.

Example:

const std::string                 long_text = "A text longer than one column holds...";
ui::Control<ui::RichTextOverflow> more;

window.set_content(ui::HorizontalStack{
    .spacing  = 12,
    .height   = 300,
    .content  = {
        ui::RichText{ .blocks = {{ .inlines = {{ .text = long_text }} }}, .overflow = more, .width = 240 },
        ui::RichTextOverflow{ .width = 240, .control = more }, // continues where the text stops
    },
});

Every field, in the order a designated initializer names them:

ui::RichTextOverflow{
    .overflow, .style, .margin, .width, .height, .min_width, .min_height, .max_width,
    .max_height, .horizontal_alignment, .vertical_alignment, .visible, .tool_tip,
    .accessibility, .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus,
    .on_key_down, .on_wheel, .control
}

Fields, in declaration order (designators must follow it):

Field Type Default
overflow ui::ControlLink<ui::RichTextOverflow> The next container of the chain, by its handle (RichTextOverflowControl::set_overflow_target()).
on_pointer_entered ui::Action The pointer moved onto the control.
on_pointer_exited ui::Action The pointer left the control.
on_got_focus ui::Action Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this.
on_lost_focus ui::Action Keyboard focus left the control and everything inside it.
on_key_down ui::Callback<ui::Handling(const ui::KeyEvent&)> A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()).
on_wheel ui::Callback<ui::Handling(const ui::PointerEvent&)> A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()).
control ui::ControlOut<ui::RichTextOverflow> Out: a non-owning observer of the built control; needed to link it into a chain.

Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, accessibility every spec carries (conventions).

build

std::unique_ptr<Element> build()&&

Consumes the spec and returns the built ui::RichTextOverflowControl; the caller owns it.

ui::RichTextControl

Derives from ui::Element.

Read-only paragraphs of formatted inlines, wrapping by default, selectable, with links and highlighters. Content that doesn't fit can flow into RichTextOverflow containers.

The content is set with set_blocks(), as Paragraphs of Inlines. Offsets, such as the selection and TextHighlighter ranges, count in plain_text(). Selection is on by default. The user can drag, double-click a word, triple-click a paragraph, press Ctrl+A and Ctrl+C, and use a context menu with Copy and Select All. In a spec tree it is built by ui::RichText.

Example:

auto  row      = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);
auto* rich     = row->add(std::make_unique<ui::RichTextControl>());
auto* overflow = row->add(std::make_unique<ui::RichTextOverflowControl>()); // measured after it
rich->set_blocks({
    {.inlines = {{.text = "RichText shows "}, {.text = "formatted", .weight = ui::FontWeight::Bold},
                 {.text = " paragraphs. "}, {.text = "Links", .navigate_uri = "https://example.com/"},
                 {.text = " work too."}}},
    {.inlines = {{.text = "What does not fit the block's height goes on in the overflow."}},
     .margin  = ui::Edges{.top = 12.0f}},
});
rich->set_text_alignment(ui::TextAlignment::Justify);
rich->set_width(200.0f);
rich->set_height(120.0f);
overflow->set_width(200.0f);
rich->set_overflow_target(overflow);
window.set_content(std::move(row));

RichTextControl

RichTextControl() = default

An empty block: Wrap, Left alignment, selection enabled.

~RichTextControl

~RichTextControl() override

Unlinks any overflow containers, which then show nothing.

RichTextControl

RichTextControl(const RichTextControl&) = delete

operator=

RichTextControl& operator=(const RichTextControl&) = delete

blocks

const std::vector<Paragraph>& blocks() const

The paragraphs, as set by set_blocks().

set_blocks

void set_blocks(std::vector<Paragraph> blocks)

Replaces the content and clears the selection.

plain_text

std::string plain_text() const

The content as plain text: the runs in order, paragraphs separated by \n. Every offset (selection, highlighters) counts in this text.

text_style

const TextStyle& text_style() const

The base style every run derives from (font family, size, weight).

set_text_style

void set_text_style(TextStyle style)

The base style every run derives from. Unset (the default) is the theme's body text. Once set, the theme's body style no longer applies.

foreground

const Paint& foreground() const

The color of runs without a color of their own: a fixed color or a theme resource. Unset follows the theme's text color.

set_foreground

void set_foreground(Paint color)

Sets the color of runs without their own; an empty source follows the theme.

wrapping

TextWrapping wrapping() const

Whether lines break at the width; Wrap by default.

set_wrapping

void set_wrapping(TextWrapping wrapping)

Sets the wrapping mode, for the block and its overflow containers.

text_alignment

TextAlignment text_alignment() const

The alignment of every paragraph, Justify included; Left by default.

set_text_alignment

void set_text_alignment(TextAlignment alignment)

Sets the alignment, for the block and its overflow containers.

character_spacing

int character_spacing() const

Extra space added after every character, in thousandths of an em; 0 by default.

set_character_spacing

void set_character_spacing(int spacing)

Sets the character spacing, for the block and its overflow containers.

selection_enabled

bool selection_enabled() const

Whether the user can select and copy the text; true by default. While on, the block is a tab stop.

set_selection_enabled

void set_selection_enabled(bool enabled)

Turns selection on or off. Either change clears the selection.

selection_highlight_color

std::optional<Color> selection_highlight_color() const

The fixed color behind selected text; nullopt (the default) when unset or a theme resource is set. Unset is the accent.

selection_highlight_resource

ThemeColor selection_highlight_resource() const

The color behind selected text as a theme resource, or nullptr.

set_selection_highlight_color

void set_selection_highlight_color(std::optional<Color> color)

Sets the fixed color behind selected text and clears a theme resource; nullopt goes back to the accent.

set_selection_highlight_color

void set_selection_highlight_color(ThemeColor resource)

Sets the color behind selected text to a theme resource, re-read under every theme, and clears a fixed one.

selection

std::pair<std::size_t, std::size_t> selection() const

The selection as [start, end) offsets into plain_text(); empty when start == end.

select

void select(std::size_t start, std::size_t end)

Selects [start, end) of plain_text(). The selection shows only while the block, or one of its overflow containers, has focus.

select_all

void select_all()

Selects the whole plain_text() (Ctrl+A).

selected_text

std::string selected_text() const

The selected text; what Ctrl+C copies.

text_highlighters

const std::vector<TextHighlighter>& text_highlighters() const

The highlighters painted over the text.

set_text_highlighters

void set_text_highlighters(std::vector<TextHighlighter> highlighters)

Replaces every highlighter.

overflow_target

RichTextOverflowControl* overflow_target() const

The first overflow container (non-owning), or nullptr.

set_overflow_target

void set_overflow_target(RichTextOverflowControl* target)

Sets where content that does not fit continues. The overflow must be in the same tree and measured after this block, such as a later sibling or a later cell of the same Grid. nullptr unlinks it.

has_overflow_content

bool has_overflow_content() const

Whether some of the content did not fit and went on to the overflow target.

focusable

bool focusable() const override

A tab stop while text selection is enabled.

focused

bool focused() const

Whether this block itself holds keyboard focus.

handle_context_menu

bool handle_context_menu(std::optional<Point> position, ContextMenuHost& host) override

Copy and Select All, while selection is enabled.

cursor_at

std::optional<CursorShape> cursor_at(Point p) const override

A hand over a link, an I-beam over selectable text.

automation_role

AutomationRole automation_role() const override

AutomationRole::Document.

automation_name_fallback

std::string automation_name_fallback() const override

plain_text().

automation_children

void automation_children(std::vector<Element*>& out) const override

One node per link in the lines this block shows. Invoking a node follows its link.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

Lays the content out at the offered width (per wrapping()); with an overflow target, keeps only the whole lines that fit the offered height.

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Re-wraps at the final width when it changed, and re-places the lines for the final height, which decides where the overflow picks up.

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

The lines this block keeps, with its highlighters and the selection (while the chain has focus).

pointer_override

bool pointer_override(const PointerEvent& e) override

Drag to select, double-click a word or triple-click a paragraph. A click on a link follows it.

focus_changed

void focus_changed(bool) override

Called by Window as focus moves. The selection shows while the chain has focus.

key_override

bool key_override(const KeyEvent& e) override

Ctrl+A selects all, Ctrl+C copies the selection.

draws_focus_ring

bool draws_focus_ring() const override

None: selectable text shows focus through its selection.

class RichTextOverflowControl

Derives from ui::Element.

Shows the part of a RichText's content that did not fit the block or the previous overflow container, laid out at its own width. Link one with RichTextControl::set_overflow_target(), and chain another one with this one's set_overflow_target(). It has no content of its own, so an unlinked overflow is empty.

The links don't own anything. Each container is owned by the tree like any element and must be measured after the one before it. Destroying one in the chain unlinks everything after it. Selection, copying and links work here as in the RichText, on the RichText's own selection.

Example:

const std::string long_article_text = "An article longer than one column holds...";
auto  columns = std::make_unique<ui::StackControl>(ui::Orientation::Horizontal);
auto* rich    = columns->add(std::make_unique<ui::RichTextControl>());
auto* second  = columns->add(std::make_unique<ui::RichTextOverflowControl>());
auto* third   = columns->add(std::make_unique<ui::RichTextOverflowControl>());
rich->set_blocks({{.inlines = {{.text = long_article_text}}}});
const auto column = [](ui::Element* e) { e->set_width(180.0f); e->set_height(240.0f); };
column(rich);
column(second);
column(third);
rich->set_overflow_target(second);
second->set_overflow_target(third);
window.set_content(std::move(columns));

RichTextOverflowControl

RichTextOverflowControl() = default

An unlinked, empty container.

~RichTextOverflowControl

~RichTextOverflowControl() override

Unlinks itself; the containers after it lose their source.

RichTextOverflowControl

RichTextOverflowControl(const RichTextOverflowControl&) = delete

operator=

RichTextOverflowControl& operator=(const RichTextOverflowControl&) = delete

content_source

RichTextControl* content_source() const

The RichText whose content this shows, nullptr while unlinked.

overflow_target

RichTextOverflowControl* overflow_target() const

The next container in the chain (non-owning), or nullptr.

set_overflow_target

void set_overflow_target(RichTextOverflowControl* target)

Sets where this container's leftover goes. Same rules as RichTextControl::set_overflow_target(); nullptr unlinks it.

has_overflow_content

bool has_overflow_content() const

Whether some content still did not fit here and went on to the next container.

content_start

std::size_t content_start() const

The offset into the RichText's plain text this container starts at (npos while it shows nothing).

focusable

bool focusable() const override

A tab stop while linked, showing something, and the RichText allows selection.

handle_context_menu

bool handle_context_menu(std::optional<Point> position, ContextMenuHost& host) override

The RichText's Copy/Select All menu.

cursor_at

std::optional<CursorShape> cursor_at(Point p) const override

As the RichText: a hand over a link, an I-beam over selectable text.

automation_role

AutomationRole automation_role() const override

AutomationRole::Document.

measure_override

Size measure_override(Size available, const MeasureContext& context) const override

Lays the rest of the RichText's content out at the offered width, keeping the whole lines that fit the offered height (all of it when nothing is linked after it).

arrange_override

void arrange_override(const Rect& final_rect, const ArrangeContext& context) override

Re-places the lines for the final height.

render_override

void render_override(Renderer& renderer, const RenderContext& context) const override

The lines this container shows, in the RichText's formatting, highlighters and selection.

pointer_override

bool pointer_override(const PointerEvent& e) override

Selection and links, as in the RichText, acting on the RichText's selection.

focus_changed

void focus_changed(bool) override

Called by Window as focus moves. The selection shows while the chain has focus.

key_override

bool key_override(const KeyEvent& e) override

Ctrl+A and Ctrl+C, as the RichText's.

draws_focus_ring

bool draws_focus_ring() const override

None: selectable text shows focus through its selection.

Tests

  • tests/test_rich_text.cpp