Added busy handlers for layer, implemented ready instruction handling in client
[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.top = "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 busyHandler = null;
72     var readyHandler = null;
73
74     var updates = new Array();
75     var autosize = 0;
76
77     function Update(updateHandler) {
78
79         this.setHandler = function(handler) {
80             updateHandler = handler;
81         };
82
83         this.hasHandler = function() {
84             return updateHandler != null;
85         };
86
87         this.handle = function() {
88             updateHandler();
89         }
90
91     }
92
93     display.setAutosize = function(flag) {
94         autosize = flag;
95     };
96
97     function reserveJob(handler) {
98         
99         // If no pending updates, just call (if available) and exit
100         if (display.isReady() && handler != null) {
101             handler();
102             return null;
103         }
104
105         // If updates are pending/executing, schedule a pending update
106         // and return a reference to it.
107         var update = new Update(handler);
108         updates.push(update);
109         return update;
110         
111     }
112
113     function handlePendingUpdates() {
114
115         // Draw all pending updates.
116         var update;
117         while ((update = updates[0]) != null && update.hasHandler()) {
118             update.handle();
119             updates.shift();
120         }
121
122         // If done with updates, call ready handler
123         if (display.isReady() && readyHandler != null)
124             readyHandler();
125
126     }
127
128     display.isReady = function() {
129         return updates.length == 0;
130     };
131
132     display.setReadyHandler = function(handler) {
133         readyHandler = handler;
134     };
135
136     display.setBusyHandler = function(handler) {
137         busyHandler = handler;
138     };
139
140
141     display.drawImage = function(x, y, image) {
142         reserveJob(function() {
143             if (autosize != 0) fitRect(x, y, image.width, image.height);
144             displayContext.drawImage(image, x, y);
145         });
146     };
147
148
149     display.draw = function(x, y, url) {
150
151         // If about to become busy, call busy handler
152         if (display.isReady() && busyHandler != null)
153             busyHandler();
154
155         var update = reserveJob(null);
156
157         var image = new Image();
158         image.onload = function() {
159
160             update.setHandler(function() {
161                 if (autosize != 0) fitRect(x, y, image.width, image.height);
162                 displayContext.drawImage(image, x, y);
163             });
164
165             // As this update originally had no handler and may have blocked
166             // other updates, handle any blocked updates.
167             handlePendingUpdates();
168
169         };
170         image.src = url;
171
172     };
173
174     // Run arbitrary function as soon as currently pending operations complete.
175     // Future operations will not block this function from being called (unlike
176     // the ready handler, which requires no pending updates)
177     display.sync = function(handler) {
178         reserveJob(handler);
179     }
180
181     display.copyRect = function(srcLayer, srcx, srcy, w, h, x, y) {
182   
183         function scheduleCopyRect() { 
184             reserveJob(function() {
185                 if (autosize != 0) fitRect(x, y, w, h);
186                 displayContext.drawImage(srcLayer, srcx, srcy, w, h, x, y, w, h);
187             });
188         }
189
190         // If we ARE the source layer, no need to sync.
191         // Syncing would result in deadlock.
192         if (display === srcLayer)
193             scheduleCopyRect();
194
195         // Otherwise synchronize copy operation with source layer
196         else
197             srcLayer.sync(scheduleCopyRect);
198
199     };
200
201     display.clearRect = function(x, y, w, h) {
202         reserveJob(function() {
203             if (autosize != 0) fitRect(x, y, w, h);
204             displayContext.clearRect(x, y, w, h);
205         });
206     };
207
208     display.filter = function(filter) {
209         reserveJob(function() {
210             var imageData = displayContext.getImageData(0, 0, width, height);
211             filter(imageData.data, width, height);
212             displayContext.putImageData(imageData, 0, 0);
213         });
214     };
215
216     return display;
217
218 }
219