├── README.md ├── Split Shape ├── .gitignore ├── README.md └── Split Shape.sketchplugin │ └── Contents │ ├── Resources │ └── icons │ │ └── splitShape.png │ └── Sketch │ ├── manifest.json │ └── script.js └── split.gif /README.md: -------------------------------------------------------------------------------- 1 | ## Notice 2 | 3 | Looking for maintainers! I have stopped using Sketch in favor of Figma and I no longer have time to keep up with the plugin API changes from Sketch which end up breaking the plugin. 4 | 5 | Sketch Split Shape 6 | ============== 7 | 8 | A Sketch Plugin that divides a rectangle into a specified number of divisions equally. 9 | 10 | ## How it works 11 | 12 | Create a rectangle the width and height of the area you want the grid to appear. Run the plugin and input the number of rows, columns and gutter between each item and hit OK. If the rectangle is within an artboard you can also specify margins which subtract the overall width of the object and center the rectangle divisions. 13 | 14 | 15 | ![Sketch Split Shape](/split.gif) 16 | -------------------------------------------------------------------------------- /Split Shape/.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /Split Shape/README.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | This example plugin for [Sketch][] demonstrates creating new layers, applying text styles, inserting bitmap images and more. 4 | 5 | [Sketch]: http://bohemiancoding.com/sketch/ 6 | -------------------------------------------------------------------------------- /Split Shape/Split Shape.sketchplugin/Contents/Resources/icons/splitShape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kupe517/sketch-split-shape/042f06eddf1ae34eb6fdaa9ad3e56ef9a150f9f2/Split Shape/Split Shape.sketchplugin/Contents/Resources/icons/splitShape.png -------------------------------------------------------------------------------- /Split Shape/Split Shape.sketchplugin/Contents/Sketch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "author" : "Brian Kuperman", 3 | "commands" : [ 4 | { 5 | "name" : "Split Shape", 6 | "script" : "script.js", 7 | "handler" : "settings", 8 | "shortcut" : "cmd ctrl g", 9 | "identifier" : "settings", 10 | "description" : "Split the currently selected shape into equal columns and rows.", 11 | "icon" : "icons/splitShape.png", 12 | }, 13 | { 14 | "script" : "script.js", 15 | "name" : "Split Again", 16 | "handler" : "splitAgain", 17 | "identifier" : "splitAgain", 18 | "description" : "Run Split Shape again with the previous settings.", 19 | "icon" : "icons/splitShape.png", 20 | } 21 | ], 22 | "menu" : { 23 | "items" : [ 24 | "settings", 25 | "splitAgain" 26 | ], 27 | "title" : "Split Shape" 28 | }, 29 | "identifier" : "https://github.com/kupe517/sketch-split-shape", 30 | "version" : "2.0", 31 | "description" : "Split the currently selected shape into equal columns and rows.", 32 | "authorEmail" : "brian@rekupe.com", 33 | "name" : "Split Shape" 34 | } 35 | -------------------------------------------------------------------------------- /Split Shape/Split Shape.sketchplugin/Contents/Sketch/script.js: -------------------------------------------------------------------------------- 1 | var columns, rows, margin, gutter, sketch, Settings, UI, document, selection; 2 | 3 | function splitAgain() { 4 | setVariables(); 5 | if(selection.length === 0){ 6 | UI.message("Oops! You have to select something for the magic to happen!"); 7 | return; 8 | } 9 | selection.layers.forEach(layer => { 10 | var x, y; 11 | var width = layer.frame.width; 12 | var height = layer.frame.height; 13 | 14 | x = layer.frame.x; 15 | y = layer.frame.y; 16 | 17 | if (margin > 0) { 18 | x = x + margin; 19 | y = (margin + y); 20 | width = width - (margin * 2); 21 | height = height - (margin * 2); 22 | } 23 | 24 | var newWidth = width - ((columns - 1) * gutter); 25 | var columnWidth = newWidth / columns; 26 | 27 | if (rows > 1) { 28 | var rowHeight = (height - ((rows - 1) * gutter)) / rows; 29 | } else { 30 | var rowHeight = height; 31 | } 32 | 33 | for (i = 0; i < columns; i++) { 34 | var newX = x + (gutter * i) + (columnWidth * i); 35 | for (j = 0; j < rows; j++) { 36 | var newY = y + (gutter * j) + (rowHeight * j); 37 | var newLayer = layer.duplicate(); 38 | sizeLayer(newLayer, columnWidth, rowHeight); 39 | moveLayer(newLayer, newX, newY); 40 | } 41 | } 42 | 43 | layer.remove(); 44 | selection.clear(); 45 | }); 46 | UI.message("Shazam! One shape becomes many! 🎉"); 47 | } 48 | 49 | function settings(context) { 50 | // Display settings window 51 | var window = createRectangleWindow(context); 52 | var alert = window[0]; 53 | 54 | var response = alert.runModal() 55 | 56 | if (response == "1000") { 57 | // This code only runs when the user clicks 'Save and run'; 58 | log("Save and run") 59 | 60 | // Save user input to user preferences 61 | saveDialogState(context); 62 | 63 | // Split the shape 64 | 65 | splitAgain(); 66 | 67 | return true; 68 | 69 | } else 70 | 71 | if (response == "1001") { 72 | // This code only runs when the user clicks 'save'; 73 | log("Save") 74 | 75 | // Save user input to user preferences 76 | saveDialogState(context); 77 | UI.message("Split Shape settings saved."); 78 | return true; 79 | 80 | } 81 | 82 | { 83 | return false; 84 | } 85 | 86 | } 87 | 88 | function setVariables(){ 89 | sketch = require('sketch/dom'); 90 | Settings = require('sketch/settings'); 91 | UI = require('sketch/ui'); 92 | document = sketch.Document.getSelectedDocument(); 93 | selection = document.selectedLayers; 94 | 95 | if(Settings.settingForKey('columnInput') == undefined){ 96 | columns = 3; 97 | }else{ 98 | columns = Settings.settingForKey('columnInput'); 99 | } 100 | 101 | if(Settings.settingForKey('rowInput') == undefined){ 102 | rows = 1; 103 | }else{ 104 | rows = Settings.settingForKey('rowInput'); 105 | } 106 | 107 | if(Settings.settingForKey('marginInput') == undefined){ 108 | margin = 0; 109 | }else{ 110 | margin = Settings.settingForKey('marginInput'); 111 | } 112 | 113 | if(Settings.settingForKey('gutterInput') == undefined){ 114 | gutter = 20; 115 | }else{ 116 | gutter = Settings.settingForKey('gutterInput'); 117 | } 118 | 119 | } 120 | 121 | function saveDialogState(context){ 122 | 123 | // The user entered some input in the dialog window and closed it. 124 | // We should save the preferences of the user so the user doesn't have to 125 | // re-enter them when running the plugin for a second time. 126 | 127 | // Save column textfield 128 | columnInput = columnTextField.intValue(); 129 | Settings.setSettingForKey('columnInput', columnInput); 130 | 131 | // Save row textfield 132 | rowInput = rowTextField.intValue(); 133 | Settings.setSettingForKey('rowInput', rowInput); 134 | 135 | // Save margin textfield 136 | marginInput = marginTextField.intValue(); 137 | Settings.setSettingForKey('marginInput', marginInput); 138 | 139 | // Save gutter textfield 140 | gutterInput = gutterTextField.intValue(); 141 | Settings.setSettingForKey('gutterInput', gutterInput); 142 | 143 | 144 | log('⚙️ ------ START SAVED SETTINGS --------- ⚙️'); 145 | log('columnInput: ' + columnInput); 146 | log('rowInput: ' + rowInput); 147 | log('marginInput: ' + marginInput); 148 | log('gutterInput: ' + gutterInput); 149 | log('⚙️ ------ END SAVED SETTINGS --------- ⚙️'); 150 | 151 | setVariables(); 152 | 153 | } 154 | 155 | 156 | function createRectangleWindow(context) { 157 | setVariables(); 158 | 159 | // Setup the window 160 | var alert = COSAlertWindow.new(); 161 | alert.setMessageText("Split Shape Settings"); 162 | alert.addButtonWithTitle("Save and run"); 163 | alert.addButtonWithTitle("Save"); 164 | alert.addButtonWithTitle("Cancel"); 165 | 166 | // Utilities 167 | utils = { 168 | "createLabel": function(frame, text) { 169 | var label = NSTextField.alloc().initWithFrame(frame); 170 | label.setStringValue(text); 171 | label.setSelectable(false); 172 | label.setEditable(false); 173 | label.setBezeled(false); 174 | label.setDrawsBackground(false); 175 | return label 176 | }, 177 | "getLayerProps": function() { 178 | var layer = selection.firstObject(); 179 | 180 | if (layer) { 181 | var x = layer.frame().x(); 182 | var y = layer.frame().y(); 183 | return [x, y]; 184 | } else { 185 | return [0, 0]; 186 | } 187 | } 188 | }; 189 | 190 | // Create the main view 191 | var viewWidth = 400; 192 | var viewHeight = 170; 193 | var viewSpacer = 10; 194 | var view = NSView.alloc().initWithFrame(NSMakeRect(0, 0, viewWidth, viewHeight)); 195 | alert.addAccessoryView(view); 196 | 197 | // Labels 198 | var infoLabel = utils.createLabel(NSMakeRect(0, viewHeight - 33, (viewWidth - 100), 35),"Your selected shape will be split into a grid based on the settings you provide below."); 199 | var horizontalLabel = utils.createLabel(NSMakeRect(-1, viewHeight - 65, (viewWidth / 2) - viewSpacer, 20), "Columns ↔"); 200 | var verticalLabel = utils.createLabel(NSMakeRect(130 + viewSpacer, viewHeight - 65, (viewWidth / 2) - viewSpacer, 20), "Rows ↕"); 201 | var marginLabel = utils.createLabel(NSMakeRect(-1, viewHeight - 130, (viewWidth / 2) - viewSpacer, 20), "Margin"); 202 | var gutterLabel = utils.createLabel(NSMakeRect(130 + viewSpacer, viewHeight - 130, (viewWidth / 2) - viewSpacer, 20), "Gutters"); 203 | 204 | view.addSubview(infoLabel); 205 | view.addSubview(horizontalLabel); 206 | view.addSubview(verticalLabel); 207 | view.addSubview(marginLabel); 208 | view.addSubview(gutterLabel); 209 | 210 | // Create inputs 211 | columnTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 85, 130, 20)); 212 | rowTextField = NSTextField.alloc().initWithFrame(NSMakeRect(130 + viewSpacer, viewHeight - 85, 130, 20)); 213 | marginTextField = NSTextField.alloc().initWithFrame(NSMakeRect(0, viewHeight - 150, 130, 20)); 214 | gutterTextField = NSTextField.alloc().initWithFrame(NSMakeRect(130 + viewSpacer, viewHeight - 150, 130, 20)); 215 | 216 | // Make TAB key work to switch between textfields 217 | [columnTextField setNextKeyView:rowTextField]; 218 | [rowTextField setNextKeyView:marginTextField]; 219 | [marginTextField setNextKeyView:gutterTextField]; 220 | 221 | //Adding inputs to the dialog 222 | view.addSubview(columnTextField); 223 | view.addSubview(rowTextField); 224 | view.addSubview(marginTextField); 225 | view.addSubview(gutterTextField); 226 | 227 | // Fill inputs 228 | columnTextField.setStringValue(columns); 229 | rowTextField.setStringValue(rows); 230 | marginTextField.setStringValue(margin); 231 | gutterTextField.setStringValue(gutter); 232 | 233 | return [alert]; 234 | } 235 | 236 | /* 237 | |-------------------------------------------------------------------------- 238 | | Utilities 239 | |-------------------------------------------------------------------------- 240 | */ 241 | 242 | function parentOffsetInArtboard(layer) { 243 | var offset = { 244 | x: 0, 245 | y: 0 246 | }; 247 | 248 | var parent = layer.parent; 249 | 250 | while (parent.name && parent.type !== 'Artboard') { 251 | offset.x += parent.frame.x; 252 | offset.y += parent.frame.y; 253 | parent = parent.parent; 254 | } 255 | return offset; 256 | } 257 | 258 | function moveLayer(layer, x, y) { 259 | var newFrame = new sketch.Rectangle(layer.frame); 260 | if(layer.parent.type == 'Artboard'){ 261 | var parentOffset = parentOffsetInArtboard(layer); 262 | newFrame.x = x - parentOffset.x; 263 | newFrame.y = y - parentOffset.y; 264 | }else{ 265 | newFrame.x = x; 266 | newFrame.y = y; 267 | } 268 | layer.frame = newFrame; 269 | } 270 | 271 | function sizeLayer(layer, width, height) { 272 | var newFrame = new sketch.Rectangle(layer.frame); 273 | newFrame.width = width; 274 | newFrame.height = height; 275 | layer.frame = newFrame; 276 | } 277 | -------------------------------------------------------------------------------- /split.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kupe517/sketch-split-shape/042f06eddf1ae34eb6fdaa9ad3e56ef9a150f9f2/split.gif --------------------------------------------------------------------------------