Control State
Neuron’s control state system manages how UI elements respond to user interactions. It tracks the current interaction state of a component, triggering visual changes and callbacks.
Control States
The system defines several states:
Initialize: Before any interaction.Default: Normal, idle state.Pressed: When the primary input is actively pressing the element.Hover: When the pointer hovers over the element.Selected: When the element is selected via gamepad or keyboard navigation.SelectedPressed: When a selected element is pressed via gamepad or keyboard.Disabled: When the element is inactive.
State Layer Affordance & Mode
Neuron also supports state layer styling:
- Affordance: Determines which part of the element (background, border, or none) is affected.
- Mode: Adjusts the style presentation (e.g.,
Default,Inverse,Light,Dark).
From a UX perspective, state affordance determines which part of your component — like the background or border — is used to signal interaction. It’s all about providing clear visual cues that a component is interactive. For example, a button might have a highlighted border or a subtle background change on hover.
State mode defines the style of these visual cues. Whether it’s Default, Light, Dark, or Inverse, mode helps match the feedback to your overall design theme while ensuring the interaction feels natural.
Avoid setting the affordance to None. Visual feedback is crucial for letting users know their input is recognized, so always keep some form of affordance enabled. Neuron’s affordance system has built-in support for all interaction methods, so breaking out of it and rolling your own might not work across all devices or input methods.
How It Works
Components (like the Interactable component) use hooks (e.g., useGuiControlState) to monitor interaction events. When an event occurs, a callback updates the control state, which can then drive style changes via design tokens.
Examples
Basic Interaction
A simple button only needs an onActivated callback. When you pass this prop, View switches to using Interactable under the hood.
local React = require(...)
local Neuron = require(...)
local View = Neuron.View
local function BasicButton()
return React.createElement(View, {
onActivated = function()
print("Button activated!")
end,
tag = "bg-primary radius-medium",
size = UDim2.new(0, 100, 0, 50),
}, {
React.createElement("TextLabel", {
Text = "Click Me",
Size = UDim2.new(1, 0, 1, 0),
})
})
end
return BasicButtonReacting to State Changes
For more control, use the onStateChanged callback to react to transitions — say, to update styles based on hover or press events.
Performance Notice
Do not declare the onStateChanged callback inline.
For functional components, wrap it in useCallback and minimize dependencies. In class components, declare it in the Init function. Use bindings wherever possible to reduce re-renders.
local React = require(...)
local Neuron = require(...)
local View = Neuron.View
local ControlState = Neuron.Enums.ControlState
local function HoverButton()
local controlState, setControlState = React.useBinding(ControlState.Initialize)
local handleStateChanged = React.useCallback(function(newState)
setControlState(newState)
print("New state:", newState)
end)
-- Dynamically choose a background based on state.
local backgroundTag = controlState:map(function(state)
if state == ControlState.Hover then
return "bg-hover"
elseif state == ControlState.Pressed then
return "bg-pressed"
else
return "bg-default"
end
end)
return React.createElement(View, {
onStateChanged = handleStateChanged,
tag = backgroundTag,
size = UDim2.new(0, 120, 0, 60),
}, {
React.createElement("TextLabel", {
Text = "Hover Over Me",
Size = UDim2.new(1, 0, 1, 0),
})
})
end
return HoverButtonCustomizing Visual Feedback with StateLayer
You can enhance visual feedback using the stateLayer prop. This allows you to set an affordance (e.g. "Background", "Border", or "None") and a mode (e.g. "Default", "Inverse", "Light", "Dark") for state transitions.
local React = require(...)
local Neuron = require(...)
local View = Neuron.View
local function StateLayerButton()
return React.createElement(View, {
onActivated = function()
print("StateLayer button activated!")
end,
onStateChanged = function(newState)
print("State changed to:", newState)
end,
stateLayer = {
mode = "Dark", -- Choose the visual style
affordance = "Border", -- Apply changes to the border
inset = true, -- Optionally, inset the effect for depth
},
tag = "bg-surface-100 radius-small",
size = UDim2.new(0, 150, 0, 75),
}, {
React.createElement("TextLabel", {
Text = "StateLayer Button",
Size = UDim2.new(1, 0, 1, 0),
})
})
end
return StateLayerButtonHandling Disabled Components
Mark a component as disabled by passing isDisabled = true. The control state system automatically transitions to "Disabled", and any interaction callbacks (like onActivated) won’t fire.
local React = require(...)
local Neuron = require(...)
local View = Neuron.View
local function DisabledButton()
return React.createElement(View, {
isDisabled = true,
tag = "bg-disabled radius-small",
onActivated = function()
print("This won't trigger because the button is disabled.")
end,
size = UDim2.new(0, 120, 0, 60),
}, {
React.createElement("TextLabel", {
Text = "Disabled",
Size = UDim2.new(1, 0, 1, 0),
})
})
end
return DisabledButton