Gamepad
GamepadView draws a touch controller — d-pads, analog sticks, face buttons, shoulders and triggers —
and lets the player hold several of them at once. Lay it over a game as a full-screen overlay, or place
it inline like any other control.
It is a real controller. GamepadView.Gamepad is a Shiny.Gamepad IGamepad whose
Kind is GamepadKind.Virtual. With the registration below it is also listed by IGamepadManager
next to any physical controller. Game code written against Shiny.Gamepad needs no changes to be played
with it: GetState(), ButtonChanged, AxisChanged, SetVibration and WaitForButton all work.
The shared package is the engine both hosts use: layouts, hit testing, and the stick and d-pad arithmetic. It is referenced for you; install it directly only to build your own renderer.
builder .UseMauiApp<App>() .UseShinyGamepad();UseShinyGamepad() registers the multi-touch handler for iOS and Android, plus a
VirtualGamepadManager that reports on-screen and physical controllers together as IGamepadManager.
The view is in the usual http://shiny.net/maui/controls XAML namespace, so no extra xmlns is needed.
builder.Services.AddShinyGamepad();@using Shiny.Gamepad@using Shiny.Controls.Gaming@using Shiny.Blazor.Controls.GamingEverything is registered Scoped, the browser’s physical-controller manager included. On Blazor Server a singleton would share one user’s pad with every connected user.
Overlay or inline
Section titled “Overlay or inline”Sizing picks how the layout fits the view:
Anchored(default) — each element sits at a fixed offset from a corner or edge at its natural size, timesControllerScale. The d-pad stays under the left thumb in portrait and landscape. Use it for an overlay covering the game.Uniform— the layout is arranged on its own design canvas and scaled to fit, with a controller body behind it (ShowBody). Use it for a controller placed inline on a page.
With PassThrough (on by default), a touch that lands on no element falls through to whatever is
beneath — the game gets every tap that misses the controller. On Blazor that is CSS pointer-events, on
Android a declined ACTION_DOWN, and on iOS PointInside.
<Grid> <!-- the game --> <GraphicsView x:Name="Game" Drawable="{Binding Drawable}" />
<!-- the controller, over it --> <shiny:GamepadView x:Name="Pad" Preset="Snes" HideWhenControllerConnected="True" IdleOpacity="0.4" /></Grid><div style="position:relative; height:100vh"> <canvas @ref="game" style="position:absolute; inset:0"></canvas>
<div style="position:absolute; inset:0; pointer-events:none"> <GamepadView @ref="pad" Preset="GamepadPreset.Snes" HideWhenControllerConnected="true" IdleOpacity="0.4" /> </div></div>Presets and face styles
Section titled “Presets and face styles”Preset |
Elements |
|---|---|
Nes |
D-pad, B and A, Select and Start |
Snes |
D-pad, Y/X/B/A diamond, L and R, Select and Start |
Standard (default) |
Two sticks, d-pad, face diamond, bumpers, triggers, View/Menu and Home — the Xbox / PlayStation / Switch Pro layout |
TwinStick |
Two sticks and a pause button |
Arcade |
One stick and six buttons in two staggered rows |
FaceStyle relabels the buttons without changing what they report: Xbox (coloured A/B/X/Y, LB/RT,
View/Menu), PlayStation (drawn cross, circle, square and triangle; L1/R2; Create/Options), Nintendo
(B/A/Y/X, L/ZR, −/+), SuperNintendo (the Super Famicom’s four colours) and Nes. Leave it null for
the preset’s own style.
Reading input
Section titled “Reading input”Read the controller the way a game reads any controller — poll it every frame:
var state = Pad.Gamepad.GetState();var move = state.GetMovement(); // left stick, with a deadzonevar dpad = state.DPad; // -1..1 on each axisif (state.IsPressed(GamepadButton.A)) player.Jump();Or listen for changes. ButtonChanged and AxisChanged on the view are raised on the UI thread (MAUI
events, Blazor OnButtonChanged / OnAxisChanged callbacks). The same events on Gamepad itself are
raised on the input thread, as they are for hardware.
Sticks report -1 to 1 with Y positive up, as every Shiny.Gamepad backend does. Triggers report 0 or
1 and fold into Buttons while held.
With the registration above, the pad is also in IGamepadManager.GetGamepads(). It raises Connected
when the view appears and Disconnected when it goes. A MAUI view that is shown again hands out a new
VirtualGamepad with the same GamepadId, the way a physical controller comes back as a new object.
Set PlayerIndex to seat it as a given player.
Touch behaviour
Section titled “Touch behaviour”- Multi-touch. A thumb on a stick while the other presses a button. Native on iOS and Android. Other MAUI platforms (Mac Catalyst, Windows, AppKit, GTK) read a single pointer, which suits a mouse. Blazor uses pointer events and is multi-touch in every mobile browser.
- Sticks and d-pads capture their finger until it lifts, so a thumb drifting off the edge keeps
steering.
DPadModeisEightWay(default) orFourWay, and the centre of the d-pad is neutral. - Buttons can be rolled across. A thumb between two neighbouring buttons presses both, and sliding moves the press — rolling across NES B and A behaves as it does on the real pad.
- Stick click. Double-tap a stick and hold to press L3/R3 (
StickClickEnabled). - Floating sticks.
GamepadElement.IsFloatingre-centres a stick wherever the thumb lands withinFloatingZoneof it, and it springs home on release. - Turbo.
GamepadElement.IsTurborapid-fires a held button atTurboRatepresses per second.
Haptics and vibration
Section titled “Haptics and vibration”| Property | Default | When it ticks |
|---|---|---|
ButtonHapticFeedback |
true |
A non-directional button is pressed — face, shoulders, triggers, Start/Select, Home |
DirectionalHapticFeedback |
false |
The d-pad takes a new direction (off by default — a rolling thumb would buzz) |
Turbo repeats never tick. On Blazor both use navigator.vibrate, which Android browsers implement and
iOS Safari does not.
Gamepad.SetVibration(...) from game code runs the device’s own motor, and GamepadCapabilities.Vibration
is reported only where one is reachable. On Android the app needs the VIBRATE permission. On iOS a
buzz has a fixed length. In the browser, Android only.
Stepping aside
Section titled “Stepping aside”HideWhenControllerConnectedhides the pad, and lets every touch through, while a physical controller is connected. It comes back when the controller goes. In a browser, a controller only becomes visible after the player presses a button on it.IdleOpacityfades the pad afterIdleDelaywithout a touch (1, the default, disables fading). It comes back to full at the next touch.
Next: Layouts & Customizing — custom layouts, the drag-and-pinch editor, saving layouts, and colours.


