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 delete color; 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