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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
if: ${{ !matrix.emscripten }}
run: |
nim r tests/test_textboxes.nim
nim r -d:silkyTesting tests/test_inputs.nim
nim r -d:silkyTesting -d:nimTypeNames tests/test_inputs.nim
nim r tests/test_atlas_png.nim
nim r -d:silkyTesting tests/test_layout.nim
nim r -d:silkyTesting tests/test_layout_regressions.nim
Expand Down
11 changes: 5 additions & 6 deletions src/silky/contexts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ type
## Multiplies 9-patch borders for a denser atlas.
sliceScale*: int = 1
layoutStack*: seq[LayoutScope]
scaledVertices: seq[DrawerVertex]
textStyle*: string = "Default"
padding*: float32 = 12
theme*: Theme = Theme()
Expand Down Expand Up @@ -965,19 +966,17 @@ proc endUi*(sk: Silky) =
scale = sk.uiScale
quadCount = sk.drawer.layers[NormalLayer].len
needsScale = not (scale ~= 1.0f)
var
quadsPtr: pointer
scaledVertices: seq[DrawerVertex]
var quadsPtr: pointer
if quadCount > 0:
if needsScale:
scaledVertices = newSeqOfCap[DrawerVertex](quadCount)
sk.scaledVertices.setLen(quadCount)
for i in 0 ..< quadCount:
var vertex = sk.drawer.layers[NormalLayer][i]
vertex.pos *= scale
vertex.clipPos *= scale
vertex.clipSize *= scale
scaledVertices.add(vertex)
quadsPtr = cast[pointer](unsafeAddr scaledVertices[0])
sk.scaledVertices[i] = vertex
quadsPtr = cast[pointer](unsafeAddr sk.scaledVertices[0])
else:
quadsPtr = cast[pointer](unsafeAddr sk.drawer.layers[NormalLayer][0])
else:
Expand Down
136 changes: 79 additions & 57 deletions src/silky/internal/inputs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -87,70 +87,92 @@ proc addEvent(inputs: TextInputs, event: InputEvent) =
raise newException(SilkyError, "Too many pending text input events")
inputs.events.add(event)

proc installPress(inputs: TextInputs, window: Window) =
## Installs this handler only when the window's callback changes.
let windowPointer = cast[pointer](window)
# The window owns the callback, so a pointer avoids a reference cycle.
let previousPress = window.onButtonPress
window.onButtonPress = proc(button: Button) =
let window = cast[Window](windowPointer)
if inputs.active and button >= Key0:
var event = inputs.modifiers(window)
event.kind = KeyDown
event.button = button
inputs.consumedKeys.incl(button)
inputs.addEvent(event)
return
if button >= Key0:
inputs.forwardedKeys.incl(button)
inputs.consumedKeys.excl(button)
if previousPress != nil:
previousPress(button)
inputs.callbacks[0] = identity(window.onButtonPress)

proc installRelease(inputs: TextInputs, window: Window) =
## Installs this handler only when the window's callback changes.
let windowPointer = cast[pointer](window)
# The window owns the callback, so a pointer avoids a reference cycle.
let previousRelease = window.onButtonRelease
inputs.releaseCallback = previousRelease
window.onButtonRelease = proc(button: Button) =
let window = cast[Window](windowPointer)
if inputs.active and button >= Key0:
var event = inputs.modifiers(window)
event.kind = KeyUp
event.button = button
inputs.consumedKeys.excl(button)
inputs.addEvent(event)
return
if button >= Key0:
inputs.forwardedKeys.excl(button)
if button in inputs.consumedKeys:
inputs.consumedKeys.excl(button)
return
if previousRelease != nil:
previousRelease(button)
inputs.callbacks[1] = identity(window.onButtonRelease)

proc installRune(inputs: TextInputs, window: Window) =
## Installs this handler only when the window's callback changes.
let windowPointer = cast[pointer](window)
# The window owns the callback, so a pointer avoids a reference cycle.
let previousRune = window.onRune
window.onRune = proc(rune: Rune) =
let window = cast[Window](windowPointer)
if inputs.active:
if inputs.editable:
var event = inputs.modifiers(window)
event.kind = TextInput
event.rune = rune
inputs.addEvent(event)
return
if previousRune != nil:
previousRune(rune)
inputs.callbacks[2] = identity(window.onRune)

