• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

graphty-org / graphty-element / 16281745178

15 Jul 2025 01:24AM UTC coverage: 70.353% (-2.2%) from 72.536%
16281745178

push

github

apowers313
fix: build and optional argument fixes

735 of 934 branches covered (78.69%)

Branch coverage included in aggregate %.

7 of 8 new or added lines in 2 files covered. (87.5%)

878 existing lines in 24 files now uncovered.

4742 of 6851 relevant lines covered (69.22%)

311.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

46.12
/src/cameras/TwoDInputController.ts
1
// TwoDInputController.ts
2
import {type Observer, PointerEventTypes, type PointerInfo, type PointerInfoPre} from "@babylonjs/core";
2✔
3
import Hammer from "hammerjs";
2!
4

5
import {TwoDCameraController, TwoDCameraControlsConfigType} from "./TwoDCameraController";
6

7
interface GestureSession {
8
    panX: number;
9
    panY: number;
10
    panStartX: number;
11
    panStartY: number;
12
    ortho: {
13
        left: number;
14
        right: number;
15
        top: number;
16
        bottom: number;
17
    };
18
    scale: number;
19
    rotation: number;
20
    startRotDeg: number;
21
}
22

23
export class InputController {
2✔
24
    private keyState: Record<string, boolean> = {};
2✔
25
    private gestureSession: GestureSession | null = null;
2✔
26
    private hammer: HammerManager | null = null;
2✔
27
    private enabled = false;
2✔
28

29
    // Store observable subscriptions for cleanup
30
    private pointerObserverHandle: Observer<PointerInfo> | null = null;
2✔
31
    private prePointerObserverHandle: Observer<PointerInfoPre> | null = null;
2✔
32

33
    private pointerDownHandler = (): void => {
2✔
34
        this.cam.canvas.focus();
×
35
    };
×
36

37
    private keyDownHandler = (e: KeyboardEvent): void => {
2✔
38
        this.keyState[e.key] = true;
×
39
    };
×
40

41
    private keyUpHandler = (e: KeyboardEvent): void => {
2✔
UNCOV
42
        this.keyState[e.key] = false;
×
UNCOV
43
    };
×
44

45
    constructor(
2✔
46
        private cam: TwoDCameraController,
77✔
47
        private canvas: HTMLCanvasElement,
77✔
48
        private config: TwoDCameraControlsConfigType,
77✔
49
    ) {
77✔
50
        this.canvas.setAttribute("tabindex", "0"); // Make focusable
77✔
51
        this.setupMouse();
77✔
52
        this.setupTouch();
77✔
53
    }
77✔
54

55
    private setupMouse(): void {
2✔
56
        let isPanning = false;
77✔
57
        let lastX = 0;
77✔
58
        let lastY = 0;
77✔
59

60
        this.pointerObserverHandle = this.cam.scene.onPointerObservable.add((pi) => {
77✔
61
            const e = pi.event as PointerEvent;
×
62

63
            switch (pi.type) {
×
64
                case PointerEventTypes.POINTERDOWN:
×
65
                    isPanning = true;
×
UNCOV
66
                    lastX = e.clientX;
×
67
                    lastY = e.clientY;
×
68
                    this.pointerDownHandler();
×
69
                    break;
×
70

71
                case PointerEventTypes.POINTERUP:
×
72
                    isPanning = false;
×
73
                    break;
×
74

75
                case PointerEventTypes.POINTERMOVE:
×
76
                    if (isPanning && e.buttons === 1) {
×
77
                        const orthoRight = this.cam.camera.orthoRight ?? 1;
×
78
                        const orthoLeft = this.cam.camera.orthoLeft ?? 1;
×
UNCOV
79
                        const orthoTop = this.cam.camera.orthoTop ?? 1;
×
80
                        const orthoBottom = this.cam.camera.orthoBottom ?? 1;
×
81
                        const scaleX = (orthoRight - orthoLeft) / this.cam.engine.getRenderWidth();
×
UNCOV
82
                        const scaleY = (orthoTop - orthoBottom) / this.cam.engine.getRenderHeight();
×
83

UNCOV
84
                        const dx = e.clientX - lastX;
×
85
                        const dy = e.clientY - lastY;
×
86

87
                        this.cam.pan(-dx * scaleX * this.config.mousePanScale, dy * scaleY * this.config.mousePanScale);
×
88

89
                        lastX = e.clientX;
×
UNCOV
90
                        lastY = e.clientY;
×
91
                    }
×
92

93
                    break;
×
94

UNCOV
95
                default:
×
UNCOV
96
                    break;
×
97
            }
×
98
        });
77✔
99

100
        this.prePointerObserverHandle = this.cam.scene.onPrePointerObservable.add((pi) => {
77✔
101
            const e = pi.event as WheelEvent;
×
102
            if (pi.type === PointerEventTypes.POINTERWHEEL) {
×
UNCOV
103
                const delta = e.deltaY > 0 ? this.config.mouseWheelZoomSpeed : 1 / this.config.mouseWheelZoomSpeed;
×
UNCOV
104
                this.cam.zoom(delta);
×
UNCOV
105
                e.preventDefault();
×
UNCOV
106
            }
×
107
        }, PointerEventTypes.POINTERWHEEL);
77✔
108
    }
77✔
109

110
    private setupTouch(): void {
2✔
111
        this.hammer = new Hammer.Manager(this.canvas);
77✔
112
        const pan = new Hammer.Pan({threshold: 0, pointers: 0});
77✔
113
        const pinch = new Hammer.Pinch();
77✔
114
        const rotate = new Hammer.Rotate();
77✔
115

116
        this.hammer.add([pan, pinch, rotate]);
77✔
117
        this.hammer.get("pinch").recognizeWith(this.hammer.get("rotate"));
77✔
118
        this.hammer.get("pan").requireFailure(this.hammer.get("pinch"));
77✔
119

120
        this.hammer.on("panstart pinchstart rotatestart", (ev) => {
77✔
121
            this.gestureSession = {
×
122
                panX: ev.center.x,
×
123
                panY: ev.center.y,
×
124
                panStartX: this.cam.camera.position.x,
×
125
                panStartY: this.cam.camera.position.y,
×
126
                ortho: {
×
127
                    left: this.cam.camera.orthoLeft ?? 1,
×
128
                    right: this.cam.camera.orthoRight ?? 1,
×
129
                    top: this.cam.camera.orthoTop ?? 1,
×
130
                    bottom: this.cam.camera.orthoBottom ?? 1,
×
131
                },
×
UNCOV
132
                scale: ev.scale || 1,
×
UNCOV
133
                rotation: this.cam.parent.rotation.z,
×
UNCOV
134
                startRotDeg: ev.rotation || 0,
×
135
            };
×
136
        });
77✔
137

138
        this.hammer.on("panmove pinchmove rotatemove", (ev) => {
77✔
139
            if (!this.gestureSession) {
×
140
                return;
×
141
            }
×
142

143
            const orthoRight = this.cam.camera.orthoRight ?? 1;
×
144
            const orthoLeft = this.cam.camera.orthoLeft ?? 1;
×
UNCOV
145
            const orthoTop = this.cam.camera.orthoTop ?? 1;
×
146
            const orthoBottom = this.cam.camera.orthoBottom ?? 1;
×
147
            const scaleX = (orthoRight - orthoLeft) / this.cam.engine.getRenderWidth();
×
UNCOV
148
            const scaleY = (orthoTop - orthoBottom) / this.cam.engine.getRenderHeight();
×
149

150
            const dx = ev.center.x - this.gestureSession.panX;
×
UNCOV
151
            const dy = ev.center.y - this.gestureSession.panY;
×
152

153
            this.cam.camera.position.x = this.gestureSession.panStartX - (dx * scaleX * this.config.touchPanScale);
×
154
            this.cam.camera.position.y = this.gestureSession.panStartY + (dy * scaleY * this.config.touchPanScale);
×
155

156
            const pinch = (ev.scale || 1) / this.gestureSession.scale;
×
UNCOV
157
            this.cam.camera.orthoLeft = this.gestureSession.ortho.left / pinch;
×
158
            this.cam.camera.orthoRight = this.gestureSession.ortho.right / pinch;
×
159
            this.cam.camera.orthoTop = this.gestureSession.ortho.top / pinch;
×
UNCOV
160
            this.cam.camera.orthoBottom = this.gestureSession.ortho.bottom / pinch;
×
161

UNCOV
162
            const rotRad = (-(ev.rotation - this.gestureSession.startRotDeg) * Math.PI) / 180;
×
163
            this.cam.parent.rotation.z = this.gestureSession.rotation + rotRad;
×
164
        });
77✔
165

166
        this.hammer.on("panend pinchend rotateend", () => {
77✔
UNCOV
167
            this.gestureSession = null;
×
168
        });
77✔
169
    }
77✔
170

171
    public enable(): void {
2✔
172
        if (this.enabled) {
12!
UNCOV
173
            return;
×
UNCOV
174
        }
×
175

176
        this.enabled = true;
12✔
177

178
        this.canvas.addEventListener("keydown", this.keyDownHandler);
12✔
179
        this.canvas.addEventListener("keyup", this.keyUpHandler);
12✔
180
    }
12✔
181

182
    public disable(): void {
2✔
183
        if (!this.enabled) {
11!
UNCOV
184
            return;
×
185
        }
×
186

187
        this.enabled = false;
11✔
188

189
        this.canvas.removeEventListener("keydown", this.keyDownHandler);
11✔
190
        this.canvas.removeEventListener("keyup", this.keyUpHandler);
11✔
191

192
        // Remove scene observable subscriptions
193
        if (this.pointerObserverHandle !== null) {
11✔
194
            this.cam.scene.onPointerObservable.remove(this.pointerObserverHandle);
11✔
195
            this.pointerObserverHandle = null;
11✔
196
        }
11✔
197

198
        if (this.prePointerObserverHandle !== null) {
11✔
199
            this.cam.scene.onPrePointerObservable.remove(this.prePointerObserverHandle);
11✔
200
            this.prePointerObserverHandle = null;
11✔
201
        }
11✔
202

203
        // Destroy Hammer.js instance
204
        if (this.hammer) {
11✔
205
            this.hammer.destroy();
11✔
206
            this.hammer = null;
11✔
207
        }
11✔
208

209
        // Clear gesture session
210
        this.gestureSession = null;
11✔
211
    }
11✔
212

213
    public applyKeyboardInertia(): void {
2✔
214
        if (!this.enabled) {
210!
215
            return;
×
UNCOV
216
        }
×
217

218
        const v = this.cam.velocity;
210✔
219
        const c = this.config;
210✔
220

221
        if (this.keyState.w || this.keyState.ArrowUp) {
210!
222
            v.y += c.panAcceleration;
×
223
        }
×
224

225
        if (this.keyState.s || this.keyState.ArrowDown) {
210!
226
            v.y -= c.panAcceleration;
×
227
        }
×
228

229
        if (this.keyState.a || this.keyState.ArrowLeft) {
210!
UNCOV
230
            v.x -= c.panAcceleration;
×
UNCOV
231
        }
×
232

233
        if (this.keyState.d || this.keyState.ArrowRight) {
210!
UNCOV
234
            v.x += c.panAcceleration;
×
UNCOV
235
        }
×
236

237
        if (this.keyState["+"] || this.keyState["="]) {
210!
UNCOV
238
            v.zoom -= c.zoomFactorPerFrame;
×
UNCOV
239
        }
×
240

241
        if (this.keyState["-"] || this.keyState._) {
210!
UNCOV
242
            v.zoom += c.zoomFactorPerFrame;
×
UNCOV
243
        }
×
244

245
        if (this.keyState.q) {
210!
UNCOV
246
            v.rotate += c.rotateSpeedPerFrame;
×
UNCOV
247
        }
×
248

249
        if (this.keyState.e) {
210!
UNCOV
250
            v.rotate -= c.rotateSpeedPerFrame;
×
UNCOV
251
        }
×
252
    }
210✔
253

254
    public update(): void {
2✔
255
        this.applyKeyboardInertia();
210✔
256
        this.cam.applyInertia();
210✔
257
    }
210✔
258
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc