Mercurial > projects > doodle
diff doodle/gtk/canvas.d @ 28:1754cb773d41
Part-way through getting to compile with configure/builder.
author | Graham St Jack <graham.stjack@internode.on.net> |
---|---|
date | Sun, 02 Aug 2009 16:27:21 +0930 |
parents | gtk/canvas.d@f3d91579bb28 |
children | c2f11e1d7470 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doodle/gtk/canvas.d Sun Aug 02 16:27:21 2009 +0930 @@ -0,0 +1,550 @@ +module doodle.gtk.canvas; + +public { + import doodle.dia.icanvas; + import doodle.tk.events; +} + +private { + 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, 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(&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); + + 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(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; + 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; + } + + 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 = 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 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; + + // Layers: + Layer[] mLayers; + EventHandler mEventHandler; + double mPPI; + } +}