Skip to content

UXApplication

UXApplication is the program. It boots the backend, owns the window list and the menu bar, runs the event loop, and sits at the top of the responder chain. An application’s main() is the same lines on every target: with UXPlatform in the build, a new application installs the driver for the backend the build links:

void main(void) {
UXApplication* app = new UXApplication();
Controller* c = new Controller();
app.setDelegate(c);
app.run();
}
#use <UXKit>

Life starts in the delegate. run boots the driver, then calls your delegate’s applicationDidStart, where you make windows, add views and install menus. Return 0 to enter the loop. Any other value aborts the run with that code. A delegate may call stop during start-up (a test, a one-shot tool), and the stop is honoured.

protocol UXApplicationDelegate {
i32 applicationDidStart(UXApplication* app);
optional void windowDidResize(UXApplication* app, UXWindow* win, i32 width, i32 height);
}

The loop has two shapes, and your code sees one. On five of the six backends, run() is the classic blocking loop: one event from the driver, one dispatch, one coalesced repaint, repeat. On iOS, the platform owns the main thread’s loop, so run() detects driverOwnsRunLoop() and hands the thread to the native loop. Your delegate still starts in applicationDidStart (fired from the platform’s own start moment) and your actions still fire, so the app’s source is the same. This is the toolkit’s one sanctioned loop inversion, and the driver handles it.

Repaints are coalesced. However many views called setNeedsDisplay in one pass of the loop, displayIfNeeded repaints once, and each window repaints only the union of the rects its views marked.

Closing windows is deferred. An action that closes its own window would tear down the view whose event is still on the stack. Control actions therefore use closeWindowLater, and the loop drains the list once no window code is running. Closing the last window ends the run.

Resizes reach you after the toolkit is done. When the optional windowDidResize fires, the native frame has finished its drag, the tree has reflowed (springs and struts, see UXView.setAutoresizeMask), and the repaint has happened. Implement it only if you lay out views by hand.

Configuring · setDriver · setHeadless · setDelegate · setMenuBar · setIcon · setIconPixels The loop · run · stop · isRunning · pump Windows · addWindow · closeWindow · closeWindowLater Files dropped on a window · setFileDropHandler · deliverFileDrop · setItemDropHandler · deliverItemDrop · setItemHoverHandler · deliverItemHover Screen · screenWidth · screenHeight Painting · displayIfNeeded

void setDriver(UXViewDriver* d)

Selects a particular backend and tells the driver its application. A program needs it only to choose a backend other than its target’s (a test of one driver, say), because new UXApplication() installs the target’s driver when UXPlatform is in the build. Windows are shown unless setHeadless asks otherwise.

void setHeadless(bool on)

Call before run. Realizes and paints windows without showing them, where the platform has that mode. On macOS the views are painted offscreen and a GL view still gets its context. The other backends show their windows as usual, which is headless enough under Xvfb or an emulator. It is for an app’s own test runs, so the source stays the same.

void setDelegate(UXApplicationDelegate* d)

Sets the app’s owner. run() refuses to start without one.

void setFileDropHandler(callback h void(u8* path, i32 window, i32 x, i32 y))

Takes files dragged onto the app’s windows from the Finder or a file manager. The handler gets each file’s path, the window’s handle and the point in its content. The path belongs to the driver, so copy it to keep it. From 0.7 on AppKit, and on GTK and Windows from 0.71.

void deliverFileDrop(u8* path, i32 window, i32 x, i32 y)

What a driver calls for each dropped file; it passes the file to the handler.

void setItemDropHandler(callback h void(u8* item, i32 window, i32 x, i32 y))

Takes rows dragged out of the app’s own tables (see setDragsRows) and dropped on its windows. The handler gets the row’s first column, the window’s handle and the point in its content. The text belongs to the driver, so copy it to keep it. From 0.7 on AppKit, and from 0.71 on GTK, Windows and the backends where the toolkit draws its tables (the web, GEM).

void deliverItemDrop(u8* item, i32 window, i32 x, i32 y)

What a driver calls for a dropped row; it passes the row to the handler.

void setItemHoverHandler(callback h void(u8* item, i32 window, i32 x, i32 y))

Follows such a row while it is dragged over the app’s windows, so the app can show what a drop would do. The point is (-1, -1) once the row has left the window or been dropped. From 0.7 on AppKit, and from 0.71 on GTK, Windows and the backends where the toolkit draws its tables.

void deliverItemHover(u8* item, i32 window, i32 x, i32 y)

What a driver calls as a row is dragged; it passes the row to the handler.

void setMenuBar(UXMenuBar* mb)

Installs the bar. From then on the platform owns the drawing, tracking and pull-down, and a pick arrives as a neutral menu-select event routed to the bar’s callbacks.

i32 run(void)

