view doodle/tk/pixel_model.d @ 77:15ca7d5cd1ed

The rulers are broken
author "David Bryant <bagnose@gmail.com>"
date Sun, 15 Aug 2010 01:36:22 +0930
parents 5cc2de64f6d0
children 024a5608087f
line wrap: on
line source

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.
// 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 screensPerMillimetre) const { return _zoom / screensPerMillimetre; }
    double zoom() const { return _zoom; }
    Rectangle viewBoundsScreen() const { return _viewBoundsScreen; }
    Rectangle canvasBoundsModel() const { return _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, 0.1, 10.0); }

        // Screen units are screens
        // Model units are millimetres
        double    _zoom;                // screens-per-millimetre
        Rectangle _viewBoundsScreen;    // bounds of the viewport in screens
        Point     _viewCentreModel;     // where in the model is the centre of our view
        Rectangle _canvasBoundsModel;   // the bounds of the canvas in millimetres
    }
}