-
-
Notifications
You must be signed in to change notification settings - Fork 2
Understanding Gondwana Input
A technical guide to the architecture, engine-cycle integration, and device-specific behavior of the
Gondwana.Inputnamespace.
- What This Page Covers
- The Core Mental Model
- Architecture at a Glance
- The Four Layers of Input
- Engine.Input
- The Engine Cycle
- Common Event Configuration
- Keyboard Architecture
- Mouse Architecture
- Gamepad Architecture
- Touch Architecture
- Gesture Recognition
- Platform Adapters
- GameHost Lifecycle Integration
- Threading and State Ownership
- Polling, Events, and Continuous State
- Pausing and Reconfiguring Input
- Input and Widgets
- Designing Game-Specific Bindings
- Common Mistakes
- Troubleshooting
- Quick Reference
- Glossary
- Related Source Files
- Where to Read Next
- Final Mental Model
Gondwana input is built around one consistent idea:
Platform code collects device state; the engine polls that state; core pollers translate it into stable Gondwana events.
The Gondwana.Input namespace defines the platform-neutral contracts and pollers for four input families:
| Input family | Core adapter contract | Core poller |
|---|---|---|
| Keyboard | IKeyboardAdapter |
KeyboardEventPoller |
| Mouse | IMouseAdapter |
MouseEventPoller |
| Gamepad |
IGamepadAdapter and IGamepadManager<T>
|
GamepadEventPoller |
| Touch |
ITouchAdapter and ITouchInput
|
TouchEventPoller |
The platform packages provide concrete adapters:
Gondwana.WinFormsGondwana.AvaloniaGondwana.BlazorGondwana.Input.SDL2- XInput support in
Gondwana.WinForms
This page explains both the common architecture and the places where the four device families intentionally differ.
A Gondwana input device is not normally consumed directly by game code.
Instead, input flows through these stages:
Platform event or native device API
↓
Platform adapter records current state
↓
Engine input poller reads that state
↓
Poller detects transitions or configured conditions
↓
Gondwana event is raised
↓
Game code responds
That division keeps the engine core independent of WinForms, Avalonia, browser DOM events, XInput, SDL2, and other platform APIs.
The adapter answers questions such as:
- Is this key currently down?
- Which mouse buttons are currently down?
- Where is the pointer?
- Which controller buttons are pressed?
- Which touch contacts are active?
The poller adds engine behavior:
- transition detection
- event throttling
- pause state
- explicit registration
- normalized event arguments
- engine-thread delivery
- gesture recognition for touch
flowchart TD
Platform["Platform input source<br/>WinForms / Avalonia / Browser / XInput / SDL2"]
Adapter["Platform adapter<br/>records device state"]
Gateway["Engine.Input<br/>EngineInputSystems"]
Poller["Core input poller<br/>runs during engine cycle"]
Config["Input configuration<br/>throttle + pause + registrations"]
EventArgs["Gondwana event arguments"]
Game["Game code / widgets / tools"]
Platform --> Adapter
Adapter --> Gateway
Gateway --> Poller
Config --> Poller
Poller --> EventArgs
EventArgs --> Game
The core is deliberately device-neutral. The platform edge is deliberately device-specific.
This is the native mechanism that knows about physical or browser input.
Examples:
- WinForms window messages
- Avalonia pointer and keyboard events
- browser keyboard, pointer, and touch events
- XInput controller state
- SDL2 game-controller state
An adapter converts platform-specific state into a small Gondwana interface.
Examples:
public interface IKeyboardAdapter
{
bool IsDown(int keyCode);
KeyboardModifierState CurrentKeyboardModifiers { get; }
}public interface IMouseAdapter
{
Point CurrentPosition { get; }
HashSet<MouseButton> PressedButtons { get; }
KeyboardModifierState CurrentKeyboardModifiers { get; }
int ScrollDelta { get; }
}Adapters are intentionally thin. They are not where game rules belong.
A poller runs as part of Gondwana's engine cycle and interprets adapter state.
Depending on the device, it may:
- compare current and previous state
- detect presses and releases
- enforce registration rules
- throttle repeated events
- package state into event arguments
- feed gesture recognizers
The final consumer can be:
- game logic
- a camera controller
- a menu
- a debug tool
- a widget
- a game-specific input-binding layer
Game code normally subscribes to the poller exposed through Engine.Input.
Engine.Input is an EngineInputSystems instance and is the central gateway to all configured input subsystems.
var input = Engine.Instance.Input;
var keyboard = input.KeyboardEventPoller;
var mouse = input.MouseEventPoller;
var gamepads = input.GamepadManager;
var gamepadEvents = input.GamepadEventPoller;
var touch = input.TouchEventPoller;Its responsibilities are intentionally small:
- expose initialized pollers
- own the selected gamepad manager
- initialize the gamepad poller when a manager is assigned
- initialize or reset touch input when
TouchAdapterchanges
It does not interpret game actions such as Jump, Open Inventory, or Select Unit. Those remain game-specific.
Input polling occurs in Engine.DoBackgroundTasks().
The current order is:
Pre-cycle timers
↓
Keyboard polling
↓
Mouse polling
↓
Touch polling
↓
Gamepad button-event polling
↓
Animation
↓
Sprite movement
↓
Collision resolution
↓
Camera/view updates
This ordering has a useful consequence:
Input handlers can modify movement, camera targets, gameplay state, or scene objects before movement and collision work occurs for that cycle.
The engine polls the four input event systems with the same high-resolution tick value:
KeyboardEventPoller.Instance?.PollForEvents(tick);
MouseEventPoller.Instance?.PollForEvents(tick);
TouchEventPoller.Instance?.PollForEvents(tick);
GamepadEventPoller.Instance?.PollForEvents(tick);Gamepad device state has one additional layer: the active IGamepadManager refreshes connected adapters during foreground processing. The event poller consumes the latest state stored by those adapters.
A native input event may occur on a UI thread at any time. Gondwana does not run arbitrary game code directly from every native callback.
Instead:
- The adapter records state.
- The engine reaches its next input-polling phase.
- The poller raises a Gondwana event in engine-cycle order.
This gives game logic a predictable place in the runtime pipeline.
Keyboard, mouse, gamepad, and touch configurations derive from InputEventConfigurationBase.
That base class supplies:
public double TimeBetweenEvents { get; set; }
public bool IsPaused { get; set; }Internally, it tracks the last event tick and determines whether the configured interval has elapsed.
The engine-wide defaults are:
| Setting | Default |
|---|---|
TimeBetweenKeyboardEvents |
0.03 seconds |
TimeBetweenMouseEvents |
0.03 seconds |
TimeBetweenGamepadEvents |
0.03 seconds |
TimeBetweenTouchEvents |
0.03 seconds |
A negative timeBetweenEvents argument generally means:
Use the corresponding value from
Engine.Configuration.
A value of 0 means:
Do not add interval-based throttling.
The shared configuration mechanism does not mean every device applies throttling identically.
| Device | What is throttled |
|---|---|
| Keyboard | Held-key Repeated events |
| Mouse | The consolidated mouse poll/event stream |
| Gamepad | Repeated button-down events while held |
| Touch | Movement events only; lifecycle transitions are preserved |
That distinction matters when choosing configuration values.
IKeyboardAdapter exposes two things:
bool IsDown(int keyCode);
KeyboardModifierState CurrentKeyboardModifiers { get; }A key code is an integer whose meaning is agreed upon by the platform adapter and the game.
Examples:
- WinForms:
(int)System.Windows.Forms.Keys.A - Avalonia:
(int)Avalonia.Input.Key.A - Blazor:
(int)BlazorKey.KeyA
The core engine does not impose one universal keyboard enumeration.
Keyboard input is opt-in per key.
keyboard.StartMonitoringKey(
keyCode: (int)Keys.A,
displayName: nameof(Keys.A));Only registered keys are checked by KeyboardEventPoller.
This keeps the hot path small and makes each game's active bindings explicit.
For every monitored key, the poller compares current and previous state.
stateDiagram-v2
[*] --> Up
Up --> Down: Pressed
Down --> Down: Repeated
Down --> Up: Released
The event exposes a KeyAction:
public enum KeyAction
{
Pressed,
Released,
Repeated
}Typical usage:
private void OnKeyDown(KeyDownEventArgs e)
{
switch (e.KeyAction)
{
case KeyAction.Pressed:
// One-time action.
break;
case KeyAction.Repeated:
// Held-key action.
break;
case KeyAction.Released:
// Stop or finalize an action.
break;
}
}StartMonitoringKey() accepts both a numeric code and an optional display name.
keyboard.StartMonitoringKey(
(int)Keys.A,
nameof(Keys.A));The event exposes the configured display value through:
e.KeyConfig.KeyWhen no display name is supplied, the poller falls back to the numeric code converted to text. Therefore, supply a display name when handlers need human-readable key identity.
KeyDownEventArgs includes:
e.Modifiers
e.IsShift
e.IsCtrl
e.IsAltExample:
if (e.KeyAction == KeyAction.Pressed &&
e.KeyConfig.Key == nameof(Keys.S) &&
e.IsCtrl)
{
SaveGame();
}Keyboard monitoring changes are queued and applied on the engine thread.
Calls such as:
keyboard.StartMonitoringKey(...);
keyboard.StopMonitoringKey(...);
keyboard.StopMonitoringAllKeys();do not directly mutate the poller's hot-path dictionaries from an arbitrary caller thread.
PauseAllKeyEvents is the global stop switch:
keyboard.PauseAllKeyEvents = true;Per-key KeyEventConfiguration.IsPaused currently gates held-key repeat generation. For complete suppression of a key, use one of these:
keyboard.StopMonitoringKey(keyCode);or:
keyboard.PauseAllKeyEvents = true;That distinction is worth remembering when implementing modal input modes.
IMouseAdapter supplies a complete mouse-state snapshot:
Point CurrentPosition { get; }
HashSet<MouseButton> PressedButtons { get; }
KeyboardModifierState CurrentKeyboardModifiers { get; }
int ScrollDelta { get; }The core mouse buttons are:
MouseButton.Left
MouseButton.Right
MouseButton.MiddleUnlike keyboard and gamepad input, mouse input does not require button-by-button registration.
The poller raises one event:
mouse.MouseEvent += OnMouseEvent;That event contains:
- current and previous cursor position
- all button states
- press and release transitions
- scroll delta
- modifier keys
- the engine tick
Each button has:
public struct MouseButtonState
{
public bool IsDown;
public bool JustPressed;
public bool JustReleased;
}Convenience properties make common checks compact:
e.LeftButtonDown
e.LeftButtonJustPressed
e.LeftButtonJustReleased
e.RightButtonDown
e.RightButtonJustPressed
e.RightButtonJustReleasedGeneric helpers are available too:
e.IsButtonDown(MouseButton.Middle);
e.IsButtonJustPressed(MouseButton.Middle);
e.IsButtonJustReleased(MouseButton.Middle);The poller raises an event when at least one of these is true:
- a button changed state
- the pointer moved and movement tracking is enabled
- the wheel produced a nonzero changed delta
- any button remains held
This supports clicks, drags, hover tracking, and continuous held-button behavior through one payload.
Configure mouse monitoring with:
mouse.StartMonitoringMouse(
trackMouseMovement: true,
timeBetweenEvents: -1,
isPaused: false);Set trackMouseMovement: false when hover or free pointer movement is not needed.
Mouse positions are client or render-surface coordinates supplied by the platform adapter.
For world interaction, convert through a View:
var view = RenderSurface.Host.ViewManager.Views[0];
var layer = Scene![0];
var worldPx = view.ScreenPxToWorldPx(
layer,
e.CurrentPosition);For tile picking:
var grid = view.ScreenPxToGrid(
layer,
e.CurrentPosition);The mouse system itself does not know which view or scene layer the game intends to target.
Gamepad input has one extra abstraction because multiple controllers may be connected.
IGamepadManager
└── ConnectedAdapters
├── IGamepadAdapter
├── IGamepadAdapter
└── ...
IGamepadManager<T> owns discovery and state refresh:
IReadOnlyCollection<T> ConnectedAdapters { get; }
void Update();Concrete managers include:
-
XInputGamepadManageron Windows -
SdlGamepadManagerfor cross-platform SDL2 input
Assigning a manager through Engine.Input.GamepadManager also initializes GamepadEventPoller against its adapter collection.
Each IGamepadAdapter exposes:
string GamepadId { get; }
IReadOnlyCollection<string> PressedButtons { get; }
GamepadStickState? LeftStick { get; }
GamepadStickState? RightStick { get; }
float LeftTrigger { get; }
float RightTrigger { get; }Buttons are strings because native naming differs between backends.
Examples:
| Backend | Example A-button name |
|---|---|
| XInput | "A" |
| SDL2 | "SDL_CONTROLLER_BUTTON_A" |
Do not assume button names are portable without a game-specific mapping layer.
Gamepad buttons are registered per controller ID:
poller.StartMonitoringButton(
gamepadId,
button: "A");The event supplies both the button configuration and the originating adapter:
private void OnButtonDown(GamepadButtonDownEventArgs e)
{
string button = e.Config.Button;
string controller = e.Adapter.GamepadId;
}GamepadEventPoller.ButtonDown is not an edge-only event.
While a monitored button remains in PressedButtons, it can fire again whenever the configured throttle interval permits.
This is useful for:
- menu navigation repeat
- continuous firing
- held acceleration
- repeated inventory scrolling
For a one-shot action, guard it in game code or use a sufficiently deliberate binding strategy.
Sticks and triggers are continuous state rather than discrete button events.
var stick = adapter.LeftStick?
.WithDeadzone(0.20f);
if (stick is { } left && left.IsEngaged())
{
player.Movement.SetVelocity(
new Vector2(left.X, -left.Y));
}GamepadStickState provides:
- normalized
XandY - raw values
MagnitudeAngleIsEngaged()Direction()WithDeadzone()
A gamepad manager discovers devices during Update().
At host initialization time, ConnectedAdapters may still be empty. A setup routine may perform one explicit initial update before registering existing controllers:
manager.Update();
foreach (var adapter in manager.ConnectedAdapters)
{
poller.StartMonitoringButton(
adapter.GamepadId,
"A");
}Do not call Update() in an unbounded custom loop. The engine already refreshes the manager as part of its runtime cycle.
Hot-plugged devices also need game-specific registration for whichever buttons the game wants to monitor.
Touch input is built around contact lifecycles and gesture recognition.
ITouchAdapter is a passive state source:
IReadOnlyList<TouchPoint> ConsumeBeganTouches();
IReadOnlyList<TouchPoint> ActiveTouches { get; }
IReadOnlyList<TouchPoint> ConsumeEndedTouches();The adapter does not raise Gondwana touch events directly.
The beginning and ending queues prevent a short contact from disappearing when it begins and ends between two engine polls.
A touch contact is represented by:
public readonly record struct TouchPoint(
int Id,
Point Position,
TouchPhase Phase);The same contact ID is retained throughout its lifecycle.
The phases are:
TouchPhase.Began
TouchPhase.Moved
TouchPhase.Stationary
TouchPhase.Ended
TouchPhase.CancelledStationary exists for compatibility, but TouchEventPoller does not raise a separate stationary event.
TouchEventPoller implements ITouchInput and exposes:
TouchBegan
TouchMoved
TouchEndedEach TouchEventArgs contains:
e.Touch
e.TickExample:
private void OnTouchBegan(
object? sender,
TouchEventArgs e)
{
Console.WriteLine(
$"Touch {e.Touch.Id} began at {e.Touch.Position}");
}Touch deliberately treats lifecycle and movement differently:
- beginnings are never throttled
- endings and cancellations are never throttled
- movement may be throttled
This preserves a valid contact lifecycle even when movement events are rate-limited.
During movement throttling, TouchEventPoller.ActiveTouches reflects the poller's last emitted positions and can intentionally lag the adapter's newest internal positions.
Pausing or stopping touch input creates a new logical contact boundary.
The poller:
- drains transient queues
- clears active-contact state
- resets gesture recognizers
That prevents a contact that began before a modal pause from unexpectedly completing a gesture after input resumes.
TouchEventPoller owns three recognizers:
TapGestureRecognizer
SwipeGestureRecognizer
PinchGestureRecognizer
It also exposes one unified event:
touch.TouchEvent += OnTouchGesture;The event payload is GestureEventArgs.
private void OnTouchGesture(GestureEventArgs e)
{
if (e.IsTap)
HandleTap(e.Tap!);
if (e.IsSwipe)
HandleSwipe(e.Swipe!);
if (e.IsPinch)
HandlePinch(e.Pinch!);
}A default tap must:
- use one contact
- finish within
0.3seconds - move no more than
20pixels - not qualify as a competing swipe
Tune it with:
touch.TapRecognizer.MaxTapDurationSeconds = 0.25;
touch.TapRecognizer.MaxTapMovementPixels = 15;A default swipe must:
- use one contact
- travel at least
30pixels - average at least
200pixels per second
Tune it with:
touch.SwipeRecognizer.MinimumSwipeDistancePixels = 40;
touch.SwipeRecognizer.MinimumSwipeSpeedPixelsPerSecond = 250;The event reports:
- direction
- start position
- end position
- speed
Pinch recognition requires exactly two active contacts.
It provides a complete lifecycle:
PinchPhase.Began
PinchPhase.Updated
PinchPhase.EndedImportant values include:
e.Pinch.Center
e.Pinch.ScaleDelta
e.Pinch.TotalScale
e.Pinch.CurrentDistanceInterpretation:
| Value | Meaning |
|---|---|
ScaleDelta > 1 |
Fingers moved apart |
ScaleDelta < 1 |
Fingers moved together |
TotalScale > 1 |
Gesture is larger than its starting distance |
TotalScale < 1 |
Gesture is smaller than its starting distance |
When a third contact appears, two-contact pinch updates pause and the baseline is safely re-established when exactly two contacts remain.
| Platform package | Keyboard | Mouse |
|---|---|---|
Gondwana.WinForms |
Yes | Yes |
Gondwana.Avalonia |
Yes | Yes |
Gondwana.Blazor |
Yes | Yes |
Initialization helpers include:
Engine.InitializeWinFormsKeyboardAdapter(control);
Engine.InitializeWinFormsMouseAdapter(control);Engine.InitializeAvaloniaKeyboardAdapter(control);
Engine.InitializeAvaloniaMouseAdapter(control);Engine.InitializeBlazorKeyboardAdapter(component);
Engine.InitializeBlazorMouseAdapter(component);| Platform package | Touch adapter |
|---|---|
Gondwana.Avalonia |
Yes |
Gondwana.Blazor |
Yes |
Gondwana.WinForms |
No built-in adapter |
Avalonia initialization:
Engine.InitializeAvaloniaTouchAdapter(
control,
emulateMouse: false);Physical touch and mouse are distinct by default. Set emulateMouse: true only when desktop mouse input should also become touch ID 0.
Blazor initialization:
Engine.InitializeBlazorTouchAdapter(component);| Backend | Package / platform | Initialization |
|---|---|---|
| XInput | Gondwana.WinForms |
InitializeXInputGamepadManager() |
| SDL2 | Gondwana.Input.SDL2 |
InitializeSdlGamepadManager() |
XInput is a good Windows default. SDL2 is the portable choice when the additional package and native dependency are appropriate.
GameHostBase configures input in this order:
ConfigureKeyboard
↓
ConfigureMouse
↓
ConfigureGamepads
↓
ConfigureTouch
↓
OnInputConfigured
Platform hosts initialize their adapters and then invoke device-specific hooks.
Typical hooks are:
OnKeyboardAdapterInitialized()
OnMouseAdapterInitialized()
OnGamepadManagerInitialized() // WinForms
OnConfigureGamepads() // Avalonia / Blazor
OnTouchAdapterInitialized()Use these hooks to:
- subscribe to poller events
- register keys or buttons
- tune recognizers
- start or reconfigure monitoring
Use UnhookEvents() to detach custom handlers.
protected override void UnhookEvents()
{
if (Engine.Input.KeyboardEventPoller is { } keyboard)
keyboard.KeyDown -= OnKeyDown;
if (Engine.Input.MouseEventPoller is { } mouse)
mouse.MouseEvent -= OnMouseEvent;
if (Engine.Input.GamepadEventPoller is { } gamepad)
gamepad.ButtonDown -= OnGamepadButtonDown;
if (Engine.Input.TouchEventPoller is { } touch)
touch.TouchEvent -= OnTouchGesture;
}GameHostBase.Dispose() invokes UnhookEvents() before stopping and disposing the engine.
On WinForms and Avalonia, platform callbacks normally occur on the UI thread.
The adapters record state in forms that can be safely consumed by the engine thread:
- volatile or synchronized key state
- snapshot collections
- concurrent queues for touch transitions
- accumulated wheel values
- immutable event payloads
The pollers are called from the engine cycle and raise game-facing input events there.
Therefore:
Input handlers should normally treat themselves as engine-thread callbacks.
It is appropriate to modify Gondwana game state there.
It is not generally appropriate to mutate a WinForms or Avalonia control directly without dispatching back to the UI thread.
Engine.UiDispatcher?.Post(() =>
{
statusLabel.Text = "Input received";
});Browser/WASM uses timer-driven engine ticks rather than the ordinary background-thread loop. The same adapter/poller architecture still applies, even when platform and engine work share one runtime thread.
Adapters should remain translation and state-storage layers.
Avoid embedding:
- movement rules
- menu navigation
- camera behavior
- action binding
- game-state transitions
That logic belongs in game code or a game-specific binding/controller layer.
Gondwana deliberately supports both event-style and state-style consumption.
Use events for discrete or throttled actions:
Key pressed
Mouse button transitioned
Gamepad button is active
Touch began
Tap recognized
Swipe recognized
Read state for continuous control:
Mouse position
Mouse button held
Gamepad stick vector
Gamepad trigger pressure
Active touch contacts
Keyboard adapter IsDown
A common pattern is:
- use events to start or stop an action
- use state during updates to control its magnitude
Example:
private bool _accelerating;
private void OnKeyDown(KeyDownEventArgs e)
{
if (e.KeyConfig.Key != nameof(Keys.W))
return;
_accelerating =
e.KeyAction != KeyAction.Released;
}Then apply acceleration from the game's update path.
keyboard.PauseAllKeyEvents = true;
keyboard.StopMonitoringKey((int)Keys.A);
keyboard.StopMonitoringAllKeys();mouse.Configuration!.IsPaused = true;
mouse.StopMonitoringMouse();
mouse.StartMonitoringMouse();gamepad.PauseAllInput = true;
gamepad.StopMonitoringButton(gamepadId, "A");
gamepad.StopMonitoringAllButtons(gamepadId);touch.Configuration!.IsPaused = true;
touch.StopMonitoringTouch();
touch.StartMonitoringTouch();Use pause when a binding should be temporarily inactive.
Use stop/unregister when a binding is no longer part of the active game mode.
WidgetInputRouter can consume:
- keyboard
- mouse
- touch
The host initializes it after input adapters are available.
The router adds widget-specific concerns:
- hit testing
- hover
- pointer capture
- click routing
- drag routing
- keyboard focus
That does not replace the core input pollers.
A game can simultaneously use:
-
WidgetInputRouterfor menus and controls - direct mouse input for world selection
- keyboard input for gameplay
- touch gestures for camera manipulation
The key design question is ownership:
Decide whether the widget, the world, or a game-level controller owns a particular gesture.
Avoid letting two consumers independently execute the same action unless that overlap is intentional.
The core APIs expose physical input. Larger games usually benefit from a game-specific action layer.
Instead of spreading this everywhere:
if (key == Keys.Space)
player.Jump();define an action:
public enum GameAction
{
MoveLeft,
MoveRight,
Jump,
Pause,
Confirm,
Cancel
}Then map physical inputs:
Keyboard Space ─┐
Gamepad A ├── Jump
Touch upward swipe ┘
A binding layer can provide:
- remapping
- multiple devices for one action
- player-specific controllers
- accessibility alternatives
- conflict detection
- serialized control settings
Gondwana does not force one action-binding architecture because the right model is game-specific.
A poller property is null until its adapter has been initialized.
Use the platform host hooks or explicit extension methods.
Keyboard requires explicit key registration.
Mouse and touch need an active configuration.
Gamepad requires per-controller, per-button registration.
This:
keyboard.StartMonitoringKey((int)Keys.A);uses the numeric key code converted to a string as the event's key identity.
This is easier to consume:
keyboard.StartMonitoringKey(
(int)Keys.A,
nameof(Keys.A));It can repeat while held.
Use throttling or game-level edge tracking for one-shot actions.
XInput and SDL2 use different string names.
Map backend names to game actions.
Game code should usually consume TouchEventPoller, not the platform adapter.
The poller owns transitions, throttling, and gestures.
Mouse and touch positions are surface-local pixels.
Use View.ScreenPxToWorldPx() or View.ScreenPxToGrid() for scene interaction.
Dispatch UI work through Engine.UiDispatcher.
Detach handlers in UnhookEvents().
Check:
- Was the platform keyboard adapter initialized?
- Is
KeyboardEventPollernon-null? - Was the specific key registered?
- Does the key code match the active platform adapter?
- Is
PauseAllKeyEventsfalse? - Did the host start the engine?
Supply displayName:
StartMonitoringKey(
(int)Keys.A,
nameof(Keys.A));Ensure:
mouse.StartMonitoringMouse(
trackMouseMovement: true);Reduce the mouse throttle:
mouse.StartMonitoringMouse(
trackMouseMovement: true,
timeBetweenEvents: 0);Use zero selectively; high-frequency pointer movement can produce substantial work.
Check:
- Is the manager initialized?
- Has the manager discovered the controller?
- Is the button registered for the correct
GamepadId? - Does the button string match the backend?
- Is the poller or button configuration paused?
Apply a deadzone:
var stick = adapter.LeftStick?
.WithDeadzone(0.20f);Avalonia ignores mouse pointers for touch by default.
Initialize with:
Engine.InitializeAvaloniaTouchAdapter(
control,
emulateMouse: true);Do this only when duplicate mouse and touch input is not a problem.
Tune the competing thresholds:
touch.TapRecognizer.MaxTapMovementPixels = 15;
touch.SwipeRecognizer.MinimumSwipeDistancePixels = 40;Use the built-in PinchRecognizer. It re-establishes its baseline when exactly two contacts become active again.
keyboard.KeyDown += OnKeyDown;
keyboard.StartMonitoringKey(
(int)Keys.A,
nameof(Keys.A));if (e.KeyAction == KeyAction.Pressed)
{
}mouse.MouseEvent += OnMouseEvent;
mouse.StartMonitoringMouse();if (e.LeftButtonJustPressed)
{
}poller.ButtonDown += OnButtonDown;
poller.StartMonitoringButton(
adapter.GamepadId,
"A");touch.TouchEvent += OnGesture;
touch.StartMonitoringTouch();if (e.IsTap)
{
}| Term | Meaning |
|---|---|
| Adapter | Platform-specific object that exposes current device state through a Gondwana interface |
| Poller | Engine-side object that reads an adapter and raises normalized Gondwana events |
| Transition | A change such as up → down or down → up |
| Throttling | Limiting how frequently an event may be emitted |
| Repeat | Continued event generation while an input remains held |
| Contact | One active touch pointer or finger |
| Gesture recognizer | State machine that converts raw touch lifecycles into tap, swipe, or pinch events |
| Deadzone | Analog range near zero treated as neutral |
| Binding | Mapping from a physical input to a game-specific action |
| Engine thread | The thread on which the normal Gondwana cycle and poller callbacks execute |
Gondwana/EngineInputSystems.csGondwana/Engine.csGondwana/Input/InputEventConfigurationBase.csGondwana/Configuration/EngineConfiguration.cs
Gondwana/Input/Keyboard/IKeyboardAdapter.csGondwana/Input/Keyboard/KeyboardEventPoller.csGondwana/Input/Keyboard/KeyDownEventArgs.csGondwana/Input/Keyboard/KeyEventConfiguration.csGondwana/Input/Keyboard/KeyAction.csGondwana/Input/Keyboard/KeyboardModifierState.cs
Gondwana/Input/Mouse/IMouseAdapter.csGondwana/Input/Mouse/MouseEventPoller.csGondwana/Input/Mouse/MouseEventArgs.csGondwana/Input/Mouse/MouseEventConfiguration.csGondwana/Input/Mouse/MouseButton.csGondwana/Input/Mouse/MouseButtonState.cs
Gondwana/Input/Gamepad/IGamepadAdapter.csGondwana/Input/Gamepad/IGamepadManager.csGondwana/Input/Gamepad/GamepadEventPoller.csGondwana/Input/Gamepad/GamepadButtonDownEventArgs.csGondwana/Input/Gamepad/GamepadStickState.csGondwana.WinForms/Input/Gamepad/XInput/Gondwana.Input.SDL2/Gamepad/SDL2/
Gondwana/Input/Touch/ITouchAdapter.csGondwana/Input/Touch/ITouchInput.csGondwana/Input/Touch/TouchEventPoller.csGondwana/Input/Touch/TouchPoint.csGondwana/Input/Touch/TouchEventArgs.csGondwana/Input/Touch/Gestures/
- Keyboard Input Quick Start
- Mouse Input Quick Start
- Gamepad Input Quick Start
- Touch Input Quick Start
- Make Your First Game in 1 Hour
- Creating Your Own Custom Widget
The platform adapter knows the device.
The poller knows timing and transitions.
Engine.Input exposes the configured subsystem.
The GameHost wires lifecycle and cleanup.
The game decides what the input means.
That separation is the center of Gondwana's input architecture.
- Home
- Make Your First Game in 30 Minutes
- Engine Architecture Overview
- Gondwana Engine Lifecycle
- Gondwana CLI Cheatsheet
- Assets Files
- Tilesheets
- Scenes and SceneLayers
- Sprites
- Views, Cameras, and Viewports
- DirectDrawing
- Game State Files
- Logging
- Movement and Controllers
- Input Handling
- Collision Detection
- Timers and Engine Timing
- Using the Effects System
- Engine Configuration