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.

Verified Commit 2b0099603323ae7a35b8ee6998d35af94f5b16c8
Commit Date 2026-03-17
Version 1.6.1
Backend Scope osapp + osgui + draw2d
This page is source-verified against the checked-out macOS backend and build files, including conditional code paths for theme handling, WebKit support, native control rasterization, and lifecycle behavior.

Relevant Source Files

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:

  • NSDefaultRunLoopMode
  • NSEventTrackingRunLoopMode
  • NSModalPanelRunLoopMode

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_data normalizes NSImage size to the bitmap size because they can disagree.
  • osimage_write special-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 NSBitmapImageRep properties.

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 real WKWebView support.
  • 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

  1. src/osapp/osx/osapp_osx.m for startup, timers, theme notification, and termination.
  2. src/osgui/osx/oswindow.m for window roles, key routing, modal behavior, and the shared field editor.
  3. src/osgui/osx/osglobals.m plus src/osgui/osx/oscolor.m for theme colors, cached native chrome, and cursor/system services.
  4. src/osgui/osx/oscontrol.m, src/osgui/osx/oscombo.m, and src/osgui/osx/ospopup.m for the shared control/text layer.
  5. src/osgui/osx/osview.m for custom view drawing and event routing.
  6. src/draw2d/osx/draw_osx.m, src/osgui/osx/osdrawctrl.m, and src/draw2d/osx/osimage.m for rendering and image handling.
  7. src/osgui/osx/osweb.m plus prj/NAppUtils.cmake for the conditional web story.