Skip to content

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.

window handlers + app model → widget objects + repaint tracking → GFX intents → the d/OS runtime → the screen

Import it

IMPORT dworks.widgets BAKED

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
  1. create the shared theme and the window's repaint record;
  2. create a widget with a stable identity and a visible caption;
  3. attach the widget to the repaint record;
  4. apply the theme;
  5. give it bounds from your layout; and
  6. 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; and
  • SetText(Value) for replacing the text from your model.
IF Focus% = 1 THEN
  Title.AcceptKey K
  Title.PublishText Event
END IF

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.

Title.SetText NewTitle$

IF Damage.Pending() THEN
  SERVICE INVALIDATE()
END IF

While drawing, paint the controls that changed, then record that the repaint is done:

Save.PaintDamaged
Title.PaintDamaged
Damage.Painted

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.

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 Command identity;
  • 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.Paint after the ordinary content so every open pane is on top;
  • while Menus.IsModalOwner() is true, send pointer press and release to Menus.Press and Menus.Release before the controls underneath;
  • use Menus.MoveFocus(-1) or Menus.MoveFocus(1) for Up and Down and Menus.ActivateFocused(...) for Enter or Right; Escape calls Menus.Dismiss;
  • handle the resulting WorksLocalEvent.Source, such as export.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.

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 WorksLocalEvent carry 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