diff --git a/examples/scrollwheel.nim b/examples/scrollwheel.nim new file mode 100644 index 0000000..39d70a0 --- /dev/null +++ b/examples/scrollwheel.nim @@ -0,0 +1,66 @@ +import + std/[strformat, times], + windy, vmath, boxy, opengl + +var + window: Window + bxy: Boxy + rectCenter: Vec2 + lastScrollAt = epochTime() + hasLastScroll = false + +const ScrollMoveScale = 1.0 + +window = newWindow("Wheel Rectangle Boxy", ivec2(1280, 800)) +window.makeContextCurrent() +loadExtensions() +bxy = newBoxy() + +rectCenter = window.size.vec2 / 2 + +echo "Use mouse wheel to move the white rectangle." +echo "Each scroll prints delta, position, and delta per second." + +proc handleScrollDelta() = + let + delta = window.scrollDelta + deltaVec = vec2(delta.x.float, delta.y.float) + now = epochTime() + if delta.x == 0 and delta.y == 0: + return + + let dt = + if hasLastScroll: + max(0.000001, now - lastScrollAt) + else: + 0.0 + + lastScrollAt = now + hasLastScroll = true + rectCenter += deltaVec * ScrollMoveScale + + let deltaPerSecond = + if dt > 0.0: + deltaVec / dt.float32 + else: + vec2(0'f32, 0'f32) + echo &"scroll delta=({deltaVec.x:.2f}, {deltaVec.y:.2f}) " & + &"delta/s=({deltaPerSecond.x:.2f}, {deltaPerSecond.y:.2f}) " & + &"position=({rectCenter.x:.2f}, {rectCenter.y:.2f})" + +window.onFrame = proc() = + bxy.beginFrame(window.size) + + let + halfSize = window.size.vec2 * 0.5 + topLeft = rectCenter - halfSize / 2 + + bxy.drawRect(rect(0, 0, window.size.x.float, window.size.y.float), color(0.08, 0.08, 0.08, 1.0)) + bxy.drawRect(rect(topLeft.x, topLeft.y, halfSize.x, halfSize.y), color(1.0, 1.0, 1.0, 1.0)) + + bxy.endFrame() + window.swapBuffers() + +while not window.closeRequested: + pollEvents() + handleScrollDelta() diff --git a/src/windy/platforms/emscripten/emdefs.nim b/src/windy/platforms/emscripten/emdefs.nim index f2e0b1e..1e8a294 100644 --- a/src/windy/platforms/emscripten/emdefs.nim +++ b/src/windy/platforms/emscripten/emdefs.nim @@ -91,7 +91,7 @@ EM_JS(void, setup_drag_drop_handlers_internal, (const char* target, void* userDa console.error("Canvas not found for drag and drop setup"); return; } - + // Prevent default drag behaviors on the canvas. // https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API // elements do not support drop by default, you have to prevent default and stop propagation to enable drop. @@ -99,43 +99,43 @@ EM_JS(void, setup_drag_drop_handlers_internal, (const char* target, void* userDa e.preventDefault(); e.stopPropagation(); }, false); - + canvas.addEventListener('dragover', function(e) { e.preventDefault(); e.stopPropagation(); }, false); - + canvas.addEventListener('dragleave', function(e) { e.preventDefault(); e.stopPropagation(); }, false); - + // Handle the drop event. canvas.addEventListener('drop', function(e) { e.preventDefault(); e.stopPropagation(); - + if (!e.dataTransfer || !e.dataTransfer.files || e.dataTransfer.files.length === 0) { return; } - + // Process each dropped file. for (let i = 0; i < e.dataTransfer.files.length; i++) { const file = e.dataTransfer.files[i]; const reader = new FileReader(); - + reader.onload = function(evt) { if (evt.target.readyState !== FileReader.DONE) return; - + const arrayBuffer = evt.target.result; const uint8Array = new Uint8Array(arrayBuffer); - + // read the raw data from the drop event into javascript. // Allocate and copy filename. const fileNameLen = lengthBytesUTF8(file.name) + 1; const fileNamePtr = _malloc(fileNameLen); stringToUTF8(file.name, fileNamePtr, fileNameLen); - + // Allocate memory for file data. const fileDataLen = uint8Array.length; const fileDataPtr = _malloc(fileDataLen); @@ -143,12 +143,12 @@ EM_JS(void, setup_drag_drop_handlers_internal, (const char* target, void* userDa // Call the C helper function. It copies the data into Nim structures. Module._windy_file_drop_callback(userData, fileNamePtr, fileDataPtr, fileDataLen); - + // Free allocated memory. _free(fileNamePtr); _free(fileDataPtr); }; - + reader.readAsArrayBuffer(file); } }, false); @@ -181,6 +181,14 @@ EM_JS(void, set_local_storage, (const char* key, const char* value), { const valueUtf8 = UTF8ToString(value); localStorage.setItem(keyUtf8, valueUtf8); }); + +EM_JS(const char*, get_platform, (), { + var s = navigator.platform || ""; + var len = lengthBytesUTF8(s) + 1; + var buf = _malloc(len); + stringToUTF8(s, buf, len); + return buf; +}); """.} proc get_window_width*(): cint {.importc.} @@ -201,6 +209,7 @@ proc set_cursor*(cursor: cstring) {.importc.} proc getLocalStorageLength*(key: cstring): cint {.importc: "get_local_storage_length".} proc getLocalStorageInto*(output: cstring, maxLen: cint, key: cstring): cint {.importc: "get_local_storage_into".} proc setLocalStorage*(key: cstring, value: cstring) {.importc: "set_local_storage".} +proc get_platform*(): cstring {.importc.} type EMSCRIPTEN_RESULT* = cint diff --git a/src/windy/platforms/emscripten/platform.nim b/src/windy/platforms/emscripten/platform.nim index 51d877e..0e09c4c 100644 --- a/src/windy/platforms/emscripten/platform.nim +++ b/src/windy/platforms/emscripten/platform.nim @@ -48,6 +48,9 @@ type fetch*: ptr emscripten_fetch_t bodyKeepAlive*: string +let + platform = $get_platform() + var quitRequested*: bool onQuitRequest*: Callback @@ -554,11 +557,19 @@ proc onMouseMove(eventType: cint, mouseEvent: ptr EmscriptenMouseEvent, userData 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) - # Normalize web wheel events to match other platforms. - let normalizedDeltaX = wheelEvent.deltaX.float32 - let normalizedDeltaY = wheelEvent.deltaY.float32 - window.state.perFrame.scrollDelta += vec2(normalizedDeltaX, normalizedDeltaY) + + # macOS and Linux both report deltaMode 0 (DOM_DELTA_PIXEL) but with + # very different magnitudes. Use a flat OS-based multiplier instead. + let scale = + if "Mac" in platform: -1.0f + else: 0.2f + let + x = wheelEvent.deltaX.float32 * scale + y = wheelEvent.deltaY.float32 * scale + + window.state.perFrame.scrollDelta += vec2(x, y) if window.onScroll != nil: window.onScroll() return 1 @@ -637,17 +648,17 @@ proc handleRune(window: Window, rune: Rune) = proc windy_file_drop_callback(userData: pointer, fileNamePtr: cstring, fileDataPtr: pointer, fileDataLen: cint) {.exportc, cdecl, codegenDecl: "EMSCRIPTEN_KEEPALIVE $# $#$#".} = ## callback to handle the file drop event. ## EMSCRIPTEN_KEEPALIVE is required to avoid dead code elimination. - + let window = cast[Window](userData) if window == nil or window.onFileDrop == nil: return - + # convert the js data into Nim data. let fileName = $fileNamePtr var fileData = newString(fileDataLen) if fileDataLen > 0: copyMem(fileData[0].addr, fileDataPtr, fileDataLen) - + window.onFileDrop(fileName, fileData) proc getState(fetch: ptr emscripten_fetch_t): EmsHttpRequestState =