diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9bf3acb0..571c9586 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,7 @@ jobs: - run: nim c examples/fixedsize.nim - run: nim c examples/fullscreen.nim - run: nim c examples/icon.nim + - run: nim c examples/mouse_inside.nim - run: nim c examples/opengl_version.nim - run: nim c examples/openurl.nim - run: nim c examples/property_changes.nim @@ -82,6 +83,8 @@ jobs: if: matrix.os == 'ubuntu-latest' - run: nim c -d:emscripten examples/icon.nim if: matrix.os == 'ubuntu-latest' + - run: nim c -d:emscripten examples/mouse_inside.nim + if: matrix.os == 'ubuntu-latest' - run: nim c -d:emscripten examples/opengl_version.nim if: matrix.os == 'ubuntu-latest' - run: nim c -d:emscripten examples/openurl.nim diff --git a/examples/mouse_inside.nim b/examples/mouse_inside.nim new file mode 100644 index 00000000..057e65ac --- /dev/null +++ b/examples/mouse_inside.nim @@ -0,0 +1,29 @@ +import opengl, os, windy + +let window = newWindow("Windy Mouse Inside", ivec2(1280, 800)) + +window.makeContextCurrent() +loadExtensions() + +var wasInside = window.mouseInside + +echo "Move the mouse in and out of the window." +echo "Red means inside, black means outside." +echo "mouseInside ", wasInside + +window.onFrame = proc() = + let inside = window.mouseInside + if inside != wasInside: + echo "mouseInside ", inside + wasInside = inside + if inside: + glClearColor(1.0f, 0.0f, 0.0f, 1.0f) + else: + glClearColor(0.0f, 0.0f, 0.0f, 1.0f) + glClear(GL_COLOR_BUFFER_BIT) + window.swapBuffers() + +while not window.closeRequested: + if window.minimized or not window.visible: + sleep(10) + pollEvents() diff --git a/src/windy/internal.nim b/src/windy/internal.nim index ae38e094..44dffedd 100644 --- a/src/windy/internal.nim +++ b/src/windy/internal.nim @@ -12,6 +12,7 @@ type closeRequested*, closed*: bool hasPrevMouse*: bool mouseCaptured*: bool + mouseInside*: bool mousePos*, mousePrevPos*: IVec2 buttonDown*, buttonToggle*: set[Button] perFrame*: PerFrame diff --git a/src/windy/platforms/emscripten/emdefs.nim b/src/windy/platforms/emscripten/emdefs.nim index a84a68f6..2a3212fd 100644 --- a/src/windy/platforms/emscripten/emdefs.nim +++ b/src/windy/platforms/emscripten/emdefs.nim @@ -425,6 +425,8 @@ type 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: "".} +proc emscripten_set_mouseenter_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenMouseEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} +proc emscripten_set_mouseleave_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenMouseEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} proc emscripten_set_wheel_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenWheelEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} proc emscripten_set_keydown_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenKeyboardEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} proc emscripten_set_keyup_callback_on_thread*(target: cstring, userData: pointer, useCapture: EM_BOOL, callback: EmscriptenKeyboardEventCallback, targetThread: pointer): EMSCRIPTEN_RESULT {.importc, header: "".} diff --git a/src/windy/platforms/emscripten/platform.nim b/src/windy/platforms/emscripten/platform.nim index b0afc77a..ec8b7942 100644 --- a/src/windy/platforms/emscripten/platform.nim +++ b/src/windy/platforms/emscripten/platform.nim @@ -325,6 +325,10 @@ proc newWindow*( proc mousePos*(window: Window): IVec2 = (window.state.mousePos.vec2 * window.contentScale).ivec2 +proc mouseInside*(window: Window): bool = + ## True while the cursor is over this window's content. + window.state.mouseInside + proc mousePrevPos*(window: Window): IVec2 = (window.state.mousePrevPos.vec2 * window.contentScale).ivec2 @@ -618,11 +622,22 @@ proc onMouseMove(eventType: cint, mouseEvent: ptr EmscriptenMouseEvent, userData window.state.mousePrevPos = window.state.mousePos # Use clientX/clientY as they are more reliably populated window.state.mousePos = ivec2(mouseEvent.clientX.int32, mouseEvent.clientY.int32) + window.state.mouseInside = true window.state.perFrame.mouseDelta += window.state.mousePos - window.state.mousePrevPos if window.onMouseMove != nil: window.onMouseMove() return 1 +proc onMouseEnter(eventType: cint, mouseEvent: ptr EmscriptenMouseEvent, userData: pointer): EM_BOOL {.cdecl.} = + let window = cast[Window](userData) + window.state.mouseInside = true + return 1 + +proc onMouseLeave(eventType: cint, mouseEvent: ptr EmscriptenMouseEvent, userData: pointer): EM_BOOL {.cdecl.} = + let window = cast[Window](userData) + window.state.mouseInside = false + return 1 + proc onWheel(eventType: cint, wheelEvent: ptr EmscriptenWheelEvent, userData: pointer): EM_BOOL {.cdecl.} = ## Handle browser wheel events with OS-specific normalization. let window = cast[Window](userData) @@ -707,6 +722,8 @@ proc setupEventHandlers(window: Window) = discard emscripten_set_mousedown_callback_on_thread(window.canvas, cast[pointer](window), 1, onMouseDown, EM_CALLBACK_THREAD_CONTEXT) discard emscripten_set_mouseup_callback_on_thread(window.canvas, cast[pointer](window), 1, onMouseUp, EM_CALLBACK_THREAD_CONTEXT) discard emscripten_set_mousemove_callback_on_thread(window.canvas, cast[pointer](window), 1, onMouseMove, EM_CALLBACK_THREAD_CONTEXT) + discard emscripten_set_mouseenter_callback_on_thread(window.canvas, cast[pointer](window), 1, onMouseEnter, EM_CALLBACK_THREAD_CONTEXT) + discard emscripten_set_mouseleave_callback_on_thread(window.canvas, cast[pointer](window), 1, onMouseLeave, EM_CALLBACK_THREAD_CONTEXT) # Wheel event discard emscripten_set_wheel_callback_on_thread(window.canvas, cast[pointer](window), 1, onWheel, EM_CALLBACK_THREAD_CONTEXT) diff --git a/src/windy/platforms/linux/x11.nim b/src/windy/platforms/linux/x11.nim index 5f84b366..f9aeeca7 100644 --- a/src/windy/platforms/linux/x11.nim +++ b/src/windy/platforms/linux/x11.nim @@ -1038,6 +1038,7 @@ proc pollEvents(window: Window) = of xeMotion: window.mousePrevPos = window.mousePos window.mousePos = ev.motion.pos + window.state.mouseInside = true window.perFrame.mouseDelta += window.mousePos - window.mousePrevPos if (window.mousePos - window.lastClickPosition).vec2.length > multiClickRadius: window.buttonClicking = {} @@ -1045,6 +1046,13 @@ proc pollEvents(window: Window) = if window.onMouseMove != nil: window.onMouseMove() + of xeEnter: + if ev.crossing.mode == 0: + window.state.mouseInside = true + of xeLeave: + if ev.crossing.mode == 0: + window.state.mouseInside = false + of xeButtonPress, xeButtonRelease: template pushScrollEvent(delta: Vec2) = @@ -1151,6 +1159,10 @@ proc pollEvents(window: Window) = proc mousePos*(window: Window): IVec2 = window.mousePos +proc mouseInside*(window: Window): bool = + ## True while the cursor is over this window's content. + window.state.mouseInside + proc mousePrevPos*(window: Window): IVec2 = window.mousePrevPos diff --git a/src/windy/platforms/macos/platform.nim b/src/windy/platforms/macos/platform.nim index 0b86d90d..89cd437c 100644 --- a/src/windy/platforms/macos/platform.nim +++ b/src/windy/platforms/macos/platform.nim @@ -325,6 +325,7 @@ proc handleMouseMove(window: Window, location: NSPoint) = window.state.mousePrevPos = window.state.mousePos window.state.mousePos = (vec2(x, y) * window.contentScale).ivec2 + window.state.mouseInside = true # Prevent a jump in the mouse delta when focusing a window. if window.state.hasPrevMouse: @@ -615,6 +616,20 @@ proc updateTrackingAreas(self: ID, cmd: SEL): ID {.cdecl.} = callSuper(self, cmd) +proc mouseEntered(self: ID, cmd: SEL, event: NSEvent): ID {.cdecl.} = + let window = windows.forNSWindow(self.NSView.window) + if window == nil: + return + window.state.mouseInside = true + if not window.state.mouseCaptured: + handleMouseMove(window, event.locationInWindow) + +proc mouseExited(self: ID, cmd: SEL, event: NSEvent): ID {.cdecl.} = + let window = windows.forNSWindow(self.NSView.window) + if window == nil: + return + window.state.mouseInside = false + proc mouseMoved(self: ID, cmd: SEL, event: NSEvent): ID {.cdecl.} = let window = windows.forNSWindow(self.NSView.window) if window == nil: @@ -978,6 +993,8 @@ proc init() {.raises: [].} = addMethod "acceptsFirstMouse:", acceptsFirstMouse addMethod "viewDidChangeBackingProperties", viewDidChangeBackingProperties addMethod "updateTrackingAreas", updateTrackingAreas + addMethod "mouseEntered:", mouseEntered + addMethod "mouseExited:", mouseExited addMethod "mouseMoved:", mouseMoved addMethod "mouseDragged:", mouseDragged addMethod "rightMouseDragged:", rightMouseDragged @@ -1013,6 +1030,8 @@ proc init() {.raises: [].} = addMethod "acceptsFirstMouse:", acceptsFirstMouse addMethod "viewDidChangeBackingProperties", viewDidChangeBackingProperties addMethod "updateTrackingAreas", updateTrackingAreas + addMethod "mouseEntered:", mouseEntered + addMethod "mouseExited:", mouseExited addMethod "mouseMoved:", mouseMoved addMethod "mouseDragged:", mouseDragged addMethod "rightMouseDragged:", rightMouseDragged @@ -1338,6 +1357,10 @@ proc nativeView*(window: Window): NSView = proc mousePos*(window: Window): IVec2 = window.state.mousePos +proc mouseInside*(window: Window): bool = + ## True while the cursor is over this window's content. + window.state.mouseInside + proc mousePrevPos*(window: Window): IVec2 = window.state.mousePrevPos diff --git a/src/windy/platforms/win32/platform.nim b/src/windy/platforms/win32/platform.nim index e8486f8b..6aa59ed5 100644 --- a/src/windy/platforms/win32/platform.nim +++ b/src/windy/platforms/win32/platform.nim @@ -966,6 +966,7 @@ proc wndProc( discard GetCursorPos(pos.addr) discard ScreenToClient(window.hWnd, pos.addr) window.state.mousePos = ivec2(pos.x, pos.y) + window.state.mouseInside = true window.state.perFrame.mouseDelta += window.state.mousePos - window.state.mousePrevPos if window.onMouseMove != nil: @@ -982,6 +983,7 @@ proc wndProc( return 0 of WM_MOUSELEAVE: window.trackMouseEventRegistered = false + window.state.mouseInside = false return 0 of WM_SETCURSOR: if LOWORD(lParam) == HTCLIENT: @@ -1374,6 +1376,10 @@ proc icon*(window: Window): Image = proc mousePos*(window: Window): IVec2 = window.state.mousePos +proc mouseInside*(window: Window): bool = + ## True while the cursor is over this window's content. + window.state.mouseInside + proc mousePrevPos*(window: Window): IVec2 = window.state.mousePrevPos diff --git a/tests/run_all.nim b/tests/run_all.nim index fe782d4d..e6b75129 100644 --- a/tests/run_all.nim +++ b/tests/run_all.nim @@ -16,6 +16,7 @@ const Examples = [ "fixedsize", "fullscreen", "icon", + "mouse_inside", "opengl_version", "openurl", "property_changes", diff --git a/tests/run_all_emscripten.nim b/tests/run_all_emscripten.nim index 8f051e51..87b7a5f9 100644 --- a/tests/run_all_emscripten.nim +++ b/tests/run_all_emscripten.nim @@ -21,6 +21,7 @@ const Examples = [ "fixedsize", "fullscreen", "icon", + "mouse_inside", "opengl_version", "openurl", "property_changes",