.
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
30 uint state = NONE;
31
32 Path current_path = new Path ();
33 EditPoint current_point = new EditPoint ();
34
35 int last_x = 0;
36 int last_y = 0;
37
38 double last_release_time = 0;
39 double last_press_time = 0;
40 bool button_down = false;
41
42 public BezierTool (string name) {
43 base (name, t_ ("Create Beziér curves"));
44
45 select_action.connect ((self) => {
46 state = NONE;
47 MainWindow.set_cursor (NativeWindow.VISIBLE);
48 });
49
50 deselect_action.connect ((self) => {
51 state = NONE;
52 MainWindow.set_cursor (NativeWindow.VISIBLE);
53 });
54
55 press_action.connect ((self, b, x, y) => {
56 press (b, x, y);
57 });
58
59 double_click_action.connect ((self, b, x, y) => {
60 });
61
62 release_action.connect ((self, b, x, y) => {
63 release (b, x, y);
64 });
65
66 move_action.connect ((self, x, y) => {
67 move (x, y);
68 });
69
70 key_press_action.connect ((self, keyval) => {
71 });
72
73 key_release_action.connect ((self, keyval) => {
74 });
75
76 draw_action.connect ((tool, cairo_context, glyph) => {
77 if (PenTool.can_join (current_point)) {
78 PenTool.draw_join_icon (cairo_context, last_x, last_y);
79 }
80 });
81 }
82
83 public void press (int b, int x, int y) {
84 Glyph g = MainWindow.get_current_glyph ();
85 double px, py;
86 Path? p;
87 Path path;
88
89 if (button_down) {
90 warning ("Discarding event.");
91 return;
92 }
93
94 button_down = true;
95
96 return_if_fail (state != MOVE_HANDLES);
97 return_if_fail (state != MOVE_LAST_HANDLE_RIGHT);
98 return_if_fail (state != MOVE_LAST_HANDLE_LEFT);
99
100 if (b == 2) {
101 if (g.is_open ()) {
102 stop_drawing ();
103 g.close_path ();
104 } else {
105 g.open_path ();
106 }
107
108 MainWindow.set_cursor (NativeWindow.VISIBLE);
109 state = NONE;
110
111 return;
112 }
113
114 // ignore double clicks
115 if ((GLib.get_real_time () - last_press_time) / 1000000.0 < 0.2) {
116 last_press_time = GLib.get_real_time ();
117 return;
118 }
119 last_press_time = GLib.get_real_time ();
120
121 g.store_undo_state ();
122
123 PenTool.update_orientation ();
124
125 MainWindow.set_cursor (NativeWindow.HIDDEN);
126 g.open_path ();
127
128 px = Glyph.path_coordinate_x (x);
129 py = Glyph.path_coordinate_y (y);
130
131 if (GridTool.is_visible ()) {
132 GridTool.tie_coordinate (ref px, ref py);
133 }
134
135 if (state == NONE) {
136 g.open_path ();
137 current_path = new Path ();
138 current_path.reopen ();
139 current_path.hide_end_handle = true;
140 current_point = current_path.add (px, py);
141 current_point.get_left_handle ().convert_to_line ();
142 current_point.recalculate_linear_handles ();
143 g.add_path (current_path);
144
145 set_point_type ();
146
147 if (StrokeTool.add_stroke) {
148 current_path.stroke = StrokeTool.stroke_width;
149 current_path.line_cap = StrokeTool.line_cap;
150 }
151
152 BirdFont.get_current_font ().touch ();
153
154 GlyphCanvas.redraw ();
155 state = MOVE_POINT;
156 } else if (state == MOVE_POINT) {
157 if (PenTool.can_join (current_point)) {
158 EditPoint first = current_path.get_first_point ();
159 bool swap;
160
161 p = PenTool.join_paths (current_point);
162 return_if_fail (p != null);
163 path = (!) p;
164 swap = path.get_first_point () != first;
165
166 if (current_path.points.size == 1) {
167 return_if_fail (path.is_open ());
168 current_path = path;
169 current_point = path.get_last_point ();
170 state = MOVE_POINT;
171 } else {
172 g.open_path ();
173 current_path = path;
174 current_point = !swap ? path.get_first_point () : path.get_last_point ();
175 state = !swap ? MOVE_LAST_HANDLE_RIGHT : MOVE_LAST_HANDLE_LEFT;
176 }
177 } else {
178 state = MOVE_HANDLES;
179 }
180 }
181 }
182
183 void set_point_type () {
184 PointType pt;
185
186 pt = DrawingTools.get_selected_point_type ();
187
188 current_point.type = pt;
189 current_point.get_left_handle ().type = pt;
190 current_point.get_right_handle ().type = pt;
191
192 current_point.get_left_handle ().convert_to_line ();
193 current_point.get_right_handle ().convert_to_line ();
194 }
195
196 public void release (int b, int x, int y) {
197 double px, py;
198 Glyph g;
199
200 if (!button_down) {
201 warning ("Discarding event.");
202 return;
203 }
204
205 button_down = false;
206
207 return_if_fail (state != MOVE_POINT);
208
209 // ignore double clicks
210 if ((GLib.get_real_time () - last_release_time) / 1000000.0 < 0.2) {
211 last_release_time = GLib.get_real_time ();
212 return;
213 }
214 last_release_time = GLib.get_real_time ();
215
216 px = Glyph.path_coordinate_x (x);
217 py = Glyph.path_coordinate_y (y);
218
219 if (GridTool.is_visible ()) {
220 GridTool.tie_coordinate (ref px, ref py);
221 }
222
223 g = MainWindow.get_current_glyph ();
224
225 if (state == MOVE_HANDLES) {
226 current_point = current_path.add (px, py);
227 current_path.hide_end_handle = true;
228 current_point.get_left_handle ().convert_to_line ();
229 current_point.recalculate_linear_handles ();
230 set_point_type ();
231 g.clear_active_paths ();
232 g.add_active_path (current_path);
233 GlyphCanvas.redraw ();
234 state = MOVE_POINT;
235 } else if (state == MOVE_LAST_HANDLE_LEFT || state == MOVE_LAST_HANDLE_RIGHT) {
236 current_path.update_region_boundaries ();
237 g.close_path ();
238 MainWindow.set_cursor (NativeWindow.VISIBLE);
239
240 if (Path.is_counter (g.get_paths (), current_path)) {
241 current_path.force_direction (Direction.COUNTER_CLOCKWISE);
242 } else {
243 current_path.force_direction (Direction.CLOCKWISE);
244 }
245
246 current_path.reset_stroke ();
247
248 state = NONE;
249 }
250 }
251
252 public void move (int x, int y) {
253 double px, py;
254
255 last_x = x;
256 last_y = y;
257
258 px = Glyph.path_coordinate_x (x);
259 py = Glyph.path_coordinate_y (y);
260
261 if (GridTool.is_visible ()) {
262 GridTool.tie_coordinate (ref px, ref py);
263 }
264
265 if (state == MOVE_POINT) {
266 current_point.x = px;
267 current_point.y = py;
268 current_path.hide_end_handle = true;
269 current_point.recalculate_linear_handles ();
270 current_path.reset_stroke ();
271 GlyphCanvas.redraw ();
272 } else if (state == MOVE_HANDLES
273 || state == MOVE_LAST_HANDLE_LEFT
274 || state == MOVE_LAST_HANDLE_RIGHT) {
275
276 current_path.hide_end_handle = false;
277 current_point.set_reflective_handles (true);
278 current_point.convert_to_curve ();
279
280 if (state == MOVE_LAST_HANDLE_LEFT) {
281 current_point.get_left_handle ().move_to_coordinate (px, py);
282 } else {
283 current_point.get_right_handle ().move_to_coordinate (px, py);
284 }
285
286 current_path.reset_stroke ();
287 GlyphCanvas.redraw ();
288 }
289
290 if (current_path.points.size > 0) {
291 current_path.get_first_point ().set_reflective_handles (false);
292 current_path.get_last_point ().set_reflective_handles (false);
293 }
294 }
295
296 public void switch_to_line_mode () {
297 int s = current_path.points.size;
298 EditPoint p;
299
300 if (s > 2) {
301 p = current_path.points.get (s - 2);
302 p.get_right_handle ().convert_to_line ();
303 current_point.get_left_handle ().convert_to_line ();
304 p.recalculate_linear_handles ();
305 current_point.recalculate_linear_handles ();
306 current_path.reset_stroke ();
307 GlyphCanvas.redraw ();
308
309 state = MOVE_POINT;
310 }
311 }
312
313 public void stop_drawing () {
314 if (state == MOVE_POINT && current_path.points.size > 0) {
315 current_path.delete_last_point ();
316 current_path.reset_stroke ();
317 current_path.create_full_stroke (); // cache better stroke
318 }
319
320 state = NONE;
321 }
322
323 public override void before_undo () {
324 }
325
326 public override void after_undo () {
327 if (state != NONE) {
328 MainWindow.set_cursor (NativeWindow.VISIBLE);
329 state = NONE;
330 }
331 }
332 }
333
334 }
335