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