From 8491f0b1ad7fa2374bc16887a36ba7ba3ea51a81 Mon Sep 17 00:00:00 2001 From: Greenlamp Date: Thu, 16 May 2024 12:30:13 +0200 Subject: [PATCH] added navigation icon in the setting menu --- src/configs/cfg_keyboard_azerty.ts | 4 +- src/configs/configHandler.ts | 9 ++++ src/inputs-controller.ts | 31 +++++++++++-- src/test/cfg_keyboard.example.ts | 4 +- src/test/helpers/inGameManip.ts | 32 +++++++++++++- src/test/helpers/menuManip.ts | 1 + src/test/rebinding_setting.test.ts | 17 +++++++- src/touch-controls.js | 4 +- .../settings/abstract-settings-ui-handler.ts | 37 ++++++++++++++-- src/ui/settings/settings-ui-handler.ts | 43 ++++++++++++++++--- 10 files changed, 161 insertions(+), 21 deletions(-) diff --git a/src/configs/cfg_keyboard_azerty.ts b/src/configs/cfg_keyboard_azerty.ts index c1f94f4ef28..80bd6346da8 100644 --- a/src/configs/cfg_keyboard_azerty.ts +++ b/src/configs/cfg_keyboard_azerty.ts @@ -2,8 +2,8 @@ import {Button} from "#app/enums/buttons"; import {SettingKeyboard} from "#app/system/settings-keyboard"; const cfg_keyboard_azerty = { - padID: 'keyboard', - padType: 'default', + padID: 'default', + padType: 'keyboard', deviceMapping: { KEY_A: Phaser.Input.Keyboard.KeyCodes.A, KEY_B: Phaser.Input.Keyboard.KeyCodes.B, diff --git a/src/configs/configHandler.ts b/src/configs/configHandler.ts index 8a94bf47c78..650cbdc39fd 100644 --- a/src/configs/configHandler.ts +++ b/src/configs/configHandler.ts @@ -1,3 +1,5 @@ +import {Device} from "#app/enums/devices"; + /** * Retrieves the key associated with the specified keycode from the mapping. * @@ -118,6 +120,13 @@ export function getIconWithSettingName(config, settingName) { return getIconWithKey(config, key); } +export function getIconForLatestInput(configs, source, devices, settingName) { + let config; + if (source === 'gamepad') config = configs[devices[Device.GAMEPAD]]; + else config = configs[devices[Device.KEYBOARD]]; + return getIconWithSettingName(config, settingName); +} + /** * Retrieves the setting name associated with the specified button. * diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts index 8043762aabe..c7dba61299e 100644 --- a/src/inputs-controller.ts +++ b/src/inputs-controller.ts @@ -11,7 +11,7 @@ import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler" import SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler"; import cfg_keyboard_azerty from "./configs/cfg_keyboard_azerty"; import {Device} from "#app/enums/devices"; -import {getButtonWithKeycode, regenerateIdentifiers, swap} from "#app/configs/configHandler"; +import {getButtonWithKeycode, getIconForLatestInput, regenerateIdentifiers, swap} from "#app/configs/configHandler"; export interface DeviceMapping { [key: string]: number; @@ -321,6 +321,7 @@ export class InputsController { * @param thisGamepad The gamepad that is being set up. */ setupGamepad(thisGamepad: Phaser.Input.Gamepad.Gamepad): void { + this.lastSource = 'gamepad'; const allGamepads = this.getGamepadsName(); for (const gamepad of allGamepads) { const gamepadID = gamepad.toLowerCase(); @@ -381,12 +382,12 @@ export class InputsController { * @param event The keyboard event. */ keyboardKeyDown(event): void { + this.lastSource = 'keyboard'; const keyDown = event.keyCode; this.ensureKeyboardIsInit(); if (this.keys.includes(keyDown)) return; this.keys.push(keyDown); const buttonDown = getButtonWithKeycode(this.getActiveConfig(Device.KEYBOARD), keyDown); - this.lastSource = 'keyboard'; if (buttonDown !== undefined) { this.events.emit('input_down', { controller_type: 'keyboard', @@ -402,6 +403,7 @@ export class InputsController { * @param event The keyboard event. */ keyboardKeyUp(event): void { + this.lastSource = 'keyboard'; const keyDown = event.keyCode; this.keys = this.keys.filter(k => k !== keyDown); this.ensureKeyboardIsInit() @@ -428,11 +430,11 @@ export class InputsController { if (!this.configs[this.selectedDevice[Device.KEYBOARD]]?.padID) this.setupKeyboard(); if (!pad) return; + this.lastSource = 'gamepad'; if (!this.selectedDevice[Device.GAMEPAD]) this.setChosenGamepad(pad.id); if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD].toLowerCase()) return; const buttonDown = getButtonWithKeycode(this.getActiveConfig(Device.GAMEPAD), button.index); - this.lastSource = 'gamepad'; if (buttonDown !== undefined) { this.events.emit('input_down', { controller_type: 'gamepad', @@ -453,6 +455,7 @@ export class InputsController { */ gamepadButtonUp(pad: Phaser.Input.Gamepad.Gamepad, button: Phaser.Input.Gamepad.Button, value: number): void { if (!pad) return; + this.lastSource = 'gamepad'; if (!this.gamepadSupport || pad.id.toLowerCase() !== this.selectedDevice[Device.GAMEPAD]) return; const buttonUp = getButtonWithKeycode(this.getActiveConfig(Device.GAMEPAD), button.index); if (buttonUp !== undefined) { @@ -650,6 +653,28 @@ export class InputsController { return null; } + getIconForLatestInputRecorded(settingName) { + if (this.lastSource === 'keyboard') this.ensureKeyboardIsInit(); + return getIconForLatestInput(this.configs, this.lastSource, this.selectedDevice, settingName); + } + + getLastSourceDevice(): Device { + if (this.lastSource === 'gamepad') return Device.GAMEPAD; + else return Device.KEYBOARD; + } + + getLastSourceConfig() { + const sourceDevice = this.getLastSourceDevice(); + if (sourceDevice === Device.KEYBOARD) + this.ensureKeyboardIsInit(); + return this.getActiveConfig(sourceDevice); + } + + getLastSourceType() { + const config = this.getLastSourceConfig(); + return config?.padType; + } + /** * Injects a custom mapping configuration into the configuration for a specific gamepad. * If the device does not have an existing configuration, it initializes one first. diff --git a/src/test/cfg_keyboard.example.ts b/src/test/cfg_keyboard.example.ts index 44c2f3905d6..dbca2934c9d 100644 --- a/src/test/cfg_keyboard.example.ts +++ b/src/test/cfg_keyboard.example.ts @@ -39,8 +39,8 @@ export enum SettingInterface { } const cfg_keyboard_azerty = { - padID: 'keyboard', - padType: 'default', + padID: 'default', + padType: 'keyboard', deviceMapping: { KEY_A: Phaser.Input.Keyboard.KeyCodes.A, KEY_B: Phaser.Input.Keyboard.KeyCodes.B, diff --git a/src/test/helpers/inGameManip.ts b/src/test/helpers/inGameManip.ts index 7b7b0fedb16..c28189e18d8 100644 --- a/src/test/helpers/inGameManip.ts +++ b/src/test/helpers/inGameManip.ts @@ -1,4 +1,7 @@ -import {getSettingNameWithKeycode} from "#app/configs/configHandler"; +import { + getIconForLatestInput, + getSettingNameWithKeycode +} from "#app/configs/configHandler"; import {expect} from "vitest"; import {SettingInterface} from "#app/test/cfg_keyboard.example"; @@ -7,11 +10,18 @@ export class InGameManip { private keycode; private settingName; private icon; - constructor(config) { + private configs; + private latestSource; + private selectedDevice; + + constructor(configs, config, selectedDevice) { this.config = config; + this.configs = configs; + this.selectedDevice = selectedDevice; this.keycode = null; this.settingName = null; this.icon = null; + this.latestSource = null; } whenWePressOnKeyboard(keycode) { @@ -25,6 +35,24 @@ export class InGameManip { return this; } + forTheWantedBind(settingName) { + if (!settingName.includes("Button_")) settingName = "Button_" + settingName; + this.settingName = SettingInterface[settingName]; + return this; + } + + weShouldSeeTheIcon(icon) { + if (!icon.includes("KEY_")) icon = "KEY_" + icon; + this.icon = this.config.icons[icon]; + expect(getIconForLatestInput(this.configs, this.latestSource, this.selectedDevice, this.settingName)).toEqual(this.icon); + return this; + } + + forTheSource(source) { + this.latestSource = source; + return this; + } + normalizeSettingNameString(input) { // Convert the input string to lower case const lowerCasedInput = input.toLowerCase(); diff --git a/src/test/helpers/menuManip.ts b/src/test/helpers/menuManip.ts index 055c4a5b6c9..d4e4cc9ae18 100644 --- a/src/test/helpers/menuManip.ts +++ b/src/test/helpers/menuManip.ts @@ -42,6 +42,7 @@ export class MenuManip { } whenCursorIsOnSetting(settingName) { + if (!settingName.includes("Button_")) settingName = "Button_" + settingName; this.settingName = SettingInterface[settingName]; const buttonName = this.convertNameToButtonString(settingName); expect(this.config.settings[this.settingName]).toEqual(Button[buttonName]); diff --git a/src/test/rebinding_setting.test.ts b/src/test/rebinding_setting.test.ts index b73e2c7d0db..df54f7eedb3 100644 --- a/src/test/rebinding_setting.test.ts +++ b/src/test/rebinding_setting.test.ts @@ -14,17 +14,26 @@ import { } from "#app/configs/configHandler"; import {MenuManip} from "#app/test/helpers/menuManip"; import {InGameManip} from "#app/test/helpers/inGameManip"; +import {Device} from "#app/enums/devices"; +import {InterfaceConfig} from "#app/inputs-controller"; describe('Test Rebinding', () => { let config; let inGame; let inTheSettingMenu; + const configs: Map = new Map(); + const selectedDevice = { + [Device.GAMEPAD]: null, + [Device.KEYBOARD]: 'default', + } + beforeEach(() => { config = deepCopy(cfg_keyboard_azerty); config.custom = {...config.default} regenerateIdentifiers(config); - inGame = new InGameManip(config); + configs.default = config; + inGame = new InGameManip(configs, config, selectedDevice); inTheSettingMenu = new MenuManip(config); }); @@ -308,4 +317,10 @@ describe('Test Rebinding', () => { const buttonDown = config.settings[settingName]; expect(buttonDown).toEqual(Button.DOWN); }); + it("retrieve the correct icon for a given source", () => { + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Shiny").iconDisplayedIs("KEY_R"); + inTheSettingMenu.whenCursorIsOnSetting("Cycle_Form").iconDisplayedIs("KEY_F"); + inGame.forTheSource("keyboard").forTheWantedBind("Cycle_Shiny").weShouldSeeTheIcon("R") + inGame.forTheSource("keyboard").forTheWantedBind("Cycle_Form").weShouldSeeTheIcon("F") + }); }); diff --git a/src/touch-controls.js b/src/touch-controls.js index ca40411fb9b..13ab8d0d55f 100644 --- a/src/touch-controls.js +++ b/src/touch-controls.js @@ -62,13 +62,13 @@ function simulateKeyboardEvent(eventType, key, events) { switch (eventType) { case 'keydown': events.emit('input_down', { - controller_type: 'touch', + controller_type: 'keyboard', button: button, }); break; case 'keyup': events.emit('input_up', { - controller_type: 'touch', + controller_type: 'keyboard', button: button, }); break; diff --git a/src/ui/settings/abstract-settings-ui-handler.ts b/src/ui/settings/abstract-settings-ui-handler.ts index 3d2e1edfea6..c41ee2aa69f 100644 --- a/src/ui/settings/abstract-settings-ui-handler.ts +++ b/src/ui/settings/abstract-settings-ui-handler.ts @@ -41,6 +41,7 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { protected layout: Map = new Map(); // Will contain the input icons from the selected layout protected inputsIcons: InputsIcons; + protected navigationIcons: InputsIcons; // list all the setting keys used in the selected layout (because dualshock has more buttons than xbox) protected keys: Array; @@ -85,17 +86,31 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); headerBg.setOrigin(0, 0); + this.navigationIcons = {}; + + const iconPreviousTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconPreviousTab.setScale(.1); + iconPreviousTab.setOrigin(0, -0.1); + iconPreviousTab.setPositionRelative(headerBg, 8, 4); + this.navigationIcons['BUTTON_CYCLE_FORM'] = iconPreviousTab; + + const iconNextTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconNextTab.setScale(.1); + iconNextTab.setOrigin(0, -0.1); + iconNextTab.setPositionRelative(headerBg, headerBg.width - 20, 4); + this.navigationIcons['BUTTON_CYCLE_SHINY'] = iconNextTab; + const headerText = addTextObject(this.scene, 0, 0, 'General', TextStyle.SETTINGS_LABEL); headerText.setOrigin(0, 0); - headerText.setPositionRelative(headerBg, 8, 4); + headerText.setPositionRelative(headerBg, 8 + iconPreviousTab.width/6 - 4, 4); const gamepadText = addTextObject(this.scene, 0, 0, 'Gamepad', this.titleSelected === 'Gamepad' ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL); gamepadText.setOrigin(0, 0); - gamepadText.setPositionRelative(headerBg, 50, 4); + gamepadText.setPositionRelative(headerBg, 50 + iconPreviousTab.width/6 - 4, 4); const keyboardText = addTextObject(this.scene, 0, 0, 'Keyboard', this.titleSelected === 'Keyboard' ? TextStyle.SETTINGS_SELECTED : TextStyle.SETTINGS_LABEL); keyboardText.setOrigin(0, 0); - keyboardText.setPositionRelative(headerBg, 97, 4); + keyboardText.setPositionRelative(headerBg, 97 + iconPreviousTab.width/6 - 4, 4); this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 2); this.optionsBg.setOrigin(0, 0); @@ -105,6 +120,8 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { this.settingsContainer.add(gamepadText); this.settingsContainer.add(keyboardText); this.settingsContainer.add(this.optionsBg); + this.settingsContainer.add(iconNextTab) + this.settingsContainer.add(iconPreviousTab) /// Initialize a new configuration "screen" for each type of gamepad. for (const config of this.configs) { @@ -235,6 +252,19 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { Object.keys(this.layout).forEach((key) => this.layout[key].optionsContainer.setVisible(false)); // Fetch the active gamepad configuration from the input controller. const activeConfig = this.getActiveConfig(); + + for (const settingName of Object.keys(this.navigationIcons)) { + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else { + this.navigationIcons[settingName].alpha = 0; + } + } + // Set the UI layout for the active configuration. If unsuccessful, exit the function early. if (!this.setLayout(activeConfig)) return; @@ -258,7 +288,6 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler { this.inputsIcons[elm].setFrame(icon); this.inputsIcons[elm].alpha = 1; } else { - if (!this.inputsIcons[elm]) debugger; this.inputsIcons[elm].alpha = 0; } } diff --git a/src/ui/settings/settings-ui-handler.ts b/src/ui/settings/settings-ui-handler.ts index fdc94724ee4..4021b68fa08 100644 --- a/src/ui/settings/settings-ui-handler.ts +++ b/src/ui/settings/settings-ui-handler.ts @@ -1,11 +1,12 @@ import BattleScene from "../../battle-scene"; -import { Setting, reloadSettings, settingDefaults, settingOptions } from "../../system/settings"; +import {Setting, reloadSettings, settingDefaults, settingOptions} from "../../system/settings"; import { hasTouchscreen, isMobile } from "../../touch-controls"; import { TextStyle, addTextObject } from "../text"; import { Mode } from "../ui"; import UiHandler from "../ui-handler"; import { addWindow } from "../ui-theme"; import {Button} from "../../enums/buttons"; +import {InputsIcons} from "#app/ui/settings/abstract-settings-ui-handler"; export default class SettingsUiHandler extends UiHandler { private settingsContainer: Phaser.GameObjects.Container; @@ -20,6 +21,8 @@ export default class SettingsUiHandler extends UiHandler { private settingLabels: Phaser.GameObjects.Text[]; private optionValueLabels: Phaser.GameObjects.Text[][]; + protected navigationIcons: InputsIcons; + private cursorObj: Phaser.GameObjects.NineSlice; private reloadRequired: boolean; @@ -39,20 +42,34 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.scene.game.canvas.width / 6, this.scene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains); + this.navigationIcons = {}; + const headerBg = addWindow(this.scene, 0, 0, (this.scene.game.canvas.width / 6) - 2, 24); headerBg.setOrigin(0, 0); + const iconPreviousTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconPreviousTab.setScale(.1); + iconPreviousTab.setOrigin(0, -0.1); + iconPreviousTab.setPositionRelative(headerBg, 8, 4); + this.navigationIcons['BUTTON_CYCLE_FORM'] = iconPreviousTab; + + const iconNextTab = this.scene.add.sprite(0, 0, 'keyboard'); + iconNextTab.setScale(.1); + iconNextTab.setOrigin(0, -0.1); + iconNextTab.setPositionRelative(headerBg, headerBg.width - 20, 4); + this.navigationIcons['BUTTON_CYCLE_SHINY'] = iconNextTab; + const headerText = addTextObject(this.scene, 0, 0, 'General', TextStyle.SETTINGS_SELECTED); headerText.setOrigin(0, 0); - headerText.setPositionRelative(headerBg, 8, 4); + headerText.setPositionRelative(headerBg, 8 + iconPreviousTab.width/6 - 4, 4); const gamepadText = addTextObject(this.scene, 0, 0, 'Gamepad', TextStyle.SETTINGS_LABEL); gamepadText.setOrigin(0, 0); - gamepadText.setPositionRelative(headerBg, 50, 4); + gamepadText.setPositionRelative(headerBg, 50 + iconPreviousTab.width/6 - 4, 4); const keyboardText = addTextObject(this.scene, 0, 0, 'Keyboard', TextStyle.SETTINGS_LABEL); keyboardText.setOrigin(0, 0); - keyboardText.setPositionRelative(headerBg, 97, 4); + keyboardText.setPositionRelative(headerBg, 97 + iconPreviousTab.width/6 - 4, 4); this.optionsBg = addWindow(this.scene, 0, headerBg.height, (this.scene.game.canvas.width / 6) - 2, (this.scene.game.canvas.height / 6) - headerBg.height - 2); this.optionsBg.setOrigin(0, 0); @@ -104,6 +121,8 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer.add(keyboardText); this.settingsContainer.add(this.optionsBg); this.settingsContainer.add(this.optionsContainer); + this.settingsContainer.add(iconNextTab) + this.settingsContainer.add(iconPreviousTab) ui.add(this.settingsContainer); @@ -113,9 +132,23 @@ export default class SettingsUiHandler extends UiHandler { this.settingsContainer.setVisible(false); } + updateBindings(): void { + for (const settingName of Object.keys(this.navigationIcons)) { + const icon = this.scene.inputController?.getIconForLatestInputRecorded(settingName); + if (icon) { + const type = this.scene.inputController?.getLastSourceType(); + this.navigationIcons[settingName].setTexture(type); + this.navigationIcons[settingName].setFrame(icon); + this.navigationIcons[settingName].alpha = 1; + } else + this.navigationIcons[settingName].alpha = 0; + } + } + show(args: any[]): boolean { super.show(args); - + this.updateBindings(); + const settings: object = localStorage.hasOwnProperty('settings') ? JSON.parse(localStorage.getItem('settings')) : {}; Object.keys(settingDefaults).forEach((setting, s) => this.setOptionCursor(s, settings.hasOwnProperty(setting) ? settings[setting] : settingDefaults[setting]));