d/Works widgets¶
This page introduces the d/Works widget library: ready-made buttons, text fields, toggles, menus and more, written in d/BASIC. You will import it, build a one-button window, learn how a widget reports an action back to you, and see how focus, repainting and menus work. By the end you can build a small form without painting every control from rectangles.
Buttons are easy. Agreeing on focus, repainting, keyboard activation, menus, resizing, and many different screens is the hard part.
The d/Works widgets are the standard widget library written in d/BASIC. (d/Works is the office suite being written in d/BASIC: d/Write, d/Calc, d/Present.) The library gives applications a common look and feel without hiding the application behind a framework.
Why widgets belong in the language story¶
Without a shared library, every application invents a slightly different button, caret, focus ring, menu, hit test, and redraw rule. Those differences multiply across a browser, three desktop operating systems, and the very different screens of 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.
d/Works keeps the reusable part in one place:
- theme colours and measurements;
- widget geometry and state, kept within known bounds;
- consistent focus, pressed, checked, and disabled looks;
- editable text with a visible caret;
- local typed control events;
- explicit tracking of what needs repainting; and
- popup and menu behaviour with known bounds.
It leaves the meaning of your program where it belongs: in your application.
Import it¶
The compiler builds the library into your program at build time, or binds it to the exact shared copy that is installed, and then produces the same portable bytecode as the rest of the program. There is no separate browser version of the widgets, no Mac-only control set, and no hidden JavaScript event model.
Who owns what¶
| Owner | Responsibilities |
|---|---|
| application | model, lifecycle, layout, focus order, pointer routing, what each command means, optional modal policy |
| widget | reusable control state, visual behaviour, turning input into local events, marking what needs repainting, GFX painting |
| the runtime | delivering input in one form on every host, checking what the host can do, choosing fonts, colours and how to render, presenting the frame |
This division is the secret. Widgets are ordinary objects. They do not install callbacks or take over the event loop.
The catalog¶
Policy and local coordination¶
| Type | Role |
|---|---|
WorksTheme |
the RGB565 colour palette and the role each colour plays |
WorksMetrics |
margins, gaps, control sizes, and layout measurements |
WorksSurfaceDamage |
the window's record of what needs repainting or exposing |
WorksLocalEvent |
an action value, delivered right away: source, kind, integer value, and note |
Base and presentation¶
| Type | Role |
|---|---|
WorksWidget |
base geometry, visibility, enabled state, focus, repaint marking, clipping, and the paint contract |
WorksPanel |
bounded panel with optional caption |
WorksLabel |
static text label |
Compact controls¶
| Type | Role |
|---|---|
WorksButton |
labelled command button |
WorksTextField |
single-line ASCII editor with a caret you can move but no selection |
WorksToggle |
check or toggle control |
WorksIconButton |
compact icon command with optional label and mark |
WorksTooltip |
bounded hint, positioned by the application |
Commands and transient surfaces¶
| Type | Role |
|---|---|
WorksPopoverFrame |
attached bounded popup frame |
WorksCommandRow |
row-shaped icon and text command |
WorksMenuItem |
one stable menu command, with its check, separator, disabled, and child-pane state |
WorksMenu |
anchored menu owned by the application, with up to eight physical item slots |
WorksMenuCascade |
cascade of up to four panes, with no allocation when it opens |
WorksStatusStrip |
neutral, working, warning, and ready feedback |
The setup recipe¶
Every control follows the same readable sequence:
DIM Theme AS WorksTheme
DIM Damage AS WorksSurfaceDamage
DIM Save AS WorksButton
Theme = NEW WorksTheme()
Damage = NEW WorksSurfaceDamage()
Save = NEW WorksButton("save", "Save")
Save.Attach Damage
Save.ApplyTheme Theme
Save.SetBounds 12, 12, 80, 24
Save.SetFocused -1
- create the shared theme and the window's repaint record;
- create a widget with a stable identity and a visible caption;
- attach the widget to the repaint record;
- apply the theme;
- give it bounds from your layout; and
- set its visible state through methods.
The identity "save" is your application's vocabulary. An icon or a translated caption is presentation.
A complete one-button window¶
IMPORT dworks.widgets BAKED
DIM SHARED Theme AS WorksTheme
DIM SHARED Damage AS WorksSurfaceDamage
DIM SHARED Event AS WorksLocalEvent
DIM SHARED Save AS WorksButton
DIM SHARED Width AS INTEGER
DIM SHARED Height AS INTEGER
DIM SHARED PreviousButtons AS INTEGER
DIM SHARED Pressed AS INTEGER
WINDOW SUB OPEN()
Width = 320
Height = 192
Theme = NEW WorksTheme()
Damage = NEW WorksSurfaceDamage()
Event = NEW WorksLocalEvent()
Save = NEW WorksButton("save", "Save")
Save.Attach Damage
Save.ApplyTheme Theme
Save.SetBounds 12, 12, 80, 24
Save.SetFocused -1
Damage.RequestExpose
SERVICE SET_TITLE("Widget hello")
SERVICE INVALIDATE()
END SUB
WINDOW SUB DRAW()
INTENT GFX.BEGIN_FRAME()
INTENT GFX.FILL_RECT(0, 0, Width, Height, Theme.Canvas)
Save.Paint
INTENT GFX.PRESENT()
Damage.Painted
END SUB
WINDOW SUB RESIZE(W AS INTEGER, H AS INTEGER)
Width = W
Height = H
Save.SetBounds 12, 12, 80, 24
Damage.RequestExpose
SERVICE INVALIDATE()
END SUB
WINDOW SUB CLOSE()
Save = NOTHING
Event = NOTHING
Damage = NOTHING
Theme = NOTHING
END SUB
ON KEY SUB (K AS INTEGER)
IF K = 13 THEN
Save.Activate Event
SERVICE SET_TITLE("Activated: " + Event.Note)
END IF
IF Damage.Pending() THEN SERVICE INVALIDATE()
END SUB
ON POINTER SUB (X AS INTEGER, Y AS INTEGER, Buttons AS INTEGER)
IF Buttons <> 0 AND PreviousButtons = 0 THEN
Pressed = X >= Save.X AND X < Save.X + Save.Width AND Y >= Save.Y AND Y < Save.Y + Save.Height
Save.SetDown Pressed
END IF
IF Buttons = 0 AND PreviousButtons <> 0 THEN
IF Pressed AND X >= Save.X AND X < Save.X + Save.Width AND Y >= Save.Y AND Y < Save.Y + Save.Height THEN
Save.Activate Event
SERVICE SET_TITLE("Activated: " + Event.Note)
END IF
Save.SetDown 0
Pressed = 0
END IF
PreviousButtons = Buttons
IF Damage.Pending() THEN SERVICE INVALIDATE()
END SUB
The example is deliberately explicit. The host delivers input; the application decides which control's bounds received it; the widget updates its own activation and look; the application gives Event.Source and Event.Kind their meaning.
Try the larger d/Works widget form in the playground. It adds a text field, a toggle, Tab between controls, click to place the caret, resizing, and a controller class.
Local events are values, not callbacks¶
Save.Activate Event
IF Event.Source = "save" AND Event.Kind = "activate" THEN
CALL SaveDocument
END IF
WorksLocalEvent contains:
| Field | Meaning |
|---|---|
Source |
the stable identity of the widget or command |
Kind |
the kind of action, such as activation, toggle, text, or menu |
Value |
a small typed integer payload |
Note |
a text payload or human-readable detail |
The event happens right away and belongs to your application. It is not a DOBJ (the typed object a program hands to another program when you drag, drop, copy or paste), an OS message, or a deferred host callback. The controller that receives it can inspect it, update the model, and clear or reuse it in the same turn.
Focus is a route you own¶
For a three-control form, a small integer is enough:
SUB Form.SetFocus (NextFocus AS INTEGER)
THIS.Focus = NextFocus
THIS.Title.SetFocused THIS.Focus = 1
THIS.Wrap.SetFocused THIS.Focus = 2
THIS.Save.SetFocused THIS.Focus = 3
END SUB
SUB Form.AdvanceFocus ()
THIS.Focus = THIS.Focus + 1
IF THIS.Focus > 3 THEN THIS.Focus = 1
THIS.SetFocus THIS.Focus
END SUB
Handle Tab in ON KEY, and send other keys to the control that has focus. This keeps the focus order obvious, testable, and easy to adapt for compact layouts.
Text fields and the caret¶
WorksTextField provides:
AcceptKey(K)for printable ASCII, Backspace, Delete, Left/Right, and Home/End;PlaceCaret(PX)for placing the caret with the pointer;RevealCaret()to keep the insertion point inside the visible field;PublishText(Event)to report the current edit as an event; andSetText(Value)for replacing the text from your model.
The caret is part of the widget's state. The runtime may draw the text with a high-density browser or desktop font, but the cell measurements and the click-to-position behaviour stay tied to the text's role, not to any one font.
Damage: repaint because something changed¶
Widget setters compare the old and new visible state. A real change marks the widget and its attached WorksSurfaceDamage as needing paint; assigning the same value again does not.
While drawing, paint the controls that changed, then record that the repaint is done:
Call RequestExpose when the host exposes or resizes the window and everything needs repainting. Repaint tracking is about correctness first and speed second: stale pixels are wrong, and needlessly repainting the whole window makes a small machine feel slow.
Reading the pointer¶
The application compares the widget's public X, Y, Width, and Height fields. That is not peeking at widget internals; bounds are part of the shared contract between widget and controller.
Track press and release separately. A press inside a button followed by a release outside should normally cancel the activation. Sliders and drags may use a different rule, but who owns the decision stays explicit.
Menus are application surfaces¶
WorksMenu is an anchored popup owned by your application, not a global OS menu. Each of its up to eight item slots can carry:
- a visible label and an optional icon;
- a stable
Commandidentity; - an integer
Value; - checked and disabled state;
- a separator; and
- an optional child-pane index.
WorksMenuCascade keeps four physical panes and a separate record of which path is open. It opens to the right when there is room and to the left when there is not, gives the deepest visible pane first claim on the pointer, and refuses to loop back on itself. Keyboard navigation wraps around and skips disabled rows and separators.
An item's icon is never its command identity. Changing the artwork must not change the behaviour.
This complete console program builds the model for a File → Export → PDF/Text cascade:
IMPORT dworks.widgets BAKED
DIM FileMenu AS WorksMenu, ExportMenu AS WorksMenu
DIM SpareTwo AS WorksMenu, SpareThree AS WorksMenu
DIM Menus AS WorksMenuCascade
DIM OpenItem AS WorksMenuItem, ExportItem AS WorksMenuItem
DIM PdfItem AS WorksMenuItem, TextItem AS WorksMenuItem
FileMenu = NEW WorksMenu("file")
OpenItem = NEW WorksMenuItem("open", "file.open", 0, "Open", "Open a document", 0)
ExportItem = NEW WorksMenuItem("export", "file.export", 0, "Export", "Choose a format", 0)
ExportItem.ChildPane = 1
FileMenu.Item0 = OpenItem
FileMenu.Item1 = ExportItem
FileMenu.ItemCount = 2
ExportMenu = NEW WorksMenu("export")
PdfItem = NEW WorksMenuItem("pdf", "export.pdf", 0, "PDF", "Portable document", 1)
TextItem = NEW WorksMenuItem("text", "export.text", 0, "Plain text", "UTF-8 text", 2)
ExportMenu.Item0 = PdfItem
ExportMenu.Item1 = TextItem
ExportMenu.ItemCount = 2
SpareTwo = NEW WorksMenu("spare.two")
SpareThree = NEW WorksMenu("spare.three")
Menus = NEW WorksMenuCascade(FileMenu, ExportMenu, SpareTwo, SpareThree)
PRINT "MENU MODEL READY: "; FileMenu.ItemCount; " + "; ExportMenu.ItemCount
END
In a window controller:
- call
Menus.OpenRoot(...)from the menu button or the shortcut; - paint
Menus.Paintafter the ordinary content so every open pane is on top; - while
Menus.IsModalOwner()is true, send pointer press and release toMenus.PressandMenus.Releasebefore the controls underneath; - use
Menus.MoveFocus(-1)orMenus.MoveFocus(1)for Up and Down andMenus.ActivateFocused(...)for Enter or Right; Escape callsMenus.Dismiss; - handle the resulting
WorksLocalEvent.Source, such asexport.pdf, through your application's normal command dispatcher; and - ask for a full repaint after the menu closes, because the pixels underneath the popup belong to the widgets below it again.
Menu text roles¶
Choose one of the exported font constants before opening the menu:
| Constant | Intended role |
|---|---|
WORKS_MENU_FONT_UI |
standard UI text |
WORKS_MENU_FONT_UI_SMALL |
compact UI text |
WORKS_MENU_FONT_NATIVE_MONO |
the machine's native monospace or cell font |
WORKS_MENU_FONT_UI_CHROME |
small text for window chrome |
The runtime maps each role to the best font it honestly has. In a browser or on a desktop that can be a real scalable font; on a compact screen it can be a bitmap font tuned for the device. Rows grow to the resolved line height, so text is not forced through a box sized for the bitmap era.
Theme once, adapt locally¶
WorksTheme carries named colours such as canvas, panel, paper, accent, text, and disabled. WorksMetrics carries shared measurements. Apply them instead of sprinkling literal colours and heights through widget code.
The runtime can map colour and font roles up or down for the screen it is drawing on. Your layout adapts in RESIZE; your application never needs IF WINDOWS or IF ATARI branches.
A widget checklist¶
- Give every actionable control a stable identity.
- Apply one theme and consistent metrics.
- Attach visible controls to the window's repaint record.
- Keep focus order in the application.
- Read press, move and release as one gesture, not as isolated clicks.
- Use setters for visible state changes.
- Clip text and transient surfaces.
- Let
WorksLocalEventcarry actions across the boundary between control and controller. - Let a DATA or Service intent, not the widget, own persistence.
- Release the object references your application owns in
CLOSE.
Next: Resources, Images & Sound →
See also: Windows & Events · Intents & Services · Drag, Drop & DOBJ · Records & Objects · Playground Guide