diff --git a/carbon/events.d b/carbon/events.d new file mode 100644 index 0000000..892fd14 --- /dev/null +++ b/carbon/events.d @@ -0,0 +1,123 @@ +module carbon.events; + +import cocoa.foundation; + +import objc.runtime; +@ObjectiveC final extern(C++) @nogc nothrow: + +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_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C, + + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +} + diff --git a/cocoa/foundation.d b/cocoa/foundation.d new file mode 100644 index 0000000..9f306a3 --- /dev/null +++ b/cocoa/foundation.d @@ -0,0 +1,38 @@ +module cocoa.foundation; + +import objc.runtime; + +extern(C) bool CGEventSourceKeyState(int stateID, ushort keyCode) nothrow @nogc; + +version(watchOS) { + alias CGFloat = float; +} else { + alias CGFloat = double; +} + +struct CGPoint { + CGFloat x; + CGFloat y; +} + +struct CGSize { + CGFloat width; + CGFloat height; +} + +struct CGRect { + CGPoint origin; + CGSize size; +} + +enum NSEventModifierFlags : NSUInteger { + AlphaShiftKeyMask = 1 << 16, + ShiftKeyMask = 1 << 17, + ControlKeyMask = 1 << 18, + AlternateKeyMask = 1 << 19, + CommandKeyMask = 1 << 20, + NumericPadKeyMask = 1 << 21, + HelpKeyMask = 1 << 22, + FunctionKeyMask = 1 << 23 +} + diff --git a/cocoa/gamecontroller.d b/cocoa/gamecontroller.d new file mode 100644 index 0000000..892e1f5 --- /dev/null +++ b/cocoa/gamecontroller.d @@ -0,0 +1,37 @@ +module cocoa.gamecontroller; + +import objc.runtime; +import cocoa.foundation; + +enum GCHapticsLocality { + Left = 1, + Right = 2 +} + +@ObjectiveC final extern(C++): +@nogc nothrow: + +class GCController { + mixin ObjcExtend!NSObject; + + @selector("haptics") + GCHaptics haptics() @trusted nothrow; +} + +class GCHaptics { + mixin ObjcExtend!NSObject; + + @selector("createEngine:") + GCHapticsEngine createEngine(NSInteger locality) @trusted nothrow; + + @selector("cancelAll") + void cancelAll() @trusted nothrow; +} + +class GCHapticsEngine { + mixin ObjcExtend!NSObject; + + @selector("createContinuousEvent:intensity:") + void createContinuousEvent(CGFloat intensity) @trusted nothrow; +} + diff --git a/cocoa/nsappdelegate.d b/cocoa/nsappdelegate.d new file mode 100644 index 0000000..6235d14 --- /dev/null +++ b/cocoa/nsappdelegate.d @@ -0,0 +1,26 @@ +module cocoa.nsappdelegate; + +import objc.meta : selector, ObjcExtend, ObjectiveC; +import objc.runtime; +import cocoa.foundation; +import cocoa.nsapplication; +import cocoa.nswindow; +import cocoa.nsnotification; + +@ObjectiveC extern(C++): +@nogc nothrow: + +interface NSApplicationDelegate { +} + +@ObjectiveC final extern(C++): +class AppDelegate { + mixin ObjcExtend!NSObject; + + static AppDelegate alloc() @selector("alloc") nothrow @nogc; + AppDelegate initialize() @selector("init"); + + void applicationDidFinishLaunching(NSNotification notification) @selector("applicationDidFinishLaunching:"); + BOOL applicationShouldTerminateAfterLastWindowClosed(NSApplication sender) @selector("applicationShouldTerminateAfterLastWindowClosed:"); +} + diff --git a/cocoa/nsapplication.d b/cocoa/nsapplication.d new file mode 100644 index 0000000..8e84e7a --- /dev/null +++ b/cocoa/nsapplication.d @@ -0,0 +1,63 @@ +module cocoa.nsapplication; + +import cocoa.nswindow; +import cocoa.foundation; +import cocoa.nsevent; +import cocoa.nsdate; +import cocoa.nsmenu; + +import objc.runtime; +@ObjectiveC final extern(C++): +@nogc nothrow: + +enum NSApplicationActivationPolicy : NSInteger { + Regular = 0, + Accessory = 1, + Prohibited = 2 +} + +class NSApplication { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("sharedApplication") + static NSApplication sharedApplication(); + + @selector("run") + void run(); + + @selector("terminate:") + void terminate(NSObject sender); + + @selector("activateIgnoringOtherApps:") + void activateIgnoringOtherApps(BOOL flag); + + @selector("setActivationPolicy:") + BOOL setActivationPolicy(NSApplicationActivationPolicy activationPolicy); + + @selector("mainWindow") + NSWindow mainWindow(); + + @selector("windows") + NSArray_!NSWindow windows(); + + @selector("keyWindow") + NSWindow keyWindow(); + + @selector("nextEventMatchingMask:untilDate:inMode:dequeue:") + NSEvent nextEventMatchingMask(NSUInteger mask, NSDate date, NSString mode, BOOL dequeue); + + @selector("sendEvent:") + void sendEvent(NSEvent event); + + @selector("setMainMenu:") + void setMainMenu(NSMenu); + + @selector("setDelegate:") + void setDelegate(NSObject); +} + +extern(D) NSApplication NSApp() @property nothrow @nogc { + return NSApplication.sharedApplication(); +} + diff --git a/cocoa/nscursor.d b/cocoa/nscursor.d new file mode 100644 index 0000000..24dc495 --- /dev/null +++ b/cocoa/nscursor.d @@ -0,0 +1,94 @@ +module cocoa.nscursor; + +import objc.runtime; +import cocoa.foundation; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +class NSCursor { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("arrowCursor") + static NSCursor arrowCursor() nothrow @nogc; + + @selector("IBeamCursor") + static NSCursor IBeamCursor() nothrow @nogc; + + @selector("IBeamCursorForVerticalLayout") + static NSCursor IBeamCursorForVerticalLayout() nothrow @nogc; + + @selector("crosshairCursor") + static NSCursor crosshairCursor() nothrow @nogc; + + @selector("closedHandCursor") + static NSCursor closedHandCursor() nothrow @nogc; + + @selector("contextualMenuCursor") + static NSCursor contextualMenuCursor() nothrow @nogc; + + @selector("disappearingItemCursor") + static NSCursor disappearingItemCursor() nothrow @nogc; + + @selector("dragCopyCursor") + static NSCursor dragCopyCursor() nothrow @nogc; + + @selector("dragLinkCursor") + static NSCursor dragLinkCursor() nothrow @nogc; + + @selector("operationNotAllowedCursor") + static NSCursor operationNotAllowedCursor() nothrow @nogc; + + @selector("pointingHandCursor") + static NSCursor pointingHandCursor() nothrow @nogc; + + @selector("_windowResizeNorthWestSouthEastCursor") + static NSCursor windowResizeNorthWestSouthEastCursor() nothrow @nogc; + + @selector("_windowResizeNorthEastSouthWestCursor") + static NSCursor windowResizeNorthEastSouthWestCursor() nothrow @nogc; + + @selector("_windowResizeSouthWestCursor") + static NSCursor windowResizeSouthWestCursor() nothrow @nogc; + + @selector("_windowResizeSouthEastCursor") + static NSCursor windowResizeSouthEastCursor() nothrow @nogc; + + @selector("_windowResizeNorthWestCursor") + static NSCursor windowResizeNorthWestCursor() nothrow @nogc; + + @selector("_windowResizeNorthEastCursor") + static NSCursor windowResizeNorthEastCursor() nothrow @nogc; + + @selector("resizeDownCursor") + static NSCursor resizeDownCursor() nothrow @nogc; + + @selector("resizeLeftCursor") + static NSCursor resizeLeftCursor() nothrow @nogc; + + @selector("resizeLeftRightCursor") + static NSCursor resizeLeftRightCursor() nothrow @nogc; + + @selector("resizeRightCursor") + static NSCursor resizeRightCursor() nothrow @nogc; + + @selector("resizeUpCursor") + static NSCursor resizeUpCursor() nothrow @nogc; + + @selector("resizeUpDownCursor") + static NSCursor resizeUpDownCursor() nothrow @nogc; + + @selector("openHandCursor") + static NSCursor openHandCursor() nothrow @nogc; + + @selector("zoomInCursor") + static NSCursor zoomInCursor() nothrow @nogc; + + @selector("zoomOutCursor") + static NSCursor zoomOutCursor() nothrow @nogc; + + @selector("set") + void set() nothrow @nogc; +} + diff --git a/cocoa/nsdate.d b/cocoa/nsdate.d new file mode 100644 index 0000000..c3d65be --- /dev/null +++ b/cocoa/nsdate.d @@ -0,0 +1,16 @@ +module cocoa.nsdate; + +import objc.runtime; +import cocoa.foundation; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +class NSDate { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("distantPast") + static NSDate distantPast(); +} + diff --git a/cocoa/nsevent.d b/cocoa/nsevent.d new file mode 100644 index 0000000..84e5aed --- /dev/null +++ b/cocoa/nsevent.d @@ -0,0 +1,114 @@ +module cocoa.nsevent; + +import cocoa.foundation; +import cocoa.nswindow; + +import objc.meta; +import objc.runtime; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +enum NSEventMaskAny = NSUIntegerMax; +enum NSUIntegerMax = size_t.max; + +__gshared NSString NSDefaultRunLoopMode; +extern (D) static this() { + NSDefaultRunLoopMode = "kCFRunLoopDefaultMode".ns; +} + +enum NSEventType : NSUInteger { + LeftMouseDown = 1, + LeftMouseUp = 2, + RightMouseDown = 3, + RightMouseUp = 4, + MouseMoved = 5, + LeftMouseDragged = 6, + RightMouseDragged = 7, + MouseEntered = 8, + MouseExited = 9, + KeyDown = 10, + KeyUp = 11, + FlagsChanged = 12, + ScrollWheel = 22, + TabletPoint = 23, + TabletProximity = 24, + OtherMouseDown = 25, + OtherMouseUp = 26, + OtherMouseDragged = 27 +} + +enum NSEventMask : NSUInteger { + LeftMouseDown = 1 << NSEventType.LeftMouseDown, + LeftMouseUp = 1 << NSEventType.LeftMouseUp, + RightMouseDown = 1 << NSEventType.RightMouseDown, + RightMouseUp = 1 << NSEventType.RightMouseUp, + MouseMoved = 1 << NSEventType.MouseMoved, + LeftMouseDragged = 1 << NSEventType.LeftMouseDragged, + RightMouseDragged = 1 << NSEventType.RightMouseDragged, + MouseEntered = 1 << NSEventType.MouseEntered, + MouseExited = 1 << NSEventType.MouseExited, + KeyDown = 1 << NSEventType.KeyDown, + KeyUp = 1 << NSEventType.KeyUp, + FlagsChanged = 1 << NSEventType.FlagsChanged, + ScrollWheel = 1 << NSEventType.ScrollWheel, + TabletPoint = 1 << NSEventType.TabletPoint, + TabletProximity = 1 << NSEventType.TabletProximity, + OtherMouseDown = 1 << NSEventType.OtherMouseDown, + OtherMouseUp = 1 << NSEventType.OtherMouseUp, + OtherMouseDragged = 1 << NSEventType.OtherMouseDragged, + AppKitDefined = 1 << 13, + SystemDefined = 1 << 14, + AnyEvent = 0xffffffff +} + +class NSEvent { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("type") + NSEventType type(); + + @selector("modifierFlags") + NSEventModifierFlags modifierFlags(); + + @selector("window") + NSWindow window() @nogc nothrow; + + @selector("windowNumber") + NSInteger windowNumber(); + + @selector("keyCode") + ushort keyCode(); + + @selector("isARepeat") + BOOL isARepeat(); + + @selector("characters") + NSString characters(); + + @selector("addLocalMonitorForEventsMatchingMask:handler:") + static NSObject addLocalMonitorForEventsMatchingMask(NSUInteger mask, NSEvent function(NSEvent) handler) @nogc nothrow; + + @selector("charactersIgnoringModifiers") + NSString charactersIgnoringModifiers(); + + @selector("addLocalMonitorForEventsMatchingMask:handler:") + static NSObject addLocalMonitorForEventsMatchingMask(NSEventMask mask, NSEvent function(NSEvent) @nogc nothrow handler); + + @selector("locationInWindow") + CGPoint locationInWindow(); + + @selector("timestamp") + CGFloat timestamp(); + + @selector("deltaX") + CGFloat deltaX(); + + @selector("deltaY") + CGFloat deltaY(); + + @selector("deltaZ") + CGFloat deltaZ(); +} + diff --git a/cocoa/nsmenu.d b/cocoa/nsmenu.d new file mode 100644 index 0000000..b335301 --- /dev/null +++ b/cocoa/nsmenu.d @@ -0,0 +1,49 @@ +module cocoa.nsmenu; + +import objc.runtime; +import objc.meta: SEL; +import cocoa.foundation; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +class NSMenu { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("new") + static NSMenu new_() nothrow @nogc; + + @selector("addItem:") + void addItem(NSMenuItem); +} + +class NSMenuItem { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("initWithTitle:action:keyEquivalent:") + NSMenuItem initWithTitle(NSString title, void* action, NSString keyEquivalent); + + @selector("alloc") + static NSMenuItem alloc() nothrow @nogc; + + @selector("new") + static NSMenuItem new_() nothrow @nogc; + + @selector("init") + NSMenuItem initialize(); + + @selector("setSubmenu:") + void setSubmenu(NSMenu); + + @selector("setTarget:") + void setTarget(NSObject); + + @selector("setAction:") + void setAction(SEL); + + @selector("setKeyEquivalentModifierMask:") + void setKeyEquivalentModifierMask(NSEventModifierFlags); +} + diff --git a/cocoa/nsnotification.d b/cocoa/nsnotification.d new file mode 100644 index 0000000..14cf409 --- /dev/null +++ b/cocoa/nsnotification.d @@ -0,0 +1,19 @@ +module cocoa.nsnotification; + +import objc.runtime; +import objc.meta; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +class NSNotification { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("object") + NSObject object(); + + @selector("userInfo") + NSDictionary userInfo(); +} + diff --git a/cocoa/nsopengl.d b/cocoa/nsopengl.d new file mode 100644 index 0000000..a77e7fc --- /dev/null +++ b/cocoa/nsopengl.d @@ -0,0 +1,68 @@ +module cocoa.nsopengl; + +import objc.runtime; +import cocoa.foundation; +import cocoa.nsview; +import bindbc.opengl; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +enum : int { + NSOpenGLPFADoubleBuffer = 5, + NSOpenGLPFAColorSize = 8, + NSOpenGLPFAAlphaSize = 11, + NSOpenGLPFADepthSize = 12, + NSOpenGLPFAStencilSize = 13 +} + +enum NSOpenGLContextParameter { + NSOpenGLCPSwapInterval = 222, + NSOpenGLCPSurfaceOrder = 235, + NSOpenGLCPSurfaceOpacity = 236, + NSOpenGLCPSurfaceBackingSize = 304, + NSOpenGLCPReclaimResources = 308, + NSOpenGLCPCurrentRendererID = 309, + NSOpenGLCPGPUVertexProcessing = 310, + NSOpenGLCPGPUFragmentProcessing = 311, + NSOpenGLCPHasDrawable = 314, + NSOpenGLCPMPSwapsInFlight = 315, +} + +class NSOpenGLPixelFormat { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("alloc") + static NSOpenGLPixelFormat alloc(); + + @selector("initWithAttributes:") + NSOpenGLPixelFormat initWithAttributes(const(int)*); +} + +class NSOpenGLContext { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("alloc") + static NSOpenGLContext alloc(); + + @selector("initWithFormat:shareContext:") + NSOpenGLContext initWithFormat(NSOpenGLPixelFormat format, NSOpenGLContext shareContext); + + @selector("setView:") + void setView(NSView view); + + @selector("flushBuffer") + void flushBuffer(); + + @selector("makeCurrentContext") + void makeCurrentContext(); + + @selector("setValues:forParameter:") + void setValues(const(int)* vals, NSOpenGLContextParameter param); + + @selector("update") + void update(); +} + diff --git a/cocoa/nsscreen.d b/cocoa/nsscreen.d new file mode 100644 index 0000000..0674608 --- /dev/null +++ b/cocoa/nsscreen.d @@ -0,0 +1,18 @@ +module cocoa.nsscreen; + +import objc.runtime; +import cocoa.foundation; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +class NSScreen { + mixin ObjcExtend!NSObject; + + @selector("mainScreen") + static NSScreen mainScreen() nothrow @nogc; + + @selector("frame") + CGRect frame() nothrow @nogc; +} + diff --git a/cocoa/nstextinput.d b/cocoa/nstextinput.d new file mode 100644 index 0000000..4c33e5f --- /dev/null +++ b/cocoa/nstextinput.d @@ -0,0 +1,18 @@ +module cocoa.nstextinput; + +import objc.runtime; +import cocoa.foundation; + +@ObjectiveC final extern(C++): +@nogc nothrow: + +class NSTextInputContext { + mixin ObjcExtend!NSObject; + + @selector("currentInputContext") + static NSTextInputContext currentInputContext() nothrow @nogc @trusted; + + @selector("selectedKeyboardInputSource") + NSNumber selectedKeyboardInputSource() nothrow @nogc @trusted; +} + diff --git a/cocoa/nsview.d b/cocoa/nsview.d new file mode 100644 index 0000000..3881e92 --- /dev/null +++ b/cocoa/nsview.d @@ -0,0 +1,91 @@ +module cocoa.nsview; + +import cocoa.foundation; +import cocoa.nswindow; + +import objc.runtime; +@ObjectiveC final extern(C++): +@nogc nothrow: + +enum NSViewLayerContentsRedrawPolicy : NSInteger { + Never = 0, + OnSetNeedsDisplay = 1, + DuringViewResize = 2, + BeforeViewResize = 3, + Crossfade = 4 +} + +enum NSViewLayerContentsPlacement : NSInteger { + ScaleAxesIndependently = 0, + ScaleProportionallyToFit = 1, + ScaleProportionallyToFill = 2, + Center = 3, + Top = 4, + TopRight = 5, + Right = 6, + BottomRight = 7, + Bottom = 8, + BottomLeft = 9, + Left = 10, + TopLeft = 11 +} + +enum NSTrackingAreaOptions : NSUInteger { + MouseEnteredAndExited = 0x01, + MouseMoved = 0x02, + CursorUpdate = 0x04, + ActiveWhenFirstResponder = 0x10, + ActiveInKeyWindow = 0x20, + ActiveInActiveApp = 0x40, + ActiveAlways = 0x80, + AssumeInside = 0x100 +} + +class NSTrackingArea { + mixin ObjcExtend!NSObject; + + @selector("alloc") + static NSTrackingArea alloc(); + + @selector("initWithRect:options:owner:userInfo:") + NSTrackingArea initWithRect(CGRect rect, NSTrackingAreaOptions options, NSView owner, NSDictionary userInfo); +} + +class NSView { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("initWithFrame:") + NSView initWithFrame(CGRect frameRect); + + @selector("addSubview:") + void addSubview(NSView view); + + @selector("setWantsLayer:") + void setWantsLayer(BOOL flag); + + @selector("setLayerContentsRedrawPolicy:") + void setLayerContentsRedrawPolicy(NSViewLayerContentsRedrawPolicy policy); + + @selector("frame") + CGRect frame(); + + @selector("setFrame:") + void setFrame(CGRect frame); + + @selector("bounds") + CGRect bounds(); + + @selector("setBounds:") + void setBounds(CGRect bounds); + + @selector("window") + NSWindow window(); + + @selector("setContentView:") + void setContentView(NSView view); + + @selector("addTrackingArea:") + void addTrackingArea(NSTrackingArea area); +} + diff --git a/cocoa/nswindow.d b/cocoa/nswindow.d new file mode 100644 index 0000000..6b1dcb1 --- /dev/null +++ b/cocoa/nswindow.d @@ -0,0 +1,68 @@ +module cocoa.nswindow; + +import cocoa.foundation; +import cocoa.nsview; + +import objc.runtime; +@ObjectiveC final extern(C++): +@nogc nothrow: + +enum NSWindowStyleMask : NSUInteger { + Borderless = 0, + Titled = 1 << 0, + Closable = 1 << 1, + Miniaturizable = 1 << 2, + Resizable = 1 << 3, + TexturedBackground = 1 << 8, + UnifiedTitleAndToolbar = 1 << 12, + FullScreen = 1 << 14, + FullSizeContentView = 1 << 15 +} + +enum NSBackingStoreType : NSUInteger { + Retained = 0, + Nonretained = 1, + Buffered = 2 +} + +class NSWindow { + mixin ObjcExtend!NSObject; + +nothrow @nogc: + @selector("initWithContentRect:styleMask:backing:defer:") + NSWindow initWithContentRect(CGRect contentRect, + NSWindowStyleMask style, + NSBackingStoreType backingStoreType, + BOOL flag); + + @selector("makeKeyAndOrderFront:") + void makeKeyAndOrderFront(NSObject sender); + + @selector("setTitle:") + void setTitle(NSString title); + + @selector("center") + void center(); + + @selector("contentView") + NSView contentView(); + + @selector("setContentView:") + void setContentView(NSView view); + + @selector("frame") + CGRect frame(); + + @selector("setFrame:display:") + void setFrame(CGRect frame, BOOL display); + + @selector("setStyleMask:") + void setStyleMask(NSWindowStyleMask styleMask); + + @selector("toggleFullScreen:") + void toggleFullScreen(NSObject sender); + + @selector("setReleasedWhenClosed:") + void setReleasedWhenClosed(BOOL); +} + diff --git a/cocoa/package.d b/cocoa/package.d new file mode 100644 index 0000000..d438eeb --- /dev/null +++ b/cocoa/package.d @@ -0,0 +1,17 @@ +module cocoa; + +public import cocoa.foundation; +public import cocoa.nsappdelegate; +public import cocoa.nsapplication; +public import cocoa.gamecontroller; +public import cocoa.nscursor; +public import cocoa.nsdate; +public import cocoa.nsevent; +public import cocoa.nsmenu; +public import cocoa.nsnotification; +public import cocoa.nsopengl; +public import cocoa.nsscreen; +public import cocoa.nstextinput; +public import cocoa.nsview; +public import cocoa.nswindow; + diff --git a/dub.sdl b/dub.sdl index 43464ff..cb87c59 100644 --- a/dub.sdl +++ b/dub.sdl @@ -11,21 +11,38 @@ dependency "x11d" version="*" versions "GL_AllowDeprecated" libs "asound" "GL" "X11" "Xext" "Xi" "libevdev" platform="linux" libs "winmm" "gdi32" "user32" "opengl32" "xinput" "ole32" "comctl32" platform="windows" + +//OSX specific +configuration "osx" { + toolchainRequirements ldc=">=1.30.0" platform="osx" + compiler "ldc2" platform="osx" + architecture "aarch64" platform="osx" + dependency "objc_meta" version="*" platform="osx" + dependency "d-metal-binding" version="*" platform="osx" + dependency "avaudioengine" version="*" platform="osx" + sourcePaths "cocoa/" "carbon/" "objc/" "source/" platform="osx" + lflags "-framework" "CoreData" "-framework" "CoreGraphics" "-framework" "Cocoa" "-framework" "Foundation" "-framework" "AppKit" "-framework" "Metal" "-framework" "MetalKit" "-framework" "OpenGL" "-framework" "AVFoundation" "-framework" "GameController" platform="osx" + libs "objc" platform="osx" +} + //lflags "/subsystem:windows" "/entry:wmainCRTStartup" platform="windows" compiler="DMD" subPackage { name "audiotest" + configurations "osx" platform="osx" sourcePaths "testsource/" dependency "iota" version="*" targetType "executable" } subPackage { name "miditest" + configurations "osx" platform="osx" sourcePaths "miditest/" dependency "iota" version="*" targetType "executable" } subPackage { name "inputtest" + configurations "osx" platform="osx" sourcePaths "inputtest/" dependency "iota" version="*" dependency "darg" version="*" @@ -35,8 +52,19 @@ subPackage { } subPackage { name "gltest" + configurations "osx" platform="osx" sourcePaths "gltest/" dependency "iota" version="*" targetType "executable" dflags "resources.res" platform="windows" } +subPackage { + name "mactest" + configurations "osx" platform="osx" + sourcePaths "mactest/" + dependency "objc_meta" version="*" + dependency "bindbc-opengl" version="*" + dependency "d-metal-binding" version="*" + dependency "iota" version="*" + targetType "executable" +} diff --git a/dub.selections.json b/dub.selections.json index 9c16ca9..78c01f9 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -1,13 +1,16 @@ { "fileVersion": 1, "versions": { + "avaudioengine": "1.1.0", "bindbc-loader": "1.1.5", "bindbc-opengl": "1.1.1", "bitleveld": "0.1.0", "collections-d": "0.5.1", + "d-metal-binding": "2.1.0", "darg": "0.1.0", "darg-plus": "2.4.0", "libx11": "0.0.1", + "objc_meta": "1.1.0", "stdx-allocator": "2.77.5", "vibe-d": "0.9.5", "wasapi": "0.1.3", diff --git a/gltest/app.d b/gltest/app.d index 0e59e77..13acbb6 100644 --- a/gltest/app.d +++ b/gltest/app.d @@ -7,6 +7,7 @@ import core.thread; import iota.controls; import iota.controls.keybscancodes; import iota.window; + import iota.controls.polling : poll; version (Windows) { import core.sys.windows.windows; diff --git a/inputtest/app.d b/inputtest/app.d index 92f73e9..5df802b 100644 --- a/inputtest/app.d +++ b/inputtest/app.d @@ -10,6 +10,13 @@ import darg; version (Windows) { import core.sys.windows.windows; + import iota.controls.polling : poll, devList, keyb; +} else version(OSX) { + import cocoa; + import metal; + import iota.controls.polling : poll, keyb; +} else { + import iota.controls.polling : poll, devList, keyb; } struct Options { @@ -39,6 +46,20 @@ immutable help = helpString!Options(); const(ubyte)[] iconData = cast(const(ubyte)[])import("icon"); +void printDeviceList() { + version(OSX) { + auto devList = MTLCopyAllDevices(); + writeln("Number of devices: ", devList.count); + for (NSUInteger i = 0; i < devList.count; i++) { + auto device = cast(MTLDevice)devList.objectAtIndex(i); + writeln(" Device ", i, ": ", device.name.toString); + } + } + else { + writeln(devList); + } +} + int main(string[] args) { //initWindow_ext(); Options options; @@ -71,7 +92,7 @@ int main(string[] args) { writeln("Input initialization error! Code: ", errCode, /* " OSCode: ", GetLastError() */); return 1; } - writeln(devList); + printDeviceList(); bool isRunning = true; keyb.setTextInput(options.textinputtest == 1); while (isRunning) { @@ -159,16 +180,16 @@ int main(string[] args) { break; case ScanCode.F5: checkForNewDevices(); - writeln(devList); + printDeviceList(); break; default: break; } } else if (event.type == InputEventType.DeviceRemoved) { removeInvalidatedDevices(); - writeln(devList); + printDeviceList(); } else if (event.type == InputEventType.DeviceAdded) { checkForNewDevices(); - writeln(devList); + printDeviceList(); } writeln(event.toString()); } diff --git a/mactest/app.d b/mactest/app.d new file mode 100644 index 0000000..59402d5 --- /dev/null +++ b/mactest/app.d @@ -0,0 +1,159 @@ +module mactest; + +version(OSX): + +import objc.meta; +import objc.runtime; +import cocoa; +import metal; + +const float[] vertexData = [ + 0.0, 1.0, 0.0, + -1.0, -1.0, 0.0, + 1.0, -1.0, 0.0 +]; + +enum metalVertexShader = q{ + vertex float4 basic_vertex( + const device packed_float3* vertex_array [[buffer(0)]], + unsigned int vid [[vertex_id]] + ) + { + return float4(vertex_array[vid], 1.0); + } +}; + +enum metalFragmentShader = q{ + fragment half4 basic_fragment() + { + return half4(1.0); + } +}; + +__gshared MTLBuffer vertexBuffer; +__gshared MTLRenderPipelineDescriptor pipelineDescriptor; +__gshared MTLCommandQueue commandQueue; +__gshared MTLRenderPipelineState state; + +enum DefaultPixelFormat = MTLPixelFormat.BGRA8Unorm_sRGB; + +extern(C) void initMetal(MTLDevice device) { + vertexBuffer = device.newBuffer(vertexData.ptr, float.sizeof*vertexData.length, MTLResourceOptions.DefaultCache); + + NSError err; + auto defaultLibrary = device.newLibraryWithSource((metalVertexShader ~ metalFragmentShader).ns, null, &err); + if(err !is null || defaultLibrary is null) { + NSLog("Error compiling shader %@".ns, err); + return; + } + + auto fragmentProgram = defaultLibrary.newFunctionWithName("basic_fragment".ns); + auto vertexProgram = defaultLibrary.newFunctionWithName("basic_vertex".ns); + + auto descriptor = MTLVertexDescriptor.vertexDescriptor; + + enum POSITION = 0; + enum BufferIndexMeshPositions = 0; + descriptor.attributes[POSITION].format = MTLVertexFormat.float3; + descriptor.attributes[POSITION].offset = 0; + descriptor.attributes[POSITION].bufferIndex = BufferIndexMeshPositions; + + descriptor.layouts[POSITION].stepRate = 1; + descriptor.layouts[POSITION].stepFunction = MTLVertexStepFunction.PerVertex; + descriptor.layouts[POSITION].stride = float.sizeof*3; + + pipelineDescriptor = MTLRenderPipelineDescriptor.alloc.initialize; + pipelineDescriptor.label = "TestProgram".ns; + pipelineDescriptor.vertexFunction = vertexProgram; + pipelineDescriptor.fragmentFunction = fragmentProgram; + pipelineDescriptor.vertexDescriptor = descriptor; + pipelineDescriptor.colorAttachments[0].pixelFormat = DefaultPixelFormat; + pipelineDescriptor.depthAttachmentPixelFormat = MTLPixelFormat.Depth32Float_Stencil8; + pipelineDescriptor.stencilAttachmentPixelFormat = MTLPixelFormat.Depth32Float_Stencil8; + + state = device.newRenderPipelineStateWithDescriptor(pipelineDescriptor, &err); + commandQueue = device.newCommandQueue(); +} + +extern(C) void renderMetal(MTKView view) { + auto desc = view.currentRenderPassDescriptor; + if(desc is null) return; + + auto cmdBuffer = commandQueue.commandBuffer(); + cmdBuffer.label = "MyCommand".ns; + + auto encoder = cmdBuffer.renderCommandEncoderWithDescriptor(desc); + encoder.setRenderPipelineState(state); + encoder.setVertexBuffer(vertexBuffer, 0, 0); + encoder.drawPrimitives(MTLPrimitiveType.Triangle, 0, 3); + encoder.endEncoding(); + + cmdBuffer.presentDrawable(view.currentDrawable); + cmdBuffer.commit(); +} + +@ObjectiveC final extern(C++) +class MetalViewDelegate { + mixin ObjcExtend!NSObject; + + static MetalViewDelegate alloc() @selector("alloc") { + return cast(MetalViewDelegate)NSObject.alloc; + } + + extern(C) void drawInMTKView(MTKView view) @selector("drawInMTKView:") { + renderMetal(view); + } +} + +int main(string[] args) { + auto app = NSApp(); + app.setActivationPolicy(NSApplicationActivationPolicy.Regular); + + auto delegate_ = AppDelegate.alloc.initialize(); + app.setDelegate(delegate_); + + auto menubar = NSMenu.new_(); + auto appMenuItem = NSMenuItem.new_(); + menubar.addItem(appMenuItem); + app.setMainMenu(menubar); + + auto appMenu = NSMenu.new_(); + auto quitMenuItem = NSMenuItem.alloc.initWithTitle( + "Quit".ns, + sel_registerName("terminate:"), + "q".ns + ); + quitMenuItem.setTarget(app); + quitMenuItem.setAction(sel_registerName("terminate:")); + appMenu.addItem(quitMenuItem); + appMenuItem.setSubmenu(appMenu); + + auto device = MTLCreateSystemDefaultDevice(); + initMetal(device); + + auto rect = cocoa.foundation.CGRect( + cocoa.foundation.CGPoint(100, 100), + cocoa.foundation.CGSize(800, 600) + ); + + auto window = NSWindow.alloc.initWithContentRect( + rect, + NSWindowStyleMask.Titled | NSWindowStyleMask.Closable | NSWindowStyleMask.Resizable, + NSBackingStoreType.Buffered, + NO + ); + + auto metalView = cast(MTKView)(NSView.alloc.initWithFrame(rect)); + auto viewDelegate = cast(MetalViewDelegate)(NSObject.alloc.initialize()); + window.setContentView(cast(NSView)metalView); + + window.setTitle("Metal Triangle".ns); + window.setReleasedWhenClosed(YES); + window.makeKeyAndOrderFront(null); + + app.activateIgnoringOtherApps(YES); + app.run(); + + return 0; +} + diff --git a/objc/iota_gen.d b/objc/iota_gen.d new file mode 100644 index 0000000..8bf8031 --- /dev/null +++ b/objc/iota_gen.d @@ -0,0 +1,36 @@ +module objc.iota_gen; +import objc.meta; +import objc.runtime; +import metal; + +import cocoa.foundation, + cocoa.nsapplication, + cocoa.nscursor, + cocoa.gamecontroller, + cocoa.nsdate, + cocoa.nsevent, + cocoa.nsopengl, + cocoa.nsscreen, + cocoa.nstextinput, + cocoa.nsview, + cocoa.nsmenu, + cocoa.nsnotification, + cocoa.nsappdelegate, + cocoa.nswindow; + +mixin ObjcLinkModule!(metal); +mixin ObjcLinkModule!(cocoa.foundation); +mixin ObjcLinkModule!(cocoa.nsapplication); +mixin ObjcLinkModule!(cocoa.nscursor); +mixin ObjcLinkModule!(cocoa.nsdate); +mixin ObjcLinkModule!(cocoa.gamecontroller); +mixin ObjcLinkModule!(cocoa.nsevent); +mixin ObjcLinkModule!(cocoa.nsopengl); +mixin ObjcLinkModule!(cocoa.nsscreen); +mixin ObjcLinkModule!(cocoa.nstextinput); +mixin ObjcLinkModule!(cocoa.nsview); +mixin ObjcLinkModule!(cocoa.nsmenu); +mixin ObjcLinkModule!(cocoa.nsnotification); +mixin ObjcLinkModule!(cocoa.nsappdelegate); +mixin ObjcLinkModule!(cocoa.nswindow); +mixin ObjcInitSelectors!(__traits(parent, {})); diff --git a/source/iota/audio/device.d b/source/iota/audio/device.d index 1c721c2..8ce6d3d 100644 --- a/source/iota/audio/device.d +++ b/source/iota/audio/device.d @@ -105,6 +105,8 @@ public string[] getOutputDeviceNames() @trusted { } return result; } else return null; + } else version (OSX) { + return null; } } /** diff --git a/source/iota/audio/output.d b/source/iota/audio/output.d index 10c843b..e163855 100644 --- a/source/iota/audio/output.d +++ b/source/iota/audio/output.d @@ -25,8 +25,12 @@ public abstract class OutputStream { * Returns the thread ID of the stream thread. * Warning: It is not advised to join this thread. */ - public @property ThreadID threadID() @nogc @safe pure nothrow const { - return _threadID; + public @property ThreadID threadID() @nogc @trusted pure nothrow const { + version (OSX) { + return cast(ThreadID)(_threadID); + } else { + return _threadID; + } } /** * Called periodically request more data to device output. diff --git a/source/iota/controls/gamectrl.d b/source/iota/controls/gamectrl.d index 4d13b36..e61c204 100644 --- a/source/iota/controls/gamectrl.d +++ b/source/iota/controls/gamectrl.d @@ -1,12 +1,14 @@ module iota.controls.gamectrl; public import iota.controls.types; -import iota.controls; +// import iota.controls; version (Windows) { import core.sys.windows.windows; import core.sys.windows.wtypes; import iota.controls.backend.windows; +} else version(OSX) { + import cocoa.gamecontroller; } else { import iota.controls.backend.linux; } @@ -140,7 +142,7 @@ public class RawInputGameController : GameController { _type = InputDeviceType.GameController; status |= StatusFlags.IsConnected; } - } else { + } else version (linux) { int[8] hatStatus; package this(string _name, ubyte _devNum, int fd, libevdev* hDevice, RawGCMapping[] mapping) { this._name = _name; @@ -480,4 +482,45 @@ version (Windows) public class XInputDevice : GameController { return HapticDeviceStatus.AllOk; return HapticDeviceStatus.DeviceInvalidated; } +} else version (OSX) { + public class GCGameController : GameController { + protected GCController* controller; + + public override uint[] getCapabilities() @safe nothrow { + if (controller.haptics) + return [HapticDevice.Capabilities.LeftMotor, HapticDevice.Capabilities.RightMotor]; + return null; + } + + public override uint[] getZones(uint capability) @safe nothrow { + return null; + } + + public override int applyEffect(uint capability, uint zone, float val, float freq = float.nan) @trusted nothrow { + if (!controller.haptics) + return HapticDeviceStatus.UnsupportedCapability; + + if (val < 0.0 || val > 1.0) + return HapticDeviceStatus.OutOfRange; + + switch (capability) { + case HapticDevice.Capabilities.LeftMotor: + controller.haptics.createEngine(GCHapticsLocality.Left).createContinuousEvent(val); + return HapticDeviceStatus.AllOk; + case HapticDevice.Capabilities.RightMotor: + controller.haptics.createEngine(GCHapticsLocality.Right).createContinuousEvent(val); + return HapticDeviceStatus.AllOk; + default: + return HapticDeviceStatus.UnsupportedCapability; + } + } + + public override int reset() @trusted nothrow { + if (controller.haptics) { + controller.haptics.cancelAll(); + return HapticDeviceStatus.AllOk; + } + return HapticDeviceStatus.UnsupportedCapability; + } + } } diff --git a/source/iota/controls/keyboard.d b/source/iota/controls/keyboard.d index 6a4f460..660f267 100644 --- a/source/iota/controls/keyboard.d +++ b/source/iota/controls/keyboard.d @@ -5,6 +5,9 @@ public import iota.controls.types; version (Windows) { import core.sys.windows.windows; import core.sys.windows.wtypes; +} else version(OSX) { + import cocoa.foundation; + import cocoa.nsevent; } else { import x11.extensions.XI; import x11.extensions.XInput; @@ -62,6 +65,9 @@ public class Keyboard : InputDevice { _type = InputDeviceType.Keyboard; status |= StatusFlags.IsConnected; } + } else version (OSX) { + package uint modifierTracker; + package bool ignoreLockLights; } else { //package XDevice* devHandle; @@ -194,6 +200,8 @@ public class Keyboard : InputDevice { if (GetKeyState(VK_SCROLL)) result |= KeyboardModifiers.ScrollLock; } + } else version (OSX) { + return cast(ubyte)modifierTracker; } else { if (modifierTracker & ShiftMask) result |= KeyboardModifiers.Shift; @@ -217,9 +225,23 @@ public class Keyboard : InputDevice { return result; } ///Sets the modifier flags for X11 input tracking. - package void setModifiers(uint flags) @nogc @safe pure nothrow { + package void setModifiers(uint flags) @nogc @safe nothrow { version (Windows) { + } else version (OSX) { + modifierTracker = 0; + if (flags & NSEventModifierFlags.ShiftKeyMask) + modifierTracker |= KeyboardModifiers.Shift; + if (flags & NSEventModifierFlags.ControlKeyMask) + modifierTracker |= KeyboardModifiers.Ctrl; + if (flags & NSEventModifierFlags.AlternateKeyMask) + modifierTracker |= KeyboardModifiers.Alt; + if (flags & NSEventModifierFlags.CommandKeyMask) + modifierTracker |= KeyboardModifiers.Meta; + if (!isIgnoringLockLights()) { + if (flags & NSEventModifierFlags.AlphaShiftKeyMask) + modifierTracker |= KeyboardModifiers.CapsLock; + } } else { modifierTracker = flags; } @@ -298,6 +320,10 @@ public class Keyboard : InputDevice { } public override string toString() @safe const { import std.conv : to; - return "{" ~ _name.to!string ~ "; devID: " ~ _devNum.to!string ~ "; devHandle: " ~ hDevice.to!string ~ "}"; + version (OSX) { + return "{" ~ _name.to!string ~ "; devID: " ~ _devNum.to!string ~ "}"; + } else { + return "{" ~ _name.to!string ~ "; devID: " ~ _devNum.to!string ~ "; devHandle: " ~ hDevice.to!string ~ "}"; + } } } \ No newline at end of file diff --git a/source/iota/controls/keybscancodes.d b/source/iota/controls/keybscancodes.d index e088f52..802e1db 100644 --- a/source/iota/controls/keybscancodes.d +++ b/source/iota/controls/keybscancodes.d @@ -453,6 +453,131 @@ version(Windows) package uint translateSC(uint input, uint aux) @nogc @safe pure default: return 0xFF_FF; } +} else version(OSX) { + package ushort translateKeyCode(const uint code) @nogc @safe pure nothrow { + switch(code) { + case 0x00: return ScanCode.A; + case 0x0B: return ScanCode.B; + case 0x08: return ScanCode.C; + case 0x02: return ScanCode.D; + case 0x0E: return ScanCode.E; + case 0x03: return ScanCode.F; + case 0x05: return ScanCode.G; + case 0x04: return ScanCode.H; + case 0x22: return ScanCode.I; + case 0x26: return ScanCode.J; + case 0x28: return ScanCode.K; + case 0x25: return ScanCode.L; + case 0x2E: return ScanCode.M; + case 0x2D: return ScanCode.N; + case 0x1F: return ScanCode.O; + case 0x23: return ScanCode.P; + case 0x0C: return ScanCode.Q; + case 0x0F: return ScanCode.R; + case 0x01: return ScanCode.S; + case 0x11: return ScanCode.T; + case 0x20: return ScanCode.U; + case 0x09: return ScanCode.V; + case 0x0D: return ScanCode.W; + case 0x07: return ScanCode.X; + case 0x10: return ScanCode.Y; + case 0x06: return ScanCode.Z; + + case 0x12: return ScanCode.n1; + case 0x13: return ScanCode.n2; + case 0x14: return ScanCode.n3; + case 0x15: return ScanCode.n4; + case 0x17: return ScanCode.n5; + case 0x16: return ScanCode.n6; + case 0x1A: return ScanCode.n7; + case 0x1C: return ScanCode.n8; + case 0x19: return ScanCode.n9; + case 0x1D: return ScanCode.n0; + + case 0x24: return ScanCode.ENTER; + case 0x35: return ScanCode.ESCAPE; + case 0x33: return ScanCode.BACKSPACE; + case 0x30: return ScanCode.TAB; + case 0x31: return ScanCode.SPACE; + + case 0x1B: return ScanCode.MINUS; + case 0x18: return ScanCode.EQUALS; + case 0x21: return ScanCode.LEFTBRACKET; + case 0x1E: return ScanCode.RIGHTBRACKET; + case 0x2A: return ScanCode.BACKSLASH; + case 0x29: return ScanCode.SEMICOLON; + case 0x27: return ScanCode.APOSTROPHE; + case 0x32: return ScanCode.GRAVE; + case 0x2B: return ScanCode.COMMA; + case 0x2F: return ScanCode.PERIOD; + case 0x2C: return ScanCode.SLASH; + case 0x39: return ScanCode.CAPSLOCK; + + case 0x7A: return ScanCode.F1; + case 0x78: return ScanCode.F2; + case 0x63: return ScanCode.F3; + case 0x76: return ScanCode.F4; + case 0x60: return ScanCode.F5; + case 0x61: return ScanCode.F6; + case 0x62: return ScanCode.F7; + case 0x64: return ScanCode.F8; + case 0x65: return ScanCode.F9; + case 0x6D: return ScanCode.F10; + case 0x67: return ScanCode.F11; + case 0x6F: return ScanCode.F12; + + case 0x69: return ScanCode.F13; + case 0x6B: return ScanCode.F14; + case 0x71: return ScanCode.F15; + case 0x6A: return ScanCode.F16; + case 0x40: return ScanCode.F17; + case 0x4F: return ScanCode.F18; + case 0x50: return ScanCode.F19; + case 0x5A: return ScanCode.F20; + + case 0x72: return ScanCode.INSERT; + case 0x73: return ScanCode.HOME; + case 0x74: return ScanCode.PAGEUP; + case 0x75: return ScanCode.DELETE; + case 0x77: return ScanCode.END; + case 0x79: return ScanCode.PAGEDOWN; + case 0x7C: return ScanCode.RIGHT; + case 0x7B: return ScanCode.LEFT; + case 0x7D: return ScanCode.DOWN; + case 0x7E: return ScanCode.UP; + + case 0x47: return ScanCode.NUMLOCK; + case 0x4B: return ScanCode.NP_DIVIDE; + case 0x43: return ScanCode.NP_MULTIPLY; + case 0x4E: return ScanCode.NP_MINUS; + case 0x45: return ScanCode.NP_PLUS; + case 0x4C: return ScanCode.NP_ENTER; + + case 0x53: return ScanCode.np1; + case 0x54: return ScanCode.np2; + case 0x55: return ScanCode.np3; + case 0x56: return ScanCode.np4; + case 0x57: return ScanCode.np5; + case 0x58: return ScanCode.np6; + case 0x59: return ScanCode.np7; + case 0x5B: return ScanCode.np8; + case 0x5C: return ScanCode.np9; + case 0x52: return ScanCode.np0; + + case 0x41: return ScanCode.NP_PERIOD; + + case 0x3B: return ScanCode.LCTRL; + case 0x38: return ScanCode.LSHIFT; + case 0x3A: return ScanCode.LALT; + case 0x37: return ScanCode.LGUI; + case 0x3E: return ScanCode.RCTRL; + case 0x3C: return ScanCode.RSHIFT; + case 0x3D: return ScanCode.RALT; + case 0x36: return ScanCode.RGUI; + + default: return ScanCode.init; + } + } } else { ///Translates KeySyms to USB HID ScanCodes ///(Temporary, only use if other methods fail) diff --git a/source/iota/controls/mouse.d b/source/iota/controls/mouse.d index 3a5369a..e0eb7a9 100644 --- a/source/iota/controls/mouse.d +++ b/source/iota/controls/mouse.d @@ -5,6 +5,9 @@ public import iota.controls.types; version (Windows) { import core.sys.windows.windows; import core.sys.windows.wtypes; +} else version(OSX) { + import cocoa.foundation; + import cocoa.nsevent; } else { import x11.X; import x11.Xlib; @@ -47,6 +50,13 @@ public class Mouse : InputDevice { _type = InputDeviceType.Mouse; status |= StatusFlags.IsConnected; } + } else version(OSX) { + package this(string _name, ubyte _devNum) nothrow { + this._name = _name; + this._devNum = _devNum; + _type = InputDeviceType.Mouse; + status |= StatusFlags.IsConnected; + } } else { /* package XDevice* devHandle; package this(string _name, ubyte _devNum, XID devID) nothrow { @@ -86,7 +96,11 @@ public class Mouse : InputDevice { public override string toString() @safe const { import std.conv : to; - return "{" ~ _name.to!string ~ "; devID: " ~ _devNum.to!string ~ "; devHandle: " ~ hDevice.to!string ~ "}"; + version (OSX) { + return "{" ~ _name.to!string ~ "; devID: " ~ _devNum.to!string ~ "}"; + } else { + return "{" ~ _name.to!string ~ "; devID: " ~ _devNum.to!string ~ "; devHandle: " ~ hDevice.to!string ~ "}"; + } } /* override public int poll(ref InputEvent output) @nogc nothrow { return 0; diff --git a/source/iota/controls/package.d b/source/iota/controls/package.d index d478873..ec38b34 100644 --- a/source/iota/controls/package.d +++ b/source/iota/controls/package.d @@ -7,15 +7,19 @@ public import iota.controls.keyboard; public import iota.controls.mouse; public import iota.controls.system; public import iota.controls.gamectrl; -public import iota.controls.polling; public import iota.window.oswindow; version (Windows) { import core.sys.windows.windows; import core.sys.windows.wtypes; + import iota.controls.polling; +} else version (OSX) { + import cocoa.foundation; + import iota.controls.polling : poll_osx, mainPollingFun, keyb, mouse, sys, devList; } else { import iota.controls.backend.linux; import core.stdc.errno; + import iota.controls.polling; //import core.sys.linux.fcntl; } import core.stdc.stdlib; @@ -26,6 +30,17 @@ import std.utf; import std.uni : toLower; import std.stdio; +static this() { + version(Windows) { + mainPollingFun = &poll_win_LegacyIO; + } else version(OSX) { + mainPollingFun = &poll_osx; + } else { + mainPollingFun = &poll_x11_RegularIO; + } +} + + public enum IOTA_INPUTCONFIG_MANDATORY = 0; package uint __configFlags, __osConfigFlags; /** @@ -143,6 +158,12 @@ public int initInput(uint config = 0, uint osConfig = 0, string gcmTable = null) subPollingFun = &XInputDevice.poll; } } + } else version(OSX) { + keyb = new Keyboard(); + mouse = new Mouse(); + mainPollingFun = &poll_osx; + devList ~= keyb; + devList ~= mouse; } else { if (osConfig & OSConfigFlags.libevdev_enable) { try { @@ -215,6 +236,7 @@ public int initInput(uint config = 0, uint osConfig = 0, string gcmTable = null) } version (Windows) { package const RawGCMapping[] defaultGCmapping = []; +} else version (OSX) { } else { package const RawGCMapping[] defaultGCmapping = [ RawGCMapping(RawGCMappingType.Button, 0, 0, GameControllerButtons.South), diff --git a/source/iota/controls/polling.d b/source/iota/controls/polling.d index a3ba85d..284bd02 100644 --- a/source/iota/controls/polling.d +++ b/source/iota/controls/polling.d @@ -470,6 +470,172 @@ version (Windows) { } return 1; } +} else version(OSX) { + import std.stdio; + import cocoa; + import objc.meta; + import objc.runtime; + import iota.window.oswindow; + + private OSWindow currentWindow; + public void setCurrentWindow(OSWindow window) @nogc nothrow { + currentWindow = window; + } + + private __gshared bool[256] keyHeldState; + private __gshared ubyte[256] keyRepeatState; + private __gshared NSObject eventMonitor; + + private extern(C++) NSEvent eventHandler(NSEvent event) @objc nothrow @nogc { + const ushort kc = event.keyCode(); + if (kc < 256) { + if (event.type() == NSEventType.KeyDown) { + if (!keyHeldState[kc]) { + keyHeldState[kc] = true; + keyRepeatState[kc] = 1; + } else { + keyRepeatState[kc] = (keyRepeatState[kc] < 255) ? cast(ubyte)(keyRepeatState[kc] + 1) : 255; + } + debug writefln("Monitor: Key %d down, held=%d, repeat=%d", kc, keyHeldState[kc], keyRepeatState[kc]); + } + } + return event; + } + + package int poll_osx(ref InputEvent output) nothrow @nogc @system { + NSEvent event = NSApplication.sharedApplication.nextEventMatchingMask( + NSEventMask.KeyDown | NSEventMask.KeyUp | NSEventMask.FlagsChanged | + NSEventMask.LeftMouseDown | NSEventMask.LeftMouseUp | + NSEventMask.RightMouseDown | NSEventMask.RightMouseUp | + NSEventMask.OtherMouseDown | NSEventMask.OtherMouseUp | + NSEventMask.ScrollWheel | + NSEventMask.MouseMoved | NSEventMask.MouseEntered | NSEventMask.MouseExited | + NSEventMask.AppKitDefined | NSEventMask.SystemDefined, + NSDate.distantPast(), + "kCFRunLoopDefaultMode".ns, + YES + ); + + if (event is null) return 0; + + output.handle = event.window; + output.timestamp = cast(long)(event.timestamp() * 1_000_000); + + NSEventType eventType = event.type(); + + if (eventType == NSEventType.FlagsChanged) { + output.type = InputEventType.Keyboard; + output.source = keyb; + + NSEventModifierFlags flags = event.modifierFlags(); + + void handleModifierKey(NSEventModifierFlags flags, NSEventModifierFlags mask, uint scanCode) { + bool isPressed = (flags & mask) != 0; + output.button.id = scanCode; + output.button.dir = isPressed ? 1 : 0; + output.button.repeat = isPressed ? 1 : 0; + } + + handleModifierKey(flags, NSEventModifierFlags.ShiftKeyMask, ScanCode.LSHIFT); + handleModifierKey(flags, NSEventModifierFlags.ControlKeyMask, ScanCode.LCTRL); + handleModifierKey(flags, NSEventModifierFlags.AlternateKeyMask, ScanCode.LALT); + handleModifierKey(flags, NSEventModifierFlags.CommandKeyMask, ScanCode.LGUI); + + output.button.aux = cast(ushort)flags; + NSApplication.sharedApplication.sendEvent(event); + return 1; + } + + if (eventType == NSEventType.KeyDown || eventType == NSEventType.KeyUp) { + output.type = InputEventType.Keyboard; + output.source = keyb; + const ushort kc = translateKeyCode(event.keyCode()); + output.button.id = kc; + + if (kc < 256) { + bool isHeld = CGEventSourceKeyState(0, kc); // 0 = kCGEventSourceStateHIDSystemState + output.button.dir = isHeld ? 1 : 0; + if (isHeld) { + keyRepeatState[kc] = cast(ubyte)(keyRepeatState[kc] < 255 ? keyRepeatState[kc] + 1 : 255); + } else { + keyRepeatState[kc] = 0; + } + output.button.repeat = keyRepeatState[kc]; + } + + output.button.aux = cast(ushort)event.modifierFlags(); + NSApplication.sharedApplication.sendEvent(event); + return 1; + } + + // Only process mouse events if they occur in our window + if (currentWindow !is null && event.window() == currentWindow.getNSWindow()) { + switch (eventType) { + case NSEventType.ScrollWheel: + output.type = InputEventType.MouseScroll; + output.source = mouse; + output.mouseSE.xS = cast(short)(event.deltaX() * 127); + output.mouseSE.yS = cast(short)(event.deltaY() * 127); + auto loc = event.locationInWindow(); + output.mouseSE.x = cast(int)loc.x; + output.mouseSE.y = cast(int)loc.y; + break; + + case NSEventType.LeftMouseDown: + case NSEventType.RightMouseDown: + case NSEventType.OtherMouseDown: + output.type = InputEventType.MouseClick; + output.source = mouse; + output.mouseCE.dir = 1; + output.mouseCE.button = eventType == NSEventType.LeftMouseDown ? 1 : + eventType == NSEventType.RightMouseDown ? 2 : 3; + auto loc = event.locationInWindow(); + output.mouseCE.x = cast(int)loc.x; + output.mouseCE.y = cast(int)loc.y; + break; + + case NSEventType.LeftMouseUp: + case NSEventType.RightMouseUp: + case NSEventType.OtherMouseUp: + output.type = InputEventType.MouseClick; + output.source = mouse; + output.mouseCE.dir = 0; + output.mouseCE.button = eventType == NSEventType.LeftMouseUp ? 1 : + eventType == NSEventType.RightMouseUp ? 2 : 3; + auto loc = event.locationInWindow(); + output.mouseCE.x = cast(int)loc.x; + output.mouseCE.y = cast(int)loc.y; + break; + + case NSEventType.MouseMoved: + output.type = InputEventType.MouseMove; + output.source = mouse; + auto loc = event.locationInWindow(); + output.mouseME.x = cast(int)loc.x; + output.mouseME.y = cast(int)loc.y; + output.mouseME.xD = cast(int)event.deltaX(); + output.mouseME.yD = cast(int)event.deltaY(); + break; + default: + break; + } + } + + NSApp.sendEvent(event); + return 0; + } + + void setEventMonitor() { + eventMonitor = NSEvent.addLocalMonitorForEventsMatchingMask( + NSEventMask.KeyDown | NSEventMask.KeyUp | NSEventMask.FlagsChanged, + &eventHandler + ); + } + + static this() { + mainPollingFun = &poll_osx; + } + } else { import x11.X; diff --git a/source/iota/controls/system.d b/source/iota/controls/system.d index b862713..b60b1b2 100644 --- a/source/iota/controls/system.d +++ b/source/iota/controls/system.d @@ -9,6 +9,9 @@ version (Windows) { import std.utf; import std.stdio; import iota.controls.keybscancodes : translateSC, translatePS2MC; +} else version (OSX) { + import cocoa.nsscreen; + import cocoa.foundation; } else { import x11.X; import x11.Xlib; @@ -38,11 +41,21 @@ public class System : InputDevice { Win_RawInput = 1 << 8, } package this(uint config = 0, uint osConfig = 0) nothrow { - version (Windows) { - - screenSize = [GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CXVIRTUALSCREEN), - GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)]; - } + version (Windows) { + screenSize = [GetSystemMetrics(SM_CXVIRTUALSCREEN), GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)]; + } else version(OSX) { + NSScreen mainScreen = NSScreen.mainScreen(); + if (mainScreen !is null) { + CGRect frame = mainScreen.frame(); + screenSize = [ + cast(int)frame.size.width, + cast(int)frame.size.height, + cast(int)frame.size.width, + cast(int)frame.size.height + ]; + } + } } } diff --git a/source/iota/controls/types.d b/source/iota/controls/types.d index dd62e99..8f55b8f 100644 --- a/source/iota/controls/types.d +++ b/source/iota/controls/types.d @@ -9,7 +9,7 @@ public import core.time : MonoTime; version (Windows) { import core.sys.windows.wtypes; import core.sys.windows.windows; -} else { +} else version (linux) { import iota.controls.backend.linux; } @@ -66,6 +66,13 @@ public enum InputEventType { InputLangChange, ApplExit, Debug_DataDump, ///RawInput data dump + // MacOS specific + GestureBegin = 32, + GestureEnd = 33, + GestureZoom = 34, + GestureRotate = 35, + GestureSwipe = 36, + ForceTouchPressure = 37 } /** * Defines text command event types. @@ -178,7 +185,7 @@ public abstract class InputDevice { protected InputDeviceType _type; /// Defines the type of the input device version (Windows) { package void* hDevice; /// Field for RawInput - } else { + } else version (linux) { package libevdev* hDevice; /// Field for evdev package int fd; } @@ -488,9 +495,13 @@ public struct InputEvent { ArbPtrEvent arbPtr; uint[5] rawData; } + string toString() @trusted { + // The cast to void* is required for OSX. It will be more readable if we don't use version'ed + // code here. Leave as-is, unless it somehow causes problems on Windows or Linux? string result = "Source: " ~ (source is null ? "null" : "{" ~ source.toString ~ "}") ~ " ; Window handle: " ~ - to!string(cast(size_t)handle) ~ " ; Timestamp: " ~ to!string(timestamp) ~ " ; Type: " ~ to!string(type) ~ + to!string(cast(size_t)cast(void*)handle) ~ " ; Timestamp: " ~ + to!string(timestamp) ~ " ; Type: " ~ to!string(type) ~ " ; Rest: {"; switch (type) { case InputEventType.Keyboard, InputEventType.GCButton: diff --git a/source/iota/window/fbdev.d b/source/iota/window/fbdev.d index 02bede6..2f9f58e 100644 --- a/source/iota/window/fbdev.d +++ b/source/iota/window/fbdev.d @@ -6,6 +6,9 @@ version (Windows) { import core.sys.windows.wingdi; import std.utf : toUTF16z; import std.conv : to; +} else version(OSX) { + import cocoa.nsopengl; + import cocoa.nsview; } import std.math : nextPow2; @@ -79,6 +82,8 @@ public class OpenGLRenderer : FrameBufferRenderer { ]; version (Windows) { protected HGLRC renderingContext; + } else version (OSX) { + protected NSOpenGLContext renderingContext; } public static int initGL() { loadOpenGL(); @@ -93,6 +98,23 @@ public class OpenGLRenderer : FrameBufferRenderer { //renderingSurface = window; HDC hdc = GetDC(window); renderingContext = wglCreateContext(hdc); + } else version (OSX) { + int[] attrs = [ + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADepthSize, 24, + NSOpenGLPFAStencilSize, 8, + 0 + ]; + + NSOpenGLPixelFormat format = NSOpenGLPixelFormat.alloc.initWithAttributes(attrs.ptr); + renderingContext = NSOpenGLContext.alloc.initWithFormat(format, null); + renderingContext.setView(cast(NSView)window.contentView()); + + glGenTextures(1, &textureID); + glGenBuffers(1, &vbo); + glBufferData(GL_ARRAY_BUFFER, verticles.length, verticles.ptr, GL_STATIC_DRAW); } glGenTextures(1, &textureID); //glGenBuffers(1, &frameBufferID); @@ -102,6 +124,10 @@ public class OpenGLRenderer : FrameBufferRenderer { ~this() { version (Windows) { wglDeleteContext(renderingContext); + } else version (OSX) { + if (renderingContext !is null) { + renderingContext = null; + } } } diff --git a/source/iota/window/oswindow.d b/source/iota/window/oswindow.d index 249d481..ddce35c 100644 --- a/source/iota/window/oswindow.d +++ b/source/iota/window/oswindow.d @@ -12,7 +12,18 @@ version (Windows) { extern (Windows) nothrow @nogc { void SetThemeAppProperties(DWORD dwFlags); } */ - +} else version(OSX) { + import cocoa; + import objc.meta; + import objc.runtime; + import bindbc.opengl; + import iota.controls.polling : setEventMonitor; + struct Display { + int width; + int height; + } + + alias inputContext = void*; // For OSX compatibility with the existing interface } else { import x11.Xlib; import x11.X; @@ -167,7 +178,37 @@ public class OSWindow { } while (deviceResult); } } - } else { + } else version (OSX) { + protected NSWindow nsWindow; + protected NSView contentView; + private NSOpenGLContext glContext; + private NSOpenGLPixelFormat pixelFormat; + + @property Display mainDisplay() { + Display disp; + NSScreen screen = NSScreen.mainScreen(); + CGRect frame = screen.frame(); + disp.width = cast(int)frame.size.width; + disp.height = cast(int)frame.size.height; + return disp; + } + + @property inputContext ic() { + return null; // Temporary stub for OSX + } + + public NSWindow getNSWindow() @nogc nothrow { + return nsWindow; + } + + shared static this() { + // Initialize NSApplication + auto app = NSApplication.sharedApplication; + app.setActivationPolicy(NSApplicationActivationPolicy.Regular); + app.activateIgnoringOtherApps(YES); + setEventMonitor(); + } + } else version (linux) { public static Atom WM_DELETE_WINDOW; public static Display* mainDisplay; public static Window root; @@ -374,6 +415,64 @@ public class OSWindow { //SetLayeredWindowAttributes(windowHandle, 0, 0xFF, LWA_ALPHA); ShowWindow(windowHandle, SW_RESTORE); UpdateWindow(windowHandle); + } else version(OSX) { + CGRect frame = CGRect(CGPoint(x, y), CGSize(w, h)); + + auto app = NSApp(); + app.setActivationPolicy(NSApplicationActivationPolicy.Regular); + + auto delegate_ = AppDelegate.alloc.initialize(); + NSApp.setDelegate(delegate_); + + // Create menubar and app menu + NSMenu menubar = NSMenu.new_(); + NSMenuItem appMenuItem = NSMenuItem.new_(); + menubar.addItem(appMenuItem); + NSApp.setMainMenu(menubar); + + NSMenu appMenu = NSMenu.new_(); + NSMenuItem quitMenuItem = NSMenuItem.alloc() + .initWithTitle("Quit".ns, sel_registerName("terminate:"), "q".ns); + quitMenuItem.setTarget(app); + quitMenuItem.setAction(sel_registerName("terminate:")); + appMenu.addItem(quitMenuItem); + appMenuItem.setSubmenu(appMenu); + + // Create and set up window + nsWindow = NSWindow.alloc.initWithContentRect( + frame, + NSWindowStyleMask.Titled | NSWindowStyleMask.Closable | + NSWindowStyleMask.Miniaturizable | NSWindowStyleMask.Resizable, + NSBackingStoreType.Buffered, + NO + ); + + nsWindow.setTitle(title.ns); + nsWindow.center(); + + // contentView = NSView.alloc.initWithFrame(frame); + // nsWindow.setContentView(contentView); + + // After contentView creation + NSTrackingAreaOptions options = NSTrackingAreaOptions.ActiveAlways | + NSTrackingAreaOptions.MouseMoved | + NSTrackingAreaOptions.MouseEnteredAndExited; + NSTrackingArea trackingArea = NSTrackingArea.alloc.initWithRect( + contentView.bounds(), + options, + contentView, + null + ); + contentView.addTrackingArea(trackingArea); + + + // Set up window and activation + import iota.controls.polling : setCurrentWindow; + nsWindow.makeKeyAndOrderFront(null); + NSApp.activateIgnoringOtherApps(YES); + setCurrentWindow(this); + + refCount ~= this; } else { string nameUTF8 = toUTF8(title); windowname = toStringz(nameUTF8); @@ -407,32 +506,41 @@ public class OSWindow { } ~this() { - version (Windows) { - if (glRenderingContext) wglDeleteContext(glRenderingContext); - DestroyWindow(windowHandle); - UnregisterClassW(classname, mainInst); + version(Windows) { + if(glRenderingContext) wglDeleteContext(glRenderingContext); + DestroyWindow(windowHandle); + UnregisterClassW(classname, mainInst); DestroyIcon(hIcon); - } else { - if (glxContext) { - glXMakeCurrent(mainDisplay, None, null); - glXDestroyContext(mainDisplay, glxContext); + } else version(OSX) { + if(nsWindow) { + nsWindow.release(); + nsWindow = null; + } + if(contentView) { + contentView.release(); + contentView = null; + } + } else { + if(glxContext) { + glXMakeCurrent(mainDisplay, None, null); + glXDestroyContext(mainDisplay, glxContext); glxContext = null; - } - if (ic) { + } + if(ic) { XDestroyIC(ic); ic = null; } - if (im) { + if(im) { XCloseIM(im); im = null; } XSync(mainDisplay, False); if (windowHandle) { - XDestroyWindow(mainDisplay, windowHandle); + XDestroyWindow(mainDisplay, windowHandle); windowHandle = 0; } XFlush(mainDisplay); - } + } } /** * Compares two classes to each other. @@ -448,7 +556,11 @@ public class OSWindow { } ///Just to make the scanner happy... public override size_t toHash() const @nogc @safe pure nothrow { - return cast(size_t)windowHandle; + version (OSX) { + return 0xf456; // Just random number, otherwise we get and error on OSX + } else { + return cast(size_t)windowHandle; + } } /** * Returns the handle of this window. @@ -487,29 +599,36 @@ public class OSWindow { * w = Width of the active area of the window. * h = Height of the active area of the window. */ - public void moveWindow(int x, int y, int w, int h) @nogc nothrow @trusted { - version (Windows) { - MoveWindow(windowHandle, x, y, w, h, TRUE); - } else { - XMoveResizeWindow(mainDisplay, windowHandle, x, y, w, h); - } - } + public void moveWindow(int x, int y, int w, int h) @nogc nothrow @trusted { + version(Windows) { + MoveWindow(windowHandle, x, y, w, h, TRUE); + } else version(OSX) { + CGRect frame = CGRect(CGPoint(x, y), CGSize(w, h)); + nsWindow.setFrame(frame, YES); + } else { + XMoveResizeWindow(mainDisplay, windowHandle, x, y, w, h); + } + } /** * Returns an array, of which the first two elements are the top-left coordinates * of the window, and the last two elements are the size of the active area of the * window. */ - public int[4] getWindowPosition() @nogc nothrow @trusted { - version (Windows) { - RECT windowSize; - GetWindowRect(windowHandle, &windowSize); - return [windowSize.left, windowSize.top, windowSize.right - windowSize.left + 1, - windowSize.bottom - windowSize.top + 1]; - } else { - XWindowAttributes winattr; - XGetWindowAttributes(mainDisplay, windowHandle, &winattr); - return [winattr.x, winattr.y, winattr.width, winattr.height]; - } + public int[4] getWindowPosition() @nogc nothrow @trusted { + version(Windows) { + RECT windowSize; + GetWindowRect(windowHandle, &windowSize); + return [windowSize.left, windowSize.top, windowSize.right - windowSize.left + 1, + windowSize.bottom - windowSize.top + 1]; + } else version(OSX) { + CGRect frame = nsWindow.frame(); + return [cast(int)frame.origin.x, cast(int)frame.origin.y, + cast(int)frame.size.width, cast(int)frame.size.height]; + } else { + XWindowAttributes winattr; + XGetWindowAttributes(mainDisplay, windowHandle, &winattr); + return [winattr.x, winattr.y, winattr.width, winattr.height]; + } } /** * Sets the window to the given screen mode. @@ -560,6 +679,23 @@ public class OSWindow { break; } return cast(int)errorcode; + } else version (OSX) { + switch(mode) { + case DisplayMode.Windowed: + nsWindow.setStyleMask(NSWindowStyleMask.Titled | NSWindowStyleMask.Closable | + NSWindowStyleMask.Miniaturizable | NSWindowStyleMask.Resizable); + nsWindow.setFrame(CGRect(CGPoint(prevVals[0], prevVals[1]), + CGSize(prevVals[2], prevVals[3])), YES); + return 0; + + case DisplayMode.FullscreenDesktop: + prevVals = getWindowPosition(); + nsWindow.toggleFullScreen(null); + return 0; + + default: + return 1; + } } else { Atom windowType = XInternAtom (mainDisplay, "_NET_WM_WINDOW_TYPE_NORMAL", True); XEvent ev; @@ -731,6 +867,51 @@ public class OSWindow { /* SetCursor(winCursor); SetClassLongW(windowHandle, GCL_HCURSOR, cast(DWORD)winCursor); ShowCursor(TRUE); */ + } else version(OSX) { + NSCursor systemCursor; + final switch(cursor) with(StandardCursors) { + case Arrow: + NSCursor.arrowCursor().set(); break; + case TextSelect: + NSCursor.IBeamCursor().set(); break; + case Busy: + case WaitArrow: + NSCursor.operationNotAllowedCursor().set(); break; + case PrecisionSelect: + NSCursor.crosshairCursor().set(); break; + case AltSelect: + case HelpSelect: + NSCursor.contextualMenuCursor().set(); break; + case ResizeTopLeft: + NSCursor.windowResizeNorthWestSouthEastCursor().set(); break; + case ResizeTopRight: + NSCursor.windowResizeNorthEastSouthWestCursor().set(); break; + case ResizeBottomLeft: + NSCursor.windowResizeNorthWestSouthEastCursor().set(); break; + case ResizeBottomRight: + NSCursor.windowResizeNorthEastSouthWestCursor().set(); break; + case ResizeLeft: + NSCursor.resizeLeftCursor().set(); break; + case ResizeRight: + NSCursor.resizeRightCursor().set(); break; + case ResizeHoriz: + NSCursor.resizeLeftRightCursor().set(); break; + case ResizeTop: + NSCursor.resizeUpCursor().set(); break; + case ResizeBottom: + NSCursor.resizeDownCursor().set(); break; + case ResizeVert: + NSCursor.resizeUpDownCursor().set(); break; + case Move: + NSCursor.openHandCursor().set(); break; + case Forbidden: + NSCursor.operationNotAllowedCursor().set(); break; + case Hand: + case LocationSelect: + case PersonSelect: + NSCursor.pointingHandCursor().set(); break; + } + systemCursor.set(); } else { Cursor x11_cursor; final switch (cursor) with (StandardCursors) { @@ -822,6 +1003,29 @@ public class OSWindow { glRenderingContext = wglCreateContext(windowDeviceContext); wglMakeCurrent(windowDeviceContext, glRenderingContext); return glRenderingContext; + } else version(OSX) { + if (!glContext) { + static immutable int[] attrs = [ + NSOpenGLPFADoubleBuffer, + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADepthSize, 24, + NSOpenGLPFAStencilSize, 8, + 0 + ]; + + pixelFormat = NSOpenGLPixelFormat.alloc.initWithAttributes(attrs.ptr); + glContext = NSOpenGLContext.alloc.initWithFormat(pixelFormat, null); + + const int swapInterval = 1; + glContext.setValues(&swapInterval, NSOpenGLContextParameter.NSOpenGLCPSwapInterval); + + NSView contentView = getNSWindow().contentView(); + glContext.setView(contentView); + } + + glContext.makeCurrentContext(); + return cast(void*)glContext; } else { if (glxContext) return glxContext; glxContext = glXCreateContext(mainDisplay, vInfo, null, GL_TRUE); @@ -830,23 +1034,47 @@ public class OSWindow { } } public void gl_swapBuffers() @nogc nothrow @trusted { - version (Windows) SwapBuffers(windowDeviceContext); - else glXSwapBuffers(mainDisplay, windowHandle); + version (Windows) { + SwapBuffers(windowDeviceContext); + } else version(OSX) { + (cast(NSOpenGLContext)getOpenGLHandle()).flushBuffer(); + } else glXSwapBuffers(mainDisplay, windowHandle); } public void gl_makeCurrent() @nogc nothrow @trusted { - version (Windows) wglMakeCurrent(windowDeviceContext, glRenderingContext); + version (Windows) { + wglMakeCurrent(windowDeviceContext, glRenderingContext); + } else version (OSX) { + (cast(NSOpenGLContext)getOpenGLHandle()).makeCurrentContext(); + } else glXMakeCurrent(mainDisplay, windowHandle, glxContext); } /** * Returns the current input language code. (Linux UNIMPLEMENTED) */ - public uint getInputLangCode() @nogc @safe nothrow const { + public uint getInputLangCode() @nogc nothrow const { version (Windows) return inputLang; + else version(OSX) + return NSTextInputContext.currentInputContext().selectedKeyboardInputSource().intValue(); else return 0; } + public void processEvents() @nogc nothrow { + version(OSX) { + NSEvent event = NSApplication.sharedApplication().nextEventMatchingMask( + NSEventMaskAny, + NSDate.distantPast(), + "kCFRunLoopDefaultMode".ns, + true + ); + + if (event !is null) { + NSApplication.sharedApplication().sendEvent(event); + } + } + } + /** * Callback function for windowing. Can be overridden to process more messages than by default. * See Microsoft documentation for details on return value and parameters. diff --git a/source/iota/window/types.d b/source/iota/window/types.d index a5a7ab4..d05d776 100644 --- a/source/iota/window/types.d +++ b/source/iota/window/types.d @@ -7,6 +7,9 @@ import std.conv : to; version (Windows) { import core.sys.windows.windows; import core.sys.windows.wtypes; +} else version(OSX) { + import cocoa.nswindow; + import cocoa.nsscreen; } else { import x11.Xlib; import x11.X; @@ -16,16 +19,36 @@ version (Windows) { */ version (Windows) { alias WindowH = HWND; +} else version(OSX) { + alias WindowH = NSWindow; } else { alias WindowH = Window; //alias WindowH = void*; } -///Defines window configuration flags. -public enum WindowCfgFlags { - FixedSize = 1 << 0, ///Creates a non-resizable window. + +version (OSX) { + public enum WindowCfgFlags { + FixedSize = 1 << 0, ///Creates a non-resizable window. + IgnoreMenuKey = 1 << 16,///Makes the window to ignore "menu" (Alt, F11) key under Windows. + MetalEnabled = 1 << 17,///Enables Metal support for the window. + } + + enum InputDeviceFlags { + HasForceFeedback = 1 << 0, + HasPressure = 1 << 1, + HasTrackpad = 1 << 2, + HasForceTouch = 1 << 3 + } + +} else { + ///Defines window configuration flags. + public enum WindowCfgFlags { + FixedSize = 1 << 0, ///Creates a non-resizable window. NoDecorations = 1 << 1, ///Creates a window without "server-side" decorations - IgnoreMenuKey = 1 << 16,///Makes the window to ignore "menu" (Alt, F11) key under Windows. + IgnoreMenuKey = 1 << 16,///Makes the window to ignore "menu" (Alt, F11) key under Windows. + } } + /** * Defines various window option flags that can be supplied during creating a new window. */ @@ -147,4 +170,7 @@ public struct ScreenMode { string toString() @safe const { return width.to!string ~ "x" ~ height.to!string ~ "@" ~ refreshRate.to!string ~ "Hz"; } + version (OSX) { + NSScreen* screen; // Store native NSScreen reference + } }