diff --git a/runtimes/web/src/ui/app.ts b/runtimes/web/src/ui/app.ts index 24cbadc5..1b90c475 100644 --- a/runtimes/web/src/ui/app.ts +++ b/runtimes/web/src/ui/app.ts @@ -19,6 +19,8 @@ class InputState { mouseButtons = 0; } +const maxVibrateMs = 1024; + @customElement("wasm4-app") export class App extends LitElement { static styles = css` @@ -76,6 +78,11 @@ export class App extends LitElement { private readonly diskPrefix: string; + private _vibrateMs = 0; + get vibrateMs (): number { + return this._vibrateMs; + } + readonly onPointerUp = (event: PointerEvent) => { if (event.pointerType == "touch") { // Try to go fullscreen on mobile @@ -670,6 +677,23 @@ export class App extends LitElement { return netplay; } + canVibrate (): boolean { + // No reliable way to tell, so best guess + return 'ontouchstart' in window && navigator.vibrate !== undefined; + } + + cycleVibrateLevel (): number { + if (this._vibrateMs < 1) { + this._vibrateMs = 1; + } else if (this._vibrateMs < maxVibrateMs) { + this._vibrateMs *= 2; + } else { + this._vibrateMs = 0; + } + + return this._vibrateMs; + } + getNetplaySummary () { return this.netplay ? this.netplay.getSummary() : []; } diff --git a/runtimes/web/src/ui/menu-overlay.ts b/runtimes/web/src/ui/menu-overlay.ts index e5ce13be..425fb80e 100644 --- a/runtimes/web/src/ui/menu-overlay.ts +++ b/runtimes/web/src/ui/menu-overlay.ts @@ -3,6 +3,7 @@ import { customElement, state } from 'lit/decorators.js'; import { map } from 'lit/directives/map.js'; import { App } from "./app"; +import { VirtualGamepad } from "./virtual-gamepad"; import * as constants from "../constants"; const optionContext = { @@ -15,10 +16,11 @@ const optionIndex = [ CONTINUE: 0, SAVE_STATE: 1, LOAD_STATE: 2, - DISK_OPTIONS: 3, + BUTTON_VIBRATE: 3, + DISK_OPTIONS: 4, // OPTIONS: null, - COPY_NETPLAY_LINK: 4, - RESET_CART: 5, + COPY_NETPLAY_LINK: 5, + RESET_CART: 6, }, { BACK: 0, @@ -33,6 +35,7 @@ const options = [ "CONTINUE", "SAVE STATE", "LOAD STATE", + "VIBRATE:", "DISK OPTIONS", // "OPTIONS", "COPY NETPLAY URL", @@ -115,6 +118,7 @@ export class MenuOverlay extends LitElement { @state() private selectedIdx = 0; @state() private netplaySummary: { playerIdx: number, ping: number }[] = []; + @state() private vibrateLevel = ""; private netplayPollInterval?: number; @@ -183,6 +187,9 @@ export class MenuOverlay extends LitElement { this.app.loadGameState(); this.app.closeMenu(); break; + case this.optionIndex.BUTTON_VIBRATE: + this.vibrateLevel = this.app.cycleVibrateLevel() + "ms"; + break; case this.optionIndex.DISK_OPTIONS: this.switchContext(optionContext.DISK); break; @@ -219,9 +226,17 @@ export class MenuOverlay extends LitElement { if (pressedThisFrame & constants.BUTTON_DOWN) { this.selectedIdx++; + if (this.optionContext === optionContext.DEFAULT && this.selectedIdx === this.optionIndex.BUTTON_VIBRATE && + !this.app.canVibrate()) { + this.selectedIdx++; + } } if (pressedThisFrame & constants.BUTTON_UP) { this.selectedIdx--; + if (this.optionContext === optionContext.DEFAULT && this.selectedIdx === this.optionIndex.BUTTON_VIBRATE && + !this.app.canVibrate()) { + this.selectedIdx--; + } } this.selectedIdx = (this.selectedIdx + this.options.length) % this.options.length; } @@ -243,11 +258,16 @@ export class MenuOverlay extends LitElement { } render () { + if (!this.vibrateLevel) { + this.vibrateLevel = this.app.vibrateMs + "ms"; + } return html`