aboutsummaryrefslogtreecommitdiff
path: root/src/libui_sdl/libui/darwin
diff options
context:
space:
mode:
authorStapleButter <thetotalworm@gmail.com>2017-09-09 02:30:51 +0200
committerStapleButter <thetotalworm@gmail.com>2017-09-09 02:30:51 +0200
commit70e4841d311d68689724768157cc9cbfbde7a9fc (patch)
treeba9499f77d1258530a7e60aa6e1732c41d98161c /src/libui_sdl/libui/darwin
parent81747d6c34eb159481a6ca3f283d065fa3568617 (diff)
another UI attempt, I guess.
sorry.
Diffstat (limited to 'src/libui_sdl/libui/darwin')
-rw-r--r--src/libui_sdl/libui/darwin/CMakeLists.txt79
-rw-r--r--src/libui_sdl/libui/darwin/alloc.m89
-rw-r--r--src/libui_sdl/libui/darwin/area.m475
-rw-r--r--src/libui_sdl/libui/darwin/areaevents.m159
-rw-r--r--src/libui_sdl/libui/darwin/autolayout.m161
-rw-r--r--src/libui_sdl/libui/darwin/box.m469
-rw-r--r--src/libui_sdl/libui/darwin/button.m113
-rw-r--r--src/libui_sdl/libui/darwin/checkbox.m129
-rw-r--r--src/libui_sdl/libui/darwin/colorbutton.m159
-rw-r--r--src/libui_sdl/libui/darwin/combobox.m145
-rw-r--r--src/libui_sdl/libui/darwin/control.m84
-rw-r--r--src/libui_sdl/libui/darwin/datetimepicker.m42
-rw-r--r--src/libui_sdl/libui/darwin/debug.m19
-rw-r--r--src/libui_sdl/libui/darwin/draw.m454
-rw-r--r--src/libui_sdl/libui/darwin/drawtext.m655
-rw-r--r--src/libui_sdl/libui/darwin/editablecombo.m185
-rw-r--r--src/libui_sdl/libui/darwin/entry.m251
-rw-r--r--src/libui_sdl/libui/darwin/fontbutton.m218
-rw-r--r--src/libui_sdl/libui/darwin/form.m561
-rw-r--r--src/libui_sdl/libui/darwin/grid.m800
-rw-r--r--src/libui_sdl/libui/darwin/group.m194
-rw-r--r--src/libui_sdl/libui/darwin/image.m82
-rw-r--r--src/libui_sdl/libui/darwin/label.m43
-rw-r--r--src/libui_sdl/libui/darwin/main.m239
-rw-r--r--src/libui_sdl/libui/darwin/map.m59
-rw-r--r--src/libui_sdl/libui/darwin/menu.m368
-rw-r--r--src/libui_sdl/libui/darwin/multilineentry.m233
-rw-r--r--src/libui_sdl/libui/darwin/progressbar.m78
-rw-r--r--src/libui_sdl/libui/darwin/radiobuttons.m207
-rw-r--r--src/libui_sdl/libui/darwin/scrollview.m61
-rw-r--r--src/libui_sdl/libui/darwin/separator.m45
-rw-r--r--src/libui_sdl/libui/darwin/slider.m147
-rw-r--r--src/libui_sdl/libui/darwin/spinbox.m214
-rw-r--r--src/libui_sdl/libui/darwin/stddialogs.m123
-rw-r--r--src/libui_sdl/libui/darwin/tab.m292
-rw-r--r--src/libui_sdl/libui/darwin/text.m19
-rw-r--r--src/libui_sdl/libui/darwin/uipriv_darwin.h146
-rw-r--r--src/libui_sdl/libui/darwin/util.m15
-rw-r--r--src/libui_sdl/libui/darwin/window.m407
-rw-r--r--src/libui_sdl/libui/darwin/winmoveresize.m253
40 files changed, 8472 insertions, 0 deletions
diff --git a/src/libui_sdl/libui/darwin/CMakeLists.txt b/src/libui_sdl/libui/darwin/CMakeLists.txt
new file mode 100644
index 0000000..dbef5d4
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/CMakeLists.txt
@@ -0,0 +1,79 @@
+# 3 june 2016
+
+list(APPEND _LIBUI_SOURCES
+ darwin/alloc.m
+ darwin/area.m
+ darwin/areaevents.m
+ darwin/autolayout.m
+ darwin/box.m
+ darwin/button.m
+ darwin/checkbox.m
+ darwin/colorbutton.m
+ darwin/combobox.m
+ darwin/control.m
+ darwin/datetimepicker.m
+ darwin/debug.m
+ darwin/draw.m
+ darwin/drawtext.m
+ darwin/editablecombo.m
+ darwin/entry.m
+ darwin/fontbutton.m
+ darwin/form.m
+ darwin/grid.m
+ darwin/group.m
+ darwin/image.m
+ darwin/label.m
+ darwin/main.m
+ darwin/map.m
+ darwin/menu.m
+ darwin/multilineentry.m
+ darwin/progressbar.m
+ darwin/radiobuttons.m
+ darwin/scrollview.m
+ darwin/separator.m
+ darwin/slider.m
+ darwin/spinbox.m
+ darwin/stddialogs.m
+ darwin/tab.m
+ darwin/text.m
+ darwin/util.m
+ darwin/window.m
+ darwin/winmoveresize.m
+)
+set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE)
+
+list(APPEND _LIBUI_INCLUDEDIRS
+ darwin
+)
+set(_LIBUI_INCLUDEDIRS _LIBUI_INCLUDEDIRS PARENT_SCOPE)
+
+set(_LIBUINAME libui PARENT_SCOPE)
+if(NOT BUILD_SHARED_LIBS)
+ set(_LIBUINAME libui-temporary PARENT_SCOPE)
+endif()
+# thanks to Mr-Hide in irc.freenode.net/#cmake
+macro(_handle_static)
+ set_target_properties(${_LIBUINAME} PROPERTIES
+ ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
+ set(_aname $<TARGET_FILE:${_LIBUINAME}>)
+ set(_lname libui-combined.list)
+ set(_oname libui-combined.o)
+ add_custom_command(
+ OUTPUT ${_oname}
+ COMMAND
+ nm -m ${_aname} | sed -E -n "'s/^[0-9a-f]* \\([A-Z_]+,[a-z_]+\\) external //p'" > ${_lname}
+ COMMAND
+ ld -exported_symbols_list ${_lname} -r -all_load ${_aname} -o ${_oname}
+ COMMENT "Removing hidden symbols")
+ add_library(libui STATIC ${_oname})
+ # otherwise cmake won't know which linker to use
+ set_target_properties(libui PROPERTIES
+ LINKER_LANGUAGE C)
+ set(_aname)
+ set(_lname)
+ set(_oname)
+endmacro()
+
+set(_LIBUI_LIBS
+ objc "-framework Foundation" "-framework AppKit"
+PARENT_SCOPE)
diff --git a/src/libui_sdl/libui/darwin/alloc.m b/src/libui_sdl/libui/darwin/alloc.m
new file mode 100644
index 0000000..e271b90
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/alloc.m
@@ -0,0 +1,89 @@
+// 4 december 2014
+#import <stdlib.h>
+#import "uipriv_darwin.h"
+
+static NSMutableArray *allocations;
+NSMutableArray *delegates;
+
+void initAlloc(void)
+{
+ allocations = [NSMutableArray new];
+ delegates = [NSMutableArray new];
+}
+
+#define UINT8(p) ((uint8_t *) (p))
+#define PVOID(p) ((void *) (p))
+#define EXTRA (sizeof (size_t) + sizeof (const char **))
+#define DATA(p) PVOID(UINT8(p) + EXTRA)
+#define BASE(p) PVOID(UINT8(p) - EXTRA)
+#define SIZE(p) ((size_t *) (p))
+#define CCHAR(p) ((const char **) (p))
+#define TYPE(p) CCHAR(UINT8(p) + sizeof (size_t))
+
+void uninitAlloc(void)
+{
+ NSMutableString *str;
+ NSValue *v;
+
+ [delegates release];
+ if ([allocations count] == 0) {
+ [allocations release];
+ return;
+ }
+ str = [NSMutableString new];
+ for (v in allocations) {
+ void *ptr;
+
+ ptr = [v pointerValue];
+ [str appendString:[NSString stringWithFormat:@"%p %s\n", ptr, *TYPE(ptr)]];
+ }
+ userbug("Some data was leaked; either you left a uiControl lying around or there's a bug in libui itself. Leaked data:\n%s", [str UTF8String]);
+ [str release];
+}
+
+void *uiAlloc(size_t size, const char *type)
+{
+ void *out;
+
+ out = malloc(EXTRA + size);
+ if (out == NULL) {
+ fprintf(stderr, "memory exhausted in uiAlloc()\n");
+ abort();
+ }
+ memset(DATA(out), 0, size);
+ *SIZE(out) = size;
+ *TYPE(out) = type;
+ [allocations addObject:[NSValue valueWithPointer:out]];
+ return DATA(out);
+}
+
+void *uiRealloc(void *p, size_t new, const char *type)
+{
+ void *out;
+ size_t *s;
+
+ if (p == NULL)
+ return uiAlloc(new, type);
+ p = BASE(p);
+ out = realloc(p, EXTRA + new);
+ if (out == NULL) {
+ fprintf(stderr, "memory exhausted in uiRealloc()\n");
+ abort();
+ }
+ s = SIZE(out);
+ if (new <= *s)
+ memset(((uint8_t *) DATA(out)) + *s, 0, new - *s);
+ *s = new;
+ [allocations removeObject:[NSValue valueWithPointer:p]];
+ [allocations addObject:[NSValue valueWithPointer:out]];
+ return DATA(out);
+}
+
+void uiFree(void *p)
+{
+ if (p == NULL)
+ implbug("attempt to uiFree(NULL)");
+ p = BASE(p);
+ free(p);
+ [allocations removeObject:[NSValue valueWithPointer:p]];
+}
diff --git a/src/libui_sdl/libui/darwin/area.m b/src/libui_sdl/libui/darwin/area.m
new file mode 100644
index 0000000..23162e6
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/area.m
@@ -0,0 +1,475 @@
+// 9 september 2015
+#import "uipriv_darwin.h"
+
+// 10.8 fixups
+#define NSEventModifierFlags NSUInteger
+
+@interface areaView : NSView {
+ uiArea *libui_a;
+ NSTrackingArea *libui_ta;
+ NSSize libui_ss;
+ BOOL libui_enabled;
+}
+- (id)initWithFrame:(NSRect)r area:(uiArea *)a;
+- (uiModifiers)parseModifiers:(NSEvent *)e;
+- (void)doMouseEvent:(NSEvent *)e;
+- (int)sendKeyEvent:(uiAreaKeyEvent *)ke;
+- (int)doKeyDownUp:(NSEvent *)e up:(int)up;
+- (int)doKeyDown:(NSEvent *)e;
+- (int)doKeyUp:(NSEvent *)e;
+- (int)doFlagsChanged:(NSEvent *)e;
+- (void)setupNewTrackingArea;
+- (void)setScrollingSize:(NSSize)s;
+- (BOOL)isEnabled;
+- (void)setEnabled:(BOOL)e;
+@end
+
+struct uiArea {
+ uiDarwinControl c;
+ NSView *view; // either sv or area depending on whether it is scrolling
+ NSScrollView *sv;
+ areaView *area;
+ struct scrollViewData *d;
+ uiAreaHandler *ah;
+ BOOL scrolling;
+ NSEvent *dragevent;
+};
+
+@implementation areaView
+
+- (id)initWithFrame:(NSRect)r area:(uiArea *)a
+{
+ self = [super initWithFrame:r];
+ if (self) {
+ self->libui_a = a;
+ [self setupNewTrackingArea];
+ self->libui_ss = r.size;
+ self->libui_enabled = YES;
+ }
+ return self;
+}
+
+- (void)drawRect:(NSRect)r
+{
+ uiArea *a = self->libui_a;
+ CGContextRef c;
+ uiAreaDrawParams dp;
+
+ c = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
+ // see draw.m under text for why we need the height
+ dp.Context = newContext(c, [self bounds].size.height);
+
+ dp.AreaWidth = 0;
+ dp.AreaHeight = 0;
+ if (!a->scrolling) {
+ dp.AreaWidth = [self frame].size.width;
+ dp.AreaHeight = [self frame].size.height;
+ }
+
+ dp.ClipX = r.origin.x;
+ dp.ClipY = r.origin.y;
+ dp.ClipWidth = r.size.width;
+ dp.ClipHeight = r.size.height;
+
+ // no need to save or restore the graphics state to reset transformations; Cocoa creates a brand-new context each time
+ (*(a->ah->Draw))(a->ah, a, &dp);
+
+ freeContext(dp.Context);
+}
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (BOOL)acceptsFirstResponder
+{
+ return YES;
+}
+
+- (uiModifiers)parseModifiers:(NSEvent *)e
+{
+ NSEventModifierFlags mods;
+ uiModifiers m;
+
+ m = 0;
+ mods = [e modifierFlags];
+ if ((mods & NSControlKeyMask) != 0)
+ m |= uiModifierCtrl;
+ if ((mods & NSAlternateKeyMask) != 0)
+ m |= uiModifierAlt;
+ if ((mods & NSShiftKeyMask) != 0)
+ m |= uiModifierShift;
+ if ((mods & NSCommandKeyMask) != 0)
+ m |= uiModifierSuper;
+ return m;
+}
+
+- (void)setupNewTrackingArea
+{
+ self->libui_ta = [[NSTrackingArea alloc] initWithRect:[self bounds]
+ options:(NSTrackingMouseEnteredAndExited |
+ NSTrackingMouseMoved |
+ NSTrackingActiveAlways |
+ NSTrackingInVisibleRect |
+ NSTrackingEnabledDuringMouseDrag)
+ owner:self
+ userInfo:nil];
+ [self addTrackingArea:self->libui_ta];
+}
+
+- (void)updateTrackingAreas
+{
+ [self removeTrackingArea:self->libui_ta];
+ [self->libui_ta release];
+ [self setupNewTrackingArea];
+}
+
+// capture on drag is done automatically on OS X
+- (void)doMouseEvent:(NSEvent *)e
+{
+ uiArea *a = self->libui_a;
+ uiAreaMouseEvent me;
+ NSPoint point;
+ int buttonNumber;
+ NSUInteger pmb;
+ unsigned int i, max;
+
+ // this will convert point to drawing space
+ // thanks swillits in irc.freenode.net/#macdev
+ point = [self convertPoint:[e locationInWindow] fromView:nil];
+ me.X = point.x;
+ me.Y = point.y;
+
+ me.AreaWidth = 0;
+ me.AreaHeight = 0;
+ if (!a->scrolling) {
+ me.AreaWidth = [self frame].size.width;
+ me.AreaHeight = [self frame].size.height;
+ }
+
+ buttonNumber = [e buttonNumber] + 1;
+ // swap button numbers 2 and 3 (right and middle)
+ if (buttonNumber == 2)
+ buttonNumber = 3;
+ else if (buttonNumber == 3)
+ buttonNumber = 2;
+
+ me.Down = 0;
+ me.Up = 0;
+ me.Count = 0;
+ switch ([e type]) {
+ case NSLeftMouseDown:
+ case NSRightMouseDown:
+ case NSOtherMouseDown:
+ me.Down = buttonNumber;
+ me.Count = [e clickCount];
+ break;
+ case NSLeftMouseUp:
+ case NSRightMouseUp:
+ case NSOtherMouseUp:
+ me.Up = buttonNumber;
+ break;
+ case NSLeftMouseDragged:
+ case NSRightMouseDragged:
+ case NSOtherMouseDragged:
+ // we include the button that triggered the dragged event in the Held fields
+ buttonNumber = 0;
+ break;
+ }
+
+ me.Modifiers = [self parseModifiers:e];
+
+ pmb = [NSEvent pressedMouseButtons];
+ me.Held1To64 = 0;
+ if (buttonNumber != 1 && (pmb & 1) != 0)
+ me.Held1To64 |= 1;
+ if (buttonNumber != 2 && (pmb & 4) != 0)
+ me.Held1To64 |= 2;
+ if (buttonNumber != 3 && (pmb & 2) != 0)
+ me.Held1To64 |= 4;
+ // buttons 4..32
+ // https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html#//apple_ref/c/tdef/CGMouseButton says Quartz only supports up to 32 buttons
+ max = 32;
+ for (i = 4; i <= max; i++) {
+ uint64_t j;
+
+ if (buttonNumber == i)
+ continue;
+ j = 1 << (i - 1);
+ if ((pmb & j) != 0)
+ me.Held1To64 |= j;
+ }
+
+ if (self->libui_enabled) {
+ // and allow dragging here
+ a->dragevent = e;
+ (*(a->ah->MouseEvent))(a->ah, a, &me);
+ a->dragevent = nil;
+ }
+}
+
+#define mouseEvent(name) \
+ - (void)name:(NSEvent *)e \
+ { \
+ [self doMouseEvent:e]; \
+ }
+mouseEvent(mouseMoved)
+mouseEvent(mouseDragged)
+mouseEvent(rightMouseDragged)
+mouseEvent(otherMouseDragged)
+mouseEvent(mouseDown)
+mouseEvent(rightMouseDown)
+mouseEvent(otherMouseDown)
+mouseEvent(mouseUp)
+mouseEvent(rightMouseUp)
+mouseEvent(otherMouseUp)
+
+- (void)mouseEntered:(NSEvent *)e
+{
+ uiArea *a = self->libui_a;
+
+ if (self->libui_enabled)
+ (*(a->ah->MouseCrossed))(a->ah, a, 0);
+}
+
+- (void)mouseExited:(NSEvent *)e
+{
+ uiArea *a = self->libui_a;
+
+ if (self->libui_enabled)
+ (*(a->ah->MouseCrossed))(a->ah, a, 1);
+}
+
+// note: there is no equivalent to WM_CAPTURECHANGED on Mac OS X; there literally is no way to break a grab like that
+// even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons
+// therefore, no DragBroken()
+
+- (int)sendKeyEvent:(uiAreaKeyEvent *)ke
+{
+ uiArea *a = self->libui_a;
+
+ return (*(a->ah->KeyEvent))(a->ah, a, ke);
+}
+
+- (int)doKeyDownUp:(NSEvent *)e up:(int)up
+{
+ uiAreaKeyEvent ke;
+
+ ke.Key = 0;
+ ke.ExtKey = 0;
+ ke.Modifier = 0;
+
+ ke.Modifiers = [self parseModifiers:e];
+
+ ke.Up = up;
+
+ if (!fromKeycode([e keyCode], &ke))
+ return 0;
+ return [self sendKeyEvent:&ke];
+}
+
+- (int)doKeyDown:(NSEvent *)e
+{
+ return [self doKeyDownUp:e up:0];
+}
+
+- (int)doKeyUp:(NSEvent *)e
+{
+ return [self doKeyDownUp:e up:1];
+}
+
+- (int)doFlagsChanged:(NSEvent *)e
+{
+ uiAreaKeyEvent ke;
+ uiModifiers whichmod;
+
+ ke.Key = 0;
+ ke.ExtKey = 0;
+
+ // Mac OS X sends this event on both key up and key down.
+ // Fortunately -[e keyCode] IS valid here, so we can simply map from key code to Modifiers, get the value of [e modifierFlags], and check if the respective bit is set or not — that will give us the up/down state
+ if (!keycodeModifier([e keyCode], &whichmod))
+ return 0;
+ ke.Modifier = whichmod;
+ ke.Modifiers = [self parseModifiers:e];
+ ke.Up = (ke.Modifiers & ke.Modifier) == 0;
+ // and then drop the current modifier from Modifiers
+ ke.Modifiers &= ~ke.Modifier;
+ return [self sendKeyEvent:&ke];
+}
+
+- (void)setFrameSize:(NSSize)size
+{
+ uiArea *a = self->libui_a;
+
+ [super setFrameSize:size];
+ if (!a->scrolling)
+ // we must redraw everything on resize because Windows requires it
+ [self setNeedsDisplay:YES];
+}
+
+// TODO does this update the frame?
+- (void)setScrollingSize:(NSSize)s
+{
+ self->libui_ss = s;
+ [self invalidateIntrinsicContentSize];
+}
+
+- (NSSize)intrinsicContentSize
+{
+ if (!self->libui_a->scrolling)
+ return [super intrinsicContentSize];
+ return self->libui_ss;
+}
+
+- (BOOL)becomeFirstResponder
+{
+ return [self isEnabled];
+}
+
+- (BOOL)isEnabled
+{
+ return self->libui_enabled;
+}
+
+- (void)setEnabled:(BOOL)e
+{
+ self->libui_enabled = e;
+ if (!self->libui_enabled && [self window] != nil)
+ if ([[self window] firstResponder] == self)
+ [[self window] makeFirstResponder:nil];
+}
+
+@end
+
+uiDarwinControlAllDefaultsExceptDestroy(uiArea, view)
+
+static void uiAreaDestroy(uiControl *c)
+{
+ uiArea *a = uiArea(c);
+
+ if (a->scrolling)
+ scrollViewFreeData(a->sv, a->d);
+ [a->area release];
+ if (a->scrolling)
+ [a->sv release];
+ uiFreeControl(uiControl(a));
+}
+
+// called by subclasses of -[NSApplication sendEvent:]
+// by default, NSApplication eats some key events
+// this prevents that from happening with uiArea
+// see http://stackoverflow.com/questions/24099063/how-do-i-detect-keyup-in-my-nsview-with-the-command-key-held and http://lists.apple.com/archives/cocoa-dev/2003/Oct/msg00442.html
+int sendAreaEvents(NSEvent *e)
+{
+ NSEventType type;
+ id focused;
+ areaView *view;
+
+ type = [e type];
+ if (type != NSKeyDown && type != NSKeyUp && type != NSFlagsChanged)
+ return 0;
+ focused = [[e window] firstResponder];
+ if (focused == nil)
+ return 0;
+ if (![focused isKindOfClass:[areaView class]])
+ return 0;
+ view = (areaView *) focused;
+ switch (type) {
+ case NSKeyDown:
+ return [view doKeyDown:e];
+ case NSKeyUp:
+ return [view doKeyUp:e];
+ case NSFlagsChanged:
+ return [view doFlagsChanged:e];
+ }
+ return 0;
+}
+
+void uiAreaSetSize(uiArea *a, int width, int height)
+{
+ if (!a->scrolling)
+ userbug("You cannot call uiAreaSetSize() on a non-scrolling uiArea. (area: %p)", a);
+ [a->area setScrollingSize:NSMakeSize(width, height)];
+}
+
+void uiAreaQueueRedrawAll(uiArea *a)
+{
+ [a->area setNeedsDisplay:YES];
+}
+
+void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height)
+{
+ if (!a->scrolling)
+ userbug("You cannot call uiAreaScrollTo() on a non-scrolling uiArea. (area: %p)", a);
+ [a->area scrollRectToVisible:NSMakeRect(x, y, width, height)];
+ // don't worry about the return value; it just says whether scrolling was needed
+}
+
+void uiAreaBeginUserWindowMove(uiArea *a)
+{
+ libuiNSWindow *w;
+
+ w = (libuiNSWindow *) [a->area window];
+ if (w == nil)
+ return; // TODO
+ if (a->dragevent == nil)
+ return; // TODO
+ [w libui_doMove:a->dragevent];
+}
+
+void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge)
+{
+ libuiNSWindow *w;
+
+ w = (libuiNSWindow *) [a->area window];
+ if (w == nil)
+ return; // TODO
+ if (a->dragevent == nil)
+ return; // TODO
+ [w libui_doResize:a->dragevent on:edge];
+}
+
+uiArea *uiNewArea(uiAreaHandler *ah)
+{
+ uiArea *a;
+
+ uiDarwinNewControl(uiArea, a);
+
+ a->ah = ah;
+ a->scrolling = NO;
+
+ a->area = [[areaView alloc] initWithFrame:NSZeroRect area:a];
+
+ a->view = a->area;
+
+ return a;
+}
+
+uiArea *uiNewScrollingArea(uiAreaHandler *ah, int width, int height)
+{
+ uiArea *a;
+ struct scrollViewCreateParams p;
+
+ uiDarwinNewControl(uiArea, a);
+
+ a->ah = ah;
+ a->scrolling = YES;
+
+ a->area = [[areaView alloc] initWithFrame:NSMakeRect(0, 0, width, height)
+ area:a];
+
+ memset(&p, 0, sizeof (struct scrollViewCreateParams));
+ p.DocumentView = a->area;
+ p.BackgroundColor = [NSColor controlColor];
+ p.DrawsBackground = 1;
+ p.Bordered = NO;
+ p.HScroll = YES;
+ p.VScroll = YES;
+ a->sv = mkScrollView(&p, &(a->d));
+
+ a->view = a->sv;
+
+ return a;
+}
diff --git a/src/libui_sdl/libui/darwin/areaevents.m b/src/libui_sdl/libui/darwin/areaevents.m
new file mode 100644
index 0000000..d7ceaaa
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/areaevents.m
@@ -0,0 +1,159 @@
+// 30 march 2014
+#import "uipriv_darwin.h"
+
+/*
+Mac OS X uses its own set of hardware key codes that are different from PC keyboard scancodes, but are positional (like PC keyboard scancodes). These are defined in <HIToolbox/Events.h>, a Carbon header. As far as I can tell, there's no way to include this header without either using an absolute path or linking Carbon into the program, so the constant values are used here instead.
+
+The Cocoa docs do guarantee that -[NSEvent keyCode] results in key codes that are the same as those returned by Carbon; that is, these codes.
+*/
+
+// use uintptr_t to be safe
+static const struct {
+ uintptr_t keycode;
+ char equiv;
+} keycodeKeys[] = {
+ { 0x00, 'a' },
+ { 0x01, 's' },
+ { 0x02, 'd' },
+ { 0x03, 'f' },
+ { 0x04, 'h' },
+ { 0x05, 'g' },
+ { 0x06, 'z' },
+ { 0x07, 'x' },
+ { 0x08, 'c' },
+ { 0x09, 'v' },
+ { 0x0B, 'b' },
+ { 0x0C, 'q' },
+ { 0x0D, 'w' },
+ { 0x0E, 'e' },
+ { 0x0F, 'r' },
+ { 0x10, 'y' },
+ { 0x11, 't' },
+ { 0x12, '1' },
+ { 0x13, '2' },
+ { 0x14, '3' },
+ { 0x15, '4' },
+ { 0x16, '6' },
+ { 0x17, '5' },
+ { 0x18, '=' },
+ { 0x19, '9' },
+ { 0x1A, '7' },
+ { 0x1B, '-' },
+ { 0x1C, '8' },
+ { 0x1D, '0' },
+ { 0x1E, ']' },
+ { 0x1F, 'o' },
+ { 0x20, 'u' },
+ { 0x21, '[' },
+ { 0x22, 'i' },
+ { 0x23, 'p' },
+ { 0x25, 'l' },
+ { 0x26, 'j' },
+ { 0x27, '\'' },
+ { 0x28, 'k' },
+ { 0x29, ';' },
+ { 0x2A, '\\' },
+ { 0x2B, ',' },
+ { 0x2C, '/' },
+ { 0x2D, 'n' },
+ { 0x2E, 'm' },
+ { 0x2F, '.' },
+ { 0x32, '`' },
+ { 0x24, '\n' },
+ { 0x30, '\t' },
+ { 0x31, ' ' },
+ { 0x33, '\b' },
+ { 0xFFFF, 0 },
+};
+
+static const struct {
+ uintptr_t keycode;
+ uiExtKey equiv;
+} keycodeExtKeys[] = {
+ { 0x41, uiExtKeyNDot },
+ { 0x43, uiExtKeyNMultiply },
+ { 0x45, uiExtKeyNAdd },
+ { 0x4B, uiExtKeyNDivide },
+ { 0x4C, uiExtKeyNEnter },
+ { 0x4E, uiExtKeyNSubtract },
+ { 0x52, uiExtKeyN0 },
+ { 0x53, uiExtKeyN1 },
+ { 0x54, uiExtKeyN2 },
+ { 0x55, uiExtKeyN3 },
+ { 0x56, uiExtKeyN4 },
+ { 0x57, uiExtKeyN5 },
+ { 0x58, uiExtKeyN6 },
+ { 0x59, uiExtKeyN7 },
+ { 0x5B, uiExtKeyN8 },
+ { 0x5C, uiExtKeyN9 },
+ { 0x35, uiExtKeyEscape },
+ { 0x60, uiExtKeyF5 },
+ { 0x61, uiExtKeyF6 },
+ { 0x62, uiExtKeyF7 },
+ { 0x63, uiExtKeyF3 },
+ { 0x64, uiExtKeyF8 },
+ { 0x65, uiExtKeyF9 },
+ { 0x67, uiExtKeyF11 },
+ { 0x6D, uiExtKeyF10 },
+ { 0x6F, uiExtKeyF12 },
+ { 0x72, uiExtKeyInsert }, // listed as the Help key but it's in the same position on an Apple keyboard as the Insert key on a Windows keyboard; thanks to SeanieB from irc.badnik.net and Psy in irc.freenode.net/#macdev for confirming they have the same code
+ { 0x73, uiExtKeyHome },
+ { 0x74, uiExtKeyPageUp },
+ { 0x75, uiExtKeyDelete },
+ { 0x76, uiExtKeyF4 },
+ { 0x77, uiExtKeyEnd },
+ { 0x78, uiExtKeyF2 },
+ { 0x79, uiExtKeyPageDown },
+ { 0x7A, uiExtKeyF1 },
+ { 0x7B, uiExtKeyLeft },
+ { 0x7C, uiExtKeyRight },
+ { 0x7D, uiExtKeyDown },
+ { 0x7E, uiExtKeyUp },
+ { 0xFFFF, 0 },
+};
+
+static const struct {
+ uintptr_t keycode;
+ uiModifiers equiv;
+} keycodeModifiers[] = {
+ { 0x37, uiModifierSuper }, // left command
+ { 0x38, uiModifierShift }, // left shift
+ { 0x3A, uiModifierAlt }, // left option
+ { 0x3B, uiModifierCtrl }, // left control
+ { 0x3C, uiModifierShift }, // right shift
+ { 0x3D, uiModifierAlt }, // right alt
+ { 0x3E, uiModifierCtrl }, // right control
+ // the following is not in Events.h for some reason
+ // thanks to Nicole and jedivulcan from irc.badnik.net
+ { 0x36, uiModifierSuper }, // right command
+ { 0xFFFF, 0 },
+};
+
+BOOL fromKeycode(unsigned short keycode, uiAreaKeyEvent *ke)
+{
+ int i;
+
+ for (i = 0; keycodeKeys[i].keycode != 0xFFFF; i++)
+ if (keycodeKeys[i].keycode == keycode) {
+ ke->Key = keycodeKeys[i].equiv;
+ return YES;
+ }
+ for (i = 0; keycodeExtKeys[i].keycode != 0xFFFF; i++)
+ if (keycodeExtKeys[i].keycode == keycode) {
+ ke->ExtKey = keycodeExtKeys[i].equiv;
+ return YES;
+ }
+ return NO;
+}
+
+BOOL keycodeModifier(unsigned short keycode, uiModifiers *mod)
+{
+ int i;
+
+ for (i = 0; keycodeModifiers[i].keycode != 0xFFFF; i++)
+ if (keycodeModifiers[i].keycode == keycode) {
+ *mod = keycodeModifiers[i].equiv;
+ return YES;
+ }
+ return NO;
+}
diff --git a/src/libui_sdl/libui/darwin/autolayout.m b/src/libui_sdl/libui/darwin/autolayout.m
new file mode 100644
index 0000000..9964155
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/autolayout.m
@@ -0,0 +1,161 @@
+// 15 august 2015
+#import "uipriv_darwin.h"
+
+NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc)
+{
+ NSLayoutConstraint *constraint;
+
+ constraint = [NSLayoutConstraint constraintWithItem:view1
+ attribute:attr1
+ relatedBy:relation
+ toItem:view2
+ attribute:attr2
+ multiplier:multiplier
+ constant:c];
+ // apparently only added in 10.9
+ if ([constraint respondsToSelector:@selector(setIdentifier:)])
+ [((id) constraint) setIdentifier:desc];
+ return constraint;
+}
+
+CGFloat uiDarwinMarginAmount(void *reserved)
+{
+ return 20.0;
+}
+
+CGFloat uiDarwinPaddingAmount(void *reserved)
+{
+ return 8.0;
+}
+
+// this is needed for NSSplitView to work properly; see http://stackoverflow.com/questions/34574478/how-can-i-set-the-position-of-a-nssplitview-nowadays-setpositionofdivideratind (stal in irc.freenode.net/#macdev came up with the exact combination)
+// turns out it also works on NSTabView and NSBox too, possibly others!
+// and for bonus points, it even seems to fix unsatisfiable-constraint-autoresizing-mask issues with NSTabView and NSBox too!!! this is nuts
+void jiggleViewLayout(NSView *view)
+{
+ [view setNeedsLayout:YES];
+ [view layoutSubtreeIfNeeded];
+}
+
+static CGFloat margins(int margined)
+{
+ if (!margined)
+ return 0.0;
+ return uiDarwinMarginAmount(NULL);
+}
+
+void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc)
+{
+ CGFloat margin;
+
+ margin = margins(margined);
+
+ c->leadingConstraint = mkConstraint(contentView, NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ childView, NSLayoutAttributeLeading,
+ 1, -margin,
+ [desc stringByAppendingString:@" leading constraint"]);
+ [contentView addConstraint:c->leadingConstraint];
+ [c->leadingConstraint retain];
+
+ c->topConstraint = mkConstraint(contentView, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ childView, NSLayoutAttributeTop,
+ 1, -margin,
+ [desc stringByAppendingString:@" top constraint"]);
+ [contentView addConstraint:c->topConstraint];
+ [c->topConstraint retain];
+
+ c->trailingConstraintGreater = mkConstraint(contentView, NSLayoutAttributeTrailing,
+ NSLayoutRelationGreaterThanOrEqual,
+ childView, NSLayoutAttributeTrailing,
+ 1, margin,
+ [desc stringByAppendingString:@" trailing >= constraint"]);
+ if (hugsTrailing)
+ [c->trailingConstraintGreater setPriority:NSLayoutPriorityDefaultLow];
+ [contentView addConstraint:c->trailingConstraintGreater];
+ [c->trailingConstraintGreater retain];
+
+ c->trailingConstraintEqual = mkConstraint(contentView, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ childView, NSLayoutAttributeTrailing,
+ 1, margin,
+ [desc stringByAppendingString:@" trailing == constraint"]);
+ if (!hugsTrailing)
+ [c->trailingConstraintEqual setPriority:NSLayoutPriorityDefaultLow];
+ [contentView addConstraint:c->trailingConstraintEqual];
+ [c->trailingConstraintEqual retain];
+
+ c->bottomConstraintGreater = mkConstraint(contentView, NSLayoutAttributeBottom,
+ NSLayoutRelationGreaterThanOrEqual,
+ childView, NSLayoutAttributeBottom,
+ 1, margin,
+ [desc stringByAppendingString:@" bottom >= constraint"]);
+ if (hugsBottom)
+ [c->bottomConstraintGreater setPriority:NSLayoutPriorityDefaultLow];
+ [contentView addConstraint:c->bottomConstraintGreater];
+ [c->bottomConstraintGreater retain];
+
+ c->bottomConstraintEqual = mkConstraint(contentView, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ childView, NSLayoutAttributeBottom,
+ 1, margin,
+ [desc stringByAppendingString:@" bottom == constraint"]);
+ if (!hugsBottom)
+ [c->bottomConstraintEqual setPriority:NSLayoutPriorityDefaultLow];
+ [contentView addConstraint:c->bottomConstraintEqual];
+ [c->bottomConstraintEqual retain];
+}
+
+void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv)
+{
+ if (c->leadingConstraint != nil) {
+ [cv removeConstraint:c->leadingConstraint];
+ [c->leadingConstraint release];
+ c->leadingConstraint = nil;
+ }
+ if (c->topConstraint != nil) {
+ [cv removeConstraint:c->topConstraint];
+ [c->topConstraint release];
+ c->topConstraint = nil;
+ }
+ if (c->trailingConstraintGreater != nil) {
+ [cv removeConstraint:c->trailingConstraintGreater];
+ [c->trailingConstraintGreater release];
+ c->trailingConstraintGreater = nil;
+ }
+ if (c->trailingConstraintEqual != nil) {
+ [cv removeConstraint:c->trailingConstraintEqual];
+ [c->trailingConstraintEqual release];
+ c->trailingConstraintEqual = nil;
+ }
+ if (c->bottomConstraintGreater != nil) {
+ [cv removeConstraint:c->bottomConstraintGreater];
+ [c->bottomConstraintGreater release];
+ c->bottomConstraintGreater = nil;
+ }
+ if (c->bottomConstraintEqual != nil) {
+ [cv removeConstraint:c->bottomConstraintEqual];
+ [c->bottomConstraintEqual release];
+ c->bottomConstraintEqual = nil;
+ }
+}
+
+void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined)
+{
+ CGFloat margin;
+
+ margin = margins(margined);
+ if (c->leadingConstraint != nil)
+ [c->leadingConstraint setConstant:-margin];
+ if (c->topConstraint != nil)
+ [c->topConstraint setConstant:-margin];
+ if (c->trailingConstraintGreater != nil)
+ [c->trailingConstraintGreater setConstant:margin];
+ if (c->trailingConstraintEqual != nil)
+ [c->trailingConstraintEqual setConstant:margin];
+ if (c->bottomConstraintGreater != nil)
+ [c->bottomConstraintGreater setConstant:margin];
+ if (c->bottomConstraintEqual != nil)
+ [c->bottomConstraintEqual setConstant:margin];
+}
diff --git a/src/libui_sdl/libui/darwin/box.m b/src/libui_sdl/libui/darwin/box.m
new file mode 100644
index 0000000..18d536d
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/box.m
@@ -0,0 +1,469 @@
+// 15 august 2015
+#import "uipriv_darwin.h"
+
+// TODO hiding all stretchy controls still hugs trailing edge
+
+@interface boxChild : NSObject
+@property uiControl *c;
+@property BOOL stretchy;
+@property NSLayoutPriority oldPrimaryHuggingPri;
+@property NSLayoutPriority oldSecondaryHuggingPri;
+- (NSView *)view;
+@end
+
+@interface boxView : NSView {
+ uiBox *b;
+ NSMutableArray *children;
+ BOOL vertical;
+ int padded;
+
+ NSLayoutConstraint *first;
+ NSMutableArray *inBetweens;
+ NSLayoutConstraint *last;
+ NSMutableArray *otherConstraints;
+
+ NSLayoutAttribute primaryStart;
+ NSLayoutAttribute primaryEnd;
+ NSLayoutAttribute secondaryStart;
+ NSLayoutAttribute secondaryEnd;
+ NSLayoutAttribute primarySize;
+ NSLayoutConstraintOrientation primaryOrientation;
+ NSLayoutConstraintOrientation secondaryOrientation;
+}
+- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb;
+- (void)onDestroy;
+- (void)removeOurConstraints;
+- (void)syncEnableStates:(int)enabled;
+- (CGFloat)paddingAmount;
+- (void)establishOurConstraints;
+- (void)append:(uiControl *)c stretchy:(int)stretchy;
+- (void)delete:(int)n;
+- (int)isPadded;
+- (void)setPadded:(int)p;
+- (BOOL)hugsTrailing;
+- (BOOL)hugsBottom;
+- (int)nStretchy;
+@end
+
+struct uiBox {
+ uiDarwinControl c;
+ boxView *view;
+};
+
+@implementation boxChild
+
+- (NSView *)view
+{
+ return (NSView *) uiControlHandle(self.c);
+}
+
+@end
+
+@implementation boxView
+
+- (id)initWithVertical:(BOOL)vert b:(uiBox *)bb
+{
+ self = [super initWithFrame:NSZeroRect];
+ if (self != nil) {
+ // the weird names vert and bb are to shut the compiler up about shadowing because implicit this/self is stupid
+ self->b = bb;
+ self->vertical = vert;
+ self->padded = 0;
+ self->children = [NSMutableArray new];
+
+ self->inBetweens = [NSMutableArray new];
+ self->otherConstraints = [NSMutableArray new];
+
+ if (self->vertical) {
+ self->primaryStart = NSLayoutAttributeTop;
+ self->primaryEnd = NSLayoutAttributeBottom;
+ self->secondaryStart = NSLayoutAttributeLeading;
+ self->secondaryEnd = NSLayoutAttributeTrailing;
+ self->primarySize = NSLayoutAttributeHeight;
+ self->primaryOrientation = NSLayoutConstraintOrientationVertical;
+ self->secondaryOrientation = NSLayoutConstraintOrientationHorizontal;
+ } else {
+ self->primaryStart = NSLayoutAttributeLeading;
+ self->primaryEnd = NSLayoutAttributeTrailing;
+ self->secondaryStart = NSLayoutAttributeTop;
+ self->secondaryEnd = NSLayoutAttributeBottom;
+ self->primarySize = NSLayoutAttributeWidth;
+ self->primaryOrientation = NSLayoutConstraintOrientationHorizontal;
+ self->secondaryOrientation = NSLayoutConstraintOrientationVertical;
+ }
+ }
+ return self;
+}
+
+- (void)onDestroy
+{
+ boxChild *bc;
+
+ [self removeOurConstraints];
+ [self->inBetweens release];
+ [self->otherConstraints release];
+
+ for (bc in self->children) {
+ uiControlSetParent(bc.c, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
+ uiControlDestroy(bc.c);
+ }
+ [self->children release];
+}
+
+- (void)removeOurConstraints
+{
+ if (self->first != nil) {
+ [self removeConstraint:self->first];
+ [self->first release];
+ self->first = nil;
+ }
+ if ([self->inBetweens count] != 0) {
+ [self removeConstraints:self->inBetweens];
+ [self->inBetweens removeAllObjects];
+ }
+ if (self->last != nil) {
+ [self removeConstraint:self->last];
+ [self->last release];
+ self->last = nil;
+ }
+ if ([self->otherConstraints count] != 0) {
+ [self removeConstraints:self->otherConstraints];
+ [self->otherConstraints removeAllObjects];
+ }
+}
+
+- (void)syncEnableStates:(int)enabled
+{
+ boxChild *bc;
+
+ for (bc in self->children)
+ uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), enabled);
+}
+
+- (CGFloat)paddingAmount
+{
+ if (!self->padded)
+ return 0.0;
+ return uiDarwinPaddingAmount(NULL);
+}
+
+- (void)establishOurConstraints
+{
+ boxChild *bc;
+ CGFloat padding;
+ NSView *prev;
+ NSLayoutConstraint *c;
+ BOOL (*hugsSecondary)(uiDarwinControl *);
+
+ [self removeOurConstraints];
+ if ([self->children count] == 0)
+ return;
+ padding = [self paddingAmount];
+
+ // first arrange in the primary direction
+ prev = nil;
+ for (bc in self->children) {
+ if (!uiControlVisible(bc.c))
+ continue;
+ if (prev == nil) { // first view
+ self->first = mkConstraint(self, self->primaryStart,
+ NSLayoutRelationEqual,
+ [bc view], self->primaryStart,
+ 1, 0,
+ @"uiBox first primary constraint");
+ [self addConstraint:self->first];
+ [self->first retain];
+ prev = [bc view];
+ continue;
+ }
+ // not the first; link it
+ c = mkConstraint(prev, self->primaryEnd,
+ NSLayoutRelationEqual,
+ [bc view], self->primaryStart,
+ 1, -padding,
+ @"uiBox in-between primary constraint");
+ [self addConstraint:c];
+ [self->inBetweens addObject:c];
+ prev = [bc view];
+ }
+ if (prev == nil) // no control visible; act as if no controls
+ return;
+ self->last = mkConstraint(prev, self->primaryEnd,
+ NSLayoutRelationEqual,
+ self, self->primaryEnd,
+ 1, 0,
+ @"uiBox last primary constraint");
+ [self addConstraint:self->last];
+ [self->last retain];
+
+ // then arrange in the secondary direction
+ hugsSecondary = uiDarwinControlHugsTrailingEdge;
+ if (!self->vertical)
+ hugsSecondary = uiDarwinControlHugsBottom;
+ for (bc in self->children) {
+ if (!uiControlVisible(bc.c))
+ continue;
+ c = mkConstraint(self, self->secondaryStart,
+ NSLayoutRelationEqual,
+ [bc view], self->secondaryStart,
+ 1, 0,
+ @"uiBox secondary start constraint");
+ [self addConstraint:c];
+ [self->otherConstraints addObject:c];
+ c = mkConstraint([bc view], self->secondaryEnd,
+ NSLayoutRelationLessThanOrEqual,
+ self, self->secondaryEnd,
+ 1, 0,
+ @"uiBox secondary end <= constraint");
+ if ((*hugsSecondary)(uiDarwinControl(bc.c)))
+ [c setPriority:NSLayoutPriorityDefaultLow];
+ [self addConstraint:c];
+ [self->otherConstraints addObject:c];
+ c = mkConstraint([bc view], self->secondaryEnd,
+ NSLayoutRelationEqual,
+ self, self->secondaryEnd,
+ 1, 0,
+ @"uiBox secondary end == constraint");
+ if (!(*hugsSecondary)(uiDarwinControl(bc.c)))
+ [c setPriority:NSLayoutPriorityDefaultLow];
+ [self addConstraint:c];
+ [self->otherConstraints addObject:c];
+ }
+
+ // and make all stretchy controls the same size
+ if ([self nStretchy] == 0)
+ return;
+ prev = nil; // first stretchy view
+ for (bc in self->children) {
+ if (!uiControlVisible(bc.c))
+ continue;
+ if (!bc.stretchy)
+ continue;
+ if (prev == nil) {
+ prev = [bc view];
+ continue;
+ }
+ c = mkConstraint(prev, self->primarySize,
+ NSLayoutRelationEqual,
+ [bc view], self->primarySize,
+ 1, 0,
+ @"uiBox stretchy size constraint");
+ [self addConstraint:c];
+ [self->otherConstraints addObject:c];
+ }
+}
+
+- (void)append:(uiControl *)c stretchy:(int)stretchy
+{
+ boxChild *bc;
+ NSLayoutPriority priority;
+ int oldnStretchy;
+
+ bc = [boxChild new];
+ bc.c = c;
+ bc.stretchy = stretchy;
+ bc.oldPrimaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->primaryOrientation);
+ bc.oldSecondaryHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(bc.c), self->secondaryOrientation);
+
+ uiControlSetParent(bc.c, uiControl(self->b));
+ uiDarwinControlSetSuperview(uiDarwinControl(bc.c), self);
+ uiDarwinControlSyncEnableState(uiDarwinControl(bc.c), uiControlEnabledToUser(uiControl(self->b)));
+
+ // if a control is stretchy, it should not hug in the primary direction
+ // otherwise, it should *forcibly* hug
+ if (bc.stretchy)
+ priority = NSLayoutPriorityDefaultLow;
+ else
+ // LONGTERM will default high work?
+ priority = NSLayoutPriorityRequired;
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), priority, self->primaryOrientation);
+ // make sure controls don't hug their secondary direction so they fill the width of the view
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), NSLayoutPriorityDefaultLow, self->secondaryOrientation);
+
+ oldnStretchy = [self nStretchy];
+ [self->children addObject:bc];
+
+ [self establishOurConstraints];
+ if (bc.stretchy)
+ if (oldnStretchy == 0)
+ uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
+
+ [bc release]; // we don't need the initial reference now
+}
+
+- (void)delete:(int)n
+{
+ boxChild *bc;
+ int stretchy;
+
+ bc = (boxChild *) [self->children objectAtIndex:n];
+ stretchy = bc.stretchy;
+
+ uiControlSetParent(bc.c, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(bc.c), nil);
+
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldPrimaryHuggingPri, self->primaryOrientation);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(bc.c), bc.oldSecondaryHuggingPri, self->secondaryOrientation);
+
+ [self->children removeObjectAtIndex:n];
+
+ [self establishOurConstraints];
+ if (stretchy)
+ if ([self nStretchy] == 0)
+ uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->b));
+}
+
+- (int)isPadded
+{
+ return self->padded;
+}
+
+- (void)setPadded:(int)p
+{
+ CGFloat padding;
+ NSLayoutConstraint *c;
+
+ self->padded = p;
+ padding = [self paddingAmount];
+ for (c in self->inBetweens)
+ [c setConstant:-padding];
+}
+
+- (BOOL)hugsTrailing
+{
+ if (self->vertical) // always hug if vertical
+ return YES;
+ return [self nStretchy] != 0;
+}
+
+- (BOOL)hugsBottom
+{
+ if (!self->vertical) // always hug if horizontal
+ return YES;
+ return [self nStretchy] != 0;
+}
+
+- (int)nStretchy
+{
+ boxChild *bc;
+ int n;
+
+ n = 0;
+ for (bc in self->children) {
+ if (!uiControlVisible(bc.c))
+ continue;
+ if (bc.stretchy)
+ n++;
+ }
+ return n;
+}
+
+@end
+
+static void uiBoxDestroy(uiControl *c)
+{
+ uiBox *b = uiBox(c);
+
+ [b->view onDestroy];
+ [b->view release];
+ uiFreeControl(uiControl(b));
+}
+
+uiDarwinControlDefaultHandle(uiBox, view)
+uiDarwinControlDefaultParent(uiBox, view)
+uiDarwinControlDefaultSetParent(uiBox, view)
+uiDarwinControlDefaultToplevel(uiBox, view)
+uiDarwinControlDefaultVisible(uiBox, view)
+uiDarwinControlDefaultShow(uiBox, view)
+uiDarwinControlDefaultHide(uiBox, view)
+uiDarwinControlDefaultEnabled(uiBox, view)
+uiDarwinControlDefaultEnable(uiBox, view)
+uiDarwinControlDefaultDisable(uiBox, view)
+
+static void uiBoxSyncEnableState(uiDarwinControl *c, int enabled)
+{
+ uiBox *b = uiBox(c);
+
+ if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(b), enabled))
+ return;
+ [b->view syncEnableStates:enabled];
+}
+
+uiDarwinControlDefaultSetSuperview(uiBox, view)
+
+static BOOL uiBoxHugsTrailingEdge(uiDarwinControl *c)
+{
+ uiBox *b = uiBox(c);
+
+ return [b->view hugsTrailing];
+}
+
+static BOOL uiBoxHugsBottom(uiDarwinControl *c)
+{
+ uiBox *b = uiBox(c);
+
+ return [b->view hugsBottom];
+}
+
+static void uiBoxChildEdgeHuggingChanged(uiDarwinControl *c)
+{
+ uiBox *b = uiBox(c);
+
+ [b->view establishOurConstraints];
+}
+
+uiDarwinControlDefaultHuggingPriority(uiBox, view)
+uiDarwinControlDefaultSetHuggingPriority(uiBox, view)
+
+static void uiBoxChildVisibilityChanged(uiDarwinControl *c)
+{
+ uiBox *b = uiBox(c);
+
+ [b->view establishOurConstraints];
+}
+
+void uiBoxAppend(uiBox *b, uiControl *c, int stretchy)
+{
+ // LONGTERM on other platforms
+ // or at leat allow this and implicitly turn it into a spacer
+ if (c == NULL)
+ userbug("You cannot add NULL to a uiBox.");
+ [b->view append:c stretchy:stretchy];
+}
+
+void uiBoxDelete(uiBox *b, int n)
+{
+ [b->view delete:n];
+}
+
+int uiBoxPadded(uiBox *b)
+{
+ return [b->view isPadded];
+}
+
+void uiBoxSetPadded(uiBox *b, int padded)
+{
+ [b->view setPadded:padded];
+}
+
+static uiBox *finishNewBox(BOOL vertical)
+{
+ uiBox *b;
+
+ uiDarwinNewControl(uiBox, b);
+
+ b->view = [[boxView alloc] initWithVertical:vertical b:b];
+
+ return b;
+}
+
+uiBox *uiNewHorizontalBox(void)
+{
+ return finishNewBox(NO);
+}
+
+uiBox *uiNewVerticalBox(void)
+{
+ return finishNewBox(YES);
+}
diff --git a/src/libui_sdl/libui/darwin/button.m b/src/libui_sdl/libui/darwin/button.m
new file mode 100644
index 0000000..baccabb
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/button.m
@@ -0,0 +1,113 @@
+// 13 august 2015
+#import "uipriv_darwin.h"
+
+struct uiButton {
+ uiDarwinControl c;
+ NSButton *button;
+ void (*onClicked)(uiButton *, void *);
+ void *onClickedData;
+};
+
+@interface buttonDelegateClass : NSObject {
+ struct mapTable *buttons;
+}
+- (IBAction)onClicked:(id)sender;
+- (void)registerButton:(uiButton *)b;
+- (void)unregisterButton:(uiButton *)b;
+@end
+
+@implementation buttonDelegateClass
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ self->buttons = newMap();
+ return self;
+}
+
+- (void)dealloc
+{
+ mapDestroy(self->buttons);
+ [super dealloc];
+}
+
+- (IBAction)onClicked:(id)sender
+{
+ uiButton *b;
+
+ b = (uiButton *) mapGet(self->buttons, sender);
+ (*(b->onClicked))(b, b->onClickedData);
+}
+
+- (void)registerButton:(uiButton *)b
+{
+ mapSet(self->buttons, b->button, b);
+ [b->button setTarget:self];
+ [b->button setAction:@selector(onClicked:)];
+}
+
+- (void)unregisterButton:(uiButton *)b
+{
+ [b->button setTarget:nil];
+ mapDelete(self->buttons, b->button);
+}
+
+@end
+
+static buttonDelegateClass *buttonDelegate = nil;
+
+uiDarwinControlAllDefaultsExceptDestroy(uiButton, button)
+
+static void uiButtonDestroy(uiControl *c)
+{
+ uiButton *b = uiButton(c);
+
+ [buttonDelegate unregisterButton:b];
+ [b->button release];
+ uiFreeControl(uiControl(b));
+}
+
+char *uiButtonText(uiButton *b)
+{
+ return uiDarwinNSStringToText([b->button title]);
+}
+
+void uiButtonSetText(uiButton *b, const char *text)
+{
+ [b->button setTitle:toNSString(text)];
+}
+
+void uiButtonOnClicked(uiButton *b, void (*f)(uiButton *, void *), void *data)
+{
+ b->onClicked = f;
+ b->onClickedData = data;
+}
+
+static void defaultOnClicked(uiButton *b, void *data)
+{
+ // do nothing
+}
+
+uiButton *uiNewButton(const char *text)
+{
+ uiButton *b;
+
+ uiDarwinNewControl(uiButton, b);
+
+ b->button = [[NSButton alloc] initWithFrame:NSZeroRect];
+ [b->button setTitle:toNSString(text)];
+ [b->button setButtonType:NSMomentaryPushInButton];
+ [b->button setBordered:YES];
+ [b->button setBezelStyle:NSRoundedBezelStyle];
+ uiDarwinSetControlFont(b->button, NSRegularControlSize);
+
+ if (buttonDelegate == nil) {
+ buttonDelegate = [[buttonDelegateClass new] autorelease];
+ [delegates addObject:buttonDelegate];
+ }
+ [buttonDelegate registerButton:b];
+ uiButtonOnClicked(b, defaultOnClicked, NULL);
+
+ return b;
+}
diff --git a/src/libui_sdl/libui/darwin/checkbox.m b/src/libui_sdl/libui/darwin/checkbox.m
new file mode 100644
index 0000000..dd1ce09
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/checkbox.m
@@ -0,0 +1,129 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+struct uiCheckbox {
+ uiDarwinControl c;
+ NSButton *button;
+ void (*onToggled)(uiCheckbox *, void *);
+ void *onToggledData;
+};
+
+@interface checkboxDelegateClass : NSObject {
+ struct mapTable *buttons;
+}
+- (IBAction)onToggled:(id)sender;
+- (void)registerCheckbox:(uiCheckbox *)c;
+- (void)unregisterCheckbox:(uiCheckbox *)c;
+@end
+
+@implementation checkboxDelegateClass
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ self->buttons = newMap();
+ return self;
+}
+
+- (void)dealloc
+{
+ mapDestroy(self->buttons);
+ [super dealloc];
+}
+
+- (IBAction)onToggled:(id)sender
+{
+ uiCheckbox *c;
+
+ c = (uiCheckbox *) mapGet(self->buttons, sender);
+ (*(c->onToggled))(c, c->onToggledData);
+}
+
+- (void)registerCheckbox:(uiCheckbox *)c
+{
+ mapSet(self->buttons, c->button, c);
+ [c->button setTarget:self];
+ [c->button setAction:@selector(onToggled:)];
+}
+
+- (void)unregisterCheckbox:(uiCheckbox *)c
+{
+ [c->button setTarget:nil];
+ mapDelete(self->buttons, c->button);
+}
+
+@end
+
+static checkboxDelegateClass *checkboxDelegate = nil;
+
+uiDarwinControlAllDefaultsExceptDestroy(uiCheckbox, button)
+
+static void uiCheckboxDestroy(uiControl *cc)
+{
+ uiCheckbox *c = uiCheckbox(cc);
+
+ [checkboxDelegate unregisterCheckbox:c];
+ [c->button release];
+ uiFreeControl(uiControl(c));
+}
+
+char *uiCheckboxText(uiCheckbox *c)
+{
+ return uiDarwinNSStringToText([c->button title]);
+}
+
+void uiCheckboxSetText(uiCheckbox *c, const char *text)
+{
+ [c->button setTitle:toNSString(text)];
+}
+
+void uiCheckboxOnToggled(uiCheckbox *c, void (*f)(uiCheckbox *, void *), void *data)
+{
+ c->onToggled = f;
+ c->onToggledData = data;
+}
+
+int uiCheckboxChecked(uiCheckbox *c)
+{
+ return [c->button state] == NSOnState;
+}
+
+void uiCheckboxSetChecked(uiCheckbox *c, int checked)
+{
+ NSInteger state;
+
+ state = NSOnState;
+ if (!checked)
+ state = NSOffState;
+ [c->button setState:state];
+}
+
+static void defaultOnToggled(uiCheckbox *c, void *data)
+{
+ // do nothing
+}
+
+uiCheckbox *uiNewCheckbox(const char *text)
+{
+ uiCheckbox *c;
+
+ uiDarwinNewControl(uiCheckbox, c);
+
+ c->button = [[NSButton alloc] initWithFrame:NSZeroRect];
+ [c->button setTitle:toNSString(text)];
+ [c->button setButtonType:NSSwitchButton];
+ // doesn't seem to have an associated bezel style
+ [c->button setBordered:NO];
+ [c->button setTransparent:NO];
+ uiDarwinSetControlFont(c->button, NSRegularControlSize);
+
+ if (checkboxDelegate == nil) {
+ checkboxDelegate = [[checkboxDelegateClass new] autorelease];
+ [delegates addObject:checkboxDelegate];
+ }
+ [checkboxDelegate registerCheckbox:c];
+ uiCheckboxOnToggled(c, defaultOnToggled, NULL);
+
+ return c;
+}
diff --git a/src/libui_sdl/libui/darwin/colorbutton.m b/src/libui_sdl/libui/darwin/colorbutton.m
new file mode 100644
index 0000000..83b6157
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/colorbutton.m
@@ -0,0 +1,159 @@
+// 15 may 2016
+#import "uipriv_darwin.h"
+
+// TODO no intrinsic height?
+
+@interface colorButton : NSColorWell {
+ uiColorButton *libui_b;
+ BOOL libui_changing;
+ BOOL libui_setting;
+}
+- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b;
+- (void)deactivateOnClose:(NSNotification *)note;
+- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a;
+- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a;
+@end
+
+// only one may be active at one time
+static colorButton *activeColorButton = nil;
+
+struct uiColorButton {
+ uiDarwinControl c;
+ colorButton *button;
+ void (*onChanged)(uiColorButton *, void *);
+ void *onChangedData;
+};
+
+@implementation colorButton
+
+- (id)initWithFrame:(NSRect)frame libuiColorButton:(uiColorButton *)b
+{
+ self = [super initWithFrame:frame];
+ if (self) {
+ // the default color is white; set it to black first (see -setColor: below for why we do it first)
+ [self libuiSetColor:0.0 g:0.0 b:0.0 a:1.0];
+
+ self->libui_b = b;
+ self->libui_changing = NO;
+ }
+ return self;
+}
+
+- (void)activate:(BOOL)exclusive
+{
+ if (activeColorButton != nil)
+ activeColorButton->libui_changing = YES;
+ [NSColorPanel setPickerMask:NSColorPanelAllModesMask];
+ [[NSColorPanel sharedColorPanel] setShowsAlpha:YES];
+ [super activate:YES];
+ activeColorButton = self;
+ // see stddialogs.m for details
+ [[NSColorPanel sharedColorPanel] setWorksWhenModal:NO];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(deactivateOnClose:)
+ name:NSWindowWillCloseNotification
+ object:[NSColorPanel sharedColorPanel]];
+}
+
+- (void)deactivate
+{
+ [super deactivate];
+ activeColorButton = nil;
+ if (!self->libui_changing)
+ [[NSColorPanel sharedColorPanel] orderOut:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:NSWindowWillCloseNotification
+ object:[NSColorPanel sharedColorPanel]];
+ self->libui_changing = NO;
+}
+
+- (void)deactivateOnClose:(NSNotification *)note
+{
+ [self deactivate];
+}
+
+- (void)setColor:(NSColor *)color
+{
+ uiColorButton *b = self->libui_b;
+
+ [super setColor:color];
+ // this is called by NSColorWell's init, so we have to guard
+ // also don't signal during a programmatic change
+ if (b != nil && !self->libui_setting)
+ (*(b->onChanged))(b, b->onChangedData);
+}
+
+- (void)libuiColor:(double *)r g:(double *)g b:(double *)b a:(double *)a
+{
+ NSColor *rgba;
+ CGFloat cr, cg, cb, ca;
+
+ // the given color may not be an RGBA color, which will cause the -getRed:green:blue:alpha: call to throw an exception
+ rgba = [[self color] colorUsingColorSpace:[NSColorSpace sRGBColorSpace]];
+ [rgba getRed:&cr green:&cg blue:&cb alpha:&ca];
+ *r = cr;
+ *g = cg;
+ *b = cb;
+ *a = ca;
+ // rgba will be autoreleased since it isn't a new or init call
+}
+
+- (void)libuiSetColor:(double)r g:(double)g b:(double)b a:(double)a
+{
+ self->libui_setting = YES;
+ [self setColor:[NSColor colorWithSRGBRed:r green:g blue:b alpha:a]];
+ self->libui_setting = NO;
+}
+
+// NSColorWell has no intrinsic size by default; give it the default Interface Builder size.
+- (NSSize)intrinsicContentSize
+{
+ return NSMakeSize(44, 23);
+}
+
+@end
+
+uiDarwinControlAllDefaults(uiColorButton, button)
+
+// we do not want color change events to be sent to any controls other than the color buttons
+// see main.m for more details
+BOOL colorButtonInhibitSendAction(SEL sel, id from, id to)
+{
+ if (sel != @selector(changeColor:))
+ return NO;
+ return ![to isKindOfClass:[colorButton class]];
+}
+
+static void defaultOnChanged(uiColorButton *b, void *data)
+{
+ // do nothing
+}
+
+void uiColorButtonColor(uiColorButton *b, double *r, double *g, double *bl, double *a)
+{
+ [b->button libuiColor:r g:g b:bl a:a];
+}
+
+void uiColorButtonSetColor(uiColorButton *b, double r, double g, double bl, double a)
+{
+ [b->button libuiSetColor:r g:g b:bl a:a];
+}
+
+void uiColorButtonOnChanged(uiColorButton *b, void (*f)(uiColorButton *, void *), void *data)
+{
+ b->onChanged = f;
+ b->onChangedData = data;
+}
+
+uiColorButton *uiNewColorButton(void)
+{
+ uiColorButton *b;
+
+ uiDarwinNewControl(uiColorButton, b);
+
+ b->button = [[colorButton alloc] initWithFrame:NSZeroRect libuiColorButton:b];
+
+ uiColorButtonOnChanged(b, defaultOnChanged, NULL);
+
+ return b;
+}
diff --git a/src/libui_sdl/libui/darwin/combobox.m b/src/libui_sdl/libui/darwin/combobox.m
new file mode 100644
index 0000000..89a2e28
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/combobox.m
@@ -0,0 +1,145 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
+// NSPopUpButton is fine.
+#define comboboxWidth 96
+
+struct uiCombobox {
+ uiDarwinControl c;
+ NSPopUpButton *pb;
+ NSArrayController *pbac;
+ void (*onSelected)(uiCombobox *, void *);
+ void *onSelectedData;
+};
+
+@interface comboboxDelegateClass : NSObject {
+ struct mapTable *comboboxes;
+}
+- (IBAction)onSelected:(id)sender;
+- (void)registerCombobox:(uiCombobox *)c;
+- (void)unregisterCombobox:(uiCombobox *)c;
+@end
+
+@implementation comboboxDelegateClass
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ self->comboboxes = newMap();
+ return self;
+}
+
+- (void)dealloc
+{
+ mapDestroy(self->comboboxes);
+ [super dealloc];
+}
+
+- (IBAction)onSelected:(id)sender
+{
+ uiCombobox *c;
+
+ c = uiCombobox(mapGet(self->comboboxes, sender));
+ (*(c->onSelected))(c, c->onSelectedData);
+}
+
+- (void)registerCombobox:(uiCombobox *)c
+{
+ mapSet(self->comboboxes, c->pb, c);
+ [c->pb setTarget:self];
+ [c->pb setAction:@selector(onSelected:)];
+}
+
+- (void)unregisterCombobox:(uiCombobox *)c
+{
+ [c->pb setTarget:nil];
+ mapDelete(self->comboboxes, c->pb);
+}
+
+@end
+
+static comboboxDelegateClass *comboboxDelegate = nil;
+
+uiDarwinControlAllDefaultsExceptDestroy(uiCombobox, pb)
+
+static void uiComboboxDestroy(uiControl *cc)
+{
+ uiCombobox *c = uiCombobox(cc);
+
+ [comboboxDelegate unregisterCombobox:c];
+ [c->pb unbind:@"contentObjects"];
+ [c->pb unbind:@"selectedIndex"];
+ [c->pbac release];
+ [c->pb release];
+ uiFreeControl(uiControl(c));
+}
+
+void uiComboboxAppend(uiCombobox *c, const char *text)
+{
+ [c->pbac addObject:toNSString(text)];
+}
+
+int uiComboboxSelected(uiCombobox *c)
+{
+ return [c->pb indexOfSelectedItem];
+}
+
+void uiComboboxSetSelected(uiCombobox *c, int n)
+{
+ [c->pb selectItemAtIndex:n];
+}
+
+void uiComboboxOnSelected(uiCombobox *c, void (*f)(uiCombobox *c, void *data), void *data)
+{
+ c->onSelected = f;
+ c->onSelectedData = data;
+}
+
+static void defaultOnSelected(uiCombobox *c, void *data)
+{
+ // do nothing
+}
+
+uiCombobox *uiNewCombobox(void)
+{
+ uiCombobox *c;
+ NSPopUpButtonCell *pbcell;
+
+ uiDarwinNewControl(uiCombobox, c);
+
+ c->pb = [[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO];
+ [c->pb setPreferredEdge:NSMinYEdge];
+ pbcell = (NSPopUpButtonCell *) [c->pb cell];
+ [pbcell setArrowPosition:NSPopUpArrowAtBottom];
+ // the font defined by Interface Builder is Menu 13, which is lol
+ // just use the regular control size for consistency
+ uiDarwinSetControlFont(c->pb, NSRegularControlSize);
+
+ // NSPopUpButton doesn't work like a combobox
+ // - it automatically selects the first item
+ // - it doesn't support duplicates
+ // but we can use a NSArrayController and Cocoa bindings to bypass these restrictions
+ c->pbac = [NSArrayController new];
+ [c->pbac setAvoidsEmptySelection:NO];
+ [c->pbac setSelectsInsertedObjects:NO];
+ [c->pbac setAutomaticallyRearrangesObjects:NO];
+ [c->pb bind:@"contentValues"
+ toObject:c->pbac
+ withKeyPath:@"arrangedObjects"
+ options:nil];
+ [c->pb bind:@"selectedIndex"
+ toObject:c->pbac
+ withKeyPath:@"selectionIndex"
+ options:nil];
+
+ if (comboboxDelegate == nil) {
+ comboboxDelegate = [[comboboxDelegateClass new] autorelease];
+ [delegates addObject:comboboxDelegate];
+ }
+ [comboboxDelegate registerCombobox:c];
+ uiComboboxOnSelected(c, defaultOnSelected, NULL);
+
+ return c;
+}
diff --git a/src/libui_sdl/libui/darwin/control.m b/src/libui_sdl/libui/darwin/control.m
new file mode 100644
index 0000000..9eaf47a
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/control.m
@@ -0,0 +1,84 @@
+// 16 august 2015
+#import "uipriv_darwin.h"
+
+void uiDarwinControlSyncEnableState(uiDarwinControl *c, int state)
+{
+ (*(c->SyncEnableState))(c, state);
+}
+
+void uiDarwinControlSetSuperview(uiDarwinControl *c, NSView *superview)
+{
+ (*(c->SetSuperview))(c, superview);
+}
+
+BOOL uiDarwinControlHugsTrailingEdge(uiDarwinControl *c)
+{
+ return (*(c->HugsTrailingEdge))(c);
+}
+
+BOOL uiDarwinControlHugsBottom(uiDarwinControl *c)
+{
+ return (*(c->HugsBottom))(c);
+}
+
+void uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl *c)
+{
+ (*(c->ChildEdgeHuggingChanged))(c);
+}
+
+NSLayoutPriority uiDarwinControlHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
+{
+ return (*(c->HuggingPriority))(c, orientation);
+}
+
+void uiDarwinControlSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
+{
+ (*(c->SetHuggingPriority))(c, priority, orientation);
+}
+
+void uiDarwinControlChildVisibilityChanged(uiDarwinControl *c)
+{
+ (*(c->ChildVisibilityChanged))(c);
+}
+
+void uiDarwinSetControlFont(NSControl *c, NSControlSize size)
+{
+ [c setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]];
+}
+
+#define uiDarwinControlSignature 0x44617277
+
+uiDarwinControl *uiDarwinAllocControl(size_t n, uint32_t typesig, const char *typenamestr)
+{
+ return uiDarwinControl(uiAllocControl(n, uiDarwinControlSignature, typesig, typenamestr));
+}
+
+BOOL uiDarwinShouldStopSyncEnableState(uiDarwinControl *c, BOOL enabled)
+{
+ int ce;
+
+ ce = uiControlEnabled(uiControl(c));
+ // only stop if we're going from disabled back to enabled; don't stop under any other condition
+ // (if we stop when going from enabled to disabled then enabled children of a disabled control won't get disabled at the OS level)
+ if (!ce && enabled)
+ return YES;
+ return NO;
+}
+
+void uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl *c)
+{
+ uiControl *parent;
+
+ parent = uiControlParent(uiControl(c));
+ if (parent != NULL)
+ uiDarwinControlChildEdgeHuggingChanged(uiDarwinControl(parent));
+}
+
+void uiDarwinNotifyVisibilityChanged(uiDarwinControl *c)
+{
+ uiControl *parent;
+
+ parent = uiControlParent(uiControl(c));
+ if (parent != NULL)
+ uiDarwinControlChildVisibilityChanged(uiDarwinControl(parent));
+}
diff --git a/src/libui_sdl/libui/darwin/datetimepicker.m b/src/libui_sdl/libui/darwin/datetimepicker.m
new file mode 100644
index 0000000..44364d9
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/datetimepicker.m
@@ -0,0 +1,42 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+struct uiDateTimePicker {
+ uiDarwinControl c;
+ NSDatePicker *dp;
+};
+
+uiDarwinControlAllDefaults(uiDateTimePicker, dp)
+
+static uiDateTimePicker *finishNewDateTimePicker(NSDatePickerElementFlags elements)
+{
+ uiDateTimePicker *d;
+
+ uiDarwinNewControl(uiDateTimePicker, d);
+
+ d->dp = [[NSDatePicker alloc] initWithFrame:NSZeroRect];
+ [d->dp setBordered:NO];
+ [d->dp setBezeled:YES];
+ [d->dp setDrawsBackground:YES];
+ [d->dp setDatePickerStyle:NSTextFieldAndStepperDatePickerStyle];
+ [d->dp setDatePickerElements:elements];
+ [d->dp setDatePickerMode:NSSingleDateMode];
+ uiDarwinSetControlFont(d->dp, NSRegularControlSize);
+
+ return d;
+}
+
+uiDateTimePicker *uiNewDateTimePicker(void)
+{
+ return finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag | NSHourMinuteSecondDatePickerElementFlag);
+}
+
+uiDateTimePicker *uiNewDatePicker(void)
+{
+ return finishNewDateTimePicker(NSYearMonthDayDatePickerElementFlag);
+}
+
+uiDateTimePicker *uiNewTimePicker(void)
+{
+ return finishNewDateTimePicker(NSHourMinuteSecondDatePickerElementFlag);
+}
diff --git a/src/libui_sdl/libui/darwin/debug.m b/src/libui_sdl/libui/darwin/debug.m
new file mode 100644
index 0000000..c91c6a7
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/debug.m
@@ -0,0 +1,19 @@
+// 13 may 2016
+#import "uipriv_darwin.h"
+
+// LONGTERM don't halt on release builds
+
+void realbug(const char *file, const char *line, const char *func, const char *prefix, const char *format, va_list ap)
+{
+ NSMutableString *str;
+ NSString *formatted;
+
+ str = [NSMutableString new];
+ [str appendString:[NSString stringWithFormat:@"[libui] %s:%s:%s() %s", file, line, func, prefix]];
+ formatted = [[NSString alloc] initWithFormat:[NSString stringWithUTF8String:format] arguments:ap];
+ [str appendString:formatted];
+ [formatted release];
+ NSLog(@"%@", str);
+ [str release];
+ __builtin_trap();
+}
diff --git a/src/libui_sdl/libui/darwin/draw.m b/src/libui_sdl/libui/darwin/draw.m
new file mode 100644
index 0000000..262ad3e
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/draw.m
@@ -0,0 +1,454 @@
+// 6 september 2015
+#import "uipriv_darwin.h"
+
+struct uiDrawPath {
+ CGMutablePathRef path;
+ uiDrawFillMode fillMode;
+ BOOL ended;
+};
+
+uiDrawPath *uiDrawNewPath(uiDrawFillMode mode)
+{
+ uiDrawPath *p;
+
+ p = uiNew(uiDrawPath);
+ p->path = CGPathCreateMutable();
+ p->fillMode = mode;
+ return p;
+}
+
+void uiDrawFreePath(uiDrawPath *p)
+{
+ CGPathRelease((CGPathRef) (p->path));
+ uiFree(p);
+}
+
+void uiDrawPathNewFigure(uiDrawPath *p, double x, double y)
+{
+ if (p->ended)
+ userbug("You cannot call uiDrawPathNewFigure() on a uiDrawPath that has already been ended. (path; %p)", p);
+ CGPathMoveToPoint(p->path, NULL, x, y);
+}
+
+void uiDrawPathNewFigureWithArc(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
+{
+ double sinStart, cosStart;
+ double startx, starty;
+
+ if (p->ended)
+ userbug("You cannot call uiDrawPathNewFigureWithArc() on a uiDrawPath that has already been ended. (path; %p)", p);
+ sinStart = sin(startAngle);
+ cosStart = cos(startAngle);
+ startx = xCenter + radius * cosStart;
+ starty = yCenter + radius * sinStart;
+ CGPathMoveToPoint(p->path, NULL, startx, starty);
+ uiDrawPathArcTo(p, xCenter, yCenter, radius, startAngle, sweep, negative);
+}
+
+void uiDrawPathLineTo(uiDrawPath *p, double x, double y)
+{
+ // TODO refine this to require being in a path
+ if (p->ended)
+ implbug("attempt to add line to ended path in uiDrawPathLineTo()");
+ CGPathAddLineToPoint(p->path, NULL, x, y);
+}
+
+void uiDrawPathArcTo(uiDrawPath *p, double xCenter, double yCenter, double radius, double startAngle, double sweep, int negative)
+{
+ bool cw;
+
+ // TODO likewise
+ if (p->ended)
+ implbug("attempt to add arc to ended path in uiDrawPathArcTo()");
+ if (sweep > 2 * uiPi)
+ sweep = 2 * uiPi;
+ cw = false;
+ if (negative)
+ cw = true;
+ CGPathAddArc(p->path, NULL,
+ xCenter, yCenter,
+ radius,
+ startAngle, startAngle + sweep,
+ cw);
+}
+
+void uiDrawPathBezierTo(uiDrawPath *p, double c1x, double c1y, double c2x, double c2y, double endX, double endY)
+{
+ // TODO likewise
+ if (p->ended)
+ implbug("attempt to add bezier to ended path in uiDrawPathBezierTo()");
+ CGPathAddCurveToPoint(p->path, NULL,
+ c1x, c1y,
+ c2x, c2y,
+ endX, endY);
+}
+
+void uiDrawPathCloseFigure(uiDrawPath *p)
+{
+ // TODO likewise
+ if (p->ended)
+ implbug("attempt to close figure of ended path in uiDrawPathCloseFigure()");
+ CGPathCloseSubpath(p->path);
+}
+
+void uiDrawPathAddRectangle(uiDrawPath *p, double x, double y, double width, double height)
+{
+ if (p->ended)
+ userbug("You cannot call uiDrawPathAddRectangle() on a uiDrawPath that has already been ended. (path; %p)", p);
+ CGPathAddRect(p->path, NULL, CGRectMake(x, y, width, height));
+}
+
+void uiDrawPathEnd(uiDrawPath *p)
+{
+ p->ended = TRUE;
+}
+
+struct uiDrawContext {
+ CGContextRef c;
+ CGFloat height; // needed for text; see below
+};
+
+uiDrawContext *newContext(CGContextRef ctxt, CGFloat height)
+{
+ uiDrawContext *c;
+
+ c = uiNew(uiDrawContext);
+ c->c = ctxt;
+ c->height = height;
+ return c;
+}
+
+void freeContext(uiDrawContext *c)
+{
+ uiFree(c);
+}
+
+// a stroke is identical to a fill of a stroked path
+// we need to do this in order to stroke with a gradient; see http://stackoverflow.com/a/25034854/3408572
+// doing this for other brushes works too
+void uiDrawStroke(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b, uiDrawStrokeParams *p)
+{
+ CGLineCap cap;
+ CGLineJoin join;
+ CGPathRef dashPath;
+ CGFloat *dashes;
+ size_t i;
+ uiDrawPath p2;
+
+ if (!path->ended)
+ userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
+
+ switch (p->Cap) {
+ case uiDrawLineCapFlat:
+ cap = kCGLineCapButt;
+ break;
+ case uiDrawLineCapRound:
+ cap = kCGLineCapRound;
+ break;
+ case uiDrawLineCapSquare:
+ cap = kCGLineCapSquare;
+ break;
+ }
+ switch (p->Join) {
+ case uiDrawLineJoinMiter:
+ join = kCGLineJoinMiter;
+ break;
+ case uiDrawLineJoinRound:
+ join = kCGLineJoinRound;
+ break;
+ case uiDrawLineJoinBevel:
+ join = kCGLineJoinBevel;
+ break;
+ }
+
+ // create a temporary path identical to the previous one
+ dashPath = (CGPathRef) path->path;
+ if (p->NumDashes != 0) {
+ dashes = (CGFloat *) uiAlloc(p->NumDashes * sizeof (CGFloat), "CGFloat[]");
+ for (i = 0; i < p->NumDashes; i++)
+ dashes[i] = p->Dashes[i];
+ dashPath = CGPathCreateCopyByDashingPath(path->path,
+ NULL,
+ p->DashPhase,
+ dashes,
+ p->NumDashes);
+ uiFree(dashes);
+ }
+ // the documentation is wrong: this produces a path suitable for calling CGPathCreateCopyByStrokingPath(), not for filling directly
+ // the cast is safe; we never modify the CGPathRef and always cast it back to a CGPathRef anyway
+ p2.path = (CGMutablePathRef) CGPathCreateCopyByStrokingPath(dashPath,
+ NULL,
+ p->Thickness,
+ cap,
+ join,
+ p->MiterLimit);
+ if (p->NumDashes != 0)
+ CGPathRelease(dashPath);
+
+ // always draw stroke fills using the winding rule
+ // otherwise intersecting figures won't draw correctly
+ p2.fillMode = uiDrawFillModeWinding;
+ p2.ended = path->ended;
+ uiDrawFill(c, &p2, b);
+ // and clean up
+ CGPathRelease((CGPathRef) (p2.path));
+}
+
+// for a solid fill, we can merely have Core Graphics fill directly
+static void fillSolid(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
+{
+ // TODO this uses DeviceRGB; switch to sRGB
+ CGContextSetRGBFillColor(ctxt, b->R, b->G, b->B, b->A);
+ switch (p->fillMode) {
+ case uiDrawFillModeWinding:
+ CGContextFillPath(ctxt);
+ break;
+ case uiDrawFillModeAlternate:
+ CGContextEOFillPath(ctxt);
+ break;
+ }
+}
+
+// for a gradient fill, we need to clip to the path and then draw the gradient
+// see http://stackoverflow.com/a/25034854/3408572
+static void fillGradient(CGContextRef ctxt, uiDrawPath *p, uiDrawBrush *b)
+{
+ CGGradientRef gradient;
+ CGColorSpaceRef colorspace;
+ CGFloat *colors;
+ CGFloat *locations;
+ size_t i;
+
+ // gradients need a color space
+ // for consistency with windows, use sRGB
+ colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
+
+ // make the gradient
+ colors = uiAlloc(b->NumStops * 4 * sizeof (CGFloat), "CGFloat[]");
+ locations = uiAlloc(b->NumStops * sizeof (CGFloat), "CGFloat[]");
+ for (i = 0; i < b->NumStops; i++) {
+ colors[i * 4 + 0] = b->Stops[i].R;
+ colors[i * 4 + 1] = b->Stops[i].G;
+ colors[i * 4 + 2] = b->Stops[i].B;
+ colors[i * 4 + 3] = b->Stops[i].A;
+ locations[i] = b->Stops[i].Pos;
+ }
+ gradient = CGGradientCreateWithColorComponents(colorspace, colors, locations, b->NumStops);
+ uiFree(locations);
+ uiFree(colors);
+
+ // because we're mucking with clipping, we need to save the graphics state and restore it later
+ CGContextSaveGState(ctxt);
+
+ // clip
+ switch (p->fillMode) {
+ case uiDrawFillModeWinding:
+ CGContextClip(ctxt);
+ break;
+ case uiDrawFillModeAlternate:
+ CGContextEOClip(ctxt);
+ break;
+ }
+
+ // draw the gradient
+ switch (b->Type) {
+ case uiDrawBrushTypeLinearGradient:
+ CGContextDrawLinearGradient(ctxt,
+ gradient,
+ CGPointMake(b->X0, b->Y0),
+ CGPointMake(b->X1, b->Y1),
+ kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
+ break;
+ case uiDrawBrushTypeRadialGradient:
+ CGContextDrawRadialGradient(ctxt,
+ gradient,
+ CGPointMake(b->X0, b->Y0),
+ // make the start circle radius 0 to make it a point
+ 0,
+ CGPointMake(b->X1, b->Y1),
+ b->OuterRadius,
+ kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
+ break;
+ }
+
+ // and clean up
+ CGContextRestoreGState(ctxt);
+ CGGradientRelease(gradient);
+ CGColorSpaceRelease(colorspace);
+}
+
+void uiDrawFill(uiDrawContext *c, uiDrawPath *path, uiDrawBrush *b)
+{
+ if (!path->ended)
+ userbug("You cannot call uiDrawStroke() on a uiDrawPath that has not been ended. (path: %p)", path);
+ CGContextAddPath(c->c, (CGPathRef) (path->path));
+ switch (b->Type) {
+ case uiDrawBrushTypeSolid:
+ fillSolid(c->c, path, b);
+ return;
+ case uiDrawBrushTypeLinearGradient:
+ case uiDrawBrushTypeRadialGradient:
+ fillGradient(c->c, path, b);
+ return;
+// case uiDrawBrushTypeImage:
+ // TODO
+ return;
+ }
+ userbug("Unknown brush type %d passed to uiDrawFill().", b->Type);
+}
+
+static void m2c(uiDrawMatrix *m, CGAffineTransform *c)
+{
+ c->a = m->M11;
+ c->b = m->M12;
+ c->c = m->M21;
+ c->d = m->M22;
+ c->tx = m->M31;
+ c->ty = m->M32;
+}
+
+static void c2m(CGAffineTransform *c, uiDrawMatrix *m)
+{
+ m->M11 = c->a;
+ m->M12 = c->b;
+ m->M21 = c->c;
+ m->M22 = c->d;
+ m->M31 = c->tx;
+ m->M32 = c->ty;
+}
+
+void uiDrawMatrixTranslate(uiDrawMatrix *m, double x, double y)
+{
+ CGAffineTransform c;
+
+ m2c(m, &c);
+ c = CGAffineTransformTranslate(c, x, y);
+ c2m(&c, m);
+}
+
+void uiDrawMatrixScale(uiDrawMatrix *m, double xCenter, double yCenter, double x, double y)
+{
+ CGAffineTransform c;
+ double xt, yt;
+
+ m2c(m, &c);
+ xt = x;
+ yt = y;
+ scaleCenter(xCenter, yCenter, &xt, &yt);
+ c = CGAffineTransformTranslate(c, xt, yt);
+ c = CGAffineTransformScale(c, x, y);
+ c = CGAffineTransformTranslate(c, -xt, -yt);
+ c2m(&c, m);
+}
+
+void uiDrawMatrixRotate(uiDrawMatrix *m, double x, double y, double amount)
+{
+ CGAffineTransform c;
+
+ m2c(m, &c);
+ c = CGAffineTransformTranslate(c, x, y);
+ c = CGAffineTransformRotate(c, amount);
+ c = CGAffineTransformTranslate(c, -x, -y);
+ c2m(&c, m);
+}
+
+void uiDrawMatrixSkew(uiDrawMatrix *m, double x, double y, double xamount, double yamount)
+{
+ fallbackSkew(m, x, y, xamount, yamount);
+}
+
+void uiDrawMatrixMultiply(uiDrawMatrix *dest, uiDrawMatrix *src)
+{
+ CGAffineTransform c;
+ CGAffineTransform d;
+
+ m2c(dest, &c);
+ m2c(src, &d);
+ c = CGAffineTransformConcat(c, d);
+ c2m(&c, dest);
+}
+
+// there is no test for invertibility; CGAffineTransformInvert() is merely documented as returning the matrix unchanged if it isn't invertible
+// therefore, special care must be taken to catch matrices who are their own inverses
+// TODO figure out which matrices these are and do so
+int uiDrawMatrixInvertible(uiDrawMatrix *m)
+{
+ CGAffineTransform c, d;
+
+ m2c(m, &c);
+ d = CGAffineTransformInvert(c);
+ return CGAffineTransformEqualToTransform(c, d) == false;
+}
+
+int uiDrawMatrixInvert(uiDrawMatrix *m)
+{
+ CGAffineTransform c, d;
+
+ m2c(m, &c);
+ d = CGAffineTransformInvert(c);
+ if (CGAffineTransformEqualToTransform(c, d))
+ return 0;
+ c2m(&d, m);
+ return 1;
+}
+
+void uiDrawMatrixTransformPoint(uiDrawMatrix *m, double *x, double *y)
+{
+ CGAffineTransform c;
+ CGPoint p;
+
+ m2c(m, &c);
+ p = CGPointApplyAffineTransform(CGPointMake(*x, *y), c);
+ *x = p.x;
+ *y = p.y;
+}
+
+void uiDrawMatrixTransformSize(uiDrawMatrix *m, double *x, double *y)
+{
+ CGAffineTransform c;
+ CGSize s;
+
+ m2c(m, &c);
+ s = CGSizeApplyAffineTransform(CGSizeMake(*x, *y), c);
+ *x = s.width;
+ *y = s.height;
+}
+
+void uiDrawTransform(uiDrawContext *c, uiDrawMatrix *m)
+{
+ CGAffineTransform cm;
+
+ m2c(m, &cm);
+ CGContextConcatCTM(c->c, cm);
+}
+
+void uiDrawClip(uiDrawContext *c, uiDrawPath *path)
+{
+ if (!path->ended)
+ userbug("You cannot call uiDrawCilp() on a uiDrawPath that has not been ended. (path: %p)", path);
+ CGContextAddPath(c->c, (CGPathRef) (path->path));
+ switch (path->fillMode) {
+ case uiDrawFillModeWinding:
+ CGContextClip(c->c);
+ break;
+ case uiDrawFillModeAlternate:
+ CGContextEOClip(c->c);
+ break;
+ }
+}
+
+// TODO figure out what besides transforms these save/restore on all platforms
+void uiDrawSave(uiDrawContext *c)
+{
+ CGContextSaveGState(c->c);
+}
+
+void uiDrawRestore(uiDrawContext *c)
+{
+ CGContextRestoreGState(c->c);
+}
+
+void uiDrawText(uiDrawContext *c, double x, double y, uiDrawTextLayout *layout)
+{
+ doDrawText(c->c, c->height, x, y, layout);
+}
diff --git a/src/libui_sdl/libui/darwin/drawtext.m b/src/libui_sdl/libui/darwin/drawtext.m
new file mode 100644
index 0000000..c376536
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/drawtext.m
@@ -0,0 +1,655 @@
+// 6 september 2015
+#import "uipriv_darwin.h"
+
+// TODO
+#define complain(...) implbug(__VA_ARGS__)
+
+// TODO double-check that we are properly handling allocation failures (or just toll free bridge from cocoa)
+struct uiDrawFontFamilies {
+ CFArrayRef fonts;
+};
+
+uiDrawFontFamilies *uiDrawListFontFamilies(void)
+{
+ uiDrawFontFamilies *ff;
+
+ ff = uiNew(uiDrawFontFamilies);
+ ff->fonts = CTFontManagerCopyAvailableFontFamilyNames();
+ if (ff->fonts == NULL)
+ implbug("error getting available font names (no reason specified) (TODO)");
+ return ff;
+}
+
+int uiDrawFontFamiliesNumFamilies(uiDrawFontFamilies *ff)
+{
+ return CFArrayGetCount(ff->fonts);
+}
+
+char *uiDrawFontFamiliesFamily(uiDrawFontFamilies *ff, int n)
+{
+ CFStringRef familystr;
+ char *family;
+
+ familystr = (CFStringRef) CFArrayGetValueAtIndex(ff->fonts, n);
+ // toll-free bridge
+ family = uiDarwinNSStringToText((NSString *) familystr);
+ // Get Rule means we do not free familystr
+ return family;
+}
+
+void uiDrawFreeFontFamilies(uiDrawFontFamilies *ff)
+{
+ CFRelease(ff->fonts);
+ uiFree(ff);
+}
+
+struct uiDrawTextFont {
+ CTFontRef f;
+};
+
+uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain)
+{
+ uiDrawTextFont *font;
+
+ font = uiNew(uiDrawTextFont);
+ font->f = f;
+ if (retain)
+ CFRetain(font->f);
+ return font;
+}
+
+uiDrawTextFont *mkTextFontFromNSFont(NSFont *f)
+{
+ // toll-free bridging; we do retain, though
+ return mkTextFont((CTFontRef) f, YES);
+}
+
+static CFMutableDictionaryRef newAttrList(void)
+{
+ CFMutableDictionaryRef attr;
+
+ attr = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ if (attr == NULL)
+ complain("error creating attribute dictionary in newAttrList()()");
+ return attr;
+}
+
+static void addFontFamilyAttr(CFMutableDictionaryRef attr, const char *family)
+{
+ CFStringRef cfstr;
+
+ cfstr = CFStringCreateWithCString(NULL, family, kCFStringEncodingUTF8);
+ if (cfstr == NULL)
+ complain("error creating font family name CFStringRef in addFontFamilyAttr()");
+ CFDictionaryAddValue(attr, kCTFontFamilyNameAttribute, cfstr);
+ CFRelease(cfstr); // dictionary holds its own reference
+}
+
+static void addFontSizeAttr(CFMutableDictionaryRef attr, double size)
+{
+ CFNumberRef n;
+
+ n = CFNumberCreate(NULL, kCFNumberDoubleType, &size);
+ CFDictionaryAddValue(attr, kCTFontSizeAttribute, n);
+ CFRelease(n);
+}
+
+#if 0
+TODO
+// See http://stackoverflow.com/questions/4810409/does-coretext-support-small-caps/4811371#4811371 and https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c for what these do
+// And fortunately, unlike the traits (see below), unmatched features are simply ignored without affecting the other features :D
+static void addFontSmallCapsAttr(CFMutableDictionaryRef attr)
+{
+ CFMutableArrayRef outerArray;
+ CFMutableDictionaryRef innerDict;
+ CFNumberRef numType, numSelector;
+ int num;
+
+ outerArray = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+ if (outerArray == NULL)
+ complain("error creating outer CFArray for adding small caps attributes in addFontSmallCapsAttr()");
+
+ // Apple's headers say these are deprecated, but a few fonts still rely on them
+ num = kLetterCaseType;
+ numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
+ num = kSmallCapsSelector;
+ numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
+ innerDict = newAttrList();
+ CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
+ CFRelease(numType);
+ CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
+ CFRelease(numSelector);
+ CFArrayAppendValue(outerArray, innerDict);
+ CFRelease(innerDict); // and likewise for CFArray
+
+ // these are the non-deprecated versions of the above; some fonts have these instead
+ num = kLowerCaseType;
+ numType = CFNumberCreate(NULL, kCFNumberIntType, &num);
+ num = kLowerCaseSmallCapsSelector;
+ numSelector = CFNumberCreate(NULL, kCFNumberIntType, &num);
+ innerDict = newAttrList();
+ CFDictionaryAddValue(innerDict, kCTFontFeatureTypeIdentifierKey, numType);
+ CFRelease(numType);
+ CFDictionaryAddValue(innerDict, kCTFontFeatureSelectorIdentifierKey, numSelector);
+ CFRelease(numSelector);
+ CFArrayAppendValue(outerArray, innerDict);
+ CFRelease(innerDict); // and likewise for CFArray
+
+ CFDictionaryAddValue(attr, kCTFontFeatureSettingsAttribute, outerArray);
+ CFRelease(outerArray);
+}
+#endif
+
+// Named constants for these were NOT added until 10.11, and even then they were added as external symbols instead of macros, so we can't use them directly :(
+// kode54 got these for me before I had access to El Capitan; thanks to him.
+#define ourNSFontWeightUltraLight -0.800000
+#define ourNSFontWeightThin -0.600000
+#define ourNSFontWeightLight -0.400000
+#define ourNSFontWeightRegular 0.000000
+#define ourNSFontWeightMedium 0.230000
+#define ourNSFontWeightSemibold 0.300000
+#define ourNSFontWeightBold 0.400000
+#define ourNSFontWeightHeavy 0.560000
+#define ourNSFontWeightBlack 0.620000
+static const CGFloat ctWeights[] = {
+ // yeah these two have their names swapped; blame Pango
+ [uiDrawTextWeightThin] = ourNSFontWeightUltraLight,
+ [uiDrawTextWeightUltraLight] = ourNSFontWeightThin,
+ [uiDrawTextWeightLight] = ourNSFontWeightLight,
+ // for this one let's go between Light and Regular
+ // we're doing nearest so if there happens to be an exact value hopefully it's close enough
+ [uiDrawTextWeightBook] = ourNSFontWeightLight + ((ourNSFontWeightRegular - ourNSFontWeightLight) / 2),
+ [uiDrawTextWeightNormal] = ourNSFontWeightRegular,
+ [uiDrawTextWeightMedium] = ourNSFontWeightMedium,
+ [uiDrawTextWeightSemiBold] = ourNSFontWeightSemibold,
+ [uiDrawTextWeightBold] = ourNSFontWeightBold,
+ // for this one let's go between Bold and Heavy
+ [uiDrawTextWeightUltraBold] = ourNSFontWeightBold + ((ourNSFontWeightHeavy - ourNSFontWeightBold) / 2),
+ [uiDrawTextWeightHeavy] = ourNSFontWeightHeavy,
+ [uiDrawTextWeightUltraHeavy] = ourNSFontWeightBlack,
+};
+
+// Unfortunately there are still no named constants for these.
+// Let's just use normalized widths.
+// As far as I can tell (OS X only ships with condensed fonts, not expanded fonts; TODO), regardless of condensed or expanded, negative means condensed and positive means expanded.
+// TODO verify this is correct
+static const CGFloat ctStretches[] = {
+ [uiDrawTextStretchUltraCondensed] = -1.0,
+ [uiDrawTextStretchExtraCondensed] = -0.75,
+ [uiDrawTextStretchCondensed] = -0.5,
+ [uiDrawTextStretchSemiCondensed] = -0.25,
+ [uiDrawTextStretchNormal] = 0.0,
+ [uiDrawTextStretchSemiExpanded] = 0.25,
+ [uiDrawTextStretchExpanded] = 0.5,
+ [uiDrawTextStretchExtraExpanded] = 0.75,
+ [uiDrawTextStretchUltraExpanded] = 1.0,
+};
+
+struct closeness {
+ CFIndex index;
+ CGFloat weight;
+ CGFloat italic;
+ CGFloat stretch;
+ CGFloat distance;
+};
+
+// Stupidity: CTFont requires an **exact match for the entire traits dictionary**, otherwise it will **drop ALL the traits**.
+// We have to implement the closest match ourselves.
+// Also we have to do this before adding the small caps flags, because the matching descriptors won't have those.
+CTFontDescriptorRef matchTraits(CTFontDescriptorRef against, uiDrawTextWeight weight, uiDrawTextItalic italic, uiDrawTextStretch stretch)
+{
+ CGFloat targetWeight;
+ CGFloat italicCloseness, obliqueCloseness, normalCloseness;
+ CGFloat targetStretch;
+ CFArrayRef matching;
+ CFIndex i, n;
+ struct closeness *closeness;
+ CTFontDescriptorRef current;
+ CTFontDescriptorRef out;
+
+ targetWeight = ctWeights[weight];
+ switch (italic) {
+ case uiDrawTextItalicNormal:
+ italicCloseness = 1;
+ obliqueCloseness = 1;
+ normalCloseness = 0;
+ break;
+ case uiDrawTextItalicOblique:
+ italicCloseness = 0.5;
+ obliqueCloseness = 0;
+ normalCloseness = 1;
+ break;
+ case uiDrawTextItalicItalic:
+ italicCloseness = 0;
+ obliqueCloseness = 0.5;
+ normalCloseness = 1;
+ break;
+ }
+ targetStretch = ctStretches[stretch];
+
+ matching = CTFontDescriptorCreateMatchingFontDescriptors(against, NULL);
+ if (matching == NULL)
+ // no matches; give the original back and hope for the best
+ return against;
+ n = CFArrayGetCount(matching);
+ if (n == 0) {
+ // likewise
+ CFRelease(matching);
+ return against;
+ }
+
+ closeness = (struct closeness *) uiAlloc(n * sizeof (struct closeness), "struct closeness[]");
+ for (i = 0; i < n; i++) {
+ CFDictionaryRef traits;
+ CFNumberRef cfnum;
+ CTFontSymbolicTraits symbolic;
+
+ closeness[i].index = i;
+
+ current = CFArrayGetValueAtIndex(matching, i);
+ traits = CTFontDescriptorCopyAttribute(current, kCTFontTraitsAttribute);
+ if (traits == NULL) {
+ // couldn't get traits; be safe by ranking it lowest
+ // LONGTERM figure out what the longest possible distances are
+ closeness[i].weight = 3;
+ closeness[i].italic = 2;
+ closeness[i].stretch = 3;
+ continue;
+ }
+
+ symbolic = 0; // assume no symbolic traits if none are listed
+ cfnum = CFDictionaryGetValue(traits, kCTFontSymbolicTrait);
+ if (cfnum != NULL) {
+ SInt32 s;
+
+ if (CFNumberGetValue(cfnum, kCFNumberSInt32Type, &s) == false)
+ complain("error getting symbolic traits in matchTraits()");
+ symbolic = (CTFontSymbolicTraits) s;
+ // Get rule; do not release cfnum
+ }
+
+ // now try weight
+ cfnum = CFDictionaryGetValue(traits, kCTFontWeightTrait);
+ if (cfnum != NULL) {
+ CGFloat val;
+
+ // LONGTERM instead of complaining for this and width and possibly also symbolic traits above, should we just fall through to the default?
+ if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
+ complain("error getting weight value in matchTraits()");
+ closeness[i].weight = val - targetWeight;
+ } else
+ // okay there's no weight key; let's try the literal meaning of the symbolic constant
+ // LONGTERM is the weight key guaranteed?
+ if ((symbolic & kCTFontBoldTrait) != 0)
+ closeness[i].weight = ourNSFontWeightBold - targetWeight;
+ else
+ closeness[i].weight = ourNSFontWeightRegular - targetWeight;
+
+ // italics is a bit harder because Core Text doesn't expose a concept of obliqueness
+ // Pango just does a g_strrstr() (backwards case-sensitive search) for "Oblique" in the font's style name (see https://git.gnome.org/browse/pango/tree/pango/pangocoretext-fontmap.c); let's do that too I guess
+ if ((symbolic & kCTFontItalicTrait) != 0)
+ closeness[i].italic = italicCloseness;
+ else {
+ CFStringRef styleName;
+ BOOL isOblique;
+
+ isOblique = NO; // default value
+ styleName = CTFontDescriptorCopyAttribute(current, kCTFontStyleNameAttribute);
+ if (styleName != NULL) {
+ CFRange range;
+
+ // note the use of the toll-free bridge for the string literal, since CFSTR() *can* return NULL
+ range = CFStringFind(styleName, (CFStringRef) @"Oblique", kCFCompareBackwards);
+ if (range.location != kCFNotFound)
+ isOblique = YES;
+ CFRelease(styleName);
+ }
+ if (isOblique)
+ closeness[i].italic = obliqueCloseness;
+ else
+ closeness[i].italic = normalCloseness;
+ }
+
+ // now try width
+ // TODO this does not seem to be enough for Skia's extended variants; the width trait is 0 but the Expanded flag is on
+ // TODO verify the rest of this matrix (what matrix?)
+ cfnum = CFDictionaryGetValue(traits, kCTFontWidthTrait);
+ if (cfnum != NULL) {
+ CGFloat val;
+
+ if (CFNumberGetValue(cfnum, kCFNumberCGFloatType, &val) == false)
+ complain("error getting width value in matchTraits()");
+ closeness[i].stretch = val - targetStretch;
+ } else
+ // okay there's no width key; let's try the literal meaning of the symbolic constant
+ // LONGTERM is the width key guaranteed?
+ if ((symbolic & kCTFontExpandedTrait) != 0)
+ closeness[i].stretch = 1.0 - targetStretch;
+ else if ((symbolic & kCTFontCondensedTrait) != 0)
+ closeness[i].stretch = -1.0 - targetStretch;
+ else
+ closeness[i].stretch = 0.0 - targetStretch;
+
+ CFRelease(traits);
+ }
+
+ // now figure out the 3-space difference between the three and sort by that
+ for (i = 0; i < n; i++) {
+ CGFloat weight, italic, stretch;
+
+ weight = closeness[i].weight;
+ weight *= weight;
+ italic = closeness[i].italic;
+ italic *= italic;
+ stretch = closeness[i].stretch;
+ stretch *= stretch;
+ closeness[i].distance = sqrt(weight + italic + stretch);
+ }
+ qsort_b(closeness, n, sizeof (struct closeness), ^(const void *aa, const void *bb) {
+ const struct closeness *a = (const struct closeness *) aa;
+ const struct closeness *b = (const struct closeness *) bb;
+
+ // via http://www.gnu.org/software/libc/manual/html_node/Comparison-Functions.html#Comparison-Functions
+ // LONGTERM is this really the best way? isn't it the same as if (*a < *b) return -1; if (*a > *b) return 1; return 0; ?
+ return (a->distance > b->distance) - (a->distance < b->distance);
+ });
+ // and the first element of the sorted array is what we want
+ out = CFArrayGetValueAtIndex(matching, closeness[0].index);
+ CFRetain(out); // get rule
+
+ // release everything
+ uiFree(closeness);
+ CFRelease(matching);
+ // and release the original descriptor since we no longer need it
+ CFRelease(against);
+
+ return out;
+}
+
+// Now remember what I said earlier about having to add the small caps traits after calling the above? This gets a dictionary back so we can do so.
+CFMutableDictionaryRef extractAttributes(CTFontDescriptorRef desc)
+{
+ CFDictionaryRef dict;
+ CFMutableDictionaryRef mdict;
+
+ dict = CTFontDescriptorCopyAttributes(desc);
+ // this might not be mutable, so make a mutable copy
+ mdict = CFDictionaryCreateMutableCopy(NULL, 0, dict);
+ CFRelease(dict);
+ return mdict;
+}
+
+uiDrawTextFont *uiDrawLoadClosestFont(const uiDrawTextFontDescriptor *desc)
+{
+ CTFontRef f;
+ CFMutableDictionaryRef attr;
+ CTFontDescriptorRef cfdesc;
+
+ attr = newAttrList();
+ addFontFamilyAttr(attr, desc->Family);
+ addFontSizeAttr(attr, desc->Size);
+
+ // now we have to do the traits matching, so create a descriptor, match the traits, and then get the attributes back
+ cfdesc = CTFontDescriptorCreateWithAttributes(attr);
+ // TODO release attr?
+ cfdesc = matchTraits(cfdesc, desc->Weight, desc->Italic, desc->Stretch);
+
+ // specify the initial size again just to be safe
+ f = CTFontCreateWithFontDescriptor(cfdesc, desc->Size, NULL);
+ // TODO release cfdesc?
+
+ return mkTextFont(f, NO); // we hold the initial reference; no need to retain again
+}
+
+void uiDrawFreeTextFont(uiDrawTextFont *font)
+{
+ CFRelease(font->f);
+ uiFree(font);
+}
+
+uintptr_t uiDrawTextFontHandle(uiDrawTextFont *font)
+{
+ return (uintptr_t) (font->f);
+}
+
+void uiDrawTextFontDescribe(uiDrawTextFont *font, uiDrawTextFontDescriptor *desc)
+{
+ // TODO
+}
+
+// text sizes and user space points are identical:
+// - https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/TypoFeatures/TextSystemFeatures.html#//apple_ref/doc/uid/TP40009459-CH6-51627-BBCCHIFF text points are 72 per inch
+// - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html#//apple_ref/doc/uid/TP40003290-CH204-SW5 user space points are 72 per inch
+void uiDrawTextFontGetMetrics(uiDrawTextFont *font, uiDrawTextFontMetrics *metrics)
+{
+ metrics->Ascent = CTFontGetAscent(font->f);
+ metrics->Descent = CTFontGetDescent(font->f);
+ metrics->Leading = CTFontGetLeading(font->f);
+ metrics->UnderlinePos = CTFontGetUnderlinePosition(font->f);
+ metrics->UnderlineThickness = CTFontGetUnderlineThickness(font->f);
+}
+
+struct uiDrawTextLayout {
+ CFMutableAttributedStringRef mas;
+ CFRange *charsToRanges;
+ double width;
+};
+
+uiDrawTextLayout *uiDrawNewTextLayout(const char *str, uiDrawTextFont *defaultFont, double width)
+{
+ uiDrawTextLayout *layout;
+ CFAttributedStringRef immutable;
+ CFMutableDictionaryRef attr;
+ CFStringRef backing;
+ CFIndex i, j, n;
+
+ layout = uiNew(uiDrawTextLayout);
+
+ // TODO docs say we need to use a different set of key callbacks
+ // TODO see if the font attribute key callbacks need to be the same
+ attr = newAttrList();
+ // this will retain defaultFont->f; no need to worry
+ CFDictionaryAddValue(attr, kCTFontAttributeName, defaultFont->f);
+
+ immutable = CFAttributedStringCreate(NULL, (CFStringRef) [NSString stringWithUTF8String:str], attr);
+ if (immutable == NULL)
+ complain("error creating immutable attributed string in uiDrawNewTextLayout()");
+ CFRelease(attr);
+
+ layout->mas = CFAttributedStringCreateMutableCopy(NULL, 0, immutable);
+ if (layout->mas == NULL)
+ complain("error creating attributed string in uiDrawNewTextLayout()");
+ CFRelease(immutable);
+
+ uiDrawTextLayoutSetWidth(layout, width);
+
+ // unfortunately the CFRanges for attributes expect UTF-16 codepoints
+ // we want graphemes
+ // fortunately CFStringGetRangeOfComposedCharactersAtIndex() is here for us
+ // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html says that this does work on all multi-codepoint graphemes (despite the name), and that this is the preferred function for this particular job anyway
+ backing = CFAttributedStringGetString(layout->mas);
+ n = CFStringGetLength(backing);
+ // allocate one extra, just to be safe
+ layout->charsToRanges = (CFRange *) uiAlloc((n + 1) * sizeof (CFRange), "CFRange[]");
+ i = 0;
+ j = 0;
+ while (i < n) {
+ CFRange range;
+
+ range = CFStringGetRangeOfComposedCharactersAtIndex(backing, i);
+ i = range.location + range.length;
+ layout->charsToRanges[j] = range;
+ j++;
+ }
+ // and set the last one
+ layout->charsToRanges[j].location = i;
+ layout->charsToRanges[j].length = 0;
+
+ return layout;
+}
+
+void uiDrawFreeTextLayout(uiDrawTextLayout *layout)
+{
+ uiFree(layout->charsToRanges);
+ CFRelease(layout->mas);
+ uiFree(layout);
+}
+
+void uiDrawTextLayoutSetWidth(uiDrawTextLayout *layout, double width)
+{
+ layout->width = width;
+}
+
+struct framesetter {
+ CTFramesetterRef fs;
+ CFMutableDictionaryRef frameAttrib;
+ CGSize extents;
+};
+
+// TODO CTFrameProgression for RTL/LTR
+// TODO kCTParagraphStyleSpecifierMaximumLineSpacing, kCTParagraphStyleSpecifierMinimumLineSpacing, kCTParagraphStyleSpecifierLineSpacingAdjustment for line spacing
+static void mkFramesetter(uiDrawTextLayout *layout, struct framesetter *fs)
+{
+ CFRange fitRange;
+ CGFloat width;
+
+ fs->fs = CTFramesetterCreateWithAttributedString(layout->mas);
+ if (fs->fs == NULL)
+ complain("error creating CTFramesetter object in mkFramesetter()");
+
+ // TODO kCTFramePathWidthAttributeName?
+ fs->frameAttrib = NULL;
+
+ width = layout->width;
+ if (layout->width < 0)
+ width = CGFLOAT_MAX;
+ // TODO these seem to be floor()'d or truncated?
+ fs->extents = CTFramesetterSuggestFrameSizeWithConstraints(fs->fs,
+ CFRangeMake(0, 0),
+ fs->frameAttrib,
+ CGSizeMake(width, CGFLOAT_MAX),
+ &fitRange); // not documented as accepting NULL
+}
+
+static void freeFramesetter(struct framesetter *fs)
+{
+ if (fs->frameAttrib != NULL)
+ CFRelease(fs->frameAttrib);
+ CFRelease(fs->fs);
+}
+
+// LONGTERM allow line separation and leading to be factored into a wrapping text layout
+
+// TODO reconcile differences in character wrapping on platforms
+void uiDrawTextLayoutExtents(uiDrawTextLayout *layout, double *width, double *height)
+{
+ struct framesetter fs;
+
+ mkFramesetter(layout, &fs);
+ *width = fs.extents.width;
+ *height = fs.extents.height;
+ freeFramesetter(&fs);
+}
+
+// Core Text doesn't draw onto a flipped view correctly; we have to do this
+// see the iOS bits of the first example at https://developer.apple.com/library/mac/documentation/StringsTextFonts/Conceptual/CoreText_Programming/LayoutOperations/LayoutOperations.html#//apple_ref/doc/uid/TP40005533-CH12-SW1 (iOS is naturally flipped)
+// TODO how is this affected by the CTM?
+static void prepareContextForText(CGContextRef c, CGFloat cheight, double *y)
+{
+ CGContextSaveGState(c);
+ CGContextTranslateCTM(c, 0, cheight);
+ CGContextScaleCTM(c, 1.0, -1.0);
+ CGContextSetTextMatrix(c, CGAffineTransformIdentity);
+
+ // wait, that's not enough; we need to offset y values to account for our new flipping
+ *y = cheight - *y;
+}
+
+// TODO placement is incorrect for Helvetica
+void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout)
+{
+ struct framesetter fs;
+ CGRect rect;
+ CGPathRef path;
+ CTFrameRef frame;
+
+ prepareContextForText(c, cheight, &y);
+ mkFramesetter(layout, &fs);
+
+ // oh, and since we're flipped, y is the bottom-left coordinate of the rectangle, not the top-left
+ // since we are flipped, we subtract
+ y -= fs.extents.height;
+
+ rect.origin = CGPointMake(x, y);
+ rect.size = fs.extents;
+ path = CGPathCreateWithRect(rect, NULL);
+
+ frame = CTFramesetterCreateFrame(fs.fs,
+ CFRangeMake(0, 0),
+ path,
+ fs.frameAttrib);
+ if (frame == NULL)
+ complain("error creating CTFrame object in doDrawText()");
+ CTFrameDraw(frame, c);
+ CFRelease(frame);
+
+ CFRelease(path);
+
+ freeFramesetter(&fs);
+ CGContextRestoreGState(c);
+}
+
+// LONGTERM provide an equivalent to CTLineGetTypographicBounds() on uiDrawTextLayout?
+
+// LONGTERM keep this for later features and documentation purposes
+#if 0
+ w = CTLineGetTypographicBounds(line, &ascent, &descent, NULL);
+ // though CTLineGetTypographicBounds() returns 0 on error, it also returns 0 on an empty string, so we can't reasonably check for error
+ CFRelease(line);
+
+ // LONGTERM provide a way to get the image bounds as a separate function later
+ bounds = CTLineGetImageBounds(line, c);
+ // though CTLineGetImageBounds() returns CGRectNull on error, it also returns CGRectNull on an empty string, so we can't reasonably check for error
+
+ // CGContextSetTextPosition() positions at the baseline in the case of CTLineDraw(); we need the top-left corner instead
+ CTLineGetTypographicBounds(line, &yoff, NULL, NULL);
+ // remember that we're flipped, so we subtract
+ y -= yoff;
+ CGContextSetTextPosition(c, x, y);
+#endif
+
+static CFRange charsToRange(uiDrawTextLayout *layout, int startChar, int endChar)
+{
+ CFRange start, end;
+ CFRange out;
+
+ start = layout->charsToRanges[startChar];
+ end = layout->charsToRanges[endChar];
+ out.location = start.location;
+ out.length = end.location - start.location;
+ return out;
+}
+
+#define rangeToCFRange() charsToRange(layout, startChar, endChar)
+
+void uiDrawTextLayoutSetColor(uiDrawTextLayout *layout, int startChar, int endChar, double r, double g, double b, double a)
+{
+ CGColorSpaceRef colorspace;
+ CGFloat components[4];
+ CGColorRef color;
+
+ // for consistency with windows, use sRGB
+ colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
+ components[0] = r;
+ components[1] = g;
+ components[2] = b;
+ components[3] = a;
+ color = CGColorCreate(colorspace, components);
+ CGColorSpaceRelease(colorspace);
+
+ CFAttributedStringSetAttribute(layout->mas,
+ rangeToCFRange(),
+ kCTForegroundColorAttributeName,
+ color);
+ CGColorRelease(color); // TODO safe?
+}
diff --git a/src/libui_sdl/libui/darwin/editablecombo.m b/src/libui_sdl/libui/darwin/editablecombo.m
new file mode 100644
index 0000000..434add7
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/editablecombo.m
@@ -0,0 +1,185 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+// So why did I split uiCombobox into uiCombobox and uiEditableCombobox? Here's (90% of the; the other 10% is GTK+ events) answer:
+// When you type a value into a NSComboBox that just happens to be in the list, it will autoselect that item!
+// I can't seem to find a workaround.
+// Fortunately, there's other weird behaviors that made this split worth it.
+// And besides, selected items make little sense with editable comboboxes... you either separate or combine them with the text entry :V
+
+// NSComboBoxes have no intrinsic width; we'll use the default Interface Builder width for them.
+#define comboboxWidth 96
+
+@interface libui_intrinsicWidthNSComboBox : NSComboBox
+@end
+
+@implementation libui_intrinsicWidthNSComboBox
+
+- (NSSize)intrinsicContentSize
+{
+ NSSize s;
+
+ s = [super intrinsicContentSize];
+ s.width = comboboxWidth;
+ return s;
+}
+
+@end
+
+struct uiEditableCombobox {
+ uiDarwinControl c;
+ NSComboBox *cb;
+ void (*onChanged)(uiEditableCombobox *, void *);
+ void *onChangedData;
+};
+
+@interface editableComboboxDelegateClass : NSObject<NSComboBoxDelegate> {
+ struct mapTable *comboboxes;
+}
+- (void)controlTextDidChange:(NSNotification *)note;
+- (void)comboBoxSelectionDidChange:(NSNotification *)note;
+- (void)registerCombobox:(uiEditableCombobox *)c;
+- (void)unregisterCombobox:(uiEditableCombobox *)c;
+@end
+
+@implementation editableComboboxDelegateClass
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ self->comboboxes = newMap();
+ return self;
+}
+
+- (void)dealloc
+{
+ mapDestroy(self->comboboxes);
+ [super dealloc];
+}
+
+- (void)controlTextDidChange:(NSNotification *)note
+{
+ uiEditableCombobox *c;
+
+ c = uiEditableCombobox(mapGet(self->comboboxes, [note object]));
+ (*(c->onChanged))(c, c->onChangedData);
+}
+
+// the above doesn't handle when an item is selected; this will
+- (void)comboBoxSelectionDidChange:(NSNotification *)note
+{
+ // except this is sent BEFORE the entry is changed, and that doesn't send the above, so
+ // this is via http://stackoverflow.com/a/21059819/3408572 - it avoids the need to manage selected items
+ // this still isn't perfect — I get residual changes to the same value while navigating the list — but it's good enough
+ [self performSelector:@selector(controlTextDidChange:)
+ withObject:note
+ afterDelay:0];
+}
+
+- (void)registerCombobox:(uiEditableCombobox *)c
+{
+ mapSet(self->comboboxes, c->cb, c);
+ [c->cb setDelegate:self];
+}
+
+- (void)unregisterCombobox:(uiEditableCombobox *)c
+{
+ [c->cb setDelegate:nil];
+ mapDelete(self->comboboxes, c->cb);
+}
+
+@end
+
+static editableComboboxDelegateClass *comboboxDelegate = nil;
+
+uiDarwinControlAllDefaultsExceptDestroy(uiEditableCombobox, cb)
+
+static void uiEditableComboboxDestroy(uiControl *cc)
+{
+ uiEditableCombobox *c = uiEditableCombobox(cc);
+
+ [comboboxDelegate unregisterCombobox:c];
+ [c->cb release];
+ uiFreeControl(uiControl(c));
+}
+
+void uiEditableComboboxAppend(uiEditableCombobox *c, const char *text)
+{
+ [c->cb addItemWithObjectValue:toNSString(text)];
+}
+
+char *uiEditableComboboxText(uiEditableCombobox *c)
+{
+ return uiDarwinNSStringToText([c->cb stringValue]);
+}
+
+void uiEditableComboboxSetText(uiEditableCombobox *c, const char *text)
+{
+ NSString *t;
+
+ t = toNSString(text);
+ [c->cb setStringValue:t];
+ // yes, let's imitate the behavior that caused uiEditableCombobox to be separate in the first place!
+ // just to avoid confusion when users see an option in the list in the text field but not selected in the list
+ [c->cb selectItemWithObjectValue:t];
+}
+
+#if 0
+// LONGTERM
+void uiEditableComboboxSetSelected(uiEditableCombobox *c, int n)
+{
+ if (c->editable) {
+ // see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ComboBox/Tasks/SettingComboBoxValue.html#//apple_ref/doc/uid/20000256
+ id delegate;
+
+ // this triggers the delegate; turn it off for now
+ delegate = [c->cb delegate];
+ [c->cb setDelegate:nil];
+
+ // this seems to work fine for -1 too
+ [c->cb selectItemAtIndex:n];
+ if (n == -1)
+ [c->cb setObjectValue:@""];
+ else
+ [c->cb setObjectValue:[c->cb objectValueOfSelectedItem]];
+
+ [c->cb setDelegate:delegate];
+ return;
+ }
+ [c->pb selectItemAtIndex:n];
+}
+#endif
+
+void uiEditableComboboxOnChanged(uiEditableCombobox *c, void (*f)(uiEditableCombobox *c, void *data), void *data)
+{
+ c->onChanged = f;
+ c->onChangedData = data;
+}
+
+static void defaultOnChanged(uiEditableCombobox *c, void *data)
+{
+ // do nothing
+}
+
+uiEditableCombobox *uiNewEditableCombobox(void)
+{
+ uiEditableCombobox *c;
+
+ uiDarwinNewControl(uiEditableCombobox, c);
+
+ c->cb = [[libui_intrinsicWidthNSComboBox alloc] initWithFrame:NSZeroRect];
+ [c->cb setUsesDataSource:NO];
+ [c->cb setButtonBordered:YES];
+ [c->cb setCompletes:NO];
+ uiDarwinSetControlFont(c->cb, NSRegularControlSize);
+
+ if (comboboxDelegate == nil) {
+ comboboxDelegate = [[editableComboboxDelegateClass new] autorelease];
+ [delegates addObject:comboboxDelegate];
+ }
+ [comboboxDelegate registerCombobox:c];
+ uiEditableComboboxOnChanged(c, defaultOnChanged, NULL);
+
+ return c;
+}
diff --git a/src/libui_sdl/libui/darwin/entry.m b/src/libui_sdl/libui/darwin/entry.m
new file mode 100644
index 0000000..219d080
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/entry.m
@@ -0,0 +1,251 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+// Text fields for entering text have no intrinsic width; we'll use the default Interface Builder width for them.
+#define textfieldWidth 96
+
+@interface libui_intrinsicWidthNSTextField : NSTextField
+@end
+
+@implementation libui_intrinsicWidthNSTextField
+
+- (NSSize)intrinsicContentSize
+{
+ NSSize s;
+
+ s = [super intrinsicContentSize];
+ s.width = textfieldWidth;
+ return s;
+}
+
+@end
+
+// TODO does this have one on its own?
+@interface libui_intrinsicWidthNSSecureTextField : NSSecureTextField
+@end
+
+@implementation libui_intrinsicWidthNSSecureTextField
+
+- (NSSize)intrinsicContentSize
+{
+ NSSize s;
+
+ s = [super intrinsicContentSize];
+ s.width = textfieldWidth;
+ return s;
+}
+
+@end
+
+// TODO does this have one on its own?
+@interface libui_intrinsicWidthNSSearchField : NSSearchField
+@end
+
+@implementation libui_intrinsicWidthNSSearchField
+
+- (NSSize)intrinsicContentSize
+{
+ NSSize s;
+
+ s = [super intrinsicContentSize];
+ s.width = textfieldWidth;
+ return s;
+}
+
+@end
+
+struct uiEntry {
+ uiDarwinControl c;
+ NSTextField *textfield;
+ void (*onChanged)(uiEntry *, void *);
+ void *onChangedData;
+};
+
+static BOOL isSearchField(NSTextField *tf)
+{
+ return [tf isKindOfClass:[NSSearchField class]];
+}
+
+@interface entryDelegateClass : NSObject<NSTextFieldDelegate> {
+ struct mapTable *entries;
+}
+- (void)controlTextDidChange:(NSNotification *)note;
+- (IBAction)onSearch:(id)sender;
+- (void)registerEntry:(uiEntry *)e;
+- (void)unregisterEntry:(uiEntry *)e;
+@end
+
+@implementation entryDelegateClass
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ self->entries = newMap();
+ return self;
+}
+
+- (void)dealloc
+{
+ mapDestroy(self->entries);
+ [super dealloc];
+}
+
+- (void)controlTextDidChange:(NSNotification *)note
+{
+ [self onSearch:[note object]];
+}
+
+- (IBAction)onSearch:(id)sender
+{
+ uiEntry *e;
+
+ e = (uiEntry *) mapGet(self->entries, sender);
+ (*(e->onChanged))(e, e->onChangedData);
+}
+
+- (void)registerEntry:(uiEntry *)e
+{
+ mapSet(self->entries, e->textfield, e);
+ if (isSearchField(e->textfield)) {
+ [e->textfield setTarget:self];
+ [e->textfield setAction:@selector(onSearch:)];
+ } else
+ [e->textfield setDelegate:self];
+}
+
+- (void)unregisterEntry:(uiEntry *)e
+{
+ if (isSearchField(e->textfield))
+ [e->textfield setTarget:nil];
+ else
+ [e->textfield setDelegate:nil];
+ mapDelete(self->entries, e->textfield);
+}
+
+@end
+
+static entryDelegateClass *entryDelegate = nil;
+
+uiDarwinControlAllDefaultsExceptDestroy(uiEntry, textfield)
+
+static void uiEntryDestroy(uiControl *c)
+{
+ uiEntry *e = uiEntry(c);
+
+ [entryDelegate unregisterEntry:e];
+ [e->textfield release];
+ uiFreeControl(uiControl(e));
+}
+
+char *uiEntryText(uiEntry *e)
+{
+ return uiDarwinNSStringToText([e->textfield stringValue]);
+}
+
+void uiEntrySetText(uiEntry *e, const char *text)
+{
+ [e->textfield setStringValue:toNSString(text)];
+ // don't queue the control for resize; entry sizes are independent of their contents
+}
+
+void uiEntryOnChanged(uiEntry *e, void (*f)(uiEntry *, void *), void *data)
+{
+ e->onChanged = f;
+ e->onChangedData = data;
+}
+
+int uiEntryReadOnly(uiEntry *e)
+{
+ return [e->textfield isEditable] == NO;
+}
+
+void uiEntrySetReadOnly(uiEntry *e, int readonly)
+{
+ BOOL editable;
+
+ editable = YES;
+ if (readonly)
+ editable = NO;
+ [e->textfield setEditable:editable];
+}
+
+static void defaultOnChanged(uiEntry *e, void *data)
+{
+ // do nothing
+}
+
+// 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)
+{
+ uiDarwinSetControlFont(t, NSRegularControlSize);
+
+ // 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];
+}
+
+static NSTextField *realNewEditableTextField(Class class)
+{
+ NSTextField *tf;
+
+ tf = [[class alloc] initWithFrame:NSZeroRect];
+ [tf setSelectable:YES]; // otherwise the setting is masked by the editable default of YES
+ finishNewTextField(tf, YES);
+ return tf;
+}
+
+NSTextField *newEditableTextField(void)
+{
+ return realNewEditableTextField([libui_intrinsicWidthNSTextField class]);
+}
+
+static uiEntry *finishNewEntry(Class class)
+{
+ uiEntry *e;
+
+ uiDarwinNewControl(uiEntry, e);
+
+ e->textfield = realNewEditableTextField(class);
+
+ if (entryDelegate == nil) {
+ entryDelegate = [[entryDelegateClass new] autorelease];
+ [delegates addObject:entryDelegate];
+ }
+ [entryDelegate registerEntry:e];
+ uiEntryOnChanged(e, defaultOnChanged, NULL);
+
+ return e;
+}
+
+uiEntry *uiNewEntry(void)
+{
+ return finishNewEntry([libui_intrinsicWidthNSTextField class]);
+}
+
+uiEntry *uiNewPasswordEntry(void)
+{
+ return finishNewEntry([libui_intrinsicWidthNSSecureTextField class]);
+}
+
+uiEntry *uiNewSearchEntry(void)
+{
+ uiEntry *e;
+ NSSearchField *s;
+
+ e = finishNewEntry([libui_intrinsicWidthNSSearchField class]);
+ s = (NSSearchField *) (e->textfield);
+ // TODO these are only on 10.10
+// [s setSendsSearchStringImmediately:NO];
+// [s setSendsWholeSearchString:NO];
+ [s setBordered:NO];
+ [s setBezelStyle:NSTextFieldRoundedBezel];
+ [s setBezeled:YES];
+ return e;
+}
diff --git a/src/libui_sdl/libui/darwin/fontbutton.m b/src/libui_sdl/libui/darwin/fontbutton.m
new file mode 100644
index 0000000..22bc646
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/fontbutton.m
@@ -0,0 +1,218 @@
+// 14 april 2016
+#import "uipriv_darwin.h"
+
+@interface fontButton : NSButton {
+ uiFontButton *libui_b;
+ NSFont *libui_font;
+}
+- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b;
+- (void)updateFontButtonLabel;
+- (IBAction)fontButtonClicked:(id)sender;
+- (void)activateFontButton;
+- (void)deactivateFontButton:(BOOL)activatingAnother;
+- (void)deactivateOnClose:(NSNotification *)note;
+- (uiDrawTextFont *)libuiFont;
+@end
+
+// only one may be active at one time
+static fontButton *activeFontButton = nil;
+
+struct uiFontButton {
+ uiDarwinControl c;
+ fontButton *button;
+ void (*onChanged)(uiFontButton *, void *);
+ void *onChangedData;
+};
+
+@implementation fontButton
+
+- (id)initWithFrame:(NSRect)frame libuiFontButton:(uiFontButton *)b
+{
+ self = [super initWithFrame:frame];
+ if (self) {
+ self->libui_b = b;
+
+ // imitate a NSColorWell in appearance
+ [self setButtonType:NSPushOnPushOffButton];
+ [self setBordered:YES];
+ [self setBezelStyle:NSShadowlessSquareBezelStyle];
+
+ // default font values according to the CTFontDescriptor reference
+ // this is autoreleased (thanks swillits in irc.freenode.net/#macdev)
+ self->libui_font = [[NSFont fontWithName:@"Helvetica" size:12.0] retain];
+ [self updateFontButtonLabel];
+
+ // for when clicked
+ [self setTarget:self];
+ [self setAction:@selector(fontButtonClicked:)];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ // clean up notifications
+ if (activeFontButton == self)
+ [self deactivateFontButton:NO];
+ [self->libui_font release];
+ [super dealloc];
+}
+
+- (void)updateFontButtonLabel
+{
+ NSString *title;
+
+ title = [NSString stringWithFormat:@"%@ %g",
+ [self->libui_font displayName],
+ [self->libui_font pointSize]];
+ [self setTitle:title];
+}
+
+- (IBAction)fontButtonClicked:(id)sender
+{
+ if ([self state] == NSOnState)
+ [self activateFontButton];
+ else
+ [self deactivateFontButton:NO];
+}
+
+- (void)activateFontButton
+{
+ NSFontManager *sfm;
+
+ sfm = [NSFontManager sharedFontManager];
+ if (activeFontButton != nil)
+ [activeFontButton deactivateFontButton:YES];
+ [sfm setTarget:self];
+ [sfm setSelectedFont:self->libui_font isMultiple:NO];
+ [sfm orderFrontFontPanel:self];
+ activeFontButton = self;
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(deactivateOnClose:)
+ name:NSWindowWillCloseNotification
+ object:[NSFontPanel sharedFontPanel]];
+ [self setState:NSOnState];
+}
+
+- (void)deactivateFontButton:(BOOL)activatingAnother
+{
+ NSFontManager *sfm;
+
+ sfm = [NSFontManager sharedFontManager];
+ [sfm setTarget:nil];
+ if (!activatingAnother)
+ [[NSFontPanel sharedFontPanel] orderOut:self];
+ activeFontButton = nil;
+ [[NSNotificationCenter defaultCenter] removeObserver:self
+ name:NSWindowWillCloseNotification
+ object:[NSFontPanel sharedFontPanel]];
+ [self setState:NSOffState];
+}
+
+- (void)deactivateOnClose:(NSNotification *)note
+{
+ [self deactivateFontButton:NO];
+}
+
+- (void)changeFont:(id)sender
+{
+ NSFontManager *fm;
+ NSFont *old;
+ uiFontButton *b = self->libui_b;
+
+ fm = (NSFontManager *) sender;
+ old = self->libui_font;
+ self->libui_font = [sender convertFont:self->libui_font];
+ // do this even if it returns the same; we don't own anything that isn't from a new or alloc/init
+ [self->libui_font retain];
+ // do this second just in case
+ [old release];
+ [self updateFontButtonLabel];
+ (*(b->onChanged))(b, b->onChangedData);
+}
+
+- (NSUInteger)validModesForFontPanel:(NSFontPanel *)panel
+{
+ return NSFontPanelFaceModeMask |
+ NSFontPanelSizeModeMask |
+ NSFontPanelCollectionModeMask;
+}
+
+- (uiDrawTextFont *)libuiFont
+{
+ return mkTextFontFromNSFont(self->libui_font);
+}
+
+@end
+
+uiDarwinControlAllDefaults(uiFontButton, button)
+
+// we do not want font change events to be sent to any controls other than the font buttons
+// see main.m for more details
+BOOL fontButtonInhibitSendAction(SEL sel, id from, id to)
+{
+ if (sel != @selector(changeFont:))
+ return NO;
+ return ![to isKindOfClass:[fontButton class]];
+}
+
+// we do not want NSFontPanelValidation messages to be sent to any controls other than the font buttons when a font button is active
+// see main.m for more details
+BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override)
+{
+ if (activeFontButton == nil)
+ return NO;
+ if (sel != @selector(validModesForFontPanel:))
+ return NO;
+ *override = activeFontButton;
+ return YES;
+}
+
+// we also don't want the panel to be usable when there's a dialog running; see stddialogs.m for more details on that
+// unfortunately the panel seems to ignore -setWorksWhenModal: so we'll have to do things ourselves
+@interface nonModalFontPanel : NSFontPanel
+@end
+
+@implementation nonModalFontPanel
+
+- (BOOL)worksWhenModal
+{
+ return NO;
+}
+
+@end
+
+void setupFontPanel(void)
+{
+ [NSFontManager setFontPanelFactory:[nonModalFontPanel class]];
+}
+
+static void defaultOnChanged(uiFontButton *b, void *data)
+{
+ // do nothing
+}
+
+uiDrawTextFont *uiFontButtonFont(uiFontButton *b)
+{
+ return [b->button libuiFont];
+}
+
+void uiFontButtonOnChanged(uiFontButton *b, void (*f)(uiFontButton *, void *), void *data)
+{
+ b->onChanged = f;
+ b->onChangedData = data;
+}
+
+uiFontButton *uiNewFontButton(void)
+{
+ uiFontButton *b;
+
+ uiDarwinNewControl(uiFontButton, b);
+
+ b->button = [[fontButton alloc] initWithFrame:NSZeroRect libuiFontButton:b];
+ uiDarwinSetControlFont(b->button, NSRegularControlSize);
+
+ uiFontButtonOnChanged(b, defaultOnChanged, NULL);
+
+ return b;
+}
diff --git a/src/libui_sdl/libui/darwin/form.m b/src/libui_sdl/libui/darwin/form.m
new file mode 100644
index 0000000..7cdb965
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/form.m
@@ -0,0 +1,561 @@
+// 7 june 2016
+#import "uipriv_darwin.h"
+
+// TODO in the test program, sometimes one of the radio buttons can disappear (try when spaced)
+
+@interface formChild : NSView
+@property uiControl *c;
+@property (strong) NSTextField *label;
+@property BOOL stretchy;
+@property NSLayoutPriority oldHorzHuggingPri;
+@property NSLayoutPriority oldVertHuggingPri;
+@property (strong) NSLayoutConstraint *baseline;
+@property (strong) NSLayoutConstraint *leading;
+@property (strong) NSLayoutConstraint *top;
+@property (strong) NSLayoutConstraint *trailing;
+@property (strong) NSLayoutConstraint *bottom;
+- (id)initWithLabel:(NSTextField *)l;
+- (void)onDestroy;
+- (NSView *)view;
+@end
+
+@interface formView : NSView {
+ uiForm *f;
+ NSMutableArray *children;
+ int padded;
+
+ NSLayoutConstraint *first;
+ NSMutableArray *inBetweens;
+ NSLayoutConstraint *last;
+ NSMutableArray *widths;
+ NSMutableArray *leadings;
+ NSMutableArray *middles;
+ NSMutableArray *trailings;
+}
+- (id)initWithF:(uiForm *)ff;
+- (void)onDestroy;
+- (void)removeOurConstraints;
+- (void)syncEnableStates:(int)enabled;
+- (CGFloat)paddingAmount;
+- (void)establishOurConstraints;
+- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy;
+- (void)delete:(int)n;
+- (int)isPadded;
+- (void)setPadded:(int)p;
+- (BOOL)hugsTrailing;
+- (BOOL)hugsBottom;
+- (int)nStretchy;
+@end
+
+struct uiForm {
+ uiDarwinControl c;
+ formView *view;
+};
+
+@implementation formChild
+
+- (id)initWithLabel:(NSTextField *)l
+{
+ self = [super initWithFrame:NSZeroRect];
+ if (self) {
+ self.label = l;
+ [self.label setTranslatesAutoresizingMaskIntoConstraints:NO];
+ [self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
+ [self.label setContentHuggingPriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
+ [self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationHorizontal];
+ [self.label setContentCompressionResistancePriority:NSLayoutPriorityRequired forOrientation:NSLayoutConstraintOrientationVertical];
+ [self addSubview:self.label];
+
+ self.leading = mkConstraint(self.label, NSLayoutAttributeLeading,
+ NSLayoutRelationGreaterThanOrEqual,
+ self, NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiForm label leading");
+ [self addConstraint:self.leading];
+ self.top = mkConstraint(self.label, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeTop,
+ 1, 0,
+ @"uiForm label top");
+ [self addConstraint:self.top];
+ self.trailing = mkConstraint(self.label, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeTrailing,
+ 1, 0,
+ @"uiForm label trailing");
+ [self addConstraint:self.trailing];
+ self.bottom = mkConstraint(self.label, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeBottom,
+ 1, 0,
+ @"uiForm label bottom");
+ [self addConstraint:self.bottom];
+ }
+ return self;
+}
+
+- (void)onDestroy
+{
+ [self removeConstraint:self.trailing];
+ self.trailing = nil;
+ [self removeConstraint:self.top];
+ self.top = nil;
+ [self removeConstraint:self.bottom];
+ self.bottom = nil;
+
+ [self.label removeFromSuperview];
+ self.label = nil;
+}
+
+- (NSView *)view
+{
+ return (NSView *) uiControlHandle(self.c);
+}
+
+@end
+
+@implementation formView
+
+- (id)initWithF:(uiForm *)ff
+{
+ self = [super initWithFrame:NSZeroRect];
+ if (self != nil) {
+ self->f = ff;
+ self->padded = 0;
+ self->children = [NSMutableArray new];
+
+ self->inBetweens = [NSMutableArray new];
+ self->widths = [NSMutableArray new];
+ self->leadings = [NSMutableArray new];
+ self->middles = [NSMutableArray new];
+ self->trailings = [NSMutableArray new];
+ }
+ return self;
+}
+
+- (void)onDestroy
+{
+ formChild *fc;
+
+ [self removeOurConstraints];
+ [self->inBetweens release];
+ [self->widths release];
+ [self->leadings release];
+ [self->middles release];
+ [self->trailings release];
+
+ for (fc in self->children) {
+ [self removeConstraint:fc.baseline];
+ fc.baseline = nil;
+ uiControlSetParent(fc.c, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);
+ uiControlDestroy(fc.c);
+ [fc onDestroy];
+ [fc removeFromSuperview];
+ }
+ [self->children release];
+}
+
+- (void)removeOurConstraints
+{
+ if (self->first != nil) {
+ [self removeConstraint:self->first];
+ [self->first release];
+ self->first = nil;
+ }
+ if ([self->inBetweens count] != 0) {
+ [self removeConstraints:self->inBetweens];
+ [self->inBetweens removeAllObjects];
+ }
+ if (self->last != nil) {
+ [self removeConstraint:self->last];
+ [self->last release];
+ self->last = nil;
+ }
+ if ([self->widths count] != 0) {
+ [self removeConstraints:self->widths];
+ [self->widths removeAllObjects];
+ }
+ if ([self->leadings count] != 0) {
+ [self removeConstraints:self->leadings];
+ [self->leadings removeAllObjects];
+ }
+ if ([self->middles count] != 0) {
+ [self removeConstraints:self->middles];
+ [self->middles removeAllObjects];
+ }
+ if ([self->trailings count] != 0) {
+ [self removeConstraints:self->trailings];
+ [self->trailings removeAllObjects];
+ }
+}
+
+- (void)syncEnableStates:(int)enabled
+{
+ formChild *fc;
+
+ for (fc in self->children)
+ uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), enabled);
+}
+
+- (CGFloat)paddingAmount
+{
+ if (!self->padded)
+ return 0.0;
+ return uiDarwinPaddingAmount(NULL);
+}
+
+- (void)establishOurConstraints
+{
+ formChild *fc;
+ CGFloat padding;
+ NSView *prev, *prevlabel;
+ NSLayoutConstraint *c;
+
+ [self removeOurConstraints];
+ if ([self->children count] == 0)
+ return;
+ padding = [self paddingAmount];
+
+ // first arrange the children vertically and make them the same width
+ prev = nil;
+ for (fc in self->children) {
+ [fc setHidden:!uiControlVisible(fc.c)];
+ if (!uiControlVisible(fc.c))
+ continue;
+ if (prev == nil) { // first view
+ self->first = mkConstraint(self, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ [fc view], NSLayoutAttributeTop,
+ 1, 0,
+ @"uiForm first vertical constraint");
+ [self addConstraint:self->first];
+ [self->first retain];
+ prev = [fc view];
+ prevlabel = fc;
+ continue;
+ }
+ // not the first; link it
+ c = mkConstraint(prev, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ [fc view], NSLayoutAttributeTop,
+ 1, -padding,
+ @"uiForm in-between vertical constraint");
+ [self addConstraint:c];
+ [self->inBetweens addObject:c];
+ // and make the same width
+ c = mkConstraint(prev, NSLayoutAttributeWidth,
+ NSLayoutRelationEqual,
+ [fc view], NSLayoutAttributeWidth,
+ 1, 0,
+ @"uiForm control width constraint");
+ [self addConstraint:c];
+ [self->widths addObject:c];
+ c = mkConstraint(prevlabel, NSLayoutAttributeWidth,
+ NSLayoutRelationEqual,
+ fc, NSLayoutAttributeWidth,
+ 1, 0,
+ @"uiForm label lwidth constraint");
+ [self addConstraint:c];
+ [self->widths addObject:c];
+ prev = [fc view];
+ prevlabel = fc;
+ }
+ if (prev == nil) // all hidden; act as if nothing there
+ return;
+ self->last = mkConstraint(prev, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeBottom,
+ 1, 0,
+ @"uiForm last vertical constraint");
+ [self addConstraint:self->last];
+ [self->last retain];
+
+ // now arrange the controls horizontally
+ for (fc in self->children) {
+ if (!uiControlVisible(fc.c))
+ continue;
+ c = mkConstraint(self, NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ fc, NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiForm leading constraint");
+ [self addConstraint:c];
+ [self->leadings addObject:c];
+ // coerce the control to be as wide as possible
+ // see http://stackoverflow.com/questions/37710892/in-auto-layout-i-set-up-labels-that-shouldnt-grow-horizontally-and-controls-th
+ c = mkConstraint(self, NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ [fc view], NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiForm leading constraint");
+ [c setPriority:NSLayoutPriorityDefaultHigh];
+ [self addConstraint:c];
+ [self->leadings addObject:c];
+ c = mkConstraint(fc, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ [fc view], NSLayoutAttributeLeading,
+ 1, -padding,
+ @"uiForm middle constraint");
+ [self addConstraint:c];
+ [self->middles addObject:c];
+ c = mkConstraint([fc view], NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeTrailing,
+ 1, 0,
+ @"uiForm trailing constraint");
+ [self addConstraint:c];
+ [self->trailings addObject:c];
+ // TODO
+ c = mkConstraint(fc, NSLayoutAttributeBottom,
+ NSLayoutRelationLessThanOrEqual,
+ self, NSLayoutAttributeBottom,
+ 1, 0,
+ @"TODO");
+ [self addConstraint:c];
+ [self->trailings addObject:c];
+ }
+
+ // and make all stretchy controls have the same height
+ prev = nil;
+ for (fc in self->children) {
+ if (!uiControlVisible(fc.c))
+ continue;
+ if (!fc.stretchy)
+ continue;
+ if (prev == nil) {
+ prev = [fc view];
+ continue;
+ }
+ c = mkConstraint([fc view], NSLayoutAttributeHeight,
+ NSLayoutRelationEqual,
+ prev, NSLayoutAttributeHeight,
+ 1, 0,
+ @"uiForm stretchy constraint");
+ [self addConstraint:c];
+ // TODO make a dedicated array for this
+ [self->leadings addObject:c];
+ }
+
+ // we don't arrange the labels vertically; that's done when we add the control since those constraints don't need to change (they just need to be at their baseline)
+}
+
+- (void)append:(NSString *)label c:(uiControl *)c stretchy:(int)stretchy
+{
+ formChild *fc;
+ NSLayoutPriority priority;
+ NSLayoutAttribute attribute;
+ int oldnStretchy;
+
+ fc = [[formChild alloc] initWithLabel:newLabel(label)];
+ fc.c = c;
+ fc.stretchy = stretchy;
+ fc.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationHorizontal);
+ fc.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(fc.c), NSLayoutConstraintOrientationVertical);
+ [fc setTranslatesAutoresizingMaskIntoConstraints:NO];
+ [self addSubview:fc];
+
+ uiControlSetParent(fc.c, uiControl(self->f));
+ uiDarwinControlSetSuperview(uiDarwinControl(fc.c), self);
+ uiDarwinControlSyncEnableState(uiDarwinControl(fc.c), uiControlEnabledToUser(uiControl(self->f)));
+
+ // if a control is stretchy, it should not hug vertically
+ // otherwise, it should *forcibly* hug
+ if (fc.stretchy)
+ priority = NSLayoutPriorityDefaultLow;
+ else
+ // LONGTERM will default high work?
+ priority = NSLayoutPriorityRequired;
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), priority, NSLayoutConstraintOrientationVertical);
+ // make sure controls don't hug their horizontal direction so they fill the width of the view
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
+
+ // and constrain the baselines to position the label vertically
+ // if the view is a scroll view, align tops, not baselines
+ // this is what Interface Builder does
+ attribute = NSLayoutAttributeBaseline;
+ if ([[fc view] isKindOfClass:[NSScrollView class]])
+ attribute = NSLayoutAttributeTop;
+ fc.baseline = mkConstraint(fc.label, attribute,
+ NSLayoutRelationEqual,
+ [fc view], attribute,
+ 1, 0,
+ @"uiForm baseline constraint");
+ [self addConstraint:fc.baseline];
+
+ oldnStretchy = [self nStretchy];
+ [self->children addObject:fc];
+
+ [self establishOurConstraints];
+ if (fc.stretchy)
+ if (oldnStretchy == 0)
+ uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));
+
+ [fc release]; // we don't need the initial reference now
+}
+
+- (void)delete:(int)n
+{
+ formChild *fc;
+ int stretchy;
+
+ fc = (formChild *) [self->children objectAtIndex:n];
+ stretchy = fc.stretchy;
+
+ uiControlSetParent(fc.c, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(fc.c), nil);
+
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(fc.c), fc.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
+
+ [fc onDestroy];
+ [self->children removeObjectAtIndex:n];
+
+ [self establishOurConstraints];
+ if (stretchy)
+ if ([self nStretchy] == 0)
+ uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->f));
+}
+
+- (int)isPadded
+{
+ return self->padded;
+}
+
+- (void)setPadded:(int)p
+{
+ CGFloat padding;
+ NSLayoutConstraint *c;
+
+ self->padded = p;
+ padding = [self paddingAmount];
+ for (c in self->inBetweens)
+ [c setConstant:-padding];
+ for (c in self->middles)
+ [c setConstant:-padding];
+}
+
+- (BOOL)hugsTrailing
+{
+ return YES; // always hug trailing
+}
+
+- (BOOL)hugsBottom
+{
+ // only hug if we have stretchy
+ return [self nStretchy] != 0;
+}
+
+- (int)nStretchy
+{
+ formChild *fc;
+ int n;
+
+ n = 0;
+ for (fc in self->children) {
+ if (!uiControlVisible(fc.c))
+ continue;
+ if (fc.stretchy)
+ n++;
+ }
+ return n;
+}
+
+@end
+
+static void uiFormDestroy(uiControl *c)
+{
+ uiForm *f = uiForm(c);
+
+ [f->view onDestroy];
+ [f->view release];
+ uiFreeControl(uiControl(f));
+}
+
+uiDarwinControlDefaultHandle(uiForm, view)
+uiDarwinControlDefaultParent(uiForm, view)
+uiDarwinControlDefaultSetParent(uiForm, view)
+uiDarwinControlDefaultToplevel(uiForm, view)
+uiDarwinControlDefaultVisible(uiForm, view)
+uiDarwinControlDefaultShow(uiForm, view)
+uiDarwinControlDefaultHide(uiForm, view)
+uiDarwinControlDefaultEnabled(uiForm, view)
+uiDarwinControlDefaultEnable(uiForm, view)
+uiDarwinControlDefaultDisable(uiForm, view)
+
+static void uiFormSyncEnableState(uiDarwinControl *c, int enabled)
+{
+ uiForm *f = uiForm(c);
+
+ if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(f), enabled))
+ return;
+ [f->view syncEnableStates:enabled];
+}
+
+uiDarwinControlDefaultSetSuperview(uiForm, view)
+
+static BOOL uiFormHugsTrailingEdge(uiDarwinControl *c)
+{
+ uiForm *f = uiForm(c);
+
+ return [f->view hugsTrailing];
+}
+
+static BOOL uiFormHugsBottom(uiDarwinControl *c)
+{
+ uiForm *f = uiForm(c);
+
+ return [f->view hugsBottom];
+}
+
+static void uiFormChildEdgeHuggingChanged(uiDarwinControl *c)
+{
+ uiForm *f = uiForm(c);
+
+ [f->view establishOurConstraints];
+}
+
+uiDarwinControlDefaultHuggingPriority(uiForm, view)
+uiDarwinControlDefaultSetHuggingPriority(uiForm, view)
+
+static void uiFormChildVisibilityChanged(uiDarwinControl *c)
+{
+ uiForm *f = uiForm(c);
+
+ [f->view establishOurConstraints];
+}
+
+void uiFormAppend(uiForm *f, const char *label, uiControl *c, int stretchy)
+{
+ // LONGTERM on other platforms
+ // or at leat allow this and implicitly turn it into a spacer
+ if (c == NULL)
+ userbug("You cannot add NULL to a uiForm.");
+ [f->view append:toNSString(label) c:c stretchy:stretchy];
+}
+
+void uiFormDelete(uiForm *f, int n)
+{
+ [f->view delete:n];
+}
+
+int uiFormPadded(uiForm *f)
+{
+ return [f->view isPadded];
+}
+
+void uiFormSetPadded(uiForm *f, int padded)
+{
+ [f->view setPadded:padded];
+}
+
+uiForm *uiNewForm(void)
+{
+ uiForm *f;
+
+ uiDarwinNewControl(uiForm, f);
+
+ f->view = [[formView alloc] initWithF:f];
+
+ return f;
+}
diff --git a/src/libui_sdl/libui/darwin/grid.m b/src/libui_sdl/libui/darwin/grid.m
new file mode 100644
index 0000000..d5c5fb1
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/grid.m
@@ -0,0 +1,800 @@
+// 11 june 2016
+#import "uipriv_darwin.h"
+
+// TODO the assorted test doesn't work right at all
+
+@interface gridChild : NSView
+@property uiControl *c;
+@property int left;
+@property int top;
+@property int xspan;
+@property int yspan;
+@property int hexpand;
+@property uiAlign halign;
+@property int vexpand;
+@property uiAlign valign;
+
+@property (strong) NSLayoutConstraint *leadingc;
+@property (strong) NSLayoutConstraint *topc;
+@property (strong) NSLayoutConstraint *trailingc;
+@property (strong) NSLayoutConstraint *bottomc;
+@property (strong) NSLayoutConstraint *xcenterc;
+@property (strong) NSLayoutConstraint *ycenterc;
+
+@property NSLayoutPriority oldHorzHuggingPri;
+@property NSLayoutPriority oldVertHuggingPri;
+- (void)setC:(uiControl *)c grid:(uiGrid *)g;
+- (void)onDestroy;
+- (NSView *)view;
+@end
+
+@interface gridView : NSView {
+ uiGrid *g;
+ NSMutableArray *children;
+ int padded;
+
+ NSMutableArray *edges;
+ NSMutableArray *inBetweens;
+
+ NSMutableArray *emptyCellViews;
+}
+- (id)initWithG:(uiGrid *)gg;
+- (void)onDestroy;
+- (void)removeOurConstraints;
+- (void)syncEnableStates:(int)enabled;
+- (CGFloat)paddingAmount;
+- (void)establishOurConstraints;
+- (void)append:(gridChild *)gc;
+- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at;
+- (int)isPadded;
+- (void)setPadded:(int)p;
+- (BOOL)hugsTrailing;
+- (BOOL)hugsBottom;
+- (int)nhexpand;
+- (int)nvexpand;
+@end
+
+struct uiGrid {
+ uiDarwinControl c;
+ gridView *view;
+};
+
+@implementation gridChild
+
+- (void)setC:(uiControl *)c grid:(uiGrid *)g
+{
+ self.c = c;
+ self.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationHorizontal);
+ self.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(self.c), NSLayoutConstraintOrientationVertical);
+
+ uiControlSetParent(self.c, uiControl(g));
+ uiDarwinControlSetSuperview(uiDarwinControl(self.c), self);
+ uiDarwinControlSyncEnableState(uiDarwinControl(self.c), uiControlEnabledToUser(uiControl(g)));
+
+ if (self.halign == uiAlignStart || self.halign == uiAlignFill) {
+ self.leadingc = mkConstraint(self, NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ [self view], NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiGrid child horizontal alignment start constraint");
+ [self addConstraint:self.leadingc];
+ }
+ if (self.halign == uiAlignCenter) {
+ self.xcenterc = mkConstraint(self, NSLayoutAttributeCenterX,
+ NSLayoutRelationEqual,
+ [self view], NSLayoutAttributeCenterX,
+ 1, 0,
+ @"uiGrid child horizontal alignment center constraint");
+ [self addConstraint:self.xcenterc];
+ }
+ if (self.halign == uiAlignEnd || self.halign == uiAlignFill) {
+ self.trailingc = mkConstraint(self, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ [self view], NSLayoutAttributeTrailing,
+ 1, 0,
+ @"uiGrid child horizontal alignment end constraint");
+ [self addConstraint:self.trailingc];
+ }
+
+ if (self.valign == uiAlignStart || self.valign == uiAlignFill) {
+ self.topc = mkConstraint(self, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ [self view], NSLayoutAttributeTop,
+ 1, 0,
+ @"uiGrid child vertical alignment start constraint");
+ [self addConstraint:self.topc];
+ }
+ if (self.valign == uiAlignCenter) {
+ self.ycenterc = mkConstraint(self, NSLayoutAttributeCenterY,
+ NSLayoutRelationEqual,
+ [self view], NSLayoutAttributeCenterY,
+ 1, 0,
+ @"uiGrid child vertical alignment center constraint");
+ [self addConstraint:self.ycenterc];
+ }
+ if (self.valign == uiAlignEnd || self.valign == uiAlignFill) {
+ self.bottomc = mkConstraint(self, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ [self view], NSLayoutAttributeBottom,
+ 1, 0,
+ @"uiGrid child vertical alignment end constraint");
+ [self addConstraint:self.bottomc];
+ }
+}
+
+- (void)onDestroy
+{
+ if (self.leadingc != nil) {
+ [self removeConstraint:self.leadingc];
+ self.leadingc = nil;
+ }
+ if (self.topc != nil) {
+ [self removeConstraint:self.topc];
+ self.topc = nil;
+ }
+ if (self.trailingc != nil) {
+ [self removeConstraint:self.trailingc];
+ self.trailingc = nil;
+ }
+ if (self.bottomc != nil) {
+ [self removeConstraint:self.bottomc];
+ self.bottomc = nil;
+ }
+ if (self.xcenterc != nil) {
+ [self removeConstraint:self.xcenterc];
+ self.xcenterc = nil;
+ }
+ if (self.ycenterc != nil) {
+ [self removeConstraint:self.ycenterc];
+ self.ycenterc = nil;
+ }
+
+ uiControlSetParent(self.c, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(self.c), nil);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(self.c), self.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
+}
+
+- (NSView *)view
+{
+ return (NSView *) uiControlHandle(self.c);
+}
+
+@end
+
+@implementation gridView
+
+- (id)initWithG:(uiGrid *)gg
+{
+ self = [super initWithFrame:NSZeroRect];
+ if (self != nil) {
+ self->g = gg;
+ self->padded = 0;
+ self->children = [NSMutableArray new];
+
+ self->edges = [NSMutableArray new];
+ self->inBetweens = [NSMutableArray new];
+
+ self->emptyCellViews = [NSMutableArray new];
+ }
+ return self;
+}
+
+- (void)onDestroy
+{
+ gridChild *gc;
+
+ [self removeOurConstraints];
+ [self->edges release];
+ [self->inBetweens release];
+
+ [self->emptyCellViews release];
+
+ for (gc in self->children) {
+ [gc onDestroy];
+ uiControlDestroy(gc.c);
+ [gc removeFromSuperview];
+ }
+ [self->children release];
+}
+
+- (void)removeOurConstraints
+{
+ NSView *v;
+
+ if ([self->edges count] != 0) {
+ [self removeConstraints:self->edges];
+ [self->edges removeAllObjects];
+ }
+ if ([self->inBetweens count] != 0) {
+ [self removeConstraints:self->inBetweens];
+ [self->inBetweens removeAllObjects];
+ }
+
+ for (v in self->emptyCellViews)
+ [v removeFromSuperview];
+ [self->emptyCellViews removeAllObjects];
+}
+
+- (void)syncEnableStates:(int)enabled
+{
+ gridChild *gc;
+
+ for (gc in self->children)
+ uiDarwinControlSyncEnableState(uiDarwinControl(gc.c), enabled);
+}
+
+- (CGFloat)paddingAmount
+{
+ if (!self->padded)
+ return 0.0;
+ return uiDarwinPaddingAmount(NULL);
+}
+
+// LONGTERM stop early if all controls are hidden
+- (void)establishOurConstraints
+{
+ gridChild *gc;
+ CGFloat padding;
+ int xmin, ymin;
+ int xmax, ymax;
+ int xcount, ycount;
+ BOOL first;
+ int **gg;
+ NSView ***gv;
+ BOOL **gspan;
+ int x, y;
+ int i;
+ NSLayoutConstraint *c;
+ int firstx, firsty;
+ BOOL *hexpand, *vexpand;
+ BOOL doit;
+ BOOL onlyEmptyAndSpanning;
+
+ [self removeOurConstraints];
+ if ([self->children count] == 0)
+ return;
+ padding = [self paddingAmount];
+
+ // first, figure out the minimum and maximum row and column numbers
+ // ignore hidden controls
+ first = YES;
+ for (gc in self->children) {
+ // this bit is important: it ensures row ymin and column xmin have at least one cell to draw, so the onlyEmptyAndSpanning logic below will never run on those rows
+ if (!uiControlVisible(gc.c))
+ continue;
+ if (first) {
+ xmin = gc.left;
+ ymin = gc.top;
+ xmax = gc.left + gc.xspan;
+ ymax = gc.top + gc.yspan;
+ first = NO;
+ continue;
+ }
+ if (xmin > gc.left)
+ xmin = gc.left;
+ if (ymin > gc.top)
+ ymin = gc.top;
+ if (xmax < (gc.left + gc.xspan))
+ xmax = gc.left + gc.xspan;
+ if (ymax < (gc.top + gc.yspan))
+ ymax = gc.top + gc.yspan;
+ }
+ if (first != NO) // the entire grid is hidden; do nothing
+ return;
+ xcount = xmax - xmin;
+ ycount = ymax - ymin;
+
+ // now build a topological map of the grid gg[y][x]
+ // also figure out which cells contain spanned views so they can be ignored later
+ // treat hidden controls by keeping the indices -1
+ gg = (int **) uiAlloc(ycount * sizeof (int *), "int[][]");
+ gspan = (BOOL **) uiAlloc(ycount * sizeof (BOOL *), "BOOL[][]");
+ for (y = 0; y < ycount; y++) {
+ gg[y] = (int *) uiAlloc(xcount * sizeof (int), "int[]");
+ gspan[y] = (BOOL *) uiAlloc(xcount * sizeof (BOOL), "BOOL[]");
+ for (x = 0; x < xcount; x++)
+ gg[y][x] = -1; // empty
+ }
+ for (i = 0; i < [self->children count]; i++) {
+ gc = (gridChild *) [self->children objectAtIndex:i];
+ if (!uiControlVisible(gc.c))
+ continue;
+ for (y = gc.top; y < gc.top + gc.yspan; y++)
+ for (x = gc.left; x < gc.left + gc.xspan; x++) {
+ gg[y - ymin][x - xmin] = i;
+ if (x != gc.left || y != gc.top)
+ gspan[y - ymin][x - xmin] = YES;
+ }
+ }
+
+ // if a row or column only contains emptys and spanning cells of a opposite-direction spannings, remove it by duplicating the previous row or column
+ for (y = 0; y < ycount; y++) {
+ onlyEmptyAndSpanning = YES;
+ for (x = 0; x < xcount; x++)
+ if (gg[y][x] != -1) {
+ gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
+ if (gc.yspan == 1 || gc.top - ymin == y) {
+ onlyEmptyAndSpanning = NO;
+ break;
+ }
+ }
+ if (onlyEmptyAndSpanning)
+ for (x = 0; x < xcount; x++) {
+ gg[y][x] = gg[y - 1][x];
+ gspan[y][x] = YES;
+ }
+ }
+ for (x = 0; x < xcount; x++) {
+ onlyEmptyAndSpanning = YES;
+ for (y = 0; y < ycount; y++)
+ if (gg[y][x] != -1) {
+ gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
+ if (gc.xspan == 1 || gc.left - xmin == x) {
+ onlyEmptyAndSpanning = NO;
+ break;
+ }
+ }
+ if (onlyEmptyAndSpanning)
+ for (y = 0; y < ycount; y++) {
+ gg[y][x] = gg[y][x - 1];
+ gspan[y][x] = YES;
+ }
+ }
+
+ // now build a topological map of the grid's views gv[y][x]
+ // for any empty cell, create a dummy view
+ gv = (NSView ***) uiAlloc(ycount * sizeof (NSView **), "NSView *[][]");
+ for (y = 0; y < ycount; y++) {
+ gv[y] = (NSView **) uiAlloc(xcount * sizeof (NSView *), "NSView *[]");
+ for (x = 0; x < xcount; x++)
+ if (gg[y][x] == -1) {
+ gv[y][x] = [[NSView alloc] initWithFrame:NSZeroRect];
+ [gv[y][x] setTranslatesAutoresizingMaskIntoConstraints:NO];
+ [self addSubview:gv[y][x]];
+ [self->emptyCellViews addObject:gv[y][x]];
+ } else {
+ gc = (gridChild *) [self->children objectAtIndex:gg[y][x]];
+ gv[y][x] = gc;
+ }
+ }
+
+ // now figure out which rows and columns really expand
+ hexpand = (BOOL *) uiAlloc(xcount * sizeof (BOOL), "BOOL[]");
+ vexpand = (BOOL *) uiAlloc(ycount * sizeof (BOOL), "BOOL[]");
+ // first, which don't span
+ for (gc in self->children) {
+ if (!uiControlVisible(gc.c))
+ continue;
+ if (gc.hexpand && gc.xspan == 1)
+ hexpand[gc.left - xmin] = YES;
+ if (gc.vexpand && gc.yspan == 1)
+ vexpand[gc.top - ymin] = YES;
+ }
+ // second, which do span
+ // the way we handle this is simple: if none of the spanned rows/columns expand, make all rows/columns expand
+ for (gc in self->children) {
+ if (!uiControlVisible(gc.c))
+ continue;
+ if (gc.hexpand && gc.xspan != 1) {
+ doit = YES;
+ for (x = gc.left; x < gc.left + gc.xspan; x++)
+ if (hexpand[x - xmin]) {
+ doit = NO;
+ break;
+ }
+ if (doit)
+ for (x = gc.left; x < gc.left + gc.xspan; x++)
+ hexpand[x - xmin] = YES;
+ }
+ if (gc.vexpand && gc.yspan != 1) {
+ doit = YES;
+ for (y = gc.top; y < gc.top + gc.yspan; y++)
+ if (vexpand[y - ymin]) {
+ doit = NO;
+ break;
+ }
+ if (doit)
+ for (y = gc.top; y < gc.top + gc.yspan; y++)
+ vexpand[y - ymin] = YES;
+ }
+ }
+
+ // now establish all the edge constraints
+ // leading and trailing edges
+ for (y = 0; y < ycount; y++) {
+ c = mkConstraint(self, NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ gv[y][0], NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiGrid leading edge constraint");
+ [self addConstraint:c];
+ [self->edges addObject:c];
+ c = mkConstraint(self, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ gv[y][xcount - 1], NSLayoutAttributeTrailing,
+ 1, 0,
+ @"uiGrid trailing edge constraint");
+ [self addConstraint:c];
+ [self->edges addObject:c];
+ }
+ // top and bottom edges
+ for (x = 0; x < xcount; x++) {
+ c = mkConstraint(self, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ gv[0][x], NSLayoutAttributeTop,
+ 1, 0,
+ @"uiGrid top edge constraint");
+ [self addConstraint:c];
+ [self->edges addObject:c];
+ c = mkConstraint(self, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ gv[ycount - 1][x], NSLayoutAttributeBottom,
+ 1, 0,
+ @"uiGrid bottom edge constraint");
+ [self addConstraint:c];
+ [self->edges addObject:c];
+ }
+
+ // now align leading and top edges
+ // do NOT align spanning cells!
+ for (x = 0; x < xcount; x++) {
+ for (y = 0; y < ycount; y++)
+ if (!gspan[y][x])
+ break;
+ firsty = y;
+ for (y++; y < ycount; y++) {
+ if (gspan[y][x])
+ continue;
+ c = mkConstraint(gv[firsty][x], NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ gv[y][x], NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiGrid column leading constraint");
+ [self addConstraint:c];
+ [self->edges addObject:c];
+ }
+ }
+ for (y = 0; y < ycount; y++) {
+ for (x = 0; x < xcount; x++)
+ if (!gspan[y][x])
+ break;
+ firstx = x;
+ for (x++; x < xcount; x++) {
+ if (gspan[y][x])
+ continue;
+ c = mkConstraint(gv[y][firstx], NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ gv[y][x], NSLayoutAttributeTop,
+ 1, 0,
+ @"uiGrid row top constraint");
+ [self addConstraint:c];
+ [self->edges addObject:c];
+ }
+ }
+
+ // now string adjacent views together
+ for (y = 0; y < ycount; y++)
+ for (x = 1; x < xcount; x++)
+ if (gv[y][x - 1] != gv[y][x]) {
+ c = mkConstraint(gv[y][x - 1], NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ gv[y][x], NSLayoutAttributeLeading,
+ 1, -padding,
+ @"uiGrid internal horizontal constraint");
+ [self addConstraint:c];
+ [self->inBetweens addObject:c];
+ }
+ for (x = 0; x < xcount; x++)
+ for (y = 1; y < ycount; y++)
+ if (gv[y - 1][x] != gv[y][x]) {
+ c = mkConstraint(gv[y - 1][x], NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ gv[y][x], NSLayoutAttributeTop,
+ 1, -padding,
+ @"uiGrid internal vertical constraint");
+ [self addConstraint:c];
+ [self->inBetweens addObject:c];
+ }
+
+ // now set priorities for all widgets that expand or not
+ // if a cell is in an expanding row, OR If it spans, then it must be willing to stretch
+ // otherwise, it tries not to
+ // note we don't use NSLayoutPriorityRequired as that will cause things to squish when they shouldn't
+ for (gc in self->children) {
+ NSLayoutPriority priority;
+
+ if (!uiControlVisible(gc.c))
+ continue;
+ if (hexpand[gc.left - xmin] || gc.xspan != 1)
+ priority = NSLayoutPriorityDefaultLow;
+ else
+ priority = NSLayoutPriorityDefaultHigh;
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationHorizontal);
+ // same for vertical direction
+ if (vexpand[gc.top - ymin] || gc.yspan != 1)
+ priority = NSLayoutPriorityDefaultLow;
+ else
+ priority = NSLayoutPriorityDefaultHigh;
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(gc.c), priority, NSLayoutConstraintOrientationVertical);
+ }
+
+ // TODO make all expanding rows/columns the same height/width
+
+ // and finally clean up
+ uiFree(hexpand);
+ uiFree(vexpand);
+ for (y = 0; y < ycount; y++) {
+ uiFree(gg[y]);
+ uiFree(gv[y]);
+ uiFree(gspan[y]);
+ }
+ uiFree(gg);
+ uiFree(gv);
+ uiFree(gspan);
+}
+
+- (void)append:(gridChild *)gc
+{
+ BOOL update;
+ int oldnh, oldnv;
+
+ [gc setTranslatesAutoresizingMaskIntoConstraints:NO];
+ [self addSubview:gc];
+
+ // no need to set priority here; that's done in establishOurConstraints
+
+ oldnh = [self nhexpand];
+ oldnv = [self nvexpand];
+ [self->children addObject:gc];
+
+ [self establishOurConstraints];
+ update = NO;
+ if (gc.hexpand)
+ if (oldnh == 0)
+ update = YES;
+ if (gc.vexpand)
+ if (oldnv == 0)
+ update = YES;
+ if (update)
+ uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(self->g));
+
+ [gc release]; // we don't need the initial reference now
+}
+
+- (void)insert:(gridChild *)gc after:(uiControl *)c at:(uiAt)at
+{
+ gridChild *other;
+ BOOL found;
+
+ found = NO;
+ for (other in self->children)
+ if (other.c == c) {
+ found = YES;
+ break;
+ }
+ if (!found)
+ userbug("Existing control %p is not in grid %p; you cannot add other controls next to it", c, self->g);
+
+ switch (at) {
+ case uiAtLeading:
+ gc.left = other.left - gc.xspan;
+ gc.top = other.top;
+ break;
+ case uiAtTop:
+ gc.left = other.left;
+ gc.top = other.top - gc.yspan;
+ break;
+ case uiAtTrailing:
+ gc.left = other.left + other.xspan;
+ gc.top = other.top;
+ break;
+ case uiAtBottom:
+ gc.left = other.left;
+ gc.top = other.top + other.yspan;
+ break;
+ // TODO add error checks to ALL enums
+ }
+
+ [self append:gc];
+}
+
+- (int)isPadded
+{
+ return self->padded;
+}
+
+- (void)setPadded:(int)p
+{
+ CGFloat padding;
+ NSLayoutConstraint *c;
+
+#if 0 /* TODO */
+dispatch_after(
+dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC),
+dispatch_get_main_queue(),
+^{ [[self window] visualizeConstraints:[self constraints]]; }
+);
+#endif
+ self->padded = p;
+ padding = [self paddingAmount];
+ for (c in self->inBetweens)
+ switch ([c firstAttribute]) {
+ case NSLayoutAttributeLeading:
+ case NSLayoutAttributeTop:
+ [c setConstant:padding];
+ break;
+ case NSLayoutAttributeTrailing:
+ case NSLayoutAttributeBottom:
+ [c setConstant:-padding];
+ break;
+ }
+}
+
+- (BOOL)hugsTrailing
+{
+ // only hug if we have horizontally expanding
+ return [self nhexpand] != 0;
+}
+
+- (BOOL)hugsBottom
+{
+ // only hug if we have vertically expanding
+ return [self nvexpand] != 0;
+}
+
+- (int)nhexpand
+{
+ gridChild *gc;
+ int n;
+
+ n = 0;
+ for (gc in self->children) {
+ if (!uiControlVisible(gc.c))
+ continue;
+ if (gc.hexpand)
+ n++;
+ }
+ return n;
+}
+
+- (int)nvexpand
+{
+ gridChild *gc;
+ int n;
+
+ n = 0;
+ for (gc in self->children) {
+ if (!uiControlVisible(gc.c))
+ continue;
+ if (gc.vexpand)
+ n++;
+ }
+ return n;
+}
+
+@end
+
+static void uiGridDestroy(uiControl *c)
+{
+ uiGrid *g = uiGrid(c);
+
+ [g->view onDestroy];
+ [g->view release];
+ uiFreeControl(uiControl(g));
+}
+
+uiDarwinControlDefaultHandle(uiGrid, view)
+uiDarwinControlDefaultParent(uiGrid, view)
+uiDarwinControlDefaultSetParent(uiGrid, view)
+uiDarwinControlDefaultToplevel(uiGrid, view)
+uiDarwinControlDefaultVisible(uiGrid, view)
+uiDarwinControlDefaultShow(uiGrid, view)
+uiDarwinControlDefaultHide(uiGrid, view)
+uiDarwinControlDefaultEnabled(uiGrid, view)
+uiDarwinControlDefaultEnable(uiGrid, view)
+uiDarwinControlDefaultDisable(uiGrid, view)
+
+static void uiGridSyncEnableState(uiDarwinControl *c, int enabled)
+{
+ uiGrid *g = uiGrid(c);
+
+ if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))
+ return;
+ [g->view syncEnableStates:enabled];
+}
+
+uiDarwinControlDefaultSetSuperview(uiGrid, view)
+
+static BOOL uiGridHugsTrailingEdge(uiDarwinControl *c)
+{
+ uiGrid *g = uiGrid(c);
+
+ return [g->view hugsTrailing];
+}
+
+static BOOL uiGridHugsBottom(uiDarwinControl *c)
+{
+ uiGrid *g = uiGrid(c);
+
+ return [g->view hugsBottom];
+}
+
+static void uiGridChildEdgeHuggingChanged(uiDarwinControl *c)
+{
+ uiGrid *g = uiGrid(c);
+
+ [g->view establishOurConstraints];
+}
+
+uiDarwinControlDefaultHuggingPriority(uiGrid, view)
+uiDarwinControlDefaultSetHuggingPriority(uiGrid, view)
+
+static void uiGridChildVisibilityChanged(uiDarwinControl *c)
+{
+ uiGrid *g = uiGrid(c);
+
+ [g->view establishOurConstraints];
+}
+
+static gridChild *toChild(uiControl *c, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign, uiGrid *g)
+{
+ gridChild *gc;
+
+ if (xspan < 0)
+ userbug("You cannot have a negative xspan in a uiGrid cell.");
+ if (yspan < 0)
+ userbug("You cannot have a negative yspan in a uiGrid cell.");
+ gc = [gridChild new];
+ gc.xspan = xspan;
+ gc.yspan = yspan;
+ gc.hexpand = hexpand;
+ gc.halign = halign;
+ gc.vexpand = vexpand;
+ gc.valign = valign;
+ [gc setC:c grid:g];
+ return gc;
+}
+
+void uiGridAppend(uiGrid *g, uiControl *c, int left, int top, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
+{
+ gridChild *gc;
+
+ // LONGTERM on other platforms
+ // or at leat allow this and implicitly turn it into a spacer
+ if (c == NULL)
+ userbug("You cannot add NULL to a uiGrid.");
+ gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
+ gc.left = left;
+ gc.top = top;
+ [g->view append:gc];
+}
+
+void uiGridInsertAt(uiGrid *g, uiControl *c, uiControl *existing, uiAt at, int xspan, int yspan, int hexpand, uiAlign halign, int vexpand, uiAlign valign)
+{
+ gridChild *gc;
+
+ gc = toChild(c, xspan, yspan, hexpand, halign, vexpand, valign, g);
+ [g->view insert:gc after:existing at:at];
+}
+
+int uiGridPadded(uiGrid *g)
+{
+ return [g->view isPadded];
+}
+
+void uiGridSetPadded(uiGrid *g, int padded)
+{
+ [g->view setPadded:padded];
+}
+
+uiGrid *uiNewGrid(void)
+{
+ uiGrid *g;
+
+ uiDarwinNewControl(uiGrid, g);
+
+ g->view = [[gridView alloc] initWithG:g];
+
+ return g;
+}
diff --git a/src/libui_sdl/libui/darwin/group.m b/src/libui_sdl/libui/darwin/group.m
new file mode 100644
index 0000000..0050bbd
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/group.m
@@ -0,0 +1,194 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+struct uiGroup {
+ uiDarwinControl c;
+ NSBox *box;
+ uiControl *child;
+ NSLayoutPriority oldHorzHuggingPri;
+ NSLayoutPriority oldVertHuggingPri;
+ int margined;
+ struct singleChildConstraints constraints;
+ NSLayoutPriority horzHuggingPri;
+ NSLayoutPriority vertHuggingPri;
+};
+
+static void removeConstraints(uiGroup *g)
+{
+ // set to contentView instead of to the box itself, otherwise we get clipping underneath the label
+ singleChildConstraintsRemove(&(g->constraints), [g->box contentView]);
+}
+
+static void uiGroupDestroy(uiControl *c)
+{
+ uiGroup *g = uiGroup(c);
+
+ removeConstraints(g);
+ if (g->child != NULL) {
+ uiControlSetParent(g->child, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);
+ uiControlDestroy(g->child);
+ }
+ [g->box release];
+ uiFreeControl(uiControl(g));
+}
+
+uiDarwinControlDefaultHandle(uiGroup, box)
+uiDarwinControlDefaultParent(uiGroup, box)
+uiDarwinControlDefaultSetParent(uiGroup, box)
+uiDarwinControlDefaultToplevel(uiGroup, box)
+uiDarwinControlDefaultVisible(uiGroup, box)
+uiDarwinControlDefaultShow(uiGroup, box)
+uiDarwinControlDefaultHide(uiGroup, box)
+uiDarwinControlDefaultEnabled(uiGroup, box)
+uiDarwinControlDefaultEnable(uiGroup, box)
+uiDarwinControlDefaultDisable(uiGroup, box)
+
+static void uiGroupSyncEnableState(uiDarwinControl *c, int enabled)
+{
+ uiGroup *g = uiGroup(c);
+
+ if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(g), enabled))
+ return;
+ if (g->child != NULL)
+ uiDarwinControlSyncEnableState(uiDarwinControl(g->child), enabled);
+}
+
+uiDarwinControlDefaultSetSuperview(uiGroup, box)
+
+static void groupRelayout(uiGroup *g)
+{
+ NSView *childView;
+
+ removeConstraints(g);
+ if (g->child == NULL)
+ return;
+ childView = (NSView *) uiControlHandle(g->child);
+ singleChildConstraintsEstablish(&(g->constraints),
+ [g->box contentView], childView,
+ uiDarwinControlHugsTrailingEdge(uiDarwinControl(g->child)),
+ uiDarwinControlHugsBottom(uiDarwinControl(g->child)),
+ g->margined,
+ @"uiGroup");
+ // needed for some very rare drawing errors...
+ jiggleViewLayout(g->box);
+}
+
+// TODO rename these since I'm starting to get confused by what they mean by hugging
+BOOL uiGroupHugsTrailingEdge(uiDarwinControl *c)
+{
+ uiGroup *g = uiGroup(c);
+
+ // TODO make a function?
+ return g->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;
+}
+
+BOOL uiGroupHugsBottom(uiDarwinControl *c)
+{
+ uiGroup *g = uiGroup(c);
+
+ return g->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;
+}
+
+static void uiGroupChildEdgeHuggingChanged(uiDarwinControl *c)
+{
+ uiGroup *g = uiGroup(c);
+
+ groupRelayout(g);
+}
+
+static NSLayoutPriority uiGroupHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
+{
+ uiGroup *g = uiGroup(c);
+
+ if (orientation == NSLayoutConstraintOrientationHorizontal)
+ return g->horzHuggingPri;
+ return g->vertHuggingPri;
+}
+
+static void uiGroupSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
+{
+ uiGroup *g = uiGroup(c);
+
+ if (orientation == NSLayoutConstraintOrientationHorizontal)
+ g->horzHuggingPri = priority;
+ else
+ g->vertHuggingPri = priority;
+ uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(g));
+}
+
+static void uiGroupChildVisibilityChanged(uiDarwinControl *c)
+{
+ uiGroup *g = uiGroup(c);
+
+ groupRelayout(g);
+}
+
+char *uiGroupTitle(uiGroup *g)
+{
+ return uiDarwinNSStringToText([g->box title]);
+}
+
+void uiGroupSetTitle(uiGroup *g, const char *title)
+{
+ [g->box setTitle:toNSString(title)];
+}
+
+void uiGroupSetChild(uiGroup *g, uiControl *child)
+{
+ NSView *childView;
+
+ if (g->child != NULL) {
+ removeConstraints(g);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), g->oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
+ uiControlSetParent(g->child, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(g->child), nil);
+ }
+ g->child = child;
+ if (g->child != NULL) {
+ childView = (NSView *) uiControlHandle(g->child);
+ uiControlSetParent(g->child, uiControl(g));
+ uiDarwinControlSetSuperview(uiDarwinControl(g->child), [g->box contentView]);
+ uiDarwinControlSyncEnableState(uiDarwinControl(g->child), uiControlEnabledToUser(uiControl(g)));
+ // don't hug, just in case we're a stretchy group
+ g->oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationHorizontal);
+ g->oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(g->child), NSLayoutConstraintOrientationVertical);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(g->child), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);
+ }
+ groupRelayout(g);
+}
+
+int uiGroupMargined(uiGroup *g)
+{
+ return g->margined;
+}
+
+void uiGroupSetMargined(uiGroup *g, int margined)
+{
+ g->margined = margined;
+ singleChildConstraintsSetMargined(&(g->constraints), g->margined);
+}
+
+uiGroup *uiNewGroup(const char *title)
+{
+ uiGroup *g;
+
+ uiDarwinNewControl(uiGroup, g);
+
+ g->box = [[NSBox alloc] initWithFrame:NSZeroRect];
+ [g->box setTitle:toNSString(title)];
+ [g->box setBoxType:NSBoxPrimary];
+ [g->box setBorderType:NSLineBorder];
+ [g->box setTransparent:NO];
+ [g->box setTitlePosition:NSAtTop];
+ // we can't use uiDarwinSetControlFont() because the selector is different
+ [g->box setTitleFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
+
+ // default to low hugging to not hug edges
+ g->horzHuggingPri = NSLayoutPriorityDefaultLow;
+ g->vertHuggingPri = NSLayoutPriorityDefaultLow;
+
+ return g;
+}
diff --git a/src/libui_sdl/libui/darwin/image.m b/src/libui_sdl/libui/darwin/image.m
new file mode 100644
index 0000000..b62de31
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/image.m
@@ -0,0 +1,82 @@
+// 25 june 2016
+#import "uipriv_darwin.h"
+
+struct uiImage {
+ NSImage *i;
+ NSSize size;
+ NSMutableArray *swizzled;
+};
+
+uiImage *uiNewImage(double width, double height)
+{
+ uiImage *i;
+
+ i = uiNew(uiImage);
+ i->size = NSMakeSize(width, height);
+ i->i = [[NSImage alloc] initWithSize:i->size];
+ i->swizzled = [NSMutableArray new];
+ return i;
+}
+
+void uiFreeImage(uiImage *i)
+{
+ NSValue *v;
+
+ [i->i release];
+ // to be safe, do this after releasing the image
+ for (v in i->swizzled)
+ uiFree([v pointerValue]);
+ [i->swizzled release];
+ uiFree(i);
+}
+
+void uiImageAppend(uiImage *i, void *pixels, int pixelWidth, int pixelHeight, int pixelStride)
+{
+ NSBitmapImageRep *repCalibrated, *repsRGB;
+ uint8_t *swizzled, *bp, *sp;
+ int x, y;
+ unsigned char *pix[1];
+
+ // OS X demands that R and B are in the opposite order from what we expect
+ // we must swizzle :(
+ // LONGTERM test on a big-endian system
+ swizzled = (uint8_t *) uiAlloc((pixelStride * pixelHeight * 4) * sizeof (uint8_t), "uint8_t[]");
+ bp = (uint8_t *) pixels;
+ sp = swizzled;
+ for (y = 0; y < pixelHeight * pixelStride; y += pixelStride)
+ for (x = 0; x < pixelStride; x++) {
+ sp[0] = bp[2];
+ sp[1] = bp[1];
+ sp[2] = bp[0];
+ sp[3] = bp[3];
+ sp += 4;
+ bp += 4;
+ }
+
+ pix[0] = (unsigned char *) swizzled;
+ repCalibrated = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:pix
+ pixelsWide:pixelWidth
+ pixelsHigh:pixelHeight
+ bitsPerSample:8
+ samplesPerPixel:4
+ hasAlpha:YES
+ isPlanar:NO
+ colorSpaceName:NSCalibratedRGBColorSpace
+ bitmapFormat:0
+ bytesPerRow:pixelStride
+ bitsPerPixel:32];
+ repsRGB = [repCalibrated bitmapImageRepByRetaggingWithColorSpace:[NSColorSpace sRGBColorSpace]];
+ [repCalibrated release];
+
+ [i->i addRepresentation:repsRGB];
+ [repsRGB setSize:i->size];
+ [repsRGB release];
+
+ // we need to keep swizzled alive for NSBitmapImageRep
+ [i->swizzled addObject:[NSValue valueWithPointer:swizzled]];
+}
+
+NSImage *imageImage(uiImage *i)
+{
+ return i->i;
+}
diff --git a/src/libui_sdl/libui/darwin/label.m b/src/libui_sdl/libui/darwin/label.m
new file mode 100644
index 0000000..897bc3f
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/label.m
@@ -0,0 +1,43 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+struct uiLabel {
+ uiDarwinControl c;
+ NSTextField *textfield;
+};
+
+uiDarwinControlAllDefaults(uiLabel, textfield)
+
+char *uiLabelText(uiLabel *l)
+{
+ return uiDarwinNSStringToText([l->textfield stringValue]);
+}
+
+void uiLabelSetText(uiLabel *l, const char *text)
+{
+ [l->textfield setStringValue:toNSString(text)];
+}
+
+NSTextField *newLabel(NSString *str)
+{
+ NSTextField *tf;
+
+ tf = [[NSTextField alloc] initWithFrame:NSZeroRect];
+ [tf setStringValue:str];
+ [tf setEditable:NO];
+ [tf setSelectable:NO];
+ [tf setDrawsBackground:NO];
+ finishNewTextField(tf, NO);
+ return tf;
+}
+
+uiLabel *uiNewLabel(const char *text)
+{
+ uiLabel *l;
+
+ uiDarwinNewControl(uiLabel, l);
+
+ l->textfield = newLabel(toNSString(text));
+
+ return l;
+}
diff --git a/src/libui_sdl/libui/darwin/main.m b/src/libui_sdl/libui/darwin/main.m
new file mode 100644
index 0000000..59a8683
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/main.m
@@ -0,0 +1,239 @@
+// 6 april 2015
+#import "uipriv_darwin.h"
+
+static BOOL canQuit = NO;
+static NSAutoreleasePool *globalPool;
+static applicationClass *app;
+static appDelegate *delegate;
+
+static BOOL (^isRunning)(void);
+static BOOL stepsIsRunning;
+
+@implementation applicationClass
+
+- (void)sendEvent:(NSEvent *)e
+{
+ if (sendAreaEvents(e) != 0)
+ return;
+ [super sendEvent:e];
+}
+
+// NSColorPanel always sends changeColor: to the first responder regardless of whether there's a target set on it
+// we can override it here (see colorbutton.m)
+// thanks to mikeash in irc.freenode.net/#macdev for informing me this is how the first responder chain is initiated
+// it turns out NSFontManager also sends changeFont: through this; let's inhibit that here too (see fontbutton.m)
+- (BOOL)sendAction:(SEL)sel to:(id)to from:(id)from
+{
+ if (colorButtonInhibitSendAction(sel, from, to))
+ return NO;
+ if (fontButtonInhibitSendAction(sel, from, to))
+ return NO;
+ return [super sendAction:sel to:to from:from];
+}
+
+// likewise, NSFontManager also sends NSFontPanelValidation messages to the first responder, however it does NOT use sendAction:from:to:!
+// instead, it uses this one (thanks swillits in irc.freenode.net/#macdev)
+// we also need to override it (see fontbutton.m)
+- (id)targetForAction:(SEL)sel to:(id)to from:(id)from
+{
+ id override;
+
+ if (fontButtonOverrideTargetForAction(sel, from, to, &override))
+ return override;
+ return [super targetForAction:sel to:to from:from];
+}
+
+// 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.
+
+ // well let's not do nothing; let's actually quit our graceful way
+ NSEvent *e;
+
+ if (!canQuit)
+ implbug("call to [NSApp terminate:] when not ready to terminate; definitely contact andlabs");
+
+ [realNSApp() stop:realNSApp()];
+ // 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];
+ [realNSApp() 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)
+
+ // and in case uiMainSteps() was called
+ stepsIsRunning = NO;
+}
+
+@end
+
+@implementation appDelegate
+
+- (void)dealloc
+{
+ // Apple docs: "Don't Use Accessor Methods in Initializer Methods and dealloc"
+ [_menuManager release];
+ [super dealloc];
+}
+
+- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app
+{
+ // for debugging
+ NSLog(@"in applicationShouldTerminate:");
+ if (shouldQuit()) {
+ canQuit = YES;
+ // this will call terminate:, which is the same as uiQuit()
+ return NSTerminateNow;
+ }
+ return NSTerminateCancel;
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
+{
+ return NO;
+}
+
+@end
+
+uiInitOptions options;
+
+const char *uiInit(uiInitOptions *o)
+{
+ @autoreleasepool {
+ options = *o;
+ app = [[applicationClass sharedApplication] retain];
+ // 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
+ [realNSApp() setActivationPolicy:NSApplicationActivationPolicyRegular];
+ delegate = [appDelegate new];
+ [realNSApp() setDelegate:delegate];
+
+ initAlloc();
+
+ // always do this so we always have an application menu
+ appDelegate().menuManager = [[menuManager new] autorelease];
+ [realNSApp() setMainMenu:[appDelegate().menuManager makeMenubar]];
+
+ setupFontPanel();
+ }
+
+ globalPool = [[NSAutoreleasePool alloc] init];
+
+ return NULL;
+}
+
+void uiUninit(void)
+{
+ if (!globalPool) {
+ userbug("You must call uiInit() first!");
+ }
+ [globalPool release];
+
+ @autoreleasepool {
+ [delegate release];
+ [realNSApp() setDelegate:nil];
+ [app release];
+ uninitAlloc();
+ }
+}
+
+void uiFreeInitError(const char *err)
+{
+}
+
+void uiMain(void)
+{
+ isRunning = ^{
+ return [realNSApp() isRunning];
+ };
+ [realNSApp() run];
+}
+
+void uiMainSteps(void)
+{
+ // SDL does this and it seems to be necessary for the menubar to work (see #182)
+ [realNSApp() finishLaunching];
+ isRunning = ^{
+ return stepsIsRunning;
+ };
+ stepsIsRunning = YES;
+}
+
+int uiMainStep(int wait)
+{
+ struct nextEventArgs nea;
+
+ nea.mask = NSAnyEventMask;
+
+ // ProPuke did this in his original PR requesting this
+ // I'm not sure if this will work, but I assume it will...
+ nea.duration = [NSDate distantPast];
+ if (wait) // but this is normal so it will work
+ nea.duration = [NSDate distantFuture];
+
+ nea.mode = NSDefaultRunLoopMode;
+ nea.dequeue = YES;
+
+ return mainStep(&nea, ^(NSEvent *e) {
+ return NO;
+ });
+}
+
+// see also:
+// - http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
+// - https://github.com/gnustep/gui/blob/master/Source/NSApplication.m
+int mainStep(struct nextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *e))
+{
+ NSDate *expire;
+ NSEvent *e;
+ NSEventType type;
+
+ @autoreleasepool {
+ if (!isRunning())
+ return 0;
+
+ e = [realNSApp() nextEventMatchingMask:nea->mask
+ untilDate:nea->duration
+ inMode:nea->mode
+ dequeue:nea->dequeue];
+ if (e == nil)
+ return 1;
+
+ type = [e type];
+ if (!interceptEvent(e))
+ [realNSApp() sendEvent:e];
+ [realNSApp() updateWindows];
+
+ // GNUstep does this
+ // it also updates the Services menu but there doesn't seem to be a public API for that so
+ if (type != NSPeriodic && type != NSMouseMoved)
+ [[realNSApp() mainMenu] update];
+
+ return 1;
+ }
+}
+
+void uiQuit(void)
+{
+ canQuit = YES;
+ [realNSApp() terminate:realNSApp()];
+}
+
+// thanks to mikeash in irc.freenode.net/#macdev for suggesting the use of Grand Central Dispatch for this
+// LONGTERM will dispatch_get_main_queue() break after _CFRunLoopSetCurrent()?
+void uiQueueMain(void (*f)(void *data), void *data)
+{
+ // dispatch_get_main_queue() is a serial queue so it will not execute multiple uiQueueMain() functions concurrently
+ // the signature of f matches dispatch_function_t
+ dispatch_async_f(dispatch_get_main_queue(), data, f);
+}
diff --git a/src/libui_sdl/libui/darwin/map.m b/src/libui_sdl/libui/darwin/map.m
new file mode 100644
index 0000000..46a7b8d
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/map.m
@@ -0,0 +1,59 @@
+// 17 august 2015
+#import "uipriv_darwin.h"
+
+// unfortunately NSMutableDictionary copies its keys, meaning we can't use it for pointers
+// hence, this file
+// we could expose a NSMapTable directly, but let's treat all pointers as opaque and hide the implementation, just to be safe and prevent even more rewrites later
+struct mapTable {
+ NSMapTable *m;
+};
+
+struct mapTable *newMap(void)
+{
+ struct mapTable *m;
+
+ m = uiNew(struct mapTable);
+ m->m = [[NSMapTable alloc] initWithKeyOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
+ valueOptions:(NSPointerFunctionsOpaqueMemory | NSPointerFunctionsOpaquePersonality)
+ capacity:0];
+ return m;
+}
+
+void mapDestroy(struct mapTable *m)
+{
+ if ([m->m count] != 0)
+ implbug("attempt to destroy map with items inside");
+ [m->m release];
+ uiFree(m);
+}
+
+void *mapGet(struct mapTable *m, void *key)
+{
+ return NSMapGet(m->m, key);
+}
+
+void mapSet(struct mapTable *m, void *key, void *value)
+{
+ NSMapInsert(m->m, key, value);
+}
+
+void mapDelete(struct mapTable *m, void *key)
+{
+ NSMapRemove(m->m, key);
+}
+
+void mapWalk(struct mapTable *m, void (*f)(void *key, void *value))
+{
+ NSMapEnumerator e = NSEnumerateMapTable(m->m);
+ void *k = NULL;
+ void *v = NULL;
+ while (NSNextMapEnumeratorPair(&e, &k, &v)) {
+ f(k, v);
+ }
+ NSEndMapTableEnumeration(&e);
+}
+
+void mapReset(struct mapTable *m)
+{
+ NSResetMapTable(m->m);
+}
diff --git a/src/libui_sdl/libui/darwin/menu.m b/src/libui_sdl/libui/darwin/menu.m
new file mode 100644
index 0000000..735cac5
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/menu.m
@@ -0,0 +1,368 @@
+// 28 april 2015
+#import "uipriv_darwin.h"
+
+static NSMutableArray *menus = nil;
+static BOOL menusFinalized = NO;
+
+struct uiMenu {
+ NSMenu *menu;
+ NSMenuItem *item;
+ NSMutableArray *items;
+};
+
+struct uiMenuItem {
+ NSMenuItem *item;
+ int type;
+ BOOL disabled;
+ void (*onClicked)(uiMenuItem *, uiWindow *, void *);
+ void *onClickedData;
+};
+
+enum {
+ typeRegular,
+ typeCheckbox,
+ typeQuit,
+ typePreferences,
+ typeAbout,
+ typeSeparator,
+};
+
+static void mapItemReleaser(void *key, void *value)
+{
+ uiMenuItem *item;
+
+ item = (uiMenuItem *)value;
+ [item->item release];
+}
+
+@implementation menuManager
+
+- (id)init
+{
+ self = [super init];
+ if (self) {
+ self->items = newMap();
+ self->hasQuit = NO;
+ self->hasPreferences = NO;
+ self->hasAbout = NO;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ mapWalk(self->items, mapItemReleaser);
+ mapReset(self->items);
+ mapDestroy(self->items);
+ uninitMenus();
+ [super dealloc];
+}
+
+- (IBAction)onClicked:(id)sender
+{
+ uiMenuItem *item;
+
+ item = (uiMenuItem *) mapGet(self->items, sender);
+ if (item->type == typeCheckbox)
+ uiMenuItemSetChecked(item, !uiMenuItemChecked(item));
+ // use the key window as the source of the menu event; it's the active window
+ (*(item->onClicked))(item, windowFromNSWindow([realNSApp() keyWindow]), item->onClickedData);
+}
+
+- (IBAction)onQuitClicked:(id)sender
+{
+ if (shouldQuit())
+ uiQuit();
+}
+
+- (void)register:(NSMenuItem *)item to:(uiMenuItem *)smi
+{
+ switch (smi->type) {
+ case typeQuit:
+ if (self->hasQuit)
+ userbug("You can't have multiple Quit menu items in one program.");
+ self->hasQuit = YES;
+ break;
+ case typePreferences:
+ if (self->hasPreferences)
+ userbug("You can't have multiple Preferences menu items in one program.");
+ self->hasPreferences = YES;
+ break;
+ case typeAbout:
+ if (self->hasAbout)
+ userbug("You can't have multiple About menu items in one program.");
+ self->hasAbout = YES;
+ break;
+ }
+ mapSet(self->items, item, smi);
+}
+
+// on OS X there are two ways to handle menu items being enabled or disabled: automatically and manually
+// unfortunately, the application menu requires automatic menu handling for the Hide, Hide Others, and Show All items to work correctly
+// therefore, we have to handle enabling of the other options ourselves
+- (BOOL)validateMenuItem:(NSMenuItem *)item
+{
+ uiMenuItem *smi;
+
+ // disable the special items if they aren't present
+ if (item == self.quitItem && !self->hasQuit)
+ return NO;
+ if (item == self.preferencesItem && !self->hasPreferences)
+ return NO;
+ if (item == self.aboutItem && !self->hasAbout)
+ return NO;
+ // then poll the item's enabled/disabled state
+ smi = (uiMenuItem *) mapGet(self->items, item);
+ return !smi->disabled;
+}
+
+// Cocoa constructs the default application menu by hand for each program; that's what MainMenu.[nx]ib does
+- (void)buildApplicationMenu:(NSMenu *)menubar
+{
+ NSString *appName;
+ NSMenuItem *appMenuItem;
+ NSMenu *appMenu;
+ NSMenuItem *item;
+ NSString *title;
+ NSMenu *servicesMenu;
+
+ // note: no need to call setAppleMenu: on this anymore; see https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKitOlderNotes/#X10_6Notes
+ appName = [[NSProcessInfo processInfo] processName];
+ appMenuItem = [[[NSMenuItem alloc] initWithTitle:appName action:NULL keyEquivalent:@""] autorelease];
+ appMenu = [[[NSMenu alloc] initWithTitle:appName] autorelease];
+ [appMenuItem setSubmenu:appMenu];
+ [menubar addItem:appMenuItem];
+
+ // first is About
+ title = [@"About " stringByAppendingString:appName];
+ item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onClicked:) keyEquivalent:@""] autorelease];
+ [item setTarget:self];
+ [appMenu addItem:item];
+ self.aboutItem = item;
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // next is Preferences
+ item = [[[NSMenuItem alloc] initWithTitle:@"Preferences…" action:@selector(onClicked:) keyEquivalent:@","] autorelease];
+ [item setTarget:self];
+ [appMenu addItem:item];
+ self.preferencesItem = item;
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // next is Services
+ item = [[[NSMenuItem alloc] initWithTitle:@"Services" action:NULL keyEquivalent:@""] autorelease];
+ servicesMenu = [[[NSMenu alloc] initWithTitle:@"Services"] autorelease];
+ [item setSubmenu:servicesMenu];
+ [realNSApp() setServicesMenu:servicesMenu];
+ [appMenu addItem:item];
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // next are the three hiding options
+ title = [@"Hide " stringByAppendingString:appName];
+ item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(hide:) keyEquivalent:@"h"] autorelease];
+ // the .xib file says they go to -1 ("First Responder", which sounds wrong...)
+ // to do that, we simply leave the target as nil
+ [appMenu addItem:item];
+ item = [[[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"] autorelease];
+ [item setKeyEquivalentModifierMask:(NSAlternateKeyMask | NSCommandKeyMask)];
+ [appMenu addItem:item];
+ item = [[[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""] autorelease];
+ [appMenu addItem:item];
+
+ [appMenu addItem:[NSMenuItem separatorItem]];
+
+ // and finally Quit
+ // DON'T use @selector(terminate:) as the action; we handle termination ourselves
+ title = [@"Quit " stringByAppendingString:appName];
+ item = [[[NSMenuItem alloc] initWithTitle:title action:@selector(onQuitClicked:) keyEquivalent:@"q"] autorelease];
+ [item setTarget:self];
+ [appMenu addItem:item];
+ self.quitItem = item;
+}
+
+- (NSMenu *)makeMenubar
+{
+ NSMenu *menubar;
+
+ menubar = [[[NSMenu alloc] initWithTitle:@""] autorelease];
+ [self buildApplicationMenu:menubar];
+ return menubar;
+}
+
+@end
+
+static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
+{
+ // do nothing
+}
+
+void uiMenuItemEnable(uiMenuItem *item)
+{
+ item->disabled = NO;
+ // we don't need to explicitly update the menus here; they'll be updated the next time they're opened (thanks mikeash in irc.freenode.net/#macdev)
+}
+
+void uiMenuItemDisable(uiMenuItem *item)
+{
+ item->disabled = YES;
+}
+
+void uiMenuItemOnClicked(uiMenuItem *item, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
+{
+ if (item->type == typeQuit)
+ userbug("You can't call uiMenuItemOnClicked() on a Quit item; use uiOnShouldQuit() instead.");
+ item->onClicked = f;
+ item->onClickedData = data;
+}
+
+int uiMenuItemChecked(uiMenuItem *item)
+{
+ return [item->item state] != NSOffState;
+}
+
+void uiMenuItemSetChecked(uiMenuItem *item, int checked)
+{
+ NSInteger state;
+
+ state = NSOffState;
+ if ([item->item state] == NSOffState)
+ state = NSOnState;
+ [item->item setState:state];
+}
+
+static uiMenuItem *newItem(uiMenu *m, int type, const char *name)
+{
+ @autoreleasepool {
+
+ uiMenuItem *item;
+
+ if (menusFinalized)
+ userbug("You can't create a new menu item after menus have been finalized.");
+
+ item = uiNew(uiMenuItem);
+
+ item->type = type;
+ switch (item->type) {
+ case typeQuit:
+ item->item = [appDelegate().menuManager.quitItem retain];
+ break;
+ case typePreferences:
+ item->item = [appDelegate().menuManager.preferencesItem retain];
+ break;
+ case typeAbout:
+ item->item = [appDelegate().menuManager.aboutItem retain];
+ break;
+ case typeSeparator:
+ item->item = [[NSMenuItem separatorItem] retain];
+ [m->menu addItem:item->item];
+ break;
+ default:
+ item->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:@selector(onClicked:) keyEquivalent:@""];
+ [item->item setTarget:appDelegate().menuManager];
+ [m->menu addItem:item->item];
+ break;
+ }
+
+ [appDelegate().menuManager register:item->item to:item];
+ item->onClicked = defaultOnClicked;
+
+ [m->items addObject:[NSValue valueWithPointer:item]];
+
+ return item;
+
+ } // @autoreleasepool
+}
+
+uiMenuItem *uiMenuAppendItem(uiMenu *m, const char *name)
+{
+ return newItem(m, typeRegular, name);
+}
+
+uiMenuItem *uiMenuAppendCheckItem(uiMenu *m, const char *name)
+{
+ return newItem(m, typeCheckbox, name);
+}
+
+uiMenuItem *uiMenuAppendQuitItem(uiMenu *m)
+{
+ // duplicate check is in the register:to: selector
+ return newItem(m, typeQuit, NULL);
+}
+
+uiMenuItem *uiMenuAppendPreferencesItem(uiMenu *m)
+{
+ // duplicate check is in the register:to: selector
+ return newItem(m, typePreferences, NULL);
+}
+
+uiMenuItem *uiMenuAppendAboutItem(uiMenu *m)
+{
+ // duplicate check is in the register:to: selector
+ return newItem(m, typeAbout, NULL);
+}
+
+void uiMenuAppendSeparator(uiMenu *m)
+{
+ newItem(m, typeSeparator, NULL);
+}
+
+uiMenu *uiNewMenu(const char *name)
+{
+ @autoreleasepool {
+
+ uiMenu *m;
+
+ if (menusFinalized)
+ userbug("You can't create a new menu after menus have been finalized.");
+ if (menus == nil)
+ menus = [NSMutableArray new];
+
+ m = uiNew(uiMenu);
+
+ m->menu = [[NSMenu alloc] initWithTitle:toNSString(name)];
+ // use automatic menu item enabling for all menus for consistency's sake
+
+ m->item = [[NSMenuItem alloc] initWithTitle:toNSString(name) action:NULL keyEquivalent:@""];
+ [m->item setSubmenu:m->menu];
+
+ m->items = [NSMutableArray new];
+
+ [[realNSApp() mainMenu] addItem:m->item];
+
+ [menus addObject:[NSValue valueWithPointer:m]];
+
+ return m;
+
+ } // @autoreleasepool
+}
+
+void finalizeMenus(void)
+{
+ menusFinalized = YES;
+}
+
+void uninitMenus(void)
+{
+ if (menus == NULL)
+ return;
+ [menus enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
+ NSValue *v;
+ uiMenu *m;
+
+ v = (NSValue *) obj;
+ m = (uiMenu *) [v pointerValue];
+ [m->items enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL *stop) {
+ NSValue *v;
+ uiMenuItem *mi;
+
+ v = (NSValue *) obj;
+ mi = (uiMenuItem *) [v pointerValue];
+ uiFree(mi);
+ }];
+ [m->items release];
+ uiFree(m);
+ }];
+ [menus release];
+}
diff --git a/src/libui_sdl/libui/darwin/multilineentry.m b/src/libui_sdl/libui/darwin/multilineentry.m
new file mode 100644
index 0000000..605e900
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/multilineentry.m
@@ -0,0 +1,233 @@
+// 8 december 2015
+#import "uipriv_darwin.h"
+
+// NSTextView has no intrinsic content size by default, which wreaks havoc on a pure-Auto Layout system
+// we'll have to take over to get it to work
+// see also http://stackoverflow.com/questions/24210153/nstextview-not-properly-resizing-with-auto-layout and http://stackoverflow.com/questions/11237622/using-autolayout-with-expanding-nstextviews
+@interface intrinsicSizeTextView : NSTextView {
+ uiMultilineEntry *libui_e;
+}
+- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e;
+@end
+
+struct uiMultilineEntry {
+ uiDarwinControl c;
+ NSScrollView *sv;
+ intrinsicSizeTextView *tv;
+ struct scrollViewData *d;
+ void (*onChanged)(uiMultilineEntry *, void *);
+ void *onChangedData;
+ BOOL changing;
+};
+
+@implementation intrinsicSizeTextView
+
+- (id)initWithFrame:(NSRect)r e:(uiMultilineEntry *)e
+{
+ self = [super initWithFrame:r];
+ if (self)
+ self->libui_e = e;
+ return self;
+}
+
+- (NSSize)intrinsicContentSize
+{
+ NSTextContainer *textContainer;
+ NSLayoutManager *layoutManager;
+ NSRect rect;
+
+ textContainer = [self textContainer];
+ layoutManager = [self layoutManager];
+ [layoutManager ensureLayoutForTextContainer:textContainer];
+ rect = [layoutManager usedRectForTextContainer:textContainer];
+ return rect.size;
+}
+
+- (void)didChangeText
+{
+ [super didChangeText];
+ [self invalidateIntrinsicContentSize];
+ if (!self->libui_e->changing)
+ (*(self->libui_e->onChanged))(self->libui_e, self->libui_e->onChangedData);
+}
+
+@end
+
+uiDarwinControlAllDefaultsExceptDestroy(uiMultilineEntry, sv)
+
+static void uiMultilineEntryDestroy(uiControl *c)
+{
+ uiMultilineEntry *e = uiMultilineEntry(c);
+
+ scrollViewFreeData(e->sv, e->d);
+ [e->tv release];
+ [e->sv release];
+ uiFreeControl(uiControl(e));
+}
+
+static void defaultOnChanged(uiMultilineEntry *e, void *data)
+{
+ // do nothing
+}
+
+char *uiMultilineEntryText(uiMultilineEntry *e)
+{
+ return uiDarwinNSStringToText([e->tv string]);
+}
+
+void uiMultilineEntrySetText(uiMultilineEntry *e, const char *text)
+{
+ [[e->tv textStorage] replaceCharactersInRange:NSMakeRange(0, [[e->tv string] length])
+ withString:toNSString(text)];
+ // must be called explicitly according to the documentation of shouldChangeTextInRange:replacementString:
+ e->changing = YES;
+ [e->tv didChangeText];
+ e->changing = NO;
+}
+
+// TODO scroll to end?
+void uiMultilineEntryAppend(uiMultilineEntry *e, const char *text)
+{
+ [[e->tv textStorage] replaceCharactersInRange:NSMakeRange([[e->tv string] length], 0)
+ withString:toNSString(text)];
+ e->changing = YES;
+ [e->tv didChangeText];
+ e->changing = NO;
+}
+
+void uiMultilineEntryOnChanged(uiMultilineEntry *e, void (*f)(uiMultilineEntry *e, void *data), void *data)
+{
+ e->onChanged = f;
+ e->onChangedData = data;
+}
+
+int uiMultilineEntryReadOnly(uiMultilineEntry *e)
+{
+ return [e->tv isEditable] == NO;
+}
+
+void uiMultilineEntrySetReadOnly(uiMultilineEntry *e, int readonly)
+{
+ BOOL editable;
+
+ editable = YES;
+ if (readonly)
+ editable = NO;
+ [e->tv setEditable:editable];
+}
+
+static uiMultilineEntry *finishMultilineEntry(BOOL hscroll)
+{
+ uiMultilineEntry *e;
+ NSFont *font;
+ struct scrollViewCreateParams p;
+
+ uiDarwinNewControl(uiMultilineEntry, e);
+
+ e->tv = [[intrinsicSizeTextView alloc] initWithFrame:NSZeroRect e:e];
+
+ // verified against Interface Builder for a sufficiently customized text view
+
+ // NSText properties:
+ // this is what Interface Builder sets the background color to
+ [e->tv setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:1.0]];
+ [e->tv setDrawsBackground:YES];
+ [e->tv setEditable:YES];
+ [e->tv setSelectable:YES];
+ [e->tv setFieldEditor:NO];
+ [e->tv setRichText:NO];
+ [e->tv setImportsGraphics:NO];
+ [e->tv setUsesFontPanel:NO];
+ [e->tv setRulerVisible:NO];
+ // we'll handle font last
+ // while setAlignment: has been around since 10.0, the named constant "NSTextAlignmentNatural" seems to have only been introduced in 10.11
+#define ourNSTextAlignmentNatural 4
+ [e->tv setAlignment:ourNSTextAlignmentNatural];
+ // textColor is set to nil, just keep the dfault
+ [e->tv setBaseWritingDirection:NSWritingDirectionNatural];
+ [e->tv setHorizontallyResizable:NO];
+ [e->tv setVerticallyResizable:YES];
+
+ // NSTextView properties:
+ [e->tv setAllowsDocumentBackgroundColorChange:NO];
+ [e->tv setAllowsUndo:YES];
+ // default paragraph style is nil; keep default
+ [e->tv setAllowsImageEditing:NO];
+ [e->tv setAutomaticQuoteSubstitutionEnabled:NO];
+ [e->tv setAutomaticLinkDetectionEnabled:NO];
+ [e->tv setDisplaysLinkToolTips:YES];
+ [e->tv setUsesRuler:NO];
+ [e->tv setUsesInspectorBar:NO];
+ [e->tv setSelectionGranularity:NSSelectByCharacter];
+ // there is a dedicated named insertion point color but oh well
+ [e->tv setInsertionPointColor:[NSColor controlTextColor]];
+ // typing attributes is nil; keep default (we change it below for fonts though)
+ [e->tv setSmartInsertDeleteEnabled:NO];
+ [e->tv setContinuousSpellCheckingEnabled:NO];
+ [e->tv setGrammarCheckingEnabled:NO];
+ [e->tv setUsesFindPanel:YES];
+ [e->tv setEnabledTextCheckingTypes:0];
+ [e->tv setAutomaticDashSubstitutionEnabled:NO];
+ [e->tv setAutomaticDataDetectionEnabled:NO];
+ [e->tv setAutomaticSpellingCorrectionEnabled:NO];
+ [e->tv setAutomaticTextReplacementEnabled:NO];
+ [e->tv setUsesFindBar:NO];
+ [e->tv setIncrementalSearchingEnabled:NO];
+
+ // NSTextContainer properties:
+ [[e->tv textContainer] setWidthTracksTextView:YES];
+ [[e->tv textContainer] setHeightTracksTextView:NO];
+
+ // NSLayoutManager properties:
+ [[e->tv layoutManager] setAllowsNonContiguousLayout:YES];
+
+ // now just to be safe; this will do some of the above but whatever
+ disableAutocorrect(e->tv);
+
+ // see https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextUILayer/Tasks/TextInScrollView.html
+ // notice we don't use the Auto Layout code; see scrollview.m for more details
+ [e->tv setMaxSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
+ [e->tv setVerticallyResizable:YES];
+ [e->tv setHorizontallyResizable:hscroll];
+ if (hscroll) {
+ [e->tv setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
+ [[e->tv textContainer] setWidthTracksTextView:NO];
+ } else {
+ [e->tv setAutoresizingMask:NSViewWidthSizable];
+ [[e->tv textContainer] setWidthTracksTextView:YES];
+ }
+ [[e->tv textContainer] setContainerSize:NSMakeSize(CGFLOAT_MAX, CGFLOAT_MAX)];
+
+ // don't use uiDarwinSetControlFont() directly; we have to do a little extra work to set the font
+ font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
+ [e->tv setTypingAttributes:[NSDictionary
+ dictionaryWithObject:font
+ forKey:NSFontAttributeName]];
+ // e->tv font from Interface Builder is nil, but setFont:nil throws an exception
+ // let's just set it to the standard control font anyway, just to be safe
+ [e->tv setFont:font];
+
+ memset(&p, 0, sizeof (struct scrollViewCreateParams));
+ p.DocumentView = e->tv;
+ // this is what Interface Builder sets it to
+ p.BackgroundColor = [NSColor colorWithCalibratedWhite:1.0 alpha:1.0];
+ p.DrawsBackground = YES;
+ p.Bordered = YES;
+ p.HScroll = hscroll;
+ p.VScroll = YES;
+ e->sv = mkScrollView(&p, &(e->d));
+
+ uiMultilineEntryOnChanged(e, defaultOnChanged, NULL);
+
+ return e;
+}
+
+uiMultilineEntry *uiNewMultilineEntry(void)
+{
+ return finishMultilineEntry(NO);
+}
+
+uiMultilineEntry *uiNewNonWrappingMultilineEntry(void)
+{
+ return finishMultilineEntry(YES);
+}
diff --git a/src/libui_sdl/libui/darwin/progressbar.m b/src/libui_sdl/libui/darwin/progressbar.m
new file mode 100644
index 0000000..b538228
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/progressbar.m
@@ -0,0 +1,78 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+// NSProgressIndicator has no intrinsic width by default; use the default width in Interface Builder
+#define progressIndicatorWidth 100
+
+@interface intrinsicWidthNSProgressIndicator : NSProgressIndicator
+@end
+
+@implementation intrinsicWidthNSProgressIndicator
+
+- (NSSize)intrinsicContentSize
+{
+ NSSize s;
+
+ s = [super intrinsicContentSize];
+ s.width = progressIndicatorWidth;
+ return s;
+}
+
+@end
+
+struct uiProgressBar {
+ uiDarwinControl c;
+ NSProgressIndicator *pi;
+};
+
+uiDarwinControlAllDefaults(uiProgressBar, pi)
+
+int uiProgressBarValue(uiProgressBar *p)
+{
+ if ([p->pi isIndeterminate])
+ return -1;
+ return [p->pi doubleValue];
+}
+
+void uiProgressBarSetValue(uiProgressBar *p, int value)
+{
+ if (value == -1) {
+ [p->pi setIndeterminate:YES];
+ [p->pi startAnimation:p->pi];
+ return;
+ }
+
+ if ([p->pi isIndeterminate]) {
+ [p->pi setIndeterminate:NO];
+ [p->pi stopAnimation:p->pi];
+ }
+
+ if (value < 0 || value > 100)
+ userbug("Value %d out of range for a uiProgressBar.", value);
+
+ // on 10.8 there's an animation when the progress bar increases, just like with Aero
+ if (value == 100) {
+ [p->pi setMaxValue:101];
+ [p->pi setDoubleValue:101];
+ [p->pi setDoubleValue:100];
+ [p->pi setMaxValue:100];
+ return;
+ }
+ [p->pi setDoubleValue:((double) (value + 1))];
+ [p->pi setDoubleValue:((double) value)];
+}
+
+uiProgressBar *uiNewProgressBar(void)
+{
+ uiProgressBar *p;
+
+ uiDarwinNewControl(uiProgressBar, p);
+
+ p->pi = [[intrinsicWidthNSProgressIndicator alloc] initWithFrame:NSZeroRect];
+ [p->pi setControlSize:NSRegularControlSize];
+ [p->pi setBezeled:YES];
+ [p->pi setStyle:NSProgressIndicatorBarStyle];
+ [p->pi setIndeterminate:NO];
+
+ return p;
+}
diff --git a/src/libui_sdl/libui/darwin/radiobuttons.m b/src/libui_sdl/libui/darwin/radiobuttons.m
new file mode 100644
index 0000000..25d773c
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/radiobuttons.m
@@ -0,0 +1,207 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+// TODO resizing the controlgallery vertically causes the third button to still resize :|
+
+// In the old days you would use a NSMatrix for this; as of OS X 10.8 this was deprecated and now you need just a bunch of NSButtons with the same superview AND same action method.
+// This is documented on the NSMatrix page, but the rest of the OS X documentation says to still use NSMatrix.
+// NSMatrix has weird quirks anyway...
+
+// LONGTERM 6 units of spacing between buttons, as suggested by Interface Builder?
+
+@interface radioButtonsDelegate : NSObject {
+ uiRadioButtons *libui_r;
+}
+- (id)initWithR:(uiRadioButtons *)r;
+- (IBAction)onClicked:(id)sender;
+@end
+
+struct uiRadioButtons {
+ uiDarwinControl c;
+ NSView *view;
+ NSMutableArray *buttons;
+ NSMutableArray *constraints;
+ NSLayoutConstraint *lastv;
+ radioButtonsDelegate *delegate;
+ void (*onSelected)(uiRadioButtons *, void *);
+ void *onSelectedData;
+};
+
+@implementation radioButtonsDelegate
+
+- (id)initWithR:(uiRadioButtons *)r
+{
+ self = [super init];
+ if (self)
+ self->libui_r = r;
+ return self;
+}
+
+- (IBAction)onClicked:(id)sender
+{
+ uiRadioButtons *r = self->libui_r;
+
+ (*(r->onSelected))(r, r->onSelectedData);
+}
+
+@end
+
+uiDarwinControlAllDefaultsExceptDestroy(uiRadioButtons, view)
+
+static void defaultOnSelected(uiRadioButtons *r, void *data)
+{
+ // do nothing
+}
+
+static void uiRadioButtonsDestroy(uiControl *c)
+{
+ uiRadioButtons *r = uiRadioButtons(c);
+ NSButton *b;
+
+ // drop the constraints
+ [r->view removeConstraints:r->constraints];
+ [r->constraints release];
+ if (r->lastv != nil)
+ [r->lastv release];
+ // destroy the buttons
+ for (b in r->buttons) {
+ [b setTarget:nil];
+ [b removeFromSuperview];
+ }
+ [r->buttons release];
+ // destroy the delegate
+ [r->delegate release];
+ // and destroy ourselves
+ [r->view release];
+ uiFreeControl(uiControl(r));
+}
+
+static NSButton *buttonAt(uiRadioButtons *r, int n)
+{
+ return (NSButton *) [r->buttons objectAtIndex:n];
+}
+
+void uiRadioButtonsAppend(uiRadioButtons *r, const char *text)
+{
+ NSButton *b, *b2;
+ NSLayoutConstraint *constraint;
+
+ b = [[NSButton alloc] initWithFrame:NSZeroRect];
+ [b setTitle:toNSString(text)];
+ [b setButtonType:NSRadioButton];
+ // doesn't seem to have an associated bezel style
+ [b setBordered:NO];
+ [b setTransparent:NO];
+ uiDarwinSetControlFont(b, NSRegularControlSize);
+ [b setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+ [b setTarget:r->delegate];
+ [b setAction:@selector(onClicked:)];
+
+ [r->buttons addObject:b];
+ [r->view addSubview:b];
+
+ // pin horizontally to the edges of the superview
+ constraint = mkConstraint(b, NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ r->view, NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiRadioButtons button leading constraint");
+ [r->view addConstraint:constraint];
+ [r->constraints addObject:constraint];
+ constraint = mkConstraint(b, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ r->view, NSLayoutAttributeTrailing,
+ 1, 0,
+ @"uiRadioButtons button trailing constraint");
+ [r->view addConstraint:constraint];
+ [r->constraints addObject:constraint];
+
+ // if this is the first view, pin it to the top
+ // otherwise pin to the bottom of the last
+ if ([r->buttons count] == 1)
+ constraint = mkConstraint(b, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ r->view, NSLayoutAttributeTop,
+ 1, 0,
+ @"uiRadioButtons first button top constraint");
+ else {
+ b2 = buttonAt(r, [r->buttons count] - 2);
+ constraint = mkConstraint(b, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ b2, NSLayoutAttributeBottom,
+ 1, 0,
+ @"uiRadioButtons non-first button top constraint");
+ }
+ [r->view addConstraint:constraint];
+ [r->constraints addObject:constraint];
+
+ // if there is a previous bottom constraint, remove it
+ if (r->lastv != nil) {
+ [r->view removeConstraint:r->lastv];
+ [r->constraints removeObject:r->lastv];
+ [r->lastv release];
+ }
+
+ // and make the new bottom constraint
+ r->lastv = mkConstraint(b, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ r->view, NSLayoutAttributeBottom,
+ 1, 0,
+ @"uiRadioButtons last button bottom constraint");
+ [r->view addConstraint:r->lastv];
+ [r->constraints addObject:r->lastv];
+ [r->lastv retain];
+}
+
+int uiRadioButtonsSelected(uiRadioButtons *r)
+{
+ NSButton *b;
+ NSUInteger i;
+
+ for (i = 0; i < [r->buttons count]; i++) {
+ b = (NSButton *) [r->buttons objectAtIndex:i];
+ if ([b state] == NSOnState)
+ return i;
+ }
+ return -1;
+}
+
+void uiRadioButtonsSetSelected(uiRadioButtons *r, int n)
+{
+ NSButton *b;
+ NSInteger state;
+
+ state = NSOnState;
+ if (n == -1) {
+ n = uiRadioButtonsSelected(r);
+ if (n == -1) // from nothing to nothing; do nothing
+ return;
+ state = NSOffState;
+ }
+ b = (NSButton *) [r->buttons objectAtIndex:n];
+ [b setState:state];
+}
+
+void uiRadioButtonsOnSelected(uiRadioButtons *r, void (*f)(uiRadioButtons *, void *), void *data)
+{
+ r->onSelected = f;
+ r->onSelectedData = data;
+}
+
+uiRadioButtons *uiNewRadioButtons(void)
+{
+ uiRadioButtons *r;
+
+ uiDarwinNewControl(uiRadioButtons, r);
+
+ r->view = [[NSView alloc] initWithFrame:NSZeroRect];
+ r->buttons = [NSMutableArray new];
+ r->constraints = [NSMutableArray new];
+
+ r->delegate = [[radioButtonsDelegate alloc] initWithR:r];
+
+ uiRadioButtonsOnSelected(r, defaultOnSelected, NULL);
+
+ return r;
+}
diff --git a/src/libui_sdl/libui/darwin/scrollview.m b/src/libui_sdl/libui/darwin/scrollview.m
new file mode 100644
index 0000000..b0b4040
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/scrollview.m
@@ -0,0 +1,61 @@
+// 27 may 2016
+#include "uipriv_darwin.h"
+
+// see http://stackoverflow.com/questions/37979445/how-do-i-properly-set-up-a-scrolling-nstableview-using-auto-layout-what-ive-tr for why we don't use auto layout
+// TODO do the same with uiGroup and uiTab?
+
+struct scrollViewData {
+ BOOL hscroll;
+ BOOL vscroll;
+};
+
+NSScrollView *mkScrollView(struct scrollViewCreateParams *p, struct scrollViewData **dout)
+{
+ NSScrollView *sv;
+ NSBorderType border;
+ struct scrollViewData *d;
+
+ sv = [[NSScrollView alloc] initWithFrame:NSZeroRect];
+ if (p->BackgroundColor != nil)
+ [sv setBackgroundColor:p->BackgroundColor];
+ [sv setDrawsBackground:p->DrawsBackground];
+ border = NSNoBorder;
+ if (p->Bordered)
+ border = NSBezelBorder;
+ // document view seems to set the cursor properly
+ [sv setBorderType:border];
+ [sv setAutohidesScrollers:YES];
+ [sv setHasHorizontalRuler:NO];
+ [sv setHasVerticalRuler:NO];
+ [sv setRulersVisible:NO];
+ [sv setScrollerKnobStyle:NSScrollerKnobStyleDefault];
+ // the scroller style is documented as being set by default for us
+ // LONGTERM verify line and page for programmatically created NSTableView
+ [sv setScrollsDynamically:YES];
+ [sv setFindBarPosition:NSScrollViewFindBarPositionAboveContent];
+ [sv setUsesPredominantAxisScrolling:NO];
+ [sv setHorizontalScrollElasticity:NSScrollElasticityAutomatic];
+ [sv setVerticalScrollElasticity:NSScrollElasticityAutomatic];
+ [sv setAllowsMagnification:NO];
+
+ [sv setDocumentView:p->DocumentView];
+ d = uiNew(struct scrollViewData);
+ scrollViewSetScrolling(sv, d, p->HScroll, p->VScroll);
+
+ *dout = d;
+ return sv;
+}
+
+// based on http://blog.bjhomer.com/2014/08/nsscrollview-and-autolayout.html because (as pointed out there) Apple's official guide is really only for iOS
+void scrollViewSetScrolling(NSScrollView *sv, struct scrollViewData *d, BOOL hscroll, BOOL vscroll)
+{
+ d->hscroll = hscroll;
+ [sv setHasHorizontalScroller:d->hscroll];
+ d->vscroll = vscroll;
+ [sv setHasVerticalScroller:d->vscroll];
+}
+
+void scrollViewFreeData(NSScrollView *sv, struct scrollViewData *d)
+{
+ uiFree(d);
+}
diff --git a/src/libui_sdl/libui/darwin/separator.m b/src/libui_sdl/libui/darwin/separator.m
new file mode 100644
index 0000000..a37a376
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/separator.m
@@ -0,0 +1,45 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+// TODO make this intrinsic
+#define separatorWidth 96
+#define separatorHeight 96
+
+struct uiSeparator {
+ uiDarwinControl c;
+ NSBox *box;
+};
+
+uiDarwinControlAllDefaults(uiSeparator, box)
+
+uiSeparator *uiNewHorizontalSeparator(void)
+{
+ uiSeparator *s;
+
+ uiDarwinNewControl(uiSeparator, s);
+
+ // make the initial width >= initial height to force horizontal
+ s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 100, 1)];
+ [s->box setBoxType:NSBoxSeparator];
+ [s->box setBorderType:NSGrooveBorder];
+ [s->box setTransparent:NO];
+ [s->box setTitlePosition:NSNoTitle];
+
+ return s;
+}
+
+uiSeparator *uiNewVerticalSeparator(void)
+{
+ uiSeparator *s;
+
+ uiDarwinNewControl(uiSeparator, s);
+
+ // make the initial height >= initial width to force vertical
+ s->box = [[NSBox alloc] initWithFrame:NSMakeRect(0, 0, 1, 100)];
+ [s->box setBoxType:NSBoxSeparator];
+ [s->box setBorderType:NSGrooveBorder];
+ [s->box setTransparent:NO];
+ [s->box setTitlePosition:NSNoTitle];
+
+ return s;
+}
diff --git a/src/libui_sdl/libui/darwin/slider.m b/src/libui_sdl/libui/darwin/slider.m
new file mode 100644
index 0000000..f00da50
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/slider.m
@@ -0,0 +1,147 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+// Horizontal sliders have no intrinsic width; we'll use the default Interface Builder width for them.
+// This will also be used for the initial frame size, to ensure the slider is always horizontal (see below).
+#define sliderWidth 92
+
+@interface libui_intrinsicWidthNSSlider : NSSlider
+@end
+
+@implementation libui_intrinsicWidthNSSlider
+
+- (NSSize)intrinsicContentSize
+{
+ NSSize s;
+
+ s = [super intrinsicContentSize];
+ s.width = sliderWidth;
+ return s;
+}
+
+@end
+
+struct uiSlider {
+ uiDarwinControl c;
+ NSSlider *slider;
+ void (*onChanged)(uiSlider *, void *);
+ void *onChangedData;
+};
+
+@interface sliderDelegateClass : NSObject {
+ struct mapTable *sliders;
+}
+- (IBAction)onChanged:(id)sender;
+- (void)registerSlider:(uiSlider *)b;
+- (void)unregisterSlider:(uiSlider *)b;
+@end
+
+@implementation sliderDelegateClass
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ self->sliders = newMap();
+ return self;
+}
+
+- (void)dealloc
+{
+ mapDestroy(self->sliders);
+ [super dealloc];
+}
+
+- (IBAction)onChanged:(id)sender
+{
+ uiSlider *s;
+
+ s = (uiSlider *) mapGet(self->sliders, sender);
+ (*(s->onChanged))(s, s->onChangedData);
+}
+
+- (void)registerSlider:(uiSlider *)s
+{
+ mapSet(self->sliders, s->slider, s);
+ [s->slider setTarget:self];
+ [s->slider setAction:@selector(onChanged:)];
+}
+
+- (void)unregisterSlider:(uiSlider *)s
+{
+ [s->slider setTarget:nil];
+ mapDelete(self->sliders, s->slider);
+}
+
+@end
+
+static sliderDelegateClass *sliderDelegate = nil;
+
+uiDarwinControlAllDefaultsExceptDestroy(uiSlider, slider)
+
+static void uiSliderDestroy(uiControl *c)
+{
+ uiSlider *s = uiSlider(c);
+
+ [sliderDelegate unregisterSlider:s];
+ [s->slider release];
+ uiFreeControl(uiControl(s));
+}
+
+int uiSliderValue(uiSlider *s)
+{
+ return [s->slider integerValue];
+}
+
+void uiSliderSetValue(uiSlider *s, int value)
+{
+ [s->slider setIntegerValue:value];
+}
+
+void uiSliderOnChanged(uiSlider *s, void (*f)(uiSlider *, void *), void *data)
+{
+ s->onChanged = f;
+ s->onChangedData = data;
+}
+
+static void defaultOnChanged(uiSlider *s, void *data)
+{
+ // do nothing
+}
+
+uiSlider *uiNewSlider(int min, int max)
+{
+ uiSlider *s;
+ NSSliderCell *cell;
+ int temp;
+
+ if (min >= max) {
+ temp = min;
+ min = max;
+ max = temp;
+ }
+
+ uiDarwinNewControl(uiSlider, s);
+
+ // a horizontal slider is defined as one where the width > height, not by a flag
+ // to be safe, don't use NSZeroRect, but make it horizontal from the get-go
+ s->slider = [[libui_intrinsicWidthNSSlider alloc]
+ initWithFrame:NSMakeRect(0, 0, sliderWidth, 2)];
+ [s->slider setMinValue:min];
+ [s->slider setMaxValue:max];
+ [s->slider setAllowsTickMarkValuesOnly:NO];
+ [s->slider setNumberOfTickMarks:0];
+ [s->slider setTickMarkPosition:NSTickMarkAbove];
+
+ cell = (NSSliderCell *) [s->slider cell];
+ [cell setSliderType:NSLinearSlider];
+
+ if (sliderDelegate == nil) {
+ sliderDelegate = [[sliderDelegateClass new] autorelease];
+ [delegates addObject:sliderDelegate];
+ }
+ [sliderDelegate registerSlider:s];
+ uiSliderOnChanged(s, defaultOnChanged, NULL);
+
+ return s;
+}
diff --git a/src/libui_sdl/libui/darwin/spinbox.m b/src/libui_sdl/libui/darwin/spinbox.m
new file mode 100644
index 0000000..73474d0
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/spinbox.m
@@ -0,0 +1,214 @@
+// 14 august 2015
+#import "uipriv_darwin.h"
+
+@interface libui_spinbox : NSView<NSTextFieldDelegate> {
+ NSTextField *tf;
+ NSNumberFormatter *formatter;
+ NSStepper *stepper;
+
+ NSInteger value;
+ NSInteger minimum;
+ NSInteger maximum;
+
+ uiSpinbox *spinbox;
+}
+- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb;
+// see https://github.com/andlabs/ui/issues/82
+- (NSInteger)libui_value;
+- (void)libui_setValue:(NSInteger)val;
+- (void)setMinimum:(NSInteger)min;
+- (void)setMaximum:(NSInteger)max;
+- (IBAction)stepperClicked:(id)sender;
+- (void)controlTextDidChange:(NSNotification *)note;
+@end
+
+struct uiSpinbox {
+ uiDarwinControl c;
+ libui_spinbox *spinbox;
+ void (*onChanged)(uiSpinbox *, void *);
+ void *onChangedData;
+};
+
+// yes folks, this varies by operating system! woo!
+// 10.10 started drawing the NSStepper one point too low, so we have to fix it up conditionally
+// TODO test this; we'll probably have to substitute 10_9
+static CGFloat stepperYDelta(void)
+{
+ // via https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/
+ if (floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9)
+ return 0;
+ return -1;
+}
+
+@implementation libui_spinbox
+
+- (id)initWithFrame:(NSRect)r spinbox:(uiSpinbox *)sb
+{
+ self = [super initWithFrame:r];
+ if (self) {
+ self->tf = newEditableTextField();
+ [self->tf setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+ self->formatter = [NSNumberFormatter new];
+ [self->formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
+ [self->formatter setLocalizesFormat:NO];
+ [self->formatter setUsesGroupingSeparator:NO];
+ [self->formatter setHasThousandSeparators:NO];
+ [self->formatter setAllowsFloats:NO];
+ [self->tf setFormatter:self->formatter];
+
+ self->stepper = [[NSStepper alloc] initWithFrame:NSZeroRect];
+ [self->stepper setIncrement:1];
+ [self->stepper setValueWraps:NO];
+ [self->stepper setAutorepeat:YES]; // hold mouse button to step repeatedly
+ [self->stepper setTranslatesAutoresizingMaskIntoConstraints:NO];
+
+ [self->tf setDelegate:self];
+ [self->stepper setTarget:self];
+ [self->stepper setAction:@selector(stepperClicked:)];
+
+ [self addSubview:self->tf];
+ [self addSubview:self->stepper];
+
+ [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeLeading,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeLeading,
+ 1, 0,
+ @"uiSpinbox left edge")];
+ [self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeTrailing,
+ 1, 0,
+ @"uiSpinbox right edge")];
+ [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeTop,
+ 1, 0,
+ @"uiSpinbox top edge text field")];
+ [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeBottom,
+ 1, 0,
+ @"uiSpinbox bottom edge text field")];
+ [self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeTop,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeTop,
+ 1, stepperYDelta(),
+ @"uiSpinbox top edge stepper")];
+ [self addConstraint:mkConstraint(self->stepper, NSLayoutAttributeBottom,
+ NSLayoutRelationEqual,
+ self, NSLayoutAttributeBottom,
+ 1, stepperYDelta(),
+ @"uiSpinbox bottom edge stepper")];
+ [self addConstraint:mkConstraint(self->tf, NSLayoutAttributeTrailing,
+ NSLayoutRelationEqual,
+ self->stepper, NSLayoutAttributeLeading,
+ 1, -3, // arbitrary amount; good enough visually (and it seems to match NSDatePicker too, at least on 10.11, which is even better)
+ @"uiSpinbox space between text field and stepper")];
+
+ self->spinbox = sb;
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [self->tf setDelegate:nil];
+ [self->tf removeFromSuperview];
+ [self->tf release];
+ [self->formatter release];
+ [self->stepper setTarget:nil];
+ [self->stepper removeFromSuperview];
+ [self->stepper release];
+ [super dealloc];
+}
+
+- (NSInteger)libui_value
+{
+ return self->value;
+}
+
+- (void)libui_setValue:(NSInteger)val
+{
+ self->value = val;
+ if (self->value < self->minimum)
+ self->value = self->minimum;
+ if (self->value > self->maximum)
+ self->value = self->maximum;
+ [self->tf setIntegerValue:self->value];
+ [self->stepper setIntegerValue:self->value];
+}
+
+- (void)setMinimum:(NSInteger)min
+{
+ self->minimum = min;
+ [self->formatter setMinimum:[NSNumber numberWithInteger:self->minimum]];
+ [self->stepper setMinValue:((double) (self->minimum))];
+}
+
+- (void)setMaximum:(NSInteger)max
+{
+ self->maximum = max;
+ [self->formatter setMaximum:[NSNumber numberWithInteger:self->maximum]];
+ [self->stepper setMaxValue:((double) (self->maximum))];
+}
+
+- (IBAction)stepperClicked:(id)sender
+{
+ [self libui_setValue:[self->stepper integerValue]];
+ (*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);
+}
+
+- (void)controlTextDidChange:(NSNotification *)note
+{
+ [self libui_setValue:[self->tf integerValue]];
+ (*(self->spinbox->onChanged))(self->spinbox, self->spinbox->onChangedData);
+}
+
+@end
+
+uiDarwinControlAllDefaults(uiSpinbox, spinbox)
+
+int uiSpinboxValue(uiSpinbox *s)
+{
+ return [s->spinbox libui_value];
+}
+
+void uiSpinboxSetValue(uiSpinbox *s, int value)
+{
+ [s->spinbox libui_setValue:value];
+}
+
+void uiSpinboxOnChanged(uiSpinbox *s, void (*f)(uiSpinbox *, void *), void *data)
+{
+ s->onChanged = f;
+ s->onChangedData = data;
+}
+
+static void defaultOnChanged(uiSpinbox *s, void *data)
+{
+ // do nothing
+}
+
+uiSpinbox *uiNewSpinbox(int min, int max)
+{
+ uiSpinbox *s;
+ int temp;
+
+ if (min >= max) {
+ temp = min;
+ min = max;
+ max = temp;
+ }
+
+ uiDarwinNewControl(uiSpinbox, s);
+
+ s->spinbox = [[libui_spinbox alloc] initWithFrame:NSZeroRect spinbox:s];
+ [s->spinbox setMinimum:min];
+ [s->spinbox setMaximum:max];
+ [s->spinbox libui_setValue:min];
+
+ uiSpinboxOnChanged(s, defaultOnChanged, NULL);
+
+ return s;
+}
diff --git a/src/libui_sdl/libui/darwin/stddialogs.m b/src/libui_sdl/libui/darwin/stddialogs.m
new file mode 100644
index 0000000..57ce959
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/stddialogs.m
@@ -0,0 +1,123 @@
+// 26 june 2015
+#import "uipriv_darwin.h"
+
+// LONGTERM restructure this whole file
+// LONGTERM explicitly document this works as we want
+// LONGTERM note that font and color buttons also do this
+
+#define windowWindow(w) ((NSWindow *) uiControlHandle(uiControl(w)))
+
+// source of code modal logic: http://stackoverflow.com/questions/604768/wait-for-nsalert-beginsheetmodalforwindow
+
+// note: whether extensions are actually shown depends on a user setting in Finder; we can't control it here
+static void setupSavePanel(NSSavePanel *s)
+{
+ [s setCanCreateDirectories:YES];
+ [s setShowsHiddenFiles:YES];
+ [s setExtensionHidden:NO];
+ [s setCanSelectHiddenExtension:NO];
+ [s setTreatsFilePackagesAsDirectories:YES];
+}
+
+static char *runSavePanel(NSWindow *parent, NSSavePanel *s)
+{
+ char *filename;
+
+ [s beginSheetModalForWindow:parent completionHandler:^(NSInteger result) {
+ [realNSApp() stopModalWithCode:result];
+ }];
+ if ([realNSApp() runModalForWindow:s] != NSFileHandlingPanelOKButton)
+ return NULL;
+ filename = uiDarwinNSStringToText([[s URL] path]);
+ return filename;
+}
+
+char *uiOpenFile(uiWindow *parent)
+{
+ NSOpenPanel *o;
+
+ o = [NSOpenPanel openPanel];
+ [o setCanChooseFiles:YES];
+ [o setCanChooseDirectories:NO];
+ [o setResolvesAliases:NO];
+ [o setAllowsMultipleSelection:NO];
+ setupSavePanel(o);
+ // panel is autoreleased
+ return runSavePanel(windowWindow(parent), o);
+}
+
+char *uiSaveFile(uiWindow *parent)
+{
+ NSSavePanel *s;
+
+ s = [NSSavePanel savePanel];
+ setupSavePanel(s);
+ // panel is autoreleased
+ return runSavePanel(windowWindow(parent), s);
+}
+
+// I would use a completion handler for NSAlert as well, but alas NSAlert's are 10.9 and higher only
+@interface libuiCodeModalAlertPanel : NSObject {
+ NSAlert *panel;
+ NSWindow *parent;
+}
+- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w;
+- (NSInteger)run;
+- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data;
+@end
+
+@implementation libuiCodeModalAlertPanel
+
+- (id)initWithPanel:(NSAlert *)p parent:(NSWindow *)w
+{
+ self = [super init];
+ if (self) {
+ self->panel = p;
+ self->parent = w;
+ }
+ return self;
+}
+
+- (NSInteger)run
+{
+ [self->panel beginSheetModalForWindow:self->parent
+ modalDelegate:self
+ didEndSelector:@selector(panelEnded:result:data:)
+ contextInfo:NULL];
+ return [realNSApp() runModalForWindow:[self->panel window]];
+}
+
+- (void)panelEnded:(NSAlert *)panel result:(NSInteger)result data:(void *)data
+{
+ [realNSApp() stopModalWithCode:result];
+}
+
+@end
+
+static void msgbox(NSWindow *parent, const char *title, const char *description, NSAlertStyle style)
+{
+ NSAlert *a;
+ libuiCodeModalAlertPanel *cm;
+
+ a = [NSAlert new];
+ [a setAlertStyle:style];
+ [a setShowsHelp:NO];
+ [a setShowsSuppressionButton:NO];
+ [a setMessageText:toNSString(title)];
+ [a setInformativeText:toNSString(description)];
+ [a addButtonWithTitle:@"OK"];
+ cm = [[libuiCodeModalAlertPanel alloc] initWithPanel:a parent:parent];
+ [cm run];
+ [cm release];
+ [a release];
+}
+
+void uiMsgBox(uiWindow *parent, const char *title, const char *description)
+{
+ msgbox(windowWindow(parent), title, description, NSInformationalAlertStyle);
+}
+
+void uiMsgBoxError(uiWindow *parent, const char *title, const char *description)
+{
+ msgbox(windowWindow(parent), title, description, NSCriticalAlertStyle);
+}
diff --git a/src/libui_sdl/libui/darwin/tab.m b/src/libui_sdl/libui/darwin/tab.m
new file mode 100644
index 0000000..3d2ca9f
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/tab.m
@@ -0,0 +1,292 @@
+// 15 august 2015
+#import "uipriv_darwin.h"
+
+// TODO need to jiggle on tab change too (second page disabled tab label initially ambiguous)
+
+@interface tabPage : NSObject {
+ struct singleChildConstraints constraints;
+ int margined;
+ NSView *view; // the NSTabViewItem view itself
+ NSObject *pageID;
+}
+@property uiControl *c;
+@property NSLayoutPriority oldHorzHuggingPri;
+@property NSLayoutPriority oldVertHuggingPri;
+- (id)initWithView:(NSView *)v pageID:(NSObject *)o;
+- (NSView *)childView;
+- (void)establishChildConstraints;
+- (void)removeChildConstraints;
+- (int)isMargined;
+- (void)setMargined:(int)m;
+@end
+
+struct uiTab {
+ uiDarwinControl c;
+ NSTabView *tabview;
+ NSMutableArray *pages;
+ NSLayoutPriority horzHuggingPri;
+ NSLayoutPriority vertHuggingPri;
+};
+
+@implementation tabPage
+
+- (id)initWithView:(NSView *)v pageID:(NSObject *)o
+{
+ self = [super init];
+ if (self != nil) {
+ self->view = [v retain];
+ self->pageID = [o retain];
+ }
+ return self;
+}
+
+- (void)dealloc
+{
+ [self removeChildConstraints];
+ [self->view release];
+ [self->pageID release];
+ [super dealloc];
+}
+
+- (NSView *)childView
+{
+ return (NSView *) uiControlHandle(self.c);
+}
+
+- (void)establishChildConstraints
+{
+ [self removeChildConstraints];
+ if (self.c == NULL)
+ return;
+ singleChildConstraintsEstablish(&(self->constraints),
+ self->view, [self childView],
+ uiDarwinControlHugsTrailingEdge(uiDarwinControl(self.c)),
+ uiDarwinControlHugsBottom(uiDarwinControl(self.c)),
+ self->margined,
+ @"uiTab page");
+}
+
+- (void)removeChildConstraints
+{
+ singleChildConstraintsRemove(&(self->constraints), self->view);
+}
+
+- (int)isMargined
+{
+ return self->margined;
+}
+
+- (void)setMargined:(int)m
+{
+ self->margined = m;
+ singleChildConstraintsSetMargined(&(self->constraints), self->margined);
+}
+
+@end
+
+static void uiTabDestroy(uiControl *c)
+{
+ uiTab *t = uiTab(c);
+ tabPage *page;
+
+ // first remove all tab pages so we can destroy all the children
+ while ([t->tabview numberOfTabViewItems] != 0)
+ [t->tabview removeTabViewItem:[t->tabview tabViewItemAtIndex:0]];
+ // then destroy all the children
+ for (page in t->pages) {
+ [page removeChildConstraints];
+ uiControlSetParent(page.c, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(page.c), nil);
+ uiControlDestroy(page.c);
+ }
+ // and finally destroy ourselves
+ [t->pages release];
+ [t->tabview release];
+ uiFreeControl(uiControl(t));
+}
+
+uiDarwinControlDefaultHandle(uiTab, tabview)
+uiDarwinControlDefaultParent(uiTab, tabview)
+uiDarwinControlDefaultSetParent(uiTab, tabview)
+uiDarwinControlDefaultToplevel(uiTab, tabview)
+uiDarwinControlDefaultVisible(uiTab, tabview)
+uiDarwinControlDefaultShow(uiTab, tabview)
+uiDarwinControlDefaultHide(uiTab, tabview)
+uiDarwinControlDefaultEnabled(uiTab, tabview)
+uiDarwinControlDefaultEnable(uiTab, tabview)
+uiDarwinControlDefaultDisable(uiTab, tabview)
+
+static void uiTabSyncEnableState(uiDarwinControl *c, int enabled)
+{
+ uiTab *t = uiTab(c);
+ tabPage *page;
+
+ if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(t), enabled))
+ return;
+ for (page in t->pages)
+ uiDarwinControlSyncEnableState(uiDarwinControl(page.c), enabled);
+}
+
+uiDarwinControlDefaultSetSuperview(uiTab, tabview)
+
+static void tabRelayout(uiTab *t)
+{
+ tabPage *page;
+
+ for (page in t->pages)
+ [page establishChildConstraints];
+ // and this gets rid of some weird issues with regards to box alignment
+ jiggleViewLayout(t->tabview);
+}
+
+BOOL uiTabHugsTrailingEdge(uiDarwinControl *c)
+{
+ uiTab *t = uiTab(c);
+
+ return t->horzHuggingPri < NSLayoutPriorityWindowSizeStayPut;
+}
+
+BOOL uiTabHugsBottom(uiDarwinControl *c)
+{
+ uiTab *t = uiTab(c);
+
+ return t->vertHuggingPri < NSLayoutPriorityWindowSizeStayPut;
+}
+
+static void uiTabChildEdgeHuggingChanged(uiDarwinControl *c)
+{
+ uiTab *t = uiTab(c);
+
+ tabRelayout(t);
+}
+
+static NSLayoutPriority uiTabHuggingPriority(uiDarwinControl *c, NSLayoutConstraintOrientation orientation)
+{
+ uiTab *t = uiTab(c);
+
+ if (orientation == NSLayoutConstraintOrientationHorizontal)
+ return t->horzHuggingPri;
+ return t->vertHuggingPri;
+}
+
+static void uiTabSetHuggingPriority(uiDarwinControl *c, NSLayoutPriority priority, NSLayoutConstraintOrientation orientation)
+{
+ uiTab *t = uiTab(c);
+
+ if (orientation == NSLayoutConstraintOrientationHorizontal)
+ t->horzHuggingPri = priority;
+ else
+ t->vertHuggingPri = priority;
+ uiDarwinNotifyEdgeHuggingChanged(uiDarwinControl(t));
+}
+
+static void uiTabChildVisibilityChanged(uiDarwinControl *c)
+{
+ uiTab *t = uiTab(c);
+
+ tabRelayout(t);
+}
+
+void uiTabAppend(uiTab *t, const char *name, uiControl *child)
+{
+ uiTabInsertAt(t, name, [t->pages count], child);
+}
+
+void uiTabInsertAt(uiTab *t, const char *name, int n, uiControl *child)
+{
+ tabPage *page;
+ NSView *view;
+ NSTabViewItem *i;
+ NSObject *pageID;
+
+ uiControlSetParent(child, uiControl(t));
+
+ view = [[[NSView alloc] initWithFrame:NSZeroRect] autorelease];
+ // note: if we turn off the autoresizing mask, nothing shows up
+ uiDarwinControlSetSuperview(uiDarwinControl(child), view);
+ uiDarwinControlSyncEnableState(uiDarwinControl(child), uiControlEnabledToUser(uiControl(t)));
+
+ // the documentation says these can be nil but the headers say these must not be; let's be safe and make them non-nil anyway
+ pageID = [NSObject new];
+ page = [[[tabPage alloc] initWithView:view pageID:pageID] autorelease];
+ page.c = child;
+
+ // don't hug, just in case we're a stretchy tab
+ page.oldHorzHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationHorizontal);
+ page.oldVertHuggingPri = uiDarwinControlHuggingPriority(uiDarwinControl(page.c), NSLayoutConstraintOrientationVertical);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationHorizontal);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), NSLayoutPriorityDefaultLow, NSLayoutConstraintOrientationVertical);
+
+ [t->pages insertObject:page atIndex:n];
+
+ i = [[[NSTabViewItem alloc] initWithIdentifier:pageID] autorelease];
+ [i setLabel:toNSString(name)];
+ [i setView:view];
+ [t->tabview insertTabViewItem:i atIndex:n];
+
+ tabRelayout(t);
+}
+
+void uiTabDelete(uiTab *t, int n)
+{
+ tabPage *page;
+ uiControl *child;
+ NSTabViewItem *i;
+
+ page = (tabPage *) [t->pages objectAtIndex:n];
+
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldHorzHuggingPri, NSLayoutConstraintOrientationHorizontal);
+ uiDarwinControlSetHuggingPriority(uiDarwinControl(page.c), page.oldVertHuggingPri, NSLayoutConstraintOrientationVertical);
+
+ child = page.c;
+ [page removeChildConstraints];
+ [t->pages removeObjectAtIndex:n];
+
+ uiControlSetParent(child, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(child), nil);
+
+ i = [t->tabview tabViewItemAtIndex:n];
+ [t->tabview removeTabViewItem:i];
+
+ tabRelayout(t);
+}
+
+int uiTabNumPages(uiTab *t)
+{
+ return [t->pages count];
+}
+
+int uiTabMargined(uiTab *t, int n)
+{
+ tabPage *page;
+
+ page = (tabPage *) [t->pages objectAtIndex:n];
+ return [page isMargined];
+}
+
+void uiTabSetMargined(uiTab *t, int n, int margined)
+{
+ tabPage *page;
+
+ page = (tabPage *) [t->pages objectAtIndex:n];
+ [page setMargined:margined];
+}
+
+uiTab *uiNewTab(void)
+{
+ uiTab *t;
+
+ uiDarwinNewControl(uiTab, t);
+
+ t->tabview = [[NSTabView alloc] initWithFrame:NSZeroRect];
+ // also good for NSTabView (same selector and everything)
+ uiDarwinSetControlFont((NSControl *) (t->tabview), NSRegularControlSize);
+
+ t->pages = [NSMutableArray new];
+
+ // default to low hugging to not hug edges
+ t->horzHuggingPri = NSLayoutPriorityDefaultLow;
+ t->vertHuggingPri = NSLayoutPriorityDefaultLow;
+
+ return t;
+}
diff --git a/src/libui_sdl/libui/darwin/text.m b/src/libui_sdl/libui/darwin/text.m
new file mode 100644
index 0000000..f0d3dab
--- /dev/null
+++ b/src/libui_sdl/libui/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/src/libui_sdl/libui/darwin/uipriv_darwin.h b/src/libui_sdl/libui/darwin/uipriv_darwin.h
new file mode 100644
index 0000000..6bca87b
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/uipriv_darwin.h
@@ -0,0 +1,146 @@
+// 6 january 2015
+#define MAC_OS_X_VERSION_MIN_REQUIRED MAC_OS_X_VERSION_10_8
+#define MAC_OS_X_VERSION_MAX_ALLOWED MAC_OS_X_VERSION_10_8
+#import <Cocoa/Cocoa.h>
+#import "../ui.h"
+#import "../ui_darwin.h"
+#import "../common/uipriv.h"
+
+#if __has_feature(objc_arc)
+#error Sorry, libui cannot be compiled with ARC.
+#endif
+
+#define toNSString(str) [NSString stringWithUTF8String:(str)]
+#define fromNSString(str) [(str) UTF8String]
+
+#ifndef NSAppKitVersionNumber10_9
+#define NSAppKitVersionNumber10_9 1265
+#endif
+
+/*TODO remove this*/typedef struct uiImage uiImage;
+
+// menu.m
+@interface menuManager : NSObject {
+ struct mapTable *items;
+ BOOL hasQuit;
+ BOOL hasPreferences;
+ BOOL hasAbout;
+}
+@property (strong) NSMenuItem *quitItem;
+@property (strong) NSMenuItem *preferencesItem;
+@property (strong) NSMenuItem *aboutItem;
+// NSMenuValidation is only informal
+- (BOOL)validateMenuItem:(NSMenuItem *)item;
+- (NSMenu *)makeMenubar;
+@end
+extern void finalizeMenus(void);
+extern void uninitMenus(void);
+
+// main.m
+@interface applicationClass : NSApplication
+@end
+// this is needed because NSApp is of type id, confusing clang
+#define realNSApp() ((applicationClass *) NSApp)
+@interface appDelegate : NSObject <NSApplicationDelegate>
+@property (strong) menuManager *menuManager;
+@end
+#define appDelegate() ((appDelegate *) [realNSApp() delegate])
+struct nextEventArgs {
+ NSEventMask mask;
+ NSDate *duration;
+ // LONGTERM no NSRunLoopMode?
+ NSString *mode;
+ BOOL dequeue;
+};
+extern int mainStep(struct nextEventArgs *nea, BOOL (^interceptEvent)(NSEvent *));
+
+// util.m
+extern void disableAutocorrect(NSTextView *);
+
+// entry.m
+extern void finishNewTextField(NSTextField *, BOOL);
+extern NSTextField *newEditableTextField(void);
+
+// window.m
+@interface libuiNSWindow : NSWindow
+- (void)libui_doMove:(NSEvent *)initialEvent;
+- (void)libui_doResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge;
+@end
+extern uiWindow *windowFromNSWindow(NSWindow *);
+
+// alloc.m
+extern NSMutableArray *delegates;
+extern void initAlloc(void);
+extern void uninitAlloc(void);
+
+// autolayout.m
+extern NSLayoutConstraint *mkConstraint(id view1, NSLayoutAttribute attr1, NSLayoutRelation relation, id view2, NSLayoutAttribute attr2, CGFloat multiplier, CGFloat c, NSString *desc);
+extern void jiggleViewLayout(NSView *view);
+struct singleChildConstraints {
+ NSLayoutConstraint *leadingConstraint;
+ NSLayoutConstraint *topConstraint;
+ NSLayoutConstraint *trailingConstraintGreater;
+ NSLayoutConstraint *trailingConstraintEqual;
+ NSLayoutConstraint *bottomConstraintGreater;
+ NSLayoutConstraint *bottomConstraintEqual;
+};
+extern void singleChildConstraintsEstablish(struct singleChildConstraints *c, NSView *contentView, NSView *childView, BOOL hugsTrailing, BOOL hugsBottom, int margined, NSString *desc);
+extern void singleChildConstraintsRemove(struct singleChildConstraints *c, NSView *cv);
+extern void singleChildConstraintsSetMargined(struct singleChildConstraints *c, int margined);
+
+// map.m
+extern struct mapTable *newMap(void);
+extern void mapDestroy(struct mapTable *m);
+extern void *mapGet(struct mapTable *m, void *key);
+extern void mapSet(struct mapTable *m, void *key, void *value);
+extern void mapDelete(struct mapTable *m, void *key);
+extern void mapWalk(struct mapTable *m, void (*f)(void *key, void *value));
+extern void mapReset(struct mapTable *m);
+
+// area.m
+extern int sendAreaEvents(NSEvent *);
+
+// areaevents.m
+extern BOOL fromKeycode(unsigned short keycode, uiAreaKeyEvent *ke);
+extern BOOL keycodeModifier(unsigned short keycode, uiModifiers *mod);
+
+// draw.m
+extern uiDrawContext *newContext(CGContextRef, CGFloat);
+extern void freeContext(uiDrawContext *);
+
+// drawtext.m
+extern uiDrawTextFont *mkTextFont(CTFontRef f, BOOL retain);
+extern uiDrawTextFont *mkTextFontFromNSFont(NSFont *f);
+extern void doDrawText(CGContextRef c, CGFloat cheight, double x, double y, uiDrawTextLayout *layout);
+
+// fontbutton.m
+extern BOOL fontButtonInhibitSendAction(SEL sel, id from, id to);
+extern BOOL fontButtonOverrideTargetForAction(SEL sel, id from, id to, id *override);
+extern void setupFontPanel(void);
+
+// colorbutton.m
+extern BOOL colorButtonInhibitSendAction(SEL sel, id from, id to);
+
+// scrollview.m
+struct scrollViewCreateParams {
+ NSView *DocumentView;
+ NSColor *BackgroundColor;
+ BOOL DrawsBackground;
+ BOOL Bordered;
+ BOOL HScroll;
+ BOOL VScroll;
+};
+struct scrollViewData;
+extern NSScrollView *mkScrollView(struct scrollViewCreateParams *p, struct scrollViewData **dout);
+extern void scrollViewSetScrolling(NSScrollView *sv, struct scrollViewData *d, BOOL hscroll, BOOL vscroll);
+extern void scrollViewFreeData(NSScrollView *sv, struct scrollViewData *d);
+
+// label.m
+extern NSTextField *newLabel(NSString *str);
+
+// image.m
+extern NSImage *imageImage(uiImage *);
+
+// winmoveresize.m
+extern void doManualMove(NSWindow *w, NSEvent *initialEvent);
+extern void doManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge);
diff --git a/src/libui_sdl/libui/darwin/util.m b/src/libui_sdl/libui/darwin/util.m
new file mode 100644
index 0000000..ab87390
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/util.m
@@ -0,0 +1,15 @@
+// 7 april 2015
+#import "uipriv_darwin.h"
+
+// LONGTERM do we really want to do this? make it an option?
+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/src/libui_sdl/libui/darwin/window.m b/src/libui_sdl/libui/darwin/window.m
new file mode 100644
index 0000000..97c22e6
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/window.m
@@ -0,0 +1,407 @@
+// 15 august 2015
+#import "uipriv_darwin.h"
+
+#define defaultStyleMask (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
+
+struct uiWindow {
+ uiDarwinControl c;
+ NSWindow *window;
+ uiControl *child;
+ int margined;
+ int (*onClosing)(uiWindow *, void *);
+ void *onClosingData;
+ struct singleChildConstraints constraints;
+ void (*onContentSizeChanged)(uiWindow *, void *);
+ void *onContentSizeChangedData;
+ BOOL suppressSizeChanged;
+ int fullscreen;
+ int borderless;
+};
+
+@implementation libuiNSWindow
+
+- (void)libui_doMove:(NSEvent *)initialEvent
+{
+ doManualMove(self, initialEvent);
+}
+
+- (void)libui_doResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge
+{
+ doManualResize(self, initialEvent, edge);
+}
+
+@end
+
+@interface windowDelegateClass : NSObject<NSWindowDelegate> {
+ struct mapTable *windows;
+}
+- (BOOL)windowShouldClose:(id)sender;
+- (void)windowDidResize:(NSNotification *)note;
+- (void)windowDidEnterFullScreen:(NSNotification *)note;
+- (void)windowDidExitFullScreen:(NSNotification *)note;
+- (void)registerWindow:(uiWindow *)w;
+- (void)unregisterWindow:(uiWindow *)w;
+- (uiWindow *)lookupWindow:(NSWindow *)w;
+@end
+
+@implementation windowDelegateClass
+
+- (id)init
+{
+ self = [super init];
+ if (self)
+ self->windows = newMap();
+ return self;
+}
+
+- (void)dealloc
+{
+ mapDestroy(self->windows);
+ [super dealloc];
+}
+
+- (BOOL)windowShouldClose:(id)sender
+{
+ uiWindow *w;
+
+ w = [self lookupWindow:((NSWindow *) sender)];
+ // w should not be NULL; we are only the delegate of registered windows
+ if ((*(w->onClosing))(w, w->onClosingData))
+ uiControlDestroy(uiControl(w));
+ return NO;
+}
+
+- (void)windowDidResize:(NSNotification *)note
+{
+ uiWindow *w;
+
+ w = [self lookupWindow:((NSWindow *) [note object])];
+ if (!w->suppressSizeChanged)
+ (*(w->onContentSizeChanged))(w, w->onContentSizeChangedData);
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification *)note
+{
+ uiWindow *w;
+
+ w = [self lookupWindow:((NSWindow *) [note object])];
+ if (!w->suppressSizeChanged)
+ w->fullscreen = 1;
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)note
+{
+ uiWindow *w;
+
+ w = [self lookupWindow:((NSWindow *) [note object])];
+ if (!w->suppressSizeChanged)
+ w->fullscreen = 0;
+}
+
+- (void)registerWindow:(uiWindow *)w
+{
+ mapSet(self->windows, w->window, w);
+ [w->window setDelegate:self];
+}
+
+- (void)unregisterWindow:(uiWindow *)w
+{
+ [w->window setDelegate:nil];
+ mapDelete(self->windows, w->window);
+}
+
+- (uiWindow *)lookupWindow:(NSWindow *)w
+{
+ uiWindow *v;
+
+ v = uiWindow(mapGet(self->windows, w));
+ // this CAN (and IS ALLOWED TO) return NULL, just in case we're called with some OS X-provided window as the key window
+ return v;
+}
+
+@end
+
+static windowDelegateClass *windowDelegate = nil;
+
+static void removeConstraints(uiWindow *w)
+{
+ NSView *cv;
+
+ cv = [w->window contentView];
+ singleChildConstraintsRemove(&(w->constraints), cv);
+}
+
+static void uiWindowDestroy(uiControl *c)
+{
+ uiWindow *w = uiWindow(c);
+
+ // hide the window
+ [w->window orderOut:w->window];
+ removeConstraints(w);
+ if (w->child != NULL) {
+ uiControlSetParent(w->child, NULL);
+ uiDarwinControlSetSuperview(uiDarwinControl(w->child), nil);
+ uiControlDestroy(w->child);
+ }
+ [windowDelegate unregisterWindow:w];
+ [w->window release];
+ uiFreeControl(uiControl(w));
+}
+
+uiDarwinControlDefaultHandle(uiWindow, window)
+
+uiControl *uiWindowParent(uiControl *c)
+{
+ return NULL;
+}
+
+void uiWindowSetParent(uiControl *c, uiControl *parent)
+{
+ uiUserBugCannotSetParentOnToplevel("uiWindow");
+}
+
+static int uiWindowToplevel(uiControl *c)
+{
+ return 1;
+}
+
+static int uiWindowVisible(uiControl *c)
+{
+ uiWindow *w = uiWindow(c);
+
+ return [w->window isVisible];
+}
+
+static void uiWindowShow(uiControl *c)
+{
+ uiWindow *w = (uiWindow *) c;
+
+ [w->window makeKeyAndOrderFront:w->window];
+}
+
+static void uiWindowHide(uiControl *c)
+{
+ uiWindow *w = (uiWindow *) c;
+
+ [w->window orderOut:w->window];
+}
+
+uiDarwinControlDefaultEnabled(uiWindow, window)
+uiDarwinControlDefaultEnable(uiWindow, window)
+uiDarwinControlDefaultDisable(uiWindow, window)
+
+static void uiWindowSyncEnableState(uiDarwinControl *c, int enabled)
+{
+ uiWindow *w = uiWindow(c);
+
+ if (uiDarwinShouldStopSyncEnableState(uiDarwinControl(w), enabled))
+ return;
+ if (w->child != NULL)
+ uiDarwinControlSyncEnableState(uiDarwinControl(w->child), enabled);
+}
+
+static void uiWindowSetSuperview(uiDarwinControl *c, NSView *superview)
+{
+ // TODO
+}
+
+static void windowRelayout(uiWindow *w)
+{
+ NSView *childView;
+ NSView *contentView;
+
+ removeConstraints(w);
+ if (w->child == NULL)
+ return;
+ childView = (NSView *) uiControlHandle(w->child);
+ contentView = [w->window contentView];
+ singleChildConstraintsEstablish(&(w->constraints),
+ contentView, childView,
+ uiDarwinControlHugsTrailingEdge(uiDarwinControl(w->child)),
+ uiDarwinControlHugsBottom(uiDarwinControl(w->child)),
+ w->margined,
+ @"uiWindow");
+}
+
+uiDarwinControlDefaultHugsTrailingEdge(uiWindow, window)
+uiDarwinControlDefaultHugsBottom(uiWindow, window)
+
+static void uiWindowChildEdgeHuggingChanged(uiDarwinControl *c)
+{
+ uiWindow *w = uiWindow(c);
+
+ windowRelayout(w);
+}
+
+// TODO
+uiDarwinControlDefaultHuggingPriority(uiWindow, window)
+uiDarwinControlDefaultSetHuggingPriority(uiWindow, window)
+// end TODO
+
+static void uiWindowChildVisibilityChanged(uiDarwinControl *c)
+{
+ uiWindow *w = uiWindow(c);
+
+ windowRelayout(w);
+}
+
+char *uiWindowTitle(uiWindow *w)
+{
+ return uiDarwinNSStringToText([w->window title]);
+}
+
+void uiWindowSetTitle(uiWindow *w, const char *title)
+{
+ [w->window setTitle:toNSString(title)];
+}
+
+void uiWindowContentSize(uiWindow *w, int *width, int *height)
+{
+ NSRect r;
+
+ r = [w->window contentRectForFrameRect:[w->window frame]];
+ *width = r.size.width;
+ *height = r.size.height;
+}
+
+void uiWindowSetContentSize(uiWindow *w, int width, int height)
+{
+ w->suppressSizeChanged = YES;
+ [w->window setContentSize:NSMakeSize(width, height)];
+ w->suppressSizeChanged = NO;
+}
+
+int uiWindowFullscreen(uiWindow *w)
+{
+ return w->fullscreen;
+}
+
+void uiWindowSetFullscreen(uiWindow *w, int fullscreen)
+{
+ if (w->fullscreen && fullscreen)
+ return;
+ if (!w->fullscreen && !fullscreen)
+ return;
+ w->fullscreen = fullscreen;
+ if (w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; don't toggle while borderless
+ return;
+ w->suppressSizeChanged = YES;
+ [w->window toggleFullScreen:w->window];
+ w->suppressSizeChanged = NO;
+ if (!w->fullscreen && w->borderless) // borderless doesn't play nice with fullscreen; restore borderless after removing
+ [w->window setStyleMask:NSBorderlessWindowMask];
+}
+
+void uiWindowOnContentSizeChanged(uiWindow *w, void (*f)(uiWindow *, void *), void *data)
+{
+ w->onContentSizeChanged = f;
+ w->onContentSizeChangedData = data;
+}
+
+void uiWindowOnClosing(uiWindow *w, int (*f)(uiWindow *, void *), void *data)
+{
+ w->onClosing = f;
+ w->onClosingData = data;
+}
+
+int uiWindowBorderless(uiWindow *w)
+{
+ return w->borderless;
+}
+
+void uiWindowSetBorderless(uiWindow *w, int borderless)
+{
+ w->borderless = borderless;
+ if (w->borderless) {
+ // borderless doesn't play nice with fullscreen; wait for later
+ if (!w->fullscreen)
+ [w->window setStyleMask:NSBorderlessWindowMask];
+ } else {
+ [w->window setStyleMask:defaultStyleMask];
+ // borderless doesn't play nice with fullscreen; restore state
+ if (w->fullscreen) {
+ w->suppressSizeChanged = YES;
+ [w->window toggleFullScreen:w->window];
+ w->suppressSizeChanged = NO;
+ }
+ }
+}
+
+void uiWindowSetChild(uiWindow *w, uiControl *child)
+{
+ NSView *childView;
+
+ if (w->child != NULL) {
+ childView = (NSView *) uiControlHandle(w->child);
+ [childView removeFromSuperview];
+ uiControlSetParent(w->child, NULL);
+ }
+ w->child = child;
+ if (w->child != NULL) {
+ uiControlSetParent(w->child, uiControl(w));
+ childView = (NSView *) uiControlHandle(w->child);
+ uiDarwinControlSetSuperview(uiDarwinControl(w->child), [w->window contentView]);
+ uiDarwinControlSyncEnableState(uiDarwinControl(w->child), uiControlEnabledToUser(uiControl(w)));
+ }
+ windowRelayout(w);
+}
+
+int uiWindowMargined(uiWindow *w)
+{
+ return w->margined;
+}
+
+void uiWindowSetMargined(uiWindow *w, int margined)
+{
+ w->margined = margined;
+ singleChildConstraintsSetMargined(&(w->constraints), w->margined);
+}
+
+static int defaultOnClosing(uiWindow *w, void *data)
+{
+ return 0;
+}
+
+static void defaultOnPositionContentSizeChanged(uiWindow *w, void *data)
+{
+ // do nothing
+}
+
+uiWindow *uiNewWindow(const char *title, int width, int height, int hasMenubar)
+{
+ uiWindow *w;
+
+ finalizeMenus();
+
+ uiDarwinNewControl(uiWindow, w);
+
+ w->window = [[libuiNSWindow alloc] initWithContentRect:NSMakeRect(0, 0, (CGFloat) width, (CGFloat) height)
+ styleMask:defaultStyleMask
+ backing:NSBackingStoreBuffered
+ defer:YES];
+ [w->window setTitle:toNSString(title)];
+
+ // do NOT release when closed
+ // we manually do this in uiWindowDestroy() above
+ [w->window setReleasedWhenClosed:NO];
+
+ if (windowDelegate == nil) {
+ windowDelegate = [[windowDelegateClass new] autorelease];
+ [delegates addObject:windowDelegate];
+ }
+ [windowDelegate registerWindow:w];
+ uiWindowOnClosing(w, defaultOnClosing, NULL);
+ uiWindowOnContentSizeChanged(w, defaultOnPositionContentSizeChanged, NULL);
+
+ return w;
+}
+
+// utility function for menus
+uiWindow *windowFromNSWindow(NSWindow *w)
+{
+ if (w == nil)
+ return NULL;
+ if (windowDelegate == nil) // no windows were created yet; we're called with some OS X-provided window
+ return NULL;
+ return [windowDelegate lookupWindow:w];
+}
diff --git a/src/libui_sdl/libui/darwin/winmoveresize.m b/src/libui_sdl/libui/darwin/winmoveresize.m
new file mode 100644
index 0000000..9145b7b
--- /dev/null
+++ b/src/libui_sdl/libui/darwin/winmoveresize.m
@@ -0,0 +1,253 @@
+// 1 november 2016
+#import "uipriv_darwin.h"
+
+// because we are changing the window frame each time the mouse moves, the successive -[NSEvent locationInWindow]s cannot be meaningfully used together
+// make sure they are all following some sort of standard to avoid this problem; the screen is the most obvious possibility since it requires only one conversion (the only one that a NSWindow provides)
+static NSPoint makeIndependent(NSPoint p, NSWindow *w)
+{
+ NSRect r;
+
+ r.origin = p;
+ // mikeash in irc.freenode.net/#macdev confirms both that any size will do and that we can safely ignore the resultant size
+ r.size = NSZeroSize;
+ return [w convertRectToScreen:r].origin;
+}
+
+struct onMoveDragParams {
+ NSWindow *w;
+ // using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
+ // TODO will this make things like the menubar and dock easier too?
+ NSRect initialFrame;
+ NSPoint initialPoint;
+};
+
+void onMoveDrag(struct onMoveDragParams *p, NSEvent *e)
+{
+ NSPoint new;
+ NSRect frame;
+ CGFloat offx, offy;
+
+ new = makeIndependent([e locationInWindow], p->w);
+ frame = p->initialFrame;
+
+ offx = new.x - p->initialPoint.x;
+ offy = new.y - p->initialPoint.y;
+ frame.origin.x += offx;
+ frame.origin.y += offy;
+
+ // TODO handle the menubar
+ // TODO wait the system does this for us already?!
+
+ [p->w setFrameOrigin:frame.origin];
+}
+
+void doManualMove(NSWindow *w, NSEvent *initialEvent)
+{
+ __block struct onMoveDragParams mdp;
+ struct nextEventArgs nea;
+ BOOL (^handleEvent)(NSEvent *e);
+ __block BOOL done;
+
+ // this is only available on 10.11 and newer (LONGTERM FUTURE)
+ // but use it if available; this lets us use the real OS dragging code, which means we can take advantage of OS features like Spaces
+ if ([w respondsToSelector:@selector(performWindowDragWithEvent:)]) {
+ [((id) w) performWindowDragWithEvent:initialEvent];
+ return;
+ }
+
+ mdp.w = w;
+ mdp.initialFrame = [mdp.w frame];
+ mdp.initialPoint = makeIndependent([initialEvent locationInWindow], mdp.w);
+
+ nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
+ nea.duration = [NSDate distantFuture];
+ nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking
+ nea.dequeue = YES;
+ handleEvent = ^(NSEvent *e) {
+ if ([e type] == NSLeftMouseUp) {
+ done = YES;
+ return YES; // do not send
+ }
+ onMoveDrag(&mdp, e);
+ return YES; // do not send
+ };
+ done = NO;
+ while (mainStep(&nea, handleEvent))
+ if (done)
+ break;
+}
+
+// see http://stackoverflow.com/a/40352996/3408572
+static void minMaxAutoLayoutSizes(NSWindow *w, NSSize *min, NSSize *max)
+{
+ NSLayoutConstraint *cw, *ch;
+ NSView *contentView;
+ NSRect prevFrame;
+
+ // if adding these constraints causes the window to change size somehow, don't show it to the user and change it back afterwards
+ NSDisableScreenUpdates();
+ prevFrame = [w frame];
+
+ // minimum: encourage the window to be as small as possible
+ contentView = [w contentView];
+ cw = mkConstraint(contentView, NSLayoutAttributeWidth,
+ NSLayoutRelationEqual,
+ nil, NSLayoutAttributeNotAnAttribute,
+ 0, 0,
+ @"window minimum width finding constraint");
+ [cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
+ [contentView addConstraint:cw];
+ ch = mkConstraint(contentView, NSLayoutAttributeHeight,
+ NSLayoutRelationEqual,
+ nil, NSLayoutAttributeNotAnAttribute,
+ 0, 0,
+ @"window minimum height finding constraint");
+ [ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
+ [contentView addConstraint:ch];
+ *min = [contentView fittingSize];
+ [contentView removeConstraint:cw];
+ [contentView removeConstraint:ch];
+
+ // maximum: encourage the window to be as large as possible
+ contentView = [w contentView];
+ cw = mkConstraint(contentView, NSLayoutAttributeWidth,
+ NSLayoutRelationEqual,
+ nil, NSLayoutAttributeNotAnAttribute,
+ 0, CGFLOAT_MAX,
+ @"window maximum width finding constraint");
+ [cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
+ [contentView addConstraint:cw];
+ ch = mkConstraint(contentView, NSLayoutAttributeHeight,
+ NSLayoutRelationEqual,
+ nil, NSLayoutAttributeNotAnAttribute,
+ 0, CGFLOAT_MAX,
+ @"window maximum height finding constraint");
+ [ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
+ [contentView addConstraint:ch];
+ *max = [contentView fittingSize];
+ [contentView removeConstraint:cw];
+ [contentView removeConstraint:ch];
+
+ [w setFrame:prevFrame display:YES]; // TODO really YES?
+ NSEnableScreenUpdates();
+}
+
+static void handleResizeLeft(NSRect *frame, NSPoint old, NSPoint new)
+{
+ frame->origin.x += new.x - old.x;
+ frame->size.width -= new.x - old.x;
+}
+
+// TODO properly handle the menubar
+// TODO wait, OS X does it for us?!
+static void handleResizeTop(NSRect *frame, NSPoint old, NSPoint new)
+{
+ frame->size.height += new.y - old.y;
+}
+
+static void handleResizeRight(NSRect *frame, NSPoint old, NSPoint new)
+{
+ frame->size.width += new.x - old.x;
+}
+
+
+// TODO properly handle the menubar
+static void handleResizeBottom(NSRect *frame, NSPoint old, NSPoint new)
+{
+ frame->origin.y += new.y - old.y;
+ frame->size.height -= new.y - old.y;
+}
+
+struct onResizeDragParams {
+ NSWindow *w;
+ // using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
+ // TODO will this make things like the menubar and dock easier too?
+ NSRect initialFrame;
+ NSPoint initialPoint;
+ uiWindowResizeEdge edge;
+ NSSize min;
+ NSSize max;
+};
+
+static void onResizeDrag(struct onResizeDragParams *p, NSEvent *e)
+{
+ NSPoint new;
+ NSRect frame;
+
+ new = makeIndependent([e locationInWindow], p->w);
+ frame = p->initialFrame;
+
+ // horizontal
+ switch (p->edge) {
+ case uiWindowResizeEdgeLeft:
+ case uiWindowResizeEdgeTopLeft:
+ case uiWindowResizeEdgeBottomLeft:
+ handleResizeLeft(&frame, p->initialPoint, new);
+ break;
+ case uiWindowResizeEdgeRight:
+ case uiWindowResizeEdgeTopRight:
+ case uiWindowResizeEdgeBottomRight:
+ handleResizeRight(&frame, p->initialPoint, new);
+ break;
+ }
+ // vertical
+ switch (p->edge) {
+ case uiWindowResizeEdgeTop:
+ case uiWindowResizeEdgeTopLeft:
+ case uiWindowResizeEdgeTopRight:
+ handleResizeTop(&frame, p->initialPoint, new);
+ break;
+ case uiWindowResizeEdgeBottom:
+ case uiWindowResizeEdgeBottomLeft:
+ case uiWindowResizeEdgeBottomRight:
+ handleResizeBottom(&frame, p->initialPoint, new);
+ break;
+ }
+
+ // constrain
+ // TODO should we constrain against anything else as well? minMaxAutoLayoutSizes() already gives us nonnegative sizes, but...
+ if (frame.size.width < p->min.width)
+ frame.size.width = p->min.width;
+ if (frame.size.height < p->min.height)
+ frame.size.height = p->min.height;
+ // TODO > or >= ?
+ if (frame.size.width > p->max.width)
+ frame.size.width = p->max.width;
+ if (frame.size.height > p->max.height)
+ frame.size.height = p->max.height;
+
+ [p->w setFrame:frame display:YES]; // and do reflect the new frame immediately
+}
+
+// TODO do our events get fired with this? *should* they?
+void doManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge)
+{
+ __block struct onResizeDragParams rdp;
+ struct nextEventArgs nea;
+ BOOL (^handleEvent)(NSEvent *e);
+ __block BOOL done;
+
+ rdp.w = w;
+ rdp.initialFrame = [rdp.w frame];
+ rdp.initialPoint = makeIndependent([initialEvent locationInWindow], rdp.w);
+ rdp.edge = edge;
+ // TODO what happens if these change during the loop?
+ minMaxAutoLayoutSizes(rdp.w, &(rdp.min), &(rdp.max));
+
+ nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
+ nea.duration = [NSDate distantFuture];
+ nea.mode = NSEventTrackingRunLoopMode; // nextEventMatchingMask: docs suggest using this for manual mouse tracking
+ nea.dequeue = YES;
+ handleEvent = ^(NSEvent *e) {
+ if ([e type] == NSLeftMouseUp) {
+ done = YES;
+ return YES; // do not send
+ }
+ onResizeDrag(&rdp, e);
+ return YES; // do not send
+ };
+ done = NO;
+ while (mainStep(&nea, handleEvent))
+ if (done)
+ break;
+}