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