Skip to content

Commit 39c185d

Browse files
authored
feat(input): add mouse input event in input library (#332)
* feat(input): add mouse input event in input library * fix(input): delete useless tests * feat(input): add mouse event handler in InputLibrary and convert IInputLibrary to IRunnerLibrary * fix(input): base input library run function to abstract function
1 parent 760a249 commit 39c185d

8 files changed

Lines changed: 460 additions & 16 deletions

File tree

.idea/[NanoForge] Engine.iml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import { type Context } from "../../../context";
12
import { type IInputLibrary } from "../interfaces";
23
import { Library } from "../library";
34

4-
export abstract class BaseInputLibrary extends Library implements IInputLibrary {}
5+
export abstract class BaseInputLibrary extends Library implements IInputLibrary {
6+
public abstract __run(_context: Context): Promise<void>;
7+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
import { type IExposedLibrary } from "../bases/exposed.library.type";
2+
import { type IRunnerLibrary } from "../bases/runner.library.type";
23

3-
export interface IInputLibrary extends IExposedLibrary {}
4+
export interface IInputLibrary extends IExposedLibrary, IRunnerLibrary {}
Lines changed: 155 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,41 @@
11
import { InputEnum } from "./input.enum";
2+
import {
3+
BUTTONS_MASKS,
4+
type DragState,
5+
MOUSE_BUTTON_MAP,
6+
type MouseState,
7+
type WheelState,
8+
} from "./mouse.types";
29

310
export class InputHandler {
411
public inputs: Record<string, boolean> = {};
12+
public mouse: MouseState = {
13+
x: 0,
14+
y: 0,
15+
prevX: 0,
16+
prevY: 0,
17+
deltaX: 0,
18+
deltaY: 0,
19+
focus: false,
20+
};
21+
public wheel: WheelState = {
22+
deltaX: 0,
23+
deltaY: 0,
24+
deltaZ: 0,
25+
};
26+
public drag: DragState = {
27+
active: false,
28+
startX: 0,
29+
startY: 0,
30+
x: 0,
31+
y: 0,
32+
deltaX: 0,
33+
deltaY: 0,
34+
};
535

636
constructor() {
37+
this.resetInputs();
38+
739
window.addEventListener("keydown", (e: KeyboardEvent) => {
840
this.inputs[e.code] = true;
941
});
@@ -12,12 +44,134 @@ export class InputHandler {
1244
this.inputs[e.code] = false;
1345
});
1446

47+
window.addEventListener("mousedown", (e: MouseEvent) => {
48+
const button = MOUSE_BUTTON_MAP[e.button];
49+
if (button === undefined) return;
50+
51+
this.inputs[button] = true;
52+
this.updatePointer(e);
53+
54+
this.drag.active = true;
55+
this.drag.button = button;
56+
this.drag.startX = e.clientX;
57+
this.drag.startY = e.clientY;
58+
this.drag.x = e.clientX;
59+
this.drag.y = e.clientY;
60+
this.drag.deltaX = 0;
61+
this.drag.deltaY = 0;
62+
});
63+
64+
window.addEventListener("mouseup", (e: MouseEvent) => {
65+
const button = MOUSE_BUTTON_MAP[e.button];
66+
if (button !== undefined) this.inputs[button] = false;
67+
68+
this.updatePointer(e);
69+
70+
if (this.drag.button === button) {
71+
this.drag.active = false;
72+
this.drag.button = undefined;
73+
this.drag.x = 0;
74+
this.drag.y = 0;
75+
this.drag.deltaX = 0;
76+
this.drag.deltaY = 0;
77+
}
78+
});
79+
80+
window.addEventListener("mousemove", (e: MouseEvent) => {
81+
this.updatePointer(e);
82+
this.updateInputsMouseButtons(e.buttons);
83+
84+
if (this.drag.active) {
85+
this.drag.x = e.clientX;
86+
this.drag.y = e.clientY;
87+
this.drag.deltaX = e.clientX - this.drag.startX;
88+
this.drag.deltaY = e.clientY - this.drag.startY;
89+
}
90+
});
91+
92+
window.addEventListener("wheel", (e: WheelEvent) => {
93+
this.wheel.deltaX += e.deltaX;
94+
this.wheel.deltaY += e.deltaY;
95+
this.wheel.deltaZ += e.deltaZ;
96+
});
97+
98+
window.addEventListener("mouseenter", () => {
99+
this.mouse.focus = true;
100+
});
101+
102+
window.addEventListener("mouseleave", () => {
103+
this.mouse.focus = false;
104+
});
105+
106+
window.addEventListener("blur", () => {
107+
this.resetInputs();
108+
});
109+
110+
document.addEventListener("visibilitychange", () => {
111+
if (document.hidden) this.resetInputs();
112+
});
113+
15114
for (const key in InputEnum) {
16115
this.inputs[key] = false;
17116
}
18117
}
19118

20-
getKeyStatus(key: InputEnum): boolean {
119+
public getKeyStatus(key: InputEnum): boolean {
21120
return this.inputs[key] || false;
22121
}
122+
123+
public getMousePosition() {
124+
return { x: this.mouse.x, y: this.mouse.y };
125+
}
126+
127+
public isDragging(button?: InputEnum): boolean {
128+
if (!button) return this.drag.active;
129+
return this.drag.active && this.drag.button === button;
130+
}
131+
132+
public resetPerFrame(): void {
133+
this.mouse.deltaX = 0;
134+
this.mouse.deltaY = 0;
135+
this.wheel.deltaX = 0;
136+
this.wheel.deltaY = 0;
137+
this.wheel.deltaZ = 0;
138+
}
139+
140+
private updatePointer(e: MouseEvent): void {
141+
this.mouse.prevX = this.mouse.x;
142+
this.mouse.prevY = this.mouse.y;
143+
this.mouse.x = e.clientX;
144+
this.mouse.y = e.clientY;
145+
this.mouse.deltaX = this.mouse.x - this.mouse.prevX;
146+
this.mouse.deltaY = this.mouse.y - this.mouse.prevY;
147+
}
148+
149+
private updateInputsMouseButtons(buttons: number): void {
150+
for (const [mask, input] of BUTTONS_MASKS) {
151+
this.inputs[input] = (buttons & mask) !== 0;
152+
}
153+
}
154+
155+
private resetInputs(): void {
156+
for (const key of Object.values(InputEnum)) {
157+
this.inputs[key] = false;
158+
}
159+
160+
this.drag.active = false;
161+
this.drag.button = undefined;
162+
this.drag.startX = 0;
163+
this.drag.startY = 0;
164+
this.drag.x = 0;
165+
this.drag.y = 0;
166+
this.drag.deltaX = 0;
167+
this.drag.deltaY = 0;
168+
169+
this.wheel.deltaX = 0;
170+
this.wheel.deltaY = 0;
171+
this.wheel.deltaZ = 0;
172+
173+
this.mouse.deltaX = 0;
174+
this.mouse.deltaY = 0;
175+
this.mouse.focus = false;
176+
}
23177
}

packages/input/src/input.enum.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,9 @@ export enum InputEnum {
141141
BrowserBack = "BrowserBack",
142142
LaunchApp1 = "LaunchApp1",
143143
LaunchMail = "LaunchMail",
144+
MouseLeft = "MouseLeft",
145+
MouseMiddle = "MouseMiddle",
146+
MouseRight = "MouseRight",
147+
Back = "BackButton",
148+
Forward = "Forward",
144149
}

packages/input/src/input.library.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import { BaseInputLibrary } from "@nanoforge-dev/common";
1+
import { BaseInputLibrary, GRAPHICS_LIBRARY } from "@nanoforge-dev/common";
22

33
import { InputHandler } from "./input-handler";
44
import { type InputEnum } from "./input.enum";
5+
import { type DragState, type MouseState, type WheelState } from "./mouse.types";
56

67
export class InputLibrary extends BaseInputLibrary {
78
private _inputHandler?: InputHandler;
89

10+
constructor() {
11+
super({ runAfter: [GRAPHICS_LIBRARY] });
12+
}
13+
914
get __name(): string {
1015
return "InputLibrary";
1116
}
@@ -14,6 +19,11 @@ export class InputLibrary extends BaseInputLibrary {
1419
this._inputHandler = new InputHandler();
1520
}
1621

22+
public override async __run() {
23+
if (!this._inputHandler) this.throwNotInitializedError();
24+
this._inputHandler.resetPerFrame();
25+
}
26+
1727
public isKeyPressed(key: InputEnum): boolean | undefined {
1828
if (!this._inputHandler) this.throwNotInitializedError();
1929
return this._inputHandler.getKeyStatus(key);
@@ -28,4 +38,29 @@ export class InputLibrary extends BaseInputLibrary {
2838
}
2939
return res;
3040
}
41+
42+
public getMousePosition(): { x: number; y: number } {
43+
if (!this._inputHandler) this.throwNotInitializedError();
44+
return this._inputHandler.getMousePosition();
45+
}
46+
47+
public getMouseState(): MouseState {
48+
if (!this._inputHandler) this.throwNotInitializedError();
49+
return this._inputHandler.mouse;
50+
}
51+
52+
public isDragging(button?: InputEnum): boolean {
53+
if (!this._inputHandler) this.throwNotInitializedError();
54+
return this._inputHandler.isDragging(button);
55+
}
56+
57+
public getDragState(): DragState {
58+
if (!this._inputHandler) this.throwNotInitializedError();
59+
return this._inputHandler.drag;
60+
}
61+
62+
public getWheelState(): WheelState {
63+
if (!this._inputHandler) this.throwNotInitializedError();
64+
return this._inputHandler.wheel;
65+
}
3166
}

packages/input/src/mouse.types.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { InputEnum } from "./input.enum";
2+
3+
export type MouseState = {
4+
x: number;
5+
y: number;
6+
prevX: number;
7+
prevY: number;
8+
deltaX: number;
9+
deltaY: number;
10+
focus: boolean;
11+
};
12+
13+
export type DragState = {
14+
active: boolean;
15+
button?: InputEnum | undefined;
16+
startX: number;
17+
startY: number;
18+
x: number;
19+
y: number;
20+
deltaX: number;
21+
deltaY: number;
22+
};
23+
24+
export type WheelState = { deltaX: number; deltaY: number; deltaZ: number };
25+
26+
export const MOUSE_BUTTON_MAP: Partial<Record<number, InputEnum>> = {
27+
0: InputEnum.MouseLeft,
28+
1: InputEnum.MouseMiddle,
29+
2: InputEnum.MouseRight,
30+
3: InputEnum.Back,
31+
4: InputEnum.Forward,
32+
};
33+
34+
export const BUTTONS_MASKS = new Map<number, InputEnum>([
35+
[1, InputEnum.MouseLeft],
36+
[2, InputEnum.MouseRight],
37+
[4, InputEnum.MouseMiddle],
38+
[8, InputEnum.Back],
39+
[16, InputEnum.Forward],
40+
]);

0 commit comments

Comments
 (0)