// macOS/AppKit F02 demo — resize correctness (../../../features/f02-resize.md).
// A continuously refreshed, corner-anchored gradient survives a three-phase
// programmatic resize storm:
// phase 1: eight setFrame:display:YES content-size changes (incl. odd sizes),
// phase 2: three setContentSize: changes (the content-rect-first resize API),
// phase 3: zoom: (a maximize-like state transition) and zoom: again to restore.
// Every geometry event is logged with BOTH the logical (point) and the physical
// (pixel, via convertRectToBacking:) size plus backingScaleFactor, and the demo
// asserts pixels == points × scale on each one (the Retina buffer-math deliverable).
// viewWillStartLiveResize/viewDidEndLiveResize and viewDidChangeBackingProperties
// are instrumented to prove which callbacks programmatic resizes do NOT trigger.
// Findings: ../../f02-resize.md; recipe and build command: ../../scaffold.md.
//
// Modes (environment variables):
// WSI_AUTO_EXIT=1 bounded run: a ~16 ms NSTimer drives setNeedsDisplay:; the
// storm phases run on the tick schedule above; after ~135
// ticks [NSApp stop:nil] (plus a synthetic
// NSEventTypeApplicationDefined post) -> exit 0 (exit 1 if
// any point/pixel assertion failed).
// (neither) runs until the window closes — no storm, so a human can
// drag the window border and capture the *interactive*
// live-resize sequence (Tier C; see the manual-run queue).
//
// Headless-safe: prints `SKIP:` and exits 0 when there is no window server
// (CGMainDisplayID() == 0), per the research-docs guidelines.
module (module) appapp;
import (package) corecore.(module) core.attributeThis module contains UDA's (User Defined Attributes) either used in
the runtime or special UDA's recognized by compiler.
Attribute Name, Linkage, Description
gnuAbiTag, C++,
Declares an ABI tag on a C++ symbol.
mustuse,,
Ensures that values of a struct or union type are not discarded.
optional, Objective-C,
Makes an Objective-C interface method optional.
selector, Objective-C,
Attaches an Objective-C selector to a method.
standalone,,
Marks a shared module constructor as not depending on any
other module constructor being run first.
weak,,
Specifies that a global symbol should be emitted with weak linkage.
Source
core/attribute.d
attribute : selector;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv, (alias) app.malloc = void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc, (alias) app.free = void core.stdc.stdlib.free(void* ptr) nothrow @nogcfree;
import instrument : instr, instrInit;
import objc.autorelease : autoreleasepool_push, autoreleasepool_pop; // objective-d
// Objective-C runtime selector lookup (objective-d wraps libobjc; declaring
// sel_registerName ourselves would clash with objc.rt's typed declaration).
import objc.rt : SEL;
// --- C-level declarations -----------------------------------------------------
// CoreGraphics C API — linked transitively via the Cocoa umbrella framework.
// CGRect has the same layout as NSRect (4 CGFloat = double on 64-bit), so the
// struct-by-value calls below reuse NSRect. The CF objects (CGColorSpaceRef,
// CGDataProviderRef, CGImageRef, CGContextRef) are opaque pointers.
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
void* void* app.CGColorSpaceCreateDeviceRGB() nothrow @nogcCGColorSpaceCreateDeviceRGB();
void void app.CGColorSpaceRelease(void* space) nothrow @nogcCGColorSpaceRelease(void* (parameter) void* spacespace);
void* void* app.CGDataProviderCreateWithData(void* info, const(void)* data, ulong size, void* releaseCallback) nothrow @nogcCGDataProviderCreateWithData(void* (parameter) void* infoinfo, const(void)* (parameter) const(void)* datadata, (alias) object.size_t = ulongsize_t (parameter) ulong sizesize,
void* (parameter) void* releaseCallbackreleaseCallback);
void void app.CGDataProviderRelease(void* provider) nothrow @nogcCGDataProviderRelease(void* (parameter) void* providerprovider);
void* void* app.CGImageCreate(ulong width, ulong height, ulong bitsPerComponent, ulong bitsPerPixel, ulong bytesPerRow, void* colorSpace, uint bitmapInfo, void* provider, const(double)* decode, bool shouldInterpolate, int intent) nothrow @nogcCGImageCreate((alias) object.size_t = ulongsize_t (parameter) ulong widthwidth, (alias) object.size_t = ulongsize_t (parameter) ulong heightheight, (alias) object.size_t = ulongsize_t (parameter) ulong bitsPerComponentbitsPerComponent,
(alias) object.size_t = ulongsize_t (parameter) ulong bitsPerPixelbitsPerPixel, (alias) object.size_t = ulongsize_t (parameter) ulong bytesPerRowbytesPerRow, void* (parameter) void* colorSpacecolorSpace, uint (parameter) uint bitmapInfobitmapInfo,
void* (parameter) void* providerprovider, const(double)* (parameter) const(double)* decodedecode, bool (parameter) bool shouldInterpolateshouldInterpolate, int (parameter) int intentintent);
void void app.CGImageRelease(void* image) nothrow @nogcCGImageRelease(void* (parameter) void* imageimage);
void void app.CGContextDrawImage(void* ctx, app.NSRect rect, void* image) nothrow @nogcCGContextDrawImage(void* (parameter) void* ctxctx, (struct) app.NSRectNSRect (parameter) app.NSRect rectrect, void* (parameter) void* imageimage);
}
enum uint (constant) uint app.kCGImageAlphaNoneSkipFirst = 6ukCGImageAlphaNoneSkipFirst = 6; // XRGB: alpha byte present, ignored
enum uint (constant) uint app.kCGBitmapByteOrder32Little = 8192ukCGBitmapByteOrder32Little = 2 << 12; // 32-bit little-endian packing
enum int (constant) int app.kCGRenderingIntentDefault = 0kCGRenderingIntentDefault = 0;
// Cocoa geometry (CGFloat == double on 64-bit). Plain C structs, not ObjC objects.
extern (C) struct (struct) app.NSPointNSPoint
{
double (field) double app.NSPoint.xx, (field) double app.NSPoint.yy;
}
extern (C) struct (struct) app.NSSizeNSSize
{
double (field) double app.NSSize.widthwidth, (field) double app.NSSize.heightheight;
}
extern (C) struct (struct) app.NSRectNSRect
{
(struct) app.NSPointNSPoint (field) app.NSPoint app.NSRect.originorigin;
(struct) app.NSSizeNSSize (field) app.NSSize app.NSRect.sizesize;
}
// --- AppKit class declarations (objective-d bundles Foundation, not AppKit) ----
extern (Objective-C):
extern class (class) app.NSObjectNSObject
{
// `alloc` is declared on the leaf classes that need it (with their own return
// type) to avoid an Objective-C covariant-return clash on the base method.
}
extern class (class) app.NSStringNSString : (class) app.NSObjectNSObject
{
static (class) app.NSStringNSString app.NSString app.NSString.alloc()alloc() @selector("alloc");
(class) app.NSStringNSString app.NSString app.NSString.initWithUTF8String(const(char)* s)initWithUTF8String(const(char)* (parameter) const(char)* ss) @selector("initWithUTF8String:");
}
extern class (class) app.NSResponderNSResponder : (class) app.NSObjectNSObject
{
}
extern class (class) app.NSEventNSEvent : (class) app.NSObjectNSObject
{
static (class) app.NSEventNSEvent app.NSEvent app.NSEvent.otherEventWithType(long type, app.NSPoint location, ulong modifierFlags, double timestamp, long windowNumber, app.NSObject context, short subtype, long data1, long data2)otherEventWithType(long (parameter) long typetype, (struct) app.NSPointNSPoint (parameter) app.NSPoint locationlocation, ulong (parameter) ulong modifierFlagsmodifierFlags,
double (parameter) double timestamptimestamp, long (parameter) long windowNumberwindowNumber, (class) app.NSObjectNSObject (parameter) app.NSObject contextcontext, short (parameter) short subtypesubtype,
long (parameter) long data1data1, long (parameter) long data2data2)
@selector("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:");
}
extern class (class) app.NSTimerNSTimer : (class) app.NSObjectNSObject
{
static (class) app.NSTimerNSTimer app.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(double interval, (class) app.NSObjectNSObject target,
SEL sel, (class) app.NSObjectNSObject userInfo, bool repeats)
@selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:");
}
extern class (class) app.NSGraphicsContextNSGraphicsContext : (class) app.NSObjectNSObject
{
static (class) app.NSGraphicsContextNSGraphicsContext app.NSGraphicsContext app.NSGraphicsContext.currentContext()currentContext() @selector("currentContext");
void* void* app.NSGraphicsContext.CGContext()CGContext() @selector("CGContext"); // CGContextRef
}
extern class (class) app.NSApplicationNSApplication : (class) app.NSResponderNSResponder
{
static (class) app.NSApplicationNSApplication app.NSApplication app.NSApplication.sharedApplication()sharedApplication() @selector("sharedApplication");
void void app.NSApplication.setActivationPolicy(long policy)setActivationPolicy(long (parameter) long policypolicy) @selector("setActivationPolicy:");
void void app.NSApplication.activateIgnoringOtherApps(bool flag)activateIgnoringOtherApps(bool (parameter) bool flagflag) @selector("activateIgnoringOtherApps:");
void void app.NSApplication.run()run() @selector("run");
void void app.NSApplication.stop(app.NSObject sender)stop((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("stop:");
void void app.NSApplication.postEvent(app.NSEvent event, bool atStart)postEvent((class) app.NSEventNSEvent (parameter) app.NSEvent eventevent, bool (parameter) bool atStartatStart) @selector("postEvent:atStart:");
void void app.NSApplication.terminate(app.NSObject sender)terminate((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("terminate:");
}
extern class (class) app.NSViewNSView : (class) app.NSResponderNSResponder
{
void void app.NSView.setFrameSize(app.NSSize newSize)setFrameSize((struct) app.NSSizeNSSize (parameter) app.NSSize newSizenewSize) @selector("setFrameSize:");
void void app.NSView.setNeedsDisplay(bool flag)setNeedsDisplay(bool (parameter) bool flagflag) @selector("setNeedsDisplay:");
(struct) app.NSRectNSRect app.NSRect app.NSView.bounds()bounds() @selector("bounds");
(struct) app.NSRectNSRect app.NSRect app.NSView.convertRectToBacking(app.NSRect rect)convertRectToBacking((struct) app.NSRectNSRect (parameter) app.NSRect rectrect) @selector("convertRectToBacking:");
}
extern class (class) app.NSWindowNSWindow : (class) app.NSResponderNSResponder
{
static (class) app.NSWindowNSWindow app.NSWindow app.NSWindow.alloc()alloc() @selector("alloc");
(class) app.NSWindowNSWindow app.NSWindow app.NSWindow.initWithContentRect(app.NSRect contentRect, ulong styleMask, ulong backing, bool defer)initWithContentRect((struct) app.NSRectNSRect (parameter) app.NSRect contentRectcontentRect, ulong (parameter) ulong styleMaskstyleMask,
ulong (parameter) ulong backingbacking, bool (parameter) bool deferdefer) @selector("initWithContentRect:styleMask:backing:defer:");
void void app.NSWindow.setTitle(app.NSString title)setTitle((class) app.NSStringNSString (parameter) app.NSString titletitle) @selector("setTitle:");
void void app.NSWindow.setContentView(app.NSView view)setContentView((class) app.NSViewNSView (parameter) app.NSView viewview) @selector("setContentView:");
void void app.NSWindow.setDelegate(app.NSObject anObject)setDelegate((class) app.NSObjectNSObject (parameter) app.NSObject anObjectanObject) @selector("setDelegate:");
void void app.NSWindow.makeKeyAndOrderFront(app.NSObject sender)makeKeyAndOrderFront((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("makeKeyAndOrderFront:");
void void app.NSWindow.setFrame(app.NSRect frameRect, bool display)setFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameRectframeRect, bool (parameter) bool displaydisplay) @selector("setFrame:display:");
void void app.NSWindow.setContentSize(app.NSSize size)setContentSize((struct) app.NSSizeNSSize (parameter) app.NSSize sizesize) @selector("setContentSize:");
void void app.NSWindow.zoom(app.NSObject sender)zoom((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("zoom:");
bool bool app.NSWindow.isZoomed()isZoomed() @selector("isZoomed");
(struct) app.NSRectNSRect app.NSRect app.NSWindow.frame()frame() @selector("frame");
(struct) app.NSRectNSRect app.NSRect app.NSWindow.frameRectForContentRect(app.NSRect contentRect)frameRectForContentRect((struct) app.NSRectNSRect (parameter) app.NSRect contentRectcontentRect) @selector("frameRectForContentRect:");
double double app.NSWindow.backingScaleFactor()backingScaleFactor() @selector("backingScaleFactor");
}
// --- The custom NSView subclass, defined in D ----------------------------------
// A *non-extern* `extern (Objective-C)` class per the scaffold recipe: bodied
// @selector methods are the subclass implementation; bodyless alloc/initWithFrame
// bind to the inherited implementations. drawRect:/tick:/windowWillClose:/
// windowDidResize:/viewWillStartLiveResize/viewDidEndLiveResize/
// viewDidChangeBackingProperties are dispatched by selector and need no base
// declaration — defining them with the right @selector shadows the NSView/
// delegate defaults.
class (class) app.GradientViewGradientView : (class) app.NSViewNSView
{
static (class) app.GradientViewGradientView app.GradientView app.GradientView.alloc()alloc() @selector("alloc");
(class) app.GradientViewGradientView app.GradientView app.GradientView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
// AppKit's geometry callback to a view: the first call fires synchronously
// inside setContentView: (our "first_configure"); every later call is one
// `resize` event. Must forward to super or the view never resizes.
override void void app.GradientView.setFrameSize(app.NSSize newSize)setFrameSize((struct) app.NSSizeNSSize (parameter) app.NSSize newSizenewSize) @selector("setFrameSize:")
{
super.super.setFrameSizesetFrameSize(newSize);
onViewResized(this, newSize);
}
void void app.GradientView.drawRect(app.NSRect dirtyRect)drawRect((struct) app.NSRectNSRect (parameter) app.NSRect dirtyRectdirtyRect) @selector("drawRect:")
{
onDrawRect(this, this.this.boundsbounds());
}
// Interactive (mouse-drag) resize brackets — instrumented to prove they do
// NOT fire for programmatic setFrame:/setContentSize:/zoom: (F02 finding;
// the interactive path is Tier C, see the manual-run queue).
void void app.GradientView.viewWillStartLiveResize()viewWillStartLiveResize() @selector("viewWillStartLiveResize")
{
instr("live_resize_start");
}
void void app.GradientView.viewDidEndLiveResize()viewDidEndLiveResize() @selector("viewDidEndLiveResize")
{
instr("live_resize_end");
}
// Fires when the backing scale changes (e.g. window moved between a 1x and
// a 2x display) — expected absent in this single-display storm.
void void app.GradientView.viewDidChangeBackingProperties()viewDidChangeBackingProperties() @selector("viewDidChangeBackingProperties")
{
immutable double _error_ scalescale = g_win !is null ? g_win.g_win.backingScaleFactorbackingScaleFactor() : 0.0;
instr("backing_changed", "scale=%.1f", scale);
}
void void app.GradientView.tick(app.NSTimer timer)tick((class) app.NSTimerNSTimer (parameter) app.NSTimer timertimer) @selector("tick:")
{
onTick(this);
}
// NSWindowDelegate notification — ordering vs setFrameSize: is a finding.
void void app.GradientView.windowDidResize(app.NSObject notification)windowDidResize((class) app.NSObjectNSObject (parameter) app.NSObject notificationnotification) @selector("windowDidResize:")
{
immutable _error_ ff = g_win.g_win.frameframe();
instr("window_did_resize", "frame=%dx%d zoomed=%d",
cast(int) f.f.sizesize.f.size.widthwidth, cast(int) f.f.sizesize.f.size.heightheight,
g_win.g_win.isZoomedisZoomed() ? 1 : 0);
}
void void app.GradientView.windowWillClose(app.NSObject notification)windowWillClose((class) app.NSObjectNSObject (parameter) app.NSObject notificationnotification) @selector("windowWillClose:")
{
instr("close_requested");
g_app.g_app.terminateterminate(null);
}
}
extern (D): // back to D linkage for the rest of the module
enum (constant) int app.NSApplicationActivationPolicyRegular = 0NSApplicationActivationPolicyRegular = 0;
enum : ulong
{
(enum value) app.NSWindowStyleMaskTitled = 1LUNSWindowStyleMaskTitled = 1,
(enum value) app.NSWindowStyleMaskClosable = 2LUNSWindowStyleMaskClosable = 2,
(enum value) app.NSWindowStyleMaskResizable = 8LUNSWindowStyleMaskResizable = 8,
}
enum ulong (constant) ulong app.NSBackingStoreBuffered = 2LUNSBackingStoreBuffered = 2;
enum long (constant) long app.NSEventTypeApplicationDefined = 15LNSEventTypeApplicationDefined = 15;
// --- Demo state (single window — module globals keep the ObjC class trivial) ---
__gshared (class) app.NSApplicationNSApplication (__gshared global) app.NSApplication app.g_appg_app;
__gshared (class) app.NSWindowNSWindow (__gshared global) app.NSWindow app.g_wing_win;
__gshared uint* (__gshared global) uint* app.g_bufg_buf; // CPU framebuffer, 0xAARRGGBB (BGRA bytes, LE)
__gshared (alias) object.size_t = ulongsize_t (__gshared global) ulong app.g_bufWg_bufW, (__gshared global) ulong app.g_bufHg_bufH;
__gshared int (__gshared global) int app.g_tickg_tick, (__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_resizesSeeng_resizesSeen, (__gshared global) int app.g_buffersAllocatedg_buffersAllocated, (__gshared global) int app.g_mismatchesg_mismatches;
__gshared bool (__gshared global) bool app.g_autog_auto;
__gshared bool (__gshared global) bool app.g_firstConfigureSeeng_firstConfigureSeen, (__gshared global) bool app.g_stopRequestedg_stopRequested;
// Phase 1 — setFrame:display:YES storm: eight content sizes, including odd
// point sizes (333x217, 415x289) to exercise the points->pixels rounding, ending
// back at the initial size.
immutable int[2][8] (immutable global) immutable(int[2][8]) app.frameStormSizesframeStormSizes = [
[640, 400], [320, 240], [800, 520], [333, 217],
[1024, 640], [415, 289], [560, 352], [480, 320],
];
enum (constant) int app.frameStormStartTick = 30frameStormStartTick = 30;
// Phase 2 — setContentSize: storm (resizes the content rect in place; the
// window's top-left is kept, so the *bottom* edge moves in AppKit's y-up space).
immutable int[2][3] (immutable global) immutable(int[2][3]) app.contentStormSizescontentStormSizes = [[600, 380], [280, 200], [480, 320]];
enum (constant) int app.contentStormStartTick = 60contentStormStartTick = 60;
enum (constant) int app.stormStrideTicks = 3stormStrideTicks = 3;
enum (constant) int app.zoomTick = 75zoomTick = 75; // zoom: #1 — maximize-like transition
enum (constant) int app.unzoomTick = 95unzoomTick = 95; // zoom: #2 — restore the saved user frame
enum (constant) int app.autoExitTick = 135autoExitTick = 135;
// Corner-anchored diagonal gradient: geometry visibly tracks the window size
// (per F02 — stretching or a stale buffer is immediately visible).
void void app.renderGradient(uint* buf, ulong w, ulong h) nothrow @nogcrenderGradient(uint* (parameter) uint* bufbuf, (alias) object.size_t = ulongsize_t (parameter) ulong ww, (alias) object.size_t = ulongsize_t (parameter) ulong hh) nothrow @nogc
{
foreach ((parameter) yy; 0 .. h)
foreach ((parameter) xx; 0 .. w)
{
immutable _error_ rr = cast(uint) (x * 255 / w);
immutable _error_ gg = cast(uint) (y * 255 / h);
immutable _error_ bb = cast(uint) ((x + y) * 255 / (w + h));
buf[y * w + x] = 0xFF00_0000 | (r << 16) | (g << 8) | b;
}
}
// Round a point extent to pixels the way AppKit does (scale is 1.0 or 2.0, so
// this is exact for integer point sizes).
long long app.pixelsFor(double points, double scale) nothrow @nogcpixelsFor(double (parameter) double pointspoints, double (parameter) double scalescale) nothrow @nogc
{
return cast(long) (points * scale + 0.5);
}
void void app.onViewResized(app.GradientView view, app.NSSize size)onViewResized((class) app.GradientViewGradientView (parameter) app.GradientView viewview, (struct) app.NSSizeNSSize (parameter) app.NSSize sizesize)
{
immutable double _error_ scalescale = g_win !is null ? g_win.g_win.backingScaleFactorbackingScaleFactor() : 1.0;
// Physical size as AppKit reports it (convertRectToBacking:) vs the naive
// points × scale math — asserting they agree is the Retina deliverable.
immutable _error_ backingbacking = view.view.convertRectToBackingconvertRectToBacking(NSRect(NSPoint(0, 0), size));
immutable long _error_ pwpw = cast(long) backing.backing.sizesize.backing.size.widthwidth;
immutable long _error_ phph = cast(long) backing.backing.sizesize.backing.size.heightheight;
immutable bool _error_ matchmatch = pw == pixelsFor(size.size.widthwidth, scale)
&& ph == pixelsFor(size.size.heightheight, scale);
if (!match)
++g_mismatches;
if (!g_firstConfigureSeen)
{
g_firstConfigureSeen = true;
instr("first_configure", "points=%dx%d pixels=%lldx%lld scale=%.1f match=%d",
cast(int) size.size.widthwidth, cast(int) size.size.heightheight, pw, ph, scale, match ? 1 : 0);
}
else
{
++g_resizesSeen;
instr("resize", "points=%dx%d pixels=%lldx%lld scale=%.1f match=%d",
cast(int) size.size.widthwidth, cast(int) size.size.heightheight, pw, ph, scale, match ? 1 : 0);
}
}
void void app.onDrawRect(app.GradientView view, app.NSRect bounds)onDrawRect((class) app.GradientViewGradientView (parameter) app.GradientView viewview, (struct) app.NSRectNSRect (parameter) app.NSRect boundsbounds)
{
// Buffer size from convertRectToBacking: — the authoritative points->pixels
// mapping (identical to bounds × backingScaleFactor; asserted in
// onViewResized on every geometry change).
immutable _error_ backingbacking = view.view.convertRectToBackingconvertRectToBacking(bounds);
immutable (unresolved type) size_tsize_t _error_ pwpw = cast((unresolved type) size_tsize_t) backing.backing.sizesize.backing.size.widthwidth;
immutable (unresolved type) size_tsize_t _error_ phph = cast((unresolved type) size_tsize_t) backing.backing.sizesize.backing.size.heightheight;
if (pw == 0 || ph == 0)
return;
// Buffer strategy: per-resize free+malloc, no pooling — reallocation only
// when the pixel size actually changed (an F02 finding; see ../../f02-resize.md).
if (pw != g_bufW || ph != g_bufH)
{
free(g_buf);
g_buf = cast(uint*) malloc(pw * ph * 4);
g_bufW = pw;
g_bufH = ph;
++g_buffersAllocated;
instr("buffer_alloc", "size=%zux%zu bytes=%zu n=%d",
pw, ph, pw * ph * 4, g_buffersAllocated);
}
renderGradient(g_buf, pw, ph);
// Wrap the pixel buffer in a CGImage and draw it into the view's current
// CGContext. The image is pw×ph *pixels* drawn into a points rect — Quartz
// maps points->pixels via the context's CTM, so the blit is 1:1 on screen.
void* _error_ cscs = CGColorSpaceCreateDeviceRGB();
void* _error_ dpdp = CGDataProviderCreateWithData(null, g_buf, pw * ph * 4, null);
void* _error_ imgimg = CGImageCreate(pw, ph, 8, 32, pw * 4, cs,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, dp, null, false,
kCGRenderingIntentDefault);
void* _error_ ctxctx = NSGraphicsContext.NSGraphicsContext.currentContextcurrentContext().NSGraphicsContext.currentContext().CGContextCGContext();
CGContextDrawImage(ctx, bounds, img);
CGImageRelease(img);
CGDataProviderRelease(dp);
CGColorSpaceRelease(cs);
++g_frames;
instr("frame_callback", "t=%d points=%dx%d pixels=%zux%zu", g_frames,
cast(int) bounds.bounds.sizesize.bounds.size.widthwidth, cast(int) bounds.bounds.sizesize.bounds.size.heightheight, pw, ph);
}
void void app.runStormStep(int tick)runStormStep(int (parameter) int ticktick)
{
// Phase 1: setFrame:display:YES — frame rect computed from the content rect.
immutable _error_ f1Endf1End = frameStormStartTick + cast(int) frameStormSizes.length * stormStrideTicks;
if (tick >= frameStormStartTick && tick < f1End
&& (tick - frameStormStartTick) % stormStrideTicks == 0)
{
immutable _error_ sizesize = frameStormSizes[(tick - frameStormStartTick) / stormStrideTicks];
immutable _error_ contentcontent = NSRect(NSPoint(120, 120), NSSize(size[0], size[1]));
instr("step", "name=setFrame_display size=%dx%d", size[0], size[1]);
g_win.g_win.setFramesetFrame(g_win.g_win.frameRectForContentRectframeRectForContentRect(content), true);
return;
}
// Phase 2: setContentSize: — the content-rect-first API, no display flag.
immutable _error_ f2Endf2End = contentStormStartTick + cast(int) contentStormSizes.length * stormStrideTicks;
if (tick >= contentStormStartTick && tick < f2End
&& (tick - contentStormStartTick) % stormStrideTicks == 0)
{
immutable _error_ sizesize = contentStormSizes[(tick - contentStormStartTick) / stormStrideTicks];
instr("step", "name=setContentSize size=%dx%d", size[0], size[1]);
g_win.g_win.setContentSizesetContentSize(NSSize(size[0], size[1]));
return;
}
// Phase 3: zoom: twice — a maximize-like state transition and its restore.
if (tick == zoomTick || tick == unzoomTick)
{
instr("step", "name=zoom pre_zoomed=%d", g_win.g_win.isZoomedisZoomed() ? 1 : 0);
g_win.g_win.zoomzoom(null);
instr("step", "name=zoom_returned zoomed=%d", g_win.g_win.isZoomedisZoomed() ? 1 : 0);
}
}
void void app.onTick(app.GradientView view)onTick((class) app.GradientViewGradientView (parameter) app.GradientView viewview)
{
++g_tick;
view.view.setNeedsDisplaysetNeedsDisplay(true); // continuous refresh; AppKit coalesces dirty marks
if (!g_auto)
return; // interactive mode: no storm — drag the border instead (Tier C)
runStormStep(g_tick);
if (g_tick < autoExitTick || g_stopRequested)
return;
g_stopRequested = true;
instr("step", "name=NSApp_stop tick=%d", g_tick);
g_app.g_app.stopstop(null);
// stop: is only checked after an event is dispatched; without a real event
// in the queue [NSApp run] would keep blocking. Post a synthetic
// application-defined event so the loop wakes up and exits.
(unresolved type) NSEventNSEvent _error_ evev = NSEvent.NSEvent.otherEventWithTypeotherEventWithType(NSEventTypeApplicationDefined,
NSPoint(0, 0), 0, 0, 0, null, 0, 0, 0);
g_app.g_app.postEventpostEvent(ev, true);
}
int int D main()main()
{
g_auto = getenv("WSI_AUTO_EXIT") !is null;
instrInit("APPKIT_F02");
instr("init_start", "auto_exit=%d", g_auto ? 1 : 0);
instr("step", "name=CGMainDisplayID");
if (CGMainDisplayID() == 0)
{
printf("SKIP: no macOS window server (CGMainDisplayID == 0)\n");
return 0;
}
// objective-d's autorelease pool wraps the AppKit setup; [NSApp run] drains
// its own per-event pools.
auto _error_ poolpool = autoreleasepool_push();
scope (exit)
autoreleasepool_pop(pool);
instr("step", "name=NSApplication_sharedApplication");
g_app = NSApplication.NSApplication.sharedApplicationsharedApplication();
instr("step", "name=setActivationPolicy policy=regular");
g_app.g_app.setActivationPolicysetActivationPolicy(NSApplicationActivationPolicyRegular);
immutable _error_ contentRectcontentRect = NSRect(NSPoint(120, 120), NSSize(480, 320));
instr("step", "name=NSWindow_initWithContentRect size=480x320");
g_win = NSWindow.NSWindow.allocalloc().NSWindow.alloc().initWithContentRectinitWithContentRect(contentRect,
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable,
NSBackingStoreBuffered, false);
g_win.g_win.setTitlesetTitle(NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String("wsi-f02"));
instr("window_created", "scale=%.1f", g_win.g_win.backingScaleFactorbackingScaleFactor());
// The D-defined NSView subclass becomes the content view (first
// setFrameSize: -> first_configure fires synchronously inside
// setContentView:) and the window delegate (windowDidResize:/windowWillClose:).
instr("step", "name=GradientView_initWithFrame");
(unresolved type) GradientViewGradientView _error_ viewview = GradientView.GradientView.allocalloc().GradientView.alloc().initWithFrameinitWithFrame(contentRect);
instr("step", "name=setContentView");
g_win.g_win.setContentViewsetContentView(view);
g_win.g_win.setDelegatesetDelegate(view);
instr("step", "name=makeKeyAndOrderFront");
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
g_app.g_app.activateIgnoringOtherAppsactivateIgnoringOtherApps(true);
// A ~16 ms repeating NSTimer drives setNeedsDisplay: and the storm schedule.
instr("step", "name=NSTimer_scheduledTimerWithTimeInterval interval_ms=16");
NSTimer.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(0.016, view, SEL.register("tick:"),
null, true);
instr("step", "name=NSApp_run");
g_app.g_app.runrun();
instr("loop_exit", "frames=%d ticks=%d resizes=%d buffers=%d mismatches=%d",
g_frames, g_tick, g_resizesSeen, g_buffersAllocated, g_mismatches);
free(g_buf);
return g_mismatches == 0 ? 0 : 1;
}