Search Docs
Loading...
Skip to content

Canvas Menu

Customize the canvas menu through complete replacement for strict control or modification for flexible extension.

Canvas Menu

5 mins
estimated time
GitHub

Overview#

The canvas menu is the floating toolbar that appears when a design block is selected on the canvas. CE.SDK iOS provides two approaches for customizing it:

ApproachModifierBest For
Replacement.imgly.canvasMenuItemsExact control over items and order, version-safe
Modification.imgly.modifyCanvasMenuItemsExtending or adjusting the default configuration

The context.selection property provides cached selection info (block, type, fillType, kind) optimized for the canvas menu’s presentation lifecycle.

Complete Replacement#

Use .imgly.canvasMenuItems to define the exact set of items from scratch. This approach is version-safe since you control everything explicitly.

.imgly.canvasMenuItems { context in
CanvasMenu.Buttons.selectGroup()
CanvasMenu.Divider()
CanvasMenu.Buttons.bringForward()
CanvasMenu.Buttons.sendBackward()
CanvasMenu.Divider()
CanvasMenu.Buttons.duplicate()
CanvasMenu.Buttons.delete()
}
  • The @CanvasMenu.Builder closure receives a CanvasMenu.Context with the current selection
  • No default items are included — only what you specify appears
  • Item order is guaranteed across editor versions

Modifying Default Items#

Use .imgly.modifyCanvasMenuItems to extend or adjust the default configuration without replacing it entirely.

.imgly.modifyCanvasMenuItems { context, items in

The modifier provides a CanvasMenu.Modifier with these operations:

OperationPurpose
items.addFirst(_:)Prepend at beginning
items.addLast(_:)Append at end
items.addBefore(id:_:)Insert before specific item
items.addAfter(id:_:)Insert after specific item
items.replace(id:_:)Replace existing item
items.remove(id:)Remove item by ID

Add items at the start or end:

items.addFirst {
CanvasMenu.Button(id: "my.package.canvasMenu.button.first") { context in
print("First Button action")
} label: { context in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
items.addLast {
CanvasMenu.Button(id: "my.package.canvasMenu.button.last") { context in
print("Last Button action")
} label: { context in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}

Position relative to existing items:

items.addBefore(id: CanvasMenu.Buttons.ID.sendBackward) {
CanvasMenu.Button(id: "my.package.canvasMenu.button.beforeSendBackward") { context in
print("Before Send Backward action")
} label: { context in
Label("Before Send Backward", systemImage: "arrow.backward.square")
}
}
items.addAfter(id: CanvasMenu.Buttons.ID.bringForward) {
CanvasMenu.Button(id: "my.package.canvasMenu.button.afterBringForward") { context in
print("After Bring Forward action")
} label: { context in
Label("After Bring Forward", systemImage: "arrow.forward.square")
}
}

Replace or remove items:

items.replace(id: CanvasMenu.Buttons.ID.duplicate) {
CanvasMenu.Button(id: "my.package.canvasMenu.button.replacedDuplicate") { context in
print("Replaced Duplicate action")
} label: { context in
Label("Replaced Duplicate", systemImage: "arrow.uturn.down.square")
}
}
items.remove(id: CanvasMenu.Buttons.ID.delete)

Item Types#

Predefined Buttons#

CE.SDK provides predefined buttons for common canvas actions:

CanvasMenu.Buttons.duplicate()
ButtonID
CanvasMenu.Buttons.bringForward.bringForward
CanvasMenu.Buttons.sendBackward.sendBackward
CanvasMenu.Buttons.duplicate.duplicate
CanvasMenu.Buttons.delete.delete
CanvasMenu.Buttons.selectGroup.selectGroup

Each predefined button accepts optional action, label, isEnabled, and isVisible parameters to customize its behavior:

CanvasMenu.Buttons.delete(
action: { context in
context.eventHandler.send(.deleteSelection)
},
label: { context in
Label { Text("Delete") } icon: { Image.imgly.delete }
},
isEnabled: { context in true },
isVisible: { context in
try context.engine.block.isAllowedByScope(context.selection.block, key: "lifecycle/destroy")
},
)

Custom Buttons#

Create custom buttons with unique IDs using reverse domain notation:

CanvasMenu.Button(
id: "my.package.canvasMenu.button.newButton",
) { context in
print("New Button action")
} label: { context in
Label("New Button", systemImage: "star.circle")
} isEnabled: { context in
true
} isVisible: { context in
true
}

Dividers#

Use CanvasMenu.Divider() to visually separate groups of items. Adjacent dividers collapse automatically, and dangling leading or trailing dividers are removed. See the complete replacement example above for usage.

Custom Items#

For full control over rendering, conform to the CanvasMenu.Item protocol:

private struct CustomCanvasMenuItem: CanvasMenu.Item {
var id: EditorComponentID { "my.package.canvasMenu.newCustomItem" }
func body(_ context: CanvasMenu.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: CanvasMenu.Context) throws -> Bool {
true
}
}

Use the custom item in your menu configuration:

CustomCanvasMenuItem()

Next Steps#