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 0x101–0x104; 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.