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 windowstate- Initial state (0=disabled, 1=enabled)x, y- Position relative to windoww, h- Button sizerender- 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 windowstate- Initial state (0=disabled, 1=enabled)inputSize- Max text length (characters)inputFlag- Behavior flags (0=normal, 8=password)x, y- Position relative to windoww, h- Field sizerender- 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 IDstate- Initial state (0=disabled, 1=enabled)x, y- Position relative to windoww, h- Sizescale- Render scalerender- 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
- Button click == 3 is your trigger - That's when you execute the action
- Always validate text input - Check if it's not empty before sending
- Use setInputTabTarget - Makes forms feel professional
- Buckets are for drag-and-drop - Regular item display doesn't need buckets
- 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.