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.
Convert last node to corner in Beziér tool
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