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.