RichTextEditor. You typically do not need to interact
with them directly. This documentation is provided for developers implementing custom platforms or extending
the Avalonia integration.
DocumentRenderer
class · internal sealed
Caches the Skia rendering of the document as an SKPicture to prevent redundant repaints.
One DocumentRenderer lives for the lifetime of a RichTextEditor instance.
| Member | Description |
|---|---|
DocumentRenderer(DocumentController) | Construct with the controller whose document will be rendered. |
InvalidateDocument() | Mark the cache as stale. The next call to Render() will re-record the picture. |
Render(SKCanvas, SKRect bounds) | Called by DocumentRenderOp during the Avalonia render thread. Re-records the picture if dirty, then replays it to the canvas. |
Dispose() | Releases the cached SKPicture. |
Design Notes
DocumentRenderer is intentionally not an ICustomDrawOperation itself,
because Avalonia disposes ICustomDrawOperation instances after each frame. Instead, a
lightweight DocumentRenderOp wrapper is created fresh each frame and delegates to the
long-lived DocumentRenderer.
| Field | Description |
|---|---|
_cache (SKPicture) | The cached recording. Replayed every frame until _documentDirty is set. |
_documentDirty (volatile bool) | Thread-safe dirty flag. Set by InvalidateDocument(), cleared after re-recording. |
DocumentRenderOp
class · internal sealed
A lightweight per-frame ICustomDrawOperation. Created fresh each Render() call
by RichTextEditor and delegates to the long-lived DocumentRenderer.
| Member | Description |
|---|---|
Bounds (Rect) | The Avalonia bounds rect passed to the draw operation. |
DocumentRenderOp(DocumentRenderer, Rect) | Construct with the renderer and current bounds. |
Dispose() | No-op — owns no resources. |
Equals(ICustomDrawOperation) | Always returns false to force Avalonia to redraw every frame. |
HitTest(Point) | Always returns false. |
Render(ImmediateDrawingContext) | Obtains the Skia canvas via TryGetFeature<ISkiaSharpApiLeaseFeature> and calls DocumentRenderer.Render(). |
// How RichTextEditor uses DocumentRenderOp each frame:
public override void Render(DrawingContext context)
{
var op = new DocumentRenderOp(_renderer, Bounds);
context.Custom(op);
}
AvaloniaClipboardHandler
class · public
Implements IClipboardHandler using Avalonia's TopLevel.Clipboard API.
Passed to DocumentController during initialization.
| Member | Description |
|---|---|
AvaloniaClipboardHandler(Control control) | Construct with any Avalonia control in the visual tree (used to access TopLevel). |
Task<string?> GetTextAsync() | Returns clipboard text via IClipboard.GetTextAsync(). |
Task SetTextAsync(string) | Writes text to the clipboard via IClipboard.SetTextAsync(). |
Task<byte[]?> GetImageDataAsync() | Checks for bitmap image data on the clipboard using named-format access. Returns PNG bytes decoded from an Avalonia Bitmap, or null. |
GetImageDataAsync() uses the older Avalonia clipboard format enumeration APIs
(GetFormatsAsync() / GetDataAsync()) because the newer API does not support
raw named-format access for bitmap data. A #pragma warning disable CS0618 suppresses the
deprecation warning.
// Usage in RichTextEditor.OnLoaded()
var clipboard = new AvaloniaClipboardHandler(this);
// 'this' is the RichTextEditor control
// Passed to DocumentController constructor
AvaloniaExtensions
class · public · staticExtension methods for Avalonia → Core type conversion.
Key enum value to the Core KeyCode enum using a static
Dictionary<Key, KeyCode> lookup table defined in Statics.KeyConversionMap.
Falls back to KeyCode.None for unmapped keys.
// Used in RichTextEditor.OnKeyDown()
var coreKey = e.Key.ToKeyCode();
var keyInfo = new KeyInfo(coreKey, shift, control, alt);
await controller.OnKeyEvent(keyInfo);
Statics
class · internal · staticHolds the KeyConversionMap dictionary used by AvaloniaExtensions.ToKeyCode().
| Member | Type | Description |
|---|---|---|
| KeyConversionMap | Dictionary<Key, KeyCode> | Maps all standard Avalonia Key values to Core KeyCode values.
Covers A–Z, 0–9, F1–F24, navigation keys (Home, End, PgUp, PgDn, arrows),
editing keys (Delete, BackSpace, Enter, Tab, Escape, Insert),
and punctuation/symbol keys. |
Anti-Flicker Architecture
Rapid editing operations (caret blink, scroll, key repeat) can trigger many invalidation requests per second. The platform layer uses a two-stage debouncing approach to avoid unnecessary Skia redraws:
Stage 1 — DocumentController.Invalidate()
Called by MoveCaret(), ScrollTo(), ScrollBy(), and
OnDocumentDidChange(). Invokes RequestRedraw?.Invoke().
Stage 2 — RichTextEditor.RequestRedrawFromController()
Calls DocumentRenderer.InvalidateDocument() and sets _redrawPending = true.
If no redraw is already queued, dispatches InvalidateVisual() at
DispatcherPriority.Render. Clears _redrawPending when the visual invalidation fires.
Stage 3 — DocumentRenderer.Render()
On the render thread, checks the volatile _documentDirty flag.
Only re-records the SKPicture when dirty. Otherwise replays the cached picture.
This means a scroll operation or caret movement does not re-execute all Skia draw calls —
only a re-recording triggers full draw-call replay.
lock(_drawLock) in DocumentController.Draw().
The _documentDirty flag in DocumentRenderer is volatile for
safe cross-thread access between the UI thread (setting dirty) and the render thread (reading/clearing dirty).
Cursor Mapping
RichTextEditor maps EditorCursor (from Core) to Avalonia StandardCursorType:
| EditorCursor | StandardCursorType | When |
|---|---|---|
Default | Ibeam | Normal text editing area |
ResizeNS | SizeNorthSouth | Top or bottom edge handle of selected image |
ResizeEW | SizeWestEast | Left or right edge handle of selected image |
ResizeDiagNWSE | TopLeftCorner | TopLeft or BottomRight corner handle |
ResizeDiagNESW | TopRightCorner | TopRight or BottomLeft corner handle |
Move | SizeAll | Image body (drag to move) |
SizeNorthWestSouthEast and SizeNorthEastSouthWest do NOT exist in Avalonia's
StandardCursorType enum. Use TopLeftCorner and TopRightCorner
respectively for diagonal resize cursors.