feat(input): #89 cursor capture — Input.cursor_mode (hide/lock/confine)
A windowed action game can hide the OS cursor and lock/confine the mouse to the window. Input.cursor_mode(mode): 0 normal, 1 hidden (draw your own reticle), 2 locked (hidden + dissociated — the mouse feeds relative motion via Input.mouse_dx/dy and Input.mouse_x/y is a clamped virtual cursor, FPS/twin-stick aim), 3 confined (dissociated but visible; the mouse can't leave the window). The platform auto-releases (shows + reconnects) while the window is not key (Cmd-Tab) and on close, so the cursor is never left captured. Headless it is a no-op (DCE'd). Native macOS impl in cocoa.ll: [NSCursor hide]/[unhide] (ref-counted, toggled only on change so the count stays balanced across focus changes), CGAssociateMouseAndMouseCursorPosition, and CGGetLastMouseDelta for the relative virtual cursor, behind a new win_cursor_mode intrinsic. Windowed-only behaviour (not in the headless golden suite); example compiles headless and links windowed. Full suite 115/0, fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f3f1336882
commit
3eb5447f74
11 changed files with 26543 additions and 26240 deletions
3
changes/cursor-capture.md
Normal file
3
changes/cursor-capture.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
bump: minor
|
||||||
|
type: feat
|
||||||
|
Cursor capture (#89) — `Input.cursor_mode(mode)` hides / locks / confines the OS mouse for a windowed game: `0` normal (visible, free), `1` hidden (hide the OS cursor while focused so a game draws its own reticle), `2` locked (hidden + dissociated — the mouse feeds *relative* motion through `Input.mouse_dx/dy`, and `Input.mouse_x/y` becomes a clamped virtual cursor, the FPS / twin-stick aim mode), `3` confined (dissociated but visible; the mouse cannot leave the window). The platform auto-releases (shows + reconnects the cursor) while the window is not key (Cmd-Tab) and on close, so the cursor is never left captured. Adds the native macOS implementation in `cocoa.ll` (`[NSCursor hide]/[unhide]`, ref-counted and toggled only on change; `CGAssociateMouseAndMouseCursorPosition`; `CGGetLastMouseDelta` for the relative virtual cursor) behind a new `win_cursor_mode` intrinsic; headless / non-windowed it is a no-op (DCE'd). Example: `examples/library/cursor_capture.ludic`.
|
||||||
34
docs/language/input/input-cursor_mode.md
Normal file
34
docs/language/input/input-cursor_mode.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
id: input-cursor_mode
|
||||||
|
name: Input.cursor_mode
|
||||||
|
category: input
|
||||||
|
kind: namespace-method
|
||||||
|
tokens: Input.cursor_mode
|
||||||
|
sig: Input.cursor_mode(mode)
|
||||||
|
tip: Hide / lock / confine the OS mouse cursor to the window (windowed).
|
||||||
|
order: 36
|
||||||
|
ns: Input
|
||||||
|
member: cursor_mode
|
||||||
|
---
|
||||||
|
|
||||||
|
Sets how a windowed game captures the OS mouse cursor:
|
||||||
|
|
||||||
|
- <code>0</code> normal (default) — the cursor is visible and free.
|
||||||
|
- <code>1</code> hidden — the cursor is hidden while the window is focused, so a game can draw its own reticle.
|
||||||
|
- <code>2</code> locked — hidden and dissociated from the mouse; the mouse feeds <em>relative</em> motion through <a href="input-mouse_dx.html"><code>Input.mouse_dx</code></a> / <code>mouse_dy</code>, and <a href="input-mouse_x.html"><code>Input.mouse_x</code></a> / <code>mouse_y</code> is a clamped virtual cursor — the FPS / twin-stick aim mode.
|
||||||
|
- <code>3</code> confined — dissociated but visible; the mouse cannot leave the window.
|
||||||
|
|
||||||
|
The platform **auto-releases** (shows the cursor and reconnects it) while the window is not the key window (Cmd-Tab), and restores it on window close, so the cursor is never left captured. On a headless / non-windowed build it is a no-op.
|
||||||
|
|
||||||
|
```ludic
|
||||||
|
program Aim {
|
||||||
|
property Tag { n: int = 0 }
|
||||||
|
model P { Tag }
|
||||||
|
@OnStart handler Boot { Input.cursor_mode(2) } # lock + hide for aiming
|
||||||
|
handler Draw phase Render {
|
||||||
|
Screen.clear(0x101018)
|
||||||
|
Screen.fill_rectangle(Input.mouse_x() - 2, Input.mouse_y() - 2, 4, 4, 0xffcc00)
|
||||||
|
Screen.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
24
examples/library/cursor_capture.ludic
Normal file
24
examples/library/cursor_capture.ludic
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
# cursor_capture.ludic — cursor capture (#89): a windowed game hides / locks the
|
||||||
|
# OS mouse and draws its own reticle from the mouse position. Input.cursor_mode is
|
||||||
|
# a no-op headless (it only touches the platform cursor on a windowed build), so
|
||||||
|
# this compiles and links both ways; the capture behaviour is windowed-only.
|
||||||
|
#
|
||||||
|
# Run windowed: bin/ludicc examples/library/cursor_capture.ludic -o /tmp/aim && /tmp/aim
|
||||||
|
program CursorCapture {
|
||||||
|
property Tag { n: int = 0 }
|
||||||
|
model P { Tag }
|
||||||
|
|
||||||
|
@OnStart handler Boot {
|
||||||
|
Input.cursor_mode(2) # 2 = locked: hide the OS cursor, feed relative motion
|
||||||
|
}
|
||||||
|
|
||||||
|
handler Draw phase Render {
|
||||||
|
Screen.clear(0x101018)
|
||||||
|
# a reticle drawn at the captured mouse position (a virtual cursor while locked)
|
||||||
|
let mx = Input.mouse_x()
|
||||||
|
let my = Input.mouse_y()
|
||||||
|
Screen.fill_rectangle(mx - 3, my - 1, 7, 2, 0xffcc00)
|
||||||
|
Screen.fill_rectangle(mx - 1, my - 3, 2, 7, 0xffcc00)
|
||||||
|
Screen.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,10 @@ declare void @CGImageRelease(ptr)
|
||||||
declare void @CGContextRelease(ptr)
|
declare void @CGContextRelease(ptr)
|
||||||
declare void @CGColorSpaceRelease(ptr)
|
declare void @CGColorSpaceRelease(ptr)
|
||||||
declare i32 @usleep(i32)
|
declare i32 @usleep(i32)
|
||||||
|
; #89 — cursor capture: NSCursor hide/unhide (ref-counted) + the CoreGraphics
|
||||||
|
; mouse-association / relative-delta calls.
|
||||||
|
declare i32 @CGAssociateMouseAndMouseCursorPosition(i32)
|
||||||
|
declare void @CGGetLastMouseDelta(ptr, ptr)
|
||||||
|
|
||||||
@.c_app = private unnamed_addr constant [14 x i8] c"NSApplication\00"
|
@.c_app = private unnamed_addr constant [14 x i8] c"NSApplication\00"
|
||||||
@.c_win = private unnamed_addr constant [9 x i8] c"NSWindow\00"
|
@.c_win = private unnamed_addr constant [9 x i8] c"NSWindow\00"
|
||||||
|
|
@ -49,6 +53,10 @@ declare i32 @usleep(i32)
|
||||||
@.c_view = private unnamed_addr constant [7 x i8] c"NSView\00"
|
@.c_view = private unnamed_addr constant [7 x i8] c"NSView\00"
|
||||||
@.c_gctx = private unnamed_addr constant [18 x i8] c"NSGraphicsContext\00"
|
@.c_gctx = private unnamed_addr constant [18 x i8] c"NSGraphicsContext\00"
|
||||||
@.c_ludic = private unnamed_addr constant [10 x i8] c"LudicView\00"
|
@.c_ludic = private unnamed_addr constant [10 x i8] c"LudicView\00"
|
||||||
|
@.c_cursor = private unnamed_addr constant [9 x i8] c"NSCursor\00"
|
||||||
|
@.s_hide = private unnamed_addr constant [5 x i8] c"hide\00"
|
||||||
|
@.s_unhide = private unnamed_addr constant [7 x i8] c"unhide\00"
|
||||||
|
@.s_iskey = private unnamed_addr constant [12 x i8] c"isKeyWindow\00"
|
||||||
|
|
||||||
@.s_shared = private unnamed_addr constant [18 x i8] c"sharedApplication\00"
|
@.s_shared = private unnamed_addr constant [18 x i8] c"sharedApplication\00"
|
||||||
@.s_policy = private unnamed_addr constant [21 x i8] c"setActivationPolicy:\00"
|
@.s_policy = private unnamed_addr constant [21 x i8] c"setActivationPolicy:\00"
|
||||||
|
|
@ -143,6 +151,14 @@ declare i32 @usleep(i32)
|
||||||
; #51 — up to 8 trackpad touch points, [active, x, y] each (framebuffer pixels),
|
; #51 — up to 8 trackpad touch points, [active, x, y] each (framebuffer pixels),
|
||||||
; refreshed by the view's touchesBegan/Moved/Ended handlers.
|
; refreshed by the view's touchesBegan/Moved/Ended handlers.
|
||||||
@W_touch = internal global [24 x i32] zeroinitializer
|
@W_touch = internal global [24 x i32] zeroinitializer
|
||||||
|
; #89 — cursor capture. @W_cursor_mode is the desired mode (0 normal, 1 hidden,
|
||||||
|
; 2 locked+relative, 3 confined). @W_cursor_hidden / @W_cursor_assoc track the
|
||||||
|
; currently-applied NSCursor hide (ref-counted, so we only toggle on a change) and
|
||||||
|
; the CGAssociateMouseAndMouseCursorPosition state, so focus loss auto-releases and
|
||||||
|
; refocus re-acquires without unbalancing the hide counter.
|
||||||
|
@W_cursor_mode = internal global i32 0
|
||||||
|
@W_cursor_hidden = internal global i32 0
|
||||||
|
@W_cursor_assoc = internal global i32 1
|
||||||
|
|
||||||
; -drawRect: — blit the framebuffer into the view.
|
; -drawRect: — blit the framebuffer into the view.
|
||||||
; The NSRect argument is ignored, so it never appears in this signature.
|
; The NSRect argument is ignored, so it never appears in this signature.
|
||||||
|
|
@ -477,6 +493,7 @@ dosend:
|
||||||
%fwd = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %app, ptr %sel_send, ptr %ev)
|
%fwd = call ptr (ptr, ptr, ptr) @objc_msgSend(ptr %app, ptr %sel_send, ptr %ev)
|
||||||
br label %pump
|
br label %pump
|
||||||
finish:
|
finish:
|
||||||
|
call void @win_cursor_maintain() ; #89 — keep the cursor state (auto-release on focus loss)
|
||||||
; a closed window ends the run
|
; a closed window ends the run
|
||||||
%win = load ptr, ptr @W_win
|
%win = load ptr, ptr @W_win
|
||||||
%hw = icmp ne ptr %win, null
|
%hw = icmp ne ptr %win, null
|
||||||
|
|
@ -519,6 +536,9 @@ entry:
|
||||||
|
|
||||||
define void @win_close() {
|
define void @win_close() {
|
||||||
entry:
|
entry:
|
||||||
|
; #89 — restore the cursor so it is never left hidden / dissociated after quit.
|
||||||
|
store i32 0, ptr @W_cursor_mode
|
||||||
|
call void @cursor_apply(i32 0, i32 1)
|
||||||
store i32 0, ptr @W_running
|
store i32 0, ptr @W_running
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
@ -550,6 +570,41 @@ entry:
|
||||||
; #51 — read the live cursor position from the window (points, origin
|
; #51 — read the live cursor position from the window (points, origin
|
||||||
; bottom-left) and convert to framebuffer pixels, y-flipped so row 0 is the
|
; bottom-left) and convert to framebuffer pixels, y-flipped so row 0 is the
|
||||||
; top, matching the framebuffer the game draws into.
|
; top, matching the framebuffer the game draws into.
|
||||||
|
; #89 — in a relative mode (locked / confined) the OS cursor is dissociated, so
|
||||||
|
; mouseLocation is frozen; accumulate CGGetLastMouseDelta into a virtual cursor
|
||||||
|
; (clamped to the framebuffer) so Input.mouse_dx/dy report the motion and
|
||||||
|
; Input.mouse_x/y give a reticle position.
|
||||||
|
%cmode = load i32, ptr @W_cursor_mode
|
||||||
|
%isrel = icmp sge i32 %cmode, 2
|
||||||
|
br i1 %isrel, label %reldelta, label %abspos
|
||||||
|
reldelta:
|
||||||
|
%dxp = alloca i32
|
||||||
|
%dyp = alloca i32
|
||||||
|
call void @CGGetLastMouseDelta(ptr %dxp, ptr %dyp)
|
||||||
|
%ddx = load i32, ptr %dxp
|
||||||
|
%ddy = load i32, ptr %dyp
|
||||||
|
%omx = load i32, ptr @W_mx
|
||||||
|
%omy = load i32, ptr @W_my
|
||||||
|
%nmx = add i32 %omx, %ddx
|
||||||
|
%nmy = add i32 %omy, %ddy
|
||||||
|
; clamp x to [0, fbw-1]
|
||||||
|
%fbwv = load i32, ptr @W_fbw
|
||||||
|
%fbwm = sub i32 %fbwv, 1
|
||||||
|
%xlo = icmp slt i32 %nmx, 0
|
||||||
|
%nmx1 = select i1 %xlo, i32 0, i32 %nmx
|
||||||
|
%xhi = icmp sgt i32 %nmx1, %fbwm
|
||||||
|
%nmx2 = select i1 %xhi, i32 %fbwm, i32 %nmx1
|
||||||
|
; clamp y to [0, fbh-1]
|
||||||
|
%fbhv = load i32, ptr @W_fbh
|
||||||
|
%fbhm = sub i32 %fbhv, 1
|
||||||
|
%ylo = icmp slt i32 %nmy, 0
|
||||||
|
%nmy1 = select i1 %ylo, i32 0, i32 %nmy
|
||||||
|
%yhi = icmp sgt i32 %nmy1, %fbhm
|
||||||
|
%nmy2 = select i1 %yhi, i32 %fbhm, i32 %nmy1
|
||||||
|
store i32 %nmx2, ptr @W_mx
|
||||||
|
store i32 %nmy2, ptr @W_my
|
||||||
|
br label %emit
|
||||||
|
abspos:
|
||||||
%win = load ptr, ptr @W_win
|
%win = load ptr, ptr @W_win
|
||||||
%nowin = icmp eq ptr %win, null
|
%nowin = icmp eq ptr %win, null
|
||||||
br i1 %nowin, label %emit, label %qpos
|
br i1 %nowin, label %emit, label %qpos
|
||||||
|
|
@ -587,6 +642,84 @@ emit:
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; ============================================================================
|
||||||
|
; #89 — cursor capture. Apply the currently-wanted hidden / associated state,
|
||||||
|
; toggling only on a change so the ref-counted [NSCursor hide]/[unhide] stays
|
||||||
|
; balanced across frames and focus changes.
|
||||||
|
define void @cursor_apply(i32 %wh, i32 %wa) {
|
||||||
|
entry:
|
||||||
|
%ch = load i32, ptr @W_cursor_hidden
|
||||||
|
%hdiff = icmp ne i32 %ch, %wh
|
||||||
|
br i1 %hdiff, label %doh, label %skiph
|
||||||
|
doh:
|
||||||
|
%curscls = call ptr @objc_getClass(ptr @.c_cursor)
|
||||||
|
%wanth = icmp ne i32 %wh, 0
|
||||||
|
br i1 %wanth, label %hide, label %unhide
|
||||||
|
hide:
|
||||||
|
%selhide = call ptr @sel_registerName(ptr @.s_hide)
|
||||||
|
%r1 = call ptr (ptr, ptr) @objc_msgSend(ptr %curscls, ptr %selhide)
|
||||||
|
br label %storeh
|
||||||
|
unhide:
|
||||||
|
%selunh = call ptr @sel_registerName(ptr @.s_unhide)
|
||||||
|
%r2 = call ptr (ptr, ptr) @objc_msgSend(ptr %curscls, ptr %selunh)
|
||||||
|
br label %storeh
|
||||||
|
storeh:
|
||||||
|
store i32 %wh, ptr @W_cursor_hidden
|
||||||
|
br label %skiph
|
||||||
|
skiph:
|
||||||
|
%ca = load i32, ptr @W_cursor_assoc
|
||||||
|
%adiff = icmp ne i32 %ca, %wa
|
||||||
|
br i1 %adiff, label %doa, label %skipa
|
||||||
|
doa:
|
||||||
|
%rc = call i32 @CGAssociateMouseAndMouseCursorPosition(i32 %wa)
|
||||||
|
store i32 %wa, ptr @W_cursor_assoc
|
||||||
|
br label %skipa
|
||||||
|
skipa:
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; Re-derive the wanted state from the mode + window key status, then apply it.
|
||||||
|
; Called on a mode change and once per frame from win_poll, so losing focus
|
||||||
|
; (Cmd-Tab) auto-releases the cursor and refocusing re-acquires it.
|
||||||
|
define void @win_cursor_maintain() {
|
||||||
|
entry:
|
||||||
|
%mode = load i32, ptr @W_cursor_mode
|
||||||
|
%win = load ptr, ptr @W_win
|
||||||
|
%nowin = icmp eq ptr %win, null
|
||||||
|
br i1 %nowin, label %release, label %askkey
|
||||||
|
askkey:
|
||||||
|
%selk = call ptr @sel_registerName(ptr @.s_iskey)
|
||||||
|
%k8 = call i8 (ptr, ptr) @objc_msgSend(ptr %win, ptr %selk)
|
||||||
|
%iskey = icmp ne i8 %k8, 0
|
||||||
|
br i1 %iskey, label %keyed, label %release
|
||||||
|
release:
|
||||||
|
; not focused (or no window): show the cursor and reconnect it
|
||||||
|
call void @cursor_apply(i32 0, i32 1)
|
||||||
|
ret void
|
||||||
|
keyed:
|
||||||
|
; want_hidden when mode is 1 (hidden) or 2 (locked)
|
||||||
|
%h1 = icmp eq i32 %mode, 1
|
||||||
|
%h2 = icmp eq i32 %mode, 2
|
||||||
|
%wh = or i1 %h1, %h2
|
||||||
|
%whi = zext i1 %wh to i32
|
||||||
|
; dissociate (relative) when mode is 2 (locked) or 3 (confined)
|
||||||
|
%a2 = icmp eq i32 %mode, 2
|
||||||
|
%a3 = icmp eq i32 %mode, 3
|
||||||
|
%dis = or i1 %a2, %a3
|
||||||
|
%wa = xor i1 %dis, true
|
||||||
|
%wai = zext i1 %wa to i32
|
||||||
|
call void @cursor_apply(i32 %whi, i32 %wai)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
; the Ludic entry point: set the desired cursor mode and apply it now.
|
||||||
|
define void @win_cursor_mode(i32 %mode) {
|
||||||
|
entry:
|
||||||
|
store i32 %mode, ptr @W_cursor_mode
|
||||||
|
call void @win_cursor_maintain()
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
; ============================================================================
|
; ============================================================================
|
||||||
; #51 — gamepad, via GameController.framework.
|
; #51 — gamepad, via GameController.framework.
|
||||||
;
|
;
|
||||||
|
|
|
||||||
|
|
@ -402,6 +402,20 @@ function input_strength(name: pointer) -> fixed {
|
||||||
return fixed(0)
|
return fixed(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ---- cursor capture (#89) --------------------------------------------------
|
||||||
|
# Set the OS cursor mode for a windowed game:
|
||||||
|
# 0 normal — cursor visible and free (default).
|
||||||
|
# 1 hidden — cursor hidden while the window is focused (draw your own reticle).
|
||||||
|
# 2 locked — hidden + dissociated; the mouse feeds relative motion through
|
||||||
|
# Input.mouse_dx/dy and Input.mouse_x/y is a clamped virtual cursor
|
||||||
|
# (the FPS / twin-stick capture mode).
|
||||||
|
# 3 confined — dissociated but visible; the mouse cannot leave the window.
|
||||||
|
# The platform auto-releases (shows + reconnects) while the window is not key
|
||||||
|
# (Cmd-Tab) and on close. Headless / non-windowed: a no-op.
|
||||||
|
function input_cursor_mode(mode: int) -> void {
|
||||||
|
if is_windowed() { win_cursor_mode(mode) }
|
||||||
|
}
|
||||||
|
|
||||||
# ---- mouse -----------------------------------------------------------------
|
# ---- mouse -----------------------------------------------------------------
|
||||||
function input_mouse_x() -> int { in_init(); return in_mx }
|
function input_mouse_x() -> int { in_init(); return in_mx }
|
||||||
function input_mouse_y() -> int { in_init(); return in_my }
|
function input_mouse_y() -> int { in_init(); return in_my }
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,7 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
||||||
if (meth == "touch_x") { bare = "input_touch_x"; push(labels, "index") }
|
if (meth == "touch_x") { bare = "input_touch_x"; push(labels, "index") }
|
||||||
if (meth == "touch_y") { bare = "input_touch_y"; push(labels, "index") }
|
if (meth == "touch_y") { bare = "input_touch_y"; push(labels, "index") }
|
||||||
if (meth == "set_touch") { bare = "input_set_touch"; push(labels, "index"); push(labels, "x"); push(labels, "y"); push(labels, "active") }
|
if (meth == "set_touch") { bare = "input_set_touch"; push(labels, "index"); push(labels, "x"); push(labels, "y"); push(labels, "active") }
|
||||||
|
if (meth == "cursor_mode") { bare = "input_cursor_mode"; push(labels, "mode") } # #89 cursor capture
|
||||||
}
|
}
|
||||||
# Audio.* (#22) — sfx/music playback over the platform audio backend
|
# Audio.* (#22) — sfx/music playback over the platform audio backend
|
||||||
# (runtime/native/audio.ludic + audio.ll). Playback is out-of-band; the
|
# (runtime/native/audio.ludic + audio.ll). Playback is out-of-band; the
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ function emit_header() -> void {
|
||||||
emith("declare void @win_mouse(ptr)\n")
|
emith("declare void @win_mouse(ptr)\n")
|
||||||
emith("declare void @win_pad(ptr)\n")
|
emith("declare void @win_pad(ptr)\n")
|
||||||
emith("declare void @win_touch(ptr)\n")
|
emith("declare void @win_touch(ptr)\n")
|
||||||
|
emith("declare void @win_cursor_mode(i32)\n")
|
||||||
emith("declare ptr @snd_load(ptr)\n") # #22 audio backend (audio.ll)
|
emith("declare ptr @snd_load(ptr)\n") # #22 audio backend (audio.ll)
|
||||||
emith("declare void @snd_play(ptr, i32, i32, i32)\n")
|
emith("declare void @snd_play(ptr, i32, i32, i32)\n")
|
||||||
emith("declare void @snd_stop(ptr)\n")
|
emith("declare void @snd_stop(ptr)\n")
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ function is_intrinsic2(name: pointer) -> bool {
|
||||||
if (name == "win_running") or (name == "win_close") { return true }
|
if (name == "win_running") or (name == "win_close") { return true }
|
||||||
if (name == "win_held") or (name == "win_mouse") { return true } # #50 device layer
|
if (name == "win_held") or (name == "win_mouse") { return true } # #50 device layer
|
||||||
if (name == "win_pad") or (name == "win_touch") { return true } # #51 gamepad / touch
|
if (name == "win_pad") or (name == "win_touch") { return true } # #51 gamepad / touch
|
||||||
|
if (name == "win_cursor_mode") { return true } # #89 cursor capture
|
||||||
if (name == "snd_load") or (name == "snd_play") or (name == "snd_stop") { return true } # #22 audio
|
if (name == "snd_load") or (name == "snd_play") or (name == "snd_stop") { return true } # #22 audio
|
||||||
if (name == "snd_playing") or (name == "snd_set_volume") or (name == "snd_set_rate") { return true }
|
if (name == "snd_playing") or (name == "snd_set_volume") or (name == "snd_set_rate") { return true }
|
||||||
if (name == "hs_req_new") or (name == "hs_req_header") or (name == "hs_req_body") { return true } # #6 HTTP
|
if (name == "hs_req_new") or (name == "hs_req_header") or (name == "hs_req_body") { return true } # #6 HTTP
|
||||||
|
|
@ -65,6 +66,8 @@ function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
||||||
# words/point [active, x, y]. Both windowed-only, DCE'd headless.
|
# words/point [active, x, y]. Both windowed-only, DCE'd headless.
|
||||||
if (name == "win_pad") { let a = arg_code(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
if (name == "win_pad") { let a = arg_code(e, 0); emit(" call void @win_pad(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
||||||
if (name == "win_touch") { let a = arg_code(e, 0); emit(" call void @win_touch(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
if (name == "win_touch") { let a = arg_code(e, 0); emit(" call void @win_touch(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
||||||
|
# #89 — set the OS cursor capture mode (windowed only; DCE'd headless).
|
||||||
|
if (name == "win_cursor_mode") { let a = arg_code(e, 0); emit(" call void @win_cursor_mode(i32 "); emit(a); emit(")\n"); return val("0", "void") }
|
||||||
# #22 audio backend — AVAudioPlayer via audio.ll; windowed-only, DCE'd headless.
|
# #22 audio backend — AVAudioPlayer via audio.ll; windowed-only, DCE'd headless.
|
||||||
if (name == "snd_load") { let a = arg_code(e, 0); return val(emit_bind(`call ptr @snd_load(ptr {a})`), "pointer") }
|
if (name == "snd_load") { let a = arg_code(e, 0); return val(emit_bind(`call ptr @snd_load(ptr {a})`), "pointer") }
|
||||||
if (name == "snd_play") {
|
if (name == "snd_play") {
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ function p_postfix() -> Node {
|
||||||
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "bind" or e.s == "rebind" or e.s == "poll" or e.s == "down" or e.s == "pressed" or e.s == "record" or e.s == "replay" or e.s == "action" or e.s == "bind_pad" or e.s == "active" or e.s == "just_pressed" or e.s == "just_released") { g_uses_input = true }
|
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "bind" or e.s == "rebind" or e.s == "poll" or e.s == "down" or e.s == "pressed" or e.s == "record" or e.s == "replay" or e.s == "action" or e.s == "bind_pad" or e.s == "active" or e.s == "just_pressed" or e.s == "just_released") { g_uses_input = true }
|
||||||
# #50 device layer — any of the multi-key / analog / mouse / gamepad / touch
|
# #50 device layer — any of the multi-key / analog / mouse / gamepad / touch
|
||||||
# methods also splices input.ludic (Input.key stays bare, no runtime).
|
# methods also splices input.ludic (Input.key stays bare, no runtime).
|
||||||
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "key_down" or e.s == "key_pressed" or e.s == "key_released" or e.s == "press" or e.s == "release" or e.s == "axis" or e.s == "axis_i" or e.s == "vector" or e.s == "strength" or e.s == "mouse_x" or e.s == "mouse_y" or e.s == "mouse_dx" or e.s == "mouse_dy" or e.s == "mouse_down" or e.s == "wheel" or e.s == "set_mouse" or e.s == "pad_connected" or e.s == "pad_button" or e.s == "pad_axis" or e.s == "set_pad" or e.s == "touch_count" or e.s == "touch_x" or e.s == "touch_y" or e.s == "set_touch") { g_uses_input = true }
|
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "key_down" or e.s == "key_pressed" or e.s == "key_released" or e.s == "press" or e.s == "release" or e.s == "axis" or e.s == "axis_i" or e.s == "vector" or e.s == "strength" or e.s == "mouse_x" or e.s == "mouse_y" or e.s == "mouse_dx" or e.s == "mouse_dy" or e.s == "mouse_down" or e.s == "wheel" or e.s == "set_mouse" or e.s == "pad_connected" or e.s == "pad_button" or e.s == "pad_axis" or e.s == "set_pad" or e.s == "touch_count" or e.s == "touch_x" or e.s == "touch_y" or e.s == "set_touch" or e.s == "cursor_mode") { g_uses_input = true }
|
||||||
# Anim.play/clip/on_frame/fired + Motion.to (#48): the ergonomic writes over
|
# Anim.play/clip/on_frame/fired + Motion.to (#48): the ergonomic writes over
|
||||||
# the SpriteAnim/Motion components live in systems.ludic and use the world
|
# the SpriteAnim/Motion components live in systems.ludic and use the world
|
||||||
# table, so splice it and force the reflection ABI even if the game leaves
|
# table, so splice it and force the reflection ABI even if the game leaves
|
||||||
|
|
|
||||||
52567
selfhost/ludicc.seed.ll
52567
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -355,6 +355,7 @@ function cmd_test() -> int {
|
||||||
smoke("ecs/hello")
|
smoke("ecs/hello")
|
||||||
smoke("events/events")
|
smoke("events/events")
|
||||||
smoke("networking/net_rt")
|
smoke("networking/net_rt")
|
||||||
|
smoke("library/cursor_capture") # #89 Input.cursor_mode compiles (no-op headless; windowed links cocoa.ll)
|
||||||
|
|
||||||
print("== self-hosted front-end binaries (ludicc / ludic) ==")
|
print("== self-hosted front-end binaries (ludicc / ludic) ==")
|
||||||
# the two commands are one multi-call native binary built from the seed with
|
# the two commands are one multi-call native binary built from the seed with
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue