UI Showcases

CrimsonCore is the aggregator layer — the only place allowed to mix CrimsonInput with a feature plugin's UI in one class. The widgets here are reference implementations that demonstrate the recommended wiring pattern; subclass them in Blueprint to use as-is, or copy them as a starting point for project-specific UIs.

Cardinal rule preserved
CrimsonSkillTree (and every other Tier-1 plugin) is free of any CrimsonInput dependency. The cross-plugin wiring lives here, where it's allowed.

UCrimsonCoreSkillTreeWidget_Display

Subclass of UCrimsonSkillTreeWidget_Display (from CrimsonSkillTree). Wires CommonUI's RegisterUIActionBinding(FBindUIActionArgs(...)) route to the base widget's input-agnostic action API. Designer sets a UCrimsonInputConfig plus four gameplay tags (zoom / pan-toggle / primary / secondary), and the skill tree responds to those actions with no project C++.

Configurable properties

PropertyCategoryPurpose
InputConfigInputTagged input config. Its UIInputActions array must contain entries for the four tags below.
ZoomTagInputAxis1D action — mouse wheel delta.
PanToggleTagInputDigital action — typically a mouse button held to pan.
PrimaryTagInputDigital action — runs RequestPrimaryActionOnHoveredNode.
SecondaryTagInputDigital action — runs RequestSecondaryActionOnHoveredNode.
PanTickIntervalInputCursor-follow timer rate while the pan gesture is held. Default: 1/60.
ZoomSpeedFactorZoomPer-wheel-tick rate. Cooked multiplier = 1 + ZoomSpeedFactor (in) or 1 / (1 + ZoomSpeedFactor) (out). Default: 0.07.
MinZoomScale / MaxZoomScaleZoomClamp range applied to CurrentScale × CookedMultiplier before the effective multiplier is fed to ZoomAtScreenPosition. Defaults: 0.75 / 1.75.
InitialCanvasScaleZoomApplied when OnGraphReady fires. 0 = leave engine default. Default: 1.5.

Step 1 — Author UI input actions

In your UCrimsonInputConfig asset, add four entries to the UI Input Actions array. Tag each with a UIInputTag.* gameplay tag.

text
DA_InputConfig_PlayerUI
UIInputActions[0].InputTag = UIInputTag.SkillTree.Zoom # Axis1D (mouse wheel)
UIInputActions[0].InputAction = IA_SkillTree_Zoom
UIInputActions[1].InputTag = UIInputTag.SkillTree.Pan # digital (RMB hold)
UIInputActions[1].InputAction = IA_SkillTree_Pan
UIInputActions[2].InputTag = UIInputTag.SkillTree.Primary # digital (LMB click)
UIInputActions[2].InputAction = IA_SkillTree_Primary
UIInputActions[3].InputTag = UIInputTag.SkillTree.Secondary # digital (RMB click on node)
UIInputActions[3].InputAction = IA_SkillTree_Secondary
CommonUI prerequisite
Requires bEnableEnhancedInputSupport = true in Project Settings → Common Input → Enhanced Input Support. Without it, CommonUI ignores UInputAction-based bindings.

Step 2 — Create the widget Blueprint

Right-click in the Content Browser → User Widget → parent class UCrimsonCoreSkillTreeWidget_Display. In the Class Defaults panel, fill in the five properties (Input Config + four tags). Bind the SkillTreeGraph widget slot to a UCrimsonSkillTreeWidget_Graph subclass placed in the widget tree.

That's the entire integration. NativeOnInitialized on the C++ side registers all four UI action bindings; handlers route to the base widget's action API.

How pan works

Pan is a press-and-hold gesture, which doesn't fit CommonUI's discrete event model. The showcase binds the same PanToggleAction twice via FBindUIActionArgs:

BindingFires onHandlerEffect
#1KeyEvent = IE_PressedHandlePanPressedBeginPan(cursor) + start a 60 Hz timer.
#2KeyEvent = IE_ReleasedHandlePanReleasedClear the timer + EndPan().

The timer's TickPan reads the cursor each tick and calls UpdatePan(cursor). PanTickInterval controls the rate (default 1/60). NativeOnDeactivated defensively clears the timer and calls EndPan() in case the release event didn't fire (e.g. widget swap mid-gesture).

How zoom works

The base plugin's ApplyZoom is a pure math primitive (multiplier + anchor, no policy). Speed cooking and clamping live in the showcase — HandleZoom does:

cpp
const float WheelDelta = /* queried from UEnhancedPlayerInput::GetActionValue(ZoomAction) */;
const float CurrentScale = SkillTreeGraph->GetCanvasRenderScale();
const float RawMultiplier = (WheelDelta > 0) ? (1.f + ZoomSpeedFactor)
: (1.f / (1.f + ZoomSpeedFactor));
const float Desired = FMath::Clamp(CurrentScale * RawMultiplier, MinZoomScale, MaxZoomScale);
const float Effective = Desired / CurrentScale;
ZoomAtScreenPosition(Effective, MousePos);

InitialCanvasScale is applied separately via SetCanvasRenderScale when the graph widget broadcasts OnGraphReady (subscribed in NativeOnInitialized).

Non-CommonUI projects

If your project doesn't use CommonUI (or has bEnableEnhancedInputSupport = false), don't use this widget. Instead, give your player controller a UCrimsonInputComponent and call the base widget's action API directly from the controller's BindNativeAction handlers. The base widget doesn't care which path you take — UCrimsonSkillTreeWidget_Display::Zoom(...) / BeginPan(...) / RequestPrimaryActionOnHoveredNode() / etc. work the same either way.

Source location
Plugins/CrimsonCore/Source/CrimsonCore/Public/UserInterface/SkillTree/CrimsonCoreSkillTreeWidget_Display.h + the matching .cpp under Private/UserInterface/SkillTree/. Copy the file pair into your own plugin if you need to diverge from the showcase.