Mercurial > projects > doodle
view doodle/gtk/canvas.d @ 68:64bd864db30d
Bah
author | daveb |
---|---|
date | Fri, 13 Aug 2010 15:53:48 +0930 |
parents | 31d10176415d |
children | d540f7e4af9e |
line wrap: on
line source
module doodle.gtk.canvas; public { import doodle.dia.icanvas; import doodle.tk.events; } private { import doodle.core.misc; import doodle.core.logging; import doodle.cairo.routines; import doodle.gtk.conversions; import cairo.Surface; 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; import gtkc.gtktypes; //import gtkc.gdktypes; import std.math; import std.stdio; import core.stdc.string : strlen; } // x and y run right and up respectively // // Model units are millimetres. // // _zoom -> pixels-per-model-unit // _viewSize -> size of view window in pixels // _viewCentre -> location in model corresponding to centre of view // _canvasBounds -> size of the virtual canvas in model coordinates // // User operations: // pan (middle click and drag) // zoom about a point (hold control and move scroll wheel) // resize the widget final class Canvas : Table, private IViewport { this(in Layer[] layers, IEventHandler eventHandler, IGrid grid, in double ppi) { super(3, 3, 0); _damage = Rectangle.DEFAULT; _layers = layers.dup; _eventHandler = eventHandler; _grid = grid; _ppi = ppi; /* trace("Layer bounds: %s", layerBounds); trace("Canvas bounds: %s", _canvasBounds); trace("View centre: %s", _viewCentre); */ // Create our child widgets and register callbacks _hRuler = new HRuler; attach(_hRuler, 1, 2, 0, 1, AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK, 0, 0); _hRuler.setMetric(MetricType.PIXELS); _vRuler = new VRuler; attach(_vRuler, 0, 1, 1, 2, AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND, 0, 0); _vRuler.setMetric(MetricType.PIXELS); _drawingArea = new DrawingArea; _drawingArea.addOnRealize(&onRealize); _drawingArea.addOnConfigure(&onConfigure); _drawingArea.addOnExpose(&onExpose); _drawingArea.addOnButtonPress(&onButtonPress); // FIXME merge delegate with next _drawingArea.addOnButtonRelease(&onButtonRelease); _drawingArea.addOnKeyPress(&onKeyPressEvent); // FIXME merge delegate with next _drawingArea.addOnKeyRelease(&onKeyReleaseEvent); _drawingArea.addOnMotionNotify(&onMotionNotify); _drawingArea.addOnScroll(&onScroll); _drawingArea.addOnEnterNotify(&onEnterNotify); // FIXME merge delegate with next _drawingArea.addOnLeaveNotify(&onLeaveNotify); _drawingArea.addOnFocusIn(&onFocusIn); _drawingArea.addOnFocusOut(&onFocusOut); _drawingArea.addOnMoveFocus(&onMoveFocus); _drawingArea.addOnGrabBroken(&onGrabBroken); _drawingArea.addOnGrabFocus(&onGrabFocus); _drawingArea.addOnGrabNotify(&onGrabNotify); // addOnPopupMenu // addOnQueryTooltip // addOnSelection* _drawingArea.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); _drawingArea.setCanFocus(true); attach(_drawingArea, 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 _hAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.5); _hAdjustment.addOnValueChanged(&onAdjustmentValueChanged); _hScrollbar = new HScrollbar(_hAdjustment); _hScrollbar.setInverted(false); attach(_hScrollbar, 1, 2, 2, 3, AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK, 0, 0); _vAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.5); _vAdjustment.addOnValueChanged(&onAdjustmentValueChanged); _vScrollbar = new VScrollbar(_vAdjustment); _vScrollbar.setInverted(true); attach(_vScrollbar, 2, 3, 1, 2, AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND, 0, 0); } protected { // XXX the compiler complains about unimplemented methods if this is private // IViewport overrides: void zoomRelative(in Point pixelDatum, 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 oldModelDatum = pixelToModel(pixelDatum); Vector pixelDistance = modelToPixel(oldModelDatum - _viewCentre); _zoom = clampZoom(factor * _zoom); _viewCentre = oldModelDatum - pixelToModel(pixelDistance); consolidateBounds; updateAdjustments; updateRulers; _grid.zoomChanged(_zoom); queueDraw; } void panRelative(in Vector pixelDisplacement) { _viewCentre = _viewCentre + pixelToModel(pixelDisplacement); consolidateBounds; updateAdjustments; updateRulers; queueDraw; } void setCursor(in Cursor cursor) { CursorType cursorType; switch (cursor) { case Cursor.DEFAULT: cursorType = CursorType.ARROW; break; case Cursor.HAND: cursorType = CursorType.HAND1; break; case Cursor.CROSSHAIR: cursorType = CursorType.CROSSHAIR; break; case Cursor.PENCIL: cursorType = CursorType.PENCIL; break; default: assert(0); } _drawingArea.setCursor(new gdk.Cursor.Cursor(cursorType)); } void damageModel(in Rectangle area) { _damage = _damage | modelToPixel(area); } void damagePixel(in Rectangle area) { _damage = _damage | area; } } private { void initialiseBounds() { Rectangle layerBounds = Rectangle.DEFAULT; foreach (layer; _layers) { layerBounds = layerBounds | layer.bounds; } assert(layerBounds.valid); // FIXME use a function that grows a rectangle about its centre // and change 2.0 to a class-level constant Rectangle paddedLayerBounds = expand(move(layerBounds, - layerBounds.size), 2.0 * layerBounds.size); // _zoom = 0.25 * _ppi / MM_PER_INCH; _canvasBounds = paddedLayerBounds; _viewCentre = _canvasBounds.centre; _grid.zoomChanged(_zoom); updateAdjustments; updateRulers; } void consolidateBounds() { Rectangle layerBounds = Rectangle.DEFAULT; foreach (layer; _layers) { layerBounds = layerBounds | layer.bounds; } assert(layerBounds.valid); Rectangle paddedLayerBounds = expand(move(layerBounds, - layerBounds.size), 2.0 * layerBounds.size); Vector z = _viewSize / _zoom; Rectangle r = Rectangle(_viewCentre - z / 2.0, z); _canvasBounds = r | paddedLayerBounds; updateAdjustments; updateRulers; } bool onConfigure(GdkEventConfigure * event, Widget widget) { assert(widget is _drawingArea); _viewSize = Vector(cast(double)event.width, cast(double)event.height); if (!_boundsValid) { initialiseBounds; _boundsValid = true; } else { consolidateBounds; } return true; } bool onExpose(GdkEventExpose * event, Widget widget) { assert(widget is _drawingArea); Drawable dr = _drawingArea.getWindow; int width, height; dr.getSize(width, height); //trace("Got expose %dx%d\n", width, height); scope modelCr = new Context(dr); scope pixelCr = new Context(dr); Rectangle pixelDamage = event is null ? Rectangle(Point(0.0, 0.0), _viewSize) : Rectangle(Point(cast(double)event.area.x, _viewSize.y - cast(double)(event.area.y + event.area.height)), Vector(cast(double)event.area.width, cast(double)event.area.height)); Rectangle modelDamage = pixelToModel(pixelDamage); //trace("Pixel damage: %s, model damage: %s", pixelDamage, modelDamage); modelCr.save; pixelCr.save; { { // Setup model context and clip modelCr.translate(0.0, _viewSize.y); modelCr.scale(_zoom, -_zoom); immutable Vector modelSize = pixelToModel(_viewSize); immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0; modelCr.translate(-viewLeftBottom.x, -viewLeftBottom.y); rectangle(modelCr, modelDamage); modelCr.clip; } { // Setup pixel context and clip pixelCr.translate(0.0, _viewSize.y); pixelCr.scale(1.0, -1.0); rectangle(pixelCr, pixelDamage); pixelCr.clip; } pixelCr.save; { // Fill the background with light grey pixelCr.setSourceRgba(0.9, 0.9, 0.9, 1.0); rectangle(pixelCr, pixelDamage); pixelCr.fill; } pixelCr.restore; // Draw each layer foreach(layer; _layers) { layer.draw(this, pixelDamage, pixelCr, modelDamage, modelCr); } } pixelCr.restore; modelCr.restore; return true; } bool onButtonPress(GdkEventButton * event, Widget widget) { assert(widget is _drawingArea); //trace("Got button event\n"); Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); Point modelPoint = pixelToModel(pixelPoint); auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type), gtk2tkButtonName(event.button), pixelPoint, modelPoint, gtk2tkMask(event.state)); _eventHandler.handleButtonPress(this, buttonEvent); fixDamage; return true; } bool onButtonRelease(GdkEventButton * event, Widget widget) { assert(widget is _drawingArea); Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); Point modelPoint = pixelToModel(pixelPoint); auto buttonEvent = new ButtonEvent(gtk2tkButtonAction(event.type), gtk2tkButtonName(event.button), pixelPoint, modelPoint, gtk2tkMask(event.state)); _eventHandler.handleButtonRelease(this, buttonEvent); fixDamage; return true; } /* public struct GdkEventKey { GdkEventType type; GdkWindow *window; byte sendEvent; uint time; uint state; uint keyval; int length; char *string; ushort hardwareKeycode; ubyte group; uint bitfield0; uint isModifier : 1; } */ bool onKeyPressEvent(GdkEventKey * event, Widget widget) { assert(widget is _drawingArea); auto keyEvent = new KeyEvent(event.string[0..strlen(event.string)].idup, event.keyval, gtk2tkMask(event.state)); message("Got key press %s", keyEvent); _eventHandler.handleKeyPress(this, keyEvent); fixDamage; return true; } bool onKeyReleaseEvent(GdkEventKey * event, Widget widget) { assert(widget is _drawingArea); auto keyEvent = new KeyEvent(event.string[0..strlen(event.string)].idup, event.keyval, gtk2tkMask(event.state)); //message("Got key release %s", keyEvent); _eventHandler.handleKeyRelease(this, keyEvent); fixDamage; return true; } bool onMotionNotify(GdkEventMotion * event, Widget widget) { assert(widget is _drawingArea); //writefln("Got motion notify\n"); gtk_widget_event(_hRuler.getWidgetStruct(), cast(GdkEvent *)event); gtk_widget_event(_vRuler.getWidgetStruct(), cast(GdkEvent *)event); Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); Point modelPoint = pixelToModel(pixelPoint); auto motionEvent = new MotionEvent(pixelPoint, modelPoint, gtk2tkMask(event.state)); _eventHandler.handleMotion(this, motionEvent); fixDamage; return true; } bool onScroll(GdkEventScroll * event, Widget widget) { assert(widget is _drawingArea); //writefln("Got scroll\n"); Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); Point modelPoint = pixelToModel(pixelPoint); auto scrollEvent = new ScrollEvent(gtk2tkDirection(event.direction), pixelPoint, modelPoint, gtk2tkMask(event.state)); _eventHandler.handleScroll(this, scrollEvent); fixDamage; 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 _drawingArea); Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); Point modelPoint = pixelToModel(pixelPoint); auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode), pixelPoint, modelPoint, gtk2tkMask(event.state)); _eventHandler.handleEnter(this, crossingEvent); fixDamage; //message("Enter %d %d %d", cast(int)event.mode, event.focus, event.state); return true; } bool onLeaveNotify(GdkEventCrossing * event, Widget widget) { assert(widget is _drawingArea); Point pixelPoint = Point(event.x + 0.5, _viewSize.y - (event.y + 0.5)); Point modelPoint = pixelToModel(pixelPoint); auto crossingEvent = new CrossingEvent(gtk2tkCrossingMode(event.mode), pixelPoint, modelPoint, gtk2tkMask(event.state)); _eventHandler.handleLeave(this, crossingEvent); fixDamage; //message("Leave %d %d %d", cast(int)event.mode, event.focus, event.state); return true; } /* public struct GdkEventFocus { GdkEventType type; GdkWindow *window; byte sendEvent; short inn; } */ bool onFocusIn(GdkEventFocus * event, Widget widget) { trace("onFocusIn"); return true; } bool onFocusOut(GdkEventFocus * event, Widget widget) { trace("onFocusOut"); return true; } void onMoveFocus(GtkDirectionType direction, Widget widget) { trace("onMoveFocus"); } bool onGrabBroken(gdk.Event.Event event, Widget widget) { trace("onGrabBroken"); return true; } void onGrabFocus(Widget widget) { //trace("onGrabFocus"); } void onGrabNotify(gboolean what, Widget widget){ trace("onGrabNotify"); } void onAdjustmentValueChanged(Adjustment adjustment) { GtkAdjustment * hGtkAdjustment = _hAdjustment.getAdjustmentStruct; GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct; Point viewLeftBottom = Point(gtk_adjustment_get_value(hGtkAdjustment), gtk_adjustment_get_value(vGtkAdjustment)); Vector modelSize = pixelToModel(_viewSize); _viewCentre = viewLeftBottom + modelSize / 2.0; updateRulers; queueDraw; } void updateRulers() { immutable Vector modelSize = pixelToModel(_viewSize); immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0; immutable Point viewRightTop = _viewCentre + modelSize / 2.0; // Define these just to obtain the position // below and we can preserve it double lower, upper, position, maxSize; _hRuler.getRange(lower, upper, position, maxSize); _hRuler.setRange(viewLeftBottom.x, viewRightTop.x, position, _zoom * 50.0); _vRuler.getRange(lower, upper, position, maxSize); _vRuler.setRange(viewRightTop.y, viewLeftBottom.y, position, _zoom * 50.0); } void updateAdjustments() { immutable Vector modelSize = pixelToModel(_viewSize); immutable Point viewLeftBottom = _viewCentre - modelSize / 2.0; immutable Point viewRightTop = _viewCentre + modelSize / 2.0; // Adjust the canvas size if necessary _canvasBounds = Rectangle(minExtents(_canvasBounds.minCorner, viewLeftBottom), maxExtents(_canvasBounds.maxCorner, viewRightTop)); // Update the adjustments GtkAdjustment * hGtkAdjustment = _hAdjustment.getAdjustmentStruct; GtkAdjustment * vGtkAdjustment = _vAdjustment.getAdjustmentStruct; gtk_adjustment_set_lower(hGtkAdjustment, _canvasBounds.minCorner.x); gtk_adjustment_set_upper(hGtkAdjustment, _canvasBounds.maxCorner.x); gtk_adjustment_set_value(hGtkAdjustment, viewLeftBottom.x); gtk_adjustment_set_step_increment(hGtkAdjustment, _canvasBounds.size.x / 16.0); gtk_adjustment_set_page_increment(hGtkAdjustment, _canvasBounds.size.x / 4.0); gtk_adjustment_set_page_size(hGtkAdjustment, modelSize.x); gtk_adjustment_set_lower(vGtkAdjustment, _canvasBounds.minCorner.y); gtk_adjustment_set_upper(vGtkAdjustment, _canvasBounds.maxCorner.y); gtk_adjustment_set_value(vGtkAdjustment, viewLeftBottom.y); gtk_adjustment_set_step_increment(vGtkAdjustment, _canvasBounds.size.y / 16.0); gtk_adjustment_set_page_increment(vGtkAdjustment, _canvasBounds.size.y / 4.0); gtk_adjustment_set_page_size(vGtkAdjustment, modelSize.y); _hAdjustment.changed; _hAdjustment.valueChanged; _vAdjustment.changed; _vAdjustment.valueChanged; } void fixDamage() { if (_damage.valid) { int x, y, w, h; _damage.getQuantised(x, y, w, h); _drawingArea.queueDrawArea(x, cast(int)_viewSize.y - (y + h), w, h); _damage = Rectangle.DEFAULT; } } static double clampZoom(in double zoom) { return clamp(zoom, 0.2, 10.0); } Point modelToPixel(in Point model) const { return Point.DEFAULT + _viewSize / 2.0 + _zoom * (model - _viewCentre); } Point pixelToModel(in Point pixel) const { return _viewCentre + (pixel - _viewSize / 2.0 - Point.DEFAULT) / _zoom; } Vector modelToPixel(in Vector model) const { return _zoom * model; } Vector pixelToModel(in Vector pixel) const { return pixel / _zoom; } Rectangle modelToPixel(in Rectangle model) const { return Rectangle(modelToPixel(model.position), modelToPixel(model.size)); } Rectangle pixelToModel(in Rectangle model) const { return Rectangle(pixelToModel(model.position), pixelToModel(model.size)); } void onRealize(Widget widget) { assert(widget is _drawingArea); //writefln("Got realize\n"); _drawingArea.grabFocus(); } bool _boundsValid; Rectangle _damage; // pixels // Model units are millimetres // Screen units are pixels double _zoom; // pixels-per-model-unit Vector _viewSize; // pixel: size of view window in pixels Point _viewCentre; // model: where in the model is the centre of our view Rectangle _canvasBounds; // model: // Child widgets: HRuler _hRuler; VRuler _vRuler; DrawingArea _drawingArea; Adjustment _hAdjustment; HScrollbar _hScrollbar; Adjustment _vAdjustment; VScrollbar _vScrollbar; Layer[] _layers; IEventHandler _eventHandler; IGrid _grid; double _ppi; static immutable MM_PER_INCH = 25.4; } }