A control is one of the client’s interactive widgets, the class its own windows are built from. Your addon builds, configures and owns it. The client draws it, handles the press and dresses it from the stylesheet.
local harvest_window = hafen.ui():window():title("Harvest"):size(160, 60):position(80, 120)
local go_button = hafen.ui():button():parent(harvest_window):position(20, 20):text("Go")
go_button:on("Pressed", function() hafen.log():write("pressed") end)
A builder hands back the same Widget every lookup gives you. Every read and write on that page answers on a control: :type(), :role(), :position(x, y), :size(w, h), :parent(w), :visible(b), :destroy(), :style(), :rule(), :info(). :type() reports the client’s class, so a button you built and one you found both read "Button". One selector, one role and one stylesheet key name both.
| Method | Returns | Permission | The control | Page |
|---|---|---|---|---|
hafen.ui():button() |
Widget |
Unprotected | A push button showing a caption or a picture. | interactive |
hafen.ui():entry() |
Widget |
Unprotected | A single-line text field. | interactive |
hafen.ui():label() |
Widget |
Unprotected | A line of text. | display |
hafen.ui():image() |
Widget |
Unprotected | A static picture. | display |
hafen.ui():separator() |
Widget |
Unprotected | A horizontal rule. | display |
hafen.ui():progress() |
Widget |
Unprotected | A fill-fraction bar. | display |
hafen.ui():check() |
Widget |
Unprotected | A checkbox showing a caption or a picture. | interactive |
hafen.ui():radio() |
Widget |
Unprotected | Buttons of which exactly one is checked. | interactive |
hafen.ui():slider() |
Widget |
Unprotected | A draggable position within a range. | interactive |
hafen.ui():scroll() |
Widget |
Unprotected | A scrolling container for other controls. | interactive |
hafen.ui():scrollbar() |
Widget |
Unprotected | A bare scroll thumb, for driving something yourself. | interactive |
hafen.ui():keybinding() |
Widget |
Unprotected | The client’s key button, joined to one of your hotkeys, or under client.settings to any binding. |
interactive |
hafen.ui():listbox(), :dropdown(), :menu(), :grid(), :table() |
Widget |
Unprotected | The row-source controls. | lists |
hafen.ui():column(), :row() |
Widget |
Unprotected | Not controls: surfaces of yours that lay the controls in them out. | column |
No builder takes an argument. A control is built bare with the client’s defaults and configured by chained setters. It draws nothing until the tick after the statement that built it, so it is never seen half-built. :parent(w) chooses where it hangs while it is being built. On screen it moves by :position(x, y).
| Method | Returns | Permission | Description |
|---|---|---|---|
:text(caption) / :text() |
self / string \| nil |
Unprotected | The caption the control displays. The read answers on any text-bearing widget. On a client control the write is a restoring level. |
:image(up, down [, hover]) / :image() |
self / table \| nil |
Unprotected | The pictures the control shows instead of a caption. |
:value(v) / :value() |
self / any \| nil |
Unprotected on a control you built. widget.value on a client control |
What the control holds. |
:source(h) / :source() |
self / string \| userdata \| nil |
Unprotected | The picture a picture control shows. |
:rows(t) / :rows() |
self / table \| nil |
Unprotected | The row source a radio or a row-source control takes. |
:range(min, max) / :range() |
self / {min=, max=} \| nil |
Unprotected | The bounds of a slider or scrollbar. |
:bind(opt), :bind(binding) / :bind() |
self / Option \| Binding \| nil |
Unprotected | The option of your addon’s a checkbox, slider, dropdown, radio or entry shows and writes, or the hotkey a key button assigns: yours, or under client.settings another addon’s or the client’s. :bind(nil) unbinds. |
Every setter returns the Widget and has a bare read. None is protected on a control you built: it is your own client-side UI, restored with your addon.
| Rule | Detail |
|---|---|
:bind(opt) |
The control takes the option’s value at once and is configured from it. A slider’s :range comes from its bounds, a dropdown’s or radio’s :rows from its choices. The user moving the control writes opt:value(v). A write to the option moves the control without firing its Changed. A control of another kind than the option’s is refused naming the one it takes. So is a control holding no value, and a client control. |
:bind(binding) |
A key button’s alone: a Binding of a hotkey your addon declared and has not ended, its key shown at once. Under client.settings, another addon’s declared hotkey or one of the client’s bindings too. An Option given to a key button, and a Binding given to any other control, are refused naming the control that takes it. |
:value() |
One verb for whatever a control holds: a progress bar’s fraction, a checkbox’s boolean, an entry’s string, a key button’s key. nil where it holds nothing. On a client control the write is driving and protected. A key button refuses the write, yours or the client’s: its key is its binding’s. |
| Value checks | The type first: a number is a number and "50" is a string. :range(min, max)’s bounds are read the same way. A progress bar refuses a value outside 0..1, naming the rule. A slider or scrollbar clamps a value outside its :range to the nearer bound, that range being yours to narrow at any time. |
| Method | Returns | Permission | Description |
|---|---|---|---|
:size(w) |
self |
Unprotected | Sets the width. The height is the control’s own art. Answered by the button, key button, text entry, checkbox, dropdown, slider and separator. |
:size(w, h) |
self |
Unprotected | Sets both, in design pixels. A box under the art’s own raises, naming the height it needs: widget:size(w, h) — a Button is 24 design px tall, which is its own ART's box: 4 clips it. |
local go_button = hafen.ui():button():size(80):text("Go")
go_button:size() -- {w = 80, h = 24}, at any interface scale
| Rule | Detail |
|---|---|
| A control showing a picture | A button or checkbox given :image(...) is that picture in both directions and takes no :size. |
| Stretchable controls | A listbox, grid, table or picture has no art to ask: it takes :size(w, h) and refuses :size(w), as a surface does. |
| Clipping | A caption wider than its box is drawn clipped, not wrapped. |
| Containers | A column places controls one under the other at their own size. :pack() sizes a window around them and keeps it sized. |
A control answers the universal :on(key, fn) keys plus one capability key, the thing that control does.
| Builder | Key | Handler receives |
|---|---|---|
:button() |
Pressed |
— |
:check(), :radio(), :slider(), :scrollbar(), :scroll(), :listbox(), :dropdown() |
Changed |
The value — interactive, lists. |
:keybinding() |
Changed |
The key it now shows, nil for unbound — interactive. |
:entry() |
Changed, Submitted |
The text. |
:menu() |
Selected |
The picked row. |
:grid() |
Cell |
event — grid. |
:label(), :image(), :separator(), :progress(), :table() |
none | — |
| Rule | Detail |
|---|---|
Pressed is an activation |
What the button did, from the mouse or the keyboard. No coordinates. The handler may destroy the window the button sits in. |
Changed fires on a real interaction only |
A :value(v) write of yours never re-enters it. Two handlers on one key both fire, in registration order. |
| The key belongs to the control, not to who built it | The same key answers on a client control, with an event that can stop or run the client’s action — edit. On yours the handler is the action: nothing to cancel, only the value handed over. A borrowed slider’s or scrollbar’s Changed is cancelable on neither: the value is written before it is reported. |
| Inner client widgets | A dropdown’s arrow is one of the client’s checkboxes with a Changed of its own. Your Changed on the dropdown is the picked row and never the arrow’s or the popup list’s. |
Draw, Update, Drop, Close and :font(h) belong to a surface you paint. A control refuses them, naming :on("Pressed", fn) and the stylesheet. The universal input keys (MouseDown, MouseUp, MouseMove, Wheel, Removed) it answers like any widget.
A control your addon built is owned: the setters answer, :destroy() ends it, :reload and disable remove it. A client control is borrowed. The reads answer. Every setter refuses naming what to do instead (:info().owned tells which you hold). Its capability key answers on both: subscribing to a borrowed control is how you take over what it does. A control found again with hafen.ui():hit(x, y) or a selector is the same object the builder returned.