A lightweight Lua library for building and managing interactive dialogs in SA:MP. Provides object-oriented APIs to create dialogs, handle navigation, and manage user input in a modular way.
- Requirements
- Installation
- Quick Start
- Core Concepts
- Library API
- Dialog API
- Callbacks Reference
- Navigation
- Pagination
- Configuration
- Performance
- Full Example
- License
- Download or clone this repository.
- Rename
easy_dialog-mainfolder toeasy_dialog. - Place it into your
lib/directory (MoonLoader folder). - Require it in your script:
local easy_dialog = require 'easy_dialog'
local Dialog = easy_dialog.Dialoglocal d = Dialog.new()
:setCaption("Hello")
:setStyle("msgbox")
:setContent("Welcome to easy-dialog!")
:setButtons("OK", "")
:setOnResponse(function(self, button)
if button == 1 then
print("Dialog confirmed")
end
end)
easy_dialog.init()
easy_dialog.register("welcome", d)
easy_dialog.start("welcome")Each dialog is an independent object with its own state, callbacks, and configuration. Dialogs are registered by name and navigated via a stack-based system.
Dialogs maintain a navigation history (stack). Use start() to begin a new root flow, go() to push dialogs onto the stack, back() to return, and home() to jump to the root.
All callbacks receive self as the first argument. Return values from child dialogs are passed to the parent's onProcessResult callback.
| Function | Description |
|---|---|
easy_dialog.init() |
Starts the internal handler loop. Call once before showing any dialogs. |
easy_dialog.stop() |
Stops the handler and clears all registered dialogs. |
| Function | Description |
|---|---|
easy_dialog.register(name, dialog) |
Registers a dialog with a unique string key. |
easy_dialog.start(name, data?) |
Opens a dialog as root. Clears the navigation stack. |
easy_dialog.go(name, data?) |
Navigates to a dialog. Behavior depends on its LaunchMode. |
easy_dialog.back() |
Returns to the previous dialog in the stack. |
easy_dialog.home() |
Returns to the root/first dialog in the stack. |
easy_dialog.done(result?) |
Closes current dialog and passes result to parent's onProcessResult. |
easy_dialog.show(dialog, isTemporary?, resetPagination?) |
Directly shows any dialog object. |
| Function | Description |
|---|---|
easy_dialog.alert(caption, text, onOK?) |
Shows a modal message box. |
easy_dialog.confirm(caption, text, onConfirm) |
Shows OK/Cancel confirmation. onConfirm(true) if OK pressed. |
easy_dialog.prompt(caption, text, onInput) |
Shows input dialog. onInput receives user's text. |
local d = Dialog.new() -- create new dialog
local d = Dialog() -- equivalent shorthand| Method | Description |
|---|---|
:setId(id) |
Set custom SAMP dialog ID. IDs 15000-32767 are reserved for auto-allocation. |
:setCaption(text) |
Set dialog title. Accepts a string or a function returning a string. |
:setStyle(style) |
Dialog style. One of: msgbox, input, list, password, tablist, tablist_headers. |
:setButtons(okText, cancelText) |
Set button labels. Cancel text can be empty for single-button dialogs. |
:setContent(text) |
Set static text content. For non-list styles. |
:setItems(items) |
Set list items. Accepts a table or a function returning a table. |
:setHeaders(headers) |
Set tablist column headers. Table of strings. |
:setItemsPerPage(count) |
Enable pagination. Set to 0 to disable. |
:setLaunchMode(mode) |
Set navigation behavior. See Navigation. |
| Method | Description |
|---|---|
:setOnResponse(fn) |
Main response handler. |
:setOnShow(fn) |
Called when dialog becomes visible. |
:setOnUpdate(fn) |
Called every frame while dialog is active. |
:setOnStart(fn) |
Called when dialog is first shown via start() or go(). |
:setOnProcessResult(fn) |
Called when returning from a child dialog via done(). |
| Method | Description |
|---|---|
:update() |
Force re-render the dialog (e.g., after changing items). |
:close(button?) |
Close the dialog programmatically. 0 = left button, 1 = right button (default). |
d() |
Shortcut: equivalent to Engine.show(d). |
Fires once when a dialog is first displayed via start() or go(). The data parameter is passed from those functions.
:setOnStart(function(self, data)
self.currentData = data
print("Started with data:", data)
end)Fires every time the dialog is shown (including page navigation).
:setOnShow(function(self)
print("Dialog shown:", self.caption)
end)Fires every frame while the dialog is active. Useful for animations or real-time updates.
:setOnUpdate(function(self, dt)
self.animationTime = (self.animationTime or 0) + dt
end)Fires when the user responds to the dialog.
| Parameter | Type | Description |
|---|---|---|
self |
Dialog | The dialog instance. |
button |
number | 1 for left button (OK/Select), 0 for right button (Cancel/Close). |
index |
number | Selected list item index (1-based), or -1 for non-list styles. |
input |
string | User's text input (for input and password styles). |
item |
any | The actual list item value at index (resolves function items automatically). |
:setOnResponse(function(self, button, index, input, item)
if button == 1 then
if self.dialog_style == "input" then
print("User typed:", input)
else
print("Selected item #" .. index .. ":", item)
end
end
end)Fires when a child dialog calls done(result). Receives whatever value was passed to done().
:setOnProcessResult(function(self, result)
print("Child returned:", result)
end)Set via :setLaunchMode(mode):
| Mode | Value | Behavior |
|---|---|---|
STANDARD |
easy_dialog.LaunchMode.STANDARD |
Default. Pushes dialog onto stack. |
ROOT |
easy_dialog.LaunchMode.ROOT |
Clears entire stack, making this dialog the new root. |
SINGLE_TOP |
easy_dialog.LaunchMode.SINGLE_TOP |
If dialog is already in stack, returns to it instead of pushing a duplicate. |
-- Main menu (root)
local main = Dialog.new()
:setLaunchMode(easy_dialog.LaunchMode.ROOT)
:setCaption("Main Menu")
:setStyle("list")
:setItems({"Settings", "About"})
:setOnResponse(function(self, button, index)
if button == 1 then
if index == 1 then easy_dialog.go("settings")
else easy_dialog.go("about")
end
end
end)
-- Settings submenu
local settings = Dialog.new()
:setCaption("Settings")
:setStyle("list")
:setItems({"Audio", "Video"})
:setOnResponse(function(self, button, index)
if button == 0 then easy_dialog.back()
elseif index == 1 then easy_dialog.go("audio")
end
end)
easy_dialog.register("main", main)
easy_dialog.register("settings", settings)
easy_dialog.start("main")For large lists, enable pagination with :setItemsPerPage(n) where n is items per page.
local d = Dialog.new()
:setCaption("Player List")
:setStyle("list")
:setItems(playerNames) -- can be a function
:setItemsPerPage(10) -- show 10 items per page
:setButtons("Select", "Close")The library automatically adds navigation rows (<< Prev page / Next page >>) and updates the caption to show Page X/Y.
If items are provided as a function, the list updates automatically on each page change:
:setItems(function(self)
return getOnlinePlayers() -- called fresh each render
end)Customize default texts via easy_dialog.configure():
easy_dialog.configure({
PAGINATION_TEXT = "Page %d/%d",
EMPTY_LIST_TEXT = "{CCCCCC}Empty list.",
PAGINATION_PREV = "{BDBDBD}<< Previous",
PAGINATION_NEXT = "{BDBDBD}Next >>"
})The library includes several optimizations for high-frequency operations:
- Launch mode validation: Pre-computed lookup table for O(1) checks instead of iteration.
- Style ID caching: Dialog style integer is computed once on
setStyle()and reused. - Global variable caching: SA-MP functions are cached in local variables within the handler loop to avoid repeated global lookups.
local easy_dialog = require 'easy_dialog'
-- Helper to create styled headers
local function header(text)
return "{FFCC00}" .. text
end
-- About dialog
local about = Dialog.new()
:setCaption("About")
:setStyle("msgbox")
:setContent("easy-dialog v1.5\nA lightweight dialog library for SA:MP.")
:setButtons("Back", "")
:setOnResponse(function(self, button)
if button == 1 then easy_dialog.back() end
end)
-- Settings dialog
local settings = Dialog.new()
:setCaption("Settings")
:setStyle("list")
:setItems({"Audio", "Video", "Controls"})
:setButtons("Select", "Back")
:setOnResponse(function(self, button, index)
if button == 0 then easy_dialog.back()
elseif button == 1 and index == 1 then
easy_dialog.alert("Audio", "Audio settings coming soon.")
end
end)
:setOnProcessResult(function(self, result)
print("Settings result:", result)
end)
-- Main menu
local mainMenu = Dialog.new()
:setLaunchMode(easy_dialog.LaunchMode.ROOT)
:setCaption("Main Menu")
:setStyle("tablist")
:setHeaders({"Feature", "Description"})
:setItems({
{"Settings", "Configure options"},
{"About", "Library information"},
})
:setButtons("OK", "Exit")
:setOnResponse(function(self, button, index, _, item)
if button == 0 then
print("Exited menu")
return
end
local choice = item[1]
if choice == "Settings" then
easy_dialog.go("settings")
elseif choice == "About" then
easy_dialog.go("about")
end
end)
:setOnStart(function(self, data)
print("Main menu started with data:", data)
end)
-- Initialize and register
easy_dialog.init()
easy_dialog.register("main", mainMenu)
easy_dialog.register("settings", settings)
easy_dialog.register("about", about)
-- Start the application
easy_dialog.start("main", { version = "1.5" })MIT. Free to use and modify.