copyRect should be synchronized with source layer/buffer's updates (as it may not...
[guacamole-common-js.git] / src / main / resources / layer.js
1
2 /*
3  *  Guacamole - Clientless Remote Desktop
4  *  Copyright (C) 2010  Michael Jumper
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU Affero General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU Affero General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Affero General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 function Layer(width, height) {
21
22     // Off-screen buffer
23     var display = document.createElement("canvas");
24     var displayContext = display.getContext("2d");
25
26     function resize(newWidth, newHeight) {
27         display.style.position = "absolute";
28         display.style.left = "0px";
29         display.style.right = "0px";
30
31         display.width = newWidth;
32         display.height = newHeight;
33
34         width = newWidth;
35         height = newHeight;
36     }
37
38     display.resize = function(newWidth, newHeight) {
39         if (newWidth != width || newHeight != height)
40             resize(newWidth, newHeight);
41     };
42
43     function fitRect(x, y, w, h) {
44         
45         // Calculate bounds
46         var opBoundX = w + x;
47         var opBoundY = h + y;
48         
49         // Determine max width
50         var resizeWidth;
51         if (opBoundX > width)
52             resizeWidth = opBoundX;
53         else
54             resizeWidth = width;
55
56         // Determine max height
57         var resizeHeight;
58         if (opBoundY > height)
59             resizeHeight = opBoundY;
60         else
61             resizeHeight = height;
62
63         // Resize if necessary
64         if (resizeWidth != width || resizeHeight != height)
65             resize(resizeWidth, resizeHeight);
66
67     }
68
69     resize(width, height);
70
71     var readyHandler = null;
72     var nextUpdateToDraw = 0;
73     var currentUpdate = 0;
74     var updates = new Array();
75     var autosize = 0;
76
77     display.setAutosize = function(flag) {
78         autosize = flag;
79     };
80
81     // Given an update ID, either call the provided update callback, or
82     // schedule the update for later.
83     function setUpdate(updateId, update) {
84
85         // If this update is the next to draw...
86         if (updateId == nextUpdateToDraw) {
87
88             // Call provided update handler.
89             update();
90
91             // Draw all pending updates.
92             var updateCallback;
93             while ((updateCallback = updates[++nextUpdateToDraw])) {
94                 updateCallback();
95                 delete updates[nextUpdateToDraw];
96             }
97
98             // If done with updates, call ready handler
99             if (display.isReady() && readyHandler != null)
100                 readyHandler();
101
102         }
103
104         // If not next to draw, set callback and wait.
105         else
106             updates[updateId] = update;
107
108     }
109
110     display.isReady = function() {
111         return currentUpdate == nextUpdateToDraw;
112     };
113
114     display.setReadyHandler = function(handler) {
115         readyHandler = handler;
116     };
117
118
119     display.drawImage = function(x, y, image) {
120         var updateId = currentUpdate++;
121
122         setUpdate(updateId, function() {
123             if (autosize != 0) fitRect(x, y, image.width, image.height);
124             displayContext.drawImage(image, x, y);
125         });
126
127     };
128
129
130     display.draw = function(x, y, url) {
131         var updateId = currentUpdate++;
132
133         var image = new Image();
134         image.onload = function() {
135             setUpdate(updateId, function() {
136                 if (autosize != 0) fitRect(x, y, image.width, image.height);
137                 displayContext.drawImage(image, x, y);
138             });
139         };
140         image.src = url;
141     };
142
143     // Run arbitrary function as soon as currently pending operations complete.
144     // Future operations will not block this function from being called (unlike
145     // the ready handler, which requires no pending updates)
146     display.sync = function(handler) {
147         var updateId = currentUpdate++;
148         setUpdate(updateId, handler);
149     }
150
151     display.copyRect = function(srcLayer, srcx, srcy, w, h, x, y) {
152         var updateId = currentUpdate++;
153    
154         // Synchronize copy operation with source layer
155         srcLayer.sync(function() { 
156             setUpdate(updateId, function() {
157                 if (autosize != 0) fitRect(x, y, w, h);
158                 displayContext.drawImage(srcLayer, srcx, srcy, w, h, x, y, w, h);
159             });
160         });
161
162     };
163
164     display.clearRect = function(x, y, w, h) {
165         var updateId = currentUpdate++;
166
167         setUpdate(updateId, function() {
168             if (autosize != 0) fitRect(x, y, w, h);
169             displayContext.clearRect(x, y, w, h);
170         });
171
172     };
173
174     display.filter = function(filter) {
175         var updateId = currentUpdate++;
176
177         setUpdate(updateId, function() {
178             var imageData = displayContext.getImageData(0, 0, width, height);
179             filter(imageData.data, width, height);
180             displayContext.putImageData(imageData, 0, 0);
181         });
182
183     };
184
185     return display;
186
187 }
188