Skip to main content

Interactive Components

Now let's make your windows actually do something! This guide covers buttons, text inputs, and item slots (buckets).

Buttons

Buttons are the bread and butter of interactive UI.

window:addButton(index, state, x, y, w, h, render)

Parameters:

  • index - Unique button ID within this window
  • state - Initial state (0=disabled, 1=enabled)
  • x, y - Position relative to window
  • w, h - Button size
  • render - Render function

Returns: Button object or nil

Button Properties

button.index  -- Button ID
button.state -- State (0=disabled, 1=enabled)
button.hover -- Mouse over? (0=no, 1=yes)
button.click -- Click state:
-- 0 = no interaction
-- 1 = just clicked
-- 2 = holding click
-- 3 = released click (DO YOUR ACTION HERE!)
button.x -- Relative X position
button.y -- Relative Y position
button.w -- Width
button.h -- Height

Button Render Function

Here's how to handle button rendering and clicks:

function MyPlugin.renderCloseButton(window, button)
-- Check button state to render correctly

if button.hover == 0 then
-- Mouse NOT over button - normal state
button:renderAsset(GlobalAsset.buttonCloseN, 0, 0, button.w, button.h)

elseif button.click == 0 then
-- Mouse over button - hover state
button:renderAsset(GlobalAsset.buttonCloseH, 0, 0, button.w, button.h)

elseif button.click == 1 then
-- Just clicked - pressed state
button:renderAsset(GlobalAsset.buttonCloseC, 0, 0, button.w, button.h)

elseif button.click == 2 then
-- Holding click - pressed state
button:renderAsset(GlobalAsset.buttonCloseC, 0, 0, button.w, button.h)

elseif button.click == 3 then
-- Released click - EXECUTE ACTION HERE
button:renderAsset(GlobalAsset.buttonCloseC, 0, 0, button.w, button.h)

-- ACTION: Close window
window:setState(0)
window:setSpot(window.ox, window.oy)
end
end

Complete Button Example

function MyPlugin.renderConfirmButton(window, button)
-- Render button background based on state
if button.hover == 0 then
button:renderAsset(GlobalAsset.buttonN, 0, 0, button.w, button.h)
elseif button.click == 0 then
button:renderAsset(GlobalAsset.buttonH, 0, 0, button.w, button.h)
elseif button.click == 1 or button.click == 2 then
button:renderAsset(GlobalAsset.buttonC, 0, 0, button.w, button.h)
elseif button.click == 3 then
button:renderAsset(GlobalAsset.buttonC, 0, 0, button.w, button.h)

-- ACTION: Send data to server
local textInput = window:getTextInput(0)
if textInput and textInput.inputText ~= "" then
MagicWorld:sendData(PluginPacket.MyPlugin, {textInput.inputText})
NoticeSend(1, "Data sent!")
end
end

-- Render text on button
button:renderText(
"Confirm",
0, 0, button.w, button.h,
4, -- Center
RGBA(255, 255, 255, 255),
FontM
)
end

Button Methods

-- Get button reference
local button = window:getButton(0)
if button then
button:setState(0) -- Disable
end

-- Remove button
local success = window:delButton(0)

-- Change button state
button:setState(0) -- Disable button
button:setState(1) -- Enable button

Text Input Fields

Want players to type something? Use text inputs!

window:addTextInput(index, state, inputSize, inputFlag, x, y, w, h, render)

Parameters:

  • index - Unique field ID within this window
  • state - Initial state (0=disabled, 1=enabled)
  • inputSize - Max text length (characters)
  • inputFlag - Behavior flags (0=normal, 8=password)
  • x, y - Position relative to window
  • w, h - Field size
  • render - Render function

Returns: TextInput object or nil

Input Flags

0 = Normal (shows text)
8 = Password (shows asterisks)

Text Input Properties

textInput.index       -- Field ID
textInput.state -- State (0=disabled, 1=enabled)
textInput.inputSize -- Max text length
textInput.inputFlag -- Flags (0=normal, 8=password)
textInput.inputText -- Current text typed
textInput.inputFocus -- Has focus? (0=no, 1=yes)
textInput.inputPress -- Key pressed (VK code)
textInput.x -- Relative X position
textInput.y -- Relative Y position
textInput.w -- Width
textInput.h -- Height

Creating Text Inputs

