Link Search Menu Expand Document

Input

These types form the platform-neutral input pipeline for OCCTSwiftViewport. Native gesture recognisers (UIKit on iOS, AppKit on macOS) or spatial-input handlers (visionOS) translate their events into ViewportInputEvent values and call ViewportController.dispatch(_:). ViewportModifierKeys bridges platform modifier-flag types into a portable OptionSet. GestureConfiguration controls both sensitivity and the mapping from modifier states to camera actions; GestureAction is the set of named actions those mappings can produce.

Topics


ViewportInputEvent

public enum ViewportInputEvent: Sendable, Equatable

A platform-neutral viewport input event. Deltas and velocities are raw values in points / points-per-second; interpretation (which GestureAction applies, sign conventions, zoom curve) is applied centrally by ViewportController.dispatch(_:), not by the platform translation layer.

This is the seam that lets non-AppKit/UIKit sources — visionOS, Catalyst, scripting, tests — drive the camera through one entry point.


.dragChanged(delta:modifiers:)

case dragChanged(delta: SIMD2<Float>, modifiers: ViewportModifierKeys)

Primary pointer drag changed. On macOS this is a mouse drag; on iOS a single-finger drag. modifiers reflects the keyboard state at the time of the event and is empty on iOS. ViewportController.dispatch(_:) resolves the applicable GestureAction via GestureConfiguration.dragAction(for:).

controller.dispatch(.dragChanged(delta: SIMD2(4, -2), modifiers: [.shift]))

.dragEnded(velocity:modifiers:)

case dragEnded(velocity: SIMD2<Float>, modifiers: ViewportModifierKeys)

Primary pointer drag ended. velocity is the release velocity in points per second and is used to seed inertia when GestureConfiguration.enableInertia is true.


.twoFingerPanChanged(translation:)

case twoFingerPanChanged(translation: SIMD2<Float>)

Two-finger pan (iOS) changed. translation is the accumulated translation of the gesture in points since it began, matching the coordinate convention of UIPanGestureRecognizer.translation(in:).


.twoFingerPanEnded(velocity:)

case twoFingerPanEnded(velocity: SIMD2<Float>)

Two-finger pan ended. velocity seeds pan inertia.


.pinchChanged(scale:)

case pinchChanged(scale: Float)

Pinch changed. scale is an incremental scale ratio relative to the previous event — 1.0 means no change. Zoom is applied toward the view centre.


.pinchAtChanged(scale:centerNDC:aspectRatio:)

case pinchAtChanged(scale: Float, centerNDC: SIMD2<Float>, aspectRatio: Float)

Pinch changed with a known gesture centre in normalized-device coordinates (NDC, −1…+1 on each axis). Zoom is applied toward centerNDC rather than the view centre, matching the behaviour of mainstream CAD and map applications. aspectRatio is viewWidth / viewHeight and is required to convert the NDC position into world space correctly.

// Zoom toward the mid-point between two fingers
let center = SIMD2<Float>(0.1, -0.2) // NDC
controller.dispatch(.pinchAtChanged(scale: 1.05, centerNDC: center, aspectRatio: aspectRatio))

.pinchEnded

case pinchEnded

Pinch gesture ended. No payload; the router uses this for gesture-state cleanup.


.rotateChanged(radians:)

case rotateChanged(radians: Float)

Two-finger rotation changed. radians is the incremental angle since the last event (positive = counter-clockwise on screen).


.rotateEnded

case rotateEnded

Two-finger rotation gesture ended.


.scroll(delta:cursorNDC:aspectRatio:)

case scroll(delta: Float, cursorNDC: SIMD2<Float>, aspectRatio: Float)

Scroll-wheel input (macOS). delta is a single-axis scroll amount; positive values zoom in. Zoom is applied toward cursorNDC so that the point under the cursor stays fixed, matching the behaviour of .pinchAtChanged. aspectRatio serves the same purpose as in that case.


.tap(ndc:count:)

case tap(ndc: SIMD2<Float>, count: Int)

