feat: 虚拟键盘显示与操作

This commit is contained in:
unanmed 2024-01-18 16:20:50 +08:00
parent e77ca3e376
commit 899d1fa68e
5 changed files with 162 additions and 13 deletions

View File

@ -5,13 +5,13 @@ export interface EmitableEvent {
[event: string]: (...params: any) => any; [event: string]: (...params: any) => any;
} }
interface Listener<T extends (...params: any) => any> { export interface Listener<T extends (...params: any) => any> {
fn: T; fn: T;
once?: boolean; once?: boolean;
immediate?: boolean; immediate?: boolean;
} }
interface ListenerOptions { export interface ListenerOptions {
once: boolean; once: boolean;
immediate: boolean; immediate: boolean;
} }
@ -22,12 +22,12 @@ type EmitFn<F extends (...params: any) => any> = (
) => any; ) => any;
export class EventEmitter<T extends EmitableEvent = {}> { export class EventEmitter<T extends EmitableEvent = {}> {
private events: { protected events: {
[P in keyof T]?: Listener<T[P]>[]; [P in keyof T]?: Listener<T[P]>[];
} = {}; } = {};
private emitted: (keyof T)[] = []; private emitted: (keyof T)[] = [];
private emitter: { protected emitter: {
[P in keyof T]?: EmitFn<T[P]>; [P in keyof T]?: EmitFn<T[P]>;
} = {}; } = {};

View File

@ -1,9 +1,14 @@
import { EmitableEvent, EventEmitter } from '@/core/common/eventEmitter'; import {
EmitableEvent,
EventEmitter,
Listener
} from '@/core/common/eventEmitter';
import { KeyCode } from '@/plugin/keyCodes'; import { KeyCode } from '@/plugin/keyCodes';
import { gameKey } from '../init/hotkey'; import { gameKey } from '../init/hotkey';
import { unwarpBinary } from './hotkey'; import { unwarpBinary } from './hotkey';
import { deleteWith } from '@/plugin/utils'; import { deleteWith } from '@/plugin/utils';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { shallowReactive } from 'vue';
export interface KeyboardEmits { export interface KeyboardEmits {
key: KeyCode; key: KeyCode;
@ -17,16 +22,30 @@ interface KeyboardItem {
y: number; y: number;
width: number; width: number;
height: number; height: number;
active?: boolean;
} }
interface AssistManager { interface AssistManager {
symbol: symbol;
end(): void; end(): void;
} }
interface VirtualKeyEmit {
preventDefault(): void;
}
type VirtualKeyEmitFn = (
item: KeyboardItem,
assist: number,
index: number,
ev: VirtualKeyEmit
) => void;
interface VirtualKeyboardEvent extends EmitableEvent { interface VirtualKeyboardEvent extends EmitableEvent {
add: (item: KeyboardItem) => void; add: (item: KeyboardItem) => void;
remove: (item: KeyboardItem) => void; remove: (item: KeyboardItem) => void;
emit: (item: KeyboardItem, assist: number, index: number) => void; extend: (extended: Keyboard) => void;
emit: VirtualKeyEmitFn;
} }
/** /**
@ -38,6 +57,11 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
id: string; id: string;
keys: KeyboardItem[] = []; keys: KeyboardItem[] = [];
assist: number = 0; assist: number = 0;
fontSize: number = 18;
scope: symbol = Symbol();
private scopeStack: symbol[] = [];
private onEmitKey: Record<symbol, Listener<VirtualKeyEmitFn>[]> = {};
constructor(id: string) { constructor(id: string) {
super(); super();
@ -50,8 +74,10 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
* @param item * @param item
*/ */
add(item: KeyboardItem) { add(item: KeyboardItem) {
this.keys.push(item); const i = shallowReactive(item);
this.emit('add', item); this.keys.push(i);
this.emit('add', i);
return this;
} }
/** /**
@ -61,6 +87,7 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
remove(item: KeyboardItem) { remove(item: KeyboardItem) {
deleteWith(this.keys, item); deleteWith(this.keys, item);
this.emit('remove', item); this.emit('remove', item);
return this;
} }
/** /**
@ -71,14 +98,49 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
withAssist(assist: number): AssistManager { withAssist(assist: number): AssistManager {
const thisAssist = this.assist; const thisAssist = this.assist;
this.assist = assist; this.assist = assist;
const symbol = this.createScope();
return { return {
symbol,
end: () => { end: () => {
this.assist = thisAssist; this.assist = thisAssist;
} }
}; };
} }
/**
*
* @returns
*/
createScope() {
const symbol = Symbol();
this.scope = symbol;
this.scopeStack.push(symbol);
const ev: Listener<VirtualKeyEmitFn>[] = [];
this.onEmitKey[symbol] = ev;
// @ts-ignore
this.events = ev;
return symbol;
}
/**
*
*/
disposeScope() {
if (this.scopeStack.length === 0) {
throw new Error(
`Cannot dispose virtual key scope since there's no scope to be disposed.`
);
}
const now = this.scopeStack.pop()!;
delete this.onEmitKey[now];
const symbol = this.scopeStack.at(-1);
if (!symbol) return;
this.scope = symbol;
// @ts-ignore
this.events = this.onEmitKey[symbol];
}
/** /**
* *
* @param keyboard * @param keyboard
@ -93,6 +155,7 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
}); });
this.keys.push(...toClone); this.keys.push(...toClone);
this.emit('extend', keyboard);
} }
/** /**
@ -100,9 +163,14 @@ export class Keyboard extends EventEmitter<VirtualKeyboardEvent> {
* @param key * @param key
*/ */
emitKey(key: KeyboardItem, index: number) { emitKey(key: KeyboardItem, index: number) {
const ev = generateKeyboardEvent(key.key, this.assist); let prevent = false;
gameKey.emitKey(key.key, this.assist, 'up', ev); const preventDefault = () => (prevent = true);
this.emit('emit', key, this.assist, index); this.emit('emit', key, this.assist, index, { preventDefault });
if (!prevent) {
const ev = generateKeyboardEvent(key.key, this.assist);
gameKey.emitKey(key.key, this.assist, 'up', ev);
}
} }
static get(id: string) { static get(id: string) {

View File

@ -1,2 +1,3 @@
import './fixed'; import './fixed';
import './hotkey'; import './hotkey';
import './keyboard';

View File

@ -0,0 +1,27 @@
import { KeyCode } from '@/plugin/keyCodes';
import { Keyboard } from '../custom/keyboard';
const qweKey = new Keyboard('qwe');
qweKey.fontSize = 20;
qweKey
.add({
key: KeyCode.KeyQ,
x: 0,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyW,
x: 50,
y: 0,
width: 45,
height: 45
})
.add({
key: KeyCode.KeyA,
x: 10,
y: 50,
width: 45,
height: 45
});

View File

@ -1,16 +1,35 @@
<template> <template>
<div class="keyboard-container"></div> <div class="keyboard-container">
<div
v-for="(key, i) of keyboard.keys"
class="keyboard-item"
@click="keyboard.emitKey(key, i)"
:active="!!key.active"
:style="{
left: `${key.x}px`,
top: `${key.y}px`,
width: `${key.width}px`,
height: `${key.height}px`
}"
>
<span class="keyboard-key button-text">{{
key.text ?? KeyCodeUtils.toString(key.key)
}}</span>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Keyboard } from '@/core/main/custom/keyboard'; import { Keyboard } from '@/core/main/custom/keyboard';
import { KeyboardEmits } from '@/core/main/custom/keyboard'; import { KeyboardEmits } from '@/core/main/custom/keyboard';
import { ref } from 'vue'; import { KeyCodeUtils } from '@/plugin/keyCodes';
const props = defineProps<{ const props = defineProps<{
keyboard: Keyboard; keyboard: Keyboard;
}>(); }>();
const fontSize = props.keyboard.fontSize;
const [width, height] = (() => { const [width, height] = (() => {
const key = props.keyboard; const key = props.keyboard;
const mw = Math.max(...key.keys.map(v => v.x)); const mw = Math.max(...key.keys.map(v => v.x));
@ -28,5 +47,39 @@ const emits = defineEmits<{
.keyboard-container { .keyboard-container {
width: v-bind(width); width: v-bind(width);
height: v-bind(height); height: v-bind(height);
display: block;
font-size: v-bind(fontSize);
}
.keyboard-item {
position: absolute;
background-color: #222;
border: 1.5px solid #ddd8;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.1s linear;
cursor: pointer;
}
.keyboard-item:hover,
.keyboard-item[active='true'] {
background-color: #555;
}
.keyboard-key[active='true'] {
color: gold;
}
.keyboard-key {
height: 100%;
min-width: 50px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
text-overflow: clip;
text-wrap: nowrap;
overflow: hidden;
} }
</style> </style>