Mercurial > projects > doodle
annotate canvas.d @ 11:fb571a3b1f0d
Checkpoint
author | "David Bryant <bagnose@gmail.com>" |
---|---|
date | Sat, 11 Jul 2009 23:32:22 +0930 |
parents | 71ca82e0eb76 |
children | a093c4fbdd43 |
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; |
41 | |
42 const double MM_PER_INCH = 25.4; | |
11 | 43 mZoom = 0.25 * ppi / MM_PER_INCH; |
44 | |
45 Rectangle total_layer_bounds; | |
0 | 46 |
11 | 47 foreach (ref layer; mLayers) { |
48 total_layer_bounds = total_layer_bounds | layer.bounds; | |
49 } | |
0 | 50 |
11 | 51 mCanvasLeftBottom = total_layer_bounds.min_corner - total_layer_bounds.size; |
52 mCanvasRightTop = total_layer_bounds.max_corner + total_layer_bounds.size; | |
53 mViewCentre = mCanvasLeftBottom + (mCanvasRightTop - mCanvasLeftBottom) / 2.0; | |
0 | 54 |
55 mHRuler = new HRuler(); | |
56 attach(mHRuler, | |
57 1, 2, | |
58 0, 1, | |
59 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK, | |
60 0, 0); | |
61 mHRuler.setMetric(GtkMetricType.CENTIMETERS); | |
62 | |
63 mVRuler = new VRuler(); | |
64 attach(mVRuler, | |
65 0, 1, | |
66 1, 2, | |
67 AttachOptions.SHRINK, AttachOptions.FILL | AttachOptions.EXPAND, | |
68 0, 0); | |
69 mVRuler.setMetric(GtkMetricType.CENTIMETERS); | |
70 | |
71 mDrawingArea = new DrawingArea(); | |
72 mDrawingArea.addOnRealize(&onRealize); | |
73 mDrawingArea.addOnConfigure(&onConfigure); | |
74 mDrawingArea.addOnExpose(&onExpose); | |
5 | 75 mDrawingArea.addOnButtonPress(&onButtonPress); |
76 mDrawingArea.addOnButtonRelease(&onButtonRelease); | |
0 | 77 mDrawingArea.addOnKeyPress(&onKeyEvent); |
78 mDrawingArea.addOnKeyRelease(&onKeyEvent); | |
79 mDrawingArea.addOnMotionNotify(&onMotionNotify); | |
80 mDrawingArea.addOnScroll(&onScroll); | |
81 attach(mDrawingArea, | |
82 1, 2, | |
83 1, 2, | |
84 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.FILL | AttachOptions.EXPAND, | |
85 0, 0); | |
86 | |
87 // value, lower, upper, step-inc, page-inc, page-size | |
88 // Give the adjustments dummy values until we receive a configure | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
89 mHAdjustment = new Adjustment(0.25, 0.0, 1.0, 0.2, 0.5, 0.5); |
0 | 90 mHAdjustment.addOnValueChanged(&onValueChanged); |
91 mHScrollbar = new HScrollbar(mHAdjustment); | |
92 attach(mHScrollbar, | |
93 1, 2, | |
94 2, 3, | |
95 AttachOptions.FILL | AttachOptions.EXPAND, AttachOptions.SHRINK, | |
96 0, 0); | |
97 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
98 mVAdjustment = new Adjustment(0.0, 0.0, 1.0, 0.2, 0.5, 0.5); |
0 | 99 mVAdjustment.addOnValueChanged(&onValueChanged); |
100 mVScrollbar = new VScrollbar(mVAdjustment); | |
101 attach(mVScrollbar, | |
102 2, 3, | |
103 1, 2, | |
104 AttachOptions.SHRINK, | |
105 AttachOptions.FILL | AttachOptions.EXPAND, | |
106 0, 0); | |
107 } | |
108 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
109 override void rel_zoom(Point screen_datum, double factor) { |
0 | 110 // Work out pixel distance from current centre to datum, |
111 // Do the zoom, then work out the new centre that keeps the | |
112 // pixel distance the same | |
113 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
114 Point old_model_datum = screen_to_model(screen_datum); |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
115 Vector pixel_distance = model_to_screen(old_model_datum - mViewCentre); |
0 | 116 mZoom = clamp_zoom(factor * mZoom); |
117 mViewCentre = old_model_datum - screen_to_model(pixel_distance); | |
118 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
119 Point new_model_datum = screen_to_model(screen_datum); |
0 | 120 |
121 update_adjustments(); | |
122 //update_rulers(new_model_datum); | |
123 update_rulers(); | |
124 queueDraw(); | |
125 } | |
126 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
127 override void rel_pan(Vector screen_displacement) { |
0 | 128 mViewCentre = mViewCentre + screen_to_model(screen_displacement); |
129 | |
130 update_adjustments(); | |
131 update_rulers(); | |
132 queueDraw(); | |
133 } | |
134 | |
11 | 135 override double zoom() const { return mZoom; } |
136 override Point model_to_screen(Point model) const { return Point.DEFAULT + mViewSize / 2.0 + mZoom * (model - mViewCentre); } | |
137 override Point screen_to_model(Point screen) const { return mViewCentre + (screen - mViewSize / 2.0 - Point.DEFAULT) / mZoom; } | |
10 | 138 override Vector model_to_screen(Vector model) const { return mZoom * model; } |
139 override Vector screen_to_model(Vector screen) const { return screen / mZoom; } | |
140 override double model_to_screen(double model) const { return mZoom * model; } | |
141 override double screen_to_model(double screen) const { return screen / mZoom; } | |
11 | 142 override Rectangle model_to_screen(Rectangle model) const { return Rectangle(model_to_screen(model.position), model_to_screen(model.size)); } |
143 override Rectangle screen_to_model(Rectangle model) const { return Rectangle(screen_to_model(model.position), screen_to_model(model.size)); } | |
10 | 144 |
0 | 145 private { |
146 | |
147 void onRealize(Widget widget) { | |
148 assert(widget is mDrawingArea); | |
149 //writefln("Got realize\n"); | |
150 } | |
151 | |
152 bool onConfigure(GdkEventConfigure * event, Widget widget) { | |
153 assert(widget is mDrawingArea); | |
154 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
155 mViewSize = Vector(cast(double)event.width, cast(double)event.height); |
0 | 156 update_adjustments(); |
157 update_rulers(); | |
158 | |
159 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
160 return true; |
0 | 161 } |
162 | |
163 bool onExpose(GdkEventExpose * event, Widget widget) { | |
164 assert(widget is mDrawingArea); | |
165 | |
166 Drawable dr = mDrawingArea.getWindow(); | |
167 | |
168 int width, height; | |
169 dr.getSize(width, height); | |
170 //writefln("Got expose %dx%d\n", width, height); | |
171 | |
5 | 172 scope cr = new Context(dr); |
11 | 173 Rectangle damage = |
174 event is null ? | |
175 Rectangle(Point(0.0, 0.0), Vector(cast(double)width, cast(double)height)) : | |
176 Rectangle(Point(cast(double)event.area.x, cast(double)event.area.y), | |
177 Vector(cast(double)event.area.width, cast(double)event.area.height)); | |
0 | 178 |
11 | 179 cr.save(); { |
180 rectangle(cr, damage); | |
0 | 181 cr.clip(); |
182 | |
11 | 183 cr.save(); { |
184 // Make the window light grey | |
185 cr.setSourceRgba(1.0, 1.0, 1.0, 0.7); | |
186 rectangle(cr, damage); | |
187 cr.fill(); | |
188 } cr.restore(); | |
0 | 189 |
11 | 190 foreach(ref layer; mLayers) { |
191 layer.draw(this, damage, cr); | |
192 } | |
193 } cr.restore(); | |
0 | 194 |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
195 return true; |
0 | 196 } |
197 | |
5 | 198 bool onButtonPress(GdkEventButton * event, Widget widget) { |
0 | 199 assert(widget is mDrawingArea); |
200 //writefln("Got button event\n"); | |
201 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
202 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
|
203 Point model_point = screen_to_model(screen_point); |
0 | 204 |
7 | 205 auto button_event = new ButtonEvent(gtk2tk_button_action(event.type), |
206 gtk2tk_button_name(event.button), | |
0 | 207 screen_point, |
208 model_point, | |
209 gtk2tk_mask(event.state)); | |
210 | |
5 | 211 mEventHandler.handle_button_press(this, button_event); |
212 | |
213 return true; | |
214 } | |
215 | |
216 bool onButtonRelease(GdkEventButton * event, Widget widget) { | |
217 assert(widget is mDrawingArea); | |
218 //writefln("Got button event\n"); | |
219 | |
220 Point screen_point = Point(event.x + 0.5, event.y + 0.5); | |
221 Point model_point = screen_to_model(screen_point); | |
222 | |
7 | 223 auto button_event = new ButtonEvent(gtk2tk_button_action(event.type), |
224 gtk2tk_button_name(event.button), | |
5 | 225 screen_point, |
226 model_point, | |
227 gtk2tk_mask(event.state)); | |
228 | |
229 mEventHandler.handle_button_release(this, button_event); | |
0 | 230 |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
231 return true; |
0 | 232 } |
233 | |
234 bool onKeyEvent(GdkEventKey * event, Widget widget) { | |
235 assert(widget is mDrawingArea); | |
236 //writefln("Got key event\n"); | |
237 | |
238 //auto key_event = new KeyEvent("", | |
239 // mEventHandle.handle_key(key_event); | |
240 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
241 return true; |
0 | 242 } |
243 | |
244 bool onMotionNotify(GdkEventMotion * event, Widget widget) { | |
245 assert(widget is mDrawingArea); | |
246 //writefln("Got motion notify\n"); | |
247 gtk_widget_event(mHRuler.getWidgetStruct(), cast(GdkEvent *)event); | |
248 gtk_widget_event(mVRuler.getWidgetStruct(), cast(GdkEvent *)event); | |
249 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
250 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
|
251 Point model_point = screen_to_model(screen_point); |
0 | 252 |
253 auto motion_event = new MotionEvent(screen_point, | |
254 model_point, | |
255 gtk2tk_mask(event.state)); | |
256 | |
257 mEventHandler.handle_motion(this, motion_event); | |
258 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
259 return true; |
0 | 260 } |
261 | |
262 bool onScroll(GdkEventScroll * event, Widget widget) { | |
263 assert(widget is mDrawingArea); | |
264 //writefln("Got scroll\n"); | |
265 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
266 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
|
267 Point model_point = screen_to_model(screen_point); |
0 | 268 |
269 auto scroll_event = new ScrollEvent(gtk2tk_direction(event.direction), | |
270 screen_point, | |
271 model_point, | |
272 gtk2tk_mask(event.state)); | |
273 | |
274 mEventHandler.handle_scroll(this, scroll_event); | |
275 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
276 return true; |
0 | 277 } |
278 | |
279 void onValueChanged(Adjustment adjustment) { | |
280 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct(); | |
281 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct(); | |
282 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
283 Point view_left_bottom = Point(gtk_adjustment_get_value(h_gtkAdjustment), |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
284 gtk_adjustment_get_value(v_gtkAdjustment)); |
9 | 285 //writefln("%s", view_left_bottom); |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
286 Vector model_size = screen_to_model(mViewSize); |
0 | 287 mViewCentre = view_left_bottom + model_size / 2.0; |
288 | |
289 update_rulers(); | |
290 | |
291 queueDraw(); | |
292 } | |
293 | |
294 void update_rulers() { | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
295 Vector model_size = screen_to_model(mViewSize); |
0 | 296 |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
297 Point view_left_bottom = mViewCentre - model_size / 2.0; |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
298 Point view_right_top = mViewCentre + model_size / 2.0; |
0 | 299 |
300 mHRuler.setRange(view_left_bottom.x, | |
301 view_right_top.x, | |
302 0.0, // TODO preserve the value | |
303 mZoom * 2000.0); | |
304 mVRuler.setRange(view_right_top.y, | |
305 view_left_bottom.y, | |
306 0.0, | |
307 mZoom * 2000.0); | |
308 } | |
309 | |
310 void update_adjustments() { | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
311 Vector model_size = screen_to_model(mViewSize); |
0 | 312 |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
313 Point view_left_bottom = mViewCentre - model_size / 2.0; |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
314 Point view_right_top = mViewCentre + model_size / 2.0; |
0 | 315 |
316 // Adjust the canvas size if necessary | |
317 mCanvasLeftBottom = min_extents(mCanvasLeftBottom, view_left_bottom); | |
318 mCanvasRightTop = max_extents(mCanvasRightTop, view_right_top); | |
319 | |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
320 Vector canvas_size = mCanvasRightTop - mCanvasLeftBottom; |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
321 Vector page_size = mPageRightTop - mPageLeftBottom; |
0 | 322 |
323 // Update the adjustments | |
324 | |
325 GtkAdjustment * h_gtkAdjustment = mHAdjustment.getAdjustmentStruct(); | |
326 GtkAdjustment * v_gtkAdjustment = mVAdjustment.getAdjustmentStruct(); | |
327 | |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
328 gtk_adjustment_set_lower(h_gtkAdjustment, mCanvasLeftBottom.x); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
329 gtk_adjustment_set_upper(h_gtkAdjustment, mCanvasRightTop.x); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
330 gtk_adjustment_set_value(h_gtkAdjustment, view_left_bottom.x); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
331 gtk_adjustment_set_step_increment(h_gtkAdjustment, canvas_size.x / 10.0); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
332 gtk_adjustment_set_page_increment(h_gtkAdjustment, canvas_size.x / 5.0); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
333 gtk_adjustment_set_page_size(h_gtkAdjustment, model_size.x); |
0 | 334 |
2
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
335 gtk_adjustment_set_lower(v_gtkAdjustment, mCanvasLeftBottom.y); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
336 gtk_adjustment_set_upper(v_gtkAdjustment, mCanvasRightTop.y); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
337 gtk_adjustment_set_value(v_gtkAdjustment, view_left_bottom.y); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
338 gtk_adjustment_set_step_increment(v_gtkAdjustment, canvas_size.y / 10.0); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
339 gtk_adjustment_set_page_increment(v_gtkAdjustment, canvas_size.y / 5.0); |
d6f44347373d
* Switched over to geometry done with structs instead of classes.
David Bryant <daveb@acres.com.au>
parents:
0
diff
changeset
|
340 gtk_adjustment_set_page_size(v_gtkAdjustment, model_size.y); |
0 | 341 |
342 mHAdjustment.changed(); | |
343 mHAdjustment.valueChanged(); | |
344 mVAdjustment.changed(); | |
345 mVAdjustment.valueChanged(); | |
346 } | |
347 | |
9 | 348 double clamp_zoom(double zoom) { return clamp(zoom, 0.02, 50.0); } |
0 | 349 |
10 | 350 EventHandler mEventHandler; |
0 | 351 |
352 // Model units are in millimetres | |
353 // Screen units are in pixels | |
354 | |
11 | 355 double mZoom; // pixels-per-model-unit (mm) |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
356 Point mViewCentre; // model: where in the model is the centre of our view |
0 | 357 |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
358 Point mCanvasLeftBottom; // model: bottom left corner of canvas |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
359 Point mCanvasRightTop; // model: top right corner of canvas |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
360 Point mPageLeftBottom; // model: bottom left corner of page |
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
361 Point mPageRightTop; // model: top right corner of page |
0 | 362 |
3
7d57cae10805
Renamed geometry2 to geometry
David Bryant <daveb@acres.com.au>
parents:
2
diff
changeset
|
363 Vector mViewSize; // screen: size of view window in pixels |
0 | 364 |
10 | 365 // Child widgets: |
0 | 366 HRuler mHRuler; |
367 VRuler mVRuler; | |
368 DrawingArea mDrawingArea; | |
369 Adjustment mHAdjustment; | |
370 HScrollbar mHScrollbar; | |
371 Adjustment mVAdjustment; | |
372 VScrollbar mVScrollbar; | |
11 | 373 |
374 // Layers: | |
375 Layer[] mLayers; | |
0 | 376 } |
377 } |