A tap or click at ndc (normalized-device coordinates). When count >= 2 the router calls reset(animated: true) to return the camera to its default view. Single taps are delivered to any onInputEvent observer but do not trigger a built-in camera action (object picking runs through a separate renderer-bound path).


ViewportModifierKeys

public struct ViewportModifierKeys: OptionSet, Sendable, Hashable

Platform-neutral keyboard-modifier state used to interpret viewport drag input. Analogous to OCCT’s Aspect_VKeyFlags. Bridge from a platform type with the init(_:) overloads, then resolve an action with GestureConfiguration.dragAction(for:).


rawValue

public let rawValue: Int

Static members

public static let shift:   ViewportModifierKeys  // Shift key
public static let control: ViewportModifierKeys  // Control key
public static let option:  ViewportModifierKeys  // Option / Alt key
public static let command: ViewportModifierKeys  // Command / Meta key

init(rawValue:)

public init(rawValue: Int)

Memberwise initialiser required by OptionSet. Prefer the platform bridge overloads or the named static members.


init(_ flags: NSEvent.ModifierFlags) — macOS

// Available on macOS only (canImport(AppKit))
public init(_ flags: NSEvent.ModifierFlags)

Bridges AppKit modifier flags into the portable representation. Maps .shift, .control, .option, and .command.

// In an NSViewController subclass
override func mouseDragged(with event: NSEvent) {
    let mods = ViewportModifierKeys(event.modifierFlags)
    controller.dispatch(.dragChanged(delta: SIMD2(Float(event.deltaX), Float(event.deltaY)),
                                     modifiers: mods))
}

init(_ flags: UIKeyModifierFlags) — iOS

// Available on iOS only (canImport(UIKit))
public init(_ flags: UIKeyModifierFlags)

Bridges UIKit key-modifier flags into the portable representation. Maps .shift.shift, .control.control, .alternate.option, .command.command.


GestureConfiguration

public struct GestureConfiguration: Sendable

Configuration for gesture handling in the viewport. Controls sensitivity, orbit-inversion flags, inertia behaviour, and the mapping from input gestures / modifier keys to GestureAction values. Assigned to ViewportConfiguration.gestureConfiguration.


Presets

.default

public static let `default` = GestureConfiguration()

Shapr3D-style defaults: single-finger / bare-mouse drag orbits; shift-drag pans; option-drag zooms; command-drag selects; two-finger pan pans; pinch zooms; double-tap/click resets.

.blender

public static let blender = GestureConfiguration(
    mouseDrag: .select,
    shiftDrag: .pan,
    optionDrag: .orbit,
    commandDrag: .zoom
)

Blender-style mapping: bare mouse drag selects; option-drag orbits; shift-drag pans; command-drag zooms. All other fields use .default values.

.fusion360

public static let fusion360 = GestureConfiguration(
    mouseDrag: .select,
    shiftDrag: .orbit,
    optionDrag: .pan,
    commandDrag: .zoom
)

Fusion 360-style mapping: bare mouse drag selects; shift-drag orbits; option-drag pans; command-drag zooms.

.visionOS

public static let visionOS = GestureConfiguration(
    dampingFactor: 0.15
)

Starting point for visionOS spatial (indirect pinch + look) input in a window or volume. Keeps the touch-style mapping (single pinch-drag orbits; two-handed pinch/twist for zoom/roll) and raises inertia damping slightly so momentum settles more predictably with indirect input. Tune sensitivities on Vision Pro hardware.


Sensitivity fields

Property Type Default Description
orbitSensitivity Float 0.005 Orbit turn rate in radians per drag point.
panSensitivity Float 0.005 Pan speed multiplier.
zoomSensitivity Float 1.0 Pinch/drag-zoom multiplier.
scrollZoomSensitivity Float 0.25 Scroll-wheel zoom multiplier.
minPanSpeed Float 0.001 Minimum pan speed floor; prevents stalling when zoomed very close.

Orbit-inversion flags

