Mercurial > projects > doodle
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; + } +}