.
1 /*
2 Copyright (C) 2015 2016 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 (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 (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 if (first.get_right_handle ().length == 0) {
313 first.convert_to_line ();
314 }
315
316 last = current_path.get_last_point ();
317 if (last.get_left_handle ().length == 0) {
318 last.convert_to_line ();
319 }
320 }
321
322 public void move (int x, int y) {
323 double px, py;
324
325 last_x = x;
326 last_y = y;
327
328 px = Glyph.path_coordinate_x (x);
329 py = Glyph.path_coordinate_y (y);
330
331 if (GridTool.is_visible ()) {
332 GridTool.tie_coordinate (ref px, ref py);
333 }
334
335 if (state == MOVE_POINT) {
336 current_point.x = px;
337 current_point.y = py;
338 current_path.hide_end_handle = true;
339 current_path.recalculate_linear_handles_for_point (current_point);
340 current_path.reset_stroke ();
341
342 if (current_point.type == PointType.QUADRATIC) {
343 current_path.create_list ();
344 current_point.get_prev ().get_right_handle ().process_connected_handle ();
345 }
346
347 GlyphCanvas.redraw ();
348 } else if (state == MOVE_HANDLES
349 || state == MOVE_LAST_HANDLE_LEFT
350 || state == MOVE_LAST_HANDLE_RIGHT) {
351
352 current_path.hide_end_handle = false;
353
354 if (!corner_node) {
355 current_point.set_reflective_handles (true);
356 current_point.convert_to_curve ();
357 }
358
359 if (state == MOVE_LAST_HANDLE_LEFT) {
360 current_point.get_left_handle ().move_to_coordinate (px, py);
361 } else {
362 current_point.get_right_handle ().move_to_coordinate (px, py);
363 }
364
365 current_path.reset_stroke ();
366 GlyphCanvas.redraw ();
367 } else if (state == MOVE_HANDLE_ON_AXIS) {
368 EditPointHandle h = current_point.get_right_handle ();
369
370 current_path.hide_end_handle = false;
371 current_point.set_reflective_handles (true);
372 current_point.convert_to_curve ();
373
374 double tied_x = 0;
375 double tied_y = 0;
376
377 PointTool.tie_angle (h.parent.x, h.parent.y,
378 px, py, out tied_x, out tied_y);
379
380 h.x = tied_x;
381 h.y = tied_y;
382
383 current_path.reset_stroke ();
384 GlyphCanvas.redraw ();
385 }
386
387 if (current_path.points.size > 0) {
388 current_path.get_first_point ().set_reflective_handles (false);
389 current_path.get_last_point ().set_reflective_handles (false);
390 }
391
392 convert_zero_length_handles_to_lines ();
393 }
394
395 public void switch_to_line_mode () {
396 int s = current_path.points.size;
397 EditPoint p;
398
399 if (s > 2) {
400 p = current_path.points.get (s - 2);
401 p.set_tie_handle (false);
402 p.set_reflective_handles (false);
403 p.get_right_handle ().convert_to_line ();
404 current_point.get_left_handle ().convert_to_line ();
405 current_path.recalculate_linear_handles_for_point (p);
406 current_path.recalculate_linear_handles_for_point (current_point);
407 current_path.reset_stroke ();
408 GlyphCanvas.redraw ();
409
410 state = MOVE_POINT;
411 }
412 }
413
414 public void stop_drawing () {
415 if (state == MOVE_POINT
416 && current_path.points.size > 0
417 && current_path.is_open ()) {
418
419 current_path.delete_last_point ();
420 current_path.reset_stroke ();
421 current_path.create_full_stroke (); // cache better stroke
422 }
423
424 state = NONE;
425 }
426
427 public override void before_undo () {
428 }
429
430 public override void after_undo () {
431 if (state != NONE) {
432 MainWindow.set_cursor (NativeWindow.VISIBLE);
433 state = NONE;
434 }
435 }
436
437 public void create_corner () {
438 Glyph g = MainWindow.get_current_glyph ();
439
440 corner_node = true;
441 g.open_path ();
442
443 if (current_path.is_open ()) {
444 current_path.delete_last_point ();
445 current_path.reset_stroke ();
446 current_point = current_path.get_last_point ();
447 current_point.set_tie_handle (false);
448 current_point.set_reflective_handles (false);
449 state = MOVE_HANDLES;
450 } else {
451 state = swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT;
452 }
453
454 current_point.set_reflective_handles (false);
455 current_point.get_right_handle ().convert_to_curve ();
456 }
457
458 public void move_handle_on_axis () {
459 state = MOVE_HANDLE_ON_AXIS;
460 }
461 }
462
463 }
464