proc installFocus(inputs: TextInputs, window: Window) =
## Installs this handler only when the window's callback changes.
let windowPointer = cast[pointer](window)
# The window owns the callback, so a pointer avoids a reference cycle.
let previousFocus = window.onFocusChange
window.onFocusChange = proc() =
inputs.stopInput(cast[Window](windowPointer))
if previousFocus != nil:
previousFocus()
inputs.callbacks[3] = identity(window.onFocusChange)

proc overrideCallbacks(
inputs: TextInputs, window: Window, initialize = false
) =
## Automatically installs handlers for any changed callback slots.
let windowPointer = cast[pointer](window)
# The window owns these callbacks, so a pointer avoids a reference cycle.
## Checks handlers without allocating closure state on unchanged frames.
if initialize or identity(window.onButtonPress) != inputs.callbacks[0]:
let previousPress = window.onButtonPress
window.onButtonPress = proc(button: Button) =
let window = cast[Window](windowPointer)
if inputs.active and button >= Key0:
var event = inputs.modifiers(window)
event.kind = KeyDown
event.button = button
inputs.consumedKeys.incl(button)
inputs.addEvent(event)
return
if button >= Key0:
inputs.forwardedKeys.incl(button)
inputs.consumedKeys.excl(button)
if previousPress != nil:
previousPress(button)
inputs.callbacks[0] = identity(window.onButtonPress)
inputs.installPress(window)
if initialize or identity(window.onButtonRelease) != inputs.callbacks[1]:
let previousRelease = window.onButtonRelease
inputs.releaseCallback = previousRelease
window.onButtonRelease = proc(button: Button) =
let window = cast[Window](windowPointer)
if inputs.active and button >= Key0:
var event = inputs.modifiers(window)
event.kind = KeyUp
event.button = button
inputs.consumedKeys.excl(button)
inputs.addEvent(event)
return
if button >= Key0:
inputs.forwardedKeys.excl(button)
if button in inputs.consumedKeys:
inputs.consumedKeys.excl(button)
return
if previousRelease != nil:
previousRelease(button)
inputs.callbacks[1] = identity(window.onButtonRelease)
inputs.installRelease(window)
if initialize or identity(window.onRune) != inputs.callbacks[2]:
let previousRune = window.onRune
window.onRune = proc(rune: Rune) =
let window = cast[Window](windowPointer)
if inputs.active:
if inputs.editable:
var event = inputs.modifiers(window)
event.kind = TextInput
event.rune = rune
inputs.addEvent(event)
return
if previousRune != nil:
previousRune(rune)
inputs.callbacks[2] = identity(window.onRune)
inputs.installRune(window)
if initialize or identity(window.onFocusChange) != inputs.callbacks[3]:
let previousFocus = window.onFocusChange
window.onFocusChange = proc() =
inputs.stopInput(cast[Window](windowPointer))
if previousFocus != nil:
previousFocus()
inputs.callbacks[3] = identity(window.onFocusChange)
inputs.installFocus(window)

proc newTextInputs*(window: Window): TextInputs =
## Sets up all text input handling directly from the window.
Expand Down
15 changes: 14 additions & 1 deletion tests/test_inputs.nim
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import
std/unicode,
vmath,
silky
silky, silky/internal/inputs

when not defined(silkyTesting):
{.error: "Compile with -d:silkyTesting".}
Expand Down Expand Up @@ -537,4 +537,17 @@ block:
f.frame()
doAssert not f.state.focused and not window.runeInputEnabled

when defined(nimTypeNames):
block:
echo "Testing unchanged input frames allocate zero"
let
window = newWindow()
inputs = newTextInputs(window)
let before = getMemCounters()
for i in 0 ..< 100:
inputs.beginInputFrame(window)
inputs.endInputFrame(window)
let after = getMemCounters()
doAssert after[0] == before[0], "Unchanged handlers must reuse closures."

echo "All ordered text input tests passed"
Loading