Mercurial > games > semicongine
changeset 149:0aa18fe8f7f1
add: base code structure for cross-platform sound
author | Sam <sam@basx.dev> |
---|---|
date | Thu, 27 Apr 2023 00:36:45 +0700 |
parents | ae8b0d89a2ee |
children | 7ed6f87a0fe1 |
files | src/semicongine/audio.nim src/semicongine/platform/audio.nim src/semicongine/platform/linux/audio.nim src/semicongine/platform/linux/window.nim src/semicongine/platform/linux/xlib.nim src/semicongine/platform/window.nim src/semicongine/platform/windows/audio.nim src/semicongine/platform/windows/win32.nim src/semicongine/platform/windows/window.nim |
diffstat | 6 files changed, 273 insertions(+), 265 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/platform/audio.nim Thu Apr 27 00:36:45 2023 +0700 @@ -0,0 +1,6 @@ +when defined(linux): + include ./linux/audio +elif defined(windows): + include ./windows/audio + +export audio
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/platform/linux/window.nim Thu Apr 27 00:36:45 2023 +0700 @@ -0,0 +1,149 @@ +import std/options +import std/tables +import + x11/xlib, + x11/xutil, + x11/keysym +import x11/x + +import ../../events +import ../../math/vector + +import ./symkey_map + +export keysym + +var deleteMessage*: Atom + +type + NativeWindow* = object + display*: ptr xlib.Display + window*: x.Window + emptyCursor: Cursor + +template checkXlibResult*(call: untyped) = + let value = call + if value == 0: + raise newException(Exception, "Xlib error: " & astToStr(call) & + " returned " & $value) + +proc createWindow*(title: string): NativeWindow = + checkXlibResult XInitThreads() + let display = XOpenDisplay(nil) + if display == nil: + quit "Failed to open display" + + let + screen = XDefaultScreen(display) + rootWindow = XRootWindow(display, screen) + foregroundColor = XBlackPixel(display, screen) + backgroundColor = XWhitePixel(display, screen) + + let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0, + foregroundColor, backgroundColor) + checkXlibResult XSetStandardProperties(display, window, title, "window", 0, + nil, 0, nil) + checkXlibResult XSelectInput(display, window, PointerMotionMask or + ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or ExposureMask) + checkXlibResult XMapWindow(display, window) + + deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false)) + checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1) + + # quite a lot of work to hide the cursor... + var data = "\0".cstring + var pixmap = XCreateBitmapFromData(display, window, data, 1, 1) + var color: XColor + var empty_cursor = XCreatePixmapCursor(display, pixmap, pixmap, addr(color), + addr(color), 0, 0) + checkXlibResult XFreePixmap(display, pixmap) + checkXlibResult XDefineCursor(display, window, empty_cursor) + + return NativeWindow(display: display, window: window, + emptyCursor: empty_cursor) + +proc destroy*(window: NativeWindow) = + checkXlibResult window.display.XFreeCursor(window.emptyCursor) + checkXlibResult window.display.XDestroyWindow(window.window) + discard window.display.XCloseDisplay() # always returns 0 + +proc size*(window: NativeWindow): (int, int) = + var attribs: XWindowAttributes + checkXlibResult XGetWindowAttributes(window.display, window.window, addr(attribs)) + return (int(attribs.width), int(attribs.height)) + +proc pendingEvents*(window: NativeWindow): seq[Event] = + var + event: XEvent + serials: Table[culong, Table[int, seq[Event]]] + while window.display.XPending() > 0: + discard window.display.XNextEvent(addr(event)) + case event.theType + of ClientMessage: + if cast[Atom](event.xclient.data.l[0]) == deleteMessage: + result.add(Event(eventType: Quit)) + of KeyPress: + let keyevent = cast[PXKeyEvent](addr(event)) + let xkey = int(keyevent.keycode) + # ugly, but required to catch auto-repeat keys of X11 + if not (keyevent.serial in serials): + serials[keyevent.serial] = initTable[int, seq[Event]]() + if not (xkey in serials[keyevent.serial]): + serials[keyevent.serial][xkey] = newSeq[Event]() + serials[keyevent.serial][xkey].add(Event(eventType: KeyPressed, + key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))) + of KeyRelease: + let keyevent = cast[PXKeyEvent](addr(event)) + let xkey = int(keyevent.keycode) + # ugly, but required to catch auto-repeat keys of X11 + if not (keyevent.serial in serials): + serials[keyevent.serial] = initTable[int, seq[Event]]() + if not (xkey in serials[keyevent.serial]): + serials[keyevent.serial][xkey] = newSeq[Event]() + serials[keyevent.serial][xkey].add Event(eventType: KeyReleased, + key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN)) + of ButtonPress: + let button = int(cast[PXButtonEvent](addr(event)).button) + result.add Event(eventType: MousePressed, + button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN)) + of ButtonRelease: + let button = int(cast[PXButtonEvent](addr(event)).button) + result.add Event(eventType: MouseReleased, + button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN)) + of MotionNotify: + let motion = cast[PXMotionEvent](addr(event)) + result.add Event(eventType: MouseMoved, x: motion.x, y: motion.y) + of ConfigureNotify, Expose: + result.add Event(eventType: ResizedWindow) + else: + discard + # little hack to work around X11 auto-repeat keys + for (serial, keys) in serials.pairs: + for (key, events) in keys.pairs: + if events.len == 1: + result.add events[0] + + +proc getMousePosition*(window: NativeWindow): Option[Vec2f] = + var + root: x.Window + win: x.Window + rootX: cint + rootY: cint + winX: cint + winY: cint + mask: cuint + onscreen = XQueryPointer( + window.display, + window.window, + addr(root), + addr(win), + addr(rootX), + addr(rootY), + addr(winX), + addr(winY), + addr(mask), + ) + if onscreen != 0: + result = some(Vec2f([float32(winX), float32(winY)])) +
--- a/src/semicongine/platform/linux/xlib.nim Thu Apr 27 00:30:19 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,149 +0,0 @@ -import std/options -import std/tables -import - x11/xlib, - x11/xutil, - x11/keysym -import x11/x - -import ../../events -import ../../math/vector - -import ./symkey_map - -export keysym - -var deleteMessage*: Atom - -type - NativeWindow* = object - display*: ptr xlib.Display - window*: x.Window - emptyCursor: Cursor - -template checkXlibResult*(call: untyped) = - let value = call - if value == 0: - raise newException(Exception, "Xlib error: " & astToStr(call) & - " returned " & $value) - -proc createWindow*(title: string): NativeWindow = - checkXlibResult XInitThreads() - let display = XOpenDisplay(nil) - if display == nil: - quit "Failed to open display" - - let - screen = XDefaultScreen(display) - rootWindow = XRootWindow(display, screen) - foregroundColor = XBlackPixel(display, screen) - backgroundColor = XWhitePixel(display, screen) - - let window = XCreateSimpleWindow(display, rootWindow, -1, -1, 800, 600, 0, - foregroundColor, backgroundColor) - checkXlibResult XSetStandardProperties(display, window, title, "window", 0, - nil, 0, nil) - checkXlibResult XSelectInput(display, window, PointerMotionMask or - ButtonPressMask or ButtonReleaseMask or KeyPressMask or KeyReleaseMask or ExposureMask) - checkXlibResult XMapWindow(display, window) - - deleteMessage = XInternAtom(display, "WM_DELETE_WINDOW", XBool(false)) - checkXlibResult XSetWMProtocols(display, window, addr(deleteMessage), 1) - - # quite a lot of work to hide the cursor... - var data = "\0".cstring - var pixmap = XCreateBitmapFromData(display, window, data, 1, 1) - var color: XColor - var empty_cursor = XCreatePixmapCursor(display, pixmap, pixmap, addr(color), - addr(color), 0, 0) - checkXlibResult XFreePixmap(display, pixmap) - checkXlibResult XDefineCursor(display, window, empty_cursor) - - return NativeWindow(display: display, window: window, - emptyCursor: empty_cursor) - -proc destroy*(window: NativeWindow) = - checkXlibResult window.display.XFreeCursor(window.emptyCursor) - checkXlibResult window.display.XDestroyWindow(window.window) - discard window.display.XCloseDisplay() # always returns 0 - -proc size*(window: NativeWindow): (int, int) = - var attribs: XWindowAttributes - checkXlibResult XGetWindowAttributes(window.display, window.window, addr(attribs)) - return (int(attribs.width), int(attribs.height)) - -proc pendingEvents*(window: NativeWindow): seq[Event] = - var - event: XEvent - serials: Table[culong, Table[int, seq[Event]]] - while window.display.XPending() > 0: - discard window.display.XNextEvent(addr(event)) - case event.theType - of ClientMessage: - if cast[Atom](event.xclient.data.l[0]) == deleteMessage: - result.add(Event(eventType: Quit)) - of KeyPress: - let keyevent = cast[PXKeyEvent](addr(event)) - let xkey = int(keyevent.keycode) - # ugly, but required to catch auto-repeat keys of X11 - if not (keyevent.serial in serials): - serials[keyevent.serial] = initTable[int, seq[Event]]() - if not (xkey in serials[keyevent.serial]): - serials[keyevent.serial][xkey] = newSeq[Event]() - serials[keyevent.serial][xkey].add(Event(eventType: KeyPressed, - key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN))) - of KeyRelease: - let keyevent = cast[PXKeyEvent](addr(event)) - let xkey = int(keyevent.keycode) - # ugly, but required to catch auto-repeat keys of X11 - if not (keyevent.serial in serials): - serials[keyevent.serial] = initTable[int, seq[Event]]() - if not (xkey in serials[keyevent.serial]): - serials[keyevent.serial][xkey] = newSeq[Event]() - serials[keyevent.serial][xkey].add Event(eventType: KeyReleased, - key: KeyTypeMap.getOrDefault(xkey, Key.UNKNOWN)) - of ButtonPress: - let button = int(cast[PXButtonEvent](addr(event)).button) - result.add Event(eventType: MousePressed, - button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN)) - of ButtonRelease: - let button = int(cast[PXButtonEvent](addr(event)).button) - result.add Event(eventType: MouseReleased, - button: MouseButtonTypeMap.getOrDefault(button, MouseButton.UNKNOWN)) - of MotionNotify: - let motion = cast[PXMotionEvent](addr(event)) - result.add Event(eventType: MouseMoved, x: motion.x, y: motion.y) - of ConfigureNotify, Expose: - result.add Event(eventType: ResizedWindow) - else: - discard - # little hack to work around X11 auto-repeat keys - for (serial, keys) in serials.pairs: - for (key, events) in keys.pairs: - if events.len == 1: - result.add events[0] - - -proc getMousePosition*(window: NativeWindow): Option[Vec2f] = - var - root: x.Window - win: x.Window - rootX: cint - rootY: cint - winX: cint - winY: cint - mask: cuint - onscreen = XQueryPointer( - window.display, - window.window, - addr(root), - addr(win), - addr(rootX), - addr(rootY), - addr(winX), - addr(winY), - addr(mask), - ) - if onscreen != 0: - result = some(Vec2f([float32(winX), float32(winY)])) -
--- a/src/semicongine/platform/window.nim Thu Apr 27 00:30:19 2023 +0700 +++ b/src/semicongine/platform/window.nim Thu Apr 27 00:36:45 2023 +0700 @@ -1,4 +1,6 @@ when defined(linux): - include ./linux/xlib + include ./linux/window elif defined(windows): - include ./windows/win32 + include ./windows/window + +export window
--- a/src/semicongine/platform/windows/win32.nim Thu Apr 27 00:30:19 2023 +0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,114 +0,0 @@ -import std/options -import winim - -import ./virtualkey_map -import ../../events -import ../../math/vector - -type - NativeWindow* = object - hinstance*: HINSTANCE - hwnd*: HWND - -# sorry, have to use module-global variable to capture windows events -var currentEvents: seq[Event] - -template checkWin32Result*(call: untyped) = - let value = call - if value == 0: - raise newException(Exception, "Win32 error: " & astToStr(call) & " returned " & $value) - - -proc MapLeftRightKeys(key: INT, lparam: LPARAM): INT = - case key - of VK_SHIFT: - MapVirtualKey(UINT((lParam and 0x00ff0000) shr 16), MAPVK_VSC_TO_VK_EX) - of VK_CONTROL: - if (lParam and 0x01000000) == 0: VK_LCONTROL else: VK_RCONTROL - of VK_MENU: - if (lParam and 0x01000000) == 0: VK_LMENU else: VK_RMENU - else: - key - -proc WindowHandler(hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM): LRESULT {.stdcall.} = - case uMsg - of WM_DESTROY: - currentEvents.add(Event(eventType: events.EventType.Quit)) - of WM_KEYDOWN, WM_SYSKEYDOWN: - let key = MapLeftRightKeys(INT(wParam), lParam) - currentEvents.add(Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN))) - of WM_KEYUP, WM_SYSKEYUP: - let key = MapLeftRightKeys(INT(wParam), lParam) - currentEvents.add(Event(eventType: KeyReleased, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN))) - of WM_LBUTTONDOWN: - currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse1)) - of WM_LBUTTONUP: - currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse1)) - of WM_MBUTTONDOWN: - currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse2)) - of WM_MBUTTONUP: - currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse2)) - of WM_RBUTTONDOWN: - currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse3)) - of WM_RBUTTONUP: - currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse3)) - of WM_MOUSEMOVE: - currentEvents.add(Event(eventType: events.MouseMoved, x: GET_X_LPARAM(lParam), y: GET_Y_LPARAM(lParam))) - else: - return DefWindowProc(hwnd, uMsg, wParam, lParam) - - -proc createWindow*(title: string): NativeWindow = - result.hInstance = HINSTANCE(GetModuleHandle(nil)) - var - windowClassName = T"EngineWindowClass" - windowName = T(title) - windowClass = WNDCLASSEX( - cbSize: UINT(WNDCLASSEX.sizeof), - lpfnWndProc: WindowHandler, - hInstance: result.hInstance, - lpszClassName: windowClassName, - ) - - if(RegisterClassEx(addr(windowClass)) == 0): - raise newException(Exception, "Unable to register window class") - - result.hwnd = CreateWindowEx( - DWORD(0), - windowClassName, - windowName, - DWORD(WS_OVERLAPPEDWINDOW), - CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, - HMENU(0), - HINSTANCE(0), - result.hInstance, - nil - ) - - discard ShowWindow(result.hwnd, SW_SHOW) - discard ShowCursor(false) - -proc destroy*(window: NativeWindow) = - discard - -proc size*(window: NativeWindow): (int, int) = - var rect: RECT - checkWin32Result GetWindowRect(window.hwnd, addr(rect)) - (int(rect.right - rect.left), int(rect.bottom - rect.top)) - -proc pendingEvents*(window: NativeWindow): seq[Event] = - # empty queue - currentEvents = newSeq[Event]() - var msg: MSG - # fill queue - while PeekMessage(addr(msg), window.hwnd, 0, 0, PM_REMOVE): - TranslateMessage(addr(msg)) - DispatchMessage(addr(msg)) - return currentEvents - -proc getMousePosition*(window: NativeWindow): Option[Vec2f] = - var p: POINT - let res = GetCursorPos(addr(p)) - if res: - return some(Vec2f([float32(p.x), float32(p.y)])) - return none(Vec2f)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/semicongine/platform/windows/window.nim Thu Apr 27 00:36:45 2023 +0700 @@ -0,0 +1,114 @@ +import std/options +import winim + +import ./virtualkey_map +import ../../events +import ../../math/vector + +type + NativeWindow* = object + hinstance*: HINSTANCE + hwnd*: HWND + +# sorry, have to use module-global variable to capture windows events +var currentEvents: seq[Event] + +template checkWin32Result*(call: untyped) = + let value = call + if value == 0: + raise newException(Exception, "Win32 error: " & astToStr(call) & " returned " & $value) + + +proc MapLeftRightKeys(key: INT, lparam: LPARAM): INT = + case key + of VK_SHIFT: + MapVirtualKey(UINT((lParam and 0x00ff0000) shr 16), MAPVK_VSC_TO_VK_EX) + of VK_CONTROL: + if (lParam and 0x01000000) == 0: VK_LCONTROL else: VK_RCONTROL + of VK_MENU: + if (lParam and 0x01000000) == 0: VK_LMENU else: VK_RMENU + else: + key + +proc WindowHandler(hwnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM): LRESULT {.stdcall.} = + case uMsg + of WM_DESTROY: + currentEvents.add(Event(eventType: events.EventType.Quit)) + of WM_KEYDOWN, WM_SYSKEYDOWN: + let key = MapLeftRightKeys(INT(wParam), lParam) + currentEvents.add(Event(eventType: KeyPressed, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN))) + of WM_KEYUP, WM_SYSKEYUP: + let key = MapLeftRightKeys(INT(wParam), lParam) + currentEvents.add(Event(eventType: KeyReleased, key: KeyTypeMap.getOrDefault(key, Key.UNKNOWN))) + of WM_LBUTTONDOWN: + currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse1)) + of WM_LBUTTONUP: + currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse1)) + of WM_MBUTTONDOWN: + currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse2)) + of WM_MBUTTONUP: + currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse2)) + of WM_RBUTTONDOWN: + currentEvents.add(Event(eventType: MousePressed, button: MouseButton.Mouse3)) + of WM_RBUTTONUP: + currentEvents.add(Event(eventType: MouseReleased, button: MouseButton.Mouse3)) + of WM_MOUSEMOVE: + currentEvents.add(Event(eventType: events.MouseMoved, x: GET_X_LPARAM(lParam), y: GET_Y_LPARAM(lParam))) + else: + return DefWindowProc(hwnd, uMsg, wParam, lParam) + + +proc createWindow*(title: string): NativeWindow = + result.hInstance = HINSTANCE(GetModuleHandle(nil)) + var + windowClassName = T"EngineWindowClass" + windowName = T(title) + windowClass = WNDCLASSEX( + cbSize: UINT(WNDCLASSEX.sizeof), + lpfnWndProc: WindowHandler, + hInstance: result.hInstance, + lpszClassName: windowClassName, + ) + + if(RegisterClassEx(addr(windowClass)) == 0): + raise newException(Exception, "Unable to register window class") + + result.hwnd = CreateWindowEx( + DWORD(0), + windowClassName, + windowName, + DWORD(WS_OVERLAPPEDWINDOW), + CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, + HMENU(0), + HINSTANCE(0), + result.hInstance, + nil + ) + + discard ShowWindow(result.hwnd, SW_SHOW) + discard ShowCursor(false) + +proc destroy*(window: NativeWindow) = + discard + +proc size*(window: NativeWindow): (int, int) = + var rect: RECT + checkWin32Result GetWindowRect(window.hwnd, addr(rect)) + (int(rect.right - rect.left), int(rect.bottom - rect.top)) + +proc pendingEvents*(window: NativeWindow): seq[Event] = + # empty queue + currentEvents = newSeq[Event]() + var msg: MSG + # fill queue + while PeekMessage(addr(msg), window.hwnd, 0, 0, PM_REMOVE): + TranslateMessage(addr(msg)) + DispatchMessage(addr(msg)) + return currentEvents + +proc getMousePosition*(window: NativeWindow): Option[Vec2f] = + var p: POINT + let res = GetCursorPos(addr(p)) + if res: + return some(Vec2f([float32(p.x), float32(p.y)])) + return none(Vec2f)