Skip to content

Windows & events

This page shows you how to open a window from Python and respond to what happens in it. You will write a handful of decorated functions, draw with a few drawing requests, and react to keys and the pointer. d/OS owns the window, the keyboard focus, the input queue and the schedule; your handlers run inside short, bounded turns and ask for things through intents (an intent is a named request such as "fill this rectangle" that the machine carries out however it can) and services.

The whole example

import dos

# A GUI program: the shared D/OS lifecycle delivers open, draw, input and close.
clicks = 0

@dos.on("WINDOW_OPEN")
def open_window():
    dos.service("SET_TITLE", "d/Python — Hello, Mac")

@dos.on("WINDOW_DRAW")
def draw_window():
    dos.intent("GFX", "BEGIN_FRAME")
    dos.intent("GFX", "FILL_RECT", 0, 0, 320, 200, 4226)
    dos.intent("GFX", "FILL_RECT", 12, 12, 296, 44, 6371)
    dos.intent("GFX", "DRAW_TEXT", 24, 26, "d/Python", 1, -1, 6371)
    dos.intent("GFX", "DRAW_TEXT", 24, 78, "Python. Shared DBC. Real windows.", 1, -1, 4226)
    dos.intent("GFX", "DRAW_TEXT", 24, 104, "Key events: " + str(clicks), 1, 2047, 4226)
    dos.intent("GFX", "DRAW_TEXT", 24, 150, "Press a key to update this window.", 1, -21163, 4226)
    dos.intent("GFX", "PRESENT")

@dos.on("ON_KEY")
def on_key(key):
    global clicks
    clicks += 1
    dos.service("INVALIDATE")

@dos.on("WINDOW_CLOSE")
def close_window():
    pass

This is window.py from the examples. It runs in the playground and, unchanged, opens a real macOS window from the command line.

The handlers

Handler Arguments When
WINDOW_OPEN none once, after the module's top-level code has run, before the first draw
WINDOW_DRAW none whenever d/OS needs the window's contents
WINDOW_RESIZE width, height on size changes; declaring it makes the window resizable
ON_KEY key a key code
ON_POINTER x, y, buttons the pointer position and a button mask
WINDOW_CLOSE none once; release anything you hold

If you leave out a required handler, the compiler fills in an empty one. The playground maps Enter to 13, Escape to 27, Backspace to 8, Tab to 9 and the arrow keys to 0x1010x104; printable keys arrive as their code point.

Drawing

Drawing is a frame: GFX.BEGIN_FRAME, some fills and text, GFX.PRESENT. In the playground the window is 320 by 200 units; a native window reports its size through dos.service("WIDTH") and dos.service("HEIGHT"). Colors are packed integers in the runtime's own format; the examples use 4226, 6371, 2047, -1, -736 and -21163. The same drawing tests, written in both d/BASIC and d/Python, compare the exact calls, the buffer contents and individual pixels, with frame optimization both on and off.

# fragment: inside a WINDOW_DRAW handler
dos.intent("GFX", "BEGIN_FRAME")
dos.intent("GFX", "FILL_RECT", x, y, width, height, color)
dos.intent("GFX", "DRAW_TEXT", x, y, "text", 1, foreground, background)
dos.intent("GFX", "PRESENT")

Asking for a redraw

A handler that changes something does not draw. It calls dos.service("INVALIDATE"), and d/OS schedules a draw turn. This rule is what keeps the interface responsive: d/OS decides when drawing happens, and a program never repaints on its own clock.

Pointer input

buttons is a mask; buttons % 2 == 1 means the primary button is down. The idiom for a click is to remember whether the pointer was down last time and act on the change:

# fragment: an ON_POINTER handler that acts on press, not on movement
@dos.on("ON_POINTER")
def on_pointer(x, y, buttons):
    global pointer_down
    down = buttons % 2 == 1
    if down and not pointer_down:
        handle_press(x, y)
        dos.service("INVALIDATE")
    pointer_down = down

Your First Game uses exactly this to place an X.

What d/OS keeps for itself

The window's state, the focus, priority and the schedule. Handlers run within existing time budgets; memory allocation and long built-in operations either yield or do a bounded amount of work; and the interface never waits on a program. Where memory lives is invisible to the program. These are laws of d/OS, and they apply to d/BASIC in the same words.

Continue with Retained Widgets, where the shared engine does the drawing for you.