better-xcloud/src/modules/ui/dialog/profile-manger/mkb-mapping-manager-dialog.ts
2024-12-23 05:55:11 +07:00

261 lines
9.3 KiB
TypeScript
Executable File

import type { MkbPresetData, MkbPresetRecord } from "@/types/presets";
import { BaseProfileManagerDialog } from "./base-profile-manager-dialog";
import { t } from "@/utils/translation";
import { MkbMappingPresetsTable } from "@/utils/local-db/mkb-mapping-presets-table";
import { GamepadKey, GamepadKeyName } from "@/enums/gamepad";
import { CE, createSettingRow } from "@/utils/html";
import { MouseMapTo, MkbPresetKey, type KeyCode } from "@/enums/mkb";
import { BxKeyBindingButton, BxKeyBindingButtonFlag } from "@/web-components/bx-key-binding-button";
import { StreamSettings } from "@/utils/stream-settings";
import { BxNumberStepper } from "@/web-components/bx-number-stepper";
import { deepClone } from "@/utils/global";
import { BxSelectElement } from "@/web-components/bx-select";
type MkbButtonDataset = {
keySlot: number,
buttonIndex: GamepadKey,
}
export class MkbMappingManagerDialog extends BaseProfileManagerDialog<MkbPresetRecord> {
private static instance: MkbMappingManagerDialog;
public static getInstance = () => MkbMappingManagerDialog.instance ?? (MkbMappingManagerDialog.instance = new MkbMappingManagerDialog(t('virtual-controller')));
declare protected $content: HTMLElement;
private readonly KEYS_PER_BUTTON = 2;
private readonly BUTTONS_ORDER = [
GamepadKey.HOME,
GamepadKey.UP, GamepadKey.DOWN, GamepadKey.LEFT, GamepadKey.RIGHT,
GamepadKey.A, GamepadKey.B, GamepadKey.X, GamepadKey.Y,
GamepadKey.LB, GamepadKey.RB, GamepadKey.LT, GamepadKey.RT,
GamepadKey.SELECT, GamepadKey.START,
GamepadKey.L3, GamepadKey.LS_UP, GamepadKey.LS_DOWN, GamepadKey.LS_LEFT, GamepadKey.LS_RIGHT,
GamepadKey.R3, GamepadKey.RS_UP, GamepadKey.RS_DOWN, GamepadKey.RS_LEFT, GamepadKey.RS_RIGHT,
];
protected readonly BLANK_PRESET_DATA: MkbPresetData = {
mapping: {},
mouse: {
[MkbPresetKey.MOUSE_MAP_TO]: MouseMapTo.RS,
[MkbPresetKey.MOUSE_SENSITIVITY_X]: 100,
[MkbPresetKey.MOUSE_SENSITIVITY_Y]: 100,
[MkbPresetKey.MOUSE_DEADZONE_COUNTERWEIGHT]: 20,
},
};
private readonly allKeyElements: BxKeyBindingButton[] = [];
private $mouseMapTo!: BxSelectElement;
private $mouseSensitivityX!: BxNumberStepper;
private $mouseSensitivityY!: BxNumberStepper;
private $mouseDeadzone!: BxNumberStepper;
private $unbindNote!: HTMLElement;
constructor(title: string) {
super(title, MkbMappingPresetsTable.getInstance());
this.render();
}
private onBindingKey = (e: MouseEvent) => {
const $btn = e.target as HTMLButtonElement;
if ($btn.disabled) {
return;
}
if (e.button !== 0) {
return;
}
};
private parseDataset($btn: BxKeyBindingButton): MkbButtonDataset {
const dataset = $btn.dataset;
return {
keySlot: parseInt(dataset.keySlot!),
buttonIndex: parseInt(dataset.buttonIndex!),
};
}
private onKeyChanged = (e: Event) => {
const $current = e.target as BxKeyBindingButton;
const keyInfo = $current.keyInfo;
// Unbind duplicated keys
if (keyInfo) {
for (const $elm of this.allKeyElements) {
if ($elm !== $current && $elm.keyInfo?.code === keyInfo.code) {
// Unbind manually
$elm.unbindKey(true);
}
}
}
// Save preset
this.savePreset();
}
private render() {
const $rows = CE('div', false,
this.$unbindNote = CE('i', { class: 'bx-mkb-note' }, t('right-click-to-unbind')),
);
for (const buttonIndex of this.BUTTONS_ORDER) {
const [buttonName, buttonPrompt] = GamepadKeyName[buttonIndex];
let $elm;
const $fragment = document.createDocumentFragment();
for (let i = 0; i < this.KEYS_PER_BUTTON; i++) {
$elm = BxKeyBindingButton.create({
title: buttonPrompt,
isPrompt: true,
allowedFlags: [BxKeyBindingButtonFlag.KEYBOARD_PRESS, BxKeyBindingButtonFlag.MOUSE_CLICK, BxKeyBindingButtonFlag.MOUSE_WHEEL],
onChanged: this.onKeyChanged,
});
$elm.dataset.buttonIndex = buttonIndex.toString();
$elm.dataset.keySlot = i.toString();
$elm.addEventListener('mouseup', this.onBindingKey);
$fragment.appendChild($elm);
this.allKeyElements.push($elm);
}
const $keyRow = CE('div', {
class: 'bx-mkb-key-row',
_nearby: { orientation: 'horizontal' },
},
CE('label', { title: buttonName }, buttonPrompt),
$fragment,
);
$rows.appendChild($keyRow);
}
const savePreset = () => this.savePreset();
const $extraSettings = CE('div', false,
createSettingRow(
t('map-mouse-to'),
this.$mouseMapTo = BxSelectElement.create(CE('select', { _on: { input: savePreset } },
CE('option', { value: MouseMapTo.RS }, t('right-stick')),
CE('option', { value: MouseMapTo.LS }, t('left-stick')),
CE('option', { value: MouseMapTo.OFF }, t('off')),
)),
),
createSettingRow(
t('horizontal-sensitivity'),
this.$mouseSensitivityX = BxNumberStepper.create('hor_sensitivity', 0, 1, 300, {
suffix: '%',
exactTicks: 50,
}, savePreset),
),
createSettingRow(
t('vertical-sensitivity'),
this.$mouseSensitivityY = BxNumberStepper.create('ver_sensitivity', 0, 1, 300, {
suffix: '%',
exactTicks: 50,
}, savePreset),
),
createSettingRow(
t('deadzone-counterweight'),
this.$mouseDeadzone = BxNumberStepper.create('deadzone_counterweight', 0, 1, 50, {
suffix: '%',
exactTicks: 10,
}, savePreset),
),
);
this.$content = CE('div', false,
$rows,
$extraSettings,
);
}
protected switchPreset(id: number): void {
const preset = this.allPresets.data[id];
if (!preset) {
this.currentPresetId = 0;
return;
}
const presetData = preset.data;
this.currentPresetId = id;
const isDefaultPreset = id <= 0;
this.updateButtonStates();
// Toggle unbind note
this.$unbindNote.classList.toggle('bx-gone', isDefaultPreset);
// Update buttons
for (const $elm of this.allKeyElements) {
const { buttonIndex, keySlot } = this.parseDataset($elm);
const buttonKeys = presetData.mapping[buttonIndex];
if (buttonKeys && buttonKeys[keySlot]) {
$elm.bindKey({
code: buttonKeys[keySlot],
}, true)
} else {
$elm.unbindKey(true);
}
$elm.disabled = isDefaultPreset;
}
// Update mouse settings
const mouse = presetData.mouse;
this.$mouseMapTo.value = mouse.mapTo.toString();
this.$mouseSensitivityX.value = mouse.sensitivityX.toString();
this.$mouseSensitivityY.value = mouse.sensitivityY.toString();
this.$mouseDeadzone.value = mouse.deadzoneCounterweight.toString();
this.$mouseMapTo.disabled = isDefaultPreset;
this.$mouseSensitivityX.dataset.disabled = isDefaultPreset.toString();
this.$mouseSensitivityY.dataset.disabled = isDefaultPreset.toString();
this.$mouseDeadzone.dataset.disabled = isDefaultPreset.toString();
}
private savePreset() {
const presetData = deepClone(this.BLANK_PRESET_DATA) as MkbPresetData;
// Get mapping
for (const $elm of this.allKeyElements) {
const { buttonIndex, keySlot } = this.parseDataset($elm);
const mapping = presetData.mapping;
if (!mapping[buttonIndex]) {
mapping[buttonIndex] = [];
}
if (!$elm.keyInfo) {
// Remove empty key from mapping
delete mapping[buttonIndex][keySlot];
} else {
mapping[buttonIndex][keySlot] = $elm.keyInfo.code as KeyCode;
}
}
// Get mouse settings
const mouse = presetData.mouse;
mouse.mapTo = parseInt(this.$mouseMapTo.value) as MouseMapTo;
mouse.sensitivityX = parseInt(this.$mouseSensitivityX.value);
mouse.sensitivityY = parseInt(this.$mouseSensitivityY.value);
mouse.deadzoneCounterweight = parseInt(this.$mouseDeadzone.value);
const oldPreset = this.allPresets.data[this.currentPresetId!];
const newPreset = {
id: this.currentPresetId!,
name: oldPreset.name,
data: presetData,
};
this.presetsDb.updatePreset(newPreset);
this.allPresets.data[this.currentPresetId!] = newPreset;
}
onBeforeUnmount() {
StreamSettings.refreshMkbSettings();
super.onBeforeUnmount();
}
}