CalmUI
Unreal Engine

Input

Use mouse, keyboard, or gamepad with the same CalmUI screen.

Who owns the input mode

By default CalmUI applies the player controller's input mode itself on every screen input-policy change: FInputModeGameOnly under Passthrough, FInputModeGameAndUI otherwise, plus the move/look ignore flags; the screen's mouse-cursor flag applies either way. GameOnly puts the viewport into permanent mouse capture, so a host that owns its own input contexts (Enhanced Input) must turn this off and drive modes itself from OnCalmUiInputPolicyChanged, or the click that should re-acquire capture after a screen closes gets swallowed before it reaches PlayerInput.

Set Project Settings → Plugins → CalmUI → Apply host input mode to off, or add this to DefaultGame.ini:

[/Script/CalmUiRuntime.CalmUiProjectSettings]
bApplyHostInputMode=False

With it off, bind OnCalmUiInputPolicyChanged on UCalmUiLocalPlayerSubsystem and drive your own contexts, capture and lock rules from it, reading IsCalmUiBlockingGameInput(), IsCalmUiPointerOverUi() and GetCalmUiInputPolicy() as needed. An overlay panel shown under a game-and-ui/passthrough policy counts as UI for the pointer-over check only when its root element is marked data-calmui-interactive="true".

See Input profiles and rebinding for the full contract, including why leaving both the host and CalmUI driving input modes makes them fight each other.

CalmUI supports:

  • Mouse move / left button
  • Arrow keys, D-pad and left-stick navigation
  • Tab / Shift+Tab focus
  • Enter, Space, bottom face button = Accept
  • Escape, right face button = Back
  • Left face button (X / □) = Secondary, top face button (Y / △) = Tertiary (no keyboard default)
  • Mouse-wheel and input-profile Scroll event delivery
  • Right and middle mouse buttons
  • Hold to activate (ui:hold-ms) for the primary button, Enter/Space and the face button

Focus moves between available controls. Hidden and disabled controls are skipped. Use Studio's preview to check the basic interaction, then test the final input inside Unreal.

Input is owned by the player's UCalmUiLocalPlayerSubsystem. Two Local Players have separate queues, focus, active-input state, and persisted bindings. The top blocking Modal remains the only interactive View until it closes; input also continues to be serviced while gameplay is paused.

See Input profiles and rebinding to configure Enhanced Input, persistence, and the C++/Blueprint API.

Left, right, and middle mouse buttons all reach CalmUI: the default input profile binds PointerPrimary to the left button, PointerSecondary to the right button, and PointerMiddle to the middle button (ECalmUiInputAction in CalmUiInputProfile.h). Every button dispatches PointerDown/PointerUp to Core's InteractionRuntime, which synthesizes a click event with a DOM-numbered button field (0 primary, 1 middle, 2 secondary) for any button; only a primary press can start a drag interaction, so a secondary or middle press over a draggable control clicks instead of dragging. Pointer-over-UI consumption is button-agnostic: whichever button is pressed, a hit over CalmUI is consumed the same way a primary click is, and a press over the game world is left to the host exactly as it already is for the left button.

Hold to activate

An element authored with ui:hold-ms="2000" (see Hold to activate) needs its activation input held: the primary pointer button pressed on it, or Accept pressed while it is focused. The subsystem already delivers PointerDown/ PointerUp for the primary button; for Accept it now also delivers the release of every bound Accept key (ECalmUiHostInputType::AcceptRelease, from both the Slate navigation pre-processor's key-up and the controller polling fallback), which is what cancels a hold that ends early. The hold itself advances on the shared UI clock (CalmUiClock::UiDeltaSeconds(), the same clamped delta transitions and keyframes use) inside the scene view extension, so a hitch cannot complete a hold early, and on completion the element's normal click/accept is dispatched once with hold: true. During the hold the element carries :holding and publishes --calmui-hold-progress to itself and its descendants; the renderer re-resolves only that subtree as the value changes. A plain press/release on such an element does nothing.

World surfaces expose the same pair through Blueprint: AcceptCalmUiWorldSurface starts the hold and the new ReleaseAcceptCalmUiWorldSurface cancels it. Fixtures drive it with pointer_down / accept, advance_time, pointer_up / accept_release, and assert assert_state ... "state": "holding" and assert_style ... "property": "hold-progress" (R4HoldToActivate).

Secondary and tertiary

ECalmUiInputAction::Secondary and ECalmUiInputAction::Tertiary are the two extra activations of the focused control that a gamepad offers beyond Accept and Back (see Secondary and tertiary activations). The built-in profile binds them to Gamepad_FaceButton_Left (X / □) and Gamepad_FaceButton_Top (Y / △) and to no keyboard key; a press enqueues ECalmUiHostInputType::Secondary / Tertiary and the scene view extension calls InteractionRuntime::Secondary() / Tertiary(), which dispatches one secondary / tertiary event to the focused control, bubbling like Accept. There is no release counterpart because there is no hold variant.

Delivery follows Accept exactly: the Slate navigation pre-processor routes the key down (OS key repeats are ignored) while the game viewport has focus and a screen owns input; the controller polling fallback uses the same "just pressed" edge and the same guards, so a passthrough screen leaves the buttons to the game and a screen pushed or popped this frame does not receive the press that changed the stack. The text sink keeps ownership of a keyboard chord while a field is being edited.

To give the keyboard a chord, add a binding for the action in your UCalmUiInputProfile asset (for example Enter with Shift for Secondary and Enter with Alt for Tertiary, which is what Studio's preview maps) or rebind at run time with Rebind CalmUI Input: the modifier flags on FCalmUiInputBinding make Shift+Enter a different chord from the plain Enter that Accept uses, so the profile still validates. A player's persisted overrides that predate the two actions receive the profile defaults for them on load unless the default chord is already taken by a saved binding. Get CalmUI Action Prompt returns the bound key for both actions, so an ActionPrompt can show "□ Drop" / "△ Compare" next to "✕ Use".

World surfaces expose SecondaryCalmUiWorldSurface and TertiaryCalmUiWorldSurface, the counterparts of AcceptCalmUiWorldSurface. Fixtures drive the actions with the secondary / tertiary steps and assert them with assert_event ... "event": "secondary" / "tertiary" and assert_component_event (now also with exactCount). See R4SecondaryActivation.

Wheel input scrolls overflow: auto and overflow: scroll containers. Editable controls use the shared Core editor, with host keyboard and clipboard delivery. See Scrolling and text entry for the GameUI examples and platform input-method validation boundaries.

Pointer drag/drop can cross simultaneously interactive screen Views in the same player viewport when the source drag group matches the destination drop group. The frontmost hit View owns the destination; an incompatible foreground panel blocks targets behind it. The drag ghost is painted after all screen layers. dragenter and drop carry { key, target, accepted }: key is the source drag key, target is the destination drop key, and accepted reports Core group compatibility, not gameplay authorization. Game handlers still validate transfers. dragleave clears destination feedback on departure, drop, or cancellation. Hiding a participating View or changing the dragged source identity cancels the operation. Keyboard/gamepad pickup remains within its current View. Studio's single-View WASM preview uses the same pointer coordinator and event payloads.

On this page