A lightweight and flexible textbox implementation for Starbound UI, built on top of canvas rendering.
Authors: https://github.com/Mofurka, https://github.com/KrashV Please credit if you use or modify this code
Important
This module is intended to be a submodule for your project. Since Starbound does not support relative paths, you MUST install this at the following path:
/interface/StarboundTextboxInterface
You can use the following git command to add this submodule to your repo:
git submodule add https://github.com/Mofurka/StarboundTextboxInterface StarboundTextboxInterface
- UTF-8 text support
- Custom canvas-based rendering
- Cursor navigation and text selection
- Double click selection
- Mouse and keyboard interaction
- Multiline editing
- Clipboard support (copy / paste / cut)
- Word-based navigation
- Scrolling
- Placeholder (hint) support
- Customizable appearance via public API
- Dynamic height functionality
- Redo/Undo operations
local textbox = Textbox:setup("widgetName", {
-- All the provided parameters are optional
rect = {0,0,200,200},
fontSize = 8,
lineSpacing = 0,
hint = "Textbox hint",
hintColor = {255, 255, 255},
selectionColor = {255, 255, 255},
caretColor = {255, 255, 255},
tabInsertText = " ",
onChanged = function(text)
sb.logInfo("Text changed: %s", text)
end,
onEnterKey = function()
sb.logInfo("Enter key pressed")
end,
onEscapeKey = function()
sb.logInfo("Escape key pressed")
end,
maxHeight = 120, -- Turns on the dynamic height functionality
onSizeChange = function(newSize)
sb.logInfo("Textbox changed size")
end,
textFont = "hobo"
})You should put the require("/interface/StarboundTextboxInterface/textarea/scripts/textbox.lua") at the beggining or at the end of your file.
Dpending on the method there will be two different implementations:
You need to add textbox hooks like this:
require("/interface/StarboundTextboxInterface/textarea/scripts/textbox.lua")
function init()
Textbox.init()
end
function update(dt)
Textbox.update(dt)
end
function uninit()
Textbox.uninit()
endYou don't need to call the hooks
function init()
end
function update(dt)
end
function uninit()
end
require("/interface/StarboundTextboxInterface/textarea/scripts/textbox.lua")Create and initialize a new textbox instance attached to a widget (or pane).
Get current textbox text.
Get selected text.
Sets the text in the selected area.
Set text for the current textbox. Arrays are joined with "\n".
Focus the textbox and activate keyboard input.
Remove focus from the textbox and clear current selection.
Return whether textbox is currently focused.
Clear all text and reset cursor, selection, and scroll.
Set callback fired each time textbox text changes.
Set callback fired on Enter key.
Set callback fired on Escape key.
Set callback fired on changing the textbox size. Dynamic height functionality only.
Enable or disable debug logging for the textbox module.
Destroy textbox widgets/canvases and remove instance from active list.
Get current cursor position (UTF-8 character index).
Set cursor position (clamped to valid text bounds).
Set text inserted when Tab key is pressed.
Get current wrapped line count.
Get current vertical scroll offset.
Set vertical scroll offset (clamped to valid range).
Set caret color as {r, g, b, a}.
Set selection highlight color as {r, g, b, a}.
Set text color as {r, g, b, a}.
Set hint color as {r, g, b, a}.
Set the new font type.
Get current font.
Set font size and recalculate layout/line height.
Get current font size.
Set extra spacing between lines in pixels. Applied only when wrapped/content line count is geater than one.
Get current extra spacing between lines in pixels.
Set placeholder (hint) text shown when empty and unfocused.
Get current placeholder (hint) text.
Set max height of textbox. Enables the dynamic height functionality.
Get current max height of the textbox.
Sets current textbox size.
Initialize textbox module in pane context.
Update all active textboxes each frame (input + rendering).
Cleanup and destroy all active textboxes.
| Key | Action |
|---|---|
| ← → | Move cursor |
| Ctrl + ← → | Move by word |
| ↑ ↓ | Move between lines |
| Home / End | Line start / end |
| Ctrl Home / End | Text start / end |
| Backspace / Delete | Remove text |
| Ctrl + Backspace | Remove word |
| Ctrl + A | Select all |
| Ctrl + C | Copy |
| Ctrl + X | Cut |
| Ctrl + V | Paste |
| Ctrl + Z | Undo last action |
| Ctrl + Y (Ctrl + Shift + Z) | Redo last action |
| Shift + Enter | New line |
A flexible dropdown combobox implementation for Starbound UI, allowing users to select from a list of options with optional filtering.
- Button-based dropdown activation
- Customizable list styling via schemas
- Optional search/filter functionality
- Scrollable list area
- Keyboard and mouse interaction
- Selection callbacks
- Dynamic value management
- Display name / internal value separation
First, add the require statement to your script:
require "/interface/StarboundTextboxInterface/combobox/scripts/combobox.lua"Then bind the combobox to an existing button widget:
local combobox = Combobox:bind("buttonWidgetName",
{
-- Value -> Display Name mapping
["value1"] = "Display Name 1",
["value2"] = "Display Name 2",
["value3"] = "Display Name 3"
},
function(value, name)
sb.logInfo("Selected: %s (value: %s)", name, value)
end,
{
-- All options are optional
background = "/path/to/background.png",
offset = {0, -100},
defaultValue = "value1",
closeOnSelect = true,
filter = true, -- Enable search filter
-- filter = { ... } -- Or provide custom filter options
scrollArea = {}, -- Custom scroll area styling
listSchema = {}, -- Custom list item styling
onOpen = function()
sb.logInfo("Combobox opened")
end,
onClose = function()
sb.logInfo("Combobox closed")
end
}
)Values can be provided as either a table with key-value pairs or a simple array:
-- Key-value format (value -> display name)
{
["internal_value"] = "Display Name",
["color_red"] = "Red",
["color_blue"] = "Blue"
}
-- Array format (display names become both key and value)
{"Option 1", "Option 2", "Option 3"}Bind a combobox to an existing button widget. Returns a combobox instance.
Parameters:
widgetName: Name of the button widget to attach tovalues: Table of values ({value = "display name", ...})onSelect: Callback functionfunction(value, displayName)called on selectionoptions: Configuration table (see below)
Configuration options for the combobox:
background(string) - Path to the background image. Defaults to filter-appropriate background.offset(Vec2F) - Position offset{x, y}from the button widget.defaultValue(string) - Value to select by default.closeOnSelect(boolean) - Whether to close the dropdown after selection. Default:falsefilter(table|boolean) - Enable filtering. Set totruefor defaults or provide custom options.scrollArea(table) - Customize scroll area styling (thumbsandbuttonstables).listSchema(table) - Customize list item appearance:background- Path to background imagebackgroundFilter- Alternative background pathlistSelected- Path to selected item imagelistUnselected- Path to unselected item imagetextOffset-{x, y}offset for text within itemsspacing-{x, y}spacing between items
onOpen(function) - Callback when dropdown opens.onClose(function) - Callback when dropdown closes.sortKeys(boolean) - Whether to sort the table keys.
Configuration for the optional search filter:
position(Vec2F) - Position of the filter textbox{x, y}. Default:{0, 0}textOffset(Vec2F) - Text offset within the textbox{x, y}. Default:{5, 2}hint(string) - Placeholder text. Default:"..."color(string) - Hint text color. Default:"gray"height(number) - Height of the filter textbox. Default:20
Populate the list with values. If searchText is provided, filters the list by display name (case-insensitive).
Replace all values and optionally set a new default.
Toggle the dropdown visibility (open if closed, close if open).
Open the dropdown list.
Close the dropdown list.
Programmatically select an item by value.
require "/interface/combobox/combobox.class.lua"
function init()
local options = {
["opt_action"] = "Perform Action",
["opt_settings"] = "Open Settings",
["opt_about"] = "About"
}
local combobox = Combobox:bind("optionsButton", options, function(value, name)
if value == "opt_action" then
sb.logInfo("Action performed!")
elseif value == "opt_settings" then
sb.logInfo("Opening settings...")
end
end, {
defaultValue = "opt_action",
closeOnSelect = true,
filter = true,
offset = {0, -80},
onOpen = function()
sb.logInfo("Menu opened")
end
})
-- Later, update the options
combobox:updateValues({
["opt_export"] = "Export Data",
["opt_import"] = "Import Data"
})
-- Select an option programmatically
combobox:setSelected("opt_export")
endA promise-based animation system for Starbound UI widgets, enabling smooth transitions and sequential animations with callbacks.
- Smooth position animations (move)
- Progress bar animations
- Rotation with optional center point
- Scaling with optional center point
- Widget size transitions
- Pane size transitions
- Typewriter text effect
- Callback system for animation completion/errors
- Sequential and chained animations
require "/interface/StarboundTextboxInterface/animatedWidgets.lua"
-- In your update function, update the animation system each frame:
function update(dt)
animatedWidgets:update(dt)
endrequire "/interface/StarboundTextboxInterface/animatedWidgets.lua"
function init()
-- Create an animated widget wrapper around an existing widget
local widget = AnimatedWidget:bind("myWidget")
-- Create an animation and register callbacks
widget:move({100, 100}, 2.0) -- Move to (100, 100) over 2 seconds
:onSuccess(function()
sb.logInfo("Animation complete!")
end)
end
function update(dt)
animatedWidgets:update(dt)
endCreate an animated widget wrapper for the given widget name.
Animate widget position to a destination over the specified duration.
Parameters:
destination: Target position{x, y}duration: Animation duration in seconds
Returns: Promise object with callback methods
Animate a progress bar from one value to another.
Parameters:
oldValue: Starting progress valuenewValue: Ending progress valueduration: Animation duration in seconds
Returns: Promise object with callback methods
RotateImagePromise widget:rotate(number startAngle, number endAngle, number duration, Vec2F|nil rotationCenter)
Rotate an image widget from one angle to another.
Parameters:
startAngle: Starting rotation angleendAngle: Ending rotation angleduration: Animation duration in secondsrotationCenter: Optional rotation pivot point{x, y}. If provided, maintains rotation center relative to widget position.
Returns: Promise object with callback methods
ScaleImagePromise widget:scale(number startScale, number endScale, number duration, Vec2F|nil scalingCenter)
Scale an image widget from one scale to another.
Parameters:
startScale: Starting scale factorendScale: Ending scale factorduration: Animation duration in secondsscalingCenter: Optional scaling pivot point{x, y}. If provided, maintains scale center relative to widget position.
Returns: Promise object with callback methods
Smoothly resize a widget from its current size to a new size.
Parameters:
newSize: Target size{width, height}duration: Animation duration in seconds
Returns: Promise object with callback methods
Smoothly resize the entire pane from its current size to a new size.
Parameters:
newSize: Target pane size{width, height}duration: Animation duration in seconds
Returns: Promise object with callback methods
Display text using a typewriter effect (letter by letter) over the specified duration.
Parameters:
text: Target text to displayduration: Animation duration in seconds (time to display all characters)
Returns: Promise object with callback methods
All animation methods return a promise object with the following methods:
Register a callback to fire when the animation completes successfully.
widget:move({100, 100}, 2.0):onSuccess(function()
sb.logInfo("Move animation finished!")
end)Register a callback to fire if the animation encounters an error.
widget:move({100, 100}, 2.0):onError(function(error)
sb.logError("Animation error: %s", error)
end)require "/interface/StarboundTextboxInterface/animatedWidgets.lua"
function init()
local widget = AnimatedWidget:bind("animatedBox")
-- Chain multiple animations
widget:move({50, 50}, 1.0):onSuccess(function()
widget:rotate(0, 360, 2.0):onSuccess(function()
widget:scale(1, 1.5, 1.0)
end)
end)
end
function update(dt)
animatedWidgets:update(dt)
endlocal widget = AnimatedWidget:bind("scalingIcon")
-- Scale from 1x to 2x around the center point (64, 64)
widget:scale(1.0, 2.0, 1.5, {64, 64})local widget = AnimatedWidget:bind("textLabel")
-- Display text character by character over 3 seconds
widget:setText("Hello, Starbound!", 3.0):onSuccess(function()
sb.logInfo("Text fully displayed")
end)-- Animate the entire pane from current size to a new size
local widget = AnimatedWidget:bind("dummy")
widget:setPaneSize({800, 600}, 2.0):onSuccess(function()
sb.logInfo("Pane resized!")
end)Free to use and modify. Attribution is appreciated.
- @KrashV (Degranon)