From 184f50c4be8c510603b1ccc0e1f48d84a4dadada Mon Sep 17 00:00:00 2001 From: soywiz Date: Wed, 13 Dec 2023 16:30:33 +0100 Subject: [PATCH] Rewrite input section --- .obsidian/workspace.json | 42 +++--- io.md | 0 reference/resources.md | 2 +- reference/testing.md | 11 +- templates/extending.md | 2 - views/views-input.md | 281 ++++++++++----------------------------- 6 files changed, 99 insertions(+), 239 deletions(-) create mode 100644 io.md diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json index 51e0559..e4c6eb6 100644 --- a/.obsidian/workspace.json +++ b/.obsidian/workspace.json @@ -13,7 +13,7 @@ "state": { "type": "markdown", "state": { - "file": "math/utils.md", + "file": "views/views-input.md", "mode": "source", "source": false } @@ -48,7 +48,7 @@ "state": { "type": "search", "state": { - "query": "", + "query": "class KeysEvents", "matchingCase": false, "explainSearch": false, "collapseAll": false, @@ -65,11 +65,12 @@ "state": {} } } - ] + ], + "currentTab": 1 } ], "direction": "horizontal", - "width": 300 + "width": 203.5 }, "right": { "id": "59bc338dc566784c", @@ -85,7 +86,7 @@ "state": { "type": "backlink", "state": { - "file": "math/utils.md", + "file": "views/views-input.md", "collapseAll": false, "extraContext": false, "sortOrder": "alphabetical", @@ -102,7 +103,7 @@ "state": { "type": "outgoing-link", "state": { - "file": "math/utils.md", + "file": "views/views-input.md", "linksCollapsed": false, "unlinkedCollapsed": true } @@ -125,16 +126,16 @@ "state": { "type": "outline", "state": { - "file": "math/utils.md" + "file": "views/views-input.md" } } } - ] + ], + "currentTab": 3 } ], "direction": "horizontal", - "width": 300, - "collapsed": true + "width": 300 }, "left-ribbon": { "hiddenItems": { @@ -148,8 +149,17 @@ }, "active": "59a0d87ce96a06a1", "lastOpenFiles": [ - "math/Untitled.canvas", + "io/index.md", + "reference/resources.md", + "io.md", + "reference/testing.md", + "templates/filters.md", + "templates/extending.md", + "about/faq.md", + "about/jam.md", + "about/contributing.md", "math/utils.md", + "math/Untitled.canvas", "math/index.md", "io/encoding.md", "io/sstreams.md", @@ -166,7 +176,6 @@ "data-structures/algo.md", "getting-started/index.md", "index.md", - "scenes/scenes.md", "i/facebook.svg", "i/file_drag_and_drop.webm", "i/googleplus.svg", @@ -175,15 +184,8 @@ "i/onCollision.webm", "i/onCollisionShape.webm", "i/onCollisionShapeImage.webm", - "io/compression.md", - "io/aasync.md", - "_includes/bottom_desc.md", "korlibs-deps-tpl.svg", - "source code.md", "korlibs-deps-tpl.canvas", - "Untitled 1.canvas", - "korlibs-deps.md", - "data-structures/maps.md", - "math/vector.md" + "Untitled 1.canvas" ] } \ No newline at end of file diff --git a/io.md b/io.md new file mode 100644 index 0000000..e69de29 diff --git a/reference/resources.md b/reference/resources.md index a2c45d9..7c752ea 100644 --- a/reference/resources.md +++ b/reference/resources.md @@ -8,7 +8,7 @@ fa-icon: fa-archive priority: 10 --- -KorGE uses the [Virtual File Systems from Korio](/io/) to load resources from different sources, +KorGE uses the [Virtual File Systems from Korio](/io/index) to load resources from different sources, and its most basic resources are [Bitmaps](/imaging/) and [Sounds](/audio/). But it can also load bitmap fonts, tiled maps, etc. diff --git a/reference/testing.md b/reference/testing.md index 5c01807..11fac3c 100644 --- a/reference/testing.md +++ b/reference/testing.md @@ -139,11 +139,12 @@ try to test as much as possible without using views at all. If you represent your game as states and state transitions, you can then display those actions with animations and tweens. And you can convert user interactions into actions. -```nomnoml -[States] -[User Interactions] -> [User Actions] -[User Actions] -> [State Transitions] -[State Transitions] -> [View Animations] +```mermaid +graph LR; +States["States"]; +UserInteractions["User Interactions"] --> UserActions["User Actions"]; +UserActions["User Actions"] --> StateTransitions["State Transitions"]; +StateTransitions["State Transitions"] --> ViewAnimations["View Animations"]; ``` For example: diff --git a/templates/extending.md b/templates/extending.md index d3f0b2a..46b09db 100644 --- a/templates/extending.md +++ b/templates/extending.md @@ -10,8 +10,6 @@ priority: 70 It is possible to extend KorTE with new tags, functions and filters. - - {% raw %} ## Extending diff --git a/views/views-input.md b/views/views-input.md index 8f64506..70bd3f6 100644 --- a/views/views-input.md +++ b/views/views-input.md @@ -7,8 +7,11 @@ title_prefix: KorGE fa-icon: fa-gamepad priority: 30 --- +## Input State vs Events -## Accessing input state +There are two ways of handling input events. One is by checking the input state, and the other is event-based. + +### Accessing input state The `Input` singleton contains the stage of the input values at a time. It is updated once per frame. You can get it from the `Views` or the `Stage` singletons. @@ -17,61 +20,24 @@ It is updated once per frame. You can get it from the `Views` or the `Stage` sin val input = views.input ``` -### Mouse - -```kotlin -view.addUpdater { - val xy: Point = input.mouse - val buttons: Int = input.mouseButtons // flags with the pressed buttons -} -``` - -### Multi-touch - -```kotlin -view.addUpdater { - val ntouches: Int = input.activeTouches.size - val touches: List = input.activeTouches - val rawTouch0: Touch = input.touches[0] -} -``` - -### Keys - -```kotlin - -import com.soywiz.klock.milliseconds +### Events -import com.soywiz.klogger.Console - -import com.soywiz.korev.Key +KorGE provides a high level API to handle events attaching events to a View. +The events are only triggered when the associated View is attached to the stage. +Views have several extension methods to attach events to them. Which method depends on each kind of event. -view.addUpdater { timespan: TimeSpan -> - val scale = timespan / 16.milliseconds - if (input.keys[Key.LEFT]) x -= 2.0 * scale // Same as `input.keys.pressing(Key.LEFT)` - if (input.keys.pressing(Key.RIGHT)) x += 2.0 * scale - if (input.keys.justPressed(Key.ESCAPE)) views.gameWindow.close(0) - if (input.keys.justReleased(Key.ENTER)) Console.info("I'm working!") -} -``` +## Mouse/Touch Events -### Gamepads +### Mouse Input State ```kotlin view.addUpdater { - val gamepads = input.connectedGamepads - val rawGamepad0 = input.gamepads[0] - val pressedStart: Boolean = rawGamepad0[GameButton.START] - val pos: Point = rawGamepad0[GameStick.LEFT] + val xy: Point = input.mouse + val buttons: Int = input.mouseButtons // flags with the pressed buttons } -``` - -## Event-based High-level API - -KorGE provides a high level API to handle events attaching events to a View. -The events are only triggered when the associated View is attached to the stage. +``` -### Mouse/Touch Events +### Mouse Events You can handle `click`, `over` (hover), `out`, `down`, `downFromOutside`, `up`, `upOutside`, `upAnywhere`, `move`, `moveAnywhere`, `moveOutside` and `exit` mouse events. @@ -98,9 +64,37 @@ or view.onClick { /*...*/ } // suspending block ``` +### Multi-touch Events + +```kotlin +view.addUpdater { + val ntouches: Int = input.activeTouches.size + val touches: List = input.activeTouches + val rawTouch0: Touch = input.touches[0] +} +``` + +## Keys + +### Keys Input State + +```kotlin +import com.soywiz.klock.milliseconds +import com.soywiz.klogger.Console +import com.soywiz.korev.Key + +view.addUpdater { timespan: TimeSpan -> + val scale = timespan / 16.milliseconds + if (input.keys[Key.LEFT]) x -= 2.0 * scale // Same as `input.keys.pressing(Key.LEFT)` + if (input.keys.pressing(Key.RIGHT)) x += 2.0 * scale + if (input.keys.justPressed(Key.ESCAPE)) views.gameWindow.close(0) + if (input.keys.justReleased(Key.ENTER)) Console.info("I'm working!") +} +``` + ### Keys Events -For example: +To handle any key: ```kotlin // Matches any key @@ -109,6 +103,14 @@ view.keys { up { e -> /*...*/ } } +// Or as a shortcut +view.onKeyUp { e -> /*...*/ } // suspending block +view.onKeyDown { e -> /*...*/ } // suspending block +``` + +To handle only a specific key event: + +```kotlin // Matches just one key view.keys { // Executes when the key is down. Depending on the platform this might trigger multiple events. Use justDown to trigger it only once. @@ -128,10 +130,22 @@ view.keys { // Useful for UIs, the code is executed every half a second at first, and then every 100 milliseconds doing an acceleration. downRepeating(Key.LEFT) { e -> /*...*/ } } -view.onKeyDown { e -> /*...*/ } // suspending block + ``` -### Gamepad Events +## Gamepads + +### Gamepads Input State + +```kotlin +view.addUpdater { + val gamepads = input.connectedGamepads + val rawGamepad0 = input.gamepads[0] + val pressedStart: Boolean = rawGamepad0[GameButton.START] + val pos: Point = rawGamepad0[GameStick.LEFT] +} +``` +### Gamepads Events ```kotlin view.gamepad { @@ -144,173 +158,18 @@ view.gamepad { } ``` -## Handling RAW events via Components - -You can handle RAW input by creating components that handle events -and attaching to a view. - -```kotlin -// Executes once per frame -interface UpdateComponent : Component { - fun update(ms: Double) -} - -// New version of UpdateComponent -interface UpdateComponentV2 : UpdateComponent { - override fun update(dt: HRTimeSpan) -} - -// Handling all the Events -interface EventComponent : Component { - fun onEvent(event: Event) -} - -// Handling just Input Events -interface TouchComponent : Component { - fun onTouchEvent(views: Views, e: TouchEvent) -} - -interface MouseComponent : Component { - fun onMouseEvent(views: Views, event: MouseEvent) -} - -interface KeyComponent : Component { - fun onKeyEvent(views: Views, event: KeyEvent) -} - -interface GamepadComponent : Component { - fun onGamepadEvent(views: Views, event: GamePadButtonEvent) - fun onGamepadEvent(views: Views, event: GamePadStickEvent) - fun onGamepadEvent(views: Views, event: GamePadConnectionEvent) -} -``` - -## Stage event dispatching - -The stage singleton receives raw events. -You can call `stage.addEventListener`. - -NOTE: Remember to remove the event listener once finished with it. -Since you are adding it to the root node, it won't be collected automatically. +## On stage resizing Example: ```kotlin -stage.addEventListener { e -> +view.onStageResized { width, height -> + // ... } -``` -Available event types: - -* `MouseEvent` -* `TouchEvent` -* `ReshapeEvent` -* `KeyEvent` -* `GamePadConnectionEvent` -* `GamePadUpdateEvent` -* `GamePadButtonEvent` -* `GamePadStickEvent` - -## APIs - -### Mouse - -```kotlin -// Configuring MouseEvents -val View.mouse: MouseEvents -inline fun View.mouse(callback: MouseEvents.() -> T): T = mouse.run(callback) - -// Shortcuts -inline fun T.onClick(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onOver(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onOut(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onDown(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onDownFromOutside(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onUp(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onUpOutside(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onUpAnywhere(noinline handler: suspend (MouseEvents) -> Unit): T -inline fun T.onMove(noinline handler: suspend (MouseEvents) -> Unit): T - -class MouseEvents(override val view: View) : MouseComponent, UpdateComponentWithViews { - val click = Signal() - val over = Signal() - val out = Signal() - val down = Signal() - val downFromOutside = Signal() - val up = Signal() - val upOutside = Signal() - val upAnywhere = Signal() - val move = Signal() - val moveAnywhere = Signal() - val moveOutside = Signal() - val exit = Signal() - - val startedPos = Point() - val lastPos = Point() - val currentPos = Point() - - val hitTest: View? - - var downPos = Point() - var upPos = Point() - var clickedCount = 0 - val isOver: Boolean +view.onEvent(ViewsResizedEvent) { } -``` - -### Keys - - -```kotlin -// Configuring KeysEvents -val View.keys: KeysEvents -inline fun View.keys(callback: KeysEvents.() -> T): T - -// Shortcuts -inline fun T?.onKeyDown(noinline handler: suspend (KeyEvent) -> Unit) -inline fun T?.onKeyUp(noinline handler: suspend (KeyEvent) -> Unit) -inline fun T?.onKeyTyped(noinline handler: suspend (KeyEvent) -> Unit) - -class KeysEvents(override val view: View) : KeyComponent { - val onKeyDown = AsyncSignal() - val onKeyUp = AsyncSignal() - val onKeyTyped = AsyncSignal() - - // Handlers for a specific Key - fun down(key: Key, callback: (key: Key) -> Unit): Closeable - fun up(key: Key, callback: (key: Key) -> Unit): Closeable - fun typed(key: Key, callback: (key: Key) -> Unit): Closeable - - // Handlers for any keys - fun down(callback: (key: Key) -> Unit): Closeable - fun up(callback: (key: Key) -> Unit): Closeable - fun typed(callback: (key: Key) -> Unit): Closeable -} -``` - -### Gamepad - -```kotlin -// Configuring GamePadEvents -val View.gamepad: GamePadEvents -inline fun View.gamepad(callback: GamePadEvents.() -> T): T - -class GamePadEvents(override val view: View) : GamepadComponent { - val stick = Signal() - val button = Signal() - val connection = Signal() - - fun stick(playerId: Int, stick: GameStick, callback: (x: Double, y: Double) -> Unit) - fun down(playerId: Int, button: GameButton, callback: () -> Unit) - fun up(playerId: Int, button: GameButton, callback: () -> Unit) - fun connected(playerId: Int, callback: () -> Unit) - fun disconnected(playerId: Int, callback: () -> Unit) - override fun onGamepadEvent(views: Views, event: GamePadButtonEvent) - override fun onGamepadEvent(views: Views, event: GamePadStickEvent) - override fun onGamepadEvent(views: Views, event: GamePadConnectionEvent) -} - -``` +``` ## Handling Drag & Drop File Events