Skip to content

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:

  1. when the buttons go from none pressed to pressed, remember which control owns the press;
  2. update the pressed or dragging look while the pointer moves;
  3. on release, activate only if the release satisfies that control's rule; and
  4. 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:

IF W < 240 OR H < 150 THEN
  App.Compact = -1
ELSE
  App.Compact = 0
END IF

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.

IF App.NeedsPaint() THEN SERVICE INVALIDATE()

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 →