Skip to content

UXButton

UXButton is the toolkit’s push button. You give it a title and an action (a callback bound to your controller), and the platform provides the rest: GEM’s AES themes it, Win32 realizes a BUTTON child window, macOS overlays a real NSButton, iOS a real UIButton, and the web backend draws the toolkit’s neutral art on canvas. You write no drawing code and no platform code.

#use <UXKit>

A button is the smallest complete example of the toolkit’s control model:

UXButton* b = new UXButton();
b.setTitle((u8*)"Apply");
b.setAction(&controller.onApply); // a method, bound to its receiver
content.addSubview(b, UXGeom.make(8, 50, 56, 18));

(Number literals bind to the parameter’s type, so UXGeom.make(8, 50, 56, 18) needs no casts. Explicit (i16) casts that appear in some toolkit sources are not required.)

The action is a callback (callback void(UXControl* sender)). &controller.onApply carries both the receiver and the code, so there is no selector, no downcast, and no separate target field. A callback never owns its receiver, which has two consequences:

  1. No retain cycle. window → tree → button → action → controller → window would cycle if the action were strong. The callback’s weakness breaks the cycle (AppKit needs a separate weak target for the same job).
  2. A dead controller is a silent no-op. The truth test if (action) is false both when no action is set and when the receiver has been deallocated. A fired button whose controller died does nothing, instead of dispatching into a stale object.

How a press becomes your method. On backends where the button is a real native control (Win32, macOS, iOS), the OS notifies the driver (BN_CLICKED, an NSButton action, a UIButton target-action), and the driver fires the neutral widget directly by handle and node (fire-by-peer). Your onApply runs without a synthetic click or hit-test. On GEM and the web, the press arrives as an ordinary event, routes through the shared tree hit-test to mouseDown, and fires the same way. Both paths end at action(self).

A disabled button (setEnabled on the view surface) consumes nothing and fires nothing.

  • Inherits UXControl: the title/action surface and the fire path.
  • Through it, UXView: frame, hierarchy, hide/enable, the responder chain.

Configuring · setTitle · setAction Realization · kind · attachTo Events · mouseDown · fire

void setTitle(u8* s)

The label the platform renders. The string is borrowed, not copied. Keep it alive for the button’s lifetime (a literal, the common case, is always fine).

void setAction(callback a void(UXControl* sender))

The callback to fire: b.setAction(&controller.onApply), where the method has the matching shape void onApply(UXControl* sender). The receiver is never owned and auto-zeroes when it dies. The overview explains why no other ownership handling is needed.

UXKind kind(void)

Answers UXKindButton, the neutral kind realizeTree maps to the platform’s native button.

void attachTo(UXViewTree* t, UXRect frame)

Realizes the button into the tree at frame and stores the widget as the node’s peer. The peer lets a native backend fire it by handle+node with no hit-test.

void mouseDown(UXEvent* e)

The neutral press path (GEM, web, and every headless test). It consumes the click (a control never passes its click up the chain) and calls fire if enabled.

void fire(void)

Runs the action with the button as sender. The call is false-guarded, so an unset action and a deallocated receiver are both quiet no-ops.

UXButton on the web backend

The Aristo theme’s 9-sliced button face from the GEM atlas, title centred by real measurement. This is the artwork GEM draws, on canvas.

All captures are produced by frameworks/uxkit/tools/capture/capture.sh: the same posed scene per platform, regenerated, never hand-shot.

A controller whose callback counts presses. This is the complete wiring, on any backend:

#use <UXKit>
#import <Stdio.xc>
class Controller : Object <UXApplicationDelegate>
{
weak:UXApplication* app;
i32 presses;
i32 applicationDidStart(UXApplication* a) {
app = a;
UXView* content = new UXView();
UXWindow* win = new UXWindow();
a.addWindow(win);
win.open((u8*)"Buttons", UXGeom.make(10, 10, 200, 100), content);
UXButton* b = new UXButton();
b.setTitle((u8*)"Press me");
b.setAction(&self.onPress);
content.addSubview(b, UXGeom.make(8, 8, 90, 24));
win.tree.finalise();
win.displayAll();
return 0;
}
void onPress(UXControl* sender) {
presses = presses + 1;
Stdio.printf("pressed %d time(s)\n", presses);
}
}
void main(void) {
UXApplication* app = new UXApplication();
app.setDriver(new UXWebDriver()); // the ONE platform-aware line
Controller* c = new Controller();
app.setDelegate(c);
app.run();
}