changeset 71:0f7cf6c6f206

Reimplemented gtk.canvas in terms of tk.pixel_model but needs a lot of consolidation.
author "David Bryant <bagnose@gmail.com>"
date Sat, 14 Aug 2010 20:48:41 +0930
parents 0e61702c6ea6
children 5cc2de64f6d0
files doodle/gtk/canvas.d doodle/tk/pixel_model.d
diffstat 2 files changed, 98 insertions(+), 112 deletions(-) [+]
line wrap: on
line diff
--- a/doodle/gtk/canvas.d	Sat Aug 14 20:05:55 2010 +0930
+++ b/doodle/gtk/canvas.d	Sat Aug 14 20:48:41 2010 +0930
@@ -10,6 +10,7 @@
     import doodle.core.logging;
     import doodle.cairo.routines;
     import doodle.gtk.conversions;
+    import doodle.tk.pixel_model;
 
     import cairo.Surface;
 
@@ -36,8 +37,6 @@
     import core.stdc.string : strlen;
 }
 
-// x and y run right and up respectively
-
 final class Canvas : Table, private IViewport {
     this(in Layer[] layers, IEventHandler eventHandler, IGrid grid, in double pixelsPerMillimetre) {
         super(3, 3, 0);
@@ -140,25 +139,18 @@
         // IViewport overrides:
 
         void zoomRelative(in Point pixelDatum, in double factor) {
-            // Work out pixel distance from current centre to datum,
-            // Do the zoom, then work out the new centre that keeps the
-            // pixel distance the same
-
-            Point oldModelDatum = pixelToModel(pixelDatum);
-            Vector pixelDistance = modelToPixel(oldModelDatum - _viewCentre);
-            _zoom = clampZoom(factor * _zoom);
-            _viewCentre = oldModelDatum - pixelToModel(pixelDistance);
+            _pixelModel.zoomRelative(factor, pixelDatum);
 
             consolidateBounds;
 
             updateAdjustments;
             updateRulers;
-            _grid.zoomChanged(_zoom);
+            _grid.zoomChanged(_pixelModel.zoom);
             queueDraw;
         }
 
         void panRelative(in Vector pixelDisplacement) {
-            _viewCentre = _viewCentre + pixelToModel(pixelDisplacement);
+            _pixelModel.panRelativePixel(pixelDisplacement);
 
             consolidateBounds;
 
@@ -191,7 +183,7 @@
         }
 
         void damageModel(in Rectangle area) {
-            _damage = _damage | modelToPixel(area);
+            _damage = _damage | _pixelModel.modelToPixel(area);
         }
 
         void damagePixel(in Rectangle area) {
@@ -208,22 +200,16 @@
             return bounds;
         }
 
-        void initialiseBounds() {
+        void initialiseBounds(in Rectangle viewBounds) {
             Rectangle lb = layerBounds;
 
             // FIXME use a function that grows a rectangle about its centre
             // and change 2.0 to a class-level constant
             Rectangle paddedLayerBounds = expand(move(lb, - lb.size), 2.0 * lb.size);
 
-            //
-
-            // FIXME 0.25
-            _zoom = 0.25 * _pixelsPerMillimetre;       // ie 0.25 pixels represents a millimetre
+            _pixelModel = new PixelModel(0.25 * _pixelsPerMillimetre, paddedLayerBounds, viewBounds);
 
-            _canvasBounds = paddedLayerBounds;
-            _viewCentre = _canvasBounds.centre;
-
-            _grid.zoomChanged(_zoom);
+            _grid.zoomChanged(_pixelModel.zoom);
 
             updateAdjustments;
             updateRulers;
@@ -235,8 +221,7 @@
             // FIXME likewise as above
             Rectangle paddedLayerBounds = expand(move(lb, - lb.size), 2.0 * lb.size);
 
-            Rectangle r = pixelToModel(_viewBounds);
-            _canvasBounds = r | paddedLayerBounds;
+            _pixelModel.consolidateCanvasBounds(paddedLayerBounds);
 
             updateAdjustments;
             updateRulers;
@@ -245,11 +230,10 @@
         bool onConfigure(GdkEventConfigure * event, Widget widget) {
             assert(widget is _drawingArea);
 
-            _viewBounds = Rectangle(Point(0.0, 0.0), Vector(cast(double)event.width, cast(double)event.height));
+            Rectangle viewBounds = Rectangle(Point(0.0, 0.0), Vector(cast(double)event.width, cast(double)event.height));
 
-            if (!_boundsValid) {
-                initialiseBounds;
-                _boundsValid = true;
+            if (_pixelModel is null) {
+                initialiseBounds(viewBounds);
             }
             else {
                 consolidateBounds;
@@ -271,24 +255,22 @@
             scope pixelCr = new Context(dr);
 
             Rectangle pixelDamage =
-                event is null ?
-                _viewBounds :       // XXX can we do something nice with the next line?
-                Rectangle(_viewBounds.position + Vector(cast(double)event.area.x, _viewBounds.h - cast(double)(event.area.y + event.area.height)),
+                event is null ? _pixelModel.viewBounds :
+                // FIXME next line sucks
+                Rectangle(_pixelModel.viewBounds.position + Vector(cast(double)event.area.x, _pixelModel.viewBounds.h - cast(double)(event.area.y + event.area.height)),
                           Vector(cast(double)event.area.width, cast(double)event.area.height));
 
-            Rectangle modelDamage = pixelToModel(pixelDamage);
+            Rectangle modelDamage = _pixelModel.pixelToModel(pixelDamage);
 
             //trace("Pixel damage: %s, model damage: %s", pixelDamage, modelDamage);
 
             modelCr.save; pixelCr.save; {
                 {
                     // Setup model context and clip
-                    modelCr.translate(0.0, _viewBounds.h);
-                    modelCr.scale(_zoom, -_zoom);
+                    modelCr.translate(0.0, _pixelModel.viewBounds.h);
+                    modelCr.scale(_pixelModel.zoom, -_pixelModel.zoom);
 
-                    // XXX revisit
-                    immutable Vector modelSize = pixelToModel(_viewBounds.size);
-                    immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0;
+                    immutable Point viewLeftBottom = _pixelModel.pixelToModel(Point(0.0, 0.0));
                     modelCr.translate(-viewLeftBottom.x, -viewLeftBottom.y);
 
                     rectangle(modelCr, modelDamage);
@@ -297,7 +279,7 @@
 
                 {
                     // Setup pixel context and clip
-                    pixelCr.translate(0.0, _viewBounds.h);
+                    pixelCr.translate(0.0, _pixelModel.viewBounds.h);
                     pixelCr.scale(1.0, -1.0);
 
                     rectangle(pixelCr, pixelDamage);
@@ -324,8 +306,8 @@
             assert(widget is _drawingArea);
             //trace("Got button event\n");
 
-            Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
-            Point modelPoint = pixelToModel(pixelPoint);
+            Point pixelPoint = Point(event.x + 0.5, _pixelModel.viewBounds.h - (event.y + 0.5));
+            Point modelPoint = _pixelModel.pixelToModel(pixelPoint);
 
             auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type),
                                                gtk2tkButtonName(event.button),
@@ -343,8 +325,8 @@
         bool onButtonRelease(GdkEventButton * event, Widget widget) {
             assert(widget is _drawingArea);
 
-            Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
-            Point modelPoint = pixelToModel(pixelPoint);
+            Point pixelPoint = Point(event.x + 0.5, _pixelModel.viewBounds.h - (event.y + 0.5));
+            Point modelPoint = _pixelModel.pixelToModel(pixelPoint);
 
             auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type),
                                                gtk2tkButtonName(event.button),
@@ -409,8 +391,8 @@
             gtk_widget_event(_hRuler.getWidgetStruct(), cast(GdkEvent *)event);
             gtk_widget_event(_vRuler.getWidgetStruct(), cast(GdkEvent *)event);
 
-            Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
-            Point modelPoint = pixelToModel(pixelPoint);
+            Point pixelPoint = Point(event.x + 0.5, _pixelModel.viewBounds.h - (event.y + 0.5));
+            Point modelPoint = _pixelModel.pixelToModel(pixelPoint);
 
             auto motionEvent = new MotionEvent(pixelPoint,
                                                modelPoint,
@@ -427,8 +409,8 @@
             assert(widget is _drawingArea);
             //writefln("Got scroll\n");
 
-            Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
-            Point modelPoint = pixelToModel(pixelPoint);
+            Point pixelPoint = Point(event.x + 0.5, _pixelModel.viewBounds.h - (event.y + 0.5));
+            Point modelPoint = _pixelModel.pixelToModel(pixelPoint);
 
             auto scrollEvent = new ScrollEvent(gtk2tkDirection(event.direction),
                                                pixelPoint,
@@ -472,8 +454,8 @@
         bool onEnterNotify(GdkEventCrossing * event, Widget widget) {
             assert(widget is _drawingArea);
 
-            Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
-            Point modelPoint = pixelToModel(pixelPoint);
+            Point pixelPoint = Point(event.x + 0.5, _pixelModel.viewBounds.h - (event.y + 0.5));
+            Point modelPoint = _pixelModel.pixelToModel(pixelPoint);
 
             auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode),
                                                    pixelPoint,
@@ -492,8 +474,8 @@
         bool onLeaveNotify(GdkEventCrossing * event, Widget widget) {
             assert(widget is _drawingArea);
 
-            Point pixelPoint = Point(event.x + 0.5, _viewBounds.h - (event.y + 0.5));
-            Point modelPoint = pixelToModel(pixelPoint);
+            Point pixelPoint = Point(event.x + 0.5, _pixelModel.viewBounds.h - (event.y + 0.5));
+            Point modelPoint = _pixelModel.pixelToModel(pixelPoint);
 
             auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode),
                                                    pixelPoint,
@@ -548,21 +530,19 @@
             GtkAdjustment * hGtkAdjustment = _hAdjustment.getAdjustmentStruct;
             GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct;
 
-            Point viewLeftBottom = Point(gtk_adjustment_get_value(hGtkAdjustment),
-                                         gtk_adjustment_get_value(vGtkAdjustment));
+            Point oldViewLeftBottom = _pixelModel.pixelToModel(Point(0.0, 0.0));
+            Point newViewLeftBottom = Point(gtk_adjustment_get_value(hGtkAdjustment),
+                                            gtk_adjustment_get_value(vGtkAdjustment));
 
-            Vector modelSize = pixelToModel(_viewBounds.size);      // XXX
-
-            _viewCentre = viewLeftBottom + modelSize / 2.0;
+            _pixelModel.panRelativeModel(newViewLeftBottom - oldViewLeftBottom);
 
             updateRulers;
             queueDraw;
         }
 
         void updateRulers() {
-            immutable Vector modelSize = pixelToModel(_viewBounds.size);        // XXX
-            immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0;
-            immutable Point viewRightTop = _viewCentre + modelSize / 2.0;
+            immutable Point viewLeftBottom = _pixelModel.pixelToModel(Point(0.0, 0.0));
+            immutable Point viewRightTop = _pixelModel.pixelToModel(_pixelModel.viewBounds.corner1);
 
             // Define these just to obtain the position
             // below and we can preserve it
@@ -572,42 +552,43 @@
             _hRuler.setRange(viewLeftBottom.x,
                              viewRightTop.x,
                              position,
-                             _zoom * 50.0);
+                             _pixelModel.zoom * 50.0);
 
             _vRuler.getRange(lower, upper, position, maxSize);
             _vRuler.setRange(viewRightTop.y,
                              viewLeftBottom.y,
                              position,
-                             _zoom * 50.0);
+                             _pixelModel.zoom * 50.0);
         }
 
         void updateAdjustments() {
-            immutable Vector modelSize = pixelToModel(_viewBounds.size);        // XXX
-            immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0;
-            immutable Point viewRightTop = _viewCentre + modelSize / 2.0;
+            immutable Point viewLeftBottom = _pixelModel.pixelToModel(Point(0.0, 0.0));
+            immutable Point viewRightTop = _pixelModel.pixelToModel(_pixelModel.viewBounds.corner1);
 
-            // Adjust the canvas size if necessary
-            _canvasBounds = Rectangle(minExtents(_canvasBounds.corner0, viewLeftBottom),
-                                      maxExtents(_canvasBounds.corner1, viewRightTop));
+            // Adjust the canvas size if necessary FIXME is this required??
+            _pixelModel.canvasAccommodate(Rectangle(viewLeftBottom, viewRightTop));
+
+            // FIXME
+            Rectangle modelSize = _pixelModel.pixelToModel(_pixelModel.viewBounds);
 
             // Update the adjustments
 
             GtkAdjustment * hGtkAdjustment = _hAdjustment.getAdjustmentStruct;
             GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct;
 
-            gtk_adjustment_set_lower(hGtkAdjustment, _canvasBounds.corner0.x);
-            gtk_adjustment_set_upper(hGtkAdjustment, _canvasBounds.corner1.x);
+            gtk_adjustment_set_lower(hGtkAdjustment, _pixelModel.canvasBounds.x0);
+            gtk_adjustment_set_upper(hGtkAdjustment, _pixelModel.canvasBounds.x1);
             gtk_adjustment_set_value(hGtkAdjustment, viewLeftBottom.x);
-            gtk_adjustment_set_step_increment(hGtkAdjustment, _canvasBounds.size.x / 16.0);
-            gtk_adjustment_set_page_increment(hGtkAdjustment, _canvasBounds.size.x / 4.0);
-            gtk_adjustment_set_page_size(hGtkAdjustment, modelSize.x);
+            gtk_adjustment_set_step_increment(hGtkAdjustment, _pixelModel.canvasBounds.w / 16.0);
+            gtk_adjustment_set_page_increment(hGtkAdjustment, _pixelModel.canvasBounds.w / 4.0);
+            gtk_adjustment_set_page_size(hGtkAdjustment, modelSize.w);
 
-            gtk_adjustment_set_lower(vGtkAdjustment, _canvasBounds.corner0.y);
-            gtk_adjustment_set_upper(vGtkAdjustment, _canvasBounds.corner1.y);
+            gtk_adjustment_set_lower(vGtkAdjustment, _pixelModel.canvasBounds.y0);
+            gtk_adjustment_set_upper(vGtkAdjustment, _pixelModel.canvasBounds.y1);
             gtk_adjustment_set_value(vGtkAdjustment, viewLeftBottom.y);
-            gtk_adjustment_set_step_increment(vGtkAdjustment, _canvasBounds.size.y / 16.0);
-            gtk_adjustment_set_page_increment(vGtkAdjustment, _canvasBounds.size.y / 4.0);
-            gtk_adjustment_set_page_size(vGtkAdjustment, modelSize.y);
+            gtk_adjustment_set_step_increment(vGtkAdjustment, _pixelModel.canvasBounds.h / 16.0);
+            gtk_adjustment_set_page_increment(vGtkAdjustment, _pixelModel.canvasBounds.h / 4.0);
+            gtk_adjustment_set_page_size(vGtkAdjustment, modelSize.h);
 
             _hAdjustment.changed;
             _hAdjustment.valueChanged;
@@ -619,52 +600,19 @@
             if (_damage.valid) {
                 int x, y, w, h;
                 _damage.getQuantised(x, y, w, h);
-                _drawingArea.queueDrawArea(x, cast(int)_viewBounds.h - (y + h), w, h);
+                _drawingArea.queueDrawArea(x, cast(int)_pixelModel.viewBounds.h - (y + h), w, h);
                 _damage = Rectangle.DEFAULT;
             }
         }
 
-        static double clampZoom(in double zoom) { return clamp(zoom, 0.2, 10.0); }
-
-        Point modelToPixel(in Point model) const {
-            return _viewBounds.centre + _zoom * (model - _viewCentre);
-        }
-
-        Point pixelToModel(in Point pixel) const {
-            return _viewCentre + (pixel - _viewBounds.centre) / _zoom;
-        }
-
-        Vector modelToPixel(in Vector model) const {
-            return _zoom * model;
-        }
-
-        Vector pixelToModel(in Vector pixel) const {
-            return pixel / _zoom;
-        }
-
-        Rectangle modelToPixel(in Rectangle model) const {
-            return Rectangle(modelToPixel(model.position), modelToPixel(model.size));
-        }
-
-        Rectangle pixelToModel(in Rectangle model) const {
-            return Rectangle(pixelToModel(model.position), pixelToModel(model.size));
-        }
-
         void onRealize(Widget widget) {
             assert(widget is _drawingArea);
             //writefln("Got realize\n");
             _drawingArea.grabFocus();
         }
 
-        bool _boundsValid;
         Rectangle _damage;          // pixels
-
-        // Model units are millimetres
-        // Screen units are pixels
-        double _zoom;               // pixels-per-model-unit
-        Rectangle _viewBounds;      // pixel: bounds of the viewport in pixels
-        Point _viewCentre;          // model: where in the model is the centre of our view
-        Rectangle _canvasBounds;    // model: bounds of the canvas in millimetres
+        PixelModel _pixelModel;
 
         // Child widgets:
         HRuler _hRuler;
--- a/doodle/tk/pixel_model.d	Sat Aug 14 20:05:55 2010 +0930
+++ b/doodle/tk/pixel_model.d	Sat Aug 14 20:48:41 2010 +0930
@@ -4,14 +4,24 @@
     import doodle.tk.geometry;
 }
 
+private {
+    import doodle.core.misc;
+}
+
 // FIXME consider using the term Screen instead of Pixel...
 
+// This class manages the relationship between pixel space and model space.
+// It provides convenient high-level operations.
+//
+// x and y run right and up respectively for pixel and model space
+
 class PixelModel {
     this(in double zoom, in Rectangle canvasBounds, in Rectangle viewBounds) {
         _zoom = zoom;
         _viewBounds = viewBounds;
         _canvasBounds = canvasBounds;
 
+        // Choose the centre of the canvas as the centre of the view
         _viewCentre = _canvasBounds.centre;
     }
 
@@ -20,9 +30,35 @@
         _canvasBounds = r | requiredCanvasBounds;
     }
 
+    void canvasAccommodate(in Rectangle bounds) {
+        _canvasBounds = _canvasBounds | bounds;
+    }
+
+    void zoomRelative(in double factor, in Point pixelDatum) {
+        // Work out pixel distance from current centre to datum,
+        // Do the zoom, then work out the new centre that keeps the
+        // pixel distance the same
+
+        Point oldModelDatum = pixelToModel(pixelDatum);
+        Vector pixelDistance = modelToPixel(oldModelDatum - _viewCentre);
+        _zoom = clampZoom(zoom * factor);
+        _viewCentre = oldModelDatum - pixelToModel(pixelDistance);
+    }
+
+    void panRelativePixel(in Vector pixelDisplacement) {
+        _viewCentre = _viewCentre + pixelToModel(pixelDisplacement);
+    }
+
+    void panRelativeModel(in Vector modelDisplacement) {
+        _viewCentre = _viewCentre + modelDisplacement;
+    }
+
     // For normalZoom 1.0 -> 100% means the presentation on the screen is
     // one-to-one with real-life
-    double normalZoom(in double pixelsPerMillimetre) { return _zoom / pixelsPerMillimetre; }
+    double normalZoom(in double pixelsPerMillimetre) const { return _zoom / pixelsPerMillimetre; }
+    double zoom() const { return _zoom; }
+    Rectangle viewBounds() const { return _viewBounds; }
+    Rectangle canvasBounds() const { return _canvasBounds; }
 
     Point modelToPixel(in Point model) const { return _viewBounds.centre + _zoom * (model - _viewCentre); }
     Point pixelToModel(in Point pixel) const { return _viewCentre + (pixel - _viewBounds.centre) / _zoom; }
@@ -32,6 +68,8 @@
     Rectangle pixelToModel(in Rectangle model) const { return Rectangle(pixelToModel(model.position), pixelToModel(model.size)); }
 
     private {
+        static double clampZoom(in double zoom) { return clamp(zoom, 0.1, 10.0); }
+
         // Screen units are pixels
         // Model units are millimetres
         double _zoom;               // pixels-per-millimetre