UNPKG

8.35 kBJavaScriptView Raw
1"use strict";
2
3var platform = require("./platform");
4
5// prevent springy scrolling on ios
6document.ontouchmove = function(e) {
7 e.preventDefault();
8};
9
10// prevent right-click on desktop
11window.oncontextmenu = function() {
12 return false;
13};
14
15var relMouseCoords = function(canvas, event) {
16 var x = event.pageX - canvas.offsetLeft + document.body.scrollLeft;
17 var y = event.pageY - canvas.offsetTop + document.body.scrollTop;
18
19 // scale based on ratio of canvas internal dimentions to css dimensions
20 var style = window.getComputedStyle(canvas);
21 var cw = parseInt(style.width);
22 var ch = parseInt(style.height);
23
24 x *= canvas.width / cw;
25 y *= canvas.height / ch;
26
27 return {
28 x: Math.floor(x),
29 y: Math.floor(y)
30 };
31};
32
33function relMouseCoordsEjecta(canvas, event) {
34 var ratioX = canvas.width / window.innerWidth;
35 var ratioY = canvas.height / window.innerHeight;
36 var x = event.pageX * ratioX;
37 var y = event.pageY * ratioY;
38 return { x: x, y: y };
39}
40
41if (platform.isEjecta()) {
42 relMouseCoords = relMouseCoordsEjecta;
43}
44
45/**
46 * Mouse and touch input handling. An instance of Mouse is available as {@link Splat.Game#mouse}.
47 *
48 * The first touch will emulates a mouse press with button 0.
49 * This means you can use the mouse ({@link Mouse#isPressed}/{@link Mouse#consumePressed}) APIs and your game will work on touch screens (as long as you only need the left button.
50 *
51 * A mouse press will emulate a touch if the device does not support touch.
52 * This means you can use {@link Mouse#touches}, and your game will still work on a PC with a mouse.
53 * Also, if you call {@link Mouse#consumePressed} with button 0, it will add a `consumed:true` field to all current touches. This will help you prevent processing a touch multiple times.
54 *
55 * @constructor
56 * @param {external:canvas} canvas The canvas to listen for events on.
57 */
58function Mouse(canvas) {
59 /**
60 * The x coordinate of the cursor relative to the left side of the canvas.
61 * @member {number}
62 */
63 this.x = 0;
64 /**
65 * The y coordinate of the cursor relative to the top of the canvas.
66 * @member {number}
67 */
68 this.y = 0;
69 /**
70 * The current button states.
71 * @member {Array}
72 * @private
73 */
74 this.buttons = [0, 0, 0];
75
76 /**
77 * An array of the current touches on a touch screen device. Each touch has a `x`, `y`, and `id` field.
78 * @member {Array}
79 */
80 this.touches = [];
81
82 /**
83 * A function that is called when a mouse button or touch is released.
84 * @callback onmouseupHandler
85 * @param {number} x The x coordinate of the mouse or touch that was released.
86 * @param {number} y The y coordinate of the mouse or touch that was released.
87 */
88 /**
89 * A function that will be called when a mouse button is released, or a touch has stopped.
90 * This is useful for opening a URL with {@link Splat.openUrl} to avoid popup blockers.
91 * @member {onmouseupHandler}
92 */
93 this.onmouseup = undefined;
94
95 var self = this;
96 canvas.addEventListener("mousedown", function(event) {
97 var m = relMouseCoords(canvas, event);
98 self.x = m.x;
99 self.y = m.y;
100 self.buttons[event.button] = 2;
101 updateTouchFromMouse();
102 });
103 canvas.addEventListener("mouseup", function(event) {
104 var m = relMouseCoords(canvas, event);
105 self.x = m.x;
106 self.y = m.y;
107 self.buttons[event.button] = 0;
108 updateTouchFromMouse();
109 if (self.onmouseup) {
110 self.onmouseup(self.x, self.y);
111 }
112 });
113 canvas.addEventListener("mousemove", function(event) {
114 var m = relMouseCoords(canvas, event);
115 self.x = m.x;
116 self.y = m.y;
117 updateTouchFromMouse();
118 });
119
120 function updateTouchFromMouse() {
121 if (self.supportsTouch()) {
122 return;
123 }
124 var idx = touchIndexById("mouse");
125 if (self.isPressed(0)) {
126 if (idx !== undefined) {
127 var touch = self.touches[idx];
128 touch.x = self.x;
129 touch.y = self.y;
130 } else {
131 self.touches.push({
132 id: "mouse",
133 x: self.x,
134 y: self.y
135 });
136 }
137 } else if (idx !== undefined) {
138 self.touches.splice(idx, 1);
139 }
140 }
141 function updateMouseFromTouch(touch) {
142 self.x = touch.x;
143 self.y = touch.y;
144 if (self.buttons[0] === 0) {
145 self.buttons[0] = 2;
146 }
147 }
148 function touchIndexById(id) {
149 for (var i = 0; i < self.touches.length; i++) {
150 if (self.touches[i].id === id) {
151 return i;
152 }
153 }
154 return undefined;
155 }
156 function eachChangedTouch(event, onChangeFunc) {
157 var touches = event.changedTouches;
158 for (var i = 0; i < touches.length; i++) {
159 onChangeFunc(touches[i]);
160 }
161 }
162 canvas.addEventListener("touchstart", function(event) {
163 eachChangedTouch(event, function(touch) {
164 var t = relMouseCoords(canvas, touch);
165 t.id = touch.identifier;
166 if (self.touches.length === 0) {
167 t.isMouse = true;
168 updateMouseFromTouch(t);
169 }
170 self.touches.push(t);
171 });
172 });
173 canvas.addEventListener("touchmove", function(event) {
174 eachChangedTouch(event, function(touch) {
175 var idx = touchIndexById(touch.identifier);
176 var t = self.touches[idx];
177 var coords = relMouseCoords(canvas, touch);
178 t.x = coords.x;
179 t.y = coords.y;
180 if (t.isMouse) {
181 updateMouseFromTouch(t);
182 }
183 });
184 });
185 canvas.addEventListener("touchend", function(event) {
186 eachChangedTouch(event, function(touch) {
187 var idx = touchIndexById(touch.identifier);
188 var t = self.touches.splice(idx, 1)[0];
189 if (t.isMouse) {
190 if (self.touches.length === 0) {
191 self.buttons[0] = 0;
192 } else {
193 self.touches[0].isMouse = true;
194 updateMouseFromTouch(self.touches[0]);
195 }
196 }
197 if (self.onmouseup) {
198 self.onmouseup(t.x, t.y);
199 }
200 });
201 });
202}
203/**
204 * Test whether the device supports touch events. This is useful to customize messages to say either "click" or "tap".
205 * @returns {boolean}
206 */
207Mouse.prototype.supportsTouch = function() {
208 return "ontouchstart" in window || navigator.msMaxTouchPoints;
209};
210/**
211 * Test if a mouse button is currently pressed.
212 * @param {number} button The button number to test. Button 0 is typically the left mouse button, as well as the first touch location.
213 * @param {number} [x] The left edge of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
214 * @param {number} [y] The top edge of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
215 * @param {number} [width] The width of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
216 * @param {number} [height] The height of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
217 * @returns {boolean}
218 */
219Mouse.prototype.isPressed = function(button, x, y, width, height) {
220 var b = this.buttons[button] >= 1;
221 if (arguments.length > 1 && (this.x < x || this.x > x + width || this.y < y || this.y > y + height)) {
222 b = false;
223 }
224 return b;
225};
226/**
227 * Test if a mouse button is currently pressed, and was newly pressed down since the last call to consumePressed.
228 * If you call this with button 0, it will add a `consumed:true` field to all current touches. This will help you prevent processing a touch multiple times.
229 * @param {number} button The button number to test.
230 * @param {number} [x] The left edge of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
231 * @param {number} [y] The top edge of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
232 * @param {number} [width] The width of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
233 * @param {number} [height] The height of a rectangle to restrict the test to. If the mouse position is outside of this rectangle, the button will not be considered pressed.
234 * @returns {boolean}
235 */
236Mouse.prototype.consumePressed = function(button, x, y, width, height) {
237 var b = this.buttons[button] === 2;
238 if (arguments.length > 1 && (this.x < x || this.x > x + width || this.y < y || this.y > y + height)) {
239 b = false;
240 }
241 if (b) {
242 this.buttons[button] = 1;
243 if (button === 0) {
244 for (var i = 0; i < this.touches.length; i++) {
245 this.touches[i].consumed = true;
246 }
247 }
248 }
249 return b;
250};
251
252module.exports = Mouse;