Overview

RichTextEditor is an Avalonia UserControl that owns a DocumentController and a DocumentRenderer. It translates Avalonia pointer and keyboard events into Core API calls, and renders the document to an Skia canvas using an anti-flicker caching strategy.

Use RichTextEditor directly when you want to supply your own toolbar, scrollbars, or embed the editor inside a complex layout. Wire a DocumentToolBar to it by setting Toolbar.Editor = myEditor.

AXAML Usage

<pe:RichTextEditor
    x:Name="Canvas"
    Width="800"
    HorizontalAlignment="Left"
    VerticalAlignment="Top"
/>

<!-- Pair with a toolbar -->
<pe:DocumentToolBar x:Name="Toolbar" />
// In OnInitialized() or OnLoaded()
Toolbar.Editor = Canvas;

Properties

PropertyTypeDescription
DocumentController DocumentController The underlying Core controller. Created during OnLoaded(). Use this to apply styles, navigate, export, or react to events. This is a Direct Avalonia property — bindable.
SelectionInfo SelectionInfo Current paragraph-level selection state (alignment, list type, list level, line spacing). Updated on every navigation event. Bindable Direct property.
ZoomLevel double Zoom multiplier (default: 1.0). Styled Avalonia property — can be set from AXAML or code.
DocumentHeight float Computed: DocumentController.DocumentHeight. The maximum vertical scroll extent.

Events

Callback Action<DocumentInfo> ContentSizeChanged
Fired when the document's total height or width changes. Use this to update external scrollbar ranges.
Canvas.ContentSizeChanged = (info) =>
{
    vertScrollBar.Maximum = info.Height;
    vertScrollBar.ViewportSize = Canvas.Bounds.Height;
};

Methods

Method void ScrollTo(double x, double y)
Scrolls the editor to an absolute position. Call this from scrollbar value-changed handlers.
vertScrollBar.PropertyChanged += (_, e) =>
{
    if (e.Property == ScrollBar.ValueProperty)
        Canvas.ScrollTo(0, vertScrollBar.Value);
};

Input Handling (Internals)

RichTextEditor overrides these Avalonia virtual methods and bridges them to Core:

OverrideDelegates To
OnPointerPressed()DocumentController.Click(point)
OnPointerMoved() (button down)DocumentController.DragTo(point)
OnPointerMoved() (no button)DocumentController.HoverAt(point)
OnPointerReleased()DocumentController.PointerReleased(point)
OnPointerWheelChanged()DocumentController.ScrollBy(delta)
OnKeyDown()DocumentController.OnKeyEvent(keyInfo)
OnTextInput()DocumentController.Insert(text)
OnGotFocus()DocumentController.HasFocus = true
OnLostFocus()DocumentController.HasFocus = false

Avalonia key codes are converted to Core KeyCode values via AvaloniaExtensions.ToKeyCode() using a static lookup table.

Rendering

RichTextEditor overrides Render(DrawingContext) and creates a lightweight DocumentRenderOp (a custom draw operation) each frame. The actual rendering is delegated to DocumentRenderer, which maintains an SKPicture cache. The cache is only re-recorded when the document has changed, preventing unnecessary redraws.

Anti-Flicker Design Rapid calls to DocumentController.Invalidate() (e.g., during cursor blink or scroll) are debounced via _redrawPending. Only one InvalidateVisual() is dispatched per Avalonia render frame.

Custom Layout Example

A minimal setup using RichTextEditor without WordProcessor:

// AXAML (abbreviated)
<DockPanel>
    <pe:DocumentToolBar x:Name="Toolbar" DockPanel.Dock="Top" />
    <ScrollBar  x:Name="VScroll"  DockPanel.Dock="Right" Orientation="Vertical" />
    <pe:RichTextEditor x:Name="Canvas" />
</DockPanel>

// Code-behind
protected override void OnLoaded(RoutedEventArgs e)
{
    base.OnLoaded(e);
    Toolbar.Editor = Canvas;

    Canvas.ContentSizeChanged = (info) =>
    {
        VScroll.Maximum = info.Height;
    };

    VScroll.PropertyChanged += (_, args) =>
    {
        if (args.Property == ScrollBar.ValueProperty)
            Canvas.ScrollTo(0, VScroll.Value);
    };
}