Mercurial > projects > doodle
view doodle/gtk/canvas.d @ 40:1f97022e5c6d
Checkpoint. Development continues...
author | daveb |
---|---|
date | Mon, 12 Apr 2010 14:01:54 +0930 |
parents | 3f6bb0bb22dc |
children | f2e4e1d29b98 |
line wrap: on
line source
module doodle.gtk.canvas; public { import doodle.dia.icanvas; import doodle.tk.events; } private { import doodle.core.logging; import doodle.gtk.conversions; import doodle.tk.misc; import doodle.cairo.routines; import cairo.Surface; 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, Grid grid, in double ppi) { super(3, 3, 0); mDamage = Rectangle.DEFAULT; mLayers = layers.dup; mEventHandler = event_handler; mGrid = grid; 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 info("B1"); mHRuler = new HRuler; attach(mHRuler, 1, 2, 0, 1, AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK, 0, 0); mHRuler.setMetric(MetricType.PIXELS); info("B2"); mVRuler = new VRuler; attach(mVRuler, 0, 1, 1, 2, AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND, 0, 0); info("B3"); mVRuler.setMetric(MetricType.PIXELS); info("J"); mDrawingArea = new DrawingArea; mDrawingArea.addOnRealize(&on_realize); mDrawingArea.addOnConfigure(&on_configure); mDrawingArea.addOnExpose(&on_expose); mDrawingArea.addOnButtonPress(&on_button_press); mDrawingArea.addOnButtonRelease(&on_button_release); mDrawingArea.addOnKeyPress(&on_key_event); mDrawingArea.addOnKeyRelease(&on_key_event); mDrawingArea.addOnMotionNotify(&on_motion_notify); mDrawingArea.addOnScroll(&on_scroll); mDrawingArea.addOnEnterNotify(&on_enter_notify); mDrawingArea.addOnLeaveNotify(&on_leave_notify); 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); info("M"); 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); info("Q"); 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(in Point pixel_datum, in 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; mGrid.zoom_changed(mZoom); queueDraw; } override void pan_relative(in Vector pixel_displacement) { mViewCentre = mViewCentre + pixel_to_model(pixel_displacement); update_adjustments; update_rulers; queueDraw; } override void set_cursor(in Cursor cursor) { CursorType cursor_type; switch (cursor) { case Cursor.DEFAULT: cursor_type = CursorType.ARROW; break; case Cursor.HAND: cursor_type = CursorType.HAND1; break; case Cursor.CROSSHAIR: cursor_type = CursorType.CROSSHAIR; break; default: assert(0); } mDrawingArea.setCursor(new gdk.Cursor.Cursor(cursor_type)); } override void damage_model(in Rectangle area) { mDamage = mDamage | model_to_pixel(area); } override void damage_pixel(in Rectangle area) { mDamage = mDamage | area; } private { bool on_configure(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 = expand(move(layer_bounds, -layer_bounds.size), 2.0 * layer_bounds.size); mViewCentre = mCanvasBounds.centre; mGrid.zoom_changed(mZoom); 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 on_expose(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 on_button_press(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 on_button_release(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 on_key_event(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 on_motion_notify(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 on_scroll(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 on_enter_notify(GdkEventCrossing * event, Widget widget) { assert(widget is mDrawingArea); //writefln("Enter %d %d %d", cast(int)event.mode, event.focus, event.state); // TODO return true; } bool on_leave_notify(GdkEventCrossing * event, Widget widget) { assert(widget is mDrawingArea); //writefln("Leave %d %d %d", cast(int)event.mode, event.focus, event.state); // TODO 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() { invariant Vector model_size = pixel_to_model(mViewSize); invariant Point view_left_bottom = mViewCentre - model_size / 2.0; invariant 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() { invariant Vector model_size = pixel_to_model(mViewSize); invariant Point view_left_bottom = mViewCentre - model_size / 2.0; invariant 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); mDrawingArea.queueDrawArea(x, cast(int)mViewSize.y - (y + h), w, h); mDamage = Rectangle.DEFAULT; } else { //writefln("No damage"); } } double clamp_zoom(in double zoom) { return clamp(zoom, 0.2, 10.0); } Point model_to_pixel(in Point model) const { return Point.DEFAULT + mViewSize / 2.0 + mZoom * (model - mViewCentre); } Point pixel_to_model(in Point pixel) const { return mViewCentre + (pixel - mViewSize / 2.0 - Point.DEFAULT) / mZoom; } Vector model_to_pixel(in Vector model) const { return mZoom * model; } Vector pixel_to_model(in Vector pixel) const { return pixel / mZoom; } Rectangle model_to_pixel(in Rectangle model) const { return Rectangle(model_to_pixel(model.position), model_to_pixel(model.size)); } Rectangle pixel_to_model(in Rectangle model) const { return Rectangle(pixel_to_model(model.position), pixel_to_model(model.size)); } void on_realize(Widget widget) { assert(widget is mDrawingArea); //writefln("Got realize\n"); } 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; Layer[] mLayers; EventHandler mEventHandler; Grid mGrid; double mPPI; } }