Content Selection #319
matthew-carroll
started this conversation in
Design Doc
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Content selection is a major category of use-cases for rich text editors. This document describes some selection behaviors, how
super_editor
thinks about selection, and howsuper_editor
implements selection.Understanding selection requirements
Use-Cases
Here are some common selection use-cases:
Required Inputs
Selection use-cases involve:
Copy/Paste
Copy/paste is a closely related category of use-cases. Often, selected content is copied to the clipboard, or clipboard content is pasted at the caret, or in place of an expanded selection. However, copy/paste are not intrinsic to selection. Selection should make copy/paste possible, but should not prescribe any particular approach to implementing copy/paste.
How super_editor thinks about selection
super_editor
generalizes Flutter's concepts for text selection, therefore, one must understand Flutter's approach to text selection before understandingsuper_editor
's approach to content selection.Text Selection in Flutter
A text selection in Flutter is comprised of a "base position", "extent position", and an "affinity", which can be thought of as direction, e.g., "downstream" or "upstream".
Here are some definitions for text selection terms:
Generalizing Text Selection to General Selection
To generalize text selection concepts to arbitrary content within an arbitrary layout, it's important to recognize the aspects of selection that will go from constants to variables.
Text content to arbitrary content: Text selection only needs to worry about text content. However, arbitrary content may include text within structures, like lists, or non-text content, like images.
Text layout to arbitrary layout: Text selection operates on a blob of text. Text selection knows how to map cartesian coordinates to characters, and map characters to cartesian coordinates. But this layout is very specific. This particular selection mapping would need adjustments for list layouts, and it would be entirely inappropriate for images.
Single-level layout to compositional layout: Text blobs exist at a single-level of layout. Other layouts are not embedded within a single blob of text. However, documents and layouts are deeply compositional. Layouts within layouts within layouts. Text selection doesn't need to worry about up/down communication in a layout hierarchy, but general selection does.
Document Selection Concepts
Given the aforementioned differences between text selection and general selection, it begs the question, which aspects of text selection can be extended to support general selection? What does it mean to select content in general?
General selection has the following characteristics:
These are the only details that any given selection has in common with any other.
Generalizing text selection to document selection, we end up with the following concepts:
Implementing Document Selection
Logical Document Selection
The backbone of document selection is the concept of a document position. Unlike a text position, a document position can't define itself with an integer offset, because an integer offset doesn't mean anything for non-text content. Instead, a document position is comprised of a "node ID" and a "node position".
A
nodeId
uniquely identifies a content layout at the top level of a document, e.g., header, paragraph, image, horizontal rule, ordered list.A
nodePosition
identifies a specific offset within a node. TheNodePosition
type is a marker interface because different types of content require different representations for offsets within that content. Text can use an integer offset, an image might use a boolean, and a table might use a row/col data structure. This choice is left to the individual content nodes, but whatever the representation, theNodePosition
is stored within aDocumentPosition
.Given two document positions, we have a document selection.
A
DocumentSelection
represents a selection of arbitrary content, but it's helpful to provide some tools within the concept of aDocumentNode
aimed at assembling and altering a document selection.When working with document selections, it may be necessary to obtain the very first position in a node or the very last position in a node. Similarly, given two positions within a node, a document may need to determine which of those positions is upstream or downstream from the other.
DocumentNode
provides these faculties.DocumentSelection
andDocumentNode
solve the problem of a logical representation of arbitrary content selection within a document.Selecting Content
Given the logical representation of content selection,
super_editor
needs a mechanism to begin a selection, e.g., clicking in the middle of some text, or clicking on an image.Document selection interaction is handled at the document level. Gestures are captured anywhere within the document. Some gesture selection changes are made by the overall document layout, while other decisions are made by individual visual components within the document.
For example, this is the implementation of a single tap/click down:
In this example there are a couple monolithic decisions, along with a single compositional decision. First, the top-level editor UI clears any existing selection when the user taps down. Then, later, the top-level editor UI configures a collapsed selection at whatever the logical position where the user tapped. The one compositional decision is where the
docPosition
is obtained. Wherever the user tapped, there is some kind of UI component. That UI component is asked to determine an appropriateDocumentPosition
for that (x,y) offset.In addition to gesture handling, this same widget also handles keyboard input. Therefore, all selection modifications begin from this widget.
Ideally, selection behaviors would operate in a purely compositional manner, similar to layout and painting. However, given that selection behaviors often span independent visual components, it's unclear if there is any generally applicable, compositional API that would allow for all possible selection behaviors. If such an approach is possible,
super_editor
should adopt that approach, instead.With a logical representation for document selection, and a gesture/keyboard mechanism to start and alter a selection, the only remaining requirement is painting.
Mapping Cartesian Coordinates to Document Positions
When the user starts a selection at a given (x,y) coordinate, or moves the boundary of a selection to a given (x,y) coordinate, the editor must determine the document position at that (x,y). Similarly, there are times when a given document position must be mapped to a visual (x,y) coordinate.
This mapping is handled in a compositional manner.
A given (x,y) coordinate is mapped to a specific document UI component widget by way of the render tree. From there, that document UI component maps the (x,y) coordinate in local space to a
DocumentPosition
.A given
DocumentPosition
can also be mapped to an (x,y) coordinate. First, the document layout is asked to find the UI component that corresponds to the given node ID. Once the UI component is located, the UI component maps theDocumentPosition
to an (x,y) coordinate.Lastly, because the UI component is a widget, it may contain any number of other widgets that participate in the mapping process, e.g., a table that defers to a cell that defers to text.
Painting a Document Selection
Unlike starting and changing a selection, selection painting is handled on a per-component basis.
super_editor
has an overall document layout widget, which then builds other widgets for each piece of content within the document, e.g., headers, paragraphs, list items, images. When the overall document layout builds these content UI components, the document layout calculates which portion of that piece of content is selected, and then instructs the UI component to "select" that portion of itself.In the case of a paragraph, a
TextComponent
widget is constructed with a givenTextSelection
. TheTextComponent
paints selection rectangles around the content within thatTextSelection
.In the case of an image, an
ImageComponent
is told whether the image is selected or not. If the image is selected, then theImageComponent
paints a border around the image.Beta Was this translation helpful? Give feedback.
All reactions