Context
Continuous position edits currently lower too early into sparse position patches. That works, but it makes tools reason about GlyphLayerPositions when they are really expressing actions like move, rotate, resize, absolute handle placement, snapping, axis locking, or point-rule constrained movement.
We want a more ergonomic tool-facing API before adding snapping and locking behavior.
Settled Naming
Use edit/action language for the public API:
class LayerPositions
interface PositionEdit
class MoveEdit implements PositionEdit
class RotateEdit implements PositionEdit
class ResizeEdit implements PositionEdit
class PlaceEdit implements PositionEdit
Public verbs:
layer.positions.move(...)
layer.positions.rotate(...)
layer.positions.resize(...)
layer.positions.place(...)
Use move instead of translate in tool-facing code. Reserve translate for pure geometry/math or lower-level intent names.
Use resize instead of scale for bounding-box handle interactions. Reserve scale for pure numeric geometry/math.
Use place instead of direct for custom absolute point placement, such as pen handle mirroring.
Avoid public Draft names. The implementation may still use a preview/commit draft internally, but the tool-facing concept is a position edit.
Direction
Explore operation-specific position edits with fluent configuration. Chain order configures behavior only; execution order is fixed internally.
const move = layer.positions
.move(subject)
.from(PositionReference.point(hitPointId))
.axisLockedBy(AxisLock.primary({ when: () => modifiers.shiftKey }))
.snappedBy(snapping)
.constrainedBy(pointRules);
move.preview(delta);
move.commit();
For rotation:
const rotate = layer.positions
.rotate(subject, origin)
.angleSnappedBy(AngleSnap.everyDegrees(15, { when: () => modifiers.shiftKey }));
rotate.preview(angle);
rotate.commit();
For bounding-box resizing:
const resize = layer.positions
.resize(subject, boundsHandle)
.uniformBy({ when: () => modifiers.shiftKey })
.scaleSnappedBy(ScaleSnap.increment(0.1));
resize.preview(pointer);
resize.commit();
For absolute/custom placement:
const handles = layer.positions.place({ points: handlePointIds });
handles.preview([
PositionMove.to(cpOutId, handlePos),
PositionMove.to(cpInId, mirror),
]);
handles.commit();
Design Constraints
- Fluent configuration methods mutate/configure the edit before first preview and return
this.
- Chain order should not define execution order.
- Each edit should expose only relevant modifiers: move gets axis/object snapping/point rules; rotate gets angle snapping; resize gets uniform resize and scale snapping; place gets absolute position moves.
- Preview should always evaluate from the frozen base state, not accumulate from the previous preview frame.
- Raw
GlyphLayerPositions, previewPositionPatch, and commitPositionPatch should remain lowering details or test-only surfaces, not the normal tool-facing API.
- Point rules should integrate as a move modifier, not force tools into a generic
previewResolved(...) path.
- The rules layer should distinguish uniform expanded movement from truly absolute constrained geometry, so smooth-anchor handle-following can still commit as an efficient translation when possible.
- Snapping needs an explicit reference concept, e.g.
PositionReference.point(...), anchor(...), position(...), or bounds references.
Proposed Move Pipeline
Execution order should be fixed internally:
- Raw delta
- Axis lock
- Snapping via
PositionReference
- Point rules
- Preview lowering
- Commit lowering
Open Questions
- What snap result metadata is needed for overlay rendering?
- Where should object/guideline/grid snapping live: a single snapping service or separate composable snap providers?
- How should point-rule movement expose expanded-uniform vs absolute-resolved outcomes internally?
- How should resize snapping arbitrate between snapped handle positions and snapped scale factors?
- Should object/guideline angle snapping be supported for rotation, or should first pass stay with numeric angle snapping only?
Context
Continuous position edits currently lower too early into sparse position patches. That works, but it makes tools reason about
GlyphLayerPositionswhen they are really expressing actions like move, rotate, resize, absolute handle placement, snapping, axis locking, or point-rule constrained movement.We want a more ergonomic tool-facing API before adding snapping and locking behavior.
Settled Naming
Use edit/action language for the public API:
Public verbs:
Use
moveinstead oftranslatein tool-facing code. Reservetranslatefor pure geometry/math or lower-level intent names.Use
resizeinstead ofscalefor bounding-box handle interactions. Reservescalefor pure numeric geometry/math.Use
placeinstead ofdirectfor custom absolute point placement, such as pen handle mirroring.Avoid public
Draftnames. The implementation may still use a preview/commit draft internally, but the tool-facing concept is a position edit.Direction
Explore operation-specific position edits with fluent configuration. Chain order configures behavior only; execution order is fixed internally.
For rotation:
For bounding-box resizing:
For absolute/custom placement:
Design Constraints
this.GlyphLayerPositions,previewPositionPatch, andcommitPositionPatchshould remain lowering details or test-only surfaces, not the normal tool-facing API.previewResolved(...)path.PositionReference.point(...),anchor(...),position(...), or bounds references.Proposed Move Pipeline
Execution order should be fixed internally:
PositionReferenceOpen Questions