Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions examples/scrollwheel.nim
Original file line number Diff line number Diff line change
@@ -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()
33 changes: 21 additions & 12 deletions src/windy/platforms/emscripten/emdefs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -91,64 +91,64 @@ 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.
canvas.addEventListener('dragenter', function(e) {
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);
HEAPU8.set(uint8Array, fileDataPtr);

// 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);
Expand Down Expand Up @@ -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.}
Expand All @@ -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
Expand Down
25 changes: 18 additions & 7 deletions src/windy/platforms/emscripten/platform.nim
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type
fetch*: ptr emscripten_fetch_t
bodyKeepAlive*: string

let
platform = $get_platform()

var
quitRequested*: bool
onQuitRequest*: Callback
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 =
Expand Down