UNPKG

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