diff doodle/tk/screen_model.d @ 81:d92b9f04b1e8

Bed time
author "David Bryant <bagnose@gmail.com>"
date Mon, 16 Aug 2010 00:04:27 +0930
parents doodle/tk/pixel_model.d@535bae7a7305
children 100dd23c7bdf
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doodle/tk/screen_model.d	Mon Aug 16 00:04:27 2010 +0930
@@ -0,0 +1,69 @@
+module doodle.tk.screen_model;
+
+public {
+    import doodle.tk.geometry;
+}
+
+private {
+    import doodle.core.misc;
+}
+
+// This class manages the relationship between screen space and model space.
+// Screen is defined as the current window/viewport into the model
+// It provides convenient high-level operations.
+//
+// x and y run right and up respectively for screen and model space
+
+class ScreenModel {
+    this(in double zoom, in Rectangle canvasBoundsModel, in Rectangle viewBoundsScreen) {
+        _zoom = zoom;
+        _viewBoundsScreen = viewBoundsScreen;
+        _canvasBoundsModel = canvasBoundsModel;
+
+        // Choose the centre of the canvas as the centre of the view
+        _viewCentreModel = _canvasBoundsModel.centre;
+    }
+
+    void consolidateCanvasBounds(in Rectangle requiredCanvasBounds) { _canvasBoundsModel = screenToModel(_viewBoundsScreen) | requiredCanvasBounds; }
+    void canvasAccommodate(in Rectangle bounds) { _canvasBoundsModel = _canvasBoundsModel | bounds; }
+
+    void zoomRelative(in double factor, in Point screenDatum) {
+        // Work out screen distance from current centre to datum,
+        // Do the zoom, then work out the new centre that keeps the
+        // screen distance the same
+
+        Point oldModelDatum = screenToModel(screenDatum);
+        Vector screenDistance = modelToScreen(oldModelDatum - _viewCentreModel);
+        _zoom = clampZoom(zoom * factor);
+        _viewCentreModel = oldModelDatum - screenToModel(screenDistance);
+    }
+
+    void panRelativeScreen(in Vector screenDisplacement) { _viewCentreModel = _viewCentreModel + screenToModel(screenDisplacement); }
+    void panRelativeModel(in Vector modelDisplacement) { _viewCentreModel = _viewCentreModel + modelDisplacement; }
+
+    // For userZoom 1.0 -> 100% means the presentation on the screen is one-to-one with real-life
+    double userZoom(in double pixelsPerMillimetre) const { return _zoom / pixelsPerMillimetre; }
+    double zoom() const { return _zoom; }
+    Rectangle viewBoundsScreen() const { return _viewBoundsScreen; }
+    Rectangle viewBoundsModel() const { return screenToModel(_viewBoundsScreen); }
+    Rectangle canvasBoundsModel() const { return _canvasBoundsModel; }
+    Rectangle canvasBoundsScreen() const { return modelToScreen(_canvasBoundsModel); }
+
+    Point modelToScreen(in Point model) const { return _viewBoundsScreen.centre + _zoom * (model - _viewCentreModel); }
+    Point screenToModel(in Point screen) const { return _viewCentreModel + (screen - _viewBoundsScreen.centre) / _zoom; }
+    Vector modelToScreen(in Vector model) const { return _zoom * model; }
+    Vector screenToModel(in Vector screen) const { return screen / _zoom; }
+    Rectangle modelToScreen(in Rectangle model) const { return Rectangle(modelToScreen(model.position), modelToScreen(model.size)); }
+    Rectangle screenToModel(in Rectangle model) const { return Rectangle(screenToModel(model.position), screenToModel(model.size)); }
+
+    private {
+        static double clampZoom(in double zoom) { return clamp(zoom, 1e-1, 1e2); }
+
+        // Screen units are pixels
+        // Model units are millimetres
+        double    _zoom;                // pixels-per-millimetre
+        Rectangle _viewBoundsScreen;    // bounds of the viewport in screen space
+        Point     _viewCentreModel;     // where in the model is the centre of our screen
+        Rectangle _canvasBoundsModel;   // the bounds of the canvas in model space
+    }
+}