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;};editoris the committed editor. During a gesture it is also the base that every preview frame starts from.activeholds the open gesture and its preview together. There is never a preview without a gesture, or a gesture without a preview.editingholds an open text edit. See Text editing.styleis the style for new shapes. Each new shape gets a fresh seed fromEditorEnv.newSeed.textStyleis the font size, family and alignment for new text and new labels. See Text editing.enteredGroupis the group the user has entered by double-click, orundefined. 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 anyperformthat does.GestureKindnames eachGesture["kind"]:create,ink,move,resize,rotate,handleandmarquee. The gesture records are on Editing features.
Pointer events
createSession(editor?)starts on the Select tool withDEFAULT_STYLE,DEFAULT_TEXT_STYLEand 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 leavesactiveempty.pointerMove(session, pointer, env)computes the next document fromeditorand the pointer, and storespreview(editor, next)asactive.live. It adds no history. Without an open gesture it returns the session unchanged.pointerUp(session, pointer, env)commits the gesture fromeditoronce, 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, otherwiseeditor.
Highlight
bindingHighlight(session)returns the shape the arrow end being drawn or dragged is bound to in the live preview, orundefinedwhen 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 andeditoris unchanged. Call it on Escape and onpointercancel.perform(session, command)cancels the open gesture, then runs an editor command such asundo,redoordeleteSelection. Undo during a drag drops the drag first, then undoes the last committed entry.group(session, env)andungroup(session)rungroupSelectionandungroupSelectionwith 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 atpointwould 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)applieschangeto the session style and, throughrestyleSelection, to every selected shape in one entry.restyleText(session, change, env)applieschangetotextStyleand, throughrestyleTextSelection, to every selected text shape and every selected container’s label, in one entry. It re-fits each frame, soenv.measureTextis needed. It ends an open text edit first. When nothing changes, meaning no text in the selection moves andtextStylestays 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
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.