Every coordinate and size a widget takes or gives is a design pixel. That is one pixel before the client applies the user’s interface scale, the unit the client’s own layout and art are written in.
local panel = hafen.ui():widget():size(100, 40):position(30, 20)
panel:size() -- {w = 100, h = 40}, on every client
hafen.ui():scale() -- 1.5 on a client the user scaled up; nothing multiplies by it
| Method | Returns | Permission | Description |
|---|---|---|---|
hafen.ui():scale() |
number |
Unprotected | The interface scale in force, 1.0 or more. A diagnostic to print, not a unit: the numbers are already right. |
hafen.ui():scale(v) |
— | — | Raises, naming hafen.client():options():interface():scale(v), the user’s preference, which takes a client restart. Until then the two differ. |
| Where | Rule |
|---|---|
| Widget geometry | :position(x, y), :size(w, h), :rootPos(), :info().pos, :info().size — a size in w/h, a place in x/y. What you write is what you read back, at any scale: the conversion happens once, at the edge, and is exact in that direction. A place on the HUD or in the layer then follows a resize of the game window, and reads back where it has moved to, unless the widget covers the screen (native). |
| The screen’s questions | The box a widget occupies, the point hafen.ui():hit(x, y) tests, the pointer, and the :x()/:y() an input event carries are one space. None is reached through a Session, since the client draws one screen however many characters it holds. |
| The world | s:world():worldToScreen(p) answers a root pair in this unit, and s:world():screenToWorld(pt, fn) takes that table back. A projected point is hit-tested, drawn and fed to the ground with nothing between. |
| Drawing | Every coordinate, width and radius a g: verb takes. The :w()/:h() a Draw reports. The w, h an overlay painter gets. The screen_x, screen_y at a gob overlay. |
| The stylesheet | A rule’s position and size, an anchor’s offset, a padding, a border’s slice insets, a listbox’s :rowHeight(n) and a grid’s :cellSize(w, h). A theme is a set of numbers meaning one thing on every client. |
| A control’s height | A fact of the client’s pictures, not a number you write. :size(w) takes the width alone. A column stacks controls without adding heights up. :pack() sizes a window around them. |
| Your images | A 32×32 PNG is 32×32 to img:size() and covers 32 design pixels drawn, at every scale. |
| The client’s art | Authored several times larger and resampled once per scale. A {res = …} in a bg or border is measured and sliced in the same unit. It is crisper than a file of that apparent size on a scaled-up client (naming a picture). |
A font’s size |
A design pixel too: a 14 px caption fits a 20 px row on every client — hafen.font. |
local mouse = hafen.ui():mouse()
assert(hafen.ui():hit(mouse:x(), mouse:y()) == mouse:over()) -- one pair, asked two ways
local world = hafen.session():current():world()
world:screenToWorld({x = mouse:x(), y = mouse:y()}, function(position) -- the ground under the cursor, a frame later
if position then hafen.log():write("standing spot: " .. tostring(position:tileCoord().x)) end
end)
hafen.ui():sheet():rule("window[title=Equipment]"):position(40, 200):install()
local equipment_position = hafen.session():current():ui():match("window[title=Equipment]"):position() -- {x = 40, y = 200}, at any scale
The inverse conversion is not exact. A widget the client placed does not generally sit on a whole design pixel. Reading a client widget’s position and writing the same pair back may shift it by less than one design pixel. Hand a place back with
:position(nil), not by rewriting the numbers.
:position(), :size() and :rootPos().Draw and the g: verbs.hafen.font — a type size, in the same space.