BridgeFunction:push("OnLoad", function()
local window = UIWindowAdd(
UI_GROUP_INGAME, GetWindowIndex(),
0, 2, 100, 100, 300, 200, 1,
MyPlugin.renderWindow, nil
)

if window then
-- Username field
local nameInput = window:addTextInput(
0, -- ID
1, -- Enabled
20, -- Max 20 characters
0, -- Normal (not password)
20, 50, -- Position
260, 30, -- Size
MyPlugin.renderTextInput
)

if nameInput then
nameInput:setInputTextFont(FontM)
nameInput:setInputTextSpot(5, 0) -- Internal padding
nameInput:setInputTextSize(0, nameInput.h)
nameInput:setInputTextColor(RGBA(255, 255, 255, 255))
nameInput:setInputBackColor(RGBA(0, 0, 0, 200))
end

-- Password field
local passInput = window:addTextInput(
1, -- ID
1, -- Enabled
20, -- Max 20 characters
8, -- Password (shows ***)
20, 90, -- Position
260, 30, -- Size
MyPlugin.renderTextInput
)

if passInput then
passInput:setInputTextFont(FontM)
passInput:setInputTextSpot(5, 0)
passInput:setInputTextSize(0, passInput.h)
passInput:setInputTextColor(RGBA(255, 255, 255, 255))
passInput:setInputBackColor(RGBA(0, 0, 0, 200))
passInput:setInputTabTarget(0) -- Tab goes to field 0
end
end
end)

Text Input Render Function

function MyPlugin.renderTextInput(window, textInput)
-- Render field background
UIRenderWindowColor(
textInput,
0, 0, textInput.w, textInput.h,
RGBA(0, 0, 0, 200), -- Background color
RGBA(100, 100, 100, 255) -- Border color
)

-- Render focus indicator
if textInput.inputFocus == 1 then
-- Field has focus - draw highlighted border
textInput:renderColor(0, 0, textInput.w, 2, RGBA(255, 255, 0, 255))
textInput:renderColor(0, textInput.h-2, textInput.w, 2, RGBA(255, 255, 0, 255))
end
end

Text Input Methods

-- Get text input reference
local textInput = window:getTextInput(0)
if textInput then
local text = textInput.inputText
LogPrint("Typed text: " .. text)
end

-- Remove text input
window:delTextInput(0)

-- Change state
textInput:setState(1)

-- Change max size
textInput:setInputSize(50)

-- Change flag
textInput:setInputFlag(8) -- Make it a password field

-- Set text programmatically
textInput:setInputText("New text")
textInput:setInputText("") -- Clear field

-- Configure appearance
textInput:setInputTextFont(FontM)
textInput:setInputTextSpot(5, 0) -- 5px padding on left
textInput:setInputTextSize(0, textInput.h)
textInput:setInputTextColor(RGBA(255, 255, 255, 255))
textInput:setInputBackColor(RGBA(0, 0, 0, 200))

-- Set tab target
textInput:setInputTabTarget(1) -- Tab goes to field 1

-- Focus control
textInput:giveFocus() -- Give focus (allow typing)
textInput:takeFocus() -- Remove focus

Buckets (Item Slots)

Buckets are slots that can receive dragged items. Perfect for inventory systems!

window:addBucket(index, state, x, y, w, h, scale, render)

Parameters:

  • index - Unique bucket ID
  • state - Initial state (0=disabled, 1=enabled)
  • x, y - Position relative to window
  • w, h - Size
  • scale - Render scale
  • render - Render function

Returns: Bucket object or nil

Creating a Grid of Buckets

BridgeFunction:push("OnLoad", function()
local window = UIWindowAdd(
UI_GROUP_INGAME, GetWindowIndex(),
0, 2, 100, 100, 400, 300, 1,
MyPlugin.renderWindow, nil
)

if window then
-- Create 8x4 grid of buckets (inventory)
local startX = 20
local startY = 50
local slotSize = 36

for row = 0, 3 do
for col = 0, 7 do
local bucketIndex = (row * 8) + col
local x = startX + (col * slotSize)
local y = startY + (row * slotSize)

window:addBucket(
bucketIndex,
1,
x, y,
32, 32,
1,
MyPlugin.renderBucket
)
end
end
end
end)

Bucket Properties

bucket.index  -- Bucket ID
bucket.state -- State (0=disabled, 1=enabled)
bucket.hover -- Mouse over? (0=no, 1=yes)
bucket.click -- Click state
bucket.x -- X position
bucket.y -- Y position
bucket.w -- Width
bucket.h -- Height
bucket.scale -- Scale

Bucket Render Function

function MyPlugin.renderBucket(window, bucket)
-- Render slot background
local bgColor = RGBA(30, 30, 30, 200)

if bucket.hover == 1 then
-- Highlight when mouse is over
bgColor = RGBA(50, 50, 50, 255)
end

