another UI attempt, I guess.

sorry.
This commit is contained in:
StapleButter
2017-09-09 02:30:51 +02:00
parent 81747d6c34
commit 70e4841d31
244 changed files with 35965 additions and 0 deletions

View File

@ -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)

View File

@ -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]];
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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];
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}

View File

@ -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?
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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];
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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];
}

View File

@ -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];
}

View File

@ -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;
}