public var invertOrbitHorizontal: Bool  // default false
public var invertOrbitVertical: Bool    // default false

When false (default) a left-drag rotates the scene counter-clockwise — the camera orbits right around the model. Set invertOrbitHorizontal = true for “grab the model and drag it” (object follows the pointer). The same logic applies vertically for invertOrbitVertical.


Inertia

public var enableInertia: Bool    // default true
public var dampingFactor: Float   // default 0.1

enableInertia enables post-gesture momentum. dampingFactor controls how quickly momentum decays: 0 means no damping (perpetual spin); 1 means instant stop. The .visionOS preset uses 0.15.


iOS gesture mapping

public var singleFingerDrag: GestureAction  // default .orbit
public var twoFingerDrag:    GestureAction  // default .pan
public var pinchGesture:     GestureAction  // default .zoom
public var doubleTap:        GestureAction  // default .focusOnPoint

macOS gesture mapping

public var mouseDrag:     GestureAction  // default .orbit
public var shiftDrag:     GestureAction  // default .pan
public var optionDrag:    GestureAction  // default .zoom
public var commandDrag:   GestureAction  // default .select
public var scrollWheel:   GestureAction  // default .zoom
public var trackpadPinch: GestureAction  // default .zoom
public var doubleClick:   GestureAction  // default .focusOnPoint

init(...)

public init(
    orbitSensitivity:     Float         = 0.005,
    panSensitivity:       Float         = 0.005,
    zoomSensitivity:      Float         = 1.0,
    scrollZoomSensitivity: Float        = 0.25,
    minPanSpeed:          Float         = 0.001,
    invertOrbitHorizontal: Bool         = false,
    invertOrbitVertical:   Bool         = false,
    enableInertia:        Bool          = true,
    dampingFactor:        Float         = 0.1,
    singleFingerDrag:     GestureAction = .orbit,
    twoFingerDrag:        GestureAction = .pan,
    pinchGesture:         GestureAction = .zoom,
    doubleTap:            GestureAction = .focusOnPoint,
    mouseDrag:            GestureAction = .orbit,
    shiftDrag:            GestureAction = .pan,
    optionDrag:           GestureAction = .zoom,
    commandDrag:          GestureAction = .select,
    scrollWheel:          GestureAction = .zoom,
    trackpadPinch:        GestureAction = .zoom,
    doubleClick:          GestureAction = .focusOnPoint
)

Creates a gesture configuration. All parameters are optional; every field has a sensible default so you can pass only the fields you want to change.

// Orbit-invert for users who prefer "grab-and-drag" feel
var config = ViewportConfiguration()
config.gestureConfiguration = GestureConfiguration(invertOrbitHorizontal: true)

dragAction(for:)

public func dragAction(for modifiers: ViewportModifierKeys) -> GestureAction

Resolves the GestureAction for a pointer drag given the active modifier keys. Priority (highest to lowest): .command.shift.option → unmodified. This is the portable interpretation seam: platform code bridges its native modifier flags into ViewportModifierKeys and calls this method; ViewportInputRouter calls it internally from dispatch(_:).

let action = config.gestureConfiguration.dragAction(for: ViewportModifierKeys([.shift]))
// action == .pan  (with .default preset)

GestureAction

public enum GestureAction: String, CaseIterable, Sendable

The set of named actions that a gesture or modifier-key combination can trigger.

Case Description
.orbit Orbit (rotate) the camera around the pivot point.
.pan Pan the camera parallel to the view plane.
.zoom Zoom in or out.
.select Select objects under the pointer (handled outside the camera router).
.focusOnPoint Focus the camera on the point under the cursor.
.resetView Reset the camera to its default view.
.none No action — the gesture is ignored.

GestureAction conforms to CaseIterable, so you can enumerate all cases to populate a preference UI.

// Build a picker for the "mouse drag" action
Picker("Mouse drag", selection: $config.mouseDrag) {
    ForEach(GestureAction.allCases, id: \.self) { action in
        Text(action.rawValue).tag(action)
    }
}