Platform Implementation
macOS Implementation
The macOS backend maps NAppGUI's abstract GUI operations onto Cocoa/AppKit classes, Core Graphics drawing, and optional WebKit support. This page is based on the local checkout and reflects the current Objective-C backend in this repository.
Relevant Source Files
src/osapp/osx/osapp_osx.m
src/osgui/osx/oswindow.m
src/osgui/osx/osglobals.m
src/osgui/osx/oscolor.m
src/osgui/osx/oscontrol.m
src/osgui/osx/oscombo.m
src/osgui/osx/ospopup.m
src/osgui/osx/osview.m
src/osgui/osx/osdrawctrl.m
src/draw2d/osx/draw_osx.m
src/draw2d/osx/osimage.m
src/osgui/osx/osweb.m
prj/NAppUtils.cmake
prj/NAppTarget.cmake
Purpose and Scope
NAppGUI's macOS implementation is the Cocoa/AppKit backend used by the native osgui layer.
It translates the abstract widget and window operations exposed through GuiCtx into Objective-C classes,
NSView/NSControl wrappers, NSPanel-based windows, Core Graphics drawing, and optional WKWebView integration.
This backend is not a separate public framework. It is one concrete implementation behind the higher-level
gui and osapp APIs, and it becomes active when osmain(...) starts the app,
creates the native GUI context, and makes that context current.
Architecture Overview
The macOS backend uses thin Objective-C subclasses to carry NAppGUI state alongside native Cocoa behavior. Most platform objects are real AppKit objects with extra fields for listeners, control flags, layout integration, and cross-platform bookkeeping.
| Wrapper | Native Base | Role |
|---|---|---|
OSXAppDelegate |
NSObject <NSApplicationDelegate> |
Owns runloop callbacks, theme-change deferral, staged termination, and app listener state. |
OSXWindow |
NSPanel |
Stores role, tabstop state, hotkeys, shared field editor, focus bookkeeping, and window flags. |
OSXWindowDelegate |
NSObject <NSWindowDelegate> |
Bridges move, resize, close, miniaturize, and overlay deactivation into NAppGUI events. |
OSXView |
NSView |
Hosts custom drawing, mouse/keyboard listeners, scroll support, focus hooks, and overlay rendering. |
OSXWebView |
WKWebView or NSView |
Conditional web backend. It becomes a stub NSView when web support is not compiled in. |
The important design point is that the backend is not built around one monolithic window class.
Instead, each control family has a native wrapper with shared helper code in files such as oscontrol.m,
while the runloop and theme machinery live in separate application and globals modules.
Application Lifecycle and Run Loop
The lifecycle is centered on OSXAppDelegate. During _osapp_init_imp(...), NAppGUI acquires
NSApplication.sharedApplication, sets its activation policy to regular, activates it, allocates the delegate,
stores the cross-platform callback pointers, and installs the delegate on NSApp.
When Cocoa reports applicationDidFinishLaunching, the delegate does not just invoke the app callback.
It first installs a timer and registers it in three run loop modes:
NSDefaultRunLoopModeNSEventTrackingRunLoopModeNSModalPanelRunLoopMode
If the cross-platform app provided a frame/update callback, the timer runs every 0.01 seconds. If not,
a slower termination-monitor timer runs every 0.25 seconds. This keeps the app update path alive during
normal interaction, event tracking, and modal loops.
Termination is also more careful than a one-shot shutdown. _osapp_terminate_imp(...) sets a staged
termination flag, then the timer calls realTerminate across several runloop turns so Cocoa can release
deferred objects before the final [NSApp terminate:].
Secondary threads are given their own NSAutoreleasePool via _osapp_begin_thread /
_osapp_end_thread to avoid leaking autoreleased Cocoa objects such as images.
Global State and Theme Management
The macOS backend centralizes platform-wide state in osglobals.m. That module computes theme colors,
caches rasterized native assets for controls, exposes system cursors, reports screen geometry, and queues idle listeners.
Theme Change Flow
Theme change handling is deferred by design. The application delegate listens for
AppleInterfaceThemeChangedNotification and AppleColorPreferencesChangedNotification,
but the observer only sets theme_changed = TRUE. The actual refresh happens on the next timer tick,
when the new appearance is effective.
On that tick, the backend recomputes the cached colors in osglobals_theme_changed(), notifies
the optional OnThemeChanged listener, and invalidates cached checkbox and header images.
Dynamic Colors
oscolor.m uses a one-pixel helper view, OSXViewRGBA, to resolve an NSColor
into RGBA under the current effective appearance. That matters because dynamic macOS colors depend on light/dark mode
and cannot always be treated as fixed component values.
Cached Native Control Chrome
Checkbox and table-header visuals are not hand-painted from scratch. The backend creates real native controls,
rasterizes them into NSImage caches, and reuses those images in custom control drawing. There is even a
compatibility path for the macOS 26.0 SDK that attaches the view to an offscreen borderless window before caching,
because unattached views would otherwise render nothing.
Window System
Windows are implemented by OSXWindow, a subclass of NSPanel. The instance stores more
than the normal Cocoa window state: it tracks NAppGUI window flags, window role, hotkeys, tabstop state, a shared
field editor, focus bookkeeping, and whether move/resize events should currently be emitted.
Resize and Close Semantics
OSXWindowDelegate.windowWillResize converts the incoming frame size into a content size,
emits ekGUI_EVENT_WND_SIZING, then immediately emits ekGUI_EVENT_WND_SIZE with the constrained result,
and finally returns a new frame size computed from the requested content size. That means the macOS backend performs
layout-aware size negotiation before Cocoa commits the resize.
Close handling is centralized in i_close(...). It checks whether the current focus chain allows closing,
packages the close origin into EvWinClose, and lets the registered NAppGUI listener veto the close.
Keyboard Routing
OSXWindow.processKeyDown handles tab traversal, return/default-button behavior, escape behavior, and hotkeys
before falling back to Cocoa. Return can trigger the default push button, optional window-close semantics, or both depending
on the window flags. Escape can generate a close notification without immediately delegating to Cocoa.
Field Editor
Each window lazily creates an OSXText field editor in windowWillReturnFieldEditor.
NAppGUI reuses that editor per window and switches it between field-editor and regular text-view modes depending on the client control.
Launch Modes
| Mode | Backend Role | Native Behavior |
|---|---|---|
| Regular launch | ekGUI_ROLE_MAIN or ekGUI_ROLE_OVERLAY |
Main windows use orderFrontRegardless; child overlays use makeKeyAndOrderFront. |
| Hide | role cleared | orderOut with either the parent or the window itself as the sender. |
| Modal launch | ekGUI_ROLE_MODAL |
Disables worksWhenModal on the parent, fronts the window, then calls [NSApp runModalForWindow:]. |
The code deliberately prefers orderFrontRegardless for the first window instead of relying only on
makeKeyAndOrderFront(nil), with comments noting changed or noisy AppKit behavior on newer macOS releases.
Control System and Text Layer
Shared control behavior lives in oscontrol.m. Controls start hidden, attach to parents through
addSubview, detach through removeFromSuperviewWithoutNeedingDisplay, and use common helpers
for size, alignment, text color, control size, tooltips, enable/disable state, and coordinate conversion.
Text-bearing controls use OSTextAttr, which stores:
- the active
Font - the current text color
- horizontal alignment
- an optional marked character position for underlining
Text updates are applied as attributed strings. The helper builds an attribute dictionary for underline, strikeout, paragraph style, foreground color, and font, then writes that attributed string into the native cell. This is why text styling is consistent across wrapper controls such as popups and buttons.
Not every control owns its text stack directly. For example, oscombo.m wraps an NSComboBox
but delegates most text behavior to an internal text-field helper, while ospopup.m stores a local
OSTextAttr and updates the NSPopUpButton title styling through shared helpers.
Custom Views and Event Routing
OSXView is the generic custom-view surface used by NAppGUI for owner-drawn controls and general custom content.
It can carry scroll state, a lazily-created drawing context, mouse tracking, listener tables, focus listeners, and a separate overlay listener.
Drawing Path
In drawRect, the backend wraps the current NSGraphicsContext in a NAppGUI DCtx,
marks whether the view is flipped, and dispatches ekGUI_EVENT_DRAW. If an overlay listener is present, it then performs a second draw pass using the same DCtx.
OpenGL views are treated differently. When the ekVIEW_OPENGL flag is set, OSXView skips the normal
NSGraphicsContext wrapping and dispatches the draw callback directly.
Input Routing
Mouse entered, exited, moved, down, up, drag, scroll-wheel, key-down, key-up, and modifier-flag changes are all bridged through
_oslistener_* helpers. Scrollable views first translate wheel movement into scroll events before forwarding the raw wheel event.
Focus-ring behavior is also split by OS version. Newer systems use the normal focus-ring type, while older macOS targets have an explicit
focus-ring drawing fallback in drawRect.
Drawing and Image Pipeline
The macOS draw2d backend uses Core Graphics under the hood. draw_osx.m manages
CGContextRef transforms, raster vs real-2D mode switching, path operations, gradients, strokes, fills,
and text measurement through Cocoa font attributes.
The custom-control drawing helper in osdrawctrl.m sits on top of that layer. It paints backgrounds,
focus outlines, header chrome, and checkbox visuals using the theme colors and cached native images supplied by osglobals.m.
Image Representation
osimage.m treats the macOS image payload as an NSImage with one main NSBitmapImageRep.
It supports creating images from raw pixels, encoded data, file types, and drawing contexts.
Scaling uses a CGBitmapContext, and codec export uses NSBitmapImageRep.representationUsingType.
A few implementation details are worth knowing:
osimage_create_from_datanormalizesNSImagesize to the bitmap size because they can disagree.osimage_writespecial-cases BMP export by transcoding through JPEG first to avoid corrupted palette-based BMP output.- Animated images expose frame count and per-frame duration through
NSBitmapImageRepproperties.
WebView Support Model
Web content support on macOS is conditional. In osweb.m, OSXWebView is a real
WKWebView only when NAPPGUI_WEB_SUPPORT is defined and the deployment target is at least macOS 10.10.
Otherwise the same type falls back to a plain NSView stub.
That means the API surface always exists, but behavior changes depending on the build configuration:
ekGUI_WEB_NAVIGATE, back, and forward work only with realWKWebViewsupport.- Some functions such as scroller visibility are currently no-ops.
- Clipboard support is not implemented and asserts.
The build system enables this support only when NAPPGUI_WEB is on and the macOS deployment target is greater than 10.9.9999.
Key Verified Backend Notes
| Area | Verified State in This Checkout |
|---|---|
| Verification basis | This page is grounded in commit 2b009960 from 2026-03-17 and the current repository files. |
| Theme handling | Theme updates are explicitly deferred to the next timer tick after distributed notifications, then routed through osglobals_theme_changed() and the optional listener. |
| WebView support | WebView support is conditional. Without NAPPGUI_WEB_SUPPORT, the macOS web wrapper is a stub NSView, not a real browser view. |
| Native styling | Checkbox and header visuals are actually cached rasterizations of native controls, not generic custom painting from scratch. |
| Termination flow | Termination is staged across timer cycles to let Cocoa drain pending releases before final shutdown. |
Recommended Reading Order
src/osapp/osx/osapp_osx.mfor startup, timers, theme notification, and termination.src/osgui/osx/oswindow.mfor window roles, key routing, modal behavior, and the shared field editor.src/osgui/osx/osglobals.mplussrc/osgui/osx/oscolor.mfor theme colors, cached native chrome, and cursor/system services.src/osgui/osx/oscontrol.m,src/osgui/osx/oscombo.m, andsrc/osgui/osx/ospopup.mfor the shared control/text layer.src/osgui/osx/osview.mfor custom view drawing and event routing.src/draw2d/osx/draw_osx.m,src/osgui/osx/osdrawctrl.m, andsrc/draw2d/osx/osimage.mfor rendering and image handling.src/osgui/osx/osweb.mplusprj/NAppUtils.cmakefor the conditional web story.