.
1 /*
2 Copyright (C) 2015 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Math;
16 using Cairo;
17
18 namespace BirdFont {
19
20 /** Create Beziér curves. */
21 public class BezierTool : Tool {
22
23 public const uint NONE = 0;
24 public const uint MOVE_POINT = 1;
25 public const uint MOVE_HANDLES = 2;
26 public const uint MOVE_LAST_HANDLE_RIGHT = 3;
27 public const uint MOVE_LAST_HANDLE_LEFT = 4;
28 public const uint MOVE_FIRST_HANDLE = 5;
29 public const uint MOVE_HANDLE_ON_AXIS = 6;
30
31 uint state = NONE;
32
33 Path current_path = new Path ();
34 EditPoint current_point = new EditPoint ();
35
36 int last_x = 0;
37 int last_y = 0;
38
39 double last_release_time = 0;
40 double last_press_time = 0;
41 bool button_down = false;
42
43 /** Create a corner instead of a control point with reflective handles. */
44 bool corner_node = false;
45
46 /** Swap right and left handles if orientation changes */
47 bool swap = false;
48
49 public BezierTool (string name) {
50 base (name, "");
51
52 select_action.connect ((self) => {
53 state = NONE;
54 MainWindow.set_cursor (NativeWindow.VISIBLE);
55 });
56
57 deselect_action.connect ((self) => {
58 state = NONE;
59 MainWindow.set_cursor (NativeWindow.VISIBLE);
60 });
61
62 press_action.connect ((self, b, x, y) => {
63 press (b, x, y);
64 });
65
66 double_click_action.connect ((self, b, x, y) => {
67 });
68
69 release_action.connect ((self, b, x, y) => {
70 release (b, x, y);
71 });
72
73 move_action.connect ((self, x, y) => {
74 move (x, y);
75 });
76
77 key_press_action.connect ((self, keyval) => {
78 });
79
80 key_release_action.connect ((self, keyval) => {
81 });
82
83 draw_action.connect ((tool, cairo_context, glyph) => {
84 if (PenTool.can_join (current_point)) {
85 PenTool.draw_join_icon (cairo_context, last_x, last_y);
86 }
87 });
88 }
89
90 public override string get_tip () {
91 string tip = t_ ("Create Beziér curves") + "\n";
92
93 tip += HiddenTools.bezier_line.get_key_binding ();
94 tip += " - ";
95 tip += t_ ("line") + "\n";
96
97 tip += HiddenTools.bezier_corner.get_key_binding ();
98 tip += " - ";
99 tip += t_ ("corner") + "\n";
100
101 tip += HiddenTools.move_along_axis.get_key_binding ();
102 tip += " - ";
103 tip += t_ ("on axis") + "\n";
104
105 return tip;
106 }
107
108 public void press (int b, int x, int y) {
109 Glyph g = MainWindow.get_current_glyph ();
110 double px, py;
111 Path? p;
112 Path path;
113
114 if (button_down) {
115 warning ("Discarding event.");
116 return;
117 }
118
119 button_down = true;
120
121 if (state == MOVE_HANDLES
122 || state == MOVE_LAST_HANDLE_RIGHT
123 || state == MOVE_LAST_HANDLE_LEFT) {
124 return;
125 }
126
127 if (b == 2) {
128 if (g.is_open ()) {
129 stop_drawing ();
130 g.close_path ();
131 } else {
132 g.open_path ();
133 }
134
135 MainWindow.set_cursor (NativeWindow.VISIBLE);
136 state = NONE;
137
138 return;
139 }
140
141 // ignore double clicks
142 if ((GLib.get_real_time () - last_press_time) / 1000000.0 < 0.2) {
143 last_press_time = GLib.get_real_time ();
144 return;
145 }
146 last_press_time = GLib.get_real_time ();
147
148 g.store_undo_state ();
149
150 PenTool.update_orientation ();
151
152 MainWindow.set_cursor (NativeWindow.HIDDEN);
153 g.open_path ();
154
155 px = Glyph.path_coordinate_x (x);
156 py = Glyph.path_coordinate_y (y);
157
158 if (GridTool.is_visible ()) {
159 GridTool.tie_coordinate (ref px, ref py);
160 }
161
162 if (state == MOVE_HANDLE_ON_AXIS) {
163 current_point = current_path.add (px, py);
164 current_path.hide_end_handle = true;
165 current_point.get_left_handle ().convert_to_line ();
166 current_path.recalculate_linear_handles_for_point (current_point);
167 set_point_type ();
168 corner_node = false;
169 state = MOVE_POINT;
170 } else if (corner_node) {
171 if (current_path.is_open ()) {
172 current_point = current_path.add (px, py);
173 current_path.hide_end_handle = true;
174 current_point.get_left_handle ().convert_to_line ();
175 current_path.recalculate_linear_handles_for_point (current_point);
176 set_point_type ();
177 g.clear_active_paths ();
178 g.add_active_path (null, current_path);
179 GlyphCanvas.redraw ();
180 state = MOVE_POINT;
181 } else {
182 state = NONE;
183 }
184 } else if (state == NONE) {
185 g.open_path ();
186 current_path = new Path ();
187 current_path.reopen ();
188 current_path.hide_end_handle = true;
189 current_point = current_path.add (px, py);
190 current_point.get_left_handle ().convert_to_line ();
191 current_path.recalculate_linear_handles_for_point (current_point);
192 g.add_path (current_path);
193
194 set_point_type ();
195
196 if (StrokeTool.add_stroke) {
197 current_path.stroke = StrokeTool.stroke_width;
198 current_path.line_cap = StrokeTool.line_cap;
199 }
200
201 BirdFont.get_current_font ().touch ();
202
203 GlyphCanvas.redraw ();
204 state = MOVE_POINT;
205 } else if (state == MOVE_POINT) {
206 if (PenTool.can_join (current_point)) {
207 EditPoint first = current_path.get_first_point ();
208
209 p = PenTool.join_paths (current_point);
210 return_if_fail (p != null);
211 path = (!) p;
212 swap = path.get_first_point () != first;
213
214 if (current_path.points.size == 1) {
215 return_if_fail (path.is_open ());
216 current_path = path;
217 current_point = path.get_last_point ();
218 state = MOVE_POINT;
219 } else {
220 g.open_path ();
221 current_path = path;
222 current_point = !swap ? path.get_first_point () : path.get_last_point ();
223 state = !swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT;
224 }
225 } else {
226 state = MOVE_HANDLES;
227 }
228 }
229 }
230
231 void set_point_type () {
232 PointType pt;
233
234 pt = DrawingTools.get_selected_point_type ();
235
236 current_point.type = pt;
237 current_point.get_left_handle ().type = pt;
238 current_point.get_right_handle ().type = pt;
239 current_point.get_left_handle ().convert_to_line ();
240
241 current_point.get_right_handle ().convert_to_line ();
242 }
243
244 public void release (int b, int x, int y) {
245 double px, py;
246 Glyph g;
247
248 if (!button_down) {
249 warning ("Discarding event.");
250 return;
251 }
252
253 button_down = false;
254
255 if (state == NONE || state == MOVE_POINT) {
256 return;
257 }
258
259 convert_zero_length_handles_to_lines ();
260 corner_node = false;
261
262 // ignore double clicks
263 if ((GLib.get_real_time () - last_release_time) / 1000000.0 < 0.2) {
264 last_release_time = GLib.get_real_time ();
265 return;
266 }
267 last_release_time = GLib.get_real_time ();
268
269 px = Glyph.path_coordinate_x (x);
270 py = Glyph.path_coordinate_y (y);
271
272 if (GridTool.is_visible ()) {
273 GridTool.tie_coordinate (ref px, ref py);
274 }
275
276 g = MainWindow.get_current_glyph ();
277
278 if (state == MOVE_HANDLES) {
279 current_point = current_path.add (px, py);
280 current_path.hide_end_handle = true;
281 current_point.get_left_handle ().convert_to_line ();
282 current_path.recalculate_linear_handles_for_point (current_point);
283 set_point_type ();
284 g.clear_active_paths ();
285 g.add_active_path (null, current_path);
286
287 GlyphCanvas.redraw ();
288 state = MOVE_POINT;
289 } else if (state == MOVE_LAST_HANDLE_LEFT || state == MOVE_LAST_HANDLE_RIGHT) {
290 current_path.update_region_boundaries ();
291 g.close_path ();
292 MainWindow.set_cursor (NativeWindow.VISIBLE);
293
294 if (Path.is_counter (g.get_visible_path_list (), current_path)) {
295 current_path.force_direction (Direction.COUNTER_CLOCKWISE);
296 } else {
297 current_path.force_direction (Direction.CLOCKWISE);
298 }
299
300 current_path.reset_stroke ();
301
302 state = NONE;
303 }
304
305 convert_zero_length_handles_to_lines ();
306 }
307
308 void convert_zero_length_handles_to_lines () {
309 EditPoint first, last;
310
311 first = current_path.get_first_point ();
312
313 if (first.get_left_handle ().length == 0) {
314 first.get_left_handle ().convert_to_line ();
315 }
316
317 if (first.get_right_handle ().length == 0) {
318 first.get_right_handle ().convert_to_line ();
319 }
320
321 last = current_path.get_last_point ();
322
323 if (last.get_left_handle ().length == 0) {
324 last.get_left_handle ().convert_to_line ();
325 }
326
327 if (last.get_right_handle ().length == 0) {
328 last.get_right_handle ().convert_to_line ();
329 }
330 }
331
332 public void move (int x, int y) {
333 double px, py;
334
335 last_x = x;
336 last_y = y;
337
338 px = Glyph.path_coordinate_x (x);
339 py = Glyph.path_coordinate_y (y);
340
341 if (GridTool.is_visible ()) {
342 GridTool.tie_coordinate (ref px, ref py);
343 }
344
345 if (state == MOVE_POINT) {
346 current_point.x = px;
347 current_point.y = py;
348 current_path.hide_end_handle = true;
349 current_path.recalculate_linear_handles_for_point (current_point);
350 current_path.reset_stroke ();
351
352 if (current_point.type == PointType.QUADRATIC) {
353 current_path.create_list ();
354 current_point.get_prev ().get_right_handle ().process_connected_handle ();
355 }
356
357 GlyphCanvas.redraw ();
358 } else if (state == MOVE_HANDLES
359 || state == MOVE_LAST_HANDLE_LEFT
360 || state == MOVE_LAST_HANDLE_RIGHT) {
361
362 current_path.hide_end_handle = false;
363
364 if (!corner_node) {
365 current_point.set_reflective_handles (true);
366 current_point.convert_to_curve ();
367 }
368
369 if (state == MOVE_LAST_HANDLE_LEFT) {
370 current_point.get_left_handle ().move_to_coordinate (px, py);
371 } else {
372 current_point.get_right_handle ().move_to_coordinate (px, py);
373 }
374
375 current_path.reset_stroke ();
376 GlyphCanvas.redraw ();
377 } else if (state == MOVE_HANDLE_ON_AXIS) {
378 EditPointHandle h = current_point.get_right_handle ();
379
380 current_path.hide_end_handle = false;
381 current_point.set_reflective_handles (true);
382 current_point.convert_to_curve ();
383
384 double tied_x = 0;
385 double tied_y = 0;
386
387 PointTool.tie_angle (h.parent.x, h.parent.y,
388 px, py, out tied_x, out tied_y);
389
390 h.x = tied_x;
391 h.y = tied_y;
392
393 current_path.reset_stroke ();
394 GlyphCanvas.redraw ();
395 }
396
397 if (current_path.points.size > 0) {
398 current_path.get_first_point ().set_reflective_handles (false);
399 current_path.get_last_point ().set_reflective_handles (false);
400 }
401
402 convert_zero_length_handles_to_lines ();
403 }
404
405 public void switch_to_line_mode () {
406 int s = current_path.points.size;
407 EditPoint p;
408
409 if (s > 2) {
410 p = current_path.points.get (s - 2);
411 p.set_tie_handle (false);
412 p.set_reflective_handles (false);
413 p.get_right_handle ().convert_to_line ();
414 current_point.get_left_handle ().convert_to_line ();
415 current_path.recalculate_linear_handles_for_point (p);
416 current_path.recalculate_linear_handles_for_point (current_point);
417 current_path.reset_stroke ();
418 GlyphCanvas.redraw ();
419
420 state = MOVE_POINT;
421 }
422 }
423
424 public void stop_drawing () {
425 if (state == MOVE_POINT
426 && current_path.points.size > 0
427 && current_path.is_open ()) {
428
429 current_path.delete_last_point ();
430 current_path.reset_stroke ();
431 current_path.create_full_stroke (); // cache better stroke
432 }
433
434 state = NONE;
435 }
436
437 public override void before_undo () {
438 }
439
440 public override void after_undo () {
441 if (state != NONE) {
442 MainWindow.set_cursor (NativeWindow.VISIBLE);
443 state = NONE;
444 }
445 }
446
447 public void create_corner () {
448 Glyph g = MainWindow.get_current_glyph ();
449
450 corner_node = true;
451 g.open_path ();
452
453 if (current_path.is_open ()) {
454 current_path.delete_last_point ();
455 current_path.reset_stroke ();
456 current_point = current_path.get_last_point ();
457 current_point.set_tie_handle (false);
458 current_point.set_reflective_handles (false);
459 state = MOVE_HANDLES;
460 } else {
461 state = swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT;
462 }
463
464 current_point.set_reflective_handles (false);
465 current_point.get_right_handle ().convert_to_curve ();
466 }
467
468 public void move_handle_on_axis () {
469 state = MOVE_HANDLE_ON_AXIS;
470 }
471 }
472
473 }
474