Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve joystick support for up to 32 axes and buttons #1375

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/components/ExpansiblePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
<v-icon :size="interfaceStore.isOnSmallScreen ? 15 : 18" color="white" icon="mdi-information-outline" />
</v-btn>
</div>
<div v-if="hasWarningSlot" class="flex justify-end items-center w-[10%] relative">
<div v-if="hasWarningSlot" class="flex justify-end items-center w-[10%]">
<v-btn
class="ml-auto w-[10px] rounded-full mr-2"
class="rounded-full relative overflow-hidden"
size="small"
color="transparent"
elevation="0"
Expand Down Expand Up @@ -310,6 +310,7 @@ const hasWarningSlot = computed(() => !!slots.warning?.())
border-radius: 50%;
transform: translate(-50%, -50%) scale(0);
animation: rippleEffect 1.5s infinite;
pointer-events: none;
}

@keyframes rippleEffect {
Expand Down
22 changes: 13 additions & 9 deletions src/components/mini-widgets/JoystickCommIndicator.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
<template>
<div>
<div v-tooltip="tooltipText" class="relative cursor-pointer" :class="indicatorClass" @click="showDialog = true">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to better get, what was breaking here? I tried reproducing but saw no bug.

Copy link
Contributor Author

@ArturoManzoli ArturoManzoli Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the joystick config page was already open, showing this screen
image
And a joystick input was pressed, the whole config screen broke with a vNode error (dom injection incomplete).

The cause was the v-tooltip, when used as a prop, sometimes has bad collateral effects like this one. (Vuetify's fault).

(Running on master branch)
https://github.com/user-attachments/assets/2d4d11ce-c1b7-4e3b-939e-d1b11e0e8292

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow! I actually saw this one already, but had no idea the problem was the indicator! Nice catch!

<FontAwesomeIcon icon="fa-solid fa-gamepad" size="xl" />
<FontAwesomeIcon
v-if="!joystickConnected || !controllerStore.enableForwarding"
icon="fa-solid fa-slash"
size="xl"
class="absolute left-0"
/>
</div>
<v-tooltip :text="tooltipText" location="bottom">
<template #activator="{ props: tooltipProps }">
<div v-bind="tooltipProps" class="relative cursor-pointer" :class="indicatorClass" @click="showDialog = true">
<FontAwesomeIcon icon="fa-solid fa-gamepad" size="xl" />
<FontAwesomeIcon
v-if="!joystickConnected || !controllerStore.enableForwarding"
icon="fa-solid fa-slash"
size="xl"
class="absolute left-0"
/>
</div>
</template>
</v-tooltip>

<InteractionDialog
v-model="showDialog"
Expand Down
61 changes: 52 additions & 9 deletions src/stores/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useDocumentVisibility } from '@vueuse/core'
import { saveAs } from 'file-saver'
import { defineStore } from 'pinia'
import { v4 as uuid4 } from 'uuid'
import { computed, ref, toRaw, watch } from 'vue'
import { computed, onMounted, ref, toRaw, watch } from 'vue'

import {
availableGamepadToCockpitMaps,
Expand Down Expand Up @@ -82,6 +82,44 @@ export const useControllerStore = defineStore('controller', () => {
protocolMappingIndex.value = mappingIndex
}

const initializeProtocolMapping = (mapping: JoystickProtocolActionsMapping): void => {
// Initialize axesCorrespondencies for all axes up to 31
for (let axis = 0; axis <= 31; axis++) {
if (mapping.axesCorrespondencies[axis] === undefined) {
mapping.axesCorrespondencies[axis] = {
action: otherAvailableActions.no_function,
min: -1.0,
max: 1.0,
}
}
}

// Initialize buttonsCorrespondencies for all buttons up to 31
const modifierKeys = Object.keys(mapping.buttonsCorrespondencies)
for (const modKey of modifierKeys) {
const buttonsCorrespondency = mapping.buttonsCorrespondencies[modKey as CockpitModifierKeyOption]
for (let button = 0; button <= 31; button++) {
if (buttonsCorrespondency[button] === undefined) {
buttonsCorrespondency[button] = {
action: otherAvailableActions.no_function,
}
}
}
}
}

onMounted(() => {
initializeProtocolMapping(protocolMapping.value)
})

watch(
() => protocolMapping.value,
(newMapping) => {
initializeProtocolMapping(newMapping)
},
{ immediate: true, deep: true }
)

const registerControllerUpdateCallback = (callback: controllerUpdateCallback): void => {
updateCallbacks.value.push(callback)
}
Expand Down Expand Up @@ -161,13 +199,13 @@ export const useControllerStore = defineStore('controller', () => {
): ProtocolAction[] => {
let modifierKeyId = modifierKeyActions.regular.id

Object.entries(mapping.buttonsCorrespondencies.regular).forEach((e) => {
const buttonActive = joystickState.buttons[Number(e[0])] ?? 0 > 0.5
Object.entries(mapping.buttonsCorrespondencies.regular).forEach(([key, value]) => {
const buttonActive = joystickState.buttons[Number(key)] ?? 0 > 0.5
const isModifier = Object.values(modifierKeyActions)
.map((a) => JSON.stringify(a))
.includes(JSON.stringify(e[1].action))
.includes(JSON.stringify(value.action))
if (buttonActive && isModifier) {
modifierKeyId = e[1].action.id
modifierKeyId = value.action.id
}
})

Expand All @@ -176,10 +214,15 @@ export const useControllerStore = defineStore('controller', () => {
const activeActions = joystickState.buttons
.map((btnState, idx) => ({ id: idx, value: btnState }))
.filter((btn) => btn.value ?? 0 > 0.5)
.map(
(btn) =>
mapping.buttonsCorrespondencies[modifierKeyId as CockpitModifierKeyOption][btn.id as JoystickButton].action
)
.map((btn) => {
const btnMapping = mapping.buttonsCorrespondencies[modifierKeyId as CockpitModifierKeyOption][btn.id]
if (btnMapping && btnMapping.action) {
return btnMapping.action
} else {
// Return a default action or handle accordingly
return otherAvailableActions.no_function
}
})

return activeActions.concat(modKeyAction)
}
Expand Down
34 changes: 31 additions & 3 deletions src/types/joystick.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export type JoystickAxisActionCorrespondency = {
/**
* The ID of the axis that holds the correspondent action
*/
[key in JoystickAxis]: {
[key in number]: {
/**
* The protocol action that should be triggered
*/
Expand All @@ -117,7 +117,7 @@ export type JoystickButtonActionCorrespondency = {
/**
* The ID of the button that holds the correspondent action
*/
[key in JoystickButton]: {
[key in number]: {
/**
* The protocol action that should be triggered
*/
Expand Down Expand Up @@ -159,7 +159,7 @@ export interface JoystickProtocolActionsMapping {
}

export type CockpitButton = null | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 // eslint-disable-line
export type CockpitAxis = null | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
export type CockpitAxis = null | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 // eslint-disable-line

/**
* This interface defines the mapping for a specific controller from the Gamepad API to Cockpit's standard.
Expand Down Expand Up @@ -233,6 +233,34 @@ export enum JoystickAxis {
A1 = 1, // Vertical axis for left stick (negative up/positive down)
A2 = 2, // Horizontal axis for right stick (negative left/positive right)
A3 = 3, // Vertical axis for right stick (negative up/positive down)
A4 = 4, // Left trigger (positive pressed)
A5 = 5, // Right trigger (positive pressed)
A6 = 6, // Horizontal axis for D-pad (negative left/positive right)
A7 = 7, // Vertical axis for D-pad (negative up/positive down)
A8 = 8, // Extra non-standard axes
A9 = 9, // Extra non-standard axes
A10 = 10, // Extra non-standard axes
A11 = 11, // Extra non-standard axes
A12 = 12, // Extra non-standard axes
A13 = 13, // Extra non-standard axes
A14 = 14, // Extra non-standard axes
A15 = 15, // Extra non-standard axes
A16 = 16, // Extra non-standard axes
A17 = 17, // Extra non-standard axes
A18 = 18, // Extra non-standard axes
A19 = 19, // Extra non-standard axes
A20 = 20, // Extra non-standard axes
A21 = 21, // Extra non-standard axes
A22 = 22, // Extra non-standard axes
A23 = 23, // Extra non-standard axes
A24 = 24, // Extra non-standard axes
A25 = 25, // Extra non-standard axes
A26 = 26, // Extra non-standard axes
A27 = 27, // Extra non-standard axes
A28 = 28, // Extra non-standard axes
A29 = 29, // Extra non-standard axes
A30 = 30, // Extra non-standard axes
A31 = 31, // Extra non-standard axes
}

/**
Expand Down
17 changes: 9 additions & 8 deletions src/views/ConfigurationJoystickView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
:is-expanded="!interfaceStore.isOnPhoneScreen"
compact
>
<template #title>General</template>
<template #title>General settings</template>
<template #info>
<div class="flex flex-col items-start px-5 font-medium">
<li>
Expand Down Expand Up @@ -221,7 +221,7 @@
:items="tableItems"
class="elevation-1 bg-transparent rounded-lg mb-[20px]"
theme="dark"
no-data-text="Press any joystick button"
no-data-text=""
:style="interfaceStore.globalGlassMenuStyles"
>
<template #headers>
Expand All @@ -233,6 +233,7 @@
<th class="w-[120px] text-center"><p class="text-[16px] font-bold">Axis</p></th>
<th class="w-[110px] text-center"><p class="text-[16px] font-bold">Max</p></th>
</tr>
<p v-if="tableItems.length === 0" class="fixed top-[67%] left-[40%]">Press a key or move an axis</p>
</template>
<template #item="{ item }">
<tr v-if="item.type === 'axis'">
Expand Down Expand Up @@ -342,7 +343,7 @@
v-if="currentJoystick && currentJoystick?.gamepadToCockpitMap?.buttons"
:headers="headers"
:items="tableItems"
items-per-page="32"
:items-per-page="64"
class="elevation-1 bg-transparent rounded-lg mt-[5px]"
theme="dark"
:style="interfaceStore.globalGlassMenuStyles"
Expand Down Expand Up @@ -424,7 +425,7 @@
</BaseConfigurationView>
<teleport to="body">
<InteractionDialog
v-if="currentJoystick"
v-show="currentJoystick"
:show-dialog="inputClickedDialog"
max-width="auto"
variant="text-only"
Expand Down Expand Up @@ -627,7 +628,7 @@ const inputClickedDialog = ref(false)
const currentModifierKey: Ref<ProtocolAction> = ref(modifierKeyActions.regular)
const availableModifierKeys: ProtocolAction[] = Object.values(modifierKeyActions)
const showJoystickLayout = ref(true)
const currentTabVIew = ref()
const currentTabVIew = ref('table')

const protocols = Object.values(JoystickProtocol).filter((value) => typeof value === 'string')

Expand Down Expand Up @@ -682,12 +683,12 @@ const tableItems = computed(() => {
}

const originalAxes = currentJoystick.value.gamepadToCockpitMap?.axes || []
const axesItems = originalAxes.slice(0, 4).map((axis) => ({ type: 'axis', id: axis }))
const axesItems = originalAxes.slice(0, 31).map((axis) => ({ type: 'axis', id: axis }))

const originalButtons = currentJoystick.value.gamepadToCockpitMap?.buttons || []
const buttonItems = originalButtons
.map((button, index) => ({ type: 'button', id: button, index: index }))
.filter((button) => button.index >= 0 && button.index <= 15)
.filter((button) => button.index >= 0 && button.index <= 31)

return [...axesItems, ...buttonItems]
})
Expand Down Expand Up @@ -732,7 +733,7 @@ const unbindCurrentInput = (input: JoystickButtonInput): void => {
updateButtonAction(input, actions)
}

const updateButtonAction = (input: JoystickInput, action: ProtocolAction): void => {
const updateButtonAction = (input: JoystickButtonInput, action: ProtocolAction): void => {
controllerStore.protocolMapping.buttonsCorrespondencies[currentModifierKey.value.id as CockpitModifierKeyOption][
input.id
].action = action
Expand Down
Loading