Home › Utilities
Interact
Flux.Interact provides read-only boolean nodes for common UI interaction states (hover, press, selected, focused) along with pointer hit-test predicates (pointIn, sunk, covered).
local button = Flux.new "TextButton" {
Size = UDim2.fromOffset(180, 44),
Text = "Play",
}
local hovered = Flux.Interact.hover(button)
local pressed = Flux.Interact.press(button)
Flux.edit(button) {
BackgroundColor3 = function()
if pressed() then
return Color3.fromRGB(40, 90, 200)
elseif hovered() then
return Color3.fromRGB(70, 130, 255)
end
return Color3.fromRGB(55, 110, 230)
end,
}Every state node starts listening immediately and cleans up automatically when the node, its owning Scope, or object is destroyed, settling to false.
hover
Flux.Interact.hover(object, options?) returns a Node<boolean> that is true while a mouse pointer or touch contact is inside object with no input-claiming GUI drawn above it (checked via sunk).
Because hover re-checks occlusion every frame while the pointer is inside object:
- A floating menu or modal opening on top of
objectimmediately drops the hover state. - Scrolling
objectout from under a stationary pointer drops the hover state. - Closing an obstructing overlay over a stationary pointer restores the hover state.
local hovered = Flux.Interact.hover(button)
-- Container hover: stay hovered even when the pointer is over a child button inside `row`
local rowHovered = Flux.Interact.hover(row, { within = true })Pass { within = true } when tracking a container (such as a card, list row, or panel) so child buttons inside object do not count as covering it (see covered).
press
Flux.Interact.press(object) returns a Node<boolean> that is true while object is actively held down:
- Pointer / Touch: Starts on
MouseButton1orTouchinsideobject(unless covered by an input-sinking GUI above it) and remainstrueuntil that specific input ends anywhere on screen, so dragging slightly off a button never leaves it stuck. - Gamepad / Keyboard: Starts when
ButtonA,Return, orKeypadEnteris pressed whileobjectisGuiService.SelectedObject(and noTextBoxhas focus). - Opening the Roblox escape menu (
GuiService.MenuIsOpen) releases any active press immediately.
local pressed = Flux.Interact.press(button)selected
Flux.Interact.selected(object) returns a Node<boolean> tracking whether object is currently highlighted by gamepad or keyboard UI navigation (GuiService.SelectedObject):
local isSelected = Flux.Interact.selected(button)focused
Flux.Interact.focused(textBox) returns a Node<boolean> tracking whether textBox currently has keyboard focus (TextBox:IsFocused(), Focused, and FocusLost):
local isFocused = Flux.Interact.focused(searchInput)Hit-Testing Predicates
pointIn
Flux.Interact.pointIn(x, y, object) returns true if the inset-relative coordinate (x, y) lies within object's AbsolutePosition and AbsoluteSize bounds (inclusive, ignoring rotation):
if not Flux.Interact.pointIn(input.Position.X, input.Position.Y, dropdownMenu) then
isOpen(false)
endsunk
Flux.Interact.sunk(x, y, object, token?) returns true if input at (x, y) would be claimed by an Active or input-sinking GuiObject rendered above object (or if the Roblox escape menu is open). Pass input as token to memoize the underlying GetGuiObjectsAtPosition query across multiple controls on the same input event:
if not Flux.Interact.sunk(input.Position.X, input.Position.Y, control, input) then
-- Topmost interactive control at this point
endcovered
Flux.Interact.covered(x, y, object, token?) is identical to sunk, except descendants of object do not count as covering it. Use covered on containers (rows, cards, draggable panels) that contain their own buttons:
if not Flux.Interact.covered(input.Position.X, input.Position.Y, cardFrame, input) then
-- Nothing outside `cardFrame` is covering this point
end