comparison doodle/gtk/canvas.d @ 70:0e61702c6ea6

Checkpoint
author "David Bryant <bagnose@gmail.com>"
date Sat, 14 Aug 2010 20:05:55 +0930
parents d540f7e4af9e
children 0f7cf6c6f206
comparison
equal deleted inserted replaced
69:d540f7e4af9e 70:0e61702c6ea6
35 35
36 import core.stdc.string : strlen; 36 import core.stdc.string : strlen;
37 } 37 }
38 38
39 // x and y run right and up respectively 39 // x and y run right and up respectively
40 //
41 // Model units are millimetres.
42 //
43 // _zoom -> pixels-per-model-unit
44 // _viewSize -> size of view window in pixels
45 // _viewCentre -> location in model corresponding to centre of view
46 // _canvasBounds -> size of the virtual canvas in model coordinates
47 //
48 // User operations:
49 // pan (middle click and drag)
50 // zoom about a point (hold control and move scroll wheel)
51 // resize the widget
52 40
53 final class Canvas : Table, private IViewport { 41 final class Canvas : Table, private IViewport {
54 this(in Layer[] layers, IEventHandler eventHandler, IGrid grid, in double pixelsPerMillimetre) { 42 this(in Layer[] layers, IEventHandler eventHandler, IGrid grid, in double pixelsPerMillimetre) {
55 super(3, 3, 0); 43 super(3, 3, 0);
56 44
211 } 199 }
212 } 200 }
213 201
214 private { 202 private {
215 203
204 Rectangle layerBounds() {
205 Rectangle bounds = Rectangle.DEFAULT;
206 foreach (layer; _layers) { bounds = bounds | layer.bounds; }
207 assert(bounds.valid);
208 return bounds;
209 }
210
216 void initialiseBounds() { 211 void initialiseBounds() {
217 Rectangle layerBounds = Rectangle.DEFAULT; 212 Rectangle lb = layerBounds;
218
219 foreach (layer; _layers) {
220 layerBounds = layerBounds | layer.bounds;
221 }
222
223 assert(layerBounds.valid);
224 213
225 // FIXME use a function that grows a rectangle about its centre 214 // FIXME use a function that grows a rectangle about its centre
226 // and change 2.0 to a class-level constant 215 // and change 2.0 to a class-level constant
227 Rectangle paddedLayerBounds = expand(move(layerBounds, - layerBounds.size), 2.0 * layerBounds.size); 216 Rectangle paddedLayerBounds = expand(move(lb, - lb.size), 2.0 * lb.size);
228 217
229 // 218 //
230 219
220 // FIXME 0.25
231 _zoom = 0.25 * _pixelsPerMillimetre; // ie 0.25 pixels represents a millimetre 221 _zoom = 0.25 * _pixelsPerMillimetre; // ie 0.25 pixels represents a millimetre
232 222
233 _canvasBounds = paddedLayerBounds; 223 _canvasBounds = paddedLayerBounds;
234 _viewCentre = _canvasBounds.centre; 224 _viewCentre = _canvasBounds.centre;
235 225
238 updateAdjustments; 228 updateAdjustments;
239 updateRulers; 229 updateRulers;
240 } 230 }
241 231
242 void consolidateBounds() { 232 void consolidateBounds() {
243 Rectangle layerBounds = Rectangle.DEFAULT; 233 Rectangle lb = layerBounds;
244 234
245 foreach (layer; _layers) { 235 // FIXME likewise as above
246 layerBounds = layerBounds | layer.bounds; 236 Rectangle paddedLayerBounds = expand(move(lb, - lb.size), 2.0 * lb.size);
247 } 237
248 238 Rectangle r = pixelToModel(_viewBounds);
249 assert(layerBounds.valid);
250
251 Rectangle paddedLayerBounds = expand(move(layerBounds, - layerBounds.size), 2.0 * layerBounds.size);
252
253 Vector z = _viewSize / _zoom;
254 Rectangle r = Rectangle(_viewCentre - z / 2.0, z);
255 _canvasBounds = r | paddedLayerBounds; 239 _canvasBounds = r | paddedLayerBounds;
256 240
257 updateAdjustments; 241 updateAdjustments;
258 updateRulers; 242 updateRulers;
259 } 243 }
260 244
261 bool onConfigure(GdkEventConfigure * event, Widget widget) { 245 bool onConfigure(GdkEventConfigure * event, Widget widget) {
262 assert(widget is _drawingArea); 246 assert(widget is _drawingArea);
263 247
264 _viewSize = Vector(cast(double)event.width, cast(double)event.height); 248 _viewBounds = Rectangle(Point(0.0, 0.0), Vector(cast(double)event.width, cast(double)event.height));
265 249
266 if (!_boundsValid) { 250 if (!_boundsValid) {
267 initialiseBounds; 251 initialiseBounds;
268 _boundsValid = true; 252 _boundsValid = true;
269 } 253 }
286 scope modelCr = new Context(dr); 270 scope modelCr = new Context(dr);
287 scope pixelCr = new Context(dr); 271 scope pixelCr = new Context(dr);
288 272
289 Rectangle pixelDamage = 273 Rectangle pixelDamage =
290 event is null ? 274 event is null ?
291 Rectangle(Point(0.0, 0.0), _viewSize) : 275 _viewBounds : // XXX can we do something nice with the next line?
292 Rectangle(Point(cast(double)event.area.x, _viewSize.y - cast(double)(event.area.y + event.area.height)), 276 Rectangle(_viewBounds.position + Vector(cast(double)event.area.x, _viewBounds.h - cast(double)(event.area.y + event.area.height)),
293 Vector(cast(double)event.area.width, cast(double)event.area.height)); 277 Vector(cast(double)event.area.width, cast(double)event.area.height));
294 278
295 Rectangle modelDamage = pixelToModel(pixelDamage); 279 Rectangle modelDamage = pixelToModel(pixelDamage);
296 280
297 //trace("Pixel damage: %s, model damage: %s", pixelDamage, modelDamage); 281 //trace("Pixel damage: %s, model damage: %s", pixelDamage, modelDamage);
298 282
299 modelCr.save; pixelCr.save; { 283 modelCr.save; pixelCr.save; {
300 { 284 {
301 // Setup model context and clip 285 // Setup model context and clip
302 modelCr.translate(0.0, _viewSize.y); 286 modelCr.translate(0.0, _viewBounds.h);
303 modelCr.scale(_zoom, -_zoom); 287 modelCr.scale(_zoom, -_zoom);
304 288
305 immutable Vector modelSize = pixelToModel(_viewSize); 289 // XXX revisit
290 immutable Vector modelSize = pixelToModel(_viewBounds.size);
306 immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0; 291 immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0;
307 modelCr.translate(-viewLeftBottom.x, -viewLeftBottom.y); 292 modelCr.translate(-viewLeftBottom.x, -viewLeftBottom.y);
308 293
309 rectangle(modelCr, modelDamage); 294 rectangle(modelCr, modelDamage);
310 modelCr.clip; 295 modelCr.clip;
311 } 296 }
312 297
313 { 298 {
314 // Setup pixel context and clip 299 // Setup pixel context and clip
315 pixelCr.translate(0.0, _viewSize.y); 300 pixelCr.translate(0.0, _viewBounds.h);
316 pixelCr.scale(1.0, -1.0); 301 pixelCr.scale(1.0, -1.0);
317 302
318 rectangle(pixelCr, pixelDamage); 303 rectangle(pixelCr, pixelDamage);
319 pixelCr.clip; 304 pixelCr.clip;
320 } 305 }
337 322
338 bool onButtonPress(GdkEventButton * event, Widget widget) { 323 bool onButtonPress(GdkEventButton * event, Widget widget) {
339 assert(widget is _drawingArea); 324 assert(widget is _drawingArea);
340 //trace("Got button event\n"); 325 //trace("Got button event\n");
341 326
342 Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); 327 Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
343 Point modelPoint = pixelToModel(pixelPoint); 328 Point modelPoint = pixelToModel(pixelPoint);
344 329
345 auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type), 330 auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type),
346 gtk2tkButtonName(event.button), 331 gtk2tkButtonName(event.button),
347 pixelPoint, 332 pixelPoint,
356 } 341 }
357 342
358 bool onButtonRelease(GdkEventButton * event, Widget widget) { 343 bool onButtonRelease(GdkEventButton * event, Widget widget) {
359 assert(widget is _drawingArea); 344 assert(widget is _drawingArea);
360 345
361 Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); 346 Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
362 Point modelPoint = pixelToModel(pixelPoint); 347 Point modelPoint = pixelToModel(pixelPoint);
363 348
364 auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type), 349 auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type),
365 gtk2tkButtonName(event.button), 350 gtk2tkButtonName(event.button),
366 pixelPoint, 351 pixelPoint,
422 assert(widget is _drawingArea); 407 assert(widget is _drawingArea);
423 //writefln("Got motion notify\n"); 408 //writefln("Got motion notify\n");
424 gtk_widget_event(_hRuler.getWidgetStruct(), cast(GdkEvent *)event); 409 gtk_widget_event(_hRuler.getWidgetStruct(), cast(GdkEvent *)event);
425 gtk_widget_event(_vRuler.getWidgetStruct(), cast(GdkEvent *)event); 410 gtk_widget_event(_vRuler.getWidgetStruct(), cast(GdkEvent *)event);
426 411
427 Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); 412 Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
428 Point modelPoint = pixelToModel(pixelPoint); 413 Point modelPoint = pixelToModel(pixelPoint);
429 414
430 auto motionEvent = new MotionEvent(pixelPoint, 415 auto motionEvent = new MotionEvent(pixelPoint,
431 modelPoint, 416 modelPoint,
432 gtk2tkMask(event.state)); 417 gtk2tkMask(event.state));
440 425
441 bool onScroll(GdkEventScroll * event, Widget widget) { 426 bool onScroll(GdkEventScroll * event, Widget widget) {
442 assert(widget is _drawingArea); 427 assert(widget is _drawingArea);
443 //writefln("Got scroll\n"); 428 //writefln("Got scroll\n");
444 429
445 Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); 430 Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
446 Point modelPoint = pixelToModel(pixelPoint); 431 Point modelPoint = pixelToModel(pixelPoint);
447 432
448 auto scrollEvent = new ScrollEvent(gtk2tkDirection(event.direction), 433 auto scrollEvent = new ScrollEvent(gtk2tkDirection(event.direction),
449 pixelPoint, 434 pixelPoint,
450 modelPoint, 435 modelPoint,
485 */ 470 */
486 471
487 bool onEnterNotify(GdkEventCrossing * event, Widget widget) { 472 bool onEnterNotify(GdkEventCrossing * event, Widget widget) {
488 assert(widget is _drawingArea); 473 assert(widget is _drawingArea);
489 474
490 Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); 475 Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
491 Point modelPoint = pixelToModel(pixelPoint); 476 Point modelPoint = pixelToModel(pixelPoint);
492 477
493 auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode), 478 auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode),
494 pixelPoint, 479 pixelPoint,
495 modelPoint, 480 modelPoint,
505 } 490 }
506 491
507 bool onLeaveNotify(GdkEventCrossing * event, Widget widget) { 492 bool onLeaveNotify(GdkEventCrossing * event, Widget widget) {
508 assert(widget is _drawingArea); 493 assert(widget is _drawingArea);
509 494
510 Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); 495 Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
511 Point modelPoint = pixelToModel(pixelPoint); 496 Point modelPoint = pixelToModel(pixelPoint);
512 497
513 auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode), 498 auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode),
514 pixelPoint, 499 pixelPoint,
515 modelPoint, 500 modelPoint,
564 GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct; 549 GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct;
565 550
566 Point viewLeftBottom = Point(gtk_adjustment_get_value(hGtkAdjustment), 551 Point viewLeftBottom = Point(gtk_adjustment_get_value(hGtkAdjustment),
567 gtk_adjustment_get_value(vGtkAdjustment)); 552 gtk_adjustment_get_value(vGtkAdjustment));
568 553
569 Vector modelSize = pixelToModel(_viewSize); 554 Vector modelSize = pixelToModel(_viewBounds.size); // XXX
570 555
571 _viewCentre = viewLeftBottom + modelSize / 2.0; 556 _viewCentre = viewLeftBottom + modelSize / 2.0;
572 557
573 updateRulers; 558 updateRulers;
574 queueDraw; 559 queueDraw;
575 } 560 }
576 561
577 void updateRulers() { 562 void updateRulers() {
578 immutable Vector modelSize = pixelToModel(_viewSize); 563 immutable Vector modelSize = pixelToModel(_viewBounds.size); // XXX
579 immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0; 564 immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0;
580 immutable Point viewRightTop = _viewCentre + modelSize / 2.0; 565 immutable Point viewRightTop = _viewCentre + modelSize / 2.0;
581 566
582 // Define these just to obtain the position 567 // Define these just to obtain the position
583 // below and we can preserve it 568 // below and we can preserve it
595 position, 580 position,
596 _zoom * 50.0); 581 _zoom * 50.0);
597 } 582 }
598 583
599 void updateAdjustments() { 584 void updateAdjustments() {
600 immutable Vector modelSize = pixelToModel(_viewSize); 585 immutable Vector modelSize = pixelToModel(_viewBounds.size); // XXX
601 immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0; 586 immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0;
602 immutable Point viewRightTop = _viewCentre + modelSize / 2.0; 587 immutable Point viewRightTop = _viewCentre + modelSize / 2.0;
603 588
604 // Adjust the canvas size if necessary 589 // Adjust the canvas size if necessary
605 _canvasBounds = Rectangle(minExtents(_canvasBounds.minCorner, viewLeftBottom), 590 _canvasBounds = Rectangle(minExtents(_canvasBounds.corner0, viewLeftBottom),
606 maxExtents(_canvasBounds.maxCorner, viewRightTop)); 591 maxExtents(_canvasBounds.corner1, viewRightTop));
607 592
608 // Update the adjustments 593 // Update the adjustments
609 594
610 GtkAdjustment * hGtkAdjustment = _hAdjustment.getAdjustmentStruct; 595 GtkAdjustment * hGtkAdjustment = _hAdjustment.getAdjustmentStruct;
611 GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct; 596 GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct;
612 597
613 gtk_adjustment_set_lower(hGtkAdjustment, _canvasBounds.minCorner.x); 598 gtk_adjustment_set_lower(hGtkAdjustment, _canvasBounds.corner0.x);
614 gtk_adjustment_set_upper(hGtkAdjustment, _canvasBounds.maxCorner.x); 599 gtk_adjustment_set_upper(hGtkAdjustment, _canvasBounds.corner1.x);
615 gtk_adjustment_set_value(hGtkAdjustment, viewLeftBottom.x); 600 gtk_adjustment_set_value(hGtkAdjustment, viewLeftBottom.x);
616 gtk_adjustment_set_step_increment(hGtkAdjustment, _canvasBounds.size.x / 16.0); 601 gtk_adjustment_set_step_increment(hGtkAdjustment, _canvasBounds.size.x / 16.0);
617 gtk_adjustment_set_page_increment(hGtkAdjustment, _canvasBounds.size.x / 4.0); 602 gtk_adjustment_set_page_increment(hGtkAdjustment, _canvasBounds.size.x / 4.0);
618 gtk_adjustment_set_page_size(hGtkAdjustment, modelSize.x); 603 gtk_adjustment_set_page_size(hGtkAdjustment, modelSize.x);
619 604
620 gtk_adjustment_set_lower(vGtkAdjustment, _canvasBounds.minCorner.y); 605 gtk_adjustment_set_lower(vGtkAdjustment, _canvasBounds.corner0.y);
621 gtk_adjustment_set_upper(vGtkAdjustment, _canvasBounds.maxCorner.y); 606 gtk_adjustment_set_upper(vGtkAdjustment, _canvasBounds.corner1.y);
622 gtk_adjustment_set_value(vGtkAdjustment, viewLeftBottom.y); 607 gtk_adjustment_set_value(vGtkAdjustment, viewLeftBottom.y);
623 gtk_adjustment_set_step_increment(vGtkAdjustment, _canvasBounds.size.y / 16.0); 608 gtk_adjustment_set_step_increment(vGtkAdjustment, _canvasBounds.size.y / 16.0);
624 gtk_adjustment_set_page_increment(vGtkAdjustment, _canvasBounds.size.y / 4.0); 609 gtk_adjustment_set_page_increment(vGtkAdjustment, _canvasBounds.size.y / 4.0);
625 gtk_adjustment_set_page_size(vGtkAdjustment, modelSize.y); 610 gtk_adjustment_set_page_size(vGtkAdjustment, modelSize.y);
626 611
632 617
633 void fixDamage() { 618 void fixDamage() {
634 if (_damage.valid) { 619 if (_damage.valid) {
635 int x, y, w, h; 620 int x, y, w, h;
636 _damage.getQuantised(x, y, w, h); 621 _damage.getQuantised(x, y, w, h);
637 _drawingArea.queueDrawArea(x, cast(int)_viewSize.y - (y + h), w, h); 622 _drawingArea.queueDrawArea(x, cast(int)_viewBounds.h - (y + h), w, h);
638 _damage = Rectangle.DEFAULT; 623 _damage = Rectangle.DEFAULT;
639 } 624 }
640 } 625 }
641 626
642 static double clampZoom(in double zoom) { return clamp(zoom, 0.2, 10.0); } 627 static double clampZoom(in double zoom) { return clamp(zoom, 0.2, 10.0); }
643 628
644 Point modelToPixel(in Point model) const { 629 Point modelToPixel(in Point model) const {
645 return Point.DEFAULT + _viewSize / 2.0 + _zoom * (model - _viewCentre); 630 return _viewBounds.centre + _zoom * (model - _viewCentre);
646 } 631 }
647 632
648 Point pixelToModel(in Point pixel) const { 633 Point pixelToModel(in Point pixel) const {
649 return _viewCentre + (pixel - _viewSize / 2.0 - Point.DEFAULT) / _zoom; 634 return _viewCentre + (pixel - _viewBounds.centre) / _zoom;
650 } 635 }
651 636
652 Vector modelToPixel(in Vector model) const { 637 Vector modelToPixel(in Vector model) const {
653 return _zoom * model; 638 return _zoom * model;
654 } 639 }
675 Rectangle _damage; // pixels 660 Rectangle _damage; // pixels
676 661
677 // Model units are millimetres 662 // Model units are millimetres
678 // Screen units are pixels 663 // Screen units are pixels
679 double _zoom; // pixels-per-model-unit 664 double _zoom; // pixels-per-model-unit
680 Vector _viewSize; // pixel: size of view window in pixels 665 Rectangle _viewBounds; // pixel: bounds of the viewport in pixels
681 Point _viewCentre; // model: where in the model is the centre of our view 666 Point _viewCentre; // model: where in the model is the centre of our view
682 Rectangle _canvasBounds; // model: 667 Rectangle _canvasBounds; // model: bounds of the canvas in millimetres
683 668
684 // Child widgets: 669 // Child widgets:
685 HRuler _hRuler; 670 HRuler _hRuler;
686 VRuler _vRuler; 671 VRuler _vRuler;
687 DrawingArea _drawingArea; 672 DrawingArea _drawingArea;