diff gtk/canvas.d @ 22:17c2df87b459

Package refactoring.
author "David Bryant <bagnose@gmail.com>"
date Wed, 15 Jul 2009 23:31:29 +0930
parents canvas.d@d6e7a5a6f008
children a24c13bb9c98
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gtk/canvas.d	Wed Jul 15 23:31:29 2009 +0930
@@ -0,0 +1,548 @@
+module gtk.canvas;
+
+public {
+    import dia.icanvas;
+    import tk.geometry;
+    import tk.events;
+}
+
+private {
+    import tk.gtk_support;
+    import tk.misc;
+
+    import cairo.Surface;
+    import cairo_support;
+
+    import std.math;
+    import std.stdio;
+
+    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 gdk.Drawable;
+
+    import gtkc.gtk;
+}
+
+// x and y run right and up respectively
+
+class Canvas : Table, Viewport {
+    this(in Layer[] layers, EventHandler event_handler, in double ppi) {
+        super(3, 3, 0);
+
+        mDamage = Rectangle.DEFAULT;
+
+        mLayers = layers.dup;
+        mEventHandler = event_handler;
+        mPPI = ppi;
+
+        /*
+        writefln("Layer bounds: %s", layer_bounds);
+        writefln("Canvas bounds: %s", mCanvasBounds);
+        writefln("View centre: %s", mViewCentre);
+        */
+
+        // Create our child widgets and register callbacks
+
+        mHRuler = new HRuler;
+        attach(mHRuler,
+               1, 2,
+               0, 1,
+               AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
+               0, 0);
+        mHRuler.setMetric(MetricType.PIXELS);
+
+        mVRuler = new VRuler;
+        attach(mVRuler,
+               0, 1,
+               1, 2,
+               AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+        mVRuler.setMetric(MetricType.PIXELS);
+
+        mDrawingArea = new DrawingArea;
+        mDrawingArea.addOnRealize(&onRealize);
+        mDrawingArea.addOnConfigure(&onConfigure);
+        mDrawingArea.addOnExpose(&onExpose);
+        mDrawingArea.addOnButtonPress(&onButtonPress);
+        mDrawingArea.addOnButtonRelease(&onButtonRelease);
+        mDrawingArea.addOnKeyPress(&onKeyEvent);
+        mDrawingArea.addOnKeyRelease(&onKeyEvent);
+        mDrawingArea.addOnMotionNotify(&onMotionNotify);
+        mDrawingArea.addOnScroll(&onScroll);
+        mDrawingArea.addOnEnterNotify(&onEnterNotify);
+        mDrawingArea.addOnLeaveNotify(&onLeaveNotify);
+        mDrawingArea.setEvents(EventMask.EXPOSURE_MASK |
+                               EventMask.POINTER_MOTION_MASK |
+                               EventMask.POINTER_MOTION_HINT_MASK |
+                               EventMask.BUTTON_MOTION_MASK |
+                               EventMask.BUTTON_PRESS_MASK |
+                               EventMask.BUTTON_RELEASE_MASK |
+                               EventMask.KEY_PRESS_MASK |
+                               EventMask.KEY_RELEASE_MASK |
+                               EventMask.ENTER_NOTIFY_MASK |
+                               EventMask.LEAVE_NOTIFY_MASK |
+                               EventMask.FOCUS_CHANGE_MASK |
+                               EventMask.SCROLL_MASK);
+
+        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, 0.5);
+        mHAdjustment.addOnValueChanged(&onValueChanged);
+        mHScrollbar = new HScrollbar(mHAdjustment);
+        mHScrollbar.setInverted(false);
+        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.5);
+        mVAdjustment.addOnValueChanged(&onValueChanged);
+        mVScrollbar = new VScrollbar(mVAdjustment);
+        mVScrollbar.setInverted(true);
+        attach(mVScrollbar,
+               2, 3,
+               1, 2,
+               AttachOptions.SHRINK,
+               AttachOptions.FILL | AttachOptions.EXPAND,
+               0, 0);
+    }
+
+    override void zoom_relative(Point pixel_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 = pixel_to_model(pixel_datum);
+        Vector pixel_distance = model_to_pixel(old_model_datum - mViewCentre);
+        mZoom = clamp_zoom(factor * mZoom);
+        mViewCentre = old_model_datum - pixel_to_model(pixel_distance);
+
+        update_adjustments;
+        update_rulers;
+        queueDraw;
+    }
+
+    override void pan_relative(Vector pixel_displacement) {
+        mViewCentre = mViewCentre + pixel_to_model(pixel_displacement);
+
+        update_adjustments;
+        update_rulers;
+        queueDraw;
+    }
+
+    override void damage_model(Rectangle area) {
+        mDamage = mDamage | model_to_pixel(area);
+    }
+
+    override void damage_pixel(Rectangle area) {
+        mDamage = mDamage | area;
+    }
+
+    override double zoom() const {
+        return mZoom;
+    }
+
+    override Point model_to_pixel(Point model) const {
+        return Point.DEFAULT + mViewSize / 2.0 + mZoom * (model - mViewCentre);
+    }
+
+    override Point pixel_to_model(Point pixel) const {
+        return mViewCentre + (pixel - mViewSize / 2.0 - Point.DEFAULT) / mZoom;
+    }
+
+    override Vector model_to_pixel(Vector model) const {
+        return mZoom * model;
+    }
+
+    override Vector pixel_to_model(Vector pixel) const {
+        return pixel / mZoom;
+    }
+
+    override double model_to_pixel(double model) const {
+        return mZoom * model;
+    }
+
+    override double pixel_to_model(double pixel) const {
+        return pixel / mZoom;
+    }
+
+    override Rectangle model_to_pixel(Rectangle model) const {
+        return Rectangle(model_to_pixel(model.position), model_to_pixel(model.size));
+    }
+
+    override Rectangle pixel_to_model(Rectangle model) const {
+        return Rectangle(pixel_to_model(model.position), pixel_to_model(model.size));
+    }
+
+    private {
+
+        void onRealize(Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got realize\n");
+        }
+
+        bool onConfigure(GdkEventConfigure * event, Widget widget) {
+            assert(widget is mDrawingArea);
+
+            if (!mHadConfigure) {
+                const double MM_PER_INCH = 25.4;
+                mZoom = 0.25 * mPPI / MM_PER_INCH;
+
+                // Take the union of the bounds of each layer to
+                // determine the canvas size
+
+                Rectangle layer_bounds = Rectangle.DEFAULT;
+
+                foreach (ref layer; mLayers) {
+                    layer_bounds = layer_bounds | layer.bounds;
+                }
+
+                assert(layer_bounds.valid);
+
+                mCanvasBounds = layer_bounds.moved(-layer_bounds.size).expanded(2.0 * layer_bounds.size);
+                mViewCentre = mCanvasBounds.centre;
+
+                mHadConfigure = true;
+            }
+
+            mViewSize = Vector(cast(double)event.width, cast(double)event.height);
+            update_adjustments;
+            update_rulers;
+
+            //writefln("Canvas bounds: %s", mCanvasBounds);
+            //writefln("View centre: %s", mViewCentre);
+
+            return true;
+        }
+
+        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 model_cr = new Context(dr);
+            scope pixel_cr = new Context(dr);
+
+            Rectangle pixel_damage =
+                event is null ?
+                Rectangle(Point(0.0, 0.0), mViewSize) :
+                Rectangle(Point(cast(double)event.area.x, mViewSize.y - cast(double)(event.area.y + event.area.height)),
+                          Vector(cast(double)event.area.width, cast(double)event.area.height));
+
+            Rectangle model_damage = pixel_to_model(pixel_damage);
+
+            //writefln("Pixel damage: %s, model damage: %s", pixel_damage, model_damage);
+
+            model_cr.save; pixel_cr.save; {
+                // Setup model context and clip
+
+                GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
+                GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
+
+                model_cr.scale(mZoom, -mZoom);
+                model_cr.translate(-gtk_adjustment_get_value(h_gtkAdjustment),
+                                   -gtk_adjustment_get_value(v_gtkAdjustment) - gtk_adjustment_get_page_size(v_gtkAdjustment));
+
+                rectangle(model_cr, model_damage);
+                model_cr.clip;
+
+                // Setup pixel context and clip
+
+                pixel_cr.translate(0.0, mViewSize.y);
+                pixel_cr.scale(1.0, -1.0);
+
+                rectangle(pixel_cr, pixel_damage);
+                pixel_cr.clip;
+
+                // Fill the background
+
+                pixel_cr.save; {
+                    // Make the window light grey
+                    pixel_cr.setSourceRgba(0.6, 0.6, 0.6, 1.0);
+                    rectangle(pixel_cr, pixel_damage);
+                    pixel_cr.fill;
+                } pixel_cr.restore;
+
+                // Draw each layer
+
+                foreach(ref layer; mLayers) {
+                    model_cr.save; pixel_cr.save; {
+                        layer.draw(this, pixel_damage, pixel_cr, model_damage, model_cr);
+                    } pixel_cr.restore; model_cr.restore;
+                }
+            } pixel_cr.restore; model_cr.restore;
+
+            return true;
+        }
+
+        bool onButtonPress(GdkEventButton * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got button event\n");
+
+            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
+                                                gtk2tk_button_name(event.button),
+                                                pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_button_press(this, button_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        bool onButtonRelease(GdkEventButton * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got button event\n");
+
+            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
+                                                gtk2tk_button_name(event.button),
+                                                pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_button_release(this, button_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        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);
+
+            process_damage;
+
+            return true;
+        }
+
+        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 pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto motion_event = new MotionEvent(pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_motion(this, motion_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        bool onScroll(GdkEventScroll * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            //writefln("Got scroll\n");
+
+            Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
+            Point model_point = pixel_to_model(pixel_point);
+
+            auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction),
+                                                pixel_point,
+                                                model_point,
+                                                gtk2tk_mask(event.state));
+
+            mEventHandler.handle_scroll(this, scroll_event);
+
+            process_damage;
+
+            return true;
+        }
+
+        /*
+        public enum GdkCrossingMode {       
+            NORMAL,
+            GRAB,
+            UNGRAB,
+            GTK_GRAB,
+            GTK_UNGRAB,
+            STATE_CHANGED
+        }
+
+        public struct GdkEventCrossing {
+            GdkEventType type;
+            GdkWindow *window;
+            byte sendEvent;
+            GdkWindow *subwindow;
+            uint time;
+            double x;
+            double y;
+            double xRoot;
+            double yRoot;
+            GdkCrossingMode mode;
+            GdkNotifyType detail;
+            int focus;
+            uint state;
+        }
+        */
+
+        bool onEnterNotify(GdkEventCrossing * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            writefln("Enter %d %d %d", cast(int)event.mode, event.focus, event.state);
+            return true;
+        }
+
+        bool onLeaveNotify(GdkEventCrossing * event, Widget widget) {
+            assert(widget is mDrawingArea);
+            writefln("Leave %d %d %d", cast(int)event.mode, event.focus, event.state);
+            return true;
+        }
+
+        void onValueChanged(Adjustment adjustment) {
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
+
+            Point view_left_top = Point(gtk_adjustment_get_value(h_gtkAdjustment),
+                                        gtk_adjustment_get_value(v_gtkAdjustment));
+
+            Vector model_size = pixel_to_model(mViewSize);
+
+            //writefln("%s", view_left_bottom);
+            mViewCentre = view_left_top + model_size / 2.0;
+            //writefln("onValueChanged mViewCentre: %s", mViewCentre);
+
+            update_rulers;
+
+            queueDraw;
+        }
+
+        void update_rulers() {
+            Vector model_size = pixel_to_model(mViewSize);
+
+            Point view_left_bottom = mViewCentre - model_size / 2.0;
+            Point view_right_top = mViewCentre + model_size / 2.0;
+
+            // Define these just to obtain the position
+            // below and we can preserve it
+            double lower, upper, position, max_size;
+
+
+            mHRuler.getRange(lower, upper, position, max_size);
+            mHRuler.setRange(view_left_bottom.x,
+                             view_right_top.x,
+                             position,
+                             mZoom * 50.0);
+
+            mVRuler.getRange(lower, upper, position, max_size);
+            mVRuler.setRange(view_right_top.y,
+                             view_left_bottom.y,
+                             position,
+                             mZoom * 50.0);
+        }
+
+        void update_adjustments() {
+            Vector model_size = pixel_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
+            mCanvasBounds = Rectangle(min_extents(mCanvasBounds.min_corner, view_left_bottom),
+                                      max_extents(mCanvasBounds.max_corner, view_right_top));
+
+            // Update the adjustments
+
+            GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
+            GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
+
+            gtk_adjustment_set_lower(h_gtkAdjustment, mCanvasBounds.min_corner.x);
+            gtk_adjustment_set_upper(h_gtkAdjustment, mCanvasBounds.max_corner.x);
+            gtk_adjustment_set_value(h_gtkAdjustment, view_left_bottom.x);
+            gtk_adjustment_set_step_increment(h_gtkAdjustment, mCanvasBounds.size.x / 16.0);
+            gtk_adjustment_set_page_increment(h_gtkAdjustment, mCanvasBounds.size.x / 4.0);
+            gtk_adjustment_set_page_size(h_gtkAdjustment, model_size.x);
+
+            gtk_adjustment_set_lower(v_gtkAdjustment, mCanvasBounds.min_corner.y);
+            gtk_adjustment_set_upper(v_gtkAdjustment, mCanvasBounds.max_corner.y);
+            gtk_adjustment_set_value(v_gtkAdjustment, view_left_bottom.y);
+            gtk_adjustment_set_step_increment(v_gtkAdjustment, mCanvasBounds.size.y / 16.0);
+            gtk_adjustment_set_page_increment(v_gtkAdjustment, mCanvasBounds.size.y / 4.0);
+            gtk_adjustment_set_page_size(v_gtkAdjustment, model_size.y);
+
+            mHAdjustment.changed;
+            mHAdjustment.valueChanged;
+            mVAdjustment.changed;
+            mVAdjustment.valueChanged;
+        }
+
+        void process_damage() {
+            if (mDamage.valid) {
+                //writefln("Damage: %s", mDamage);
+                int x, y, w, h;
+                mDamage.get_quantised(x, y, w, h);
+                //writefln("Quantised damage: %d %d %d %d", x, y, w, h);
+                y = cast(int)mViewSize.y - (y + h);
+                //writefln("Flipped Quantised damage: %d %d %d %d", x, y, w, h);
+                mDrawingArea.queueDrawArea(x, y, w, h);
+                //mDrawingArea.queueDraw();
+                mDamage = Rectangle.DEFAULT;
+            }
+            else {
+                //writefln("No damage");
+            }
+        }
+
+        double clamp_zoom(double zoom) { return clamp(zoom, 0.2, 10.0); }
+
+        bool mHadConfigure;
+        Rectangle mDamage;          // pixels
+
+        // Model units are in millimetres
+        // Screen units are in pixels
+        double mZoom;               // pixels-per-model-unit (mm)
+        Vector mViewSize;           // pixel: size of view window in pixels
+        Point mViewCentre;          // model: where in the model is the centre of our view
+        Rectangle mCanvasBounds;    // model:
+
+        // Child widgets:
+        HRuler mHRuler;
+        VRuler mVRuler;
+        DrawingArea mDrawingArea;
+        Adjustment mHAdjustment;
+        HScrollbar mHScrollbar;
+        Adjustment mVAdjustment;
+        VScrollbar mVScrollbar;
+
+        // Layers:
+        Layer[] mLayers;
+        EventHandler mEventHandler;
+        double mPPI;
+    }
+}