summaryrefslogtreecommitdiff
path: root/new/darwin
diff options
context:
space:
mode:
Diffstat (limited to 'new/darwin')
-rw-r--r--new/darwin/alloc.m44
-rw-r--r--new/darwin/button.m79
-rw-r--r--new/darwin/checkbox.m98
-rw-r--r--new/darwin/entry.m67
-rw-r--r--new/darwin/init.m67
-rw-r--r--new/darwin/label.m53
-rw-r--r--new/darwin/main.m27
-rw-r--r--new/darwin/newcontrol.m236
-rw-r--r--new/darwin/parent.m118
-rw-r--r--new/darwin/tab.m67
-rw-r--r--new/darwin/text.m19
-rw-r--r--new/darwin/util.m20
-rw-r--r--new/darwin/window.m145
13 files changed, 1040 insertions, 0 deletions
diff --git a/new/darwin/alloc.m b/new/darwin/alloc.m
new file mode 100644
index 0000000..8f539af
--- /dev/null
+++ b/new/darwin/alloc.m
@@ -0,0 +1,44 @@
+// 4 december 2014
+#import <stdio.h>
+#import "uipriv_darwin.h"
+
+void *uiAlloc(size_t size, const char *type)
+{
+ void *out;
+
+ out = malloc(size);
+ if (out == NULL) {
+ fprintf(stderr, "memory exhausted in uiAlloc() allocating %s\n", type);
+ abort();
+ }
+ memset(out, 0, size);
+ if (options.debugLogAllocations)
+ fprintf(stderr, "%p alloc %s\n", out, type);
+ return out;
+}
+
+void *uiRealloc(void *p, size_t size, const char *type)
+{
+ void *out;
+
+ if (p == NULL)
+ return uiAlloc(size, type);
+ out = realloc(p, size);
+ if (out == NULL) {
+ fprintf(stderr, "memory exhausted in uiRealloc() reallocating %s\n", type);
+ abort();
+ }
+ // TODO zero the extra memory
+ if (options.debugLogAllocations)
+ fprintf(stderr, "%p realloc %p\n", p, out);
+ return out;
+}
+
+void uiFree(void *p)
+{
+ if (p == NULL)
+ return;
+ free(p);
+ if (options.debugLogAllocations)
+ fprintf(stderr, "%p free\n", p);
+}
diff --git a/new/darwin/button.m b/new/darwin/button.m
new file mode 100644
index 0000000..92e2c76
--- /dev/null
+++ b/new/darwin/button.m
@@ -0,0 +1,79 @@
+// 7 april 2015
+#import "uipriv_darwin.h"
+
+@interface uiNSButton : NSButton
+@property uiControl *uiC;
+@property void (*uiOnClicked)(uiControl *, void *);
+@property void *uiOnClickedData;
+@end
+
+@implementation uiNSButton
+
+- (void)viewDidMoveToSuperview
+{
+ if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) {
+ [self setTarget:nil];
+ self.uiC = NULL;
+ }
+ [super viewDidMoveToSuperview];
+}
+
+- (IBAction)uiButtonClicked:(id)sender
+{
+ (*(self.uiOnClicked))(self.uiC, self.uiOnClickedData);
+}
+
+@end
+
+static void defaultOnClicked(uiControl *c, void *data)
+{
+ // do nothing
+}
+
+uiControl *uiNewButton(const char *text)
+{
+ uiControl *c;
+ uiNSButton *b;
+
+ c = uiDarwinNewControl([uiNSButton class], NO, NO);
+ b = (uiNSButton *) uiControlHandle(c);
+ b.uiC = c;
+
+ [b setTitle:toNSString(text)];
+ [b setButtonType:NSMomentaryPushInButton];
+ [b setBordered:YES];
+ [b setBezelStyle:NSRoundedBezelStyle];
+ setStandardControlFont((NSControl *) b);
+
+ [b setTarget:b];
+ [b setAction:@selector(uiButtonClicked:)];
+
+ b.uiOnClicked = defaultOnClicked;
+
+ return b.uiC;
+}
+
+char *uiButtonText(uiControl *c)
+{
+ uiNSButton *b;
+
+ b = (uiNSButton *) uiControlHandle(c);
+ return uiDarwinNSStringToText([b title]);
+}
+
+void uiButtonSetText(uiControl *c, const char *text)
+{
+ uiNSButton *b;
+
+ b = (uiNSButton *) uiControlHandle(c);
+ [b setTitle:toNSString(text)];
+}
+
+void uiButtonOnClicked(uiControl *c, void (*f)(uiControl *, void *), void *data)
+{
+ uiNSButton *b;
+
+ b = (uiNSButton *) uiControlHandle(c);
+ b.uiOnClicked = f;
+ b.uiOnClickedData = data;
+}
diff --git a/new/darwin/checkbox.m b/new/darwin/checkbox.m
new file mode 100644
index 0000000..ca52389
--- /dev/null
+++ b/new/darwin/checkbox.m
@@ -0,0 +1,98 @@
+// 7 april 2015
+#import "uipriv_darwin.h"
+
+@interface uiCheckboxNSButton : NSButton
+@property uiControl *uiC;
+@property void (*uiOnToggled)(uiControl *, void *);
+@property void *uiOnToggledData;
+@end
+
+@implementation uiCheckboxNSButton
+
+- (void)viewDidMoveToSuperview
+{
+ if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) {
+ [self setTarget:nil];
+ self.uiC = NULL;
+ }
+ [super viewDidMoveToSuperview];
+}
+
+- (IBAction)uiCheckboxToggled:(id)sender
+{
+ (*(self.uiOnToggled))(self.uiC, self.uiOnToggledData);
+}
+
+@end
+
+static void defaultOnToggled(uiControl *c, void *data)
+{
+ // do nothing
+}
+
+uiControl *uiNewCheckbox(const char *text)
+{
+ uiControl *c;
+ uiCheckboxNSButton *cc;
+
+ c = uiDarwinNewControl([uiCheckboxNSButton class], NO, NO);
+ cc = (uiCheckboxNSButton *) uiControlHandle(c);
+ cc.uiC = c;
+
+ [cc setTitle:toNSString(text)];
+ [cc setButtonType:NSSwitchButton];
+ [cc setBordered:NO];
+ setStandardControlFont((NSControl *) cc);
+
+ [cc setTarget:cc];
+ [cc setAction:@selector(uiCheckboxToggled:)];
+
+ cc.uiOnToggled = defaultOnToggled;
+
+ return cc.uiC;
+}
+
+char *uiCheckboxText(uiControl *c)
+{
+ uiCheckboxNSButton *cc;
+
+ cc = (uiCheckboxNSButton *) uiControlHandle(c);
+ return uiDarwinNSStringToText([cc title]);
+}
+
+void uiCheckboxSetText(uiControl *c, const char *text)
+{
+ uiCheckboxNSButton *cc;
+
+ cc = (uiCheckboxNSButton *) uiControlHandle(c);
+ [cc setTitle:toNSString(text)];
+}
+
+void uiCheckboxOnToggled(uiControl *c, void (*f)(uiControl *, void *), void *data)
+{
+ uiCheckboxNSButton *cc;
+
+ cc = (uiCheckboxNSButton *) uiControlHandle(c);
+ cc.uiOnToggled = f;
+ cc.uiOnToggledData = data;
+}
+
+int uiCheckboxChecked(uiControl *c)
+{
+ uiCheckboxNSButton *cc;
+
+ cc = (uiCheckboxNSButton *) uiControlHandle(c);
+ return [cc state] == NSOnState;
+}
+
+void uiCheckboxSetChecked(uiControl *c, int checked)
+{
+ uiCheckboxNSButton *cc;
+ NSInteger state;
+
+ cc = (uiCheckboxNSButton *) uiControlHandle(c);
+ state = NSOnState;
+ if (!checked)
+ state = NSOffState;
+ [cc setState:state];
+}
diff --git a/new/darwin/entry.m b/new/darwin/entry.m
new file mode 100644
index 0000000..f2b208b
--- /dev/null
+++ b/new/darwin/entry.m
@@ -0,0 +1,67 @@
+// 9 april 2015
+#import "uipriv_darwin.h"
+
+@interface uiNSTextField : NSTextField
+@property uiControl *uiC;
+@end
+
+@implementation uiNSTextField
+
+- (void)viewDidMoveToSuperview
+{
+ if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) {
+ [self setTarget:nil];
+ self.uiC = NULL;
+ }
+ [super viewDidMoveToSuperview];
+}
+
+@end
+
+// TOOD move elsewhere
+// these are based on interface builder defaults; my comments in the old code weren't very good so I don't really know what talked about what, sorry :/
+void finishNewTextField(NSTextField *t, BOOL isEntry)
+{
+ setStandardControlFont((id) t);
+
+ // THE ORDER OF THESE CALLS IS IMPORTANT; CHANGE IT AND THE BORDERS WILL DISAPPEAR
+ [t setBordered:NO];
+ [t setBezelStyle:NSTextFieldSquareBezel];
+ [t setBezeled:isEntry];
+
+ // we don't need to worry about substitutions/autocorrect here; see window_darwin.m for details
+
+ [[t cell] setLineBreakMode:NSLineBreakByClipping];
+ [[t cell] setScrollable:YES];
+}
+
+uiControl *uiNewEntry(void)
+{
+ uiControl *c;
+ uiNSTextField *t;
+
+ c = uiDarwinNewControl([uiNSTextField class], NO, NO);
+ t = (uiNSTextField *) uiControlHandle(c);
+ t.uiC = c;
+
+ [t setSelectable:YES]; // otherwise the setting is masked by the editable default of YES
+ finishNewTextField((NSTextField *) t, YES);
+
+ return t.uiC;
+}
+
+char *uiEntryText(uiControl *c)
+{
+ uiNSTextField *t;
+
+ t = (uiNSTextField *) uiControlHandle(c);
+ return uiDarwinNSStringToText([t stringValue]);
+}
+
+void uiEntrySetText(uiControl *c, const char *text)
+{
+ uiNSTextField *t;
+
+ t = (uiNSTextField *) uiControlHandle(c);
+ [t setStringValue:toNSString(text)];
+}
diff --git a/new/darwin/init.m b/new/darwin/init.m
new file mode 100644
index 0000000..4f14a0a
--- /dev/null
+++ b/new/darwin/init.m
@@ -0,0 +1,67 @@
+// 6 april 2015
+#import "uipriv_darwin.h"
+
+@interface uiApplication : NSApplication
+@end
+
+@implementation uiApplication
+
+// hey look! we're overriding terminate:!
+// we're going to make sure we can go back to main() whether Cocoa likes it or not!
+// and just how are we going to do that, hm?
+// (note: this is called after applicationShouldTerminate:)
+- (void)terminate:(id)sender
+{
+ // yes that's right folks: DO ABSOLUTELY NOTHING.
+ // the magic is [NSApp run] will just... stop.
+
+ // for debugging
+ NSLog(@"in terminate:");
+}
+
+@end
+
+@interface uiAppDelegate : NSObject <NSApplicationDelegate>
+@end
+
+@implementation uiAppDelegate
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
+{
+ // for debugging
+ NSLog(@"in applicationShouldTerminate:");
+ return NSTerminateNow;
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
+{
+ return NO;
+}
+
+@end
+
+// we are not in control of the actual lifetimes and refcounts of NSViews (see http://stackoverflow.com/a/29523141/3408572)
+// when we're done with a view, it'll be added as a subview of this one, and this one will be released on application shutdown
+// we need this separate view because it's possible for controls to have no parent but still be alive
+NSView *destroyedControlsView;
+
+uiInitOptions options;
+
+const char *uiInit(uiInitOptions *o)
+{
+ options = *o;
+ [uiApplication sharedApplication];
+ // don't check for a NO return; something (launch services?) causes running from application bundles to always return NO when asking to change activation policy, even if the change is to the same activation policy!
+ // see https://github.com/andlabs/ui/issues/6
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+ [NSApp setDelegate:[uiAppDelegate new]];
+
+ // we can use a stock NSView for this
+ destroyedControlsView = [[NSView alloc] initWithFrame:NSZeroRect];
+
+ return NULL;
+}
+
+void uiFreeInitError(const char *err)
+{
+}
diff --git a/new/darwin/label.m b/new/darwin/label.m
new file mode 100644
index 0000000..e3fb351
--- /dev/null
+++ b/new/darwin/label.m
@@ -0,0 +1,53 @@
+// 9 april 2015
+#import "uipriv_darwin.h"
+
+@interface uiLabelNSTextField : NSTextField
+@property uiControl *uiC;
+@end
+
+@implementation uiLabelNSTextField
+
+- (void)viewDidMoveToSuperview
+{
+ if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview])) {
+ [self setTarget:nil];
+ self.uiC = NULL;
+ }
+ [super viewDidMoveToSuperview];
+}
+
+@end
+
+uiControl *uiNewLabel(const char *text)
+{
+ uiControl *c;
+ uiLabelNSTextField *l;
+
+ c = uiDarwinNewControl([uiLabelNSTextField class], NO, NO);
+ l = (uiLabelNSTextField *) uiControlHandle(c);
+ l.uiC = c;
+
+ [l setStringValue:toNSString(text)];
+ [l setEditable:NO];
+ [l setSelectable:NO];
+ [l setDrawsBackground:NO];
+ finishNewTextField((NSTextField *) l, NO);
+
+ return l.uiC;
+}
+
+char *uiLabelText(uiControl *c)
+{
+ uiLabelNSTextField *t;
+
+ t = (uiLabelNSTextField *) uiControlHandle(c);
+ return uiDarwinNSStringToText([t stringValue]);
+}
+
+void uiLabelSetText(uiControl *c, const char *text)
+{
+ uiLabelNSTextField *t;
+
+ t = (uiLabelNSTextField *) uiControlHandle(c);
+ [t setStringValue:toNSString(text)];
+}
diff --git a/new/darwin/main.m b/new/darwin/main.m
new file mode 100644
index 0000000..8663b58
--- /dev/null
+++ b/new/darwin/main.m
@@ -0,0 +1,27 @@
+// 6 april 2015
+#import "uipriv_darwin.h"
+
+// #qo LDFLAGS: -lobjc -framework Foundation -framework AppKit
+
+void uiMain(void)
+{
+ [NSApp run];
+}
+
+void uiQuit(void)
+{
+ NSEvent *e;
+
+ [NSApp stop:NSApp];
+ // stop: won't register until another event has passed; let's synthesize one
+ e = [NSEvent otherEventWithType:NSApplicationDefined
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:[[NSProcessInfo processInfo] systemUptime]
+ windowNumber:0
+ context:[NSGraphicsContext currentContext]
+ subtype:0
+ data1:0
+ data2:0];
+ [NSApp postEvent:e atStart:NO]; // let pending events take priority (this is what PostQuitMessage() on Windows does so we have to do it here too for parity; thanks to mikeash in irc.freenode.net/#macdev for confirming that this parameter should indeed be NO)
+}
diff --git a/new/darwin/newcontrol.m b/new/darwin/newcontrol.m
new file mode 100644
index 0000000..9b7c6c4
--- /dev/null
+++ b/new/darwin/newcontrol.m
@@ -0,0 +1,236 @@
+// 7 april 2015
+#include "uipriv_darwin.h"
+
+typedef struct singleView singleView;
+
+struct singleView {
+ NSView *view;
+ NSScrollView *scrollView;
+ NSView *immediate; // the control that is added to the parent container; either view or scrollView
+ uiParent *parent;
+ BOOL userHid;
+ BOOL containerHid;
+ BOOL userDisabled;
+ BOOL containerDisabled;
+};
+
+static void singleDestroy(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ [destroyedControlsView addSubview:s->immediate];
+}
+
+static uintptr_t singleHandle(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ return (uintptr_t) (s->view);
+}
+
+static void singleSetParent(uiControl *c, uiParent *parent)
+{
+ singleView *s = (singleView *) (c->internal);
+ NSView *parentView;
+ uiParent *oldparent;
+
+ oldparent = s->parent;
+ s->parent = parent;
+ if (oldparent != NULL) {
+ [s->immediate removeFromSuperview];
+ uiParentUpdate(oldparent);
+ }
+ if (s->parent != NULL) {
+ // TODO uiControlView(), uiParentView()
+ parentView = (NSView *) uiParentHandle(s->parent);
+ [parentView addSubview:s->immediate];
+ uiParentUpdate(s->parent);
+ }
+}
+
+// also good for NSBox and NSProgressIndicator
+static void singlePreferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height)
+{
+ singleView *s = (singleView *) (c->internal);
+ NSControl *control;
+ NSRect r;
+
+ control = (NSControl *) (s->view);
+ [control sizeToFit];
+ // use alignmentRect here instead of frame because we'll be resizing based on that
+ r = [control alignmentRectForFrame:[control frame]];
+ *width = (intmax_t) r.size.width;
+ *height = (intmax_t) r.size.height;
+}
+
+static void singleResize(uiControl *c, intmax_t x, intmax_t y, intmax_t width, intmax_t height, uiSizing *d)
+{
+ singleView *s = (singleView *) (c->internal);
+ NSRect frame;
+
+ frame.origin.x = x;
+ // mac os x coordinate system has (0,0) in the lower-left
+ frame.origin.y = ([[s->immediate superview] bounds].size.height - height) - y;
+ frame.size.width = width;
+ frame.size.height = height;
+ frame = [s->immediate frameForAlignmentRect:frame];
+ [s->immediate setFrame:frame];
+}
+
+static int singleVisible(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ if (s->userHid)
+ return 0;
+ return 1;
+}
+
+static void singleShow(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->userHid = NO;
+ if (!s->containerHid) {
+ [s->immediate setHidden:NO];
+ if (s->parent != NULL)
+ uiParentUpdate(s->parent);
+ }
+}
+
+static void singleHide(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->userHid = YES;
+ [s->immediate setHidden:YES];
+ if (s->parent != NULL)
+ uiParentUpdate(s->parent);
+}
+
+static void singleContainerShow(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->containerHid = NO;
+ if (!s->userHid) {
+ [s->immediate setHidden:NO];
+ if (s->parent != NULL)
+ uiParentUpdate(s->parent);
+ }
+}
+
+static void singleContainerHide(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->containerHid = YES;
+ [s->immediate setHidden:YES];
+ if (s->parent != NULL)
+ uiParentUpdate(s->parent);
+}
+
+static void enable(singleView *s)
+{
+ if ([s->view respondsToSelector:@selector(setEnabled:)])
+ [((NSControl *) (s->view)) setEnabled:YES];
+}
+
+static void disable(singleView *s)
+{
+ if ([s->view respondsToSelector:@selector(setEnabled:)])
+ [((NSControl *) (s->view)) setEnabled:NO];
+}
+
+static void singleEnable(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->userDisabled = NO;
+ if (!s->containerDisabled)
+ enable(s);
+}
+
+static void singleDisable(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->userDisabled = YES;
+ disable(s);
+}
+
+static void singleContainerEnable(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->containerDisabled = NO;
+ if (!s->userDisabled)
+ enable(s);
+}
+
+static void singleContainerDisable(uiControl *c)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ s->containerDisabled = YES;
+ disable(s);
+}
+
+uiControl *uiDarwinNewControl(Class class, BOOL inScrollView, BOOL scrollViewHasBorder)
+{
+ uiControl *c;
+ singleView *s;
+
+ s = uiNew(singleView);
+ // thanks to autoxr and arwyn in irc.freenode.net/#macdev
+ s->view = (NSView *) [[class alloc] initWithFrame:NSZeroRect];
+ s->immediate = s->view;
+
+ if (inScrollView) {
+ s->scrollView = [[NSScrollView alloc] initWithFrame:NSZeroRect];
+ [s->scrollView setDocumentView:s->view];
+ [s->scrollView setHasHorizontalScroller:YES];
+ [s->scrollView setHasVerticalScroller:YES];
+ [s->scrollView setAutohidesScrollers:YES];
+ if (scrollViewHasBorder)
+ [s->scrollView setBorderType:NSBezelBorder];
+ else
+ [s->scrollView setBorderType:NSNoBorder];
+ s->immediate = (NSView *) (s->scrollView);
+ }
+
+ // and keep a reference to s->immediate for when we remove the control from its parent
+ [s->immediate retain];
+
+ c = uiNew(uiControl);
+ c->internal = s;
+ c->destroy = singleDestroy;
+ c->handle = singleHandle;
+ c->setParent = singleSetParent;
+ c->preferredSize = singlePreferredSize;
+ c->resize = singleResize;
+ c->visible = singleVisible;
+ c->show = singleShow;
+ c->hide = singleHide;
+ c->containerShow = singleContainerShow;
+ c->containerHide = singleContainerHide;
+ c->enable = singleEnable;
+ c->disable = singleDisable;
+ c->containerEnable = singleContainerEnable;
+ c->containerDisable = singleContainerDisable;
+
+ return c;
+}
+
+BOOL uiDarwinControlFreeWhenAppropriate(uiControl *c, NSView *newSuperview)
+{
+ singleView *s = (singleView *) (c->internal);
+
+ if (newSuperview == destroyedControlsView) {
+ [s->immediate release]; // we don't need the reference anymore
+ uiFree(s);
+ uiFree(c);
+ return YES;
+ }
+ return NO;
+}
diff --git a/new/darwin/parent.m b/new/darwin/parent.m
new file mode 100644
index 0000000..abf0e59
--- /dev/null
+++ b/new/darwin/parent.m
@@ -0,0 +1,118 @@
+// 4 august 2014
+#import "uipriv_darwin.h"
+
+// calling -[className] on the content views of NSWindow, NSTabItem, and NSBox all return NSView, so I'm assuming I just need to override these
+// fornunately:
+// - NSWindow resizing calls -[setFrameSize:] (but not -[setFrame:])
+// - NSTabView resizing calls both -[setFrame:] and -[setFrameSIze:] on the current tab
+// - NSTabView switching tabs calls both -[setFrame:] and -[setFrameSize:] on the new tab
+// so we just override setFrameSize:
+// thanks to mikeash and JtRip in irc.freenode.net/#macdev
+@interface uipParent : NSView {
+// TODO
+@public
+ uiControl *child;
+ intmax_t marginLeft;
+ intmax_t marginTop;
+ intmax_t marginRight;
+ intmax_t marginBottom;
+}
+- (void)uiUpdateNow;
+@end
+
+@implementation uipParent
+
+uiLogObjCClassAllocations
+
+- (void)viewDidMoveToSuperview
+{
+ // we can't just use nil because NSTabView will set page views to nil when they're tabbed away
+ // this means that we have to explicitly move them to the destroyed controls view when we're done with them, and likewise in NSWindow
+ if ([self superview] == destroyedControlsView)
+ if (self->child != NULL) {
+ uiControlDestroy(self->child);
+ self->child = NULL;
+ [self release];
+ }
+ [super viewDidMoveToSuperview];
+}
+
+- (void)setFrameSize:(NSSize)s
+{
+ [super setFrameSize:s];
+ [self uiUpdateNow];
+}
+
+// These are based on measurements from Interface Builder.
+// These seem to be based on Auto Layout constants, but I don't see an API that exposes these...
+// This one is 8 for most pairs of controls that I've tried; the only difference is between two pushbuttons, where it's 12...
+#define macXPadding 8
+// Likewise, this one appears to be 12 for pairs of push buttons...
+#define macYPadding 8
+
+- (void)uiUpdateNow
+{
+ uiSizing d;
+ intmax_t x, y, width, height;
+
+ if (self->child == NULL)
+ return;
+ x = [self bounds].origin.x + self->marginLeft;
+ y = [self bounds].origin.y + self->marginTop;
+ width = [self bounds].size.width - (self->marginLeft + self->marginRight);
+ height = [self bounds].size.height - (self->marginTop + self->marginBottom);
+ d.xPadding = macXPadding;
+ d.yPadding = macYPadding;
+ uiControlResize(self->child, x, y, width, height, &d);
+}
+
+@end
+
+static uintptr_t parentHandle(uiParent *p)
+{
+ uipParent *pp = (uipParent *) (p->Internal);
+
+ return (uintptr_t) pp;
+}
+
+static void parentSetChild(uiParent *p, uiControl *child)
+{
+ uipParent *pp = (uipParent *) (p->Internal);
+
+ pp->child = child;
+ if (pp->child != NULL)
+ uiControlSetParent(child, p);
+}
+
+static void parentSetMargins(uiParent *p, intmax_t left, intmax_t top, intmax_t right, intmax_t bottom)
+{
+ uipParent *pp = (uipParent *) (p->Internal);
+
+ pp->marginLeft = left;
+ pp->marginTop = top;
+ pp->marginRight = right;
+ pp->marginBottom = bottom;
+}
+
+static void parentUpdate(uiParent *p)
+{
+ uipParent *pp = (uipParent *) (p->Internal);
+
+ [pp uiUpdateNow];
+}
+
+uiParent *uiNewParent(uintptr_t osParent)
+{
+ uiParent *p;
+
+ p = uiNew(uiParent);
+ p->Internal = [[uipParent alloc] initWithFrame:NSZeroRect];
+ p->Handle = parentHandle;
+ p->SetChild = parentSetChild;
+ p->SetMargins = parentSetMargins;
+ p->Update = parentUpdate;
+ // don't use osParent; we'll need to call specific selectors to set the parent view
+ // and keep the view alive so we can release it properly later
+ [((uipParent *) (p->Internal)) retain];
+ return p;
+} \ No newline at end of file
diff --git a/new/darwin/tab.m b/new/darwin/tab.m
new file mode 100644
index 0000000..c36181e
--- /dev/null
+++ b/new/darwin/tab.m
@@ -0,0 +1,67 @@
+// 12 april 2015
+#import "uipriv_darwin.h"
+
+// TODO
+// - verify margins against extra space around the tab
+// - free child containers properly
+
+@interface uiNSTabView : NSTabView
+@property uiControl *uiC;
+@end
+
+@implementation uiNSTabView
+
+- (void)viewDidMoveToSuperview
+{
+ // TODO free all tabs explicitly
+ if (uiDarwinControlFreeWhenAppropriate(self.uiC, [self superview]))
+ self.uiC = NULL;
+ [super viewDidMoveToSuperview];
+}
+
+@end
+
+// the default new control implementation uses -sizeToFit, which we don't have with NSTabView
+// fortunately, we do have -minimumSize
+static void preferredSize(uiControl *c, uiSizing *d, intmax_t *width, intmax_t *height)
+{
+ uiNSTabView *tv;
+ NSSize s;
+
+ tv = (uiNSTabView *) uiControlHandle(c);
+ s = [tv minimumSize];
+ *width = (intmax_t) (s.width);
+ *height = (intmax_t) (s.height);
+}
+
+uiControl *uiNewTab(void)
+{
+ uiControl *c;
+ uiNSTabView *t;
+
+ c = uiDarwinNewControl([uiNSTabView class], NO, NO);
+ c->preferredSize = preferredSize;
+ t = (uiNSTabView *) uiControlHandle(c);
+ t.uiC = c;
+
+ // also good for NSTabView (same selector and everything)
+ setStandardControlFont((NSControl *) t);
+
+ return c;
+}
+
+void uiTabAddPage(uiControl *c, const char *name, uiControl *child)
+{
+ uiNSTabView *tv;
+ uiParent *content;
+ NSTabViewItem *i;
+
+ content = uiNewParent(0);
+ uiParentSetChild(content, child);
+
+ i = [[NSTabViewItem alloc] initWithIdentifier:nil];
+ [i setLabel:toNSString(name)];
+ [i setView:((NSView *) uiParentHandle(content))];
+ tv = (uiNSTabView *) uiControlHandle(c);
+ [tv addTabViewItem:i];
+}
diff --git a/new/darwin/text.m b/new/darwin/text.m
new file mode 100644
index 0000000..f0d3dab
--- /dev/null
+++ b/new/darwin/text.m
@@ -0,0 +1,19 @@
+// 10 april 2015
+#import "uipriv_darwin.h"
+
+char *uiDarwinNSStringToText(NSString *s)
+{
+ char *out;
+
+ out = strdup([s UTF8String]);
+ if (out == NULL) {
+ fprintf(stderr, "memory exhausted in uiDarwinNSStringToText()\n");
+ abort();
+ }
+ return out;
+}
+
+void uiFreeText(char *s)
+{
+ free(s);
+}
diff --git a/new/darwin/util.m b/new/darwin/util.m
new file mode 100644
index 0000000..906a0ea
--- /dev/null
+++ b/new/darwin/util.m
@@ -0,0 +1,20 @@
+// 7 april 2015
+#import "uipriv_darwin.h"
+
+// also fine for NSCells and NSTexts (NSTextViews)
+void setStandardControlFont(NSControl *control)
+{
+ [control setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
+}
+
+void disableAutocorrect(NSTextView *tv)
+{
+ [tv setEnabledTextCheckingTypes:0];
+ [tv setAutomaticDashSubstitutionEnabled:NO];
+ // don't worry about automatic data detection; it won't change stringValue (thanks pretty_function in irc.freenode.net/#macdev)
+ [tv setAutomaticSpellingCorrectionEnabled:NO];
+ [tv setAutomaticTextReplacementEnabled:NO];
+ [tv setAutomaticQuoteSubstitutionEnabled:NO];
+ [tv setAutomaticLinkDetectionEnabled:NO];
+ [tv setSmartInsertDeleteEnabled:NO];
+}
diff --git a/new/darwin/window.m b/new/darwin/window.m
new file mode 100644
index 0000000..b0cab11
--- /dev/null
+++ b/new/darwin/window.m
@@ -0,0 +1,145 @@
+// 6 april 2015
+#import "uipriv_darwin.h"
+
+// TODO
+// - free chilld containers properly
+
+@interface uiWindowDelegate : NSObject <NSWindowDelegate>
+@property (assign) NSWindow *w;
+@property uiParent *content;
+@property int (*onClosing)(uiWindow *, void *);
+@property void *onClosingData;
+@property uiWindow *uiw;
+@end
+
+@implementation uiWindowDelegate
+
+uiLogObjCClassAllocations
+
+- (BOOL)windowShouldClose:(id)win
+{
+ // return exact constants to be safe
+ if ((*(self.onClosing))(self.uiw, self.onClosingData))
+ return YES;
+ return NO;
+}
+
+// after this method returns we assume the window will be released (see below), so we can go too
+- (void)windowWillClose:(NSNotification *)note
+{
+ [self.w setDelegate:nil]; // see http://stackoverflow.com/a/29523141/3408572
+
+ // when we reach this point, we need to ensure that all the window's children are destroyed (for OS parity)
+ // because we need to set the content view's superview to the destroyed controls view to trigger deletion, we need to do this manually
+ // first, replace the current content view...
+ [self.w setContentView:[[NSView alloc] initWithFrame:NSZeroRect]];
+ // ...then, trigger the deletion
+ [destroyedControlsView addSubview:((NSView *) uiParentHandle(self.content))];
+
+ uiFree(self.uiw);
+ [self release];
+}
+
+@end
+
+struct uiWindow {
+ uiWindowDelegate *d;
+ int margined;
+};
+
+static int defaultOnClosing(uiWindow *w, void *data)
+{
+ return 1;
+}
+
+uiWindow *uiNewWindow(char *title, int width, int height)
+{
+ uiWindowDelegate *d;
+
+ d = [uiWindowDelegate new];
+
+ d.w = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height)
+ styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [d.w setTitle:toNSString(title)];
+
+ // we do not want substitutions
+ // text fields, labels, etc. take their smart quotes and other autocorrect settings from their parent window, which provides a shared "field editor"
+ // so we have to turn them off here
+ // thanks akempgen in irc.freenode.net/#macdev
+ // for some reason, this selector returns NSText but is documented to return NSTextView...
+ // NOTE: if you disagree with me about disabling substitutions, start a github issue with why and I'll be happy to consider it
+ disableAutocorrect((NSTextView *) [d.w fieldEditor:YES forObject:nil]);
+
+ // this is what will destroy the window on close
+ [d.w setReleasedWhenClosed:YES];
+
+ d.content = uiNewParent(0);
+ [d.w setContentView:((NSView *) uiParentHandle(d.content))];
+
+ d.onClosing = defaultOnClosing;
+ [d.w setDelegate:d];
+
+ d.uiw = uiNew(uiWindow);
+ d.uiw->d = d;
+ return d.uiw;
+}
+
+#define D w->d
+
+void uiWindowDestroy(uiWindow *w)
+{
+ [D.w close];
+}
+
+uintptr_t uiWindowHandle(uiWindow *w)
+{
+ return (uintptr_t) (D.w);
+}
+
+char *uiWindowTitle(uiWindow *w)
+{
+ return uiDarwinNSStringToText([D.w title]);
+}
+
+void uiWindowSetTitle(uiWindow *w, const char *title)
+{
+ [D.w setTitle:toNSString(title)];
+}
+
+void uiWindowShow(uiWindow *w)
+{
+ [D.w makeKeyAndOrderFront:D.w];
+}
+
+void uiWindowHide(uiWindow *w)
+{
+ [D.w orderOut:D.w];
+}
+
+void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
+{
+ D.onClosing = f;
+ D.onClosingData = data;
+}
+
+void uiWindowSetChild(uiWindow *w, uiControl *c)
+{
+ uiParentSetChild(D.content, c);
+}
+
+int uiWindowMargined(uiWindow *w)
+{
+ return w->margined;
+}
+
+void uiWindowSetMargined(uiWindow *w, int margined)
+{
+ w->margined = margined;
+ if (w->margined)
+ uiParentSetMargins(D.content, macXMargin, macYMargin, macXMargin, macYMargin);
+ else
+ uiParentSetMargins(D.content, 0, 0, 0, 0);
+ uiParentUpdate(D.content);
+}