The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

BezierTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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