diff canvas.d @ 0:e907d2c54ec3

Initial import
author David Bryant <daveb@acres.com.au>
date Wed, 13 May 2009 15:42:39 +0930
parents
children d6f44347373d
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/canvas.d	Wed May 13 15:42:39 2009 +0930
@@ -0,0 +1,378 @@
+module canvas;
+
+import std.stdio;
+import std.math;
+
+import cairo.Context;
+import cairo.Surface;
+
+import gdk.Drawable;
+
+import gtk.Widget;
+import gtk.Toolbar;
+import gtk.Table;
+import gtk.HRuler;
+import gtk.VRuler;
+import gtk.Range;
+import gtk.HScrollbar;
+import gtk.VScrollbar;
+import gtk.DrawingArea;
+import gtk.Adjustment;
+
+import tk.misc;
+import tk.geometry;
+import tk.types;
+import tk.events;
+import tk.gtk_support;
+
+private import gtkc.gtk;
+
+import icanvas;
+
+// x and y run right and up respectively
+
+void rectangle(Context cr, Point corner1, Point corner2) {
+    double x = corner1.x;
+    double y = corner1.y;
+    double w = corner2.x - corner1.x;
+    double h = corner2.y - corner1.y;
+
+    if (w < 0.0) { x += w; w = -w; }
+    if (h < 0.0) { y += h; h = -h; }
+
+    //writefln("Rect: %f %f %f %f\n", x, y, w, h);
+
+    cr.rectangle(x, y, w, h);
+}
+
+class Canvas : Table, ICanvas {
+    static this() {
+        ORIGIN = new Point(0.0, 0.0);
+        INITIAL_PAGE_SIZE = new Vector(210.0, 297.0);       // A4
+    }
+
+    this(ICanvasEventHandler event_handler) {
+        super(3, 3, 0);
+
+        mEventHandler = event_handler;
+
+        const double PPI = 120.0;
+        const double MM_PER_INCH = 25.4;
+        mZoom = 0.25 * PPI / MM_PER_INCH;
+
+        mPageLeftBottom = ORIGIN.clone();
+        mPageRightTop = ORIGIN + INITIAL_PAGE_SIZE;
+        mViewCentre = ORIGIN + INITIAL_PAGE_SIZE / 2.0;
+
+        mCanvasLeftBottom = mPageLeftBottom - 3.0 * INITIAL_PAGE_SIZE;
+        mCanvasRightTop = mPageRightTop + 3.0 * INITIAL_PAGE_SIZE;
+
+        mHRuler = new HRuler();
+        attach(mHRuler,
+               1, 2,
+               0, 1,
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
+               0, 0);
+        mHRuler.setMetric(GtkMetricType.CENTIMETERS);
+
+        mVRuler = new VRuler();
+        attach(mVRuler,
+               0, 1,
+               1, 2,
+               AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+        mVRuler.setMetric(GtkMetricType.CENTIMETERS);
+
+        mDrawingArea = new DrawingArea();
+        mDrawingArea.addOnRealize(&onRealize);
+        mDrawingArea.addOnConfigure(&onConfigure);
+        mDrawingArea.addOnExpose(&onExpose);
+        mDrawingArea.addOnButtonPress(&onButtonEvent);
+        mDrawingArea.addOnButtonRelease(&onButtonEvent);
+        mDrawingArea.addOnKeyPress(&onKeyEvent);
+        mDrawingArea.addOnKeyRelease(&onKeyEvent);
+        mDrawingArea.addOnMotionNotify(&onMotionNotify);
+        mDrawingArea.addOnScroll(&onScroll);
+        attach(mDrawingArea,
+               1, 2,
+               1, 2, 
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+
+        // value, lower, upper, step-inc, page-inc, page-size
+        // Give the adjustments dummy values until we receive a configure
+        mHAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 1.0);
+        mHAdjustment.addOnValueChanged(&onValueChanged);
+        mHScrollbar = new HScrollbar(mHAdjustment);
+        attach(mHScrollbar,
+               1, 2,
+               2, 3,
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
+               0, 0);
+
+        mVAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.4);
+        mVAdjustment.addOnValueChanged(&onValueChanged);
+        mVScrollbar = new VScrollbar(mVAdjustment);
+        attach(mVScrollbar,
+               2, 3,
+               1, 2,
+               AttachOptions.SHRINK,
+               AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+    }
+
+    override void rel_zoom(Point screen_datum, 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 old_model_datum = screen_to_model(screen_datum);
+        Vector pixel_distance = model_to_screen(old_model_datum - mViewCentre);
+        mZoom = clamp_zoom(factor * mZoom);
+        mViewCentre = old_model_datum - screen_to_model(pixel_distance);
+
+        Point new_model_datum = screen_to_model(screen_datum);
+
+        update_adjustments();
+        //update_rulers(new_model_datum);
+        update_rulers();
+        queueDraw();
+    }
+
+    override void rel_pan(Vector screen_displacement) {
+        mViewCentre = mViewCentre + screen_to_model(screen_displacement);
+
+        update_adjustments();
+        update_rulers();
+        queueDraw();
+    }
+
+    private {
+
+        void onRealize(Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got realize\n");
+        }
+
+        bool onConfigure(GdkEventConfigure * event, Widget widget) {
+            assert(widget is mDrawingArea);
+
+            mViewSize = new Vector(cast(double)event.width, cast(double)event.height);
+            update_adjustments();
+            update_rulers();
+
+
+            return 1;
+        }
+
+        bool onExpose(GdkEventExpose * event, Widget widget) {
+            assert(widget is mDrawingArea);
+
+            Drawable dr = mDrawingArea.getWindow();
+
+            int width, height;
+            dr.getSize(width, height);
+            //writefln("Got expose %dx%d\n", width, height);
+
+            scope auto cr = new Context(dr);
+
+            if (event) {
+                cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height);
+                cr.clip();
+            }
+
+            // Make the window light grey
+            cr.setSourceRgba(1.0, 1.0, 1.0, 0.7);
+            if (event) {
+                cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height);
+            }
+            else {
+                cr.rectangle(0.0, 0.0, width, height);
+            }
+            cr.fill();
+
+            {
+                // Make the paper white, with a border
+
+                Point screen_page_left_bottom = model_to_screen(mPageLeftBottom);
+                Point screen_page_right_top = model_to_screen(mPageRightTop);
+
+                cr.setSourceRgba(1.0, 1.0, 1.0, 1.0);
+                rectangle(cr, screen_page_left_bottom, screen_page_right_top);
+                cr.fill();
+
+                cr.setSourceRgba(0.0, 0.0, 0.0, 1.0);
+                rectangle(cr, screen_page_left_bottom, screen_page_right_top);
+                cr.stroke();
+            }
+
+            return 1;
+        }
+
+        bool onButtonEvent(GdkEventButton * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got button event\n");
+
+            Point screen_point = new Point(event.x + 0.5, event.y + 0.5);
+            Point model_point = screen_to_model(screen_point);
+
+            auto button_event = new ButtonEvent(gtk2tk_click(event.type),
+                                                gtk2tk_button(event.button),
+                                                screen_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            //mEventHandle.handle_button_press
+
+            return 1;
+        }
+
+        bool onKeyEvent(GdkEventKey * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got key event\n");
+
+            //auto key_event = new KeyEvent("",
+            // mEventHandle.handle_key(key_event);
+
+            return 1;
+        }
+
+        bool onMotionNotify(GdkEventMotion * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got motion notify\n");
+            gtk_widget_event(mHRuler.getWidgetStruct(), cast(GdkEvent *)event);
+            gtk_widget_event(mVRuler.getWidgetStruct(), cast(GdkEvent *)event);
+
+            Point screen_point = new Point(event.x + 0.5, event.y + 0.5);
+            Point model_point = screen_to_model(screen_point);
+
+            auto motion_event = new MotionEvent(screen_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_motion(this, motion_event);
+
+            return 1;
+        }
+
+        bool onScroll(GdkEventScroll * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got scroll\n");
+
+            Point screen_point = new Point(event.x + 0.5, event.y + 0.5);
+            Point model_point = screen_to_model(screen_point);
+
+            auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction),
+                                                screen_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_scroll(this, scroll_event);
+
+            return 1;
+        }
+
+        void onValueChanged(Adjustment adjustment) {
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct();
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct();
+
+            Point view_left_bottom = new Point(h_gtkAdjustment.value, v_gtkAdjustment.value);
+            writefln("%s", view_left_bottom);
+            Vector model_size = screen_to_model(mViewSize);
+            mViewCentre = view_left_bottom + model_size / 2.0;
+
+            update_rulers();
+
+            queueDraw();
+        }
+
+        void update_rulers() {
+            Vector model_size = screen_to_model(mViewSize);
+
+            Point view_left_bottom = mViewCentre - model_size / 2.0;
+            Point view_right_top = mViewCentre + model_size / 2.0;
+
+            mHRuler.setRange(view_left_bottom.x,
+                             view_right_top.x,
+                             0.0,       // TODO preserve the value
+                             mZoom * 2000.0);
+            mVRuler.setRange(view_right_top.y,
+                             view_left_bottom.y,
+                             0.0,
+                             mZoom * 2000.0);
+        }
+
+        void update_adjustments() {
+            Vector model_size = screen_to_model(mViewSize);
+
+            Point view_left_bottom = mViewCentre - model_size / 2.0;
+            Point view_right_top = mViewCentre + model_size / 2.0;
+
+            // Adjust the canvas size if necessary
+            mCanvasLeftBottom = min_extents(mCanvasLeftBottom, view_left_bottom);
+            mCanvasRightTop = max_extents(mCanvasRightTop, view_right_top);
+
+            Vector canvas_size = mCanvasRightTop - mCanvasLeftBottom;
+            Vector page_size = mPageRightTop - mPageLeftBottom;
+
+            // Update the adjustments
+
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct();
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct();
+
+            h_gtkAdjustment.lower = mCanvasLeftBottom.x;
+            h_gtkAdjustment.upper = mCanvasRightTop.x;
+            h_gtkAdjustment.value = view_left_bottom.x;
+            h_gtkAdjustment.step_increment = canvas_size.x / 10.0;
+            h_gtkAdjustment.page_increment = canvas_size.x / 5.0;
+            h_gtkAdjustment.page_size = model_size.x;
+
+            v_gtkAdjustment.lower = mCanvasLeftBottom.y;
+            v_gtkAdjustment.upper = mCanvasRightTop.y;
+            v_gtkAdjustment.value = view_left_bottom.y;
+            v_gtkAdjustment.step_increment = canvas_size.y / 10.0;
+            v_gtkAdjustment.page_increment = canvas_size.y / 5.0;
+            v_gtkAdjustment.page_size = model_size.y;
+
+            mHAdjustment.changed();
+            mHAdjustment.valueChanged();
+            mVAdjustment.changed();
+            mVAdjustment.valueChanged();
+        }
+
+        Point model_to_screen(Point model) { return ORIGIN + mViewSize / 2.0 + mZoom * (model - mViewCentre); }
+        Point screen_to_model(Point screen) { return mViewCentre + (screen - mViewSize / 2.0 - ORIGIN) / mZoom; }
+        Vector model_to_screen(Vector model) { return mZoom * model; }
+        Vector screen_to_model(Vector screen) { return screen / mZoom; }
+        double model_to_screen(double model) { return mZoom * model; }
+        double screen_to_model(double screen) { return screen / mZoom; }
+
+        double clamp_zoom(double zoom) { return clamp(zoom, 0.1, 10.0); }
+
+        static const Point ORIGIN;
+        static const Vector INITIAL_PAGE_SIZE;
+
+        ICanvasEventHandler mEventHandler;
+
+        // Model units are in millimetres
+        // Screen units are in pixels
+
+        double mZoom;               // pixels-per-mm
+        Point mViewCentre;          // model: where in the model is the centre of our view
+
+        Point mCanvasLeftBottom;    // model: bottom left corner of canvas
+        Point mCanvasRightTop;      // model: top right corner of canvas
+        Point mPageLeftBottom;      // model: bottom left corner of page
+        Point mPageRightTop;        // model: top right corner of page
+
+        Vector mViewSize;           // screen: size of view window in pixels
+
+        HRuler mHRuler;
+        VRuler mVRuler;
+        DrawingArea mDrawingArea;
+        Adjustment mHAdjustment;
+        HScrollbar mHScrollbar;
+        Adjustment mVAdjustment;
+        VScrollbar mVScrollbar;
+    }
+}