comparison 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
comparison
equal deleted inserted replaced
27:f3d91579bb28 28:1754cb773d41
1 module doodle.gtk.canvas;
2
3 public {
4 import doodle.dia.icanvas;
5 import doodle.tk.events;
6 }
7
8 private {
9 import doodle.gtk.conversions;
10 import doodle.tk.misc;
11 import doodle.cairo.routines;
12
13 import cairo.Surface;
14
15 import std.math;
16 import std.stdio;
17
18 import gtk.Widget;
19 import gtk.Toolbar;
20 import gtk.Table;
21 import gtk.HRuler;
22 import gtk.VRuler;
23 import gtk.Range;
24 import gtk.HScrollbar;
25 import gtk.VScrollbar;
26 import gtk.DrawingArea;
27 import gtk.Adjustment;
28
29 import gdk.Drawable;
30
31 import gtkc.gtk;
32 }
33
34 // x and y run right and up respectively
35
36 class Canvas : Table, Viewport {
37 this(in Layer[] layers, EventHandler event_handler, in double ppi) {
38 super(3, 3, 0);
39
40 mDamage = Rectangle.DEFAULT;
41
42 mLayers = layers.dup;
43 mEventHandler = event_handler;
44 mPPI = ppi;
45
46 /*
47 writefln("Layer bounds: %s", layer_bounds);
48 writefln("Canvas bounds: %s", mCanvasBounds);
49 writefln("View centre: %s", mViewCentre);
50 */
51
52 // Create our child widgets and register callbacks
53
54 mHRuler = new HRuler;
55 attach(mHRuler,
56 1, 2,
57 0, 1,
58 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
59 0, 0);
60 mHRuler.setMetric(MetricType.PIXELS);
61
62 mVRuler = new VRuler;
63 attach(mVRuler,
64 0, 1,
65 1, 2,
66 AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND,
67 0, 0);
68 mVRuler.setMetric(MetricType.PIXELS);
69
70 mDrawingArea = new DrawingArea;
71 mDrawingArea.addOnRealize(&on_realize);
72 mDrawingArea.addOnConfigure(&on_configure);
73 mDrawingArea.addOnExpose(&on_expose);
74 mDrawingArea.addOnButtonPress(&on_button_press);
75 mDrawingArea.addOnButtonRelease(&on_button_release);
76 mDrawingArea.addOnKeyPress(&on_key_event);
77 mDrawingArea.addOnKeyRelease(&on_key_event);
78 mDrawingArea.addOnMotionNotify(&on_motion_notify);
79 mDrawingArea.addOnScroll(&on_scroll);
80 mDrawingArea.addOnEnterNotify(&on_enter_notify);
81 mDrawingArea.addOnLeaveNotify(&on_leave_notify);
82 mDrawingArea.setEvents(EventMask.EXPOSURE_MASK |
83 EventMask.POINTER_MOTION_MASK |
84 EventMask.POINTER_MOTION_HINT_MASK |
85 EventMask.BUTTON_MOTION_MASK |
86 EventMask.BUTTON_PRESS_MASK |
87 EventMask.BUTTON_RELEASE_MASK |
88 EventMask.KEY_PRESS_MASK |
89 EventMask.KEY_RELEASE_MASK |
90 EventMask.ENTER_NOTIFY_MASK |
91 EventMask.LEAVE_NOTIFY_MASK |
92 EventMask.FOCUS_CHANGE_MASK |
93 EventMask.SCROLL_MASK);
94
95 attach(mDrawingArea,
96 1, 2,
97 1, 2,
98 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.FILL | AttachOptions.EXPAND,
99 0, 0);
100
101 // value, lower, upper, step-inc, page-inc, page-size
102 // Give the adjustments dummy values until we receive a configure
103 mHAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.5);
104 mHAdjustment.addOnValueChanged(&onValueChanged);
105 mHScrollbar = new HScrollbar(mHAdjustment);
106 mHScrollbar.setInverted(false);
107 attach(mHScrollbar,
108 1, 2,
109 2, 3,
110 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK,
111 0, 0);
112
113 mVAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.5);
114 mVAdjustment.addOnValueChanged(&onValueChanged);
115 mVScrollbar = new VScrollbar(mVAdjustment);
116 mVScrollbar.setInverted(true);
117 attach(mVScrollbar,
118 2, 3,
119 1, 2,
120 AttachOptions.SHRINK,
121 AttachOptions.FILL | AttachOptions.EXPAND,
122 0, 0);
123 }
124
125 override void zoom_relative(in Point pixel_datum, in double factor) {
126 // Work out pixel distance from current centre to datum,
127 // Do the zoom, then work out the new centre that keeps the
128 // pixel distance the same
129
130 Point old_model_datum = pixel_to_model(pixel_datum);
131 Vector pixel_distance = model_to_pixel(old_model_datum - mViewCentre);
132 mZoom = clamp_zoom(factor * mZoom);
133 mViewCentre = old_model_datum - pixel_to_model(pixel_distance);
134
135 update_adjustments;
136 update_rulers;
137 queueDraw;
138 }
139
140 override void pan_relative(in Vector pixel_displacement) {
141 mViewCentre = mViewCentre + pixel_to_model(pixel_displacement);
142
143 update_adjustments;
144 update_rulers;
145 queueDraw;
146 }
147
148 override void set_cursor(in Cursor cursor) {
149 CursorType cursor_type;
150
151 switch (cursor) {
152 case Cursor.DEFAULT:
153 cursor_type = CursorType.ARROW;
154 break;
155 case Cursor.HAND:
156 cursor_type = CursorType.HAND1;
157 break;
158 case Cursor.CROSSHAIR:
159 cursor_type = CursorType.CROSSHAIR;
160 break;
161 }
162
163 mDrawingArea.setCursor(new gdk.Cursor.Cursor(cursor_type));
164 }
165
166 override void damage_model(in Rectangle area) {
167 mDamage = mDamage | model_to_pixel(area);
168 }
169
170 override void damage_pixel(in Rectangle area) {
171 mDamage = mDamage | area;
172 }
173
174 private {
175
176 bool on_configure(GdkEventConfigure * event, Widget widget) {
177 assert(widget is mDrawingArea);
178
179 if (!mHadConfigure) {
180 const double MM_PER_INCH = 25.4;
181 mZoom = 0.25 * mPPI / MM_PER_INCH;
182
183 // Take the union of the bounds of each layer to
184 // determine the canvas size
185
186 Rectangle layer_bounds = Rectangle.DEFAULT;
187
188 foreach (ref layer; mLayers) {
189 layer_bounds = layer_bounds | layer.bounds;
190 }
191
192 assert(layer_bounds.valid);
193
194 mCanvasBounds = layer_bounds.moved(-layer_bounds.size).expanded(2.0 * layer_bounds.size);
195 mViewCentre = mCanvasBounds.centre;
196
197 mHadConfigure = true;
198 }
199
200 mViewSize = Vector(cast(double)event.width, cast(double)event.height);
201 update_adjustments;
202 update_rulers;
203
204 //writefln("Canvas bounds: %s", mCanvasBounds);
205 //writefln("View centre: %s", mViewCentre);
206
207 return true;
208 }
209
210 bool on_expose(GdkEventExpose * event, Widget widget) {
211 assert(widget is mDrawingArea);
212
213 Drawable dr = mDrawingArea.getWindow;
214
215 int width, height;
216 dr.getSize(width, height);
217 //writefln("Got expose %dx%d\n", width, height);
218
219 scope model_cr = new Context(dr);
220 scope pixel_cr = new Context(dr);
221
222 Rectangle pixel_damage =
223 event is null ?
224 Rectangle(Point(0.0, 0.0), mViewSize) :
225 Rectangle(Point(cast(double)event.area.x, mViewSize.y - cast(double)(event.area.y + event.area.height)),
226 Vector(cast(double)event.area.width, cast(double)event.area.height));
227
228 Rectangle model_damage = pixel_to_model(pixel_damage);
229
230 //writefln("Pixel damage: %s, model damage: %s", pixel_damage, model_damage);
231
232 model_cr.save; pixel_cr.save; {
233 // Setup model context and clip
234
235 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
236 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
237
238 model_cr.scale(mZoom, -mZoom);
239 model_cr.translate(-gtk_adjustment_get_value(h_gtkAdjustment),
240 -gtk_adjustment_get_value(v_gtkAdjustment) - gtk_adjustment_get_page_size(v_gtkAdjustment));
241
242 rectangle(model_cr, model_damage);
243 model_cr.clip;
244
245 // Setup pixel context and clip
246
247 pixel_cr.translate(0.0, mViewSize.y);
248 pixel_cr.scale(1.0, -1.0);
249
250 rectangle(pixel_cr, pixel_damage);
251 pixel_cr.clip;
252
253 // Fill the background
254
255 pixel_cr.save; {
256 // Make the window light grey
257 pixel_cr.setSourceRgba(0.6, 0.6, 0.6, 1.0);
258 rectangle(pixel_cr, pixel_damage);
259 pixel_cr.fill;
260 } pixel_cr.restore;
261
262 // Draw each layer
263
264 foreach(ref layer; mLayers) {
265 model_cr.save; pixel_cr.save; {
266 layer.draw(this, pixel_damage, pixel_cr, model_damage, model_cr);
267 } pixel_cr.restore; model_cr.restore;
268 }
269 } pixel_cr.restore; model_cr.restore;
270
271 return true;
272 }
273
274 bool on_button_press(GdkEventButton * event, Widget widget) {
275 assert(widget is mDrawingArea);
276 //writefln("Got button event\n");
277
278 Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
279 Point model_point = pixel_to_model(pixel_point);
280
281 auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
282 gtk2tk_button_name(event.button),
283 pixel_point,
284 model_point,
285 gtk2tk_mask(event.state));
286
287 mEventHandler.handle_button_press(this, button_event);
288
289 process_damage;
290
291 return true;
292 }
293
294 bool on_button_release(GdkEventButton * event, Widget widget) {
295 assert(widget is mDrawingArea);
296 //writefln("Got button event\n");
297
298 Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
299 Point model_point = pixel_to_model(pixel_point);
300
301 auto button_event = new ButtonEvent(gtk2tk_button_action(event.type),
302 gtk2tk_button_name(event.button),
303 pixel_point,
304 model_point,
305 gtk2tk_mask(event.state));
306
307 mEventHandler.handle_button_release(this, button_event);
308
309 process_damage;
310
311 return true;
312 }
313
314 bool on_key_event(GdkEventKey * event, Widget widget) {
315 assert(widget is mDrawingArea);
316 //writefln("Got key event\n");
317
318 //auto key_event = new KeyEvent("",
319 // mEventHandle.handle_key(key_event);
320
321 process_damage;
322
323 return true;
324 }
325
326 bool on_motion_notify(GdkEventMotion * event, Widget widget) {
327 assert(widget is mDrawingArea);
328 //writefln("Got motion notify\n");
329 gtk_widget_event(mHRuler.getWidgetStruct(), cast(GdkEvent *)event);
330 gtk_widget_event(mVRuler.getWidgetStruct(), cast(GdkEvent *)event);
331
332 Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
333 Point model_point = pixel_to_model(pixel_point);
334
335 auto motion_event = new MotionEvent(pixel_point,
336 model_point,
337 gtk2tk_mask(event.state));
338
339 mEventHandler.handle_motion(this, motion_event);
340
341 process_damage;
342
343 return true;
344 }
345
346 bool on_scroll(GdkEventScroll * event, Widget widget) {
347 assert(widget is mDrawingArea);
348 //writefln("Got scroll\n");
349
350 Point pixel_point = Point(event.x + 0.5, mViewSize.y - (event.y + 0.5));
351 Point model_point = pixel_to_model(pixel_point);
352
353 auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction),
354 pixel_point,
355 model_point,
356 gtk2tk_mask(event.state));
357
358 mEventHandler.handle_scroll(this, scroll_event);
359
360 process_damage;
361
362 return true;
363 }
364
365 /*
366 public enum GdkCrossingMode {
367 NORMAL,
368 GRAB,
369 UNGRAB,
370 GTK_GRAB,
371 GTK_UNGRAB,
372 STATE_CHANGED
373 }
374
375 public struct GdkEventCrossing {
376 GdkEventType type;
377 GdkWindow *window;
378 byte sendEvent;
379 GdkWindow *subwindow;
380 uint time;
381 double x;
382 double y;
383 double xRoot;
384 double yRoot;
385 GdkCrossingMode mode;
386 GdkNotifyType detail;
387 int focus;
388 uint state;
389 }
390 */
391
392 bool on_enter_notify(GdkEventCrossing * event, Widget widget) {
393 assert(widget is mDrawingArea);
394 //writefln("Enter %d %d %d", cast(int)event.mode, event.focus, event.state);
395 // TODO
396 return true;
397 }
398
399 bool on_leave_notify(GdkEventCrossing * event, Widget widget) {
400 assert(widget is mDrawingArea);
401 //writefln("Leave %d %d %d", cast(int)event.mode, event.focus, event.state);
402 // TODO
403 return true;
404 }
405
406 void onValueChanged(Adjustment adjustment) {
407 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
408 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
409
410 Point view_left_top = Point(gtk_adjustment_get_value(h_gtkAdjustment),
411 gtk_adjustment_get_value(v_gtkAdjustment));
412
413 Vector model_size = pixel_to_model(mViewSize);
414
415 //writefln("%s", view_left_bottom);
416 mViewCentre = view_left_top + model_size / 2.0;
417 //writefln("onValueChanged mViewCentre: %s", mViewCentre);
418
419 update_rulers;
420
421 queueDraw;
422 }
423
424 void update_rulers() {
425 invariant Vector model_size = pixel_to_model(mViewSize);
426
427 invariant Point view_left_bottom = mViewCentre - model_size / 2.0;
428 invariant Point view_right_top = mViewCentre + model_size / 2.0;
429
430 // Define these just to obtain the position
431 // below and we can preserve it
432 double lower, upper, position, max_size;
433
434 mHRuler.getRange(lower, upper, position, max_size);
435 mHRuler.setRange(view_left_bottom.x,
436 view_right_top.x,
437 position,
438 mZoom * 50.0);
439
440 mVRuler.getRange(lower, upper, position, max_size);
441 mVRuler.setRange(view_right_top.y,
442 view_left_bottom.y,
443 position,
444 mZoom * 50.0);
445 }
446
447 void update_adjustments() {
448 invariant Vector model_size = pixel_to_model(mViewSize);
449
450 invariant Point view_left_bottom = mViewCentre - model_size / 2.0;
451 invariant Point view_right_top = mViewCentre + model_size / 2.0;
452
453 // Adjust the canvas size if necessary
454 mCanvasBounds = Rectangle(min_extents(mCanvasBounds.min_corner, view_left_bottom),
455 max_extents(mCanvasBounds.max_corner, view_right_top));
456
457 // Update the adjustments
458
459 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct;
460 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct;
461
462 gtk_adjustment_set_lower(h_gtkAdjustment, mCanvasBounds.min_corner.x);
463 gtk_adjustment_set_upper(h_gtkAdjustment, mCanvasBounds.max_corner.x);
464 gtk_adjustment_set_value(h_gtkAdjustment, view_left_bottom.x);
465 gtk_adjustment_set_step_increment(h_gtkAdjustment, mCanvasBounds.size.x / 16.0);
466 gtk_adjustment_set_page_increment(h_gtkAdjustment, mCanvasBounds.size.x / 4.0);
467 gtk_adjustment_set_page_size(h_gtkAdjustment, model_size.x);
468
469 gtk_adjustment_set_lower(v_gtkAdjustment, mCanvasBounds.min_corner.y);
470 gtk_adjustment_set_upper(v_gtkAdjustment, mCanvasBounds.max_corner.y);
471 gtk_adjustment_set_value(v_gtkAdjustment, view_left_bottom.y);
472 gtk_adjustment_set_step_increment(v_gtkAdjustment, mCanvasBounds.size.y / 16.0);
473 gtk_adjustment_set_page_increment(v_gtkAdjustment, mCanvasBounds.size.y / 4.0);
474 gtk_adjustment_set_page_size(v_gtkAdjustment, model_size.y);
475
476 mHAdjustment.changed;
477 mHAdjustment.valueChanged;
478 mVAdjustment.changed;
479 mVAdjustment.valueChanged;
480 }
481
482 void process_damage() {
483 if (mDamage.valid) {
484 //writefln("Damage: %s", mDamage);
485 int x, y, w, h;
486 mDamage.get_quantised(x, y, w, h);
487 mDrawingArea.queueDrawArea(x, cast(int)mViewSize.y - (y + h), w, h);
488 mDamage = Rectangle.DEFAULT;
489 }
490 else {
491 //writefln("No damage");
492 }
493 }
494
495 double clamp_zoom(in double zoom) { return clamp(zoom, 0.2, 10.0); }
496
497 Point model_to_pixel(in Point model) const {
498 return Point.DEFAULT + mViewSize / 2.0 + mZoom * (model - mViewCentre);
499 }
500
501 Point pixel_to_model(in Point pixel) const {
502 return mViewCentre + (pixel - mViewSize / 2.0 - Point.DEFAULT) / mZoom;
503 }
504
505 Vector model_to_pixel(in Vector model) const {
506 return mZoom * model;
507 }
508
509 Vector pixel_to_model(in Vector pixel) const {
510 return pixel / mZoom;
511 }
512
513 Rectangle model_to_pixel(in Rectangle model) const {
514 return Rectangle(model_to_pixel(model.position), model_to_pixel(model.size));
515 }
516
517 Rectangle pixel_to_model(in Rectangle model) const {
518 return Rectangle(pixel_to_model(model.position), pixel_to_model(model.size));
519 }
520
521 void on_realize(Widget widget) {
522 assert(widget is mDrawingArea);
523 //writefln("Got realize\n");
524 }
525
526 bool mHadConfigure;
527 Rectangle mDamage; // pixels
528
529 // Model units are in millimetres
530 // Screen units are in pixels
531 double mZoom; // pixels-per-model-unit (mm)
532 Vector mViewSize; // pixel: size of view window in pixels
533 Point mViewCentre; // model: where in the model is the centre of our view
534 Rectangle mCanvasBounds; // model:
535
536 // Child widgets:
537 HRuler mHRuler;
538 VRuler mVRuler;
539 DrawingArea mDrawingArea;
540 Adjustment mHAdjustment;
541 HScrollbar mHScrollbar;
542 Adjustment mVAdjustment;
543 VScrollbar mVScrollbar;
544
545 // Layers:
546 Layer[] mLayers;
547 EventHandler mEventHandler;
548 double mPPI;
549 }
550 }