source: OpenRLabs-Git/web2py/applications/rlabs/static/js/guacamole-common-js/modules/Display.js

main
Last change on this file was 42bd667, checked in by David Fuertes <dfuertes@…>, 4 years ago

Historial Limpio

  • Property mode set to 100644
File size: 49.3 KB
Line 
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
20var 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 */
31Guacamole.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 */
1223Guacamole.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 */
1482Guacamole.Display.VisibleLayer.__next_id = 0;
Note: See TracBrowser for help on using the repository browser.