added navigation icon in the setting menu

This commit is contained in:
Greenlamp 2024-05-16 12:30:13 +02:00
parent 72e75ffca8
commit 8491f0b1ad
10 changed files with 161 additions and 21 deletions

View File

@ -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,

View File

@ -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.
*

View File

@ -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.

View File

@ -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,

View File

@ -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();

View File

@ -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]);

View File

@ -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<string, InterfaceConfig> = 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")
});
});

View File

@ -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;

View File

@ -41,6 +41,7 @@ export default abstract class AbstractSettingsUiUiHandler extends UiHandler {
protected layout: Map<string, LayoutConfig> = new Map<string, LayoutConfig>();
// 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<String>;
@ -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;
}
}

View File

@ -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]));