Boots, starts the delegate, then runs the loop (or the platform’s loop on iOS, see the overview). Returns 0 after stop on the blocking backends, 1 if boot failed, 2 with no delegate, or the delegate’s own nonzero start code.

bool setIcon(UXImage* img)
bool setIconPixels(u8* data, i32 w, i32 h, i32 format)

The application’s icon while it runs: the Dock tile on macOS, the taskbar, Alt-Tab and title-bar icon of every window on Windows (including windows opened later), and the page’s favicon on the web. setIconPixels takes raw pixels, in the formats of drawPixels: UXPIX_RGBA bytes, such as a decoded PNG, or UXPIX_ARGB32 words. The pixels are copied, so the image can change or go away afterwards. To change the icon, for a status variant say, set it again.

Both return true when the platform shows the icon. They return false on iOS, Android, GTK and GEM, where only the app’s package gives it an icon: the asset catalog, the APK’s android:icon, the .desktop entry and icon theme, and the desktop’s resource file. That icon is also what every platform shows before the app runs (in Finder, Explorer or a launcher), and it comes from the packaging step, not from this call.

void stop(void)

Ends the loop after the current iteration, on every backend. Where the platform owns the loop (AppKit’s [NSApp run]), the driver ends that too, so stop works from a turn hook, a timer or an event handler alike. On iOS a stopped test app exits. A real iOS app does not stop, because the platform owns the process lifetime.

With UX_AUTOQUIT=<ms> in the environment, the loop stops by itself after that long, by the same path. Use it for an unattended run of an app that would wait for a person. On Windows under Wine, a process killed from outside leaves its helper processes running.

bool isRunning(void)

True between a successful start and stop().

void everyTurn(turnHook_t* fn, i32 ms)
bool turnIsDriven(void)

The app’s frame clock: fn is called once per turn — at most every ms milliseconds, 0 meaning “every turn the loop has” — from outside any draw, so it is the place to step a simulation and mark what moved. Passing (0, 0) stops it, and the app can stop its own clock from inside a turn (which is what a client that wants to exit cleanly needs, since stop only lowers a flag).

fn takes no arguments and must be a plain function, not a bound method: on the backends that own their loop the driver holds it as a C function pointer.

On macOS a turn of 30 a second or more (ms from 0 to 33) is paced by the display: it comes once per refresh of the screen the window is on, steadily, at most every ms. A slower turn comes from a timer.

Who calls fn is the driver’s answer, and this method remembers it: true from setTurnHook means the driver armed its own source and calls fn (interactive AppKit, iOS, Android — the loop owners whose native loop leaves nothing above the driver able to get a turn); false means the loop paces itself, using ms as the wait it hands nextEvent so that a turn comes round even when no input does. turnIsDriven() returns that answer, so a client that needs to know whether the clock it asked for is the clock it got asks rather than assumes:

app.everyTurn(&tick, 16);
if (!app.turnIsDriven())
// the neutral loop is doing the calling
void pump(i32 ms)

Drains pending window messages (not input) and dispatches them. This is the primitive for waiting on the platform’s answer. Backends answer geometry asynchronously (report a content size, and the scrollbar that appears changes your work area), so a program that sets and then reads without a pump reads its own request back.

void addWindow(UXWindow* w)

Registers a window with the app. The first one becomes the key window. Call this before open so events can route to the window.

void closeWindow(UXWindow* w)

Closes the window immediately. This is safe only when no code belonging to w is on the stack. Key status moves to the first remaining window, and closing the last one stops the run.

void closeWindowLater(UXWindow* w)

The safe form for control actions. It queues the close, and the loop performs it once the window’s code has unwound.

i32 screenWidth(void)
i32 screenHeight(void)

The screen (or canvas, or scene) size the driver reported at boot.

void displayIfNeeded(void)

One repaint pass over every window that marked damage. The loop calls it each iteration, and modal driver paths call it so effects show during tracking.

The smallest complete application, with the same shape on every backend:

#use <UXKit>
class App : Object <UXApplicationDelegate>
{
weak:UXApplication* app;
i32 applicationDidStart(UXApplication* a) {
app = a;
UXView* content = new UXView();
UXWindow* win = new UXWindow();
a.addWindow(win);
win.open((u8*)"Hello", UXGeom.make(20, 20, 240, 120), content);
UXButton* quit = new UXButton();
quit.setTitle((u8*)"Quit");
quit.setAction(&self.onQuit);
content.addSubview(quit, UXGeom.make(80, 46, 80, 28));
win.tree.finalise();
win.displayAll();
return 0;
}
void onQuit(UXControl* sender) { app.stop(); }
}
void main(void) {
UXApplication* app = new UXApplication();
App* a = new App();
app.setDelegate(a);
app.run();
}