Skip to content

D/Works widgets

Buttons are easy. Agreeing on focus, damage, keyboard activation, menus, resizing, and many renderers is the hard part.

D/Works is the standard widget source library written in D/BASIC. It gives applications a common visual and interaction vocabulary 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 D/OS heads with very different display capabilities.

D/Works centralizes the reusable part:

  • theme colors and measurements;
  • bounded widget geometry and state;
  • consistent focus, pressed, checked, and disabled visuals;
  • editable text with a visible caret;
  • local typed control events;
  • explicit damage tracking; and
  • popup/menu behavior with known bounds.

It still leaves program meaning where it belongs—in your application.

window handlers + app model → widget objects + damage → GFX Intents → adaptive runner → local head

Import it

IMPORT dworks.widgets BAKED

The compiler assembles the library at build time or binds its exact shared artifact, then emits the same target-blind DBC vocabulary as the rest of the program. There is no browser widget rewrite, Mac-only control set, or hidden JavaScript event model.

Who owns what

Owner Responsibilities
application model, lifecycle, layout, focus order, pointer routing, command meaning, optional modal policy
widget reusable control state, visual behavior, local event reduction, damage marking, GFX painting
runner normalized input delivery, capability admission, font/color/render route, frame presentation

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 canonical RGB565 palette and visual roles
WorksMetrics margins, gaps, control sizes, and layout measurements
WorksSurfaceDamage surface-owned dirty/expose ledger
WorksLocalEvent synchronous action value: source, kind, integer value, and note

Base and presentation

Type Role
WorksWidget base geometry, visibility, enablement, focus, damage, clipping, and 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 selection-free caret navigation
WorksToggle check/toggle control
WorksIconButton compact glyph command with optional label and mark
WorksTooltip application-positioned bounded hint

Commands and transient surfaces

Type Role
WorksPopoverFrame attached bounded popup frame
WorksCommandRow row-shaped icon/text command
WorksMenuItem one stable menu command, check, separator, disabled, and child-pane state
WorksMenu app-owned anchored menu with up to eight physical item slots
WorksMenuCascade allocation-free cascade of up to four panes
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. construct the shared theme and surface damage ledger;
  2. construct a widget with stable identity and visible caption;
  3. attach the widget to the surface ledger;
  4. apply the theme;
  5. assign bounds from application layout; and
  6. set visible interaction state through methods.

Stable identity such as "save" is application vocabulary. A glyph or 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 bounds received it; the widget reduces its own activation and visual state; the application gives Event.Source and Event.Kind meaning.

Try the larger D/Works widget form in the playground. It adds a text field, toggle, focus traversal, click-to-place 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 stable widget/command identity
Kind action category such as activation, toggle, text, or menu
Value small typed integer payload
Note text payload or human detail

The event is synchronous and application-owned. It is not a DOBJ transport, OS message, or deferred host callback. The receiving controller 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 route other keys to the focused control. This makes focus order obvious, testable, and responsive to compact layouts.

Text fields and the caret

WorksTextField provides:

  • AcceptKey(K) for printable ASCII, Backspace, Delete, Left/Right, and Home/End;
  • PlaceCaret(PX) for pointer-based placement;
  • RevealCaret() to keep the insertion point inside the visible field;
  • PublishText(Event) to reduce the current edit; and
  • SetText(Value) for model-driven replacement.
IF Focus% = 1 THEN
  Title.AcceptKey K
  Title.PublishText Event
END IF

The caret belongs to widget state. The runner may render its font with a high-density browser or desktop face, but cell metrics and click-to-position behavior remain tied to the semantic text role.

Damage: repaint because something changed

Widget setters compare old and new visible state. A real change marks the widget and its attached WorksSurfaceDamage; assigning the same value does not.

Title.SetText NewTitle$

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

During drawing, paint the damaged controls and commit the ledger:

Save.PaintDamaged
Title.PaintDamaged
Damage.Painted

Call RequestExpose when the host exposes or resizes the surface and a complete repair is needed. Damage is correctness first and performance second: stale pixels are wrong, while needless whole-surface repainting makes constrained heads feel slow.

Pointer reduction

The application currently compares public X, Y, Width, and Height fields. That is not a leak of widget internals; bounds are the shared controller contract.

Track press and release separately. A press inside followed by a release outside should normally cancel a button activation. Sliders and drags may use a different reducer, but the ownership remains explicit.

WorksMenu is an anchored popup owned by the application, not a global OS menu. Each of its up to eight item slots can carry:

  • a visible label and optional glyph;
  • 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 active path. It opens right when space permits, left when necessary, gives the deepest visible pane pointer precedence, and rejects cycles. Keyboard navigation wraps and skips disabled/separator rows.

An item's glyph is never its command identity. Changing artwork must not change behavior.

This complete console sample builds the model used by 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 shortcut;
  • paint Menus.Paint after ordinary content so every open pane is on top;
  • while Menus.IsModalOwner() is true, route pointer press/release to Menus.Press and Menus.Release before the underlying controls;
  • use Menus.MoveFocus(-1) or Menus.MoveFocus(1) for Up/Down and Menus.ActivateFocused(...) for Enter or Right; Escape calls Menus.Dismiss;
  • reduce the resulting WorksLocalEvent.Source such as export.pdf through the application's normal command dispatcher; and
  • request a full surface expose after dismissal, because the pixels underneath the popup now belong to the underlying widgets again.

Choose an exported semantic font constant before opening:

Constant Intended role
WORKS_MENU_FONT_UI standard UI text
WORKS_MENU_FONT_UI_SMALL compact UI text
WORKS_MENU_FONT_NATIVE_MONO native monospace/cell face
WORKS_MENU_FONT_UI_CHROME small chrome text

The runner maps those roles to its best honest face. On a browser or desktop that can mean a real scalable font; on a compact head it can mean a tuned resident face. Rows grow to the resolved line height, so text is not forced through a bitmap-era box.

Theme once, adapt locally

WorksTheme carries semantic colors such as canvas, panel, paper, accent, text, and disabled state. WorksMetrics carries shared measurements. Apply them instead of sprinkling literal colors and heights through widget code.

The runner can map canonical color and font roles upward or downward for the destination head. Your layout can adapt in RESIZE; your application does not need 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 surface damage ledger.
  • Keep focus order in the application.
  • Reduce press/move/release, not isolated clicks.
  • Use setters for visible state changes.
  • Clip text and transient surfaces.
  • Let WorksLocalEvent cross the control/controller boundary.
  • Let a DATA/Service Intent—not the widget—own persistence.
  • Release application-owned object references in CLOSE.

Next: Resources, Images & Sound →

See also: Windows & Events · Intents & Services · Drag, Drop & DOBJ · Records & Objects · Playground Guide