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.
Set point type 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 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