Windows & events¶
This page teaches you how to write a d/BASIC program that opens a window. You will see the handlers a window program must define, learn how keys, the pointer and dropped items arrive, handle resizing, and draw one complete frame. By the end you will be able to build a small windowed program with its own state.
A window program is not a console loop with pixels added. It has a lifecycle. The host calls your code one short turn at a time, and your program's state lives on between those turns. One compiled program file can still offer a separate console program 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, one DRAW, and one CLOSE handler. They live at the top level of the module, not as methods hidden inside some class. d/OS calls these handlers the program's faces. The INTENT GFX lines are intents: named requests such as "fill this rectangle" that the machine carries out however it can.
Lifecycle handlers¶
| Handler | Parameters | Use it for |
|---|---|---|
WINDOW SUB OPEN() |
none | create your state, set the title, ask for the first draw |
WINDOW SUB DRAW() |
none | draw one complete frame |
WINDOW SUB RESIZE(W, H) |
two INTEGERs |
lay the window out again for its new size |
WINDOW SUB CLOSE() |
none | let go of anything your program created |
WINDOW SUB ACTIVE(State) |
one INTEGER |
the window became active or inactive |
WINDOW SUB VISIBLE(State) |
one INTEGER |
the window was shown or hidden |
WINDOW SUB SUSPENDED(State) |
one INTEGER |
the program was suspended or resumed |
WINDOW SUB MINIMIZED(State) |
one INTEGER |
the window was minimized or restored |
WINDOW SUB MAXIMIZED(State) |
one INTEGER |
the window was maximized or restored |
The host passes its parameters as BYVAL INTEGER. Each handler may appear once.
State belongs to the application¶
Use module-level variables for a small program, or put the model and the 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 run on a screen under d/OS (the operating system that brings modern software to 8-bit home computers such as the Atari 800, the Commodore 64 and the Apple II), in a browser worker, or in 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
Every host turns a key press into the same integer value, so you write one handler. 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 |
Your program decides what a key means. A text field might insert printable characters, move its caret on Left and Right, and publish a local edit event. A button might activate on Enter.
Unified UI events¶
Applications that need finished Unicode text, text typed in stages through an input method (IME), focus, semantic actions, asynchronous completion, or notice of a capability changing use the unified handler:
IMPORT WIDGETS
ON EVENT SUB (E AS UIEVENT)
' Inspect the callback-scoped event and update the application model.
END SUB
ON EVENT replaces ON KEY, ON POINTER, and ON MENU; it does not sit alongside them. If a program declares both forms, the compiler won't run it and says why, so one input can never arrive twice. OPEN, DRAW, RESIZE, and CLOSE stay as they are.
The event kinds are Key, TextCommit, Composition, Pointer, Focus, Refresh, Show, Hide, SemanticAction, AsyncCompletion, CapabilityChanged, and Overflow. A Key event describes the state of a physical or logical key and never inserts text. Only TextCommit inserts finished UTF-8 text; Composition carries the live input-method range and clauses. That split keeps a composed character from arriving as a key press plus a duplicate piece of text.
UIEVENT and the data it points to can be read only while your handler is running. If your program needs to keep event data, call Snapshot() before returning. If you try to keep the borrowed view itself, d/BASIC stops and tells you exactly what is wrong.
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 are in the window's logical coordinates. Buttons is the current button state, reported the same way on every host.
A robust control reads a whole gesture, not just a coordinate:
- when the buttons go from none pressed to pressed, remember which control owns the press;
- update the pressed or dragging look while the pointer moves;
- on release, activate only if the release satisfies that control's rule; and
- clear the pressed state if the window's lifecycle cancels the gesture part-way.
This makes a button behave like a button whether the pointer is a mouse, a touch screen bridge, or a small machine's pointing device.
Drop events¶
Drag and drop uses the same handler 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 system negotiates the formats you declared and shows accept or refuse feedback while the drag is in progress. ON DROP runs only after a real, committed drop. See Drag, Drop & DOBJ for moves inside one application, transactions between applications, native OS bridges, and DOBJ, the typed object a program hands to another program when you drag, drop, copy or paste.
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 host gives the program a larger logical canvas when the window grows, and the program reflows its layout. A compact screen supplies its own viewport. The host may scale what it presents to match the device's pixels, but it never lies to your layout about the logical size.
Design a compact fallback for very small but valid sizes:
Invalidation is a request¶
SERVICE INVALIDATE() asks the host to schedule a draw turn. It does not call DRAW then and there, and it does not promise a full-window repaint.
Prefer that shape over asking for a repaint after every key you ignored. The widget library's damage tracking, its record of what needs repainting, makes the decision explicit.
Draw one complete 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 mark the start and end of the frame. Clips keep each component's drawing inside its own box. The host may draw with a high-resolution real font, a font built into the device, a GPU, or a d/OS screen; your source's geometry and drawing order are the reference on all of them.
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 handlers that change the model and note what needs repainting;
- paint: a projection of the current model into GFX intents, with no side effects of its own.
Do not change application state just because DRAW was called again. Hosts may expose, merge, or repeat draws.
Handlers are turns, not threads¶
Do not block inside a handler waiting for another event. Start the operation, keep its state, return, and continue on a later turn or when the completion arrives. The runtime runs your compiled code in slices for the same reason: so a small host stays responsive.
Where widgets fit¶
Painting one button by hand is easy. Making focus, caret placement, press and release, disabled state, menus, clipping, themes, and repainting agree across every host is not. d/Works Widgets provides those reusable objects while leaving the lifecycle and the meaning of your program in your code.
Next: Widgets →