Windows & events¶
A window face is not a console loop with pixels added. It has a lifecycle. The host calls your code in bounded turns; your model lives between those turns. One source image may still offer a separate console face for scripting—see Console, Scripts & GUI.
The minimum window¶
WINDOW SUB OPEN()
SERVICE SET_TITLE("Hello window")
SERVICE INVALIDATE()
END SUB
WINDOW SUB DRAW()
INTENT GFX.BEGIN_FRAME()
INTENT GFX.FILL_RECT(0, 0, 320, 192, 2081)
INTENT GFX.DRAW_TEXT(12, 12, "HELLO", 3, -1, 2081)
INTENT GFX.PRESENT()
END SUB
WINDOW SUB CLOSE()
END SUB
Every window program defines exactly one OPEN, DRAW, and CLOSE handler. They are module-level faces, not methods hidden inside an arbitrary class.
Lifecycle faces¶
| Face | Parameters | Use it for |
|---|---|---|
WINDOW SUB OPEN() |
none | create model state, set the title, request the first draw |
WINDOW SUB DRAW() |
none | emit one complete semantic frame |
WINDOW SUB RESIZE(W, H) |
two INTEGERs |
recompute layout for the new logical viewport |
WINDOW SUB CLOSE() |
none | release application-owned state |
WINDOW SUB ACTIVE(State) |
one INTEGER |
active/inactive transitions |
WINDOW SUB VISIBLE(State) |
one INTEGER |
visible/hidden transitions |
WINDOW SUB SUSPENDED(State) |
one INTEGER |
suspend/resume work |
WINDOW SUB MINIMIZED(State) |
one INTEGER |
minimized state changes |
WINDOW SUB MAXIMIZED(State) |
one INTEGER |
maximized state changes |
Host parameters are BYVAL INTEGER. Each face may appear once.
State belongs to the application¶
Use module storage for a small program or put the model and controller into a class:
CLASS CounterCard
PUBLIC:
Count AS INTEGER
Width AS INTEGER
Height AS INTEGER
DECLARE SUB Paint ()
END CLASS
DIM SHARED App AS CounterCard
WINDOW SUB OPEN()
App = NEW CounterCard()
App.Width = 320
App.Height = 192
SERVICE SET_TITLE("Counter")
SERVICE INVALIDATE()
END SUB
WINDOW SUB DRAW()
App.Paint
END SUB
WINDOW SUB CLOSE()
App = NOTHING
END SUB
The host owns the event loop. App owns the session. This is why the same source can live inside a D/OS surface, a browser worker, or a native desktop window.
Keyboard events¶
ON KEY SUB (K AS INTEGER)
IF K = 13 THEN App.Count = App.Count + 1
IF K = 27 THEN App.Count = 0
SERVICE INVALIDATE()
END SUB
The input is normalized to an integer key value. Common values:
| Key | Value |
|---|---|
| Backspace | 8 |
| Tab | 9 |
| Enter | 13 |
| Escape | 27 |
| Delete | 127 |
| Insert | 256 |
| printable ASCII | its ASCII value |
| Arrow Up / Down / Left / Right | 257 / 258 / 259 / 260 |
| Home / End | 261 / 262 |
| Page Up / Page Down | 263 / 264 |
function key Fn |
0x0120 + n |
The program decides what a key means. A text field might insert printable ASCII, move its caret on Left/Right, and publish a local edit event. A button might activate on Enter.
Unified UI events¶
Applications that need committed Unicode text, composition/IME, focus, semantic actions, asynchronous completion, or capability-change events use the unified face:
IMPORT WIDGETS
ON EVENT SUB (E AS UIEVENT)
' Inspect the callback-scoped event and update the application model.
END SUB
ON EVENT replaces—not supplements—ON KEY, ON POINTER, and ON MENU; declaring both forms is refused so one input turn cannot arrive twice. OPEN, DRAW, RESIZE, and CLOSE remain the same.
The event kinds are Key, TextCommit, Composition, Pointer, Focus, Refresh, Show, Hide, SemanticAction, AsyncCompletion, CapabilityChanged, and Overflow. A Key event describes physical/logical key state and never inserts text. Only TextCommit inserts committed UTF-8; Composition carries the live IME range and clauses. This split prevents a composed character from becoming a key plus duplicate text.
UIEVENT and its borrowed payloads are readable only during the callback. Call Snapshot() before returning when the application must retain event data; keeping the borrowed view itself is refused.
Pointer events¶
ON POINTER SUB (X AS INTEGER, Y AS INTEGER, Buttons AS INTEGER)
App.HandlePointer X, Y, Buttons
SERVICE INVALIDATE()
END SUB
X and Y use the surface's logical coordinate system. Buttons is the current normalized button state.
Robust controls reduce a gesture, not merely a coordinate:
- on transition from no buttons to pressed, remember which control owns the press;
- update visual down/drag state while moving;
- on release, activate only when the release satisfies that control's rule; and
- clear pressed state on lifecycle cancellation.
This makes a button behave like a button on a mouse, touch bridge, or small-machine pointing device.
Drop events¶
Drag and drop uses the same face discipline without turning every pointer move into application code:
WINDOW ACCEPTS "DOBJ", "STYL", "TEXT"
ON DROP SUB (D AS DROPITEM)
IF D.KIND$ = "TEXT" THEN App.InsertText D.TEXT$
END SUB
The compositor negotiates declared representations and shows accept/refuse feedback. ON DROP runs only after a real committed drop. See Drag, Drop & DOBJ for in-app moves, cross-application transactions, native OS bridges, and DOBJ.
Resize means layout, not magnification¶
WINDOW SUB RESIZE(W AS INTEGER, H AS INTEGER)
App.Width = W
App.Height = H
App.Layout
SERVICE INVALIDATE()
END SUB
A capable desktop runner gives the program a larger logical canvas when the window grows. The program reflows. A compact head supplies its own viewport. The runner can scale presentation for device pixels, but it does not lie to application layout about the logical size.
Design a compact fallback for very small valid extents:
Invalidation is a request¶
SERVICE INVALIDATE() asks the host to schedule a draw turn. It does not recursively call DRAW, and it does not promise a full-window repaint.
Prefer that shape over unconditional invalidation after every ignored key. The widget library's damage ledger makes the decision explicit.
Draw one complete semantic frame¶
SUB CounterCard.Paint ()
DIM Text AS STRING
Text = "COUNT: " + STR$(THIS.Count)
INTENT GFX.BEGIN_FRAME()
INTENT GFX.FILL_RECT(0, 0, THIS.Width, THIS.Height, 2081)
INTENT GFX.SET_CLIP(8, 8, THIS.Width - 16, THIS.Height - 16)
INTENT GFX.DRAW_TEXT(12, 12, Text, 3, -1, 2081)
INTENT GFX.RESET_CLIP()
INTENT GFX.PRESENT()
END SUB
BEGIN_FRAME and PRESENT bound the frame. Clips keep component drawing honest. The host may use a high-resolution real font, a resident device face, a GPU, or a composed head; the source's geometry and ordering stay canonical.
Separate model, interaction, and paint¶
A maintainable window program has three rhythms:
- model: the state that would still matter with the screen turned off;
- interaction: event reducers that change the model and mark damage;
- paint: a pure-ish projection of current model state into GFX Intents.
Avoid changing application state merely because DRAW was called again. Hosts may expose, coalesce, or repeat draws.
Faces are turns, not threads¶
Do not block inside an event handler waiting for another event. Start an operation, retain its state, return, and continue on a later face turn or completion. DBC execution itself is sliced to keep a constrained host responsive.
Where widgets fit¶
Hand-painting one button is easy. Making focus, caret placement, press/release, disabled state, menus, clipping, theme, and damage agree across systems is not. D/Works Widgets provides those reusable objects while leaving lifecycle and application meaning in your code.
Next: Widgets →