Decorations
You will learn how a host marks ranges of text (spelling squiggles, mention tints, its own highlights), how the theme decides their look, how to make a comment active, and how to find where a range is on screen without waiting a frame.
A decoration is a look over a range of one block's text. The editor draws every decoration in one layer, from the same layout as the text and in the same frame, in paged and pageless layout alike. Its own marks are decorations too: search matches, comment tints, the active comment, collaborators' selections, protected text and link hover.
dart
import 'package:vyuh_document/vyuh_document.dart';
import 'package:vyuh_document_editor/document_editor.dart';
import 'package:vyuh_document_elements/vyuh_document_elements.dart';
final controller = DocumentController(
notesConfiguration,
author: const Author.person('You'),
);
// The host's handle on what the editor shows. Keep it across rebuilds.
final view = DocumentView(controller);
final editor = DocumentEditor(controller: controller, view: view);
// The host's own style: the theme gives it a look; decorations only name it.
const mention = DocumentDecorationStyleId('mention');
DocumentEditorTheme themed(DocumentEditorTheme theme) => theme.copyWith(
decorations: theme.decorations.withStyles(const {
mention: DocumentDecorationStyle.fill(0x332F80ED, cornerRadius: 3),
}),
);
// A spellchecker and mention tints are decoration sources. Each replaces
// its own set; between checks the editor keeps them on their words.
void recheck(List<Range> misspelt, List<Range> mentions) => view
..decorate(const DocumentDecorationSourceId('spellcheck'), [
for (final range in misspelt)
RangeDecoration(range, style: DocumentDecorationStyleId.spelling),
])
..decorate(const DocumentDecorationSourceId('mentions'), [
for (final range in mentions)
// Exported: PDF and print show the tint too.
RangeDecoration(range, style: mention, exports: true),
]);
// Where a range is on screen, valid as soon as its layout is published:
// place a bubble without waiting a frame.
Rect? bubbleAnchor(Range range) => view.rectsFor(range).firstOrNull;
// The active comment paints as `comment.active`; the review panel, the
// caret and the host all set it.
void focus(CommentId id) => view.activeComment = id;Try it live: toggle the spellchecker and the mention tints, type before a squiggle, make the comment active, and place a bubble over a mention.
Sources
A host decorates through a DocumentView(controller) passed to DocumentEditor(view:). Each source (a spellchecker, a mention tagger) has a DocumentDecorationSourceId and replaces its own set whole:
| Member | Meaning |
|---|---|
view.decorate(source, decorations) | Replace source's set; an empty list removes it |
RangeDecoration(range, style:, priority:, exports:, key:) | One decoration over a range of one node's text |
view.rectsFor(range) | The global rectangles of range, one per line and page |
view.activeComment | The active comment, read and set |
The built-in ids (DocumentDecorationSourceId.comments, protection, search, participants and link) belong to the editor and the engine; a host write to one throws an ArgumentError instead of being overwritten.
A view holds no decorations of its own. The mounted editor's snapshot owns them; while no editor is mounted (between remounts, on a surface switch) the DocumentController keeps them, mapped through every change to its document.
Between a source's updates, the editor keeps each decoration on its text as edits land: typing before a squiggle moves it with its word, and deleting the word removes it. Decorations are view state. They are not saved, not part of undo, and never change layout.
A range over an inline atom (a mention chip, a field, an image) decorates the atom's box.
priority orders decorations: higher paints above lower, and lines of different decorations on the same text take separate lanes, so a review line and a spelling squiggle never draw over each other. exports: true draws the decoration in PDF and print too, in the export look.
DocumentEditorController has the same members for hosts of the runtime editor: setDecorations(source, decorations), decorations, rectsForRange(anchor) and activeCommentId.
Comments and protection
Comment tints, the active comment and protected text are derived by the engine on every commit, in every session: a mounted editor, a live document and a headless DocumentEngineSession show and export the same marks. The caret picks the narrowest open thread under it, a reply picks its thread, and a resolved or deleted thread stops being active. setCommentTints on the session (DocumentReviewConfiguration.showComments and markupAuthorIds in the editor) shows or hides the tints.
Protected text is what the session's authority refuses the actor. A host rule that judges blocks declares what it reads with blockScope: DocumentRuleBlockScope.ownBlock means only the judged block, so after an edit only the blocks it touched are judged again. A rule without it is judged again for every block after every change, which is always correct but costs time on a large document.
Looks come from the theme
A decoration names a style, never a colour. DocumentEditorTheme.decorations gives each name its look: a fill, an outline, a line (solid, double, dotted, dashed or wavy, under, through or over the text), or a mix. Give your own names a look with withStyles:
dart
theme.copyWith(
decorations: theme.decorations.withStyles(
{mention: DocumentDecorationStyle.fill(0x332F80ED, cornerRadius: 3)},
highContrastStyles: {
mention: DocumentDecorationStyle(
fillArgb: 0x552F80ED,
outlineArgb: 0xFF1A4FA8,
),
},
),
);The built-in names are spelling, grammar, search.match, search.active, comment, comment.active, protected, link.hover and participant.<slot>. Under the platform's high-contrast setting the editor uses each style's high-contrast look, which adds an outline or a line to every fill.
Review marks keep one fixed language: green dashed for insertions, red dashed with a strikethrough for deletions, and amber for format changes. Their colours come from the same look object, DocumentDecorationTheme.reviewPaletteFor(highContrast:) (set with withReviewPalettes), and changing them repaints without laying the document out again. PDF and print use the standard palette.
Range geometry
view.rectsFor(range) reads the published layout and the camera, so it is valid as soon as the layout of the current document lands: a bubble or popover can be placed in the frame that paints the edit that moved its text. Between an edit and its layout it returns nothing rather than rectangles from the previous layout. The view notifies its listeners after every layout publication, so an overlay knows when to read again.