1 | /* |
---|
2 | * Licensed to the Apache Software Foundation (ASF) under one |
---|
3 | * or more contributor license agreements. See the NOTICE file |
---|
4 | * distributed with this work for additional information |
---|
5 | * regarding copyright ownership. The ASF licenses this file |
---|
6 | * to you under the Apache License, Version 2.0 (the |
---|
7 | * "License"); you may not use this file except in compliance |
---|
8 | * with the License. You may obtain a copy of the License at |
---|
9 | * |
---|
10 | * http://www.apache.org/licenses/LICENSE-2.0 |
---|
11 | * |
---|
12 | * Unless required by applicable law or agreed to in writing, |
---|
13 | * software distributed under the License is distributed on an |
---|
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
---|
15 | * KIND, either express or implied. See the License for the |
---|
16 | * specific language governing permissions and limitations |
---|
17 | * under the License. |
---|
18 | */ |
---|
19 | |
---|
20 | var Guacamole = Guacamole || {}; |
---|
21 | |
---|
22 | /** |
---|
23 | * The Guacamole display. The display does not deal with the Guacamole |
---|
24 | * protocol, and instead implements a set of graphical operations which |
---|
25 | * embody the set of operations present in the protocol. The order operations |
---|
26 | * are executed is guaranteed to be in the same order as their corresponding |
---|
27 | * functions are called. |
---|
28 | * |
---|
29 | * @constructor |
---|
30 | */ |
---|
31 | Guacamole.Display = function() { |
---|
32 | |
---|
33 | /** |
---|
34 | * Reference to this Guacamole.Display. |
---|
35 | * @private |
---|
36 | */ |
---|
37 | var guac_display = this; |
---|
38 | |
---|
39 | var displayWidth = 0; |
---|
40 | var displayHeight = 0; |
---|
41 | var displayScale = 1; |
---|
42 | |
---|
43 | // Create display |
---|
44 | var display = document.createElement("div"); |
---|
45 | display.style.position = "relative"; |
---|
46 | display.style.width = displayWidth + "px"; |
---|
47 | display.style.height = displayHeight + "px"; |
---|
48 | |
---|
49 | // Ensure transformations on display originate at 0,0 |
---|
50 | display.style.transformOrigin = |
---|
51 | display.style.webkitTransformOrigin = |
---|
52 | display.style.MozTransformOrigin = |
---|
53 | display.style.OTransformOrigin = |
---|
54 | display.style.msTransformOrigin = |
---|
55 | "0 0"; |
---|
56 | |
---|
57 | // Create default layer |
---|
58 | var default_layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight); |
---|
59 | |
---|
60 | // Create cursor layer |
---|
61 | var cursor = new Guacamole.Display.VisibleLayer(0, 0); |
---|
62 | cursor.setChannelMask(Guacamole.Layer.SRC); |
---|
63 | |
---|
64 | // Add default layer and cursor to display |
---|
65 | display.appendChild(default_layer.getElement()); |
---|
66 | display.appendChild(cursor.getElement()); |
---|
67 | |
---|
68 | // Create bounding div |
---|
69 | var bounds = document.createElement("div"); |
---|
70 | bounds.style.position = "relative"; |
---|
71 | bounds.style.width = (displayWidth*displayScale) + "px"; |
---|
72 | bounds.style.height = (displayHeight*displayScale) + "px"; |
---|
73 | |
---|
74 | // Add display to bounds |
---|
75 | bounds.appendChild(display); |
---|
76 | |
---|
77 | /** |
---|
78 | * The X coordinate of the hotspot of the mouse cursor. The hotspot is |
---|
79 | * the relative location within the image of the mouse cursor at which |
---|
80 | * each click occurs. |
---|
81 | * |
---|
82 | * @type {Number} |
---|
83 | */ |
---|
84 | this.cursorHotspotX = 0; |
---|
85 | |
---|
86 | /** |
---|
87 | * The Y coordinate of the hotspot of the mouse cursor. The hotspot is |
---|
88 | * the relative location within the image of the mouse cursor at which |
---|
89 | * each click occurs. |
---|
90 | * |
---|
91 | * @type {Number} |
---|
92 | */ |
---|
93 | this.cursorHotspotY = 0; |
---|
94 | |
---|
95 | /** |
---|
96 | * The current X coordinate of the local mouse cursor. This is not |
---|
97 | * necessarily the location of the actual mouse - it refers only to |
---|
98 | * the location of the cursor image within the Guacamole display, as |
---|
99 | * last set by moveCursor(). |
---|
100 | * |
---|
101 | * @type {Number} |
---|
102 | */ |
---|
103 | this.cursorX = 0; |
---|
104 | |
---|
105 | /** |
---|
106 | * The current X coordinate of the local mouse cursor. This is not |
---|
107 | * necessarily the location of the actual mouse - it refers only to |
---|
108 | * the location of the cursor image within the Guacamole display, as |
---|
109 | * last set by moveCursor(). |
---|
110 | * |
---|
111 | * @type {Number} |
---|
112 | */ |
---|
113 | this.cursorY = 0; |
---|
114 | |
---|
115 | /** |
---|
116 | * Fired when the default layer (and thus the entire Guacamole display) |
---|
117 | * is resized. |
---|
118 | * |
---|
119 | * @event |
---|
120 | * @param {Number} width The new width of the Guacamole display. |
---|
121 | * @param {Number} height The new height of the Guacamole display. |
---|
122 | */ |
---|
123 | this.onresize = null; |
---|
124 | |
---|
125 | /** |
---|
126 | * Fired whenever the local cursor image is changed. This can be used to |
---|
127 | * implement special handling of the client-side cursor, or to override |
---|
128 | * the default use of a software cursor layer. |
---|
129 | * |
---|
130 | * @event |
---|
131 | * @param {HTMLCanvasElement} canvas The cursor image. |
---|
132 | * @param {Number} x The X-coordinate of the cursor hotspot. |
---|
133 | * @param {Number} y The Y-coordinate of the cursor hotspot. |
---|
134 | */ |
---|
135 | this.oncursor = null; |
---|
136 | |
---|
137 | /** |
---|
138 | * The queue of all pending Tasks. Tasks will be run in order, with new |
---|
139 | * tasks added at the end of the queue and old tasks removed from the |
---|
140 | * front of the queue (FIFO). These tasks will eventually be grouped |
---|
141 | * into a Frame. |
---|
142 | * @private |
---|
143 | * @type {Task[]} |
---|
144 | */ |
---|
145 | var tasks = []; |
---|
146 | |
---|
147 | /** |
---|
148 | * The queue of all frames. Each frame is a pairing of an array of tasks |
---|
149 | * and a callback which must be called when the frame is rendered. |
---|
150 | * @private |
---|
151 | * @type {Frame[]} |
---|
152 | */ |
---|
153 | var frames = []; |
---|
154 | |
---|
155 | /** |
---|
156 | * Flushes all pending frames. |
---|
157 | * @private |
---|
158 | */ |
---|
159 | function __flush_frames() { |
---|
160 | |
---|
161 | var rendered_frames = 0; |
---|
162 | |
---|
163 | // Draw all pending frames, if ready |
---|
164 | while (rendered_frames < frames.length) { |
---|
165 | |
---|
166 | var frame = frames[rendered_frames]; |
---|
167 | if (!frame.isReady()) |
---|
168 | break; |
---|
169 | |
---|
170 | frame.flush(); |
---|
171 | rendered_frames++; |
---|
172 | |
---|
173 | } |
---|
174 | |
---|
175 | // Remove rendered frames from array |
---|
176 | frames.splice(0, rendered_frames); |
---|
177 | |
---|
178 | } |
---|
179 | |
---|
180 | /** |
---|
181 | * An ordered list of tasks which must be executed atomically. Once |
---|
182 | * executed, an associated (and optional) callback will be called. |
---|
183 | * |
---|
184 | * @private |
---|
185 | * @constructor |
---|
186 | * @param {function} callback The function to call when this frame is |
---|
187 | * rendered. |
---|
188 | * @param {Task[]} tasks The set of tasks which must be executed to render |
---|
189 | * this frame. |
---|
190 | */ |
---|
191 | function Frame(callback, tasks) { |
---|
192 | |
---|
193 | /** |
---|
194 | * Returns whether this frame is ready to be rendered. This function |
---|
195 | * returns true if and only if ALL underlying tasks are unblocked. |
---|
196 | * |
---|
197 | * @returns {Boolean} true if all underlying tasks are unblocked, |
---|
198 | * false otherwise. |
---|
199 | */ |
---|
200 | this.isReady = function() { |
---|
201 | |
---|
202 | // Search for blocked tasks |
---|
203 | for (var i=0; i < tasks.length; i++) { |
---|
204 | if (tasks[i].blocked) |
---|
205 | return false; |
---|
206 | } |
---|
207 | |
---|
208 | // If no blocked tasks, the frame is ready |
---|
209 | return true; |
---|
210 | |
---|
211 | }; |
---|
212 | |
---|
213 | /** |
---|
214 | * Renders this frame, calling the associated callback, if any, after |
---|
215 | * the frame is complete. This function MUST only be called when no |
---|
216 | * blocked tasks exist. Calling this function with blocked tasks |
---|
217 | * will result in undefined behavior. |
---|
218 | */ |
---|
219 | this.flush = function() { |
---|
220 | |
---|
221 | // Draw all pending tasks. |
---|
222 | for (var i=0; i < tasks.length; i++) |
---|
223 | tasks[i].execute(); |
---|
224 | |
---|
225 | // Call callback |
---|
226 | if (callback) callback(); |
---|
227 | |
---|
228 | }; |
---|
229 | |
---|
230 | } |
---|
231 | |
---|
232 | /** |
---|
233 | * A container for an task handler. Each operation which must be ordered |
---|
234 | * is associated with a Task that goes into a task queue. Tasks in this |
---|
235 | * queue are executed in order once their handlers are set, while Tasks |
---|
236 | * without handlers block themselves and any following Tasks from running. |
---|
237 | * |
---|
238 | * @constructor |
---|
239 | * @private |
---|
240 | * @param {function} taskHandler The function to call when this task |
---|
241 | * runs, if any. |
---|
242 | * @param {boolean} blocked Whether this task should start blocked. |
---|
243 | */ |
---|
244 | function Task(taskHandler, blocked) { |
---|
245 | |
---|
246 | var task = this; |
---|
247 | |
---|
248 | /** |
---|
249 | * Whether this Task is blocked. |
---|
250 | * |
---|
251 | * @type {boolean} |
---|
252 | */ |
---|
253 | this.blocked = blocked; |
---|
254 | |
---|
255 | /** |
---|
256 | * Unblocks this Task, allowing it to run. |
---|
257 | */ |
---|
258 | this.unblock = function() { |
---|
259 | if (task.blocked) { |
---|
260 | task.blocked = false; |
---|
261 | __flush_frames(); |
---|
262 | } |
---|
263 | }; |
---|
264 | |
---|
265 | /** |
---|
266 | * Calls the handler associated with this task IMMEDIATELY. This |
---|
267 | * function does not track whether this task is marked as blocked. |
---|
268 | * Enforcing the blocked status of tasks is up to the caller. |
---|
269 | */ |
---|
270 | this.execute = function() { |
---|
271 | if (taskHandler) taskHandler(); |
---|
272 | }; |
---|
273 | |
---|
274 | } |
---|
275 | |
---|
276 | /** |
---|
277 | * Schedules a task for future execution. The given handler will execute |
---|
278 | * immediately after all previous tasks upon frame flush, unless this |
---|
279 | * task is blocked. If any tasks is blocked, the entire frame will not |
---|
280 | * render (and no tasks within will execute) until all tasks are unblocked. |
---|
281 | * |
---|
282 | * @private |
---|
283 | * @param {function} handler The function to call when possible, if any. |
---|
284 | * @param {boolean} blocked Whether the task should start blocked. |
---|
285 | * @returns {Task} The Task created and added to the queue for future |
---|
286 | * running. |
---|
287 | */ |
---|
288 | function scheduleTask(handler, blocked) { |
---|
289 | var task = new Task(handler, blocked); |
---|
290 | tasks.push(task); |
---|
291 | return task; |
---|
292 | } |
---|
293 | |
---|
294 | /** |
---|
295 | * Returns the element which contains the Guacamole display. |
---|
296 | * |
---|
297 | * @return {Element} The element containing the Guacamole display. |
---|
298 | */ |
---|
299 | this.getElement = function() { |
---|
300 | return bounds; |
---|
301 | }; |
---|
302 | |
---|
303 | /** |
---|
304 | * Returns the width of this display. |
---|
305 | * |
---|
306 | * @return {Number} The width of this display; |
---|
307 | */ |
---|
308 | this.getWidth = function() { |
---|
309 | return displayWidth; |
---|
310 | }; |
---|
311 | |
---|
312 | /** |
---|
313 | * Returns the height of this display. |
---|
314 | * |
---|
315 | * @return {Number} The height of this display; |
---|
316 | */ |
---|
317 | this.getHeight = function() { |
---|
318 | return displayHeight; |
---|
319 | }; |
---|
320 | |
---|
321 | /** |
---|
322 | * Returns the default layer of this display. Each Guacamole display always |
---|
323 | * has at least one layer. Other layers can optionally be created within |
---|
324 | * this layer, but the default layer cannot be removed and is the absolute |
---|
325 | * ancestor of all other layers. |
---|
326 | * |
---|
327 | * @return {Guacamole.Display.VisibleLayer} The default layer. |
---|
328 | */ |
---|
329 | this.getDefaultLayer = function() { |
---|
330 | return default_layer; |
---|
331 | }; |
---|
332 | |
---|
333 | /** |
---|
334 | * Returns the cursor layer of this display. Each Guacamole display contains |
---|
335 | * a layer for the image of the mouse cursor. This layer is a special case |
---|
336 | * and exists above all other layers, similar to the hardware mouse cursor. |
---|
337 | * |
---|
338 | * @return {Guacamole.Display.VisibleLayer} The cursor layer. |
---|
339 | */ |
---|
340 | this.getCursorLayer = function() { |
---|
341 | return cursor; |
---|
342 | }; |
---|
343 | |
---|
344 | /** |
---|
345 | * Creates a new layer. The new layer will be a direct child of the default |
---|
346 | * layer, but can be moved to be a child of any other layer. Layers returned |
---|
347 | * by this function are visible. |
---|
348 | * |
---|
349 | * @return {Guacamole.Display.VisibleLayer} The newly-created layer. |
---|
350 | */ |
---|
351 | this.createLayer = function() { |
---|
352 | var layer = new Guacamole.Display.VisibleLayer(displayWidth, displayHeight); |
---|
353 | layer.move(default_layer, 0, 0, 0); |
---|
354 | return layer; |
---|
355 | }; |
---|
356 | |
---|
357 | /** |
---|
358 | * Creates a new buffer. Buffers are invisible, off-screen surfaces. They |
---|
359 | * are implemented in the same manner as layers, but do not provide the |
---|
360 | * same nesting semantics. |
---|
361 | * |
---|
362 | * @return {Guacamole.Layer} The newly-created buffer. |
---|
363 | */ |
---|
364 | this.createBuffer = function() { |
---|
365 | var buffer = new Guacamole.Layer(0, 0); |
---|
366 | buffer.autosize = 1; |
---|
367 | return buffer; |
---|
368 | }; |
---|
369 | |
---|
370 | /** |
---|
371 | * Flush all pending draw tasks, if possible, as a new frame. If the entire |
---|
372 | * frame is not ready, the flush will wait until all required tasks are |
---|
373 | * unblocked. |
---|
374 | * |
---|
375 | * @param {function} callback The function to call when this frame is |
---|
376 | * flushed. This may happen immediately, or |
---|
377 | * later when blocked tasks become unblocked. |
---|
378 | */ |
---|
379 | this.flush = function(callback) { |
---|
380 | |
---|
381 | // Add frame, reset tasks |
---|
382 | frames.push(new Frame(callback, tasks)); |
---|
383 | tasks = []; |
---|
384 | |
---|
385 | // Attempt flush |
---|
386 | __flush_frames(); |
---|
387 | |
---|
388 | }; |
---|
389 | |
---|
390 | /** |
---|
391 | * Sets the hotspot and image of the mouse cursor displayed within the |
---|
392 | * Guacamole display. |
---|
393 | * |
---|
394 | * @param {Number} hotspotX The X coordinate of the cursor hotspot. |
---|
395 | * @param {Number} hotspotY The Y coordinate of the cursor hotspot. |
---|
396 | * @param {Guacamole.Layer} layer The source layer containing the data which |
---|
397 | * should be used as the mouse cursor image. |
---|
398 | * @param {Number} srcx The X coordinate of the upper-left corner of the |
---|
399 | * rectangle within the source layer's coordinate |
---|
400 | * space to copy data from. |
---|
401 | * @param {Number} srcy The Y coordinate of the upper-left corner of the |
---|
402 | * rectangle within the source layer's coordinate |
---|
403 | * space to copy data from. |
---|
404 | * @param {Number} srcw The width of the rectangle within the source layer's |
---|
405 | * coordinate space to copy data from. |
---|
406 | * @param {Number} srch The height of the rectangle within the source |
---|
407 | * layer's coordinate space to copy data from. |
---|
408 | |
---|
409 | */ |
---|
410 | this.setCursor = function(hotspotX, hotspotY, layer, srcx, srcy, srcw, srch) { |
---|
411 | scheduleTask(function __display_set_cursor() { |
---|
412 | |
---|
413 | // Set hotspot |
---|
414 | guac_display.cursorHotspotX = hotspotX; |
---|
415 | guac_display.cursorHotspotY = hotspotY; |
---|
416 | |
---|
417 | // Reset cursor size |
---|
418 | cursor.resize(srcw, srch); |
---|
419 | |
---|
420 | // Draw cursor to cursor layer |
---|
421 | cursor.copy(layer, srcx, srcy, srcw, srch, 0, 0); |
---|
422 | guac_display.moveCursor(guac_display.cursorX, guac_display.cursorY); |
---|
423 | |
---|
424 | // Fire cursor change event |
---|
425 | if (guac_display.oncursor) |
---|
426 | guac_display.oncursor(cursor.toCanvas(), hotspotX, hotspotY); |
---|
427 | |
---|
428 | }); |
---|
429 | }; |
---|
430 | |
---|
431 | /** |
---|
432 | * Sets whether the software-rendered cursor is shown. This cursor differs |
---|
433 | * from the hardware cursor in that it is built into the Guacamole.Display, |
---|
434 | * and relies on its own Guacamole layer to render. |
---|
435 | * |
---|
436 | * @param {Boolean} [shown=true] Whether to show the software cursor. |
---|
437 | */ |
---|
438 | this.showCursor = function(shown) { |
---|
439 | |
---|
440 | var element = cursor.getElement(); |
---|
441 | var parent = element.parentNode; |
---|
442 | |
---|
443 | // Remove from DOM if hidden |
---|
444 | if (shown === false) { |
---|
445 | if (parent) |
---|
446 | parent.removeChild(element); |
---|
447 | } |
---|
448 | |
---|
449 | // Otherwise, ensure cursor is child of display |
---|
450 | else if (parent !== display) |
---|
451 | display.appendChild(element); |
---|
452 | |
---|
453 | }; |
---|
454 | |
---|
455 | /** |
---|
456 | * Sets the location of the local cursor to the given coordinates. For the |
---|
457 | * sake of responsiveness, this function performs its action immediately. |
---|
458 | * Cursor motion is not maintained within atomic frames. |
---|
459 | * |
---|
460 | * @param {Number} x The X coordinate to move the cursor to. |
---|
461 | * @param {Number} y The Y coordinate to move the cursor to. |
---|
462 | */ |
---|
463 | this.moveCursor = function(x, y) { |
---|
464 | |
---|
465 | // Move cursor layer |
---|
466 | cursor.translate(x - guac_display.cursorHotspotX, |
---|
467 | y - guac_display.cursorHotspotY); |
---|
468 | |
---|
469 | // Update stored position |
---|
470 | guac_display.cursorX = x; |
---|
471 | guac_display.cursorY = y; |
---|
472 | |
---|
473 | }; |
---|
474 | |
---|
475 | /** |
---|
476 | * Changes the size of the given Layer to the given width and height. |
---|
477 | * Resizing is only attempted if the new size provided is actually different |
---|
478 | * from the current size. |
---|
479 | * |
---|
480 | * @param {Guacamole.Layer} layer The layer to resize. |
---|
481 | * @param {Number} width The new width. |
---|
482 | * @param {Number} height The new height. |
---|
483 | */ |
---|
484 | this.resize = function(layer, width, height) { |
---|
485 | scheduleTask(function __display_resize() { |
---|
486 | |
---|
487 | layer.resize(width, height); |
---|
488 | |
---|
489 | // Resize display if default layer is resized |
---|
490 | if (layer === default_layer) { |
---|
491 | |
---|
492 | // Update (set) display size |
---|
493 | displayWidth = width; |
---|
494 | displayHeight = height; |
---|
495 | display.style.width = displayWidth + "px"; |
---|
496 | display.style.height = displayHeight + "px"; |
---|
497 | |
---|
498 | // Update bounds size |
---|
499 | bounds.style.width = (displayWidth*displayScale) + "px"; |
---|
500 | bounds.style.height = (displayHeight*displayScale) + "px"; |
---|
501 | |
---|
502 | // Notify of resize |
---|
503 | if (guac_display.onresize) |
---|
504 | guac_display.onresize(width, height); |
---|
505 | |
---|
506 | } |
---|
507 | |
---|
508 | }); |
---|
509 | }; |
---|
510 | |
---|
511 | /** |
---|
512 | * Draws the specified image at the given coordinates. The image specified |
---|
513 | * must already be loaded. |
---|
514 | * |
---|
515 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
516 | * @param {Number} x The destination X coordinate. |
---|
517 | * @param {Number} y The destination Y coordinate. |
---|
518 | * @param {Image} image The image to draw. Note that this is an Image |
---|
519 | * object - not a URL. |
---|
520 | */ |
---|
521 | this.drawImage = function(layer, x, y, image) { |
---|
522 | scheduleTask(function __display_drawImage() { |
---|
523 | layer.drawImage(x, y, image); |
---|
524 | }); |
---|
525 | }; |
---|
526 | |
---|
527 | /** |
---|
528 | * Draws the image contained within the specified Blob at the given |
---|
529 | * coordinates. The Blob specified must already be populated with image |
---|
530 | * data. |
---|
531 | * |
---|
532 | * @param {Guacamole.Layer} layer |
---|
533 | * The layer to draw upon. |
---|
534 | * |
---|
535 | * @param {Number} x |
---|
536 | * The destination X coordinate. |
---|
537 | * |
---|
538 | * @param {Number} y |
---|
539 | * The destination Y coordinate. |
---|
540 | * |
---|
541 | * @param {Blob} blob |
---|
542 | * The Blob containing the image data to draw. |
---|
543 | */ |
---|
544 | this.drawBlob = function(layer, x, y, blob) { |
---|
545 | |
---|
546 | // Create URL for blob |
---|
547 | var url = URL.createObjectURL(blob); |
---|
548 | |
---|
549 | // Draw and free blob URL when ready |
---|
550 | var task = scheduleTask(function __display_drawBlob() { |
---|
551 | |
---|
552 | // Draw the image only if it loaded without errors |
---|
553 | if (image.width && image.height) |
---|
554 | layer.drawImage(x, y, image); |
---|
555 | |
---|
556 | // Blob URL no longer needed |
---|
557 | URL.revokeObjectURL(url); |
---|
558 | |
---|
559 | }, true); |
---|
560 | |
---|
561 | // Load image from URL |
---|
562 | var image = new Image(); |
---|
563 | image.onload = task.unblock; |
---|
564 | image.onerror = task.unblock; |
---|
565 | image.src = url; |
---|
566 | |
---|
567 | }; |
---|
568 | |
---|
569 | /** |
---|
570 | * Draws the image at the specified URL at the given coordinates. The image |
---|
571 | * will be loaded automatically, and this and any future operations will |
---|
572 | * wait for the image to finish loading. |
---|
573 | * |
---|
574 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
575 | * @param {Number} x The destination X coordinate. |
---|
576 | * @param {Number} y The destination Y coordinate. |
---|
577 | * @param {String} url The URL of the image to draw. |
---|
578 | */ |
---|
579 | this.draw = function(layer, x, y, url) { |
---|
580 | |
---|
581 | var task = scheduleTask(function __display_draw() { |
---|
582 | |
---|
583 | // Draw the image only if it loaded without errors |
---|
584 | if (image.width && image.height) |
---|
585 | layer.drawImage(x, y, image); |
---|
586 | |
---|
587 | }, true); |
---|
588 | |
---|
589 | var image = new Image(); |
---|
590 | image.onload = task.unblock; |
---|
591 | image.onerror = task.unblock; |
---|
592 | image.src = url; |
---|
593 | |
---|
594 | }; |
---|
595 | |
---|
596 | /** |
---|
597 | * Plays the video at the specified URL within this layer. The video |
---|
598 | * will be loaded automatically, and this and any future operations will |
---|
599 | * wait for the video to finish loading. Future operations will not be |
---|
600 | * executed until the video finishes playing. |
---|
601 | * |
---|
602 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
603 | * @param {String} mimetype The mimetype of the video to play. |
---|
604 | * @param {Number} duration The duration of the video in milliseconds. |
---|
605 | * @param {String} url The URL of the video to play. |
---|
606 | */ |
---|
607 | this.play = function(layer, mimetype, duration, url) { |
---|
608 | |
---|
609 | // Start loading the video |
---|
610 | var video = document.createElement("video"); |
---|
611 | video.type = mimetype; |
---|
612 | video.src = url; |
---|
613 | |
---|
614 | // Start copying frames when playing |
---|
615 | video.addEventListener("play", function() { |
---|
616 | |
---|
617 | function render_callback() { |
---|
618 | layer.drawImage(0, 0, video); |
---|
619 | if (!video.ended) |
---|
620 | window.setTimeout(render_callback, 20); |
---|
621 | } |
---|
622 | |
---|
623 | render_callback(); |
---|
624 | |
---|
625 | }, false); |
---|
626 | |
---|
627 | scheduleTask(video.play); |
---|
628 | |
---|
629 | }; |
---|
630 | |
---|
631 | /** |
---|
632 | * Transfer a rectangle of image data from one Layer to this Layer using the |
---|
633 | * specified transfer function. |
---|
634 | * |
---|
635 | * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. |
---|
636 | * @param {Number} srcx The X coordinate of the upper-left corner of the |
---|
637 | * rectangle within the source Layer's coordinate |
---|
638 | * space to copy data from. |
---|
639 | * @param {Number} srcy The Y coordinate of the upper-left corner of the |
---|
640 | * rectangle within the source Layer's coordinate |
---|
641 | * space to copy data from. |
---|
642 | * @param {Number} srcw The width of the rectangle within the source Layer's |
---|
643 | * coordinate space to copy data from. |
---|
644 | * @param {Number} srch The height of the rectangle within the source |
---|
645 | * Layer's coordinate space to copy data from. |
---|
646 | * @param {Guacamole.Layer} dstLayer The layer to draw upon. |
---|
647 | * @param {Number} x The destination X coordinate. |
---|
648 | * @param {Number} y The destination Y coordinate. |
---|
649 | * @param {Function} transferFunction The transfer function to use to |
---|
650 | * transfer data from source to |
---|
651 | * destination. |
---|
652 | */ |
---|
653 | this.transfer = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y, transferFunction) { |
---|
654 | scheduleTask(function __display_transfer() { |
---|
655 | dstLayer.transfer(srcLayer, srcx, srcy, srcw, srch, x, y, transferFunction); |
---|
656 | }); |
---|
657 | }; |
---|
658 | |
---|
659 | /** |
---|
660 | * Put a rectangle of image data from one Layer to this Layer directly |
---|
661 | * without performing any alpha blending. Simply copy the data. |
---|
662 | * |
---|
663 | * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. |
---|
664 | * @param {Number} srcx The X coordinate of the upper-left corner of the |
---|
665 | * rectangle within the source Layer's coordinate |
---|
666 | * space to copy data from. |
---|
667 | * @param {Number} srcy The Y coordinate of the upper-left corner of the |
---|
668 | * rectangle within the source Layer's coordinate |
---|
669 | * space to copy data from. |
---|
670 | * @param {Number} srcw The width of the rectangle within the source Layer's |
---|
671 | * coordinate space to copy data from. |
---|
672 | * @param {Number} srch The height of the rectangle within the source |
---|
673 | * Layer's coordinate space to copy data from. |
---|
674 | * @param {Guacamole.Layer} dstLayer The layer to draw upon. |
---|
675 | * @param {Number} x The destination X coordinate. |
---|
676 | * @param {Number} y The destination Y coordinate. |
---|
677 | */ |
---|
678 | this.put = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) { |
---|
679 | scheduleTask(function __display_put() { |
---|
680 | dstLayer.put(srcLayer, srcx, srcy, srcw, srch, x, y); |
---|
681 | }); |
---|
682 | }; |
---|
683 | |
---|
684 | /** |
---|
685 | * Copy a rectangle of image data from one Layer to this Layer. This |
---|
686 | * operation will copy exactly the image data that will be drawn once all |
---|
687 | * operations of the source Layer that were pending at the time this |
---|
688 | * function was called are complete. This operation will not alter the |
---|
689 | * size of the source Layer even if its autosize property is set to true. |
---|
690 | * |
---|
691 | * @param {Guacamole.Layer} srcLayer The Layer to copy image data from. |
---|
692 | * @param {Number} srcx The X coordinate of the upper-left corner of the |
---|
693 | * rectangle within the source Layer's coordinate |
---|
694 | * space to copy data from. |
---|
695 | * @param {Number} srcy The Y coordinate of the upper-left corner of the |
---|
696 | * rectangle within the source Layer's coordinate |
---|
697 | * space to copy data from. |
---|
698 | * @param {Number} srcw The width of the rectangle within the source Layer's |
---|
699 | * coordinate space to copy data from. |
---|
700 | * @param {Number} srch The height of the rectangle within the source |
---|
701 | * Layer's coordinate space to copy data from. |
---|
702 | * @param {Guacamole.Layer} dstLayer The layer to draw upon. |
---|
703 | * @param {Number} x The destination X coordinate. |
---|
704 | * @param {Number} y The destination Y coordinate. |
---|
705 | */ |
---|
706 | this.copy = function(srcLayer, srcx, srcy, srcw, srch, dstLayer, x, y) { |
---|
707 | scheduleTask(function __display_copy() { |
---|
708 | dstLayer.copy(srcLayer, srcx, srcy, srcw, srch, x, y); |
---|
709 | }); |
---|
710 | }; |
---|
711 | |
---|
712 | /** |
---|
713 | * Starts a new path at the specified point. |
---|
714 | * |
---|
715 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
716 | * @param {Number} x The X coordinate of the point to draw. |
---|
717 | * @param {Number} y The Y coordinate of the point to draw. |
---|
718 | */ |
---|
719 | this.moveTo = function(layer, x, y) { |
---|
720 | scheduleTask(function __display_moveTo() { |
---|
721 | layer.moveTo(x, y); |
---|
722 | }); |
---|
723 | }; |
---|
724 | |
---|
725 | /** |
---|
726 | * Add the specified line to the current path. |
---|
727 | * |
---|
728 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
729 | * @param {Number} x The X coordinate of the endpoint of the line to draw. |
---|
730 | * @param {Number} y The Y coordinate of the endpoint of the line to draw. |
---|
731 | */ |
---|
732 | this.lineTo = function(layer, x, y) { |
---|
733 | scheduleTask(function __display_lineTo() { |
---|
734 | layer.lineTo(x, y); |
---|
735 | }); |
---|
736 | }; |
---|
737 | |
---|
738 | /** |
---|
739 | * Add the specified arc to the current path. |
---|
740 | * |
---|
741 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
742 | * @param {Number} x The X coordinate of the center of the circle which |
---|
743 | * will contain the arc. |
---|
744 | * @param {Number} y The Y coordinate of the center of the circle which |
---|
745 | * will contain the arc. |
---|
746 | * @param {Number} radius The radius of the circle. |
---|
747 | * @param {Number} startAngle The starting angle of the arc, in radians. |
---|
748 | * @param {Number} endAngle The ending angle of the arc, in radians. |
---|
749 | * @param {Boolean} negative Whether the arc should be drawn in order of |
---|
750 | * decreasing angle. |
---|
751 | */ |
---|
752 | this.arc = function(layer, x, y, radius, startAngle, endAngle, negative) { |
---|
753 | scheduleTask(function __display_arc() { |
---|
754 | layer.arc(x, y, radius, startAngle, endAngle, negative); |
---|
755 | }); |
---|
756 | }; |
---|
757 | |
---|
758 | /** |
---|
759 | * Starts a new path at the specified point. |
---|
760 | * |
---|
761 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
762 | * @param {Number} cp1x The X coordinate of the first control point. |
---|
763 | * @param {Number} cp1y The Y coordinate of the first control point. |
---|
764 | * @param {Number} cp2x The X coordinate of the second control point. |
---|
765 | * @param {Number} cp2y The Y coordinate of the second control point. |
---|
766 | * @param {Number} x The X coordinate of the endpoint of the curve. |
---|
767 | * @param {Number} y The Y coordinate of the endpoint of the curve. |
---|
768 | */ |
---|
769 | this.curveTo = function(layer, cp1x, cp1y, cp2x, cp2y, x, y) { |
---|
770 | scheduleTask(function __display_curveTo() { |
---|
771 | layer.curveTo(cp1x, cp1y, cp2x, cp2y, x, y); |
---|
772 | }); |
---|
773 | }; |
---|
774 | |
---|
775 | /** |
---|
776 | * Closes the current path by connecting the end point with the start |
---|
777 | * point (if any) with a straight line. |
---|
778 | * |
---|
779 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
780 | */ |
---|
781 | this.close = function(layer) { |
---|
782 | scheduleTask(function __display_close() { |
---|
783 | layer.close(); |
---|
784 | }); |
---|
785 | }; |
---|
786 | |
---|
787 | /** |
---|
788 | * Add the specified rectangle to the current path. |
---|
789 | * |
---|
790 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
791 | * @param {Number} x The X coordinate of the upper-left corner of the |
---|
792 | * rectangle to draw. |
---|
793 | * @param {Number} y The Y coordinate of the upper-left corner of the |
---|
794 | * rectangle to draw. |
---|
795 | * @param {Number} w The width of the rectangle to draw. |
---|
796 | * @param {Number} h The height of the rectangle to draw. |
---|
797 | */ |
---|
798 | this.rect = function(layer, x, y, w, h) { |
---|
799 | scheduleTask(function __display_rect() { |
---|
800 | layer.rect(x, y, w, h); |
---|
801 | }); |
---|
802 | }; |
---|
803 | |
---|
804 | /** |
---|
805 | * Clip all future drawing operations by the current path. The current path |
---|
806 | * is implicitly closed. The current path can continue to be reused |
---|
807 | * for other operations (such as fillColor()) but a new path will be started |
---|
808 | * once a path drawing operation (path() or rect()) is used. |
---|
809 | * |
---|
810 | * @param {Guacamole.Layer} layer The layer to affect. |
---|
811 | */ |
---|
812 | this.clip = function(layer) { |
---|
813 | scheduleTask(function __display_clip() { |
---|
814 | layer.clip(); |
---|
815 | }); |
---|
816 | }; |
---|
817 | |
---|
818 | /** |
---|
819 | * Stroke the current path with the specified color. The current path |
---|
820 | * is implicitly closed. The current path can continue to be reused |
---|
821 | * for other operations (such as clip()) but a new path will be started |
---|
822 | * once a path drawing operation (path() or rect()) is used. |
---|
823 | * |
---|
824 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
825 | * @param {String} cap The line cap style. Can be "round", "square", |
---|
826 | * or "butt". |
---|
827 | * @param {String} join The line join style. Can be "round", "bevel", |
---|
828 | * or "miter". |
---|
829 | * @param {Number} thickness The line thickness in pixels. |
---|
830 | * @param {Number} r The red component of the color to fill. |
---|
831 | * @param {Number} g The green component of the color to fill. |
---|
832 | * @param {Number} b The blue component of the color to fill. |
---|
833 | * @param {Number} a The alpha component of the color to fill. |
---|
834 | */ |
---|
835 | this.strokeColor = function(layer, cap, join, thickness, r, g, b, a) { |
---|
836 | scheduleTask(function __display_strokeColor() { |
---|
837 | layer.strokeColor(cap, join, thickness, r, g, b, a); |
---|
838 | }); |
---|
839 | }; |
---|
840 | |
---|
841 | /** |
---|
842 | * Fills the current path with the specified color. The current path |
---|
843 | * is implicitly closed. The current path can continue to be reused |
---|
844 | * for other operations (such as clip()) but a new path will be started |
---|
845 | * once a path drawing operation (path() or rect()) is used. |
---|
846 | * |
---|
847 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
848 | * @param {Number} r The red component of the color to fill. |
---|
849 | * @param {Number} g The green component of the color to fill. |
---|
850 | * @param {Number} b The blue component of the color to fill. |
---|
851 | * @param {Number} a The alpha component of the color to fill. |
---|
852 | */ |
---|
853 | this.fillColor = function(layer, r, g, b, a) { |
---|
854 | scheduleTask(function __display_fillColor() { |
---|
855 | layer.fillColor(r, g, b, a); |
---|
856 | }); |
---|
857 | }; |
---|
858 | |
---|
859 | /** |
---|
860 | * Stroke the current path with the image within the specified layer. The |
---|
861 | * image data will be tiled infinitely within the stroke. The current path |
---|
862 | * is implicitly closed. The current path can continue to be reused |
---|
863 | * for other operations (such as clip()) but a new path will be started |
---|
864 | * once a path drawing operation (path() or rect()) is used. |
---|
865 | * |
---|
866 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
867 | * @param {String} cap The line cap style. Can be "round", "square", |
---|
868 | * or "butt". |
---|
869 | * @param {String} join The line join style. Can be "round", "bevel", |
---|
870 | * or "miter". |
---|
871 | * @param {Number} thickness The line thickness in pixels. |
---|
872 | * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern |
---|
873 | * within the stroke. |
---|
874 | */ |
---|
875 | this.strokeLayer = function(layer, cap, join, thickness, srcLayer) { |
---|
876 | scheduleTask(function __display_strokeLayer() { |
---|
877 | layer.strokeLayer(cap, join, thickness, srcLayer); |
---|
878 | }); |
---|
879 | }; |
---|
880 | |
---|
881 | /** |
---|
882 | * Fills the current path with the image within the specified layer. The |
---|
883 | * image data will be tiled infinitely within the stroke. The current path |
---|
884 | * is implicitly closed. The current path can continue to be reused |
---|
885 | * for other operations (such as clip()) but a new path will be started |
---|
886 | * once a path drawing operation (path() or rect()) is used. |
---|
887 | * |
---|
888 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
889 | * @param {Guacamole.Layer} srcLayer The layer to use as a repeating pattern |
---|
890 | * within the fill. |
---|
891 | */ |
---|
892 | this.fillLayer = function(layer, srcLayer) { |
---|
893 | scheduleTask(function __display_fillLayer() { |
---|
894 | layer.fillLayer(srcLayer); |
---|
895 | }); |
---|
896 | }; |
---|
897 | |
---|
898 | /** |
---|
899 | * Push current layer state onto stack. |
---|
900 | * |
---|
901 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
902 | */ |
---|
903 | this.push = function(layer) { |
---|
904 | scheduleTask(function __display_push() { |
---|
905 | layer.push(); |
---|
906 | }); |
---|
907 | }; |
---|
908 | |
---|
909 | /** |
---|
910 | * Pop layer state off stack. |
---|
911 | * |
---|
912 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
913 | */ |
---|
914 | this.pop = function(layer) { |
---|
915 | scheduleTask(function __display_pop() { |
---|
916 | layer.pop(); |
---|
917 | }); |
---|
918 | }; |
---|
919 | |
---|
920 | /** |
---|
921 | * Reset the layer, clearing the stack, the current path, and any transform |
---|
922 | * matrix. |
---|
923 | * |
---|
924 | * @param {Guacamole.Layer} layer The layer to draw upon. |
---|
925 | */ |
---|
926 | this.reset = function(layer) { |
---|
927 | scheduleTask(function __display_reset() { |
---|
928 | layer.reset(); |
---|
929 | }); |
---|
930 | }; |
---|
931 | |
---|
932 | /** |
---|
933 | * Sets the given affine transform (defined with six values from the |
---|
934 | * transform's matrix). |
---|
935 | * |
---|
936 | * @param {Guacamole.Layer} layer The layer to modify. |
---|
937 | * @param {Number} a The first value in the affine transform's matrix. |
---|
938 | * @param {Number} b The second value in the affine transform's matrix. |
---|
939 | * @param {Number} c The third value in the affine transform's matrix. |
---|
940 | * @param {Number} d The fourth value in the affine transform's matrix. |
---|
941 | * @param {Number} e The fifth value in the affine transform's matrix. |
---|
942 | * @param {Number} f The sixth value in the affine transform's matrix. |
---|
943 | */ |
---|
944 | this.setTransform = function(layer, a, b, c, d, e, f) { |
---|
945 | scheduleTask(function __display_setTransform() { |
---|
946 | layer.setTransform(a, b, c, d, e, f); |
---|
947 | }); |
---|
948 | }; |
---|
949 | |
---|
950 | /** |
---|
951 | * Applies the given affine transform (defined with six values from the |
---|
952 | * transform's matrix). |
---|
953 | * |
---|
954 | * @param {Guacamole.Layer} layer The layer to modify. |
---|
955 | * @param {Number} a The first value in the affine transform's matrix. |
---|
956 | * @param {Number} b The second value in the affine transform's matrix. |
---|
957 | * @param {Number} c The third value in the affine transform's matrix. |
---|
958 | * @param {Number} d The fourth value in the affine transform's matrix. |
---|
959 | * @param {Number} e The fifth value in the affine transform's matrix. |
---|
960 | * @param {Number} f The sixth value in the affine transform's matrix. |
---|
961 | */ |
---|
962 | this.transform = function(layer, a, b, c, d, e, f) { |
---|
963 | scheduleTask(function __display_transform() { |
---|
964 | layer.transform(a, b, c, d, e, f); |
---|
965 | }); |
---|
966 | }; |
---|
967 | |
---|
968 | /** |
---|
969 | * Sets the channel mask for future operations on this Layer. |
---|
970 | * |
---|
971 | * The channel mask is a Guacamole-specific compositing operation identifier |
---|
972 | * with a single bit representing each of four channels (in order): source |
---|
973 | * image where destination transparent, source where destination opaque, |
---|
974 | * destination where source transparent, and destination where source |
---|
975 | * opaque. |
---|
976 | * |
---|
977 | * @param {Guacamole.Layer} layer The layer to modify. |
---|
978 | * @param {Number} mask The channel mask for future operations on this |
---|
979 | * Layer. |
---|
980 | */ |
---|
981 | this.setChannelMask = function(layer, mask) { |
---|
982 | scheduleTask(function __display_setChannelMask() { |
---|
983 | layer.setChannelMask(mask); |
---|
984 | }); |
---|
985 | }; |
---|
986 | |
---|
987 | /** |
---|
988 | * Sets the miter limit for stroke operations using the miter join. This |
---|
989 | * limit is the maximum ratio of the size of the miter join to the stroke |
---|
990 | * width. If this ratio is exceeded, the miter will not be drawn for that |
---|
991 | * joint of the path. |
---|
992 | * |
---|
993 | * @param {Guacamole.Layer} layer The layer to modify. |
---|
994 | * @param {Number} limit The miter limit for stroke operations using the |
---|
995 | * miter join. |
---|
996 | */ |
---|
997 | this.setMiterLimit = function(layer, limit) { |
---|
998 | scheduleTask(function __display_setMiterLimit() { |
---|
999 | layer.setMiterLimit(limit); |
---|
1000 | }); |
---|
1001 | }; |
---|
1002 | |
---|
1003 | /** |
---|
1004 | * Removes the given layer container entirely, such that it is no longer |
---|
1005 | * contained within its parent layer, if any. |
---|
1006 | * |
---|
1007 | * @param {Guacamole.Display.VisibleLayer} layer |
---|
1008 | * The layer being removed from its parent. |
---|
1009 | */ |
---|
1010 | this.dispose = function dispose(layer) { |
---|
1011 | scheduleTask(function disposeLayer() { |
---|
1012 | layer.dispose(); |
---|
1013 | }); |
---|
1014 | }; |
---|
1015 | |
---|
1016 | /** |
---|
1017 | * Applies the given affine transform (defined with six values from the |
---|
1018 | * transform's matrix) to the given layer. |
---|
1019 | * |
---|
1020 | * @param {Guacamole.Display.VisibleLayer} layer |
---|
1021 | * The layer being distorted. |
---|
1022 | * |
---|
1023 | * @param {Number} a |
---|
1024 | * The first value in the affine transform's matrix. |
---|
1025 | * |
---|
1026 | * @param {Number} b |
---|
1027 | * The second value in the affine transform's matrix. |
---|
1028 | * |
---|
1029 | * @param {Number} c |
---|
1030 | * The third value in the affine transform's matrix. |
---|
1031 | * |
---|
1032 | * @param {Number} d |
---|
1033 | * The fourth value in the affine transform's matrix. |
---|
1034 | * |
---|
1035 | * @param {Number} e |
---|
1036 | * The fifth value in the affine transform's matrix. |
---|
1037 | * |
---|
1038 | * @param {Number} f |
---|
1039 | * The sixth value in the affine transform's matrix. |
---|
1040 | */ |
---|
1041 | this.distort = function distort(layer, a, b, c, d, e, f) { |
---|
1042 | scheduleTask(function distortLayer() { |
---|
1043 | layer.distort(a, b, c, d, e, f); |
---|
1044 | }); |
---|
1045 | }; |
---|
1046 | |
---|
1047 | /** |
---|
1048 | * Moves the upper-left corner of the given layer to the given X and Y |
---|
1049 | * coordinate, sets the Z stacking order, and reparents the layer |
---|
1050 | * to the given parent layer. |
---|
1051 | * |
---|
1052 | * @param {Guacamole.Display.VisibleLayer} layer |
---|
1053 | * The layer being moved. |
---|
1054 | * |
---|
1055 | * @param {Guacamole.Display.VisibleLayer} parent |
---|
1056 | * The parent to set. |
---|
1057 | * |
---|
1058 | * @param {Number} x |
---|
1059 | * The X coordinate to move to. |
---|
1060 | * |
---|
1061 | * @param {Number} y |
---|
1062 | * The Y coordinate to move to. |
---|
1063 | * |
---|
1064 | * @param {Number} z |
---|
1065 | * The Z coordinate to move to. |
---|
1066 | */ |
---|
1067 | this.move = function move(layer, parent, x, y, z) { |
---|
1068 | scheduleTask(function moveLayer() { |
---|
1069 | layer.move(parent, x, y, z); |
---|
1070 | }); |
---|
1071 | }; |
---|
1072 | |
---|
1073 | /** |
---|
1074 | * Sets the opacity of the given layer to the given value, where 255 is |
---|
1075 | * fully opaque and 0 is fully transparent. |
---|
1076 | * |
---|
1077 | * @param {Guacamole.Display.VisibleLayer} layer |
---|
1078 | * The layer whose opacity should be set. |
---|
1079 | * |
---|
1080 | * @param {Number} alpha |
---|
1081 | * The opacity to set. |
---|
1082 | */ |
---|
1083 | this.shade = function shade(layer, alpha) { |
---|
1084 | scheduleTask(function shadeLayer() { |
---|
1085 | layer.shade(alpha); |
---|
1086 | }); |
---|
1087 | }; |
---|
1088 | |
---|
1089 | /** |
---|
1090 | * Sets the scale of the client display element such that it renders at |
---|
1091 | * a relatively smaller or larger size, without affecting the true |
---|
1092 | * resolution of the display. |
---|
1093 | * |
---|
1094 | * @param {Number} scale The scale to resize to, where 1.0 is normal |
---|
1095 | * size (1:1 scale). |
---|
1096 | */ |
---|
1097 | this.scale = function(scale) { |
---|
1098 | |
---|
1099 | display.style.transform = |
---|
1100 | display.style.WebkitTransform = |
---|
1101 | display.style.MozTransform = |
---|
1102 | display.style.OTransform = |
---|
1103 | display.style.msTransform = |
---|
1104 | |
---|
1105 | "scale(" + scale + "," + scale + ")"; |
---|
1106 | |
---|
1107 | displayScale = scale; |
---|
1108 | |
---|
1109 | // Update bounds size |
---|
1110 | bounds.style.width = (displayWidth*displayScale) + "px"; |
---|
1111 | bounds.style.height = (displayHeight*displayScale) + "px"; |
---|
1112 | |
---|
1113 | }; |
---|
1114 | |
---|
1115 | /** |
---|
1116 | * Returns the scale of the display. |
---|
1117 | * |
---|
1118 | * @return {Number} The scale of the display. |
---|
1119 | */ |
---|
1120 | this.getScale = function() { |
---|
1121 | return displayScale; |
---|
1122 | }; |
---|
1123 | |
---|
1124 | /** |
---|
1125 | * Returns a canvas element containing the entire display, with all child |
---|
1126 | * layers composited within. |
---|
1127 | * |
---|
1128 | * @return {HTMLCanvasElement} A new canvas element containing a copy of |
---|
1129 | * the display. |
---|
1130 | */ |
---|
1131 | this.flatten = function() { |
---|
1132 | |
---|
1133 | // Get destination canvas |
---|
1134 | var canvas = document.createElement("canvas"); |
---|
1135 | canvas.width = default_layer.width; |
---|
1136 | canvas.height = default_layer.height; |
---|
1137 | |
---|
1138 | var context = canvas.getContext("2d"); |
---|
1139 | |
---|
1140 | // Returns sorted array of children |
---|
1141 | function get_children(layer) { |
---|
1142 | |
---|
1143 | // Build array of children |
---|
1144 | var children = []; |
---|
1145 | for (var index in layer.children) |
---|
1146 | children.push(layer.children[index]); |
---|
1147 | |
---|
1148 | // Sort |
---|
1149 | children.sort(function children_comparator(a, b) { |
---|
1150 | |
---|
1151 | // Compare based on Z order |
---|
1152 | var diff = a.z - b.z; |
---|
1153 | if (diff !== 0) |
---|
1154 | return diff; |
---|
1155 | |
---|
1156 | // If Z order identical, use document order |
---|
1157 | var a_element = a.getElement(); |
---|
1158 | var b_element = b.getElement(); |
---|
1159 | var position = b_element.compareDocumentPosition(a_element); |
---|
1160 | |
---|
1161 | if (position & Node.DOCUMENT_POSITION_PRECEDING) return -1; |
---|
1162 | if (position & Node.DOCUMENT_POSITION_FOLLOWING) return 1; |
---|
1163 | |
---|
1164 | // Otherwise, assume same |
---|
1165 | return 0; |
---|
1166 | |
---|
1167 | }); |
---|
1168 | |
---|
1169 | // Done |
---|
1170 | return children; |
---|
1171 | |
---|
1172 | } |
---|
1173 | |
---|
1174 | // Draws the contents of the given layer at the given coordinates |
---|
1175 | function draw_layer(layer, x, y) { |
---|
1176 | |
---|
1177 | // Draw layer |
---|
1178 | if (layer.width > 0 && layer.height > 0) { |
---|
1179 | |
---|
1180 | // Save and update alpha |
---|
1181 | var initial_alpha = context.globalAlpha; |
---|
1182 | context.globalAlpha *= layer.alpha / 255.0; |
---|
1183 | |
---|
1184 | // Copy data |
---|
1185 | context.drawImage(layer.getCanvas(), x, y); |
---|
1186 | |
---|
1187 | // Draw all children |
---|
1188 | var children = get_children(layer); |
---|
1189 | for (var i=0; i<children.length; i++) { |
---|
1190 | var child = children[i]; |
---|
1191 | draw_layer(child, x + child.x, y + child.y); |
---|
1192 | } |
---|
1193 | |
---|
1194 | // Restore alpha |
---|
1195 | context.globalAlpha = initial_alpha; |
---|
1196 | |
---|
1197 | } |
---|
1198 | |
---|
1199 | } |
---|
1200 | |
---|
1201 | // Draw default layer and all children |
---|
1202 | draw_layer(default_layer, 0, 0); |
---|
1203 | |
---|
1204 | // Return new canvas copy |
---|
1205 | return canvas; |
---|
1206 | |
---|
1207 | }; |
---|
1208 | |
---|
1209 | }; |
---|
1210 | |
---|
1211 | /** |
---|
1212 | * Simple container for Guacamole.Layer, allowing layers to be easily |
---|
1213 | * repositioned and nested. This allows certain operations to be accelerated |
---|
1214 | * through DOM manipulation, rather than raster operations. |
---|
1215 | * |
---|
1216 | * @constructor |
---|
1217 | * @augments Guacamole.Layer |
---|
1218 | * @param {Number} width The width of the Layer, in pixels. The canvas element |
---|
1219 | * backing this Layer will be given this width. |
---|
1220 | * @param {Number} height The height of the Layer, in pixels. The canvas element |
---|
1221 | * backing this Layer will be given this height. |
---|
1222 | */ |
---|
1223 | Guacamole.Display.VisibleLayer = function(width, height) { |
---|
1224 | |
---|
1225 | Guacamole.Layer.apply(this, [width, height]); |
---|
1226 | |
---|
1227 | /** |
---|
1228 | * Reference to this layer. |
---|
1229 | * @private |
---|
1230 | */ |
---|
1231 | var layer = this; |
---|
1232 | |
---|
1233 | /** |
---|
1234 | * Identifier which uniquely identifies this layer. This is COMPLETELY |
---|
1235 | * UNRELATED to the index of the underlying layer, which is specific |
---|
1236 | * to the Guacamole protocol, and not relevant at this level. |
---|
1237 | * |
---|
1238 | * @private |
---|
1239 | * @type {Number} |
---|
1240 | */ |
---|
1241 | this.__unique_id = Guacamole.Display.VisibleLayer.__next_id++; |
---|
1242 | |
---|
1243 | /** |
---|
1244 | * The opacity of the layer container, where 255 is fully opaque and 0 is |
---|
1245 | * fully transparent. |
---|
1246 | */ |
---|
1247 | this.alpha = 0xFF; |
---|
1248 | |
---|
1249 | /** |
---|
1250 | * X coordinate of the upper-left corner of this layer container within |
---|
1251 | * its parent, in pixels. |
---|
1252 | * @type {Number} |
---|
1253 | */ |
---|
1254 | this.x = 0; |
---|
1255 | |
---|
1256 | /** |
---|
1257 | * Y coordinate of the upper-left corner of this layer container within |
---|
1258 | * its parent, in pixels. |
---|
1259 | * @type {Number} |
---|
1260 | */ |
---|
1261 | this.y = 0; |
---|
1262 | |
---|
1263 | /** |
---|
1264 | * Z stacking order of this layer relative to other sibling layers. |
---|
1265 | * @type {Number} |
---|
1266 | */ |
---|
1267 | this.z = 0; |
---|
1268 | |
---|
1269 | /** |
---|
1270 | * The affine transformation applied to this layer container. Each element |
---|
1271 | * corresponds to a value from the transformation matrix, with the first |
---|
1272 | * three values being the first row, and the last three values being the |
---|
1273 | * second row. There are six values total. |
---|
1274 | * |
---|
1275 | * @type {Number[]} |
---|
1276 | */ |
---|
1277 | this.matrix = [1, 0, 0, 1, 0, 0]; |
---|
1278 | |
---|
1279 | /** |
---|
1280 | * The parent layer container of this layer, if any. |
---|
1281 | * @type {Guacamole.Display.VisibleLayer} |
---|
1282 | */ |
---|
1283 | this.parent = null; |
---|
1284 | |
---|
1285 | /** |
---|
1286 | * Set of all children of this layer, indexed by layer index. This object |
---|
1287 | * will have one property per child. |
---|
1288 | */ |
---|
1289 | this.children = {}; |
---|
1290 | |
---|
1291 | // Set layer position |
---|
1292 | var canvas = layer.getCanvas(); |
---|
1293 | canvas.style.position = "absolute"; |
---|
1294 | canvas.style.left = "0px"; |
---|
1295 | canvas.style.top = "0px"; |
---|
1296 | |
---|
1297 | // Create div with given size |
---|
1298 | var div = document.createElement("div"); |
---|
1299 | div.appendChild(canvas); |
---|
1300 | div.style.width = width + "px"; |
---|
1301 | div.style.height = height + "px"; |
---|
1302 | div.style.position = "absolute"; |
---|
1303 | div.style.left = "0px"; |
---|
1304 | div.style.top = "0px"; |
---|
1305 | div.style.overflow = "hidden"; |
---|
1306 | |
---|
1307 | /** |
---|
1308 | * Superclass resize() function. |
---|
1309 | * @private |
---|
1310 | */ |
---|
1311 | var __super_resize = this.resize; |
---|
1312 | |
---|
1313 | this.resize = function(width, height) { |
---|
1314 | |
---|
1315 | // Resize containing div |
---|
1316 | div.style.width = width + "px"; |
---|
1317 | div.style.height = height + "px"; |
---|
1318 | |
---|
1319 | __super_resize(width, height); |
---|
1320 | |
---|
1321 | }; |
---|
1322 | |
---|
1323 | /** |
---|
1324 | * Returns the element containing the canvas and any other elements |
---|
1325 | * associated with this layer. |
---|
1326 | * @returns {Element} The element containing this layer's canvas. |
---|
1327 | */ |
---|
1328 | this.getElement = function() { |
---|
1329 | return div; |
---|
1330 | }; |
---|
1331 | |
---|
1332 | /** |
---|
1333 | * The translation component of this layer's transform. |
---|
1334 | * @private |
---|
1335 | */ |
---|
1336 | var translate = "translate(0px, 0px)"; // (0, 0) |
---|
1337 | |
---|
1338 | /** |
---|
1339 | * The arbitrary matrix component of this layer's transform. |
---|
1340 | * @private |
---|
1341 | */ |
---|
1342 | var matrix = "matrix(1, 0, 0, 1, 0, 0)"; // Identity |
---|
1343 | |
---|
1344 | /** |
---|
1345 | * Moves the upper-left corner of this layer to the given X and Y |
---|
1346 | * coordinate. |
---|
1347 | * |
---|
1348 | * @param {Number} x The X coordinate to move to. |
---|
1349 | * @param {Number} y The Y coordinate to move to. |
---|
1350 | */ |
---|
1351 | this.translate = function(x, y) { |
---|
1352 | |
---|
1353 | layer.x = x; |
---|
1354 | layer.y = y; |
---|
1355 | |
---|
1356 | // Generate translation |
---|
1357 | translate = "translate(" |
---|
1358 | + x + "px," |
---|
1359 | + y + "px)"; |
---|
1360 | |
---|
1361 | // Set layer transform |
---|
1362 | div.style.transform = |
---|
1363 | div.style.WebkitTransform = |
---|
1364 | div.style.MozTransform = |
---|
1365 | div.style.OTransform = |
---|
1366 | div.style.msTransform = |
---|
1367 | |
---|
1368 | translate + " " + matrix; |
---|
1369 | |
---|
1370 | }; |
---|
1371 | |
---|
1372 | /** |
---|
1373 | * Moves the upper-left corner of this VisibleLayer to the given X and Y |
---|
1374 | * coordinate, sets the Z stacking order, and reparents this VisibleLayer |
---|
1375 | * to the given VisibleLayer. |
---|
1376 | * |
---|
1377 | * @param {Guacamole.Display.VisibleLayer} parent The parent to set. |
---|
1378 | * @param {Number} x The X coordinate to move to. |
---|
1379 | * @param {Number} y The Y coordinate to move to. |
---|
1380 | * @param {Number} z The Z coordinate to move to. |
---|
1381 | */ |
---|
1382 | this.move = function(parent, x, y, z) { |
---|
1383 | |
---|
1384 | // Set parent if necessary |
---|
1385 | if (layer.parent !== parent) { |
---|
1386 | |
---|
1387 | // Maintain relationship |
---|
1388 | if (layer.parent) |
---|
1389 | delete layer.parent.children[layer.__unique_id]; |
---|
1390 | layer.parent = parent; |
---|
1391 | parent.children[layer.__unique_id] = layer; |
---|
1392 | |
---|
1393 | // Reparent element |
---|
1394 | var parent_element = parent.getElement(); |
---|
1395 | parent_element.appendChild(div); |
---|
1396 | |
---|
1397 | } |
---|
1398 | |
---|
1399 | // Set location |
---|
1400 | layer.translate(x, y); |
---|
1401 | layer.z = z; |
---|
1402 | div.style.zIndex = z; |
---|
1403 | |
---|
1404 | }; |
---|
1405 | |
---|
1406 | /** |
---|
1407 | * Sets the opacity of this layer to the given value, where 255 is fully |
---|
1408 | * opaque and 0 is fully transparent. |
---|
1409 | * |
---|
1410 | * @param {Number} a The opacity to set. |
---|
1411 | */ |
---|
1412 | this.shade = function(a) { |
---|
1413 | layer.alpha = a; |
---|
1414 | div.style.opacity = a/255.0; |
---|
1415 | }; |
---|
1416 | |
---|
1417 | /** |
---|
1418 | * Removes this layer container entirely, such that it is no longer |
---|
1419 | * contained within its parent layer, if any. |
---|
1420 | */ |
---|
1421 | this.dispose = function() { |
---|
1422 | |
---|
1423 | // Remove from parent container |
---|
1424 | if (layer.parent) { |
---|
1425 | delete layer.parent.children[layer.__unique_id]; |
---|
1426 | layer.parent = null; |
---|
1427 | } |
---|
1428 | |
---|
1429 | // Remove from parent element |
---|
1430 | if (div.parentNode) |
---|
1431 | div.parentNode.removeChild(div); |
---|
1432 | |
---|
1433 | }; |
---|
1434 | |
---|
1435 | /** |
---|
1436 | * Applies the given affine transform (defined with six values from the |
---|
1437 | * transform's matrix). |
---|
1438 | * |
---|
1439 | * @param {Number} a The first value in the affine transform's matrix. |
---|
1440 | * @param {Number} b The second value in the affine transform's matrix. |
---|
1441 | * @param {Number} c The third value in the affine transform's matrix. |
---|
1442 | * @param {Number} d The fourth value in the affine transform's matrix. |
---|
1443 | * @param {Number} e The fifth value in the affine transform's matrix. |
---|
1444 | * @param {Number} f The sixth value in the affine transform's matrix. |
---|
1445 | */ |
---|
1446 | this.distort = function(a, b, c, d, e, f) { |
---|
1447 | |
---|
1448 | // Store matrix |
---|
1449 | layer.matrix = [a, b, c, d, e, f]; |
---|
1450 | |
---|
1451 | // Generate matrix transformation |
---|
1452 | matrix = |
---|
1453 | |
---|
1454 | /* a c e |
---|
1455 | * b d f |
---|
1456 | * 0 0 1 |
---|
1457 | */ |
---|
1458 | |
---|
1459 | "matrix(" + a + "," + b + "," + c + "," + d + "," + e + "," + f + ")"; |
---|
1460 | |
---|
1461 | // Set layer transform |
---|
1462 | div.style.transform = |
---|
1463 | div.style.WebkitTransform = |
---|
1464 | div.style.MozTransform = |
---|
1465 | div.style.OTransform = |
---|
1466 | div.style.msTransform = |
---|
1467 | |
---|
1468 | translate + " " + matrix; |
---|
1469 | |
---|
1470 | }; |
---|
1471 | |
---|
1472 | }; |
---|
1473 | |
---|
1474 | /** |
---|
1475 | * The next identifier to be assigned to the layer container. This identifier |
---|
1476 | * uniquely identifies each VisibleLayer, but is unrelated to the index of |
---|
1477 | * the layer, which exists at the protocol/client level only. |
---|
1478 | * |
---|
1479 | * @private |
---|
1480 | * @type {Number} |
---|
1481 | */ |
---|
1482 | Guacamole.Display.VisibleLayer.__next_id = 0; |
---|