view doodle/tk/pixel_model.d @ 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
line wrap: on
line source

module doodle.tk.pixel_model;

public {
    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;
    }

    void consolidateCanvasBounds(in Rectangle requiredCanvasBounds) {
        Rectangle r = pixelToModel(_viewBounds);
        _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) 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; }
    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)); }

    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
        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: the bounds of the canvas in millimetres
    }
}