Project: Interactive Button
Build a button that reacts to pointer input and fires a ViewModel trigger when clicked.
Rive Setup
- Create a ViewModel named
ButtonVMwith:pressed(Boolean)click(Trigger)
- Bind
pressedto a State Machine input or a shape color - Attach this Node script to a group on your Artboard
Node Script
--!strict
export type ButtonScript = {
width: Input<number>,
height: Input<number>,
isPressed: boolean,
fill: Paint,
stroke: Paint,
path: Path,
pressedProp: Property<boolean>?,
clickTrigger: PropertyTrigger?,
}
local function inBounds(self: ButtonScript, pos: Vector): boolean
local halfW = self.width / 2
local halfH = self.height / 2
return pos.x >= -halfW and pos.x <= halfW and pos.y >= -halfH and pos.y <= halfH
end
local function rebuild(self: ButtonScript)
local halfW = self.width / 2
local halfH = self.height / 2
self.path:reset()
self.path:moveTo(Vector.xy(-halfW, -halfH))
self.path:lineTo(Vector.xy(halfW, -halfH))
self.path:lineTo(Vector.xy(halfW, halfH))
self.path:lineTo(Vector.xy(-halfW, halfH))
self.path:close()
end
function init(self: ButtonScript, context: Context): boolean
self.isPressed = false
self.fill = Paint.with({ style = "fill", color = Color.rgb(80, 170, 255) })
self.stroke = Paint.with({ style = "stroke", thickness = 4, color = Color.rgb(40, 70, 120) })
self.path = Path.new()
local vm = context:viewModel()
if vm then
self.pressedProp = vm:getBoolean("pressed")
self.clickTrigger = vm:getTrigger("click")
end
rebuild(self)
return true
end
function update(self: ButtonScript)
rebuild(self)
end
function pointerDown(self: ButtonScript, event: PointerEvent)
if inBounds(self, event.position) then
self.isPressed = true
self.fill.color = Color.rgb(255, 140, 90)
if self.pressedProp then
self.pressedProp.value = true
end
event:hit()
end
end
function pointerUp(self: ButtonScript, event: PointerEvent)
if self.isPressed then
self.isPressed = false
self.fill.color = Color.rgb(80, 170, 255)
if self.pressedProp then
self.pressedProp.value = false
end
if inBounds(self, event.position) and self.clickTrigger then
self.clickTrigger:fire()
end
event:hit()
end
end
function draw(self: ButtonScript, renderer: Renderer)
renderer:drawPath(self.path, self.fill)
renderer:drawPath(self.path, self.stroke)
end
return function(): Node<ButtonScript>
return {
init = init,
update = update,
draw = draw,
pointerDown = pointerDown,
pointerUp = pointerUp,
width = 180,
height = 80,
isPressed = false,
path = Path.new(),
fill = Paint.new(),
stroke = Paint.new(),
pressedProp = nil,
clickTrigger = nil,
}
end
How It Works
State Management
isPressedtracks whether the button is currently being pressedpressedPropsyncs with the ViewModel'spressedboolean for visual feedbackclickTriggerfires the ViewModel trigger when a complete click occurs
Hit Detection
The inBounds helper checks if a pointer position is within the button's rectangular bounds, calculated from width and height inputs.
Pointer Events
- pointerDown: Changes color to pressed state, updates ViewModel
- pointerUp: Restores color, fires trigger only if released within bounds
Visual Feedback
The button draws itself using the Drawing API with a fill and stroke. Colors change based on the isPressed state.
Extension Ideas
- Add a hover state with
pointerMove - Animate the button depth using
advance - Use a
Soundtrigger via ViewModel - Add rounded corners by building a rounded-rect Path manually (cubic beziers)
- Implement a disabled state controlled by ViewModel boolean
Next Steps
- Continue to Data Visualization
- Need a refresher? Review Quick Reference