Search Docs
Loading...
Skip to content

Editor State

Control how users interact with content on the canvas by switching between edit modes, subscribing to state changes, and reading cursor information.

5 mins
estimated time
GitHub

Edit modes define what type of content users can currently modify on the canvas. Each mode enables different interaction behaviors — Transform mode for moving and resizing, Crop mode for adjusting content within frames, Text mode for inline text editing, and so on. The engine maintains the current edit mode as part of its state and notifies subscribers when it changes.

This guide covers:

  • The five built-in edit modes (Transform, Crop, Text, Trim, Playback)
  • Switching edit modes programmatically
  • Subscribing to state changes for UI synchronization
  • Reading cursor type and rotation
  • Tracking text cursor position for overlays
  • Detecting active user interactions

Setup#

Create a scene with an image block and a text block to demonstrate the different edit modes.

let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
// Add an image block to demonstrate Crop mode
let imageBlock = try engine.block.create(.graphic)
try engine.block.setShape(imageBlock, shape: engine.block.createShape(.rect))
try engine.block.setWidth(imageBlock, value: 350)
try engine.block.setHeight(imageBlock, value: 250)
try engine.block.setPositionX(imageBlock, value: 50)
try engine.block.setPositionY(imageBlock, value: 175)
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg",
)
try engine.block.setFill(imageBlock, fill: imageFill)
try engine.block.appendChild(to: page, child: imageBlock)
// Add a text block to demonstrate Text mode
let textBlock = try engine.block.create(.text)
try engine.block.appendChild(to: page, child: textBlock)
try engine.block.replaceText(textBlock, text: "Edit this text")
try engine.block.setTextFontSize(textBlock, fontSize: 48)
try engine.block.setWidthMode(textBlock, mode: .auto)
try engine.block.setHeightMode(textBlock, mode: .auto)
try engine.block.setPositionX(textBlock, value: 450)
try engine.block.setPositionY(textBlock, value: 275)

Edit Modes#

CE.SDK supports five built-in edit modes, each designed for a specific type of interaction with canvas content.

ModePurpose
.transformMove, resize, and rotate blocks (default)
.cropAdjust media content within block frames
.textEdit text content inline
.trimAdjust clip start and end points (video scenes)
.playbackPlay video or audio content (limited interactions)

Getting the Current Mode#

Query the current mode with engine.editor.getEditMode(). The initial mode is always .transform.

// Get the current edit mode (default is Transform)
let initialMode = engine.editor.getEditMode()
print("Initial edit mode: \(initialMode)")

Switching Edit Modes#

Use engine.editor.setEditMode(_:) to change the current editing mode. The mode determines what interactions are available on selected blocks.

// Select the image block and switch to Crop mode
try engine.block.select(imageBlock)
engine.editor.setEditMode(.crop)
print("Switched to Crop mode")
// Switch back to Transform mode
engine.editor.setEditMode(.transform)
print("Switched back to Transform mode")

Subscribing to State Changes#

The engine notifies subscribers whenever the editor state changes, including mode switches and cursor updates. Use the onStateChanged AsyncStream to react to changes.

// Subscribe to state changes using AsyncStream
let stateTask = Task {
for await _ in engine.editor.onStateChanged {
let currentMode = engine.editor.getEditMode()
print("Edit mode changed to: \(currentMode)")
}
}

Common use cases include updating toolbar UI to reflect the current mode, showing mode-specific panels, and disabling actions during Playback mode.

Cancel the task when you no longer need updates to prevent unnecessary work.

Cursor State#

The engine tracks what cursor type should be displayed based on the current context and hovered element. These APIs are most relevant for iPad apps with mouse or trackpad input (iPadOS 13.4+) and macOS apps, where users interact with a pointer. On iPhone, touch interactions don’t use a visible cursor, so you can skip this section if you’re targeting iPhone only.

Reading Cursor Type#

Use engine.editor.getCursorType() to get the cursor type to display.

// Get the cursor type to display the appropriate cursor
let cursorType = engine.editor.getCursorType()
print("Cursor type: \(cursorType)")
// Returns: .arrow, .move, .moveNotPermitted, .resize, .rotate, or .text
Cursor TypeMeaning
.arrowDefault pointer cursor
.moveElement can be moved
.moveNotPermittedElement cannot be moved in the current context
.resizeResize handle is hovered
.rotateRotation handle is hovered
.textText editing cursor

Reading Cursor Rotation#

For directional cursors like resize handles, use engine.editor.getCursorRotation() to get the rotation angle in radians. Apply this rotation to your cursor image for correct visual feedback.

// Get cursor rotation for directional cursors like resize handles
let cursorRotation = engine.editor.getCursorRotation()
print("Cursor rotation (radians): \(cursorRotation)")

Text Cursor Position#

When in Text edit mode, track the text cursor (caret) position for rendering custom overlays or toolbars near the insertion point.

Use getTextCursorPositionInScreenSpaceX() and getTextCursorPositionInScreenSpaceY() to get the cursor position in screen pixels. These values update as the user moves through text.

// Select the text block and switch to Text mode to get cursor position
try engine.block.select(textBlock)
engine.editor.setEditMode(.text)
// Get text cursor position in screen space
let textCursorX = engine.editor.getTextCursorPositionInScreenSpaceX()
let textCursorY = engine.editor.getTextCursorPositionInScreenSpaceY()
print("Text cursor position: (\(textCursorX), \(textCursorY))")

Detecting Active Interactions#

Call engine.editor.unstable_isInteractionHappening() to check if a user interaction like dragging or resizing is in progress. This is useful for deferring expensive operations until after the interaction completes.

// Check if a user interaction is currently in progress
let isInteracting = try engine.editor.unstable_isInteractionHappening()
print("Is interaction happening: \(isInteracting)")

Next Steps#

  • Undo and History — Implement undo/redo functionality and manage history stacks
  • Events — Subscribe to block creation, update, and deletion events
  • Blocks — Understand block types and the design hierarchy
  • Scenes — Learn about scene structure and page management