UI Framework (minui)
github.com/mechanical-lich/mlge/ui/minui
A comprehensive retained-mode UI library with theming, style inheritance, dual rendering paths (vector and sprite-based), and a rich widget set.
Setup
import "github.com/mechanical-lich/mlge/ui/minui"
gui := minui.NewGUI()
Core Concepts
Elements and Containers
All UI widgets implement the Element interface. Containers (like Panel) hold child elements and manage layout.
Layout
Containers support three layout directions:
| Constant | Description |
|---|---|
LayoutVertical | Children arranged top to bottom |
LayoutHorizontal | Children arranged left to right |
LayoutNone | No automatic layout (manual positioning) |
Styling
Every element can have a Style applied with CSS-like properties:
style := minui.Style{
Width: 200,
Height: 40,
Padding: minui.EdgeInsets{Top: 8, Right: 12, Bottom: 8, Left: 12},
BackgroundColor: color.RGBA{40, 40, 40, 255},
BorderColor: color.RGBA{100, 100, 100, 255},
BorderWidth: 1,
FontColor: color.White,
FontSize: 14,
HoverStyle: &minui.Style{
BackgroundColor: color.RGBA{60, 60, 60, 255},
},
}
Theming
Apply a consistent look across all widgets with a Theme:
theme := &minui.Theme{
Name: "dark",
Colors: minui.Colors{
Primary: color.RGBA{70, 130, 180, 255},
Secondary: color.RGBA{60, 60, 60, 255},
Background: color.RGBA{30, 30, 30, 255},
Surface: color.RGBA{45, 45, 45, 255},
Text: color.White,
Border: color.RGBA{80, 80, 80, 255},
Focus: color.RGBA{70, 130, 180, 255},
},
}
gui.SetTheme(theme)
When a Theme.SpriteSheet is set, widgets render using 9-slice sprites instead of vector drawing.
Widgets
Panel
Container that arranges child elements:
panel := minui.NewPanel("main-panel")
panel.SetLayoutDirection(minui.LayoutVertical)
panel.AddChild(button)
panel.AddChild(label)
gui.AddElement(panel)
Button
button := minui.NewButton("submit-btn", "Submit")
// Listen for clicks via the event system
// Event type: "ui.button.click"
IconButton
Button with an icon and text:
icon := minui.NewIcon(spriteSheet, 0, 0, 16, 16)
iconBtn := minui.NewIconButton("save-btn", icon, "Save")
Label
Static text display with multiline support:
label := minui.NewLabel("status-label", "Health: 100")
label.SetText("Health: 80")
// Create with a specific color, or change color at runtime
label := minui.NewLabelWithColor("status-label", "Health: 100", color.RGBA{0, 255, 0, 255})
label.SetColor(color.RGBA{255, 0, 0, 255})
TextInput
Single-line text input with cursor:
input := minui.NewTextInput("name-input", "Enter name...")
// Event type: "ui.textinput.change"
ListBox
Scrollable selectable list:
listBox := minui.NewListBox("inventory-list", []string{"Sword", "Shield", "Potion"})
// Event type: "ui.listbox.select"
SelectBox
Dropdown selection:
selectBox := minui.NewSelectBox("difficulty-select", []string{"Easy", "Normal", "Hard"})
RadioButton / RadioGroup
group := minui.NewRadioGroup("mode-group")
radio1 := minui.NewRadioButton("mode-easy", "Easy")
radio2 := minui.NewRadioButton("mode-hard", "Hard")
group.AddChild(radio1)
group.AddChild(radio2)
Toggle
On/off switch:
toggle := minui.NewToggle("music-toggle", "Music")
ProgressBar
bar := minui.NewProgressBar("health-bar")
bar.SetValue(0.75) // 75%
ScrollingTextArea
Multi-line scrollable text display. Text is automatically word-wrapped. Scrolls to the bottom when new text is added, and supports mouse-wheel scrolling and a draggable scrollbar thumb.
textArea := minui.NewScrollingTextArea("log-area", 400, 200)
textArea.AddText("Player entered the dungeon")
textArea.AddText("A wild goblin appears!")
// Per-line color override (nil = widget default)
textArea.AddColoredText("You are badly wounded!", color.RGBA{255, 60, 60, 255})
textArea.Clear()
RichText
A list of independently styled text spans, each rendered as one line. Height is calculated automatically from content. Useful for HUD panels where different lines need different colors, sizes, or strikethrough.
rt := minui.NewRichText("hover-panel", 200)
rt.LineHeight = 14
rt.AddSpan(minui.TextSpan{
Text: "Goblin",
Color: color.RGBA{255, 200, 100, 255},
Size: 13,
})
rt.AddSpan(minui.TextSpan{
Text: "HP: 4/10",
Color: color.RGBA{200, 80, 80, 255},
Size: 11,
Indent: 8,
})
rt.AddSpan(minui.TextSpan{
Text: "left arm",
Color: color.RGBA{120, 80, 160, 255},
Size: 11,
Indent: 8,
Strikethrough: true,
})
rt.Clear()
rt.SetPosition(x, y)
rt.Draw(screen)
RichText does not scroll — wrap lines yourself before adding spans (see text.Wrap). It has no background or border; position it manually and call Draw directly (no parent required).
ImageWidget
Displays an *ebiten.Image scaled to the widget’s bounds:
widget := minui.NewImageWidget("minimap", 150, 150)
widget.SetPosition(x, y)
// Update the image each frame
widget.Image = generateMinimapImage()
widget.Draw(screen)
ImageWidget has no parent requirement — call Draw directly. If Image is nil the widget draws nothing.
TabPanel
Tabbed container:
tabPanel := minui.NewTabPanel("settings-tabs", 600, 400)
tabPanel.AddTab("general", "General", generalPanel)
tabPanel.AddTab("audio", "Audio", audioPanel)
// Programmatically switch to a tab by ID
tabPanel.SetActiveTab("audio")
// Event type: "ui.tabpanel.change"
Tab positions: TabsTop, TabsBottom, TabsLeft, TabsRight
TreeView / TreeNode
Hierarchical tree with expand/collapse:
root := minui.NewTreeNode("root", "Game Objects")
child1 := minui.NewTreeNode("player", "Player")
child2 := minui.NewTreeNode("enemies", "Enemies")
root.AddChild(child1)
root.AddChild(child2)
// Event type: "ui.treeview.select"
Modal
Draggable dialog window:
modal := minui.NewModal("settings-modal", "Settings", 400, 300)
modal.AddChild(settingsPanel)
gui.ShowModal(modal)
// Event type: "ui.modal.close"
PopupMenu
Context/popup menu with nested submenus:
menu := minui.NewPopupMenu("context-menu")
menu.AddItem(minui.NewMenuItem("edit-item", "Edit"))
menu.AddItem(minui.NewMenuItem("delete-item", "Delete"))
// Event type: "ui.popupmenu.select"
Drawer
Sliding panel from screen edges:
drawer := minui.NewDrawer("side-drawer", minui.DrawerLeft)
drawer.AddChild(menuPanel)
Tooltip
tooltipManager := minui.NewTooltipManager()
tooltip := minui.NewTooltip("btn-tooltip")
tooltip.SetText("Click to submit")
tooltipManager.Register(button, tooltip)
ResourceBar
Horizontal bar showing resource icons and values:
bar := minui.NewResourceBar("resources")
FileModal
File browser dialog:
fileModal := minui.NewFileModal("file-browser", "Open File", 500, 400, minui.FileModalLoad)
UI Events
All UI events are dispatched through the queued event system. Common event types:
| Event Type | Triggered By |
|---|---|
ui.button.click | Button press |
ui.textinput.change | Text input value change |
ui.listbox.select | List item selection |
ui.modal.close | Modal closed |
ui.tabpanel.change | Tab switch |
ui.treeview.select | Tree node selection |
ui.popupmenu.select | Menu item selected |
Rendering
The UI supports two rendering paths:
- Vector rendering (default) — Draws widgets using filled rectangles, borders, and text
- Sprite rendering — When
Theme.SpriteSheetis set, draws widgets using 9-slice sprites from a sprite sheet for a pixel-art or custom visual style