Mercurial > projects > doodle
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; |