Mercurial > projects > doodle
annotate canvas.d @ 15:2f79aab4d385
Checkpoint
author | "David Bryant <bagnose@gmail.com>" |
---|---|
date | Sun, 12 Jul 2009 13:23:06 +0930 |
parents | 0b7e7d43a79d |
children | 9e63308b749c |
rev | line source |
---|---|
0 | 1 module canvas; |
2 | |
3 import std.stdio; | |
4 import std.math; | |
5 | |
6 import cairo.Context; | |
7 import cairo.Surface; | |
8 | |
9 import gdk.Drawable; | |
10 | |
11 import gtk.Widget; | |
12 import gtk.Toolbar; | |
13 import gtk.Table; | |
14 import gtk.HRuler; | |
15 import gtk.VRuler; | |
16 import gtk.Range; | |
17 import gtk.HScrollbar; | |
18 import gtk.VScrollbar; | |
19 import gtk.DrawingArea; | |
20 import gtk.Adjustment; | |
21 | |
22 import tk.misc; | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
23 import tk.geometry; |
0 | 24 import tk.types; |
25 import tk.events; | |
26 import tk.gtk_support; | |
27 | |
28 private import gtkc.gtk; | |
29 | |
30 import icanvas; | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
31 import cairo_support; |
0 | 32 |
33 // x and y run right and up respectively | |
34 | |
10 | 35 class Canvas : Table, Viewport { |
11 | 36 this(in Layer[] layers, EventHandler event_handler, in double ppi) { |
0 | 37 super(3, 3, 0); |
38 | |
11 | 39 mLayers = layers.dup; |
0 | 40 mEventHandler = event_handler; |
13 | 41 mPPI = ppi; |
0 | 42 |
43 const double MM_PER_INCH = 25.4; | |
13 | 44 mZoom = 0.25 * mPPI / MM_PER_INCH; |
45 | |
46 // Take the union of the bounds of each layer to | |
47 // determine the canvas size | |
11 | 48 |
15 | 49 Rectangle layer_bounds = Rectangle.DEFAULT; |
0 | 50 |
11 | 51 foreach (ref layer; mLayers) { |
15 | 52 layer_bounds = layer_bounds | layer.bounds; |
11 | 53 } |
0 | 54 |
15 | 55 assert(layer_bounds.valid); |
13 | 56 |
15 | 57 mCanvasBounds = layer_bounds.moved(-layer_bounds.size).expanded(2.0 * layer_bounds.size); |
58 mViewCentre = mCanvasBounds.centre; | |
0 | 59 |
13 | 60 // Create our child widgets and register callbacks |
61 | |
62 mHRuler = new HRuler; | |
0 | 63 attach(mHRuler, |
64 1, 2, | |
65 0, 1, | |
66 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK, | |
67 0, 0); | |
68 mHRuler.setMetric(GtkMetricType.CENTIMETERS); | |
69 | |
13 | 70 mVRuler = new VRuler; |
0 | 71 attach(mVRuler, |
72 0, 1, | |
73 1, 2, | |
74 AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND, | |
75 0, 0); | |
76 mVRuler.setMetric(GtkMetricType.CENTIMETERS); | |
77 | |
13 | 78 mDrawingArea = new DrawingArea; |
0 | 79 mDrawingArea.addOnRealize(&onRealize); |
80 mDrawingArea.addOnConfigure(&onConfigure); | |
81 mDrawingArea.addOnExpose(&onExpose); | |
5 | 82 mDrawingArea.addOnButtonPress(&onButtonPress); |
83 mDrawingArea.addOnButtonRelease(&onButtonRelease); | |
0 | 84 mDrawingArea.addOnKeyPress(&onKeyEvent); |
85 mDrawingArea.addOnKeyRelease(&onKeyEvent); | |
86 mDrawingArea.addOnMotionNotify(&onMotionNotify); | |
87 mDrawingArea.addOnScroll(&onScroll); | |
88 attach(mDrawingArea, | |
89 1, 2, | |
90 1, 2, | |
91 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.FILL | AttachOptions.EXPAND, | |
92 0, 0); | |
93 | |
94 // value, lower, upper, step-inc, page-inc, page-size | |
95 // Give the adjustments dummy values until we receive a configure | |
12 | 96 mHAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.5); |
0 | 97 mHAdjustment.addOnValueChanged(&onValueChanged); |
98 mHScrollbar = new HScrollbar(mHAdjustment); | |
99 attach(mHScrollbar, | |
100 1, 2, | |
101 2, 3, | |
102 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK, | |
103 0, 0); | |
104 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
105 mVAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.5); |
0 | 106 mVAdjustment.addOnValueChanged(&onValueChanged); |
107 mVScrollbar = new VScrollbar(mVAdjustment); | |
108 attach(mVScrollbar, | |
109 2, 3, | |
110 1, 2, | |
111 AttachOptions.SHRINK, | |
112 AttachOptions.FILL | AttachOptions.EXPAND, | |
113 0, 0); | |
114 } | |
115 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
116 override void rel_zoom(Point screen_datum, double factor) { |
0 | 117 // Work out pixel distance from current centre to datum, |
118 // Do the zoom, then work out the new centre that keeps the | |
119 // pixel distance the same | |
120 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
121 Point old_model_datum = screen_to_model(screen_datum); |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
122 Vector pixel_distance = model_to_screen(old_model_datum - mViewCentre); |
0 | 123 mZoom = clamp_zoom(factor * mZoom); |
124 mViewCentre = old_model_datum - screen_to_model(pixel_distance); | |
125 | |
12 | 126 //Point new_model_datum = screen_to_model(screen_datum); |
0 | 127 |
13 | 128 update_adjustments; |
0 | 129 //update_rulers(new_model_datum); |
13 | 130 update_rulers; |
131 queueDraw; | |
0 | 132 } |
133 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
134 override void rel_pan(Vector screen_displacement) { |
0 | 135 mViewCentre = mViewCentre + screen_to_model(screen_displacement); |
136 | |
13 | 137 update_adjustments; |
138 update_rulers; | |
139 queueDraw; | |
140 } | |
141 | |
142 override double zoom() const { | |
143 return mZoom; | |
144 } | |
145 | |
146 override Point model_to_screen(Point model) const { | |
147 return Point.DEFAULT + mViewSize / 2.0 + mZoom * (model - mViewCentre); | |
148 } | |
149 | |
150 override Point screen_to_model(Point screen) const { | |
151 return mViewCentre + (screen - mViewSize / 2.0 - Point.DEFAULT) / mZoom; | |
0 | 152 } |
153 | |
13 | 154 override Vector model_to_screen(Vector model) const { |
155 return mZoom * model; | |
156 } | |
157 | |
158 override Vector screen_to_model(Vector screen) const { | |
159 return screen / mZoom; | |
160 } | |
161 | |
162 override double model_to_screen(double model) const { | |
163 return mZoom * model; | |
164 } | |
165 | |
166 override double screen_to_model(double screen) const { | |
167 return screen / mZoom; | |
168 } | |
169 | |
170 override Rectangle model_to_screen(Rectangle model) const { | |
171 return Rectangle(model_to_screen(model.position), model_to_screen(model.size)); | |
172 } | |
173 | |
174 override Rectangle screen_to_model(Rectangle model) const { | |
175 return Rectangle(screen_to_model(model.position), screen_to_model(model.size)); | |
176 } | |
10 | 177 |
0 | 178 private { |
179 | |
180 void onRealize(Widget widget) { | |
181 assert(widget is mDrawingArea); | |
182 //writefln("Got realize\n"); | |
183 } | |
184 | |
185 bool onConfigure(GdkEventConfigure * event, Widget widget) { | |
186 assert(widget is mDrawingArea); | |
187 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
188 mViewSize = Vector(cast(double)event.width, cast(double)event.height); |
13 | 189 update_adjustments; |
190 update_rulers; | |
0 | 191 |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
192 return true; |
0 | 193 } |
194 | |
195 bool onExpose(GdkEventExpose * event, Widget widget) { | |
196 assert(widget is mDrawingArea); | |
197 | |
13 | 198 Drawable dr = mDrawingArea.getWindow; |
0 | 199 |
200 int width, height; | |
201 dr.getSize(width, height); | |
202 //writefln("Got expose %dx%d\n", width, height); | |
203 | |
12 | 204 scope model_cr = new Context(dr); |
205 scope screen_cr = new Context(dr); | |
206 | |
11 | 207 Rectangle damage = |
208 event is null ? | |
209 Rectangle(Point(0.0, 0.0), Vector(cast(double)width, cast(double)height)) : | |
210 Rectangle(Point(cast(double)event.area.x, cast(double)event.area.y), | |
211 Vector(cast(double)event.area.width, cast(double)event.area.height)); | |
0 | 212 |
13 | 213 model_cr.save; screen_cr.save; { |
12 | 214 // Setup model context |
215 | |
216 rectangle(model_cr, damage); | |
13 | 217 model_cr.clip; |
12 | 218 |
219 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct(); | |
220 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct(); | |
221 | |
15 | 222 model_cr.translate(mCanvasBounds.min_corner.x - gtk_adjustment_get_value(h_gtkAdjustment), |
223 mCanvasBounds.min_corner.y - gtk_adjustment_get_value(v_gtkAdjustment)); | |
13 | 224 model_cr.scale(mZoom, -mZoom); |
0 | 225 |
12 | 226 // Setup screen context |
227 | |
13 | 228 screen_cr.translate(0.0, mViewSize.y); |
229 screen_cr.scale(1.0, -1.0); | |
12 | 230 rectangle(screen_cr, damage); |
13 | 231 screen_cr.clip; |
12 | 232 |
233 // Fill the background | |
234 | |
13 | 235 screen_cr.save; { |
11 | 236 // Make the window light grey |
12 | 237 screen_cr.setSourceRgba(1.0, 1.0, 1.0, 0.7); |
238 rectangle(screen_cr, damage); | |
13 | 239 screen_cr.fill; |
240 } screen_cr.restore; | |
12 | 241 |
242 // Draw each layer | |
0 | 243 |
11 | 244 foreach(ref layer; mLayers) { |
13 | 245 model_cr.save; screen_cr.save; { |
12 | 246 layer.draw(this, damage, model_cr, screen_cr); |
13 | 247 } screen_cr.restore; model_cr.restore; |
11 | 248 } |
13 | 249 } screen_cr.restore; model_cr.restore; |
0 | 250 |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
251 return true; |
0 | 252 } |
253 | |
5 | 254 bool onButtonPress(GdkEventButton * event, Widget widget) { |
0 | 255 assert(widget is mDrawingArea); |
256 //writefln("Got button event\n"); | |
257 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
258 Point screen_point = Point(event.x + 0.5, event.y + 0.5); |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
259 Point model_point = screen_to_model(screen_point); |
0 | 260 |
7 | 261 auto button_event = new ButtonEvent(gtk2tk_button_action(event.type), |
262 gtk2tk_button_name(event.button), | |
0 | 263 screen_point, |
264 model_point, | |
265 gtk2tk_mask(event.state)); | |
266 | |
5 | 267 mEventHandler.handle_button_press(this, button_event); |
268 | |
269 return true; | |
270 } | |
271 | |
272 bool onButtonRelease(GdkEventButton * event, Widget widget) { | |
273 assert(widget is mDrawingArea); | |
274 //writefln("Got button event\n"); | |
275 | |
276 Point screen_point = Point(event.x + 0.5, event.y + 0.5); | |
277 Point model_point = screen_to_model(screen_point); | |
278 | |
7 | 279 auto button_event = new ButtonEvent(gtk2tk_button_action(event.type), |
280 gtk2tk_button_name(event.button), | |
5 | 281 screen_point, |
282 model_point, | |
283 gtk2tk_mask(event.state)); | |
284 | |
285 mEventHandler.handle_button_release(this, button_event); | |
0 | 286 |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
287 return true; |
0 | 288 } |
289 | |
290 bool onKeyEvent(GdkEventKey * event, Widget widget) { | |
291 assert(widget is mDrawingArea); | |
292 //writefln("Got key event\n"); | |
293 | |
294 //auto key_event = new KeyEvent("", | |
295 // mEventHandle.handle_key(key_event); | |
296 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
297 return true; |
0 | 298 } |
299 | |
300 bool onMotionNotify(GdkEventMotion * event, Widget widget) { | |
301 assert(widget is mDrawingArea); | |
302 //writefln("Got motion notify\n"); | |
303 gtk_widget_event(mHRuler.getWidgetStruct(), cast(GdkEvent *)event); | |
304 gtk_widget_event(mVRuler.getWidgetStruct(), cast(GdkEvent *)event); | |
305 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
306 Point screen_point = Point(event.x + 0.5, event.y + 0.5); |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
307 Point model_point = screen_to_model(screen_point); |
0 | 308 |
309 auto motion_event = new MotionEvent(screen_point, | |
310 model_point, | |
311 gtk2tk_mask(event.state)); | |
312 | |
313 mEventHandler.handle_motion(this, motion_event); | |
314 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
315 return true; |
0 | 316 } |
317 | |
318 bool onScroll(GdkEventScroll * event, Widget widget) { | |
319 assert(widget is mDrawingArea); | |
320 //writefln("Got scroll\n"); | |
321 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
322 Point screen_point = Point(event.x + 0.5, event.y + 0.5); |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
323 Point model_point = screen_to_model(screen_point); |
0 | 324 |
325 auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction), | |
326 screen_point, | |
327 model_point, | |
328 gtk2tk_mask(event.state)); | |
329 | |
330 mEventHandler.handle_scroll(this, scroll_event); | |
331 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
332 return true; |
0 | 333 } |
334 | |
335 void onValueChanged(Adjustment adjustment) { | |
13 | 336 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct; |
337 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct; | |
0 | 338 |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
339 Point view_left_bottom = Point(gtk_adjustment_get_value(h_gtkAdjustment), |
14 | 340 gtk_adjustment_get_value(v_gtkAdjustment)); |
9 | 341 //writefln("%s", view_left_bottom); |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
342 Vector model_size = screen_to_model(mViewSize); |
0 | 343 mViewCentre = view_left_bottom + model_size / 2.0; |
344 | |
13 | 345 update_rulers; |
0 | 346 |
13 | 347 queueDraw; |
0 | 348 } |
349 | |
350 void update_rulers() { | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
351 Vector model_size = screen_to_model(mViewSize); |
0 | 352 |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
353 Point view_left_bottom = mViewCentre - model_size / 2.0; |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
354 Point view_right_top = mViewCentre + model_size / 2.0; |
0 | 355 |
12 | 356 double lower, upper, position, max_size; |
357 | |
358 mHRuler.getRange(lower, upper, position, max_size); | |
0 | 359 mHRuler.setRange(view_left_bottom.x, |
360 view_right_top.x, | |
12 | 361 position, |
14 | 362 mZoom * 10.0); |
12 | 363 |
364 mVRuler.getRange(lower, upper, position, max_size); | |
0 | 365 mVRuler.setRange(view_right_top.y, |
366 view_left_bottom.y, | |
14 | 367 position, |
368 mZoom * 10.0); | |
0 | 369 } |
370 | |
371 void update_adjustments() { | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
372 Vector model_size = screen_to_model(mViewSize); |
0 | 373 |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
374 Point view_left_bottom = mViewCentre - model_size / 2.0; |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
375 Point view_right_top = mViewCentre + model_size / 2.0; |
0 | 376 |
377 // Adjust the canvas size if necessary | |
15 | 378 mCanvasBounds = Rectangle(min_extents(mCanvasBounds.min_corner, view_left_bottom), |
379 max_extents(mCanvasBounds.max_corner, view_right_top)); | |
0 | 380 |
381 // Update the adjustments | |
382 | |
383 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct(); | |
384 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct(); | |
385 | |
15 | 386 gtk_adjustment_set_lower(h_gtkAdjustment, mCanvasBounds.min_corner.x); |
387 gtk_adjustment_set_upper(h_gtkAdjustment, mCanvasBounds.max_corner.y); | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
388 gtk_adjustment_set_value(h_gtkAdjustment, view_left_bottom.x); |
15 | 389 gtk_adjustment_set_step_increment(h_gtkAdjustment, mCanvasBounds.size.x / 16.0); |
390 gtk_adjustment_set_page_increment(h_gtkAdjustment, mCanvasBounds.size.x / 4.0); | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
391 gtk_adjustment_set_page_size(h_gtkAdjustment, model_size.x); |
0 | 392 |
15 | 393 gtk_adjustment_set_lower(v_gtkAdjustment, mCanvasBounds.min_corner.y); |
394 gtk_adjustment_set_upper(v_gtkAdjustment, mCanvasBounds.max_corner.y); | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
395 gtk_adjustment_set_value(v_gtkAdjustment, view_left_bottom.y); |
15 | 396 gtk_adjustment_set_step_increment(v_gtkAdjustment, mCanvasBounds.size.y / 16.0); |
397 gtk_adjustment_set_page_increment(v_gtkAdjustment, mCanvasBounds.size.y / 4.0); | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
398 gtk_adjustment_set_page_size(v_gtkAdjustment, model_size.y); |
0 | 399 |
13 | 400 mHAdjustment.changed; |
401 mHAdjustment.valueChanged; | |
402 mVAdjustment.changed; | |
403 mVAdjustment.valueChanged; | |
0 | 404 } |
405 | |
9 | 406 double clamp_zoom(double zoom) { return clamp(zoom, 0.02, 50.0); } |
0 | 407 |
408 // Model units are in millimetres | |
409 // Screen units are in pixels | |
410 | |
11 | 411 double mZoom; // pixels-per-model-unit (mm) |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
412 Point mViewCentre; // model: where in the model is the centre of our view |
15 | 413 Rectangle mCanvasBounds; // model: |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
414 Vector mViewSize; // screen: size of view window in pixels |
0 | 415 |
10 | 416 // Child widgets: |
0 | 417 HRuler mHRuler; |
418 VRuler mVRuler; | |
419 DrawingArea mDrawingArea; | |
420 Adjustment mHAdjustment; | |
421 HScrollbar mHScrollbar; | |
422 Adjustment mVAdjustment; | |
423 VScrollbar mVScrollbar; | |
11 | 424 |
425 // Layers: | |
426 Layer[] mLayers; | |
13 | 427 EventHandler mEventHandler; |
428 double mPPI; | |
0 | 429 } |
430 } |