The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

ResizeTool.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/ResizeTool.vala.
Suppress zoom when canvas is moving
1 /* 2 Copyright (C) 2013 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 public class ResizeTool : Tool { 21 22 bool resize_path = false; 23 Path? resized_path = null; 24 double last_resize_y; 25 double last_resize_x; 26 27 bool move_paths = false; 28 29 ImageSurface? resize_handle; 30 31 static double selection_box_width = 0; 32 static double selection_box_height = 0; 33 static double selection_box_center_x = 0; 34 static double selection_box_center_y = 0; 35 36 static bool rotate_path = false; 37 static double last_rotate_y; 38 static double rotation = 0; 39 static double last_rotate = 0; 40 41 public double last_skew = 0; 42 43 public signal void objects_rotated (double angle); 44 public signal void objects_resized (double width, double height); 45 46 public ResizeTool (string n) { 47 base (n, t_("Resize and rotate paths")); 48 49 resize_handle = Icons.get_icon ("resize_handle.png"); 50 51 select_action.connect((self) => { 52 }); 53 54 deselect_action.connect((self) => { 55 }); 56 57 press_action.connect((self, b, x, y) => { 58 Path last_path; 59 Glyph glyph; 60 61 glyph = MainWindow.get_current_glyph (); 62 glyph.store_undo_state (); 63 64 foreach (Path p in glyph.active_paths) { 65 if (is_over_resize_handle (p, x, y)) { 66 resize_path = true; 67 resized_path = p; 68 last_resize_x = x; 69 last_resize_y = y; 70 return; 71 } 72 } 73 74 if (resized_path != null) { 75 if (is_over_resize_handle ((!) resized_path, x, y)) { 76 resize_path = true; 77 last_resize_x = x; 78 last_resize_y = y; 79 return; 80 } 81 } 82 83 foreach (Path p in glyph.active_paths) { 84 if (is_over_rotate_handle (p, x, y)) { 85 rotate_path = true; 86 return; 87 } 88 } 89 90 if (glyph.active_paths.size > 0) { 91 last_path = glyph.active_paths.get (glyph.active_paths.size - 1); 92 last_rotate = last_path.rotation ; 93 } 94 95 rotation = last_rotate; 96 last_resize_x = x; 97 last_rotate_y = y; 98 99 MoveTool.press (b, x, y); 100 101 move_paths = true; 102 }); 103 104 release_action.connect((self, b, x, y) => { 105 resize_path = false; 106 rotate_path = false; 107 move_paths = false; 108 DrawingTools.move_tool.release (b, x, y); 109 }); 110 111 move_action.connect ((self, x, y) => { 112 if (resize_path && can_resize (x, y)) { 113 resize (x, y); 114 } 115 116 if (rotate_path) { 117 rotate (x, y); 118 } 119 120 if (!rotate_path) { 121 MoveTool.update_boundaries_for_selection (); 122 MoveTool.get_selection_box_boundaries (out selection_box_center_x, 123 out selection_box_center_y, out selection_box_width, 124 out selection_box_height); 125 } 126 127 if (move_paths || rotate_path || resize_path) { 128 GlyphCanvas.redraw (); 129 } 130 131 DrawingTools.move_tool.move (x, y); 132 }); 133 134 draw_action.connect ((self, cr, glyph) => { 135 Glyph g = MainWindow.get_current_glyph (); 136 ImageSurface resize_img = (!) resize_handle; 137 138 foreach (Path p in g.active_paths) { 139 cr.set_source_surface (resize_img, Glyph.reverse_path_coordinate_x (p.xmax) - 10, Glyph.reverse_path_coordinate_y (p.ymax) - 10); 140 cr.paint (); 141 } 142 143 if (g.active_paths.size > 0) { 144 draw_rotate_handle (cr); 145 } 146 147 MoveTool.draw_actions (cr); 148 }); 149 150 key_press_action.connect ((self, keyval) => { 151 DrawingTools.move_tool.key_press (keyval); 152 }); 153 154 } 155 156 public void signal_objects_rotated () { 157 objects_rotated (rotation * (180 / PI)); 158 } 159 160 public void rotate_selected_paths (double angle, double cx, double cy) { 161 Glyph glyph = MainWindow.get_current_glyph (); 162 double dx, dy, xc2, yc2, w, h; 163 Path last_path; 164 foreach (Path p in glyph.active_paths) { 165 p.rotate (angle, cx, cy); 166 } 167 168 MoveTool.get_selection_box_boundaries (out xc2, out yc2, out w, out h); 169 170 dx = -(xc2 - cx); 171 dy = -(yc2 - cy); 172 173 foreach (Path p in glyph.active_paths) { 174 p.move (dx, dy); 175 } 176 177 last_rotate = rotation; 178 179 MoveTool.update_selection_boundaries (); 180 181 if (glyph.active_paths.size > 0) { 182 last_path = glyph.active_paths.get (glyph.active_paths.size - 1); 183 rotation = last_path.rotation; 184 185 if (rotation > PI) { 186 rotation -= 2 * PI; 187 } 188 189 last_rotate = rotation; 190 signal_objects_rotated (); 191 } 192 } 193 194 /** Move rotate handle to pixel x,y. */ 195 void rotate (double x, double y) { 196 double cx, cy, xc, yc, a, b; 197 198 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 199 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 200 xc = selection_box_center_x; 201 yc = selection_box_center_y; 202 203 a = x - cx; 204 b = y - cy; 205 206 rotation = atan (b / a); 207 208 if (a < 0) { 209 rotation += PI; 210 } 211 212 rotate_selected_paths (rotation - last_rotate, selection_box_center_x, selection_box_center_y); 213 } 214 215 static bool is_over_rotate_handle (Path p, double x, double y) { 216 double cx, cy, hx, hy; 217 double size = 10; 218 bool inx, iny; 219 220 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 221 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 222 223 hx = cos (rotation) * 75; 224 hy = sin (rotation) * 75; 225 226 inx = x - size * MainWindow.units <= cx + hx - 2.5 <= x + size * MainWindow.units; 227 iny = y - size * MainWindow.units <= cy + hy - 2.5 <= y + size * MainWindow.units; 228 229 return inx && iny; 230 } 231 232 static void draw_rotate_handle (Context cr) { 233 double cx, cy, hx, hy; 234 235 cx = Glyph.reverse_path_coordinate_x (selection_box_center_x); 236 cy = Glyph.reverse_path_coordinate_y (selection_box_center_y); 237 238 cr.save (); 239 Theme.color (cr, "Highlighted 1"); 240 cr.rectangle (cx - 2.5, cy - 2.5, 5, 5); 241 cr.fill (); 242 243 hx = cos (rotation) * 75; 244 hy = sin (rotation) * 75; 245 246 cr.set_line_width (1); 247 cr.move_to (cx, cy); 248 cr.line_to (cx + hx, cy + hy); 249 cr.stroke (); 250 251 Theme.color (cr, "Highlighted 1"); 252 cr.rectangle (cx + hx - 2.5, cy + hy - 2.5, 5, 5); 253 cr.fill (); 254 255 cr.restore (); 256 } 257 258 double get_resize_ratio (double px, double py) { 259 double ratio, x, y, w, h; 260 261 Glyph glyph = MainWindow.get_current_glyph (); 262 glyph.selection_boundaries (out x, out y, out w, out h); 263 264 ratio = 1; 265 266 if (Math.fabs (last_resize_y - py) > Math.fabs (last_resize_x - px)) { 267 ratio = 1 + (Glyph.path_coordinate_y (py) 268 - Glyph.path_coordinate_y (last_resize_y)) / h; 269 } else { 270 ratio = 1 + (Glyph.path_coordinate_x (px) 271 - Glyph.path_coordinate_x (last_resize_x)) / w; 272 } 273 274 return ratio; 275 } 276 277 public void resize_selected_paths (double ratio) { 278 double resize_pos_x = 0; 279 double resize_pos_y = 0; 280 Glyph glyph = MainWindow.get_current_glyph (); 281 double selection_minx, selection_miny, dx, dy; 282 283 get_selection_min (out resize_pos_x, out resize_pos_y); 284 285 // resize paths 286 foreach (Path selected_path in glyph.active_paths) { 287 selected_path.resize (ratio); 288 } 289 290 // move paths relative to the updated xmin and xmax 291 get_selection_min (out selection_minx, out selection_miny); 292 dx = resize_pos_x - selection_minx; 293 dy = resize_pos_y - selection_miny; 294 foreach (Path selected_path in glyph.active_paths) { 295 selected_path.move (dx, dy); 296 } 297 298 if (glyph.active_paths.size > 0) { 299 MoveTool.get_selection_box_boundaries (out selection_box_center_x, 300 out selection_box_center_y, out selection_box_width, 301 out selection_box_height); 302 objects_resized (selection_box_width, selection_box_height); 303 } 304 } 305 306 /** Move resize handle to pixel x,y. */ 307 void resize (double px, double py) { 308 double ratio; 309 310 ratio = get_resize_ratio (px, py); 311 312 if (ratio != 1) { 313 resize_selected_paths (ratio); 314 last_resize_x = px; 315 last_resize_y = py; 316 } 317 } 318 319 void get_selection_min (out double x, out double y) { 320 Glyph glyph = MainWindow.get_current_glyph (); 321 x = double.MAX; 322 y = double.MAX; 323 foreach (Path p in glyph.active_paths) { 324 if (p.xmin < x) { 325 x = p.xmin; 326 } 327 328 if (p.ymin < y) { 329 y = p.ymin; 330 } 331 } 332 } 333 334 bool can_resize (double x, double y) { 335 Glyph glyph = MainWindow.get_current_glyph (); 336 double h, w; 337 double ratio = get_resize_ratio (x, y); 338 339 foreach (Path selected_path in glyph.active_paths) { 340 h = selected_path.ymax - selected_path.ymin; 341 w = selected_path.xmax - selected_path.xmin; 342 343 if (selected_path.points.size <= 1) { 344 continue; 345 } 346 347 if (h * ratio < 1 || w * ratio < 1) { 348 return false; 349 } 350 } 351 352 return true; 353 } 354 355 bool is_over_resize_handle (Path p, double x, double y) { 356 double handle_x = Math.fabs (Glyph.reverse_path_coordinate_x (p.xmax)); 357 double handle_y = Math.fabs (Glyph.reverse_path_coordinate_y (p.ymax)); 358 return fabs (handle_x - x + 10) < 20 * MainWindow.units && fabs (handle_y - y + 10) < 20 * MainWindow.units; 359 } 360 361 public void skew (double skew) { 362 Glyph glyph = MainWindow.get_current_glyph (); 363 double dx, nx, nw, dw, x, y, w, h; 364 double s = (skew - last_skew) / 100.0; 365 366 glyph.selection_boundaries (out x, out y, out w, out h); 367 368 foreach (Path path in glyph.active_paths) { 369 SvgParser.apply_matrix (path, 1, 0, s, 1, 0, 0); 370 path.skew = skew; 371 path.update_region_boundaries (); 372 } 373 374 glyph.selection_boundaries (out nx, out y, out nw, out h); 375 376 dx = -(nx - x); 377 378 foreach (Path p in glyph.active_paths) { 379 p.move (dx, 0); 380 } 381 382 last_skew = skew; 383 384 dw = (nw - w); 385 glyph.right_limit += dw; 386 glyph.remove_lines (); 387 glyph.add_help_lines (); 388 } 389 } 390 391 } 392