Skip to content

Pointer session

The session is the one value an app shell keeps for editing. The shell converts each pointer event to a Pointer in page space, passes it in, and draws liveEditor(session). Every function is pure and returns a new session.

type Gesture =
| CreateGesture
| InkGesture
| MoveGesture
| ResizeGesture
| RotateGesture
| HandleGesture
| MarqueeGesture;
type ActiveGesture = { readonly gesture: Gesture; readonly live: Editor };
type Session = {
readonly editor: Editor;
readonly tool: Tool;
readonly style: Style;
readonly textStyle: TextStyle;
readonly enteredGroup: GroupId | undefined;
readonly active: ActiveGesture | undefined;
readonly editing: TextEdit | undefined;
};
  • editor is the committed editor. During a gesture it is also the base that every preview frame starts from.
  • active holds the open gesture and its preview together. There is never a preview without a gesture, or a gesture without a preview.
  • editing holds an open text edit. See Text editing.
  • style is the style for new shapes. Each new shape gets a fresh seed from EditorEnv.newSeed.
  • textStyle is the font size, family and alignment for new text and new labels. See Text editing.
  • enteredGroup is the group the user has entered by double-click, or undefined. A pick expands to the next level inside it, so a click selects one member. It is cleared when the selection leaves the group, and by any perform that does.
  • GestureKind names each Gesture["kind"]: create, ink, move, resize, rotate, handle and marquee. The gesture records are on Editing features.

Pointer events

  • createSession(editor?) starts on the Select tool with DEFAULT_STYLE, DEFAULT_TEXT_STYLE and no gesture.
  • pointerDown(session, pointer, env) cancels any open gesture, then begins one for the current tool. With the Select tool it picks first, which may change the selection. With a drawing tool it mints the new shape’s id. A press on empty canvas begins a marquee. A press that begins nothing leaves active empty.
  • pointerMove(session, pointer, env) computes the next document from editor and the pointer, and stores preview(editor, next) as active.live. It adds no history. Without an open gesture it returns the session unchanged.
  • pointerUp(session, pointer, env) commits the gesture from editor once, so a drag of any length is one undo step. A shape created with a drag tool is selected and the tool returns to Select. A click without a drag creates nothing and keeps the tool. A freehand stroke keeps the freehand tool.
  • liveEditor(session) is what to draw: the text preview while editing, the gesture preview during a gesture, otherwise editor.

Highlight

  • bindingHighlight(session) returns the shape the arrow end being drawn or dragged is bound to in the live preview, or undefined when no such gesture is open or the end is free. The shell outlines it. See Binding arrows on Editing features.

Commands

  • cancel(session) drops the open gesture. The preview is gone and editor is unchanged. Call it on Escape and on pointercancel.
  • perform(session, command) cancels the open gesture, then runs an editor command such as undo, redo or deleteSelection. Undo during a drag drops the drag first, then undoes the last committed entry.
  • group(session, env) and ungroup(session) run groupSelection and ungroupSelection with the entered group as the scope. Bind them to Cmd/Ctrl+G and Cmd/Ctrl+Shift+G.
  • enter(session, point, zoom) is the double-click. It enters the group a click at point would select, and selects the single shape or inner group under the pointer. On a shape with nothing to enter it returns the session unchanged.
  • restyle(session, change) applies change to the session style and, through restyleSelection, to every selected shape in one entry.
  • restyleText(session, change, env) applies change to textStyle and, through restyleTextSelection, to every selected text shape and every selected container’s label, in one entry. It re-fits each frame, so env.measureText is needed. It ends an open text edit first. When nothing changes, meaning no text in the selection moves and textStyle stays equal, it returns the session itself and adds no history. See Text editing.

The camera is not in the session. Zooming or panning during a drag does not touch the gesture. See Camera.

Example

docs/examples/session-drag.ts
import {
createEditor,
createSession,
liveEditor,
perform,
pointerDown,
pointerMove,
pointerUp,
Tool,
undo,
} from "@draw/editor";
import type { EditorEnv, Pointer, Session } from "@draw/editor";
import { bindingId, groupId, pagePoint, seed, shapeId } from "@draw/shapes";
import type { Frame } from "@draw/shapes";
const env: EditorEnv = {
measureText: (line, font) => line.length * font.size,
newShapeId: () => shapeId("drawn"),
newBindingId: () => bindingId("bound"),
newGroupId: () => groupId("unused-group"),
newSeed: () => seed(7),
};
const at = (x: number, y: number): Pointer => ({
point: pagePoint(x, y),
pressure: 0.5,
zoom: 1,
shiftKey: false,
altKey: false,
});
const start: Session = { ...createSession(createEditor()), tool: Tool.rectangle };
const pressed = pointerDown(start, at(0, 0), env);
export const dragging: Session = pointerMove(pressed, at(120, 80), env);
export const drawn: Session = pointerUp(dragging, at(120, 80), env);
export const undone: Session = perform(drawn, undo);
export const preview: Frame | undefined = liveEditor(dragging).doc.shapes.get(
shapeId("drawn"),
)?.frame;

During the drag, the preview holds a 120 by 80 rectangle and the history is still empty. The release commits one entry, selects the rectangle and returns to the Select tool. One undo removes it.