1153 lines
30 KiB
Objective-C
1153 lines
30 KiB
Objective-C
#include <Mw/Milsko.h>
|
|
#include "../../external/stb_ds.h"
|
|
#include "Mw/LowLevel/Cocoa.h"
|
|
|
|
#ifdef __clang__
|
|
#pragma clang push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
#endif
|
|
|
|
static NSRect rectFlip(NSRect originFrame) {
|
|
NSScreen* zeroScreen = [[NSScreen screens] objectAtIndex:0];
|
|
double screenHeight = [zeroScreen frame].size.height;
|
|
double originY = originFrame.origin.y;
|
|
double frameHeight = originFrame.size.height;
|
|
double destinationY = screenHeight - (originY + frameHeight);
|
|
NSRect destinationFrame = originFrame;
|
|
destinationFrame.origin.y = destinationY;
|
|
return destinationFrame;
|
|
}
|
|
|
|
static NSPoint pointFlip(NSPoint point) {
|
|
return rectFlip(NSMakeRect(point.x, point.y, 0, 0)).origin;
|
|
}
|
|
|
|
static NSRect localRectFlip(NSRect originFrame, NSView* view) {
|
|
float viewHeight = [view bounds].size.height;
|
|
NSRect destinationFrame =
|
|
NSMakeRect(originFrame.origin.x,
|
|
[view bounds].size.height -
|
|
(originFrame.origin.y + originFrame.size.height),
|
|
originFrame.size.width, originFrame.size.height);
|
|
return destinationFrame;
|
|
}
|
|
static NSPoint localPointFlip(NSPoint point, NSView* view) {
|
|
return localRectFlip(NSMakeRect(point.x, point.y, 0, 0), view).origin;
|
|
}
|
|
|
|
/* Recursively dispatch a key event to a widget and its children */
|
|
static void recursive_dispatch_key(MwLL handle, int* k) {
|
|
MwWidget h = (MwWidget)handle->common.user;
|
|
MwLLDispatch(handle, key, k);
|
|
if(h) {
|
|
int i;
|
|
for(i = 0; i < arrlen(h->children); i++) {
|
|
MwLLDispatch(h->children[i]->lowlevel, key, k);
|
|
if(arrlen(h->children[i]->children) > 0) {
|
|
recursive_dispatch_key(h->children[i]->lowlevel, k);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/* Recursively dispatch a key released event to a widget and its children */
|
|
static void recursive_dispatch_key_released(MwLL handle, int* k) {
|
|
MwWidget h = (MwWidget)handle->common.user;
|
|
MwLLDispatch(handle, key_released, k);
|
|
if(h) {
|
|
int i;
|
|
for(i = 0; i < arrlen(h->children); i++) {
|
|
MwLLDispatch(h->children[i]->lowlevel, key_released, k);
|
|
if(arrlen(h->children[i]->children) > 0) {
|
|
recursive_dispatch_key_released(h->children[i]->lowlevel, k);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
@implementation MilskoCocoaApplication
|
|
- (void)sendEvent:(NSEvent*)event {
|
|
self->doPending = MwTRUE;
|
|
[super sendEvent:event];
|
|
}
|
|
- (MwBool)pending {
|
|
if(self->doPending) {
|
|
self->doPending = MwFALSE;
|
|
return MwTRUE;
|
|
}
|
|
return MwFALSE;
|
|
}
|
|
@end
|
|
|
|
@implementation MilskoCocoaView
|
|
- (id)initWithFrame:(NSRect)frame {
|
|
givenRect = frame;
|
|
x = frame.origin.x;
|
|
y = frame.origin.y;
|
|
width = frame.size.width;
|
|
height = frame.size.height;
|
|
self = [super initWithFrame:frame];
|
|
|
|
if(width == 0 || height == 0) {
|
|
self->rep = NULL;
|
|
self->context = NULL;
|
|
} else {
|
|
[self initRepAndContextWithWidth:width Height:height];
|
|
}
|
|
|
|
self->_child = MwFALSE;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)setChild {
|
|
self->_child = MwTRUE;
|
|
}
|
|
|
|
- (NSGraphicsContext*)context {
|
|
return self->context;
|
|
}
|
|
|
|
- (NSBitmapImageRep*)getRep {
|
|
return self->rep;
|
|
}
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
NSArray* subviews = [self subviews];
|
|
int i;
|
|
|
|
if(!self->rep) {
|
|
if(dirtyRect.size.width && dirtyRect.size.height) {
|
|
[self initRepAndContextWithWidth:dirtyRect.size.width
|
|
Height:dirtyRect.size.height];
|
|
self->width = dirtyRect.size.width;
|
|
self->height = dirtyRect.size.height;
|
|
} else {
|
|
[pool release];
|
|
return;
|
|
}
|
|
}
|
|
|
|
[self->rep drawInRect:NSMakeRect(dirtyRect.origin.x, dirtyRect.origin.y, [self->rep pixelsWide], [self->rep pixelsHigh])];
|
|
|
|
for(i = 0; i < [subviews count]; i++) {
|
|
MilskoCocoaView* subview = [subviews objectAtIndex:i];
|
|
NSRect bounds = [subview bounds];
|
|
[subview drawRect:bounds];
|
|
}
|
|
[pool release];
|
|
}
|
|
|
|
- (void)initRepAndContextWithWidth:(float)w Height:(float)h {
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
self->rep =
|
|
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
|
|
pixelsWide:w
|
|
pixelsHigh:h
|
|
bitsPerSample:8
|
|
samplesPerPixel:4
|
|
hasAlpha:YES
|
|
isPlanar:NO
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
bytesPerRow:w * 4
|
|
bitsPerPixel:32];
|
|
assert(self->rep);
|
|
[self->rep retain];
|
|
|
|
[self->rep setOpaque:MwFALSE];
|
|
[self->rep setAlpha:MwTRUE];
|
|
|
|
self->context =
|
|
[NSGraphicsContext graphicsContextWithBitmapImageRep:self->rep];
|
|
assert(self->context);
|
|
[self->context retain];
|
|
[pool release];
|
|
}
|
|
|
|
- (void)destroy {
|
|
[self->rep release];
|
|
[self->context release];
|
|
}
|
|
|
|
- (void)setFrameSize:(NSSize)newSize {
|
|
[super setFrameSize:newSize];
|
|
[self->rep setSize:newSize];
|
|
|
|
self->width = newSize.width;
|
|
self->height = newSize.height;
|
|
}
|
|
|
|
- (BOOL)canBecomeKeyView {
|
|
return YES;
|
|
}
|
|
- (BOOL)acceptsFirstResponder {
|
|
return YES;
|
|
}
|
|
|
|
- (NSView*)hitTest:(NSPoint)aPoint {
|
|
if(self->_child) {
|
|
return NSPointInRect(aPoint, [self bounds]) ? self : nil;
|
|
} else {
|
|
return [super hitTest:aPoint];
|
|
}
|
|
}
|
|
|
|
- (void)handleKeyEvent:(NSEvent*)ev ll:(MwLL)ll down:(MwBool)isDown {
|
|
int ch;
|
|
/* todo: consolidate these values into the below switch case. */
|
|
enum {
|
|
kVK_ANSI_A = 0x00,
|
|
kVK_ANSI_S = 0x01,
|
|
kVK_ANSI_D = 0x02,
|
|
kVK_ANSI_F = 0x03,
|
|
kVK_ANSI_H = 0x04,
|
|
kVK_ANSI_G = 0x05,
|
|
kVK_ANSI_Z = 0x06,
|
|
kVK_ANSI_X = 0x07,
|
|
kVK_ANSI_C = 0x08,
|
|
kVK_ANSI_V = 0x09,
|
|
kVK_ANSI_B = 0x0B,
|
|
kVK_ANSI_Q = 0x0C,
|
|
kVK_ANSI_W = 0x0D,
|
|
kVK_ANSI_E = 0x0E,
|
|
kVK_ANSI_R = 0x0F,
|
|
kVK_ANSI_Y = 0x10,
|
|
kVK_ANSI_T = 0x11,
|
|
kVK_ANSI_1 = 0x12,
|
|
kVK_ANSI_2 = 0x13,
|
|
kVK_ANSI_3 = 0x14,
|
|
kVK_ANSI_4 = 0x15,
|
|
kVK_ANSI_6 = 0x16,
|
|
kVK_ANSI_5 = 0x17,
|
|
kVK_ANSI_Equal = 0x18,
|
|
kVK_ANSI_9 = 0x19,
|
|
kVK_ANSI_7 = 0x1A,
|
|
kVK_ANSI_Minus = 0x1B,
|
|
kVK_ANSI_8 = 0x1C,
|
|
kVK_ANSI_0 = 0x1D,
|
|
kVK_ANSI_RightBracket = 0x1E,
|
|
kVK_ANSI_O = 0x1F,
|
|
kVK_ANSI_U = 0x20,
|
|
kVK_ANSI_LeftBracket = 0x21,
|
|
kVK_ANSI_I = 0x22,
|
|
kVK_ANSI_P = 0x23,
|
|
kVK_ANSI_L = 0x25,
|
|
kVK_ANSI_J = 0x26,
|
|
kVK_ANSI_Quote = 0x27,
|
|
kVK_ANSI_K = 0x28,
|
|
kVK_ANSI_Semicolon = 0x29,
|
|
kVK_ANSI_Backslash = 0x2A,
|
|
kVK_ANSI_Comma = 0x2B,
|
|
kVK_ANSI_Slash = 0x2C,
|
|
kVK_ANSI_N = 0x2D,
|
|
kVK_ANSI_M = 0x2E,
|
|
kVK_ANSI_Period = 0x2F,
|
|
kVK_ANSI_Grave = 0x32,
|
|
kVK_Return = 0x24,
|
|
kVK_Space = 0x31,
|
|
kVK_Escape = 0x35,
|
|
kVK_Shift = 0x38,
|
|
kVK_Control = 0x3B,
|
|
kVK_RightShift = 0x3C,
|
|
kVK_RightControl = 0x3E,
|
|
kVK_LeftArrow = 0x7B,
|
|
kVK_RightArrow = 0x7C,
|
|
kVK_DownArrow = 0x7D,
|
|
kVK_UpArrow = 0x7E
|
|
};
|
|
#define KEY_CASE(x, y) \
|
|
case x: \
|
|
ch = y; \
|
|
break;
|
|
switch([ev keyCode]) {
|
|
KEY_CASE(kVK_ANSI_A, 'a')
|
|
KEY_CASE(kVK_ANSI_B, 'b')
|
|
KEY_CASE(kVK_ANSI_C, 'c')
|
|
KEY_CASE(kVK_ANSI_D, 'd')
|
|
KEY_CASE(kVK_ANSI_E, 'e')
|
|
KEY_CASE(kVK_ANSI_F, 'f')
|
|
KEY_CASE(kVK_ANSI_G, 'g')
|
|
KEY_CASE(kVK_ANSI_H, 'h')
|
|
KEY_CASE(kVK_ANSI_I, 'i')
|
|
KEY_CASE(kVK_ANSI_J, 'j')
|
|
KEY_CASE(kVK_ANSI_K, 'k')
|
|
KEY_CASE(kVK_ANSI_L, 'l')
|
|
KEY_CASE(kVK_ANSI_M, 'm')
|
|
KEY_CASE(kVK_ANSI_N, 'n')
|
|
KEY_CASE(kVK_ANSI_O, 'o')
|
|
KEY_CASE(kVK_ANSI_P, 'p')
|
|
KEY_CASE(kVK_ANSI_Q, 'q')
|
|
KEY_CASE(kVK_ANSI_R, 'r')
|
|
KEY_CASE(kVK_ANSI_S, 's')
|
|
KEY_CASE(kVK_ANSI_T, 't')
|
|
KEY_CASE(kVK_ANSI_U, 'u')
|
|
KEY_CASE(kVK_ANSI_V, 'v')
|
|
KEY_CASE(kVK_ANSI_W, 'w')
|
|
KEY_CASE(kVK_ANSI_X, 'x')
|
|
KEY_CASE(kVK_ANSI_Y, 'y')
|
|
KEY_CASE(kVK_ANSI_Z, 'z')
|
|
KEY_CASE(kVK_ANSI_0, '0')
|
|
KEY_CASE(kVK_ANSI_1, '1')
|
|
KEY_CASE(kVK_ANSI_2, '2')
|
|
KEY_CASE(kVK_ANSI_3, '3')
|
|
KEY_CASE(kVK_ANSI_4, '4')
|
|
KEY_CASE(kVK_ANSI_5, '5')
|
|
KEY_CASE(kVK_ANSI_6, '6')
|
|
KEY_CASE(kVK_ANSI_7, '7')
|
|
KEY_CASE(kVK_ANSI_8, '8')
|
|
KEY_CASE(kVK_ANSI_9, '9')
|
|
KEY_CASE(kVK_ANSI_Quote, '\"')
|
|
KEY_CASE(kVK_ANSI_Grave, '`')
|
|
KEY_CASE(kVK_ANSI_Backslash, '/')
|
|
KEY_CASE(kVK_ANSI_Comma, ',')
|
|
KEY_CASE(kVK_ANSI_Equal, '=')
|
|
KEY_CASE(kVK_Escape, MwLLKeyEscape)
|
|
KEY_CASE(kVK_ANSI_LeftBracket, '[')
|
|
KEY_CASE(kVK_ANSI_Minus, '-')
|
|
KEY_CASE(kVK_ANSI_Period, '.')
|
|
KEY_CASE(kVK_Return, MwLLKeyEnter)
|
|
KEY_CASE(kVK_ANSI_RightBracket, ']')
|
|
KEY_CASE(kVK_ANSI_Semicolon, ';')
|
|
KEY_CASE(kVK_ANSI_Slash, '\\')
|
|
KEY_CASE(kVK_Space, ' ')
|
|
KEY_CASE(kVK_Control, MwLLKeyControl)
|
|
KEY_CASE(kVK_RightControl, MwLLKeyControl)
|
|
KEY_CASE(kVK_Shift, MwLLKeyLeftShift)
|
|
KEY_CASE(kVK_RightShift, MwLLKeyRightShift)
|
|
KEY_CASE(kVK_DownArrow, MwLLKeyDown)
|
|
KEY_CASE(kVK_LeftArrow, MwLLKeyLeft)
|
|
KEY_CASE(kVK_RightArrow, MwLLKeyRight)
|
|
KEY_CASE(kVK_UpArrow, MwLLKeyUp)
|
|
}
|
|
if(isDown) {
|
|
recursive_dispatch_key(ll, &ch);
|
|
} else {
|
|
recursive_dispatch_key_released(ll, &ch);
|
|
}
|
|
[ll->cocoa.real nudge];
|
|
}
|
|
- (void)mouseEntered:(NSEvent*)ev {
|
|
MwLL h = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
MwLLDispatch(h, focus_in, NULL);
|
|
[h->cocoa.real pushCursor];
|
|
[super mouseEntered:ev];
|
|
}
|
|
- (void)mouseExited:(NSEvent*)ev {
|
|
MwLL h = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
MwLLDispatch(h, focus_out, NULL);
|
|
[h->cocoa.real popCursor];
|
|
[super mouseExited:ev];
|
|
}
|
|
- (void)keyDown:(NSEvent*)ev {
|
|
MwLL h = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
[self handleKeyEvent:ev ll:h down:MwTRUE];
|
|
[super keyDown:ev];
|
|
}
|
|
- (void)keyUp:(NSEvent*)ev {
|
|
MwLL h = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
[self handleKeyEvent:ev ll:h down:MwFALSE];
|
|
[super keyUp:ev];
|
|
}
|
|
|
|
- (void)mouseMoved:(NSEvent*)ev {
|
|
MwLLMouse mouse;
|
|
NSPoint mousePoint = pointFlip([ev locationInWindow]);
|
|
MwLL this = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
mouse.button = MwLLMouseLeft;
|
|
mouse.point.x = mousePoint.x;
|
|
mouse.point.y = mousePoint.y;
|
|
MwLLDispatch(this, move, &mouse);
|
|
[this->cocoa.real nudge];
|
|
[super mouseMoved:ev];
|
|
}
|
|
- (BOOL)mouseDownCanMoveWindow {
|
|
return NO;
|
|
}
|
|
|
|
- (void)mouseDown:(NSEvent*)ev {
|
|
MwLLMouse mouse;
|
|
NSPoint mousePoint = [ev locationInWindow];
|
|
MwLL this = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
mousePoint.y = [[ev window] frame].size.height - mousePoint.y;
|
|
mouse.button = MwLLMouseLeft;
|
|
mouse.point.x = mousePoint.x;
|
|
mouse.point.y = mousePoint.y;
|
|
MwLLDispatch(this, down, &mouse);
|
|
[this->cocoa.real nudge];
|
|
[super mouseDown:ev];
|
|
}
|
|
|
|
- (void)mouseUp:(NSEvent*)ev {
|
|
MwLLMouse mouse;
|
|
NSPoint mousePoint = [ev locationInWindow];
|
|
MwLL this = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
mousePoint.y = [[ev window] frame].size.height - mousePoint.y;
|
|
mouse.button = MwLLMouseLeft;
|
|
mouse.point.x = mousePoint.x;
|
|
mouse.point.y = mousePoint.y;
|
|
MwLLDispatch(this, up, &mouse);
|
|
[this->cocoa.real nudge];
|
|
[super mouseUp:ev];
|
|
}
|
|
- (void)rightMouseDown:(NSEvent*)ev {
|
|
MwLLMouse mouse;
|
|
NSPoint mousePoint = [ev locationInWindow];
|
|
MwLL this = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
mousePoint.y = [[ev window] frame].size.height - mousePoint.y;
|
|
mouse.button = MwLLMouseRight;
|
|
mouse.point.x = mousePoint.x;
|
|
mouse.point.y = mousePoint.y;
|
|
MwLLDispatch(this, down, &mouse);
|
|
[this->cocoa.real nudge];
|
|
[super rightMouseDown:ev];
|
|
}
|
|
|
|
- (void)rightMouseUp:(NSEvent*)ev {
|
|
MwLLMouse mouse;
|
|
NSPoint mousePoint = [ev locationInWindow];
|
|
MwLL this = [((MilskoFakePointer*)[[self subviews] objectAtIndex:0]) pointer];
|
|
mousePoint.y = [[ev window] frame].size.height - mousePoint.y;
|
|
mouse.button = MwLLMouseRight;
|
|
mouse.point.x = mousePoint.x;
|
|
mouse.point.y = mousePoint.y;
|
|
MwLLDispatch(this, up, &mouse);
|
|
[this->cocoa.real nudge];
|
|
[super rightMouseUp:ev];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MilskoCocoaApplicationDelegate
|
|
- (MilskoCocoaApplicationDelegate*)initWithAppl:(MilskoCocoaApplication*)_appl {
|
|
self->appl = _appl;
|
|
return self;
|
|
}
|
|
@end
|
|
|
|
@implementation MilskoCocoaWindowDelegate
|
|
|
|
- (NSSize)windowWillResize:(NSWindow*)win toSize:(NSSize)frameSize;
|
|
{
|
|
if([[[win contentView] subviews] count] >= 1) {
|
|
NSView* ptr = [[[win contentView] subviews] objectAtIndex:0];
|
|
if([ptr isKindOfClass:[MilskoFakePointer class]]) {
|
|
MwLL h = [(MilskoFakePointer*)ptr pointer];
|
|
MwLLDispatch(h, resize, NULL);
|
|
}
|
|
}
|
|
return frameSize;
|
|
}
|
|
|
|
- (void)windowDidBecomeMain:(NSNotification*)notification {
|
|
if([[[w contentView] subviews] count] != 0) {
|
|
MwLL h = [((MilskoFakePointer*)[[[w contentView] subviews]
|
|
objectAtIndex:0]) pointer];
|
|
MwLLDispatch(h, focus_in, NULL);
|
|
}
|
|
[self->w makeKeyAndOrderFront:nil];
|
|
}
|
|
|
|
- (MilskoCocoaWindowDelegate*)initWithWin:(NSWindow*)win {
|
|
self->w = win;
|
|
return self;
|
|
}
|
|
|
|
- (void)windowWillClose:(NSNotification*)notification {
|
|
if([[[w contentView] subviews] count] != 0) {
|
|
MwLL h = [((MilskoFakePointer*)[[[w contentView] subviews]
|
|
objectAtIndex:0]) pointer];
|
|
MwLLDispatch(h, close, NULL);
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MilskoFakePointer
|
|
- (void)setPointer:(void*)pointer {
|
|
[self setFrame:*(NSRect*)&pointer];
|
|
self->ptr = pointer;
|
|
};
|
|
- (void*)pointer {
|
|
return self->ptr;
|
|
};
|
|
|
|
- (void)drawRect:(NSRect)dirtyRect {
|
|
/* explicitly do nothing */
|
|
(void)dirtyRect;
|
|
}
|
|
|
|
- (void)destroy {
|
|
}
|
|
@end
|
|
|
|
@implementation MilskoCocoaWindow
|
|
- (BOOL)canBecomeKeyWindow {
|
|
return true;
|
|
}
|
|
- (BOOL)canBecomeMainWindow {
|
|
return true;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation MilskoCocoaPixmap
|
|
+ (MilskoCocoaPixmap*)newWithWidth:(int)width height:(int)height {
|
|
MilskoCocoaPixmap* p = [MilskoCocoaPixmap alloc];
|
|
|
|
p->width = width;
|
|
p->height = height;
|
|
p->image = NULL;
|
|
|
|
p->buf = malloc(width * height * 4);
|
|
|
|
p->rep =
|
|
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&p->buf
|
|
pixelsWide:(int)width
|
|
pixelsHigh:(int)height
|
|
bitsPerSample:8
|
|
samplesPerPixel:4
|
|
hasAlpha:YES
|
|
isPlanar:NO
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
bytesPerRow:(int)width * 4
|
|
bitsPerPixel:32];
|
|
assert(p->rep);
|
|
|
|
p->image = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
|
|
assert(p->image);
|
|
[p->image addRepresentation:p->rep];
|
|
|
|
return p;
|
|
}
|
|
- (void)updateWithData:(unsigned char*)_data {
|
|
memcpy(self->buf, _data, width * height * 4);
|
|
if(self->rep) {
|
|
[self->image removeRepresentation:self->rep];
|
|
[self->rep release];
|
|
}
|
|
self->rep =
|
|
[[NSBitmapImageRep alloc] initWithBitmapDataPlanes:&self->buf
|
|
pixelsWide:(int)width
|
|
pixelsHigh:(int)height
|
|
bitsPerSample:8
|
|
samplesPerPixel:4
|
|
hasAlpha:YES
|
|
isPlanar:NO
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
|
bytesPerRow:(int)width * 4
|
|
bitsPerPixel:32];
|
|
[self->image addRepresentation:self->rep];
|
|
}
|
|
- (void)destroy {
|
|
free(self->buf);
|
|
[self->image removeRepresentation:self->rep];
|
|
[self->image release];
|
|
[self->rep release];
|
|
self->image = NULL;
|
|
self->rep = NULL;
|
|
}
|
|
- (NSImage*)image {
|
|
return self->image;
|
|
}
|
|
@end
|
|
|
|
@implementation MilskoCocoa
|
|
- (void)sendClipboardEvent {
|
|
/*NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
|
NSArray* items = @[
|
|
@"public.utf8-plain-text",
|
|
@"public.utf16-external-plain-text",
|
|
@"com.apple.traditional-mac-plain-text",
|
|
];
|
|
MwLL this = self->handle.pointer;
|
|
|
|
if([pasteboard canReadItemWithDataConformingToTypes:items]) {
|
|
char* data = NULL;
|
|
size_t size = 0;
|
|
for(NSPasteboardItem* item in [pasteboard pasteboardItems]) {
|
|
for(NSString* it in items) {
|
|
NSString* itemData = [item
|
|
stringForType:(NSString*)it]; if(itemData != NULL) { if(strHash != 0 &&
|
|
strHash != [itemData hash]) { char* text = malloc([itemData length]);
|
|
strncpy(text, [itemData UTF8String],
|
|
[itemData length]); MwLLDispatch(this, clipboard, text); printf("%s -> %p\n",
|
|
text, this); free(text);
|
|
}
|
|
strHash = [itemData hash];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[pool release];*/
|
|
}
|
|
|
|
- (void)destroy {
|
|
[self->handle release];
|
|
[self->window release];
|
|
}
|
|
|
|
- (MwLL)getParent {
|
|
return self->parent;
|
|
}
|
|
|
|
- (NSView*)getView {
|
|
return view;
|
|
}
|
|
- (NSWindow*)getWindow {
|
|
return window;
|
|
}
|
|
|
|
- (void)nudge {
|
|
MwLL p = self->parent;
|
|
self->_eventsPending = MwTRUE;
|
|
|
|
if(p) {
|
|
[p->cocoa.real->view setNeedsDisplay:true];
|
|
p = p->cocoa.real->parent;
|
|
}
|
|
}
|
|
|
|
- (void)pushCursor {
|
|
[self->cursor push];
|
|
}
|
|
- (void)popCursor {
|
|
[self->cursor pop];
|
|
}
|
|
|
|
- (MilskoFakePointer*)getHandle {
|
|
return handle;
|
|
}
|
|
|
|
@end
|
|
|
|
static MwLL MwLLCreateImpl(MwLL parent, int x, int y, int width, int height) {
|
|
MwLL r;
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
MilskoCocoa* c = [MilskoCocoa alloc];
|
|
r = malloc(sizeof(*r));
|
|
|
|
MwLLCreateCommon(r);
|
|
|
|
[c retain];
|
|
c->application = (MilskoCocoaApplication*)[MilskoCocoaApplication sharedApplication];
|
|
[c->application retain];
|
|
|
|
/*
|
|
* MacOS doesn't really have a "default" window position, you're actually
|
|
* meant to center the window yourself, so if the user passes MwDEFAULT
|
|
* respond appropriately. Also for child windows just have it be 0.
|
|
*/
|
|
if(x == MwDEFAULT) {
|
|
x = r ? ([[NSScreen mainScreen] frame].size.width / 2.) - (width / 2.) : 0;
|
|
}
|
|
if(y == MwDEFAULT) {
|
|
y = r ? ([[NSScreen mainScreen] frame].size.height / 2.) - (height / 2.)
|
|
: 0;
|
|
}
|
|
c->rect = NSMakeRect(x, y, width, height);
|
|
|
|
if(parent == NULL) {
|
|
c->window = [[NSWindow alloc]
|
|
initWithContentRect:rectFlip(c->rect)
|
|
styleMask:(NSTitledWindowMask |
|
|
NSClosableWindowMask | NSMiniaturizableWindowMask |
|
|
NSResizableWindowMask)
|
|
backing:NSBackingStoreBuffered
|
|
defer:NO];
|
|
[c->window
|
|
setDelegate:[[MilskoCocoaWindowDelegate alloc] initWithWin:c->window]];
|
|
[c->window retain];
|
|
|
|
[c->window makeKeyAndOrderFront:c->application];
|
|
[c->window makeFirstResponder:c->view];
|
|
|
|
c->view = [[MilskoCocoaView alloc] initWithFrame:c->rect];
|
|
[c->view retain];
|
|
[c->window setContentView:c->view];
|
|
|
|
/* In order for our application to be front and center when launched, we need to use this function introduced in 10.5. There's no function that works on 10.4 or below as far as I know because what you're actually supposed to do is use a feature of XCode project files to modify the settings of the compiled app to set the activation policy. */
|
|
if([c->application respondsToSelector:@selector(setActivationPolicy:)]) {
|
|
[c->application
|
|
setActivationPolicy:0 /* NSApplicationActivationPolicyRegular */];
|
|
}
|
|
|
|
[c->application activateIgnoringOtherApps:MwTRUE];
|
|
[c->application setDelegate:[[MilskoCocoaApplicationDelegate alloc]
|
|
initWithAppl:c->application]];
|
|
[c->application retain];
|
|
|
|
[c->view addTrackingRect:c->rect owner:c->view userData:NULL assumeInside:MwFALSE];
|
|
c->modalSession = [c->application beginModalSessionForWindow:c->window];
|
|
} else {
|
|
MilskoCocoa* p = parent->cocoa.real;
|
|
NSRect rect = localRectFlip(c->rect, p->view);
|
|
|
|
c->window = p->window;
|
|
c->view = [[MilskoCocoaView alloc] initWithFrame:rect];
|
|
[c->view setBounds:c->rect];
|
|
[c->view retain];
|
|
[c->view setChild];
|
|
|
|
[c->view addTrackingRect:c->rect owner:c->view userData:NULL assumeInside:MwFALSE];
|
|
|
|
[p->view addSubview:c->view];
|
|
|
|
c->modalSession = p->modalSession;
|
|
}
|
|
[c->window setAcceptsMouseMovedEvents:MwTRUE];
|
|
|
|
c->handle = [[MilskoFakePointer alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
|
|
[c->handle retain];
|
|
[c->handle setPointer:r];
|
|
[c->view addSubview:c->handle];
|
|
|
|
c->parent = parent;
|
|
|
|
c->_forceRender = MwTRUE;
|
|
c->strHash = 0;
|
|
|
|
c->cursorPixmap = NULL;
|
|
|
|
[c->application finishLaunching];
|
|
|
|
c->pointerLocked = MwFALSE;
|
|
|
|
[pool release];
|
|
|
|
r->cocoa.real = c;
|
|
|
|
return r;
|
|
}
|
|
|
|
static void MwLLDestroyImpl(MwLL handle) {
|
|
MilskoCocoa* h = handle->cocoa.real;
|
|
|
|
[h destroy];
|
|
|
|
MwLLDestroyCommon(handle);
|
|
|
|
free(handle);
|
|
}
|
|
|
|
static void MwLLBeginDrawImpl(MwLL handle) {
|
|
(void)handle;
|
|
}
|
|
|
|
static void MwLLEndDrawImpl(MwLL handle) {
|
|
(void)handle;
|
|
}
|
|
|
|
static void MwLLPolygonImpl(MwLL handle, MwPoint* points, int points_count,
|
|
MwLLColor color) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
NSGraphicsContext* ctx = [self->view context];
|
|
if(ctx) {
|
|
int i;
|
|
NSBezierPath* path = [NSBezierPath bezierPath];
|
|
/* whatever rect we use for coordinate flipping */
|
|
NSRect _rect = self->parent ? [self->view bounds] : [self->window frame];
|
|
|
|
NSColor* nscolor =
|
|
[NSColor colorWithCalibratedRed:color->common.red / 255.
|
|
green:color->common.green / 255.
|
|
blue:color->common.blue / 255.
|
|
alpha:1.0];
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:ctx];
|
|
|
|
[nscolor setFill];
|
|
for(i = 0; i < points_count; i++) {
|
|
NSPoint p = localPointFlip(NSMakePoint(points[i].x, points[i].y), self->view);
|
|
if(i == 0) {
|
|
[path moveToPoint:p];
|
|
} else {
|
|
[path lineToPoint:p];
|
|
}
|
|
}
|
|
|
|
[path closePath];
|
|
[path fill];
|
|
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
[self->view setNeedsDisplay:YES];
|
|
}
|
|
[pool release];
|
|
}
|
|
|
|
static void MwLLLineImpl(MwLL handle, MwPoint* points, MwLLColor color) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
NSGraphicsContext* ctx = [self->view context];
|
|
if(ctx) {
|
|
int i;
|
|
NSBezierPath* path = [NSBezierPath bezierPath];
|
|
/* whatever rect we use for coordinate flipping */
|
|
NSRect _rect = self->parent ? [self->view bounds] : [self->window frame];
|
|
|
|
NSColor* nscolor =
|
|
[NSColor colorWithCalibratedRed:color->common.red / 255.
|
|
green:color->common.green / 255.
|
|
blue:color->common.blue / 255.
|
|
alpha:1.0];
|
|
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:ctx];
|
|
|
|
[nscolor setFill];
|
|
for(i = 0; i < 2; i++) {
|
|
NSPoint p = localPointFlip(NSMakePoint(points[i].x, points[i].y), self->view);
|
|
if(i == 0) {
|
|
[path moveToPoint:p];
|
|
} else {
|
|
[path lineToPoint:p];
|
|
}
|
|
}
|
|
|
|
[path closePath];
|
|
[path stroke];
|
|
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
[self->view setNeedsDisplay:YES];
|
|
}
|
|
[pool release];
|
|
}
|
|
|
|
static MwLLColor MwLLAllocColorImpl(MwLL handle, int r, int g, int b) {
|
|
MwLLColor c = malloc(sizeof(*c));
|
|
MwLLColorUpdate(handle, c, r, g, b);
|
|
return c;
|
|
}
|
|
|
|
static void MwLLColorUpdateImpl(MwLL handle, MwLLColor c, int r, int g, int b) {
|
|
(void)handle;
|
|
|
|
c->common.red = r;
|
|
c->common.green = g;
|
|
c->common.blue = b;
|
|
}
|
|
|
|
static void MwLLGetXYWHImpl(MwLL handle, int* x, int* y, unsigned int* w,
|
|
unsigned int* h) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSRect frame;
|
|
if(self->parent) {
|
|
frame = [self->view frame];
|
|
} else {
|
|
frame = [self->window frame];
|
|
}
|
|
frame = rectFlip(frame);
|
|
|
|
*x = frame.origin.x;
|
|
*y = frame.origin.y;
|
|
|
|
*w = frame.size.width;
|
|
*h = frame.size.height;
|
|
}
|
|
|
|
static void MwLLSetXYImpl(MwLL handle, int x, int y) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSRect frame;
|
|
if(self->parent) {
|
|
frame = [self->view frame];
|
|
} else {
|
|
frame = [self->window frame];
|
|
}
|
|
|
|
frame.origin.x = x;
|
|
frame.origin.y = y;
|
|
|
|
if(!self->parent) {
|
|
frame = rectFlip(frame);
|
|
[self->window setFrame:frame display:MwTRUE animate:MwTRUE];
|
|
} else {
|
|
frame = localRectFlip(frame, self->parent->cocoa.real->view);
|
|
[self->view setBounds:frame];
|
|
}
|
|
[self nudge];
|
|
}
|
|
|
|
static void MwLLSetWHImpl(MwLL handle, int w, int h) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSRect frame = [self->window frame];
|
|
frame.size.width = w;
|
|
frame.size.height = h;
|
|
|
|
self->rect = frame;
|
|
|
|
[self->view setFrameSize:frame.size];
|
|
if(self->parent) {
|
|
[self->view setFrame:frame];
|
|
} else {
|
|
[self->window setFrame:frame display:YES animate:false];
|
|
}
|
|
[self nudge];
|
|
}
|
|
|
|
static void MwLLFreeColorImpl(MwLLColor color) {
|
|
free(color);
|
|
}
|
|
|
|
static int MwLLPendingImpl(MwLL handle) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
MwBool isPending = [self->application pending];
|
|
[self->application runModalSession:self->modalSession];
|
|
[pool release];
|
|
return self->_forceRender || self->_eventsPending || isPending;
|
|
}
|
|
|
|
static void MwLLNextEventImpl(MwLL handle) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
MwLL h = [self->handle pointer];
|
|
if(self->_forceRender) {
|
|
MwLLDispatch(h, draw, NULL);
|
|
self->_forceRender = MwFALSE;
|
|
}
|
|
if(self->_eventsPending) {
|
|
MwLLDispatch(h, draw, NULL);
|
|
self->_eventsPending = MwFALSE;
|
|
}
|
|
[self sendClipboardEvent];
|
|
|
|
#ifdef __APPLE__
|
|
if(self->pointerLocked && [self->window isMainWindow] && self->lastEvent) {
|
|
struct CGPoint pos;
|
|
pos.x = [self->window frame].origin.x + [self->window frame].size.width / 2;
|
|
pos.y = [self->window frame].origin.y + [self->window frame].size.height / 2;
|
|
|
|
CGWarpMouseCursorPosition(pos);
|
|
}
|
|
#endif
|
|
|
|
[self->application updateWindows];
|
|
}
|
|
|
|
static void MwLLSetTitleImpl(MwLL handle, const char* title) {
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
[handle->cocoa.real->window
|
|
setTitleWithRepresentedFilename:[NSString stringWithUTF8String:title]];
|
|
[pool release];
|
|
}
|
|
|
|
static MwLLPixmap MwLLCreatePixmapImpl(MwLL handle, unsigned char* data,
|
|
int width, int height) {
|
|
MwLLPixmap r = malloc(sizeof(*r));
|
|
|
|
(void)handle;
|
|
memset(r, 0, sizeof(*r));
|
|
|
|
r->common.raw = malloc(4 * width * height);
|
|
memcpy(r->common.raw, data, 4 * width * height);
|
|
|
|
r->common.width = width;
|
|
r->common.height = height;
|
|
|
|
r->cocoa.real = [MilskoCocoaPixmap newWithWidth:width height:height];
|
|
|
|
MwLLPixmapUpdate(r);
|
|
return r;
|
|
}
|
|
|
|
static void MwLLPixmapUpdateImpl(MwLLPixmap pixmap) {
|
|
MilskoCocoaPixmap* self = pixmap->cocoa.real;
|
|
memcpy(self->buf, pixmap->common.raw, pixmap->common.width * pixmap->common.height * 4);
|
|
[pixmap->cocoa.real updateWithData:pixmap->common.raw];
|
|
}
|
|
|
|
static void MwLLDestroyPixmapImpl(MwLLPixmap pixmap) {
|
|
[pixmap->cocoa.real destroy];
|
|
[pixmap->cocoa.real release];
|
|
free(pixmap);
|
|
}
|
|
|
|
static void MwLLDrawPixmapImpl(MwLL handle, MwRect* rect, MwLLPixmap pixmap) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
|
|
MilskoCocoaPixmap* p = pixmap->cocoa.real;
|
|
NSGraphicsContext* ctx = [self->view context];
|
|
if(ctx) {
|
|
[NSGraphicsContext saveGraphicsState];
|
|
[NSGraphicsContext setCurrentContext:ctx];
|
|
|
|
[[p image]
|
|
drawInRect:localRectFlip(NSMakeRect(rect->x, rect->y, rect->width, rect->height), self->view)
|
|
fromRect:NSZeroRect
|
|
operation:NSCompositeSourceOver
|
|
fraction:1.0];
|
|
|
|
[NSGraphicsContext restoreGraphicsState];
|
|
|
|
[self->view setNeedsDisplay:YES];
|
|
}
|
|
[pool release];
|
|
MwLLForceRender(handle);
|
|
}
|
|
|
|
static void MwLLSetIconImpl(MwLL handle, MwLLPixmap pixmap) {
|
|
[handle->cocoa.real->application setApplicationIconImage:[pixmap->cocoa.real image]];
|
|
}
|
|
|
|
static void MwLLForceRenderImpl(MwLL handle) {
|
|
handle->cocoa.real->_forceRender = MwTRUE;
|
|
}
|
|
|
|
static void MwLLSetCursorImpl(MwLL handle, MwCursor* image, MwCursor* mask) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
int y, x, ys, xs;
|
|
unsigned char* di = malloc(image->width * image->height * 4);
|
|
memset(di, 0, image->width * image->height * 4);
|
|
|
|
if(self->cursorPixmap) {
|
|
[self->cursorPixmap destroy];
|
|
}
|
|
self->cursorPixmap =
|
|
[MilskoCocoaPixmap newWithWidth:image->width
|
|
height:image->height];
|
|
|
|
xs = -mask->x + image->x;
|
|
ys = MwCursorDataHeight + mask->y;
|
|
ys = MwCursorDataHeight + image->y - ys;
|
|
|
|
for(y = 0; y < mask->height; y++) {
|
|
unsigned int d = mask->data[y];
|
|
for(x = mask->width - 1; x >= 0; x--) {
|
|
int px = 0;
|
|
int idx = ((y * mask->width) + x) * 4;
|
|
|
|
if(d & 1) {
|
|
di[idx + 3] = 255;
|
|
};
|
|
d = d >> 1;
|
|
}
|
|
}
|
|
for(y = 0; y < image->height; y++) {
|
|
unsigned int d = image->data[y];
|
|
for(x = image->width - 1; x >= 0; x--) {
|
|
int px = 0;
|
|
int idx = ((y * image->width) + x) * 4;
|
|
|
|
if(d & 1) {
|
|
px = 255;
|
|
};
|
|
|
|
di[idx] = px;
|
|
di[idx + 1] = px;
|
|
di[idx + 2] = px;
|
|
d = d >> 1;
|
|
}
|
|
}
|
|
|
|
[self->cursorPixmap updateWithData:di];
|
|
|
|
self->cursor = [[NSCursor alloc]
|
|
initWithImage:[self->cursorPixmap image]
|
|
hotSpot:NSMakePoint(image->x, image->y + image->height)];
|
|
[self->cursor retain];
|
|
|
|
[self->cursor pop];
|
|
[self->cursor push];
|
|
|
|
free(di);
|
|
}
|
|
|
|
static void MwLLDetachImpl(MwLL handle, MwPoint* point) {
|
|
[handle->cocoa.real->window setParentWindow:NULL];
|
|
}
|
|
|
|
static void MwLLShowImpl(MwLL handle, int show) {
|
|
(void)show;
|
|
}
|
|
|
|
static void MwLLMakePopupImpl(MwLL handle, MwLL parent) {
|
|
(void)parent;
|
|
}
|
|
|
|
static void MwLLSetSizeHintsImpl(MwLL handle, int minx, int miny, int maxx,
|
|
int maxy) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
[self->window setMinSize:NSMakeSize(minx, miny)];
|
|
[self->window setMaxSize:NSMakeSize(maxx, maxy)];
|
|
}
|
|
|
|
static void MwLLMakeBorderlessImpl(MwLL handle, int toggle) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
MwU32 mask = [self->window styleMask];
|
|
if(toggle) {
|
|
mask ^= NSBorderlessWindowMask;
|
|
mask |= NSTitledWindowMask;
|
|
} else {
|
|
mask |= NSBorderlessWindowMask;
|
|
mask ^= NSTitledWindowMask;
|
|
}
|
|
[self->window initWithContentRect:self->rect
|
|
styleMask:mask
|
|
backing:NSBackingStoreBuffered
|
|
defer:NO];
|
|
}
|
|
|
|
static void MwLLFocusImpl(MwLL handle) {
|
|
[handle->cocoa.real->application activateIgnoringOtherApps:MwTRUE];
|
|
}
|
|
|
|
static void MwLLGrabPointerImpl(MwLL handle, int toggle) {
|
|
handle->cocoa.real->pointerLocked = toggle;
|
|
}
|
|
|
|
static void MwLLSetClipboardImpl(MwLL handle, const char* text) {
|
|
(void)handle;
|
|
(void)text;
|
|
}
|
|
|
|
static void MwLLGetClipboardImpl(MwLL handle) {
|
|
(void)handle;
|
|
}
|
|
|
|
static void MwLLMakeToolWindowImpl(MwLL handle) {
|
|
/* If my understand of what a "tool window" usually is is correct then I
|
|
* highly doubt the Mac OS has this and if they did they probably outright
|
|
* removed it along time ago is this kind of conflicts with modern UX. So
|
|
* we'll just make it borderless idgaf */
|
|
MwLLMakeBorderlessImpl(handle, 1);
|
|
}
|
|
|
|
static void MwLLGetCursorCoordImpl(MwLL handle, MwPoint* point) {
|
|
NSPoint p = [NSEvent mouseLocation];
|
|
point->x = p.x;
|
|
point->y = p.y;
|
|
}
|
|
|
|
static void MwLLGetScreenSizeImpl(MwLL handle, MwRect* rect) {
|
|
MilskoCocoa* self = handle->cocoa.real;
|
|
NSScreen* screen = [self->window screen];
|
|
rect->x = [screen frame].origin.x;
|
|
rect->y = [screen frame].origin.y;
|
|
rect->width = [screen frame].size.width;
|
|
rect->height = [screen frame].size.height;
|
|
}
|
|
|
|
static void MwLLBeginStateChangeImpl(MwLL handle) {
|
|
MwLLShow(handle, 0);
|
|
}
|
|
|
|
static void MwLLEndStateChangeImpl(MwLL handle) {
|
|
MwLLShow(handle, 1);
|
|
}
|
|
|
|
static int MwLLCocoaCallInitImpl(void) {
|
|
#ifndef __APPLE__
|
|
printf("Using GNUStep Backend\n");
|
|
#endif
|
|
|
|
[MilskoCocoaApplication sharedApplication];
|
|
|
|
return 0;
|
|
}
|
|
|
|
#include "call.c"
|
|
CALL(Cocoa);
|