|
| 1 | +<script setup lang="ts"> |
| 2 | +import {computed, inject} from 'vue' |
| 3 | +import type {GamepadController} from "@/providers/gamepadController"; |
| 4 | +import {GAMEPAD_BUTTON_ACTIONS, GAMEPAD_BUTTON_LABELS} from "@/providers/gamepadController"; |
| 5 | +
|
| 6 | +const gamepadController = inject<GamepadController>('gamepad-controller')! |
| 7 | +
|
| 8 | +const connected = computed(() => gamepadController.state.connected) |
| 9 | +const gamepadId = computed(() => { |
| 10 | + // Shorten the raw gamepad id for display |
| 11 | + const id = gamepadController.state.gamepadId |
| 12 | + if (!id) return '' |
| 13 | + // Try to extract a human-friendly name from strings like: |
| 14 | + // "DualSense Wireless Controller (STANDARD GAMEPAD Vendor: 054c Product: 0ce6)" |
| 15 | + const parenIdx = id.indexOf('(') |
| 16 | + return parenIdx > 0 ? id.slice(0, parenIdx).trim() : id |
| 17 | +}) |
| 18 | +
|
| 19 | +const activeButton = computed(() => gamepadController.state.activeButton) |
| 20 | +const activeButtonLabel = computed(() => { |
| 21 | + const b = activeButton.value |
| 22 | + if (b === null) return null |
| 23 | + return GAMEPAD_BUTTON_LABELS[b] ?? `Button ${b}` |
| 24 | +}) |
| 25 | +const activeActionLabel = computed(() => { |
| 26 | + const b = activeButton.value |
| 27 | + if (b === null) return null |
| 28 | + return GAMEPAD_BUTTON_ACTIONS[b] ?? null |
| 29 | +}) |
| 30 | +
|
| 31 | +// Gamepad reference button layout for the tooltip |
| 32 | +const buttonMap = Object.entries(GAMEPAD_BUTTON_LABELS) |
| 33 | + .filter(([idx]) => GAMEPAD_BUTTON_ACTIONS[Number(idx)] !== undefined) |
| 34 | + .map(([idx, label]) => ({ |
| 35 | + button: label, |
| 36 | + action: GAMEPAD_BUTTON_ACTIONS[Number(idx)], |
| 37 | + })) |
| 38 | +</script> |
| 39 | + |
| 40 | +<template> |
| 41 | + <!-- Gamepad icon shown in the toolbar --> |
| 42 | + <q-btn |
| 43 | + dense flat round |
| 44 | + :icon="connected ? 'sports_esports' : 'videogame_asset_off'" |
| 45 | + :color="connected ? 'white' : 'grey-5'" |
| 46 | + :title="connected ? `Connected: ${gamepadId}` : 'No gamepad connected'" |
| 47 | + > |
| 48 | + <!-- Active button flash badge --> |
| 49 | + <q-badge v-if="activeButton !== null" floating color="amber" text-color="black"> |
| 50 | + {{ activeButtonLabel }} |
| 51 | + </q-badge> |
| 52 | + |
| 53 | + <!-- Tooltip with full button map --> |
| 54 | + <q-tooltip anchor="bottom right" self="top right" :offset="[0, 8]" max-width="360px"> |
| 55 | + <div class="text-subtitle2 q-mb-xs"> |
| 56 | + <q-icon name="sports_esports" class="q-mr-xs"/> |
| 57 | + Gamepad Controls |
| 58 | + </div> |
| 59 | + |
| 60 | + <div v-if="!connected" class="text-caption text-grey-4"> |
| 61 | + Connect a PS5 DualSense or compatible gamepad to use physical controls. |
| 62 | + </div> |
| 63 | + |
| 64 | + <template v-else> |
| 65 | + <div class="text-caption text-grey-3 q-mb-sm">{{ gamepadId }}</div> |
| 66 | + |
| 67 | + <q-markup-table dense flat dark class="gamepad-table"> |
| 68 | + <thead> |
| 69 | + <tr> |
| 70 | + <th class="text-left">Button</th> |
| 71 | + <th class="text-left">Action</th> |
| 72 | + </tr> |
| 73 | + </thead> |
| 74 | + <tbody> |
| 75 | + <tr |
| 76 | + v-for="entry in buttonMap" |
| 77 | + :key="entry.button" |
| 78 | + :class="{'text-amber': activeButtonLabel === entry.button}" |
| 79 | + > |
| 80 | + <td> |
| 81 | + <q-chip dense size="sm" color="grey-8" text-color="white">{{ entry.button }}</q-chip> |
| 82 | + </td> |
| 83 | + <td class="text-caption">{{ entry.action }}</td> |
| 84 | + </tr> |
| 85 | + </tbody> |
| 86 | + </q-markup-table> |
| 87 | + </template> |
| 88 | + </q-tooltip> |
| 89 | + </q-btn> |
| 90 | + |
| 91 | + <!-- Live action indicator when a button is pressed --> |
| 92 | + <transition name="fade"> |
| 93 | + <q-chip |
| 94 | + v-if="activeButton !== null && activeActionLabel" |
| 95 | + dense |
| 96 | + color="amber" |
| 97 | + text-color="black" |
| 98 | + icon="sports_esports" |
| 99 | + class="q-mx-xs" |
| 100 | + > |
| 101 | + {{ activeActionLabel }} |
| 102 | + </q-chip> |
| 103 | + </transition> |
| 104 | +</template> |
| 105 | + |
| 106 | +<style scoped> |
| 107 | +.gamepad-table { |
| 108 | + min-width: 280px; |
| 109 | +} |
| 110 | +
|
| 111 | +.fade-enter-active, |
| 112 | +.fade-leave-active { |
| 113 | + transition: opacity 0.15s ease; |
| 114 | +} |
| 115 | +
|
| 116 | +.fade-enter-from, |
| 117 | +.fade-leave-to { |
| 118 | + opacity: 0; |
| 119 | +} |
| 120 | +</style> |
0 commit comments