├── arrowfy_menu.png ├── arrowfy_scrnshot.png ├── LICENSE ├── Arrowfy.sketchplugin └── Contents │ └── Sketch │ ├── manifest.json │ └── script.cocoascript └── README.md /arrowfy_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joclin/arrowfy/HEAD/arrowfy_menu.png -------------------------------------------------------------------------------- /arrowfy_scrnshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joclin/arrowfy/HEAD/arrowfy_scrnshot.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jocelyn Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Arrowfy.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Arrowfy", 3 | "description" : "Add simple arrowhead to either end of selected paths. Matches color and weight.", 4 | "author" : "Jocelyn Lin", 5 | "version" : "2.1", 6 | "compatibleVersion": "50", 7 | "identifier": "com.jocelynlin.sketch.arrowfy", 8 | "appcast": "https://github.com/joclin/arrowfy/blob/master/.appcast.xml", 9 | "commands": [ 10 | { 11 | "name" : "End arrow", 12 | "identifier" : "create-arrow", 13 | "shortcut" : "ctrl shift a", 14 | "script" : "script.cocoascript", 15 | "handler" : "createArrow" 16 | }, 17 | { 18 | "name" : "Beginning arrow", 19 | "identifier" : "create-arrow-begin", 20 | "script" : "script.cocoascript", 21 | "handler" : "createArrowBegin" 22 | }, 23 | { 24 | "name" : "Both ends", 25 | "identifier" : "create-arrow-bothends", 26 | "script" : "script.cocoascript", 27 | "handler" : "createArrowBothEnds" 28 | } 29 | 30 | ], 31 | "menu" : { 32 | "title" : "Arrowfy", 33 | "items": [ 34 | "create-arrow", 35 | "create-arrow-begin", 36 | "create-arrow-bothends" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arrowfy 2 | 3 | **UPDATE July 21, 2018:** [Sketch v51](https://blog.sketchapp.com/styles-in-libraries-and-fixed-elements-in-prototyping-in-sketch-51-aab4074c3f46) FINALLY fixes the truly awful default arrows that used to come with sketch! Future work on this plugin will likely cease given that there's now a built-in decent arrowhead that will only get better with each later release. 4 | 5 | ----------------- 6 | 7 | This plugin adds simple solid arrowheads to any line, sized and colored to match path style. 8 | ![Alt text](https://github.com/joclin/arrowfy/blob/master/arrowfy_scrnshot.png) 9 | 10 | ## Installation 11 | Double-click on .sketchplugin file. 12 | 13 | ## Usage 14 | Select one or multiple paths, then hit Ctrl-Shift-A to add auto-styled 15 | arrowheads to end. Menu allows adding arrowhead to beginning or both ends 16 | of paths instead. 17 | ![Alt text](https://github.com/joclin/arrowfy/blob/master/arrowfy_menu.png) 18 | 19 | ## Versions 20 | - v2.1 - Update to work in Sketch v50 + Sketch appcast (automatic update) support 21 | - v2.0 - Ability to add arrowhead to either or both ends of path. 22 | - v1.3 - Changes shortcut to Ctrl-Shift-A to avoid new shortcut overlaps. 23 | - v1.2 - Update makes this work for Sketch v46.2. 24 | - v1.1 - Update makes this work for Sketch v39. Also fixes shortcut mapping. 25 | -------------------------------------------------------------------------------- /Arrowfy.sketchplugin/Contents/Sketch/script.cocoascript: -------------------------------------------------------------------------------- 1 | // MIT Open Source License 2 | // Copyright (c) 2017 Jocelyn Lin 3 | 4 | var createArrow = function(context) { 5 | createArrowType(context, 0); 6 | } 7 | 8 | var createArrowBegin = function(context) { 9 | createArrowType(context, 1); 10 | } 11 | 12 | var createArrowBothEnds = function(context) { 13 | createArrowType(context, 2); 14 | } 15 | 16 | function createArrowType(context, endType) { 17 | // endType - which end of path to attach arrowhead 18 | // 0=begin, 1=end of path, 2=both ends 19 | 20 | // shapeType - what shape arrowhead should be 21 | // 0=filled triangle 22 | 23 | // TBD pass in scale, allow for different ends for arrowhead 24 | 25 | var doc = context.document; 26 | var selectedLayers = context.selection; 27 | var selectedCount = selectedLayers.count(); 28 | 29 | var layer; 30 | var bezier; 31 | 32 | if (selectedCount == 0) { 33 | doc.showMessage('Oops, no selection.'); 34 | } else { 35 | //add arrowhead to each path 36 | //TBD fix for nested path layers 37 | for (var i = 0; i < selectedCount; i++) { 38 | layer = selectedLayers[i]; 39 | 40 | //check that this layer is a shape 41 | if( layer && layer.isKindOfClass(MSShapeGroup) ){ 42 | layer.select_byExtendingSelection(true, false); 43 | //draw at end; 44 | if ((endType == 0) || (endType == 2)) { 45 | addArrowhead(layer, 0); 46 | } 47 | //draw at beginning; 48 | if ((endType == 1) || (endType == 2)) { 49 | addArrowhead(layer, 1); 50 | } 51 | 52 | //group and name new group 53 | var groupAction = doc.actionsController().actionForID("MSGroupAction"); 54 | groupAction.group(nil); 55 | gr = layer.parentGroup(); 56 | 57 | if ((layer.name() == 'Line') || (layer.name() == 'Path')) { 58 | gr.setName('Arrow'); 59 | } else { 60 | gr.setName(layer.name()); 61 | } 62 | 63 | } else { 64 | doc.showMessage('Oops, not a path.'); 65 | } 66 | } 67 | 68 | //reselect all new groups 69 | for (var i = 0; i < selectedCount; i++) { 70 | selectedLayers[i].parentGroup().select_byExtendingSelection(true, true); 71 | } 72 | 73 | } 74 | } 75 | 76 | function addArrowhead(layer, endType) { 77 | //in: layer - a path shape 78 | //in: endType - which end of path to attach arrowhead (0 begin, 1 end) 79 | //does: adds arrowhead shape to end of line 80 | 81 | //0 - triangle as arrowhead 82 | //TBD: different shapes 83 | var headPath = NSBezierPath.bezierPath(); 84 | headPath.moveToPoint(NSMakePoint(0,7)); 85 | headPath.lineToPoint(NSMakePoint(-14,0)); 86 | headPath.lineToPoint(NSMakePoint(0,-7)); 87 | headPath.closePath(); 88 | headPath = MSPath.pathWithBezierPath(headPath); 89 | var headShape = MSShapeGroup.shapeWithBezierPath(headPath); 90 | 91 | //scale to lineweight 92 | //TBD: user input 93 | var lineThickness = layer.style().borders().objectAtIndex(0).thickness(); 94 | var scale = 1+(lineThickness/5); 95 | headShape.frame().setWidth(Math.floor(headShape.frame().width()*scale)); 96 | headShape.frame().setHeight(Math.floor(headShape.frame().height()*scale)); 97 | 98 | //color same as line 99 | var fill = headShape.style().addStylePartOfType(0); 100 | fill.setFillType(0); 101 | var fills = headShape.style().enabledFills(); 102 | fills.lastObject().setColor(layer.style().borders().firstObject().color()); 103 | 104 | //split line depending on how curvy it is, with at least 2 points 105 | var path = layer.pathInFrameWithTransforms(); 106 | var count = path.elementCount()*2; 107 | 108 | //hacky way of avoiding weirdly angled arrowheads for tight curves 109 | //handle spirals by choosing the greater number 110 | if (count<50) { 111 | count=50; 112 | } 113 | var length = path.length(); 114 | var step = length/count; 115 | 116 | //but also make sure a step is at minimum the arrowhead size 117 | var lineThickness = layer.style().borders().objectAtIndex(0).thickness(); 118 | var scale = 1+(lineThickness/5); 119 | if (step < (scale*7)) { 120 | step = scale*7; 121 | } 122 | 123 | //choose which endpoint, 0 or length 124 | var endPoint = path.pointOnPathAtLength(length); 125 | var linePoint = path.pointOnPathAtLength(length-step); 126 | if ((endType == 1) || (endType == 2)) { 127 | //draw at beginning; 128 | endPoint = path.pointOnPathAtLength(0); 129 | linePoint = path.pointOnPathAtLength(step); 130 | } 131 | //rotate 132 | var angle = 360/(2*Math.PI) * (Math.atan2(linePoint.y - endPoint.y, linePoint.x - endPoint.x)); 133 | headShape.setRotation(-1*angle); 134 | //move center to endpoint of line 135 | headShape.frame().setX(endPoint.x - headShape.frame().width()/2); 136 | headShape.frame().setY(endPoint.y - headShape.frame().height()/2); 137 | 138 | //add to layer 139 | headShape.setName('Arrowhead'); 140 | var gr = layer.parentGroup(); 141 | gr.addLayers([headShape]); 142 | headShape.select_byExtendingSelection(true, true); 143 | } 144 | --------------------------------------------------------------------------------