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