1 /*
2 * This file is part of gtkD.
3 *
4 * gtkD is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * gtkD is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with gtkD; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
17 */
18
19 module TestDrawingArea;
20
21 //debug = trace;
22
23 private import cairo.Context;
24 private import cairo.ImageSurface;
25
26 private import gtk.VBox;
27 private import pango.PgContext;
28 private import pango.PgLayout;
29
30 private import std.stdio;
31 private import std.math;
32
33 private import gtk.Widget;
34 private import gtk.MenuItem;
35 private import gtk.ComboBox;
36 private import gtk.ComboBoxText;
37 private import gtk.Menu;
38 private import gtk.Adjustment;
39 private import gtk.HBox;
40 private import gdk.Pixbuf;
41 private import gdk.Cairo;
42 private import gdk.Color;
43 private import gdk.Event;
44
45 private import pango.PgCairo;
46 private import pango.PgFontDescription;
47
48 private import gtk.DrawingArea;
49 private import gtk.Image;
50 private import gtk.SpinButton;
51
52
53 /**
54 * This tests the gtkD drawing area widget
55 */
56 class TestDrawingArea : VBox
57 {
58
59 this()
60 {
61 super(false,4);
62
63 TestDrawing drawingArea = new TestDrawing();
64
65 ComboBoxText operators = new ComboBoxText();
66 operators.appendText("CLEAR");
67 operators.appendText("SOURCE");
68 operators.appendText("OVER");
69 operators.appendText("IN");
70 operators.appendText("OUT");
71 operators.appendText("ATOP");
72 operators.appendText("DEST");
73 operators.appendText("DEST_OVER");
74 operators.appendText("DEST_IN");
75 operators.appendText("DEST_OUT");
76 operators.appendText("DEST_ATOP");
77 operators.appendText("XOR");
78 operators.appendText("ADD");
79 operators.appendText("SATURATE");
80 operators.appendText("MULTIPLY");
81 operators.appendText("SCREEN");
82 operators.appendText("OVERLAY");
83 operators.appendText("DARKEN");
84 operators.appendText("LIGHTEN");
85 operators.appendText("COLOR_DODGE");
86 operators.appendText("COLOR_BURN");
87 operators.appendText("HARD_LIGHT");
88 operators.appendText("SOFT_LIGHT");
89 operators.appendText("DIFFERENCE");
90 operators.appendText("EXCLUSION");
91 operators.appendText("HSL_HUE");
92 operators.appendText("HSL_SATURATION");
93 operators.appendText("HSL_COLOR");
94 operators.appendText("HSL_LUMINOSITY");
95 operators.setActive(1);
96 operators.addOnChanged(&drawingArea.onOperatorsChanged);
97
98 ComboBoxText primOption = new ComboBoxText();
99 primOption.appendText("Filled Arc");
100 primOption.appendText("Arc");
101 primOption.appendText("Line");
102 primOption.appendText("Point");
103 primOption.appendText("Rectangle");
104 primOption.appendText("Filled Rectangle");
105 primOption.appendText("Text");
106 primOption.appendText("Pango text");
107 primOption.appendText("Image");
108 primOption.appendText("Polygon");
109 primOption.setActive(0);
110 primOption.addOnChanged(&drawingArea.onPrimOptionChanged);
111
112 packStart(drawingArea,true,true,0);
113
114 HBox hbox = new HBox(false,4);
115 hbox.packStart(operators,false,false,2);
116 hbox.packStart(primOption,false,false,2);
117 hbox.packStart(drawingArea.spin,false,false,2);
118 hbox.packStart(drawingArea.backSpin,false,false,2);
119
120 packStart(hbox, false, false, 0);
121 }
122
123 class TestDrawing : DrawingArea
124 {
125 CairoOperator operator = CairoOperator.OVER;
126 ImageSurface surface;
127 Color paintColor;
128 Color black;
129
130 int width;
131 int height;
132
133 bool buttonIsDown;
134
135 string primitiveType;
136 PgFontDescription font;
137 Image image;
138 Pixbuf scaledPixbuf;
139
140 SpinButton spin;
141 SpinButton backSpin;
142 static GdkPoint[] polygonStar = [
143 {0,4},
144 {1,1},
145 {4,0},
146 {1,-1},
147 {0,-4},
148 {-1,-1},
149 {-4,0},
150 {-1,1}
151 ];
152
153 this()
154 {
155 setSizeRequest(333,333);
156 width = getWidth();
157 height = getHeight();
158
159 primitiveType = "Filled Arc";
160 font = PgFontDescription.fromString("Courier 48");
161
162 image = new Image("images/gtkDlogo_a_small.png");
163 scaledPixbuf = image.getPixbuf();
164 if (scaledPixbuf is null)
165 {
166 writeln("\nFailed to load image file gtkDlogo_a_small.png");
167 }
168
169 paintColor = new Color(cast(ubyte)0,cast(ubyte)0,cast(ubyte)0);
170 black = new Color(cast(ubyte)0,cast(ubyte)0,cast(ubyte)0);
171
172 spin = new SpinButton(new Adjustment(30, 1, 400, 1, 10, 0),1,0);
173 sizeSpinChanged(spin);
174 spin.addOnValueChanged(&sizeSpinChanged);
175 backSpin = new SpinButton(new Adjustment(5, 4, 100, 1, 10, 0),1,0);
176 backSpin.addOnValueChanged(&backSpinChanged);
177
178 addOnDraw(&drawCallback);
179 addOnMotionNotify(&onMotionNotify);
180 addOnSizeAllocate(&onSizeAllocate);
181 addOnButtonPress(&onButtonPress);
182 addOnButtonRelease(&onButtonRelease);
183 }
184
185 void onSizeAllocate(GtkAllocation* allocation, Widget widget)
186 {
187 width = allocation.width;
188 height = allocation.height;
189
190 surface = ImageSurface.create(CairoFormat.ARGB32, width, height);
191 drawPoints(Context.create(surface));
192 }
193
194 public bool onButtonPress(Event event, Widget widget)
195 {
196 debug(trace) writeln("button DOWN");
197 if ( event.type == EventType.BUTTON_PRESS && event.button.button == 1 )
198 {
199 debug(trace) writeln("Button 1 down");
200 buttonIsDown = true;
201
202 drawPrimitive(cast(int)event.button.x, cast(int)event.button.y);
203 }
204 return false;
205 }
206
207 public bool onButtonRelease(Event event, Widget widget)
208 {
209 debug(trace) writeln("button UP");
210 if ( event.type == EventType.BUTTON_RELEASE && event.button.button == 1 )
211 {
212 debug(trace) writeln("Button 1 UP");
213 buttonIsDown = false;
214 }
215 return false;
216 }
217
218 /**
219 * This will be called from the expose event call back.
220 * \bug this is called on get or loose focus - review
221 */
222 public bool drawCallback(Scoped!Context context, Widget widget)
223 {
224 //Fill the Widget with the surface we are drawing on.
225 context.setSourceSurface(surface, 0, 0);
226 context.paint();
227
228 return true;
229 }
230
231 public bool onMotionNotify(Event event, Widget widget)
232 {
233 //writeln("testWindow.mouseMoved -----------------------------");
234 if ( buttonIsDown && event.type == EventType.MOTION_NOTIFY )
235 {
236 drawPrimitive(cast(int)event.motion.x, cast(int)event.motion.y);
237 }
238
239 return true;
240 }
241
242 static int backSpinCount = 0;
243
244 public void backSpinChanged(SpinButton spinButton)
245 {
246
247 debug(trace) writefln("backSpinChanged - entry %s", ++backSpinCount);
248
249 drawPoints(Context.create(surface));
250 this.queueDraw();
251
252 debug(trace) writeln("backSpinChanged - exit");
253 }
254
255 public void sizeSpinChanged(SpinButton spinButton)
256 {
257 if ( !(scaledPixbuf is null))
258 {
259 int width = spinButton.getValueAsInt();
260 scaledPixbuf = image.getPixbuf();
261
262 float ww = width * scaledPixbuf.getWidth() / 30;
263 float hh = width * scaledPixbuf.getHeight() / 30;
264
265 scaledPixbuf = scaledPixbuf.scaleSimple(cast(int)ww, cast(int)hh, GdkInterpType.HYPER);
266 }
267 }
268
269 public void drawPrimitive(int x, int y)
270 {
271 int width = spin.getValueAsInt();
272 int height = width * 3 / 4;
273
274 Context context = Context.create(surface);
275 context.setOperator(operator);
276
277 debug(trace) writefln("primitiveType = %s", primitiveType);
278
279 switch ( primitiveType )
280 {
281 case "Arc":
282 context.arc(x-width/2,y-width/2,width,0,2*PI);
283 context.stroke();
284 break;
285
286 case "Filled Arc":
287 context.arc(x-width/4,y-width/4,width/2,0,2*PI);
288 context.fill();
289 break;
290
291 case "Line":
292 context.moveTo(x, y);
293 context.lineTo(x+width, y);
294 context.stroke();
295 break;
296
297 case "Point":
298 context.rectangle(x, y, 1, 1);
299 context.fill();
300 break;
301
302 case "Rectangle":
303 context.rectangle(x-width/2, y-width/4, width, height);
304 context.stroke();
305 break;
306
307 case "Filled Rectangle":
308 context.rectangle(x-width/2, y-width/4, width, height);
309 context.fill();
310 break;
311
312 case "Text":
313 context.selectFontFace("FreeMono", CairoFontSlant.NORMAL, CairoFontWeight.NORMAL);
314 context.setFontSize(12);
315 context.moveTo(x, y);
316 context.showText("gtkD toolkit");
317 break;
318
319 case "Pango text":
320 PgLayout l = PgCairo.createLayout(context);
321 PgFontDescription fd = new PgFontDescription("Sans", width);
322
323 l.setText("Gtk+ with D");
324 l.setFontDescription(fd);
325
326 context.moveTo(x, y);
327 PgCairo.showLayout(context, l);
328 break;
329
330 case "Image":
331 if ( !(scaledPixbuf is null))
332 {
333 context.setSourcePixbuf(scaledPixbuf, x, y);
334 context.paint();
335 }
336 break;
337
338 case "Polygon":
339 //TODO: Use Context.scale and transform ?
340 for ( int scale = 10 ; scale<= 300; scale+=15)
341 {
342 context.save();
343 context.moveTo(polygonStar[0].x*scale/2+x, polygonStar[0].y*scale/2+y);
344
345 foreach(p; polygonStar[1 .. $])
346 {
347 context.lineTo(p.x*scale/2+x, p.y*scale/2+y);
348 }
349 context.closePath();
350 context.stroke();
351 context.restore();
352 }
353 break;
354
355 default:
356 context.arcNegative(x-2,y-2,4,0,6);
357 context.fill();
358 break;
359 }
360
361 //Redraw the Widget.
362 this.queueDraw();
363 }
364
365 private void drawPoints(Context context)
366 {
367 int square = backSpin.getValueAsInt();
368 int totalcount = 0;
369 int count = 0;
370 Color color = new Color();
371 int width = this.width;
372 int height = this.height;
373 int x = 0;
374 int y = 0;
375
376 debug(trace) writefln("w,h = %s %s",width ,height);
377
378 float dx = 256.0 / width;
379 float dy = 256.0 / height ;
380 float xx;
381 float yy;
382 while ( x < width || y <height )
383 {
384 context.save();
385
386 xx = x * dx;
387 yy = y * dy;
388 context.setSourceRgba( xx / 255,
389 yy / 255,
390 sqrt((xx*xx)+(yy*yy)) / 255,
391 1 );
392
393 if ( square > 1 )
394 {
395 context.rectangle(x, y, square, square);
396 context.fill();
397 }
398 else
399 {
400 context.moveTo(x, y);
401 context.stroke();
402 }
403 x +=square;
404 if ( x > width)
405 {
406 if ( y>height)
407 {
408 //y=0;
409 }
410 else
411 {
412 x = 0;
413 y+=square;
414 }
415 }
416 ++totalcount;
417
418 context.restore();
419 }
420 color.destroy();
421 }
422
423 void onOperatorsChanged(ComboBoxText comboBoxText)
424 {
425 debug(trace) writefln("CairoOperator = %s", comboBoxText.getActiveText());
426 switch ( comboBoxText.getActiveText() )
427 {
428 case "CLEAR": operator = CairoOperator.CLEAR; break;
429 case "SOURCE": operator = CairoOperator.SOURCE; break;
430 case "OVER": operator = CairoOperator.OVER; break;
431 case "IN": operator = CairoOperator.IN; break;
432 case "OUT": operator = CairoOperator.OUT; break;
433 case "ATOP": operator = CairoOperator.ATOP; break;
434 case "DEST": operator = CairoOperator.DEST; break;
435 case "DEST_OVER": operator = CairoOperator.DEST_OVER; break;
436 case "DEST_IN": operator = CairoOperator.DEST_IN; break;
437 case "DEST_OUT": operator = CairoOperator.DEST_OUT; break;
438 case "DEST_ATOP": operator = CairoOperator.DEST_ATOP; break;
439 case "XOR": operator = CairoOperator.XOR; break;
440 case "ADD": operator = CairoOperator.ADD; break;
441 case "SATURATE": operator = CairoOperator.SATURATE; break;
442 case "MULTIPLY": operator = CairoOperator.MULTIPLY; break;
443 case "SCREEN": operator = CairoOperator.SCREEN; break;
444 case "OVERLAY": operator = CairoOperator.OVERLAY; break;
445 case "DARKEN": operator = CairoOperator.DARKEN; break;
446 case "LIGHTEN": operator = CairoOperator.LIGHTEN; break;
447 case "COLOR_DODGE": operator = CairoOperator.COLOR_DODGE; break;
448 case "COLOR_BURN": operator = CairoOperator.COLOR_BURN; break;
449 case "HARD_LIGHT": operator = CairoOperator.HARD_LIGHT; break;
450 case "SOFT_LIGHT": operator = CairoOperator.SOFT_LIGHT; break;
451 case "DIFFERENCE": operator = CairoOperator.DIFFERENCE; break;
452 case "EXCLUSION": operator = CairoOperator.EXCLUSION; break;
453 case "HSL_HUE": operator = CairoOperator.HSL_HUE; break;
454 case "HSL_SATURATION": operator = CairoOperator.HSL_SATURATION; break;
455 case "HSL_COLOR": operator = CairoOperator.HSL_COLOR; break;
456 case "HSL_LUMINOSITY": operator = CairoOperator.HSL_LUMINOSITY; break;
457 default: operator = CairoOperator.OVER; break;
458 }
459 }
460
461 void onPrimOptionChanged(ComboBoxText comboBoxText)
462 {
463 primitiveType = comboBoxText.getActiveText();
464 }
465 }
466 }
467