diff --git a/examples/fixedsize.nim b/examples/fixedsize.nim index 7369443..96eda91 100644 --- a/examples/fixedsize.nim +++ b/examples/fixedsize.nim @@ -1,6 +1,6 @@ import opengl, windy -let window = newWindow("Windy Basic", ivec2(500, 500)) +let window = newWindow("Windy Fixed Size", ivec2(500, 500)) window.style = Decorated window.makeContextCurrent() diff --git a/examples/fullscreen.nim b/examples/fullscreen.nim index 06f19d2..6099a95 100644 --- a/examples/fullscreen.nim +++ b/examples/fullscreen.nim @@ -1,6 +1,6 @@ import boxy, opengl, windy -let window = newWindow("Toggle Fullscreen", ivec2(1280, 800)) +let window = newWindow("Windy Toggle Fullscreen", ivec2(1280, 800)) window.makeContextCurrent() loadExtensions() diff --git a/examples/gamepad.nim b/examples/gamepad.nim new file mode 100644 index 0000000..b0699cf --- /dev/null +++ b/examples/gamepad.nim @@ -0,0 +1,37 @@ +import opengl, windy + +# Global event handlers must be registered before the first window is created, +# this enables them to receive events from gamepads that are already connected +onGamepadConnected = proc(gamepadId: int) = + echo "Gamepad ", gamepadId, " connected: ", gamepadName(gamepadId) +onGamepadDisconnected = proc(gamepadId: int) = + echo "Gamepad ", gamepadId, " disconnected" + +let window = newWindow("Windy Gamepad", ivec2(1280, 800)) +var color = vec4(0, 0, 0, 1) + +window.makeContextCurrent() +loadExtensions() + +proc gamepad() = + for i in 0.. 0 and gamepadButtonPressure(i, btn) < 1: + echo "Gamepad ", i, " button ", btn, " pressure ", gamepadButtonPressure(i, btn) + for axis in 0.GamepadAxis..".} proc emscripten_webgl_make_context_current*(context: EMSCRIPTEN_WEBGL_CONTEXT_HANDLE): EMSCRIPTEN_RESULT {.importc, header: "".} +const EM_HTML5_MEDIUM_STRING_LEN_BYTES* = 64 + # Mouse event handling type + EmscriptenGamepadEvent* {.importc: "EmscriptenGamepadEvent", header: "".} = object + timestamp*: cdouble + numAxes*: cint + numButtons*: cint + axis*: array[64, cdouble] + analogButton*: array[64, cdouble] + digitalButton*: array[64, bool] + connected*: bool + index*: cint + id*: array[EM_HTML5_MEDIUM_STRING_LEN_BYTES, char] + mapping*: array[EM_HTML5_MEDIUM_STRING_LEN_BYTES, char] + EmscriptenMouseEvent* {.importc: "EmscriptenMouseEvent", header: "".} = object timestamp*: cdouble screenX*, screenY*: clong @@ -181,7 +195,11 @@ type EmscriptenKeyboardEventCallback* = proc(eventType: cint, keyEvent: ptr EmscriptenKeyboardEvent, userData: pointer): EM_BOOL {.cdecl.} EmscriptenFocusEventCallback* = proc(eventType: cint, focusEvent: ptr EmscriptenFocusEvent, userData: pointer): EM_BOOL {.cdecl.} EmscriptenUiEventCallback* = proc(eventType: cint, uiEvent: ptr EmscriptenUiEvent, userData: pointer): EM_BOOL {.cdecl.} + EmscriptenGamepadEventCallback* = proc(eventType: cint, gamepadEvent: ptr EmscriptenGamepadEvent, userData: pointer): EM_BOOL {.cdecl.} + EmscriptenGamepadDisconnectedEventCallback* = proc(eventType: cint, gamepadEvent: ptr EmscriptenGamepadEvent, userData: pointer): EM_BOOL {.cdecl.} +proc emscripten_set_gamepadconnected_callback_on_thread*(userData: pointer, useCapture: EM_BOOL, callback: EmscriptenGamepadEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} +proc emscripten_set_gamepaddisconnected_callback_on_thread*(userData: pointer, useCapture: EM_BOOL, callback: EmscriptenGamepadDisconnectedEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} proc emscripten_set_mousedown_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenMouseEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} proc emscripten_set_mouseup_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenMouseEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} proc emscripten_set_mousemove_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenMouseEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} @@ -193,6 +211,9 @@ proc emscripten_set_blur_callback_on_thread*(target: cstring, userData: pointer, proc emscripten_set_focus_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenFocusEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} proc emscripten_set_resize_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenUiEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} +proc emscripten_sample_gamepad_data*(): EMSCRIPTEN_RESULT {.importc, header: "".} +proc emscripten_get_gamepad_status*(index: cint, status: ptr EmscriptenGamepadEvent): EMSCRIPTEN_RESULT {.importc, header: "".} + const EMSCRIPTEN_EVENT_KEYPRESS* = 1 EMSCRIPTEN_EVENT_KEYDOWN* = 2 diff --git a/src/windy/platforms/emscripten/platform.nim b/src/windy/platforms/emscripten/platform.nim index 017196c..5f41d06 100644 --- a/src/windy/platforms/emscripten/platform.nim +++ b/src/windy/platforms/emscripten/platform.nim @@ -54,10 +54,14 @@ var mainWindow: Window # Track the main window for events httpRequests: Table[HttpRequestHandle, EmsHttpRequestState] + gamepadsConnectedMask: uint8 + gamepadStates: array[maxGamepads, GamepadState] + proc handleButtonPress(window: Window, button: Button) proc handleButtonRelease(window: Window, button: Button) proc handleRune(window: Window, rune: Rune) proc setupEventHandlers(window: Window) # Forward declaration +proc setupGamepads() proc init = if initialized: @@ -242,6 +246,7 @@ proc newWindow*( # Setup event handlers setupEventHandlers(result) + setupGamepads() result.title = title if pos != ivec2(0, 0): @@ -462,6 +467,59 @@ proc keyCodeToButton(keyCode: culong): Button = of 222: KeyApostrophe else: ButtonUnknown +gamepadPlatform() + +proc gamepadConnected*(gamepadId: int): bool = + (gamepadsConnectedMask and (1.uint8 shl gamepadId)) != 0 + +proc strcmp(a: cstring, b: cstring): cint {.importc, header: "".} + +proc onGamepadConnected(eventType: cint, gamepadEvent: ptr EmscriptenGamepadEvent, userData: pointer): EM_BOOL {.cdecl.} = + # We can only ensure known stable mappings if the gamepad reports the standard mapping + if strcmp(cast[cstring](addr gamepadEvent.mapping), cstring "standard") == 0: + gamepadsConnectedMask = gamepadsConnectedMask or (1.uint8 shl gamepadEvent.index) + gamepadStates[gamepadEvent.index].name = $gamepadEvent.id + if common.onGamepadConnected != nil: + common.onGamepadConnected(gamepadEvent.index) + return 1 + +proc onGamepadDisconnected(eventType: cint, gamepadEvent: ptr EmscriptenGamepadEvent, userData: pointer): EM_BOOL {.cdecl.} = + if (gamepadsConnectedMask and (1.uint8 shl gamepadEvent.index)) != 0: + gamepadsConnectedMask = gamepadsConnectedMask and (not (1.uint8 shl gamepadEvent.index)) + gamepadResetState(gamepadStates[gamepadEvent.index]) + if common.onGamepadDisconnected != nil: + common.onGamepadDisconnected(gamepadEvent.index) + return 1 + +proc setupGamepads() = + discard emscripten_sample_gamepad_data() # Populate gamepad status data we're about to read + + var gp: EmscriptenGamepadEvent + for i in 0..".} + +proc gamepadEvent(device: ptr udev_device, added: bool) = + # Final filtering and registration of gamepad devices, + # silently treating open errors as unavailable devices. + # The rare possible cases of failure, beyond code errors, are dire conditions like out of memory. + let devnode = udev_device_get_devnode(device) + if devnode != nil and strncmp(devnode, "/dev/input/event", 16) == 0: + let syspath = udev_device_get_syspath(device) + if added: + for i in 0.. info.minimum: + deviceAbsInfo[i][j] = info + + if onGamepadConnected != nil: onGamepadConnected(i) + break + else: + for i in 0.. 0, pos) + case event.code + of ABS_X: state.axes[GamepadLStickX.int] = axis() + of ABS_Y: state.axes[GamepadLStickY.int] = axis() + of ABS_RX: state.axes[GamepadRStickX.int] = axis() + of ABS_RY: state.axes[GamepadRStickY.int] = axis() + of ABS_Z: pressure(GamepadL2) + of ABS_RZ: pressure(GamepadR2) + of ABS_HAT0X: dpad(GamepadLeft, GamepadRight) + of ABS_HAT0Y: dpad(GamepadUp, GamepadDown) + else: discard + else: discard + gamepadUpdateButtons() diff --git a/src/windy/platforms/linux/lindefs.nim b/src/windy/platforms/linux/lindefs.nim new file mode 100644 index 0000000..76a8e68 --- /dev/null +++ b/src/windy/platforms/linux/lindefs.nim @@ -0,0 +1,211 @@ +{.passL: "-ludev -levdev."} + +# +type + epoll_data_t* {.union.} = object + `ptr`*: pointer + fd*: cint + u32*: uint32 + u64*: uint64 + epoll_event* = object + events*: uint32 + data*: epoll_data_t + +const + EPOLL_CTL_ADD* = 1 + EPOLL_CTL_DEL* = 2 + EPOLL_CTL_MOD* = 3 + EPOLLIN* = 0x001 + +{.push importc, cdecl.} + +proc epoll_create1*(flags: cint): cint +proc epoll_ctl*(epfd: cint, op: cint, fd: cint, event: ptr epoll_event): cint +proc epoll_wait*(epfd: cint, events: ptr epoll_event, maxevents: cint, timeout: cint): cint + +# +type + dev_t* = uint64 + + udev* = object + udev_list_entry* = object + udev_device* = object + udev_monitor* = object + udev_enumerate* = object + +proc udev_ref*(udev: ptr udev): ptr udev +proc udev_unref*(udev: ptr udev): ptr udev +proc udev_new*(): ptr udev + +proc udev_list_entry_get_next*(list_entry: ptr udev_list_entry): ptr udev_list_entry +proc udev_list_entry_get_by_name*(list_entry: ptr udev_list_entry, name: cstring): ptr udev_list_entry +proc udev_list_entry_get_name*(list_entry: ptr udev_list_entry): cstring +proc udev_list_entry_get_value*(list_entry: ptr udev_list_entry): cstring + +template udev_list_entry_foreach*(first_entry: ptr udev_list_entry, body: untyped) = + entry = first_entry + while entry != nil: + body + entry = udev_list_entry_get_next(entry) + +proc udev_device_ref*(udev_device: ptr udev_device): ptr udev_device +proc udev_device_unref*(udev_device: ptr udev_device): ptr udev_device +proc udev_device_get_udev*(udev_device: ptr udev_device): ptr udev +proc udev_device_new_from_syspath*(udev: ptr udev, syspath: cstring): ptr udev_device +proc udev_device_new_from_devnum*(udev: ptr udev, `type`: cchar, devnum: dev_t): ptr udev_device +proc udev_device_new_from_subsystem_sysname*(udev: ptr udev, subsystem: cstring, sysname: cstring): ptr udev_device +proc udev_device_new_from_device_id*(udev: ptr udev, id: cstring): ptr udev_device +proc udev_device_new_from_environment*(udev: ptr udev): ptr udev_device +proc udev_device_get_parent*(udev_device: ptr udev_device): ptr udev_device +proc udev_device_get_parent_with_subsystem_devtype*(udev_device: ptr udev_device, subsystem: cstring, devtype: cstring): ptr udev_device +proc udev_device_get_devpath*(udev_device: ptr udev_device): cstring +proc udev_device_get_subsystem*(udev_device: ptr udev_device): cstring +proc udev_device_get_devtype*(udev_device: ptr udev_device): cstring +proc udev_device_get_syspath*(udev_device: ptr udev_device): cstring +proc udev_device_get_sysname*(udev_device: ptr udev_device): cstring +proc udev_device_get_sysnum*(udev_device: ptr udev_device): cstring +proc udev_device_get_devnode*(udev_device: ptr udev_device): cstring +proc udev_device_get_is_initialized*(udev_device: ptr udev_device): cint +proc udev_device_get_devlinks_list_entry*(udev_device: ptr udev_device): ptr udev_list_entry +proc udev_device_get_properties_list_entry*(udev_device: ptr udev_device): ptr udev_list_entry +proc udev_device_get_tags_list_entry*(udev_device: ptr udev_device): ptr udev_list_entry +proc udev_device_get_current_tags_list_entry*(udev_device: ptr udev_device): ptr udev_list_entry +proc udev_device_get_sysattr_list_entry*(udev_device: ptr udev_device): ptr udev_list_entry +proc udev_device_get_property_value*(udev_device: ptr udev_device, key: cstring): cstring +proc udev_device_get_driver*(udev_device: ptr udev_device): cstring +proc udev_device_get_devnum*(udev_device: ptr udev_device): dev_t +proc udev_device_get_action*(udev_device: ptr udev_device): cstring +proc udev_device_get_seqnum*(udev_device: ptr udev_device): culong +proc udev_device_get_usec_since_initialized*(udev_device: ptr udev_device): culong +proc udev_device_get_sysattr_value*(udev_device: ptr udev_device, sysattr: cstring): cstring +proc udev_device_set_sysattr_value*(udev_device: ptr udev_device, sysattr: cstring, value: cstring): cint +proc udev_device_has_tag*(udev_device: ptr udev_device, tag: cstring): cint +proc udev_device_has_current_tag*(udev_device: ptr udev_device, tag: cstring): cint + +proc udev_monitor_ref*(udev_monitor: ptr udev_monitor): ptr udev_monitor +proc udev_monitor_unref*(udev_monitor: ptr udev_monitor): ptr udev_monitor +proc udev_monitor_get_udev*(udev_monitor: ptr udev_monitor): ptr udev +proc udev_monitor_new_from_netlink*(udev: ptr udev, name: cstring): ptr udev_monitor +proc udev_monitor_enable_receiving*(udev_monitor: ptr udev_monitor): cint +proc udev_monitor_set_receive_buffer_size*(udev_monitor: ptr udev_monitor, size: cint): cint +proc udev_monitor_get_fd*(udev_monitor: ptr udev_monitor): cint +proc udev_monitor_receive_device*(udev_monitor: ptr udev_monitor): ptr udev_device +proc udev_monitor_filter_add_match_subsystem_devtype*(udev_monitor: ptr udev_monitor, subsystem: cstring, devtype: cstring): cint +proc udev_monitor_filter_add_match_tag*(udev_monitor: ptr udev_monitor, tag: cstring): cint +proc udev_monitor_filter_update*(udev_monitor: ptr udev_monitor): cint +proc udev_monitor_filter_remove*(udev_monitor: ptr udev_monitor): cint + +proc udev_enumerate_ref*(udev_enumerate: ptr udev_enumerate): ptr udev_enumerate +proc udev_enumerate_unref*(udev_enumerate: ptr udev_enumerate): ptr udev_enumerate +proc udev_enumerate_get_udev*(udev_enumerate: ptr udev_enumerate): ptr udev +proc udev_enumerate_new*(udev: ptr udev): ptr udev_enumerate +proc udev_enumerate_add_match_subsystem*(udev_enumerate: ptr udev_enumerate, subsystem: cstring): cint +proc udev_enumerate_add_match_sysname*(udev_enumerate: ptr udev_enumerate, sysname: cstring): cint +proc udev_enumerate_add_match_property*(udev_enumerate: ptr udev_enumerate, property: cstring, value: cstring): cint +proc udev_enumerate_add_match_tag*(udev_enumerate: ptr udev_enumerate, tag: cstring): cint +proc udev_enumerate_add_match_parent*(udev_enumerate: ptr udev_enumerate, parent: ptr udev_device): cint +proc udev_enumerate_add_match_is_initialized*(udev_enumerate: ptr udev_enumerate): cint +proc udev_enumerate_add_syspath*(udev_enumerate: ptr udev_enumerate, syspath: cstring): cint +proc udev_enumerate_scan_devices*(udev_enumerate: ptr udev_enumerate): cint +proc udev_enumerate_scan_subsystems*(udev_enumerate: ptr udev_enumerate): cint +proc udev_enumerate_get_list_entry*(udev_enumerate: ptr udev_enumerate): ptr udev_list_entry + +# +type + timeval* = object + tv_sec*: clong + tv_usec*: clong + +# +type + input_event* = object + time*: timeval + `type`*: uint16 + code*: uint16 + value*: int32 + input_absinfo* = object + value*: int32 + minimum*: int32 + maximum*: int32 + fuzz*: int32 + flat*: int32 + resolution*: int32 + +# +const + EV_SYN* = 0 + EV_KEY* = 1 + EV_ABS* = 3 + + SYN_DROPPED* = 3 + + BTN_A* = 0x130 + BTN_B* = 0x131 + BTN_C* = 0x132 + BTN_X* = 0x133 + BTN_Y* = 0x134 + BTN_Z* = 0x135 + BTN_TL* = 0x136 + BTN_TR* = 0x137 + BTN_TL2* = 0x138 + BTN_TR2* = 0x139 + BTN_SELECT* = 0x13a + BTN_START* = 0x13b + BTN_MODE* = 0x13c + BTN_THUMBL* = 0x13d + BTN_THUMBR* = 0x13e + + ABS_X* = 0x00 + ABS_Y* = 0x01 + ABS_Z* = 0x02 + ABS_RX* = 0x03 + ABS_RY* = 0x04 + ABS_RZ* = 0x05 + ABS_THROTTLE* = 0x06 + ABS_RUDDER* = 0x07 + ABS_WHEEL* = 0x08 + ABS_GAS* = 0x09 + ABS_BRAKE* = 0x0a + ABS_HAT0X* = 0x10 + ABS_HAT0Y* = 0x11 + ABS_HAT1X* = 0x12 + ABS_HAT1Y* = 0x13 + ABS_HAT2X* = 0x14 + ABS_HAT2Y* = 0x15 + ABS_HAT3X* = 0x16 + ABS_HAT3Y* = 0x17 + ABS_PRESSURE* = 0x18 + ABS_DISTANCE* = 0x19 + ABS_TILT_X* = 0x1a + ABS_TILT_Y* = 0x1b + ABS_TOOL_WIDTH* = 0x1c + + ABS_VOLUME* = 0x20 + ABS_PROFILE* = 0x21 + + ABS_MISC* = 0x28 + +# +type + libevdev* = object + +const + LIBEVDEV_READ_FLAG_SYNC* = 1 + LIBEVDEV_READ_FLAG_NORMAL* = 2 + LIBEVDEV_READ_STATUS_SYNC* = -1 + +proc libevdev_new_from_fd*(fd: cint, dev: ptr ptr libevdev): cint +proc libevdev_free*(dev: ptr libevdev) +proc libevdev_get_fd*(dev: ptr libevdev): cint +proc libevdev_get_name*(dev: ptr libevdev): cstring +proc libevdev_get_phys*(dev: ptr libevdev): cstring +proc libevdev_get_uniq*(dev: ptr libevdev): cstring +proc libevdev_get_id_product*(dev: ptr libevdev): cint +proc libevdev_get_id_vendor*(dev: ptr libevdev): cint +proc libevdev_get_id_bustype*(dev: ptr libevdev): cint +proc libevdev_get_id_version*(dev: ptr libevdev): cint +proc libevdev_has_event_code*(dev: ptr libevdev, `type`: uint16, code: uint16): bool +proc libevdev_get_abs_info*(dev: ptr libevdev, code: uint16): ptr input_absinfo +proc libevdev_next_event*(dev: ptr libevdev, flags: cint, event: ptr input_event): cint + +{.pop.} diff --git a/src/windy/platforms/linux/x11.nim b/src/windy/platforms/linux/x11.nim index 2e0ab35..49a444f 100644 --- a/src/windy/platforms/linux/x11.nim +++ b/src/windy/platforms/linux/x11.nim @@ -7,6 +7,8 @@ import import ../../http export http +include gamepad + type XWindow = x.Window @@ -118,6 +120,8 @@ proc init = else: WmForDecoratedKind.unsupported + gamepadSetup() + initialized = true proc property( @@ -786,6 +790,7 @@ proc newWindow*( windows.add result proc pollEvents(window: Window) = + gamepadPoll() # Clear all per-frame data window.perFrame = PerFrame() diff --git a/src/windy/platforms/macos/macdefs.nim b/src/windy/platforms/macos/macdefs.nim index fbb75c0..cb7ad2f 100644 --- a/src/windy/platforms/macos/macdefs.nim +++ b/src/windy/platforms/macos/macdefs.nim @@ -1,7 +1,7 @@ import opengl, objc export objc -{.passL: "-framework Cocoa".} +{.passL: "-framework Cocoa -framework GameController".} type CGPoint* {.pure, bycopy.} = object @@ -47,6 +47,7 @@ type NSPasteboardType* = distinct NSString NSApplication* = distinct NSObject NSNotification* = distinct NSObject + NSNotificationCenter* = distinct NSObject NSEvent* = distinct NSObject NSDate* = distinct NSObject NSRunLoopMode* = distinct NSString @@ -66,6 +67,20 @@ type NSBitmapImageRep* = distinct NSObject NSDictionary* = distinct NSObject + GCDevice* = distinct NSObject + GCController* = distinct NSObject + GCPhysicalInputProfile* = distinct NSObject + GCControllerElement* = distinct NSObject + GCControllerAxisInput* = distinct GCControllerElement + GCControllerButtonInput* = distinct GCControllerElement + GCControllerDirectionPad* = distinct GCControllerElement + GCControllerTouchpad* = distinct GCControllerElement + + GCSystemGestureState* = enum + GCSystemGestureStateEnabled + GCSystemGestureStateAlwaysReceive + GCSystemGestureStateDisabled + const NSNotFound* = int.high kEmptyRange* = NSRange(location: cast[uint](NSNotFound), length: 0) @@ -120,6 +135,26 @@ var NSPasteboardTypeString* {.importc.}: NSPasteboardType NSPasteboardTypeTIFF* {.importc.}: NSPasteboardType NSDefaultRunLoopMode* {.importc.}: NSRunLoopMode + GCControllerDidConnectNotification* {.importc.}: NSString + GCControllerDidDisconnectNotification* {.importc.}: NSString + GCInputLeftShoulder* {.importc.}: NSString + GCInputRightShoulder* {.importc.}: NSString + GCInputLeftBumper* {.importc.}: NSString + GCInputRightBumper* {.importc.}: NSString + GCInputLeftTrigger* {.importc.}: NSString + GCInputRightTrigger* {.importc.}: NSString + GCInputButtonMenu* {.importc.}: NSString + GCInputButtonHome* {.importc.}: NSString + GCInputButtonOptions* {.importc.}: NSString + GCInputButtonA* {.importc.}: NSString + GCInputButtonB* {.importc.}: NSString + GCInputButtonX* {.importc.}: NSString + GCInputButtonY* {.importc.}: NSString + GCInputDirectionPad* {.importc.}: NSString + GCInputLeftThumbstick* {.importc.}: NSString + GCInputRightThumbstick* {.importc.}: NSString + GCInputLeftThumbstickButton* {.importc.}: NSString + GCInputRightThumbstickButton* {.importc.}: NSString objc: proc isKindOfClass*(self: NSObject, x: Class): bool @@ -155,6 +190,7 @@ objc: proc count*(self: NSArray): uint proc objectAtIndex*(self: NSArray, x: uint): ID proc containsObject*(self: NSArray, x: ID): bool + proc valueForKey*(self: NSDictionary, x: NSString): ID proc screens*(class: typedesc[NSScreen]): NSArray proc frame*(self: NSScreen): NSRect proc frame*(self: NSWindow): NSRect @@ -300,6 +336,32 @@ objc: properties: NSDictionary ): NSData + proc `object`*(self: NSNotification): ID + + proc defaultCenter*(class: typedesc[NSNotificationCenter]): NSNotificationCenter + proc addObserver*(self: NSNotificationCenter, x: ID, selector: SEL, name: NSString, object_mangle: ID) + + proc vendorName*(self: GCDevice): NSString + proc controllers*(class: typedesc[GCController]): NSArray + proc startWirelessControllerDiscoveryWithCompletionHandler*(class: typedesc[GCController], x: ID) + proc playerIndex*(self: GCController): int + proc setPlayerIndex*(self: GCController, x: int) + proc physicalInputProfile*(self: GCController): GCPhysicalInputProfile + proc device*(self: GCPhysicalInputProfile): GCDevice + proc lastEventTimestamp*(self: GCPhysicalInputProfile): float64 + proc buttons*(self: GCPhysicalInputProfile): NSDictionary + proc dpads*(self: GCPhysicalInputProfile): NSDictionary + proc setPreferredSystemGestureState*(self: GCControllerElement, x: GCSystemGestureState) + proc left*(self: GCControllerDirectionPad): GCControllerButtonInput + proc right*(self: GCControllerDirectionPad): GCControllerButtonInput + proc up*(self: GCControllerDirectionPad): GCControllerButtonInput + proc down*(self: GCControllerDirectionPad): GCControllerButtonInput + proc xAxis*(self: GCControllerDirectionPad): GCControllerAxisInput + proc yAxis*(self: GCControllerDirectionPad): GCControllerAxisInput + proc value*(self: GCControllerAxisInput): float32 + proc value*(self: GCControllerButtonInput): float32 + proc isPressed*(self: GCControllerButtonInput): bool + {.push inline.} proc NSMakeRect*(x, y, w, h: float64): NSRect = @@ -320,6 +382,9 @@ proc NSMakePoint*(x, y: float): NSPoint = proc `[]`*(arr: NSArray, index: int): ID = arr.objectAtIndex(index.uint) +proc `[]`*(dict: NSDictionary, key: NSString): ID = + dict.valueForKey(key) + proc callSuper*(sender: ID, cmd: SEL) = var super = objc_super( receiver: sender, diff --git a/src/windy/platforms/macos/objc.nim b/src/windy/platforms/macos/objc.nim index 507cb13..edd0f10 100644 --- a/src/windy/platforms/macos/objc.nim +++ b/src/windy/platforms/macos/objc.nim @@ -15,6 +15,7 @@ type super_class*: Class {.push cdecl, dynlib: "libobjc.dylib".} + proc objc_msgSend*() {.importc.} proc objc_msgSendSuper*() {.importc.} when defined(amd64): @@ -55,6 +56,7 @@ macro objc*(body: untyped) = numParams = 0 sel.removeSuffix("*") + sel = sel.strip(true, true, {'`'}) # Mark each proc inline: fn[4] = quote do: {.inline.} diff --git a/src/windy/platforms/macos/platform.nim b/src/windy/platforms/macos/platform.nim index 10f94b6..0141b79 100644 --- a/src/windy/platforms/macos/platform.nim +++ b/src/windy/platforms/macos/platform.nim @@ -6,6 +6,10 @@ import ../../http export http type + Gamepad* = object + numButtons*: int8 + numAxes*: int8 + Window* = ref object onCloseRequest*: Callback onFrame*: Callback @@ -40,6 +44,16 @@ var WindyAppDelegate, WindyWindow, WindyView: Class windows: seq[Window] + # Gamepad state is what we expose; profiles, lookups and inputs are cached for polling GCController each frame + gamepads: array[maxGamepads, Gamepad] + gamepadStates: array[maxGamepads, GamepadState] + gamepadProfiles: array[maxGamepads, GCPhysicalInputProfile] + gamepadTimestamps: array[maxGamepads, float64] + gamepadAxisLookup: array[maxGamepads, array[GamepadAxisCount.int, int8]] + gamepadAxisInputs: array[maxGamepads, array[GamepadAxisCount.int, GCControllerAxisInput]] + gamepadButtonLookup: array[maxGamepads, array[GamepadButtonCount.int, int8]] + gamepadButtonInputs: array[maxGamepads, array[GamepadButtonCount.int, GCControllerButtonInput]] + proc indexForNSWindow(windows: seq[Window], inner: NSWindow): int = ## Returns the window for this handle, else -1 for i, window in windows: @@ -295,6 +309,117 @@ proc applicationDidFinishLaunching( NSApp.setActivationPolicy(NSApplicationActivationPolicyRegular) NSApp.activateIgnoringOtherApps(true) +gamepadPlatform() + +proc gamepadConnected*(gamepadId: int): bool = + gamepadProfiles[gamepadId].int != 0 + +proc pollGamepads() = + for i in 0.. 0: + buttons = buttons or (uint32 1 shl dst.int) + + template axis(src: float, dst: GamepadAxis) = + state.axes[dst.int] = gamepadFilterDeadZone(src) + + axis(gamepad.leftThumbstickX, GamepadLStickX) + axis(gamepad.leftThumbstickY, GamepadLStickY) + axis(gamepad.rightThumbstickX, GamepadRStickX) + axis(gamepad.rightThumbstickY, GamepadRStickY) + remap(gamepad.leftTrigger, GamepadL2) + remap(gamepad.rightTrigger, GamepadR2) + button(GameInputGamepadMenu, GamepadStart) + button(GameInputGamepadView, GamepadSelect) + button(GameInputGamepadA, GamepadA) + button(GameInputGamepadB, GamepadB) + button(GameInputGamepadX, GamepadX) + button(GameInputGamepadY, GamepadY) + button(GameInputGamepadDPadUp, GamepadUp) + button(GameInputGamepadDPadDown, GamepadDown) + button(GameInputGamepadDPadLeft, GamepadLeft) + button(GameInputGamepadDPadRight, GamepadRight) + button(GameInputGamepadLeftShoulder, GamepadL1) + button(GameInputGamepadRightShoulder, GamepadR1) + button(GameInputGamepadLeftThumbstick, GamepadL3) + button(GameInputGamepadRightThumbstick, GamepadR3) + + gamepadUpdateButtons() + comDispose(reading) + +proc gameInputDeviceCallback(callbackToken: GameInputCallbackToken, context: pointer, device: ptr IGameInputDevice, timestamp: uint64, currentStatus: GameInputDeviceStatus, previousStatus: GameInputDeviceStatus) {.stdcall.} = + if (currentStatus and GameInputDeviceConnected) == 0: + for i in 0..