mirror of
https://github.com/melonDS-emu/melonDS.git
synced 2025-07-31 10:09:46 -06:00
another UI attempt, I guess.
sorry.
This commit is contained in:
79
src/libui_sdl/libui/darwin/CMakeLists.txt
Normal file
79
src/libui_sdl/libui/darwin/CMakeLists.txt
Normal 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)
|
89
src/libui_sdl/libui/darwin/alloc.m
Normal file
89
src/libui_sdl/libui/darwin/alloc.m
Normal 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]];
|
||||
}
|
475
src/libui_sdl/libui/darwin/area.m
Normal file
475
src/libui_sdl/libui/darwin/area.m
Normal 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;
|
||||
}
|
159
src/libui_sdl/libui/darwin/areaevents.m
Normal file
159
src/libui_sdl/libui/darwin/areaevents.m
Normal 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;
|
||||
}
|
161
src/libui_sdl/libui/darwin/autolayout.m
Normal file
161
src/libui_sdl/libui/darwin/autolayout.m
Normal 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];
|
||||
}
|
469
src/libui_sdl/libui/darwin/box.m
Normal file
469
src/libui_sdl/libui/darwin/box.m
Normal 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);
|
||||
}
|
113
src/libui_sdl/libui/darwin/button.m
Normal file
113
src/libui_sdl/libui/darwin/button.m
Normal 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;
|
||||
}
|
129
src/libui_sdl/libui/darwin/checkbox.m
Normal file
129
src/libui_sdl/libui/darwin/checkbox.m
Normal 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;
|
||||
}
|
159
src/libui_sdl/libui/darwin/colorbutton.m
Normal file
159
src/libui_sdl/libui/darwin/colorbutton.m
Normal 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;
|
||||
}
|
145
src/libui_sdl/libui/darwin/combobox.m
Normal file
145
src/libui_sdl/libui/darwin/combobox.m
Normal 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;
|
||||
}
|
84
src/libui_sdl/libui/darwin/control.m
Normal file
84
src/libui_sdl/libui/darwin/control.m
Normal 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));
|
||||
}
|
42
src/libui_sdl/libui/darwin/datetimepicker.m
Normal file
42
src/libui_sdl/libui/darwin/datetimepicker.m
Normal 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);
|
||||
}
|
19
src/libui_sdl/libui/darwin/debug.m
Normal file
19
src/libui_sdl/libui/darwin/debug.m
Normal 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();
|
||||
}
|
454
src/libui_sdl/libui/darwin/draw.m
Normal file
454
src/libui_sdl/libui/darwin/draw.m
Normal 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);
|
||||
}
|
655
src/libui_sdl/libui/darwin/drawtext.m
Normal file
655
src/libui_sdl/libui/darwin/drawtext.m
Normal 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?
|
||||
}
|
185
src/libui_sdl/libui/darwin/editablecombo.m
Normal file
185
src/libui_sdl/libui/darwin/editablecombo.m
Normal 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;
|
||||
}
|
251
src/libui_sdl/libui/darwin/entry.m
Normal file
251
src/libui_sdl/libui/darwin/entry.m
Normal 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;
|
||||
}
|
218
src/libui_sdl/libui/darwin/fontbutton.m
Normal file
218
src/libui_sdl/libui/darwin/fontbutton.m
Normal 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;
|
||||
}
|
561
src/libui_sdl/libui/darwin/form.m
Normal file
561
src/libui_sdl/libui/darwin/form.m
Normal 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;
|
||||
}
|
800
src/libui_sdl/libui/darwin/grid.m
Normal file
800
src/libui_sdl/libui/darwin/grid.m
Normal 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;
|
||||
}
|
194
src/libui_sdl/libui/darwin/group.m
Normal file
194
src/libui_sdl/libui/darwin/group.m
Normal 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;
|
||||
}
|
82
src/libui_sdl/libui/darwin/image.m
Normal file
82
src/libui_sdl/libui/darwin/image.m
Normal 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;
|
||||
}
|
43
src/libui_sdl/libui/darwin/label.m
Normal file
43
src/libui_sdl/libui/darwin/label.m
Normal 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;
|
||||
}
|
239
src/libui_sdl/libui/darwin/main.m
Normal file
239
src/libui_sdl/libui/darwin/main.m
Normal 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);
|
||||
}
|
59
src/libui_sdl/libui/darwin/map.m
Normal file
59
src/libui_sdl/libui/darwin/map.m
Normal 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);
|
||||
}
|
368
src/libui_sdl/libui/darwin/menu.m
Normal file
368
src/libui_sdl/libui/darwin/menu.m
Normal 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];
|
||||
}
|
233
src/libui_sdl/libui/darwin/multilineentry.m
Normal file
233
src/libui_sdl/libui/darwin/multilineentry.m
Normal 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);
|
||||
}
|
78
src/libui_sdl/libui/darwin/progressbar.m
Normal file
78
src/libui_sdl/libui/darwin/progressbar.m
Normal 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;
|
||||
}
|
207
src/libui_sdl/libui/darwin/radiobuttons.m
Normal file
207
src/libui_sdl/libui/darwin/radiobuttons.m
Normal 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;
|
||||
}
|
61
src/libui_sdl/libui/darwin/scrollview.m
Normal file
61
src/libui_sdl/libui/darwin/scrollview.m
Normal 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);
|
||||
}
|
45
src/libui_sdl/libui/darwin/separator.m
Normal file
45
src/libui_sdl/libui/darwin/separator.m
Normal 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;
|
||||
}
|
147
src/libui_sdl/libui/darwin/slider.m
Normal file
147
src/libui_sdl/libui/darwin/slider.m
Normal 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;
|
||||
}
|
214
src/libui_sdl/libui/darwin/spinbox.m
Normal file
214
src/libui_sdl/libui/darwin/spinbox.m
Normal 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;
|
||||
}
|
123
src/libui_sdl/libui/darwin/stddialogs.m
Normal file
123
src/libui_sdl/libui/darwin/stddialogs.m
Normal 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);
|
||||
}
|
292
src/libui_sdl/libui/darwin/tab.m
Normal file
292
src/libui_sdl/libui/darwin/tab.m
Normal 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;
|
||||
}
|
19
src/libui_sdl/libui/darwin/text.m
Normal file
19
src/libui_sdl/libui/darwin/text.m
Normal 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);
|
||||
}
|
146
src/libui_sdl/libui/darwin/uipriv_darwin.h
Normal file
146
src/libui_sdl/libui/darwin/uipriv_darwin.h
Normal 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);
|
15
src/libui_sdl/libui/darwin/util.m
Normal file
15
src/libui_sdl/libui/darwin/util.m
Normal 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];
|
||||
}
|
407
src/libui_sdl/libui/darwin/window.m
Normal file
407
src/libui_sdl/libui/darwin/window.m
Normal 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];
|
||||
}
|
253
src/libui_sdl/libui/darwin/winmoveresize.m
Normal file
253
src/libui_sdl/libui/darwin/winmoveresize.m
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user