bucket:renderColor(0, 0, bucket.w, bucket.h, bgColor)

-- Render border
bucket:renderColor(0, 0, bucket.w, 1, RGBA(100, 100, 100, 255))
bucket:renderColor(0, 0, 1, bucket.h, RGBA(100, 100, 100, 255))
bucket:renderColor(bucket.w-1, 0, 1, bucket.h, RGBA(100, 100, 100, 255))
bucket:renderColor(0, bucket.h-1, bucket.w, 1, RGBA(100, 100, 100, 255))

-- Render item if there is one
-- (normally comes from stored data)
local data = window:getAttach()
if data and data.items and data.items[bucket.index] then
local item = data.items[bucket.index]
bucket:renderItem(2, 2, 28, 28, item.index, item.level, 0, 0)
end
end

Bucket Methods

-- Get bucket reference
local bucket = window:getBucket(0)

-- Remove bucket
window:delBucket(0)

-- Change state
bucket:setState(1)

Complete Example - Login Form

Here's everything together in a working login form:

LoginForm = {
index = GetWindowIndex(),
group = UI_GROUP_INGAME,
}

function LoginForm.renderWindow(window)
-- Border and background
UIRenderWindow(window, 0, 0, window.w, window.h, 0)

-- Title
window:renderColor(0, 0, window.w, 35, RGBA(0, 0, 0, 200))
window:renderText("Login", 0, 10, window.w, 30, 4, RGBA(255,215,0,255), FontL)

-- Labels
window:renderText("Username:", 20, 50, 100, 25, 1, RGBA(255,255,255,255), FontM)
window:renderText("Password:", 20, 90, 100, 25, 1, RGBA(255,255,255,255), FontM)
end

function LoginForm.renderTextInput(window, textInput)
UIRenderWindowColor(textInput, 0, 0, textInput.w, textInput.h,
RGBA(0,0,0,200), RGBA(100,100,100,255))

if textInput.inputFocus == 1 then
textInput:renderColor(0, 0, textInput.w, 2, RGBA(0,255,0,255))
end
end

function LoginForm.renderButton(window, button)
if button.hover == 0 then
button:renderAsset(GlobalAsset.buttonN, 0, 0, button.w, button.h)
elseif button.click == 0 then
button:renderAsset(GlobalAsset.buttonH, 0, 0, button.w, button.h)
else
button:renderAsset(GlobalAsset.buttonC, 0, 0, button.w, button.h)

if button.click == 3 then
-- Execute login
local userInput = window:getTextInput(0)
local passInput = window:getTextInput(1)

if userInput and passInput then
local user = userInput.inputText
local pass = passInput.inputText

if user ~= "" and pass ~= "" then
MagicWorld:sendData(PluginPacket.Login, {user, pass})
userInput:setInputText("")
passInput:setInputText("")
window:setState(0)
else
NoticeSend(1, "Fill all fields!")
end
end
end
end

button:renderText("Enter", 0, 0, button.w, button.h, 4,
RGBA(255,255,255,255), FontM)
end

BridgeFunction:push("OnLoad", function()
local w = 320
local h = 180

local window = UIWindowAdd(
LoginForm.group, LoginForm.index,
0, 2,
GetWindowCenterX(w), GetWindowCenterY(h),
w, h, 1,
LoginForm.renderWindow, nil
)

if window then
window:setDragArea(0, 0, w, 35)

-- Username field
local userInput = window:addTextInput(0, 1, 20, 0, 20, 70, 280, 30,
LoginForm.renderTextInput)
if userInput then
userInput:setInputTextFont(FontM)
userInput:setInputTextSpot(5, 0)
userInput:setInputTextSize(0, userInput.h)
userInput:setInputTabTarget(1)
end

-- Password field
local passInput = window:addTextInput(1, 1, 20, 8, 20, 110, 280, 30,
LoginForm.renderTextInput)
if passInput then
passInput:setInputTextFont(FontM)
passInput:setInputTextSpot(5, 0)
passInput:setInputTextSize(0, passInput.h)
end

-- Enter button
window:addButton(0, 1, 110, 145, 100, 30, LoginForm.renderButton)
end
end)

Quick Tips

  1. Button click == 3 is your trigger - That's when you execute the action
  2. Always validate text input - Check if it's not empty before sending
  3. Use setInputTabTarget - Makes forms feel professional
  4. Buckets are for drag-and-drop - Regular item display doesn't need buckets
  5. Test with disabled components - Make sure disabled buttons don't do anything

Now you know how to make interactive UIs! Next: capturing keyboard and mouse input.