├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── lib ├── ColorPicker-view.coffee ├── ColorPicker.coffee ├── extensions │ ├── Alpha.coffee │ ├── Arrow.coffee │ ├── Body.coffee │ ├── Color.coffee │ ├── Definition.coffee │ ├── Format.coffee │ ├── Hue.coffee │ ├── Return.coffee │ └── Saturation.coffee └── modules │ ├── Convert.coffee │ ├── Emitter.coffee │ ├── SmartColor.coffee │ └── SmartVariable.coffee ├── package.json ├── preview.gif └── styles ├── ColorPicker.less └── extensions ├── Alpha.less ├── Arrow.less ├── Body.less ├── Color.less ├── Definition.less ├── Format.less ├── Hue.less ├── Return.less └── Saturation.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | https://github.com/thomaslindstrom/color-picker 4 | 5 | ## v2.2.0, v2.2.1, v2.2.2 6 | - Remove opening animation due to glitches in the UI and the `tool-bar` package 7 | 8 | ### v2.2.3 9 | - Improve stylus variable lookup 10 | 11 | ### v2.2.4 12 | - Fix deprecation warnings 13 | 14 | ## v2.1.0 15 | - Expose opening the `color-picker` as a service 16 | 17 | ### v2.1.1 18 | - [Probably fix](https://github.com/thomaslindstrom/color-picker/commit/a0a3c43afac9407ccbc95733113014b695d1387a) [the one bug report that has been occurring over and over again](https://github.com/thomaslindstrom/color-picker/issues/114) for the past half year or so 19 | 20 | ## v2.0.0 21 | - Completely rewritten source code 22 | - Easily convert between color formats in the Color Picker UI 23 | - Improved speed, performance and precision 24 | - Improved design: Now properly handles awkward positions 25 | - Add setting for preferred color format 26 | - Add setting for deciding what trigger key should open the Color Picker 27 | - Add setting for whether or not to serve a random color on open 28 | - Add setting for, if possible, abbreviating color values 29 | - Add setting for uppercasing color values 30 | - Add setting for automatically updating the color value in editor on change 31 | 32 | ### v2.0.1 33 | - Fix missing preview image 34 | 35 | ### v2.0.2 36 | - Fix `keymap` deprecation 37 | - Improve editor scroll binding 38 | 39 | ### v2.0.3 40 | - `Return` element was being unnecessarily rendered 41 | - Prevent sporadic `Format` element flicker 42 | - Better output abbreviation 43 | - Add Stylus variable support – *if they are preceded by `$`* 44 | - Stop mistakingly assuming color variables in unrelated files 45 | - *Behind the scenes* improvements 46 | 47 | ### v2.0.4 48 | - Remove Atom Module Cache from `package.json` 49 | 50 | ### v2.0.5 51 | - Set or replace color on key press `enter` 52 | 53 | ### v2.0.6 54 | - Opacity values equaling `1.0` would in some cases not be read 55 | - Fix issue where disabling the Shadow DOM would trigger a ton of bugs 56 | 57 | ### v2.0.7 58 | - Fix issues with placement when using `Split View` 59 | 60 | ### v2.0.8 61 | - Minor enhancements 62 | 63 | ### v2.0.9 64 | - Fix Atom version dependency 65 | 66 | ### v2.0.10 67 | - Avoid multiple of the same rendering in elements: Results in a snappier experience 68 | 69 | ### v2.0.11 70 | - Fix issue where the Color Picker, in some cases, wouldn't open 71 | 72 | ### v2.0.12 73 | - Fix issue where CSS 3D layering had broken “return” element in a recent Atom update 74 | 75 | ### v2.0.13 76 | - Update Atom version dependency 77 | - Replace deprecated functions 78 | 79 | ## v1.7.0 80 | - Fix deprecations 81 | 82 | ## v1.6.0 83 | - Fix deprecations 84 | 85 | ## v1.5.0 86 | - Move stylesheets to `/styles` directory 87 | - Replace deprecated `Atom View`, add dependency 88 | 89 | ## v1.4.4 90 | - Fix a bug where clicking a color pointer that lead to the active file didn't scroll to the definition 91 | 92 | ## v1.4.3 93 | - Remove `event-kit` dependency as it took a long time to activate 94 | - Tidy up some bits of code 95 | 96 | ## v1.4.2 97 | - Patch package. Uploading v.1.4.1 failed 98 | 99 | ## v1.4.1 100 | - Close color picker on editor scroll 101 | - Set up a `CHANGELOG.md` 102 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2015 Thomas Lindstrøm 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A Color Picker for Atom 2 | 3 | [![apm](https://img.shields.io/apm/v/color-picker.svg?style=flat-square)]() [![apm](https://img.shields.io/apm/dm/color-picker.svg?style=flat-square)]() 4 | 5 | Right click and select `Color Picker`, or hit `CMD-SHIFT-C`/`CTRL-ALT-C` to open it. Currently reads `HEX`, `HEXa`, `RGB`, `RGBa`, `HSL`, `HSLa`, `HSV`, `HSVa`, `VEC3` and `VEC4` colors – and is able to convert between the formats. 6 | 7 | It also inspects `Sass` and `LESS` color variables. Just open the `Color Picker` with the cursor at a variable and it'll look up the definition for you. From there, you can click the definition and go directly to where it's defined. 8 | 9 | ## Preview 10 | 11 | ![Color Picker in action](https://github.com/thomaslindstrom/color-picker/raw/master/preview.gif) 12 | 13 | ## Settings 14 | 15 | Open `Atom Settings`, go to `Packages` in the left hand sidebar, and press `Settings` on `color-picker` to open the list of settings available for the Color Picker. 16 | 17 | - **Abbreviate Color Values:** If possible, abbreviate color values, like for example “0.3” to “.3”, “#ffffff” to “#fff” and “rgb(0, 0, 0)” to “rgb(0,0,0)”. 18 | - **Automatically Replace Color:** Replace selected color automatically on change. Works well with as-you-type CSS reloaders. 19 | - **Preferred Color Format:** On open, the Color Picker will show a color in this format. 20 | - **Serve a random color on open:** If the Color Picker doesn't get an input color, it serves a completely random color. 21 | - **Trigger key:** Decide what trigger key should open the Color Picker. `CMD-SHIFT-{TRIGGER_KEY}` and `CTRL-ALT-{TRIGGER_KEY}`. Requires a restart. 22 | - **Uppercase Color Values:** If sensible, uppercase the color value. For example, “#aaa” becomes “#AAA”. 23 | 24 | ## To do 25 | 26 | - Selectable list of the current project color variables 27 | -------------------------------------------------------------------------------- /lib/ColorPicker-view.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker: view 3 | # ---------------------------------------------------------------------------- 4 | 5 | module.exports = -> 6 | Parent: null 7 | 8 | SmartColor: (require './modules/SmartColor')() 9 | SmartVariable: (require './modules/SmartVariable')() 10 | Emitter: (require './modules/Emitter')() 11 | 12 | extensions: {} 13 | getExtension: (extensionName) -> @extensions[extensionName] 14 | 15 | isFirstOpen: yes 16 | canOpen: yes 17 | element: null 18 | selection: null 19 | 20 | listeners: [] 21 | 22 | # ------------------------------------- 23 | # Create and activate Color Picker view 24 | # ------------------------------------- 25 | activate: -> 26 | _workspace = atom.workspace 27 | _workspaceView = atom.views.getView _workspace 28 | 29 | # Create element 30 | # --------------------------- 31 | @element = 32 | el: do -> 33 | _el = document.createElement 'div' 34 | _el.classList.add 'ColorPicker' 35 | 36 | return _el 37 | # Utility functions 38 | remove: -> @el.parentNode.removeChild @el 39 | 40 | addClass: (className) -> @el.classList.add className; return this 41 | removeClass: (className) -> @el.classList.remove className; return this 42 | hasClass: (className) -> @el.classList.contains className 43 | 44 | width: -> @el.offsetWidth 45 | height: -> @el.offsetHeight 46 | 47 | setHeight: (height) -> @el.style.height = "#{ height }px" 48 | 49 | hasChild: (child) -> 50 | if child and _parent = child.parentNode 51 | if child is @el 52 | return true 53 | else return @hasChild _parent 54 | return false 55 | 56 | # Open & Close the Color Picker 57 | isOpen: -> @hasClass 'is--open' 58 | open: -> @addClass 'is--open' 59 | close: -> @removeClass 'is--open' 60 | 61 | # Flip & Unflip the Color Picker 62 | isFlipped: -> @hasClass 'is--flipped' 63 | flip: -> @addClass 'is--flipped' 64 | unflip: -> @removeClass 'is--flipped' 65 | 66 | # Set Color Picker position 67 | # - x {Number} 68 | # - y {Number} 69 | setPosition: (x, y) -> 70 | @el.style.left = "#{ x }px" 71 | @el.style.top = "#{ y }px" 72 | return this 73 | 74 | # Add a child on the ColorPicker element 75 | add: (element) -> 76 | @el.appendChild element 77 | return this 78 | @loadExtensions() 79 | 80 | # Close the Color Picker on any activity unrelated to it 81 | # but also emit events on the Color Picker 82 | # --------------------------- 83 | @listeners.push ['mousedown', onMouseDown = (e) => 84 | return unless @element.isOpen() 85 | 86 | _isPickerEvent = @element.hasChild e.target 87 | @emitMouseDown e, _isPickerEvent 88 | return @close() unless _isPickerEvent] 89 | window.addEventListener 'mousedown', onMouseDown, true 90 | 91 | @listeners.push ['mousemove', onMouseMove = (e) => 92 | return unless @element.isOpen() 93 | 94 | _isPickerEvent = @element.hasChild e.target 95 | @emitMouseMove e, _isPickerEvent] 96 | window.addEventListener 'mousemove', onMouseMove, true 97 | 98 | @listeners.push ['mouseup', onMouseUp = (e) => 99 | return unless @element.isOpen() 100 | 101 | _isPickerEvent = @element.hasChild e.target 102 | @emitMouseUp e, _isPickerEvent] 103 | window.addEventListener 'mouseup', onMouseUp, true 104 | 105 | @listeners.push ['mousewheel', onMouseWheel = (e) => 106 | return unless @element.isOpen() 107 | 108 | _isPickerEvent = @element.hasChild e.target 109 | @emitMouseWheel e, _isPickerEvent] 110 | window.addEventListener 'mousewheel', onMouseWheel 111 | 112 | _workspaceView.addEventListener 'keydown', (e) => 113 | return unless @element.isOpen() 114 | 115 | _isPickerEvent = @element.hasChild e.target 116 | @emitKeyDown e, _isPickerEvent 117 | return @close() 118 | 119 | # Close it on scroll also 120 | atom.workspace.observeTextEditors (editor) => 121 | _editorView = atom.views.getView editor 122 | _subscriptionTop = _editorView.onDidChangeScrollTop => @close() 123 | _subscriptionLeft = _editorView.onDidChangeScrollLeft => @close() 124 | 125 | editor.onDidDestroy -> 126 | _subscriptionTop.dispose() 127 | _subscriptionLeft.dispose() 128 | @onBeforeDestroy -> 129 | _subscriptionTop.dispose() 130 | _subscriptionLeft.dispose() 131 | return 132 | 133 | # Close it when the window resizes 134 | @listeners.push ['resize', onResize = => 135 | @close()] 136 | window.addEventListener 'resize', onResize 137 | 138 | # Close it when the active item is changed 139 | _workspace.getActivePane().onDidChangeActiveItem => @close() 140 | 141 | # Place the Color Picker element 142 | # --------------------------- 143 | @close() 144 | @canOpen = yes 145 | 146 | # TODO: Is this really the best way to do this? Hint: Probably not 147 | (@Parent = (atom.views.getView atom.workspace).querySelector '.vertical') 148 | .appendChild @element.el 149 | return this 150 | 151 | # ------------------------------------- 152 | # Destroy the view and unbind events 153 | # ------------------------------------- 154 | destroy: -> 155 | @emitBeforeDestroy() 156 | 157 | for [_event, _listener] in @listeners 158 | window.removeEventListener _event, _listener 159 | 160 | @element.remove() 161 | @canOpen = no 162 | 163 | # ------------------------------------- 164 | # Load Color Picker extensions // more like dependencies 165 | # ------------------------------------- 166 | loadExtensions: -> 167 | # TODO: This is really stupid. Should this be done with `fs` or something? 168 | # TODO: Extension files have pretty much the same base. Simplify? 169 | for _extension in ['Arrow', 'Color', 'Body', 'Saturation', 'Alpha', 'Hue', 'Definition', 'Return', 'Format'] 170 | _requiredExtension = (require "./extensions/#{ _extension }")(this) 171 | @extensions[_extension] = _requiredExtension 172 | _requiredExtension.activate?() 173 | return 174 | 175 | # ------------------------------------- 176 | # Set up events and handling 177 | # ------------------------------------- 178 | # Mouse events 179 | emitMouseDown: (e, isOnPicker) -> 180 | @Emitter.emit 'mouseDown', e, isOnPicker 181 | onMouseDown: (callback) -> 182 | @Emitter.on 'mouseDown', callback 183 | 184 | emitMouseMove: (e, isOnPicker) -> 185 | @Emitter.emit 'mouseMove', e, isOnPicker 186 | onMouseMove: (callback) -> 187 | @Emitter.on 'mouseMove', callback 188 | 189 | emitMouseUp: (e, isOnPicker) -> 190 | @Emitter.emit 'mouseUp', e, isOnPicker 191 | onMouseUp: (callback) -> 192 | @Emitter.on 'mouseUp', callback 193 | 194 | emitMouseWheel: (e, isOnPicker) -> 195 | @Emitter.emit 'mouseWheel', e, isOnPicker 196 | onMouseWheel: (callback) -> 197 | @Emitter.on 'mouseWheel', callback 198 | 199 | # Key events 200 | emitKeyDown: (e, isOnPicker) -> 201 | @Emitter.emit 'keyDown', e, isOnPicker 202 | onKeyDown: (callback) -> 203 | @Emitter.on 'keyDown', callback 204 | 205 | # Position Change 206 | emitPositionChange: (position, colorPickerPosition) -> 207 | @Emitter.emit 'positionChange', position, colorPickerPosition 208 | onPositionChange: (callback) -> 209 | @Emitter.on 'positionChange', callback 210 | 211 | # Opening 212 | emitOpen: -> 213 | @Emitter.emit 'open' 214 | onOpen: (callback) -> 215 | @Emitter.on 'open', callback 216 | 217 | # Before opening 218 | emitBeforeOpen: -> 219 | @Emitter.emit 'beforeOpen' 220 | onBeforeOpen: (callback) -> 221 | @Emitter.on 'beforeOpen', callback 222 | 223 | # Closing 224 | emitClose: -> 225 | @Emitter.emit 'close' 226 | onClose: (callback) -> 227 | @Emitter.on 'close', callback 228 | 229 | # Before destroying 230 | emitBeforeDestroy: -> 231 | @Emitter.emit 'beforeDestroy' 232 | onBeforeDestroy: (callback) -> 233 | @Emitter.on 'beforeDestroy', callback 234 | 235 | # Input Color 236 | emitInputColor: (smartColor, wasFound=true) -> 237 | @Emitter.emit 'inputColor', smartColor, wasFound 238 | onInputColor: (callback) -> 239 | @Emitter.on 'inputColor', callback 240 | 241 | # Input Variable 242 | emitInputVariable: (match) -> 243 | @Emitter.emit 'inputVariable', match 244 | onInputVariable: (callback) -> 245 | @Emitter.on 'inputVariable', callback 246 | 247 | # Input Variable Color 248 | emitInputVariableColor: (smartColor, pointer) -> 249 | @Emitter.emit 'inputVariableColor', smartColor, pointer 250 | onInputVariableColor: (callback) -> 251 | @Emitter.on 'inputVariableColor', callback 252 | 253 | # ------------------------------------- 254 | # Open the Color Picker 255 | # ------------------------------------- 256 | open: (Editor=null, Cursor=null) -> 257 | return unless @canOpen 258 | @emitBeforeOpen() 259 | 260 | Editor = atom.workspace.getActiveTextEditor() unless Editor 261 | EditorView = atom.views.getView Editor 262 | EditorElement = Editor.getElement() 263 | 264 | return unless EditorView 265 | 266 | # Reset selection 267 | @selection = null 268 | 269 | # Find the current cursor 270 | # --------------------------- 271 | Cursor = Editor.getLastCursor() unless Cursor 272 | 273 | # Fail if the cursor isn't visible 274 | _visibleRowRange = EditorView.getVisibleRowRange() 275 | _cursorScreenRow = Cursor.getScreenRow() 276 | _cursorBufferRow = Cursor.getBufferRow() 277 | 278 | return if (_cursorScreenRow < _visibleRowRange[0]) or (_cursorScreenRow > _visibleRowRange[1]) 279 | 280 | # Try matching the contents of the current line to color regexes 281 | _lineContent = Cursor.getCurrentBufferLine() 282 | 283 | _colorMatches = @SmartColor.find _lineContent 284 | _variableMatches = @SmartVariable.find _lineContent, Editor.getPath() 285 | _matches = _colorMatches.concat _variableMatches 286 | 287 | # Figure out which of the matches is the one the user wants 288 | _cursorPosition = EditorElement.pixelPositionForScreenPosition Cursor.getScreenPosition() 289 | _cursorColumn = Cursor.getBufferColumn() 290 | 291 | _match = do -> for _match in _matches 292 | return _match if _match.start <= _cursorColumn and _match.end >= _cursorColumn 293 | 294 | # If we've got a match, we should select it 295 | if _match 296 | Editor.clearSelections() 297 | 298 | _selection = Editor.addSelectionForBufferRange [ 299 | [_cursorBufferRow, _match.start] 300 | [_cursorBufferRow, _match.end]] 301 | @selection = match: _match, row: _cursorBufferRow 302 | # But if we don't have a match, center the Color Picker on last cursor 303 | else 304 | @selection = column: _cursorColumn, row: _cursorBufferRow 305 | 306 | # Emit 307 | # --------------------------- 308 | if _match 309 | # The match is a variable. Look up the definition 310 | if _match.isVariable? 311 | _match.getDefinition() 312 | .then (definition) => 313 | _smartColor = (@SmartColor.find definition.value)[0].getSmartColor() 314 | @emitInputVariableColor _smartColor, definition.pointer 315 | .catch (error) => 316 | @emitInputVariableColor false 317 | @emitInputVariable _match 318 | # The match is a color 319 | else @emitInputColor _match.getSmartColor() 320 | # No match, but `randomColor` option is set 321 | else if atom.config.get 'color-picker.randomColor' 322 | _randomColor = @SmartColor.RGBArray [ 323 | ((Math.random() * 255) + .5) << 0 324 | ((Math.random() * 255) + .5) << 0 325 | ((Math.random() * 255) + .5) << 0] 326 | 327 | # Convert to `preferredColor`, and then emit it 328 | _preferredFormat = atom.config.get 'color-picker.preferredFormat' 329 | _convertedColor = _randomColor["to#{ _preferredFormat }"]() 330 | _randomColor = @SmartColor[_preferredFormat](_convertedColor) 331 | 332 | @emitInputColor _randomColor, false 333 | # No match, and it's the first open 334 | else if @isFirstOpen 335 | _redColor = @SmartColor.HEX '#f00' 336 | 337 | # Convert to `preferredColor`, and then emit it 338 | _preferredFormat = atom.config.get 'color-picker.preferredFormat' 339 | 340 | if _redColor.format isnt _preferredFormat 341 | _convertedColor = _redColor["to#{ _preferredFormat }"]() 342 | _redColor = @SmartColor[_preferredFormat](_convertedColor) 343 | @isFirstOpen = no 344 | 345 | @emitInputColor _redColor, false 346 | 347 | # After (& if) having selected text (as this might change the scroll 348 | # position) gather information about the Editor 349 | # --------------------------- 350 | PaneView = atom.views.getView atom.workspace.getActivePane() 351 | _paneOffsetTop = PaneView.offsetTop 352 | _paneOffsetLeft = PaneView.offsetLeft 353 | 354 | _editorOffsetTop = EditorView.parentNode.offsetTop 355 | _editorOffsetLeft = EditorView.querySelector('.scroll-view').offsetLeft 356 | _editorScrollTop = EditorView.getScrollTop() 357 | 358 | _lineHeight = Editor.getLineHeightInPixels() 359 | _lineOffsetLeft = EditorView.querySelector('.line').offsetLeft 360 | 361 | # Center it on the middle of the selection range 362 | # TODO: There can be lines over more than one row 363 | if _match 364 | _rect = EditorElement.pixelRectForScreenRange(_selection.getScreenRange()) 365 | _right = _rect.left + _rect.width 366 | _cursorPosition.left = _right - (_rect.width / 2) 367 | 368 | # Figure out where to place the Color Picker 369 | # --------------------------- 370 | _totalOffsetTop = _paneOffsetTop + _lineHeight - _editorScrollTop + _editorOffsetTop 371 | _totalOffsetLeft = _paneOffsetLeft + _editorOffsetLeft + _lineOffsetLeft 372 | 373 | _position = 374 | x: _cursorPosition.left + _totalOffsetLeft 375 | y: _cursorPosition.top + _totalOffsetTop 376 | 377 | # Figure out where to actually place the Color Picker by 378 | # setting up boundaries and flipping it if necessary 379 | # --------------------------- 380 | _colorPickerPosition = 381 | x: do => 382 | _colorPickerWidth = @element.width() 383 | _halfColorPickerWidth = (_colorPickerWidth / 2) << 0 384 | 385 | # Make sure the Color Picker isn't too far to the left 386 | _x = Math.max 10, _position.x - _halfColorPickerWidth 387 | # Make sure the Color Picker isn't too far to the right 388 | _x = Math.min (@Parent.offsetWidth - _colorPickerWidth - 10), _x 389 | 390 | return _x 391 | y: do => 392 | @element.unflip() 393 | 394 | # TODO: It's not really working out great 395 | 396 | # If the color picker is too far down, flip it 397 | if @element.height() + _position.y > @Parent.offsetHeight - 32 398 | @element.flip() 399 | return _position.y - _lineHeight - @element.height() 400 | # But if it's fine, keep the Y position 401 | else return _position.y 402 | 403 | # Set Color Picker position and emit events 404 | @element.setPosition _colorPickerPosition.x, _colorPickerPosition.y 405 | @emitPositionChange _position, _colorPickerPosition 406 | 407 | # Open the Color Picker 408 | requestAnimationFrame => # wait for class delay 409 | @element.open() 410 | @emitOpen() 411 | return true 412 | 413 | # ------------------------------------- 414 | # Replace selected color 415 | # ------------------------------------- 416 | canReplace: yes 417 | replace: (color) -> 418 | return unless @canReplace 419 | @canReplace = no 420 | 421 | Editor = atom.workspace.getActiveTextEditor() 422 | Editor.clearSelections() 423 | 424 | if @selection.match 425 | _cursorStart = @selection.match.start 426 | _cursorEnd = @selection.match.end 427 | else _cursorStart = _cursorEnd = @selection.column 428 | 429 | # Select the color we're going to replace 430 | Editor.addSelectionForBufferRange [ 431 | [@selection.row, _cursorStart] 432 | [@selection.row, _cursorEnd]] 433 | Editor.replaceSelectedText null, => color 434 | 435 | # Select the newly inserted color and move the cursor to it 436 | setTimeout => 437 | Editor.setCursorBufferPosition [ 438 | @selection.row, _cursorStart] 439 | Editor.clearSelections() 440 | 441 | # Update selection length 442 | @selection.match?.end = _cursorStart + color.length 443 | 444 | Editor.addSelectionForBufferRange [ 445 | [@selection.row, _cursorStart] 446 | [@selection.row, _cursorStart + color.length]] 447 | return setTimeout ( => @canReplace = yes), 100 448 | return 449 | 450 | # ------------------------------------- 451 | # Close the Color Picker 452 | # ------------------------------------- 453 | close: -> 454 | @element.close() 455 | @emitClose() 456 | -------------------------------------------------------------------------------- /lib/ColorPicker.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker 3 | # ---------------------------------------------------------------------------- 4 | 5 | module.exports = 6 | view: null 7 | 8 | activate: -> 9 | @view = (require './ColorPicker-view')() 10 | _command = 'color-picker:open' 11 | 12 | # Set key bindings 13 | # --------------------------- 14 | _triggerKey = (atom.config.get 'color-picker.triggerKey').toLowerCase() 15 | _TriggerKey = _triggerKey.toUpperCase() 16 | 17 | # TODO this doesn't look too good 18 | _macSelector = '.platform-darwin atom-workspace' 19 | _windowsSelector = '.platform-win32 atom-workspace' 20 | _linuxSelector = '.platform-linux atom-workspace' 21 | 22 | _keymap = {} 23 | 24 | # Mac OS X 25 | _keymap["#{ _macSelector }"] = {} 26 | _keymap["#{ _macSelector }"]["cmd-#{ _TriggerKey }"] = _command 27 | # Windows 28 | _keymap["#{ _windowsSelector }"] = {} 29 | _keymap["#{ _windowsSelector }"]["ctrl-alt-#{ _triggerKey }"] = _command 30 | # Linux 31 | _keymap["#{ _linuxSelector }"] = {} 32 | _keymap["#{ _linuxSelector }"]["ctrl-alt-#{ _triggerKey }"] = _command 33 | 34 | # Add the keymap 35 | atom.keymaps.add 'color-picker:trigger', _keymap 36 | 37 | # Add context menu command 38 | # --------------------------- 39 | atom.contextMenu.add 'atom-text-editor': [ 40 | label: 'Color Picker' 41 | command: _command] 42 | 43 | # Add color-picker:open command 44 | # --------------------------- 45 | _commands = {}; _commands["#{ _command }"] = => 46 | return unless @view?.canOpen 47 | @view.open() 48 | atom.commands.add 'atom-text-editor', _commands 49 | 50 | return @view.activate() 51 | 52 | deactivate: -> @view?.destroy() 53 | 54 | provideColorPicker: -> 55 | return { 56 | open: (Editor, Cursor) => 57 | return unless @view?.canOpen 58 | return @view.open Editor, Cursor 59 | } 60 | 61 | config: 62 | # Random color configuration: On Color Picker open, show a random color 63 | randomColor: 64 | title: 'Serve a random color on open' 65 | description: 'If the Color Picker doesn\'t get an input color, it serves a completely random color.' 66 | type: 'boolean' 67 | default: true 68 | # Automatic Replace configuration: Replace color value on change 69 | automaticReplace: 70 | title: 'Automatically Replace Color' 71 | description: 'Replace selected color automatically on change. Works well with as-you-type CSS reloaders.' 72 | type: 'boolean' 73 | default: false 74 | # Always output alpha value 75 | alphaChannelAlways: 76 | title: 'Always include alpha channel value' 77 | description: 'Output alpha channel value, even if it is 1.0' 78 | type: 'boolean' 79 | default: false 80 | # Abbreviate values configuration: If possible, abbreviate color values. Eg. “0.3” to “.3” 81 | # TODO: Can we abbreviate something else? 82 | abbreviateValues: 83 | title: 'Abbreviate Color Values' 84 | description: 'If possible, abbreviate color values, like for example “0.3” to “.3”, “#ffffff” to “#fff” and “rgb(0, 0, 0)” to “rgb(0,0,0)”.' 85 | type: 'boolean' 86 | default: false 87 | # Uppercase color value configuration: Uppercase for example HEX color values 88 | # TODO: Does it make sense to uppercase anything other than HEX colors? 89 | uppercaseColorValues: 90 | title: 'Uppercase Color Values' 91 | description: 'If sensible, uppercase the color value. For example, “#aaa” becomes “#AAA”.' 92 | type: 'boolean' 93 | default: false 94 | # Preferred color format configuration: Set what color format the color picker should display initially 95 | preferredFormat: 96 | title: 'Preferred Color Format' 97 | description: 'On open, the Color Picker will show a color in this format.' 98 | type: 'string' 99 | enum: ['RGB', 'HEX', 'HSL', 'HSV', 'VEC'] 100 | default: 'RGB' 101 | # Trigger key: Set what trigger key opens the color picker 102 | # TODO more options? 103 | triggerKey: 104 | title: 'Trigger key' 105 | description: 'Decide what trigger key should open the Color Picker. `CMD-SHIFT-{TRIGGER_KEY}` and `CTRL-ALT-{TRIGGER_KEY}`. Requires a restart.' 106 | type: 'string' 107 | enum: ['C', 'E', 'H', 'K'] 108 | default: 'C' 109 | -------------------------------------------------------------------------------- /lib/extensions/Alpha.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Alpha 3 | # Color Alpha controller 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | Emitter: (require '../modules/Emitter')() 8 | 9 | element: null 10 | control: null 11 | canvas: null 12 | 13 | # ------------------------------------- 14 | # Set up events and handling 15 | # ------------------------------------- 16 | # Selection Changed event 17 | emitSelectionChanged: -> 18 | @Emitter.emit 'selectionChanged', @control.selection 19 | onSelectionChanged: (callback) -> 20 | @Emitter.on 'selectionChanged', callback 21 | 22 | # Color Changed event 23 | emitColorChanged: -> 24 | @Emitter.emit 'colorChanged', @control.selection.color 25 | onColorChanged: (callback) -> 26 | @Emitter.on 'colorChanged', callback 27 | 28 | # ------------------------------------- 29 | # Create and activate Alpha controller 30 | # ------------------------------------- 31 | activate: -> 32 | Body = colorPicker.getExtension 'Body' 33 | 34 | # Create element 35 | # --------------------------- 36 | @element = 37 | el: do -> 38 | _classPrefix = Body.element.el.className 39 | _el = document.createElement 'div' 40 | _el.classList.add "#{ _classPrefix }-alpha" 41 | 42 | return _el 43 | # Utility functions 44 | width: 0 45 | height: 0 46 | getWidth: -> return @width or @el.offsetWidth 47 | getHeight: -> return @height or @el.offsetHeight 48 | 49 | rect: null 50 | getRect: -> return @rect or @updateRect() 51 | updateRect: -> @rect = @el.getClientRects()[0] 52 | 53 | # Add a child on the Alpha element 54 | add: (element) -> 55 | @el.appendChild element 56 | return this 57 | Body.element.add @element.el, 1 58 | 59 | # Update element rect position when Color Picker opens 60 | # --------------------------- 61 | colorPicker.onOpen => @element.updateRect() 62 | 63 | # Create and draw canvas 64 | # --------------------------- 65 | setTimeout => # wait for the DOM 66 | Alpha = this 67 | Saturation = colorPicker.getExtension 'Saturation' 68 | 69 | # Prepare some variables 70 | _elementWidth = @element.getWidth() 71 | _elementHeight = @element.getHeight() 72 | 73 | # Create canvas element 74 | @canvas = 75 | el: do -> 76 | _el = document.createElement 'canvas' 77 | _el.width = _elementWidth 78 | _el.height = _elementHeight 79 | _el.classList.add "#{ Alpha.element.el.className }-canvas" 80 | 81 | return _el 82 | # Utility functions 83 | context: null 84 | getContext: -> @context or (@context = @el.getContext '2d') 85 | 86 | # Render Alpha canvas 87 | previousRender: null 88 | render: (smartColor) -> 89 | _rgb = ( do -> 90 | unless smartColor 91 | return colorPicker.SmartColor.HEX '#f00' 92 | else return smartColor 93 | ).toRGBArray().join ',' 94 | 95 | return if @previousRender and @previousRender is _rgb 96 | 97 | # Get context and clear it 98 | _context = @getContext() 99 | _context.clearRect 0, 0, _elementWidth, _elementHeight 100 | 101 | # Draw alpha channel 102 | _gradient = _context.createLinearGradient 0, 0, 1, _elementHeight 103 | _gradient.addColorStop .01, "rgba(#{ _rgb },1)" 104 | _gradient.addColorStop .99, "rgba(#{ _rgb },0)" 105 | 106 | _context.fillStyle = _gradient 107 | _context.fillRect 0, 0, _elementWidth, _elementHeight 108 | return @previousRender = _rgb 109 | 110 | # Render again on Saturation color change 111 | Saturation.onColorChanged (smartColor) => 112 | @canvas.render smartColor 113 | @canvas.render() 114 | 115 | # Add to Alpha element 116 | @element.add @canvas.el 117 | 118 | # Create Alpha control element 119 | # --------------------------- 120 | setTimeout => # wait for the DOM 121 | hasChild = (element, child) -> 122 | if child and _parent = child.parentNode 123 | if child is element 124 | return true 125 | else return hasChild element, _parent 126 | return false 127 | 128 | # Create element 129 | Alpha = this 130 | Saturation = colorPicker.getExtension 'Saturation' 131 | 132 | @control = 133 | el: do -> 134 | _el = document.createElement 'div' 135 | _el.classList.add "#{ Alpha.element.el.className }-control" 136 | 137 | return _el 138 | isGrabbing: no 139 | 140 | previousControlPosition: null 141 | updateControlPosition: (y) -> 142 | _joined = ",#{ y }" 143 | return if @previousControlPosition and @previousControlPosition is _joined 144 | 145 | requestAnimationFrame => 146 | @el.style.top = "#{ y }px" 147 | return @previousControlPosition = _joined 148 | 149 | selection: 150 | y: 0 151 | color: null 152 | alpha: null 153 | setSelection: (e, alpha=null, offset=null) -> 154 | _rect = Alpha.element.getRect() 155 | _width = Alpha.element.getWidth() 156 | _height = Alpha.element.getHeight() 157 | 158 | if e then _y = e.pageY - _rect.top 159 | # Set the alpha directly 160 | else if (typeof alpha is 'number') 161 | _y = _height - (alpha * _height) # reversed, 1 is top 162 | # Handle scroll 163 | else if (typeof offset is 'number') 164 | _y = @selection.y + offset 165 | # Default to previous values 166 | else _y = @selection.y 167 | 168 | _y = @selection.y = Math.max 0, (Math.min _height, _y) 169 | 170 | _alpha = 1 - (_y / _height) # reversed, 1 is top 171 | @selection.alpha = (Math.round _alpha * 100) / 100 # 2 decimal precision 172 | 173 | # Update the smartColor (if any) 174 | if _smartColor = @selection.color 175 | _RGBAArray = _smartColor.toRGBAArray() 176 | _RGBAArray[3] = @selection.alpha 177 | 178 | @selection.color = colorPicker.SmartColor.RGBAArray _RGBAArray 179 | Alpha.emitColorChanged() 180 | # Or set a default red 181 | else @selection.color = colorPicker.SmartColor.RGBAArray [255, 0, 0, @selection.alpha] 182 | 183 | _position = 184 | y: Math.max 3, (Math.min (_height - 6), _y) 185 | @updateControlPosition _position.y 186 | 187 | return Alpha.emitSelectionChanged() 188 | 189 | refreshSelection: -> @setSelection() 190 | @control.refreshSelection() 191 | 192 | # If the Color Picker is fed a color, set it 193 | colorPicker.onInputColor (smartColor) => 194 | @control.setSelection null, smartColor.getAlpha() 195 | 196 | # Reset 197 | colorPicker.onOpen => @control.isGrabbing = no 198 | colorPicker.onClose => @control.isGrabbing = no 199 | 200 | # Bind controller events 201 | Saturation.onColorChanged (smartColor) => 202 | @control.selection.color = smartColor 203 | @control.refreshSelection() 204 | 205 | colorPicker.onMouseDown (e, isOnPicker) => 206 | return unless isOnPicker and hasChild Alpha.element.el, e.target 207 | e.preventDefault() 208 | @control.isGrabbing = yes 209 | @control.setSelection e 210 | 211 | colorPicker.onMouseMove (e) => 212 | return unless @control.isGrabbing 213 | @control.setSelection e 214 | 215 | colorPicker.onMouseUp (e) => 216 | return unless @control.isGrabbing 217 | @control.isGrabbing = no 218 | @control.setSelection e 219 | 220 | colorPicker.onMouseWheel (e, isOnPicker) => 221 | return unless isOnPicker and hasChild Alpha.element.el, e.target 222 | e.preventDefault() 223 | @control.setSelection null, null, (e.wheelDeltaY * .33) # make it a bit softer 224 | 225 | # Add to Alpha element 226 | @element.add @control.el 227 | return this 228 | -------------------------------------------------------------------------------- /lib/extensions/Arrow.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Arrow 3 | # An arrow pointing at the current selection 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | element: null 8 | 9 | # ------------------------------------- 10 | # Create and activate Arrow 11 | # ------------------------------------- 12 | activate: -> 13 | _halfArrowWidth = null 14 | 15 | # Create element 16 | # --------------------------- 17 | @element = 18 | el: do -> 19 | _classPrefix = colorPicker.element.el.className 20 | _el = document.createElement 'div' 21 | _el.classList.add "#{ _classPrefix }-arrow" 22 | 23 | return _el 24 | # Utility functions 25 | addClass: (className) -> @el.classList.add className; return this 26 | removeClass: (className) -> @el.classList.remove className; return this 27 | hasClass: (className) -> @el.classList.contains className 28 | 29 | width: -> @el.offsetWidth 30 | height: -> @el.offsetHeight 31 | 32 | # Set Arrow position 33 | # - x {Number} 34 | setPosition: (x) -> 35 | @el.style.left = "#{ x }px" 36 | return this 37 | 38 | # Set the Color element background color 39 | previousColor: null 40 | setColor: (smartColor) -> 41 | _color = smartColor.toRGBA?() or 'none' 42 | return if @previousColor and @previousColor is _color 43 | 44 | @el.style.borderTopColor = _color 45 | @el.style.borderBottomColor = _color 46 | return @previousColor = _color 47 | colorPicker.element.add @element.el 48 | 49 | # Get and save arrow width 50 | # --------------------------- 51 | setTimeout => _halfArrowWidth = (@element.width() / 2) << 0 52 | 53 | # Increase Color Picker height 54 | # --------------------------- 55 | setTimeout => 56 | _newHeight = colorPicker.element.height() + @element.height() 57 | colorPicker.element.setHeight _newHeight 58 | 59 | # Set Arrow color on Alpha change 60 | # --------------------------- 61 | setTimeout => # wait for the DOM 62 | Alpha = colorPicker.getExtension 'Alpha' 63 | 64 | Alpha.onColorChanged (smartColor) => 65 | if smartColor then @element.setColor smartColor 66 | # Default to #f00 red 67 | else colorPicker.SmartColor.HEX '#f00' 68 | return 69 | 70 | # Set Arrow color to transparent when a variable is input 71 | # --------------------------- 72 | colorPicker.onInputVariable => 73 | @element.setColor colorPicker.SmartColor.RGBAArray [0, 0, 0, 0] 74 | 75 | # ... but set it to the variable color when that is found 76 | # --------------------------- 77 | colorPicker.onInputVariableColor (smartColor) => 78 | return unless smartColor 79 | @element.setColor smartColor 80 | 81 | # Place the Arrow 82 | # --------------------------- 83 | colorPicker.onPositionChange (position, colorPickerPosition) => 84 | @element.setPosition position.x - colorPickerPosition.x 85 | return this 86 | -------------------------------------------------------------------------------- /lib/extensions/Body.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Body 3 | # The Color Picker Body, serves as the container for color controls 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | element: null 8 | 9 | # ------------------------------------- 10 | # Create and activate Body 11 | # ------------------------------------- 12 | activate: -> 13 | @element = 14 | el: do -> 15 | _classPrefix = colorPicker.element.el.className 16 | _el = document.createElement 'div' 17 | _el.classList.add "#{ _classPrefix }-body" 18 | 19 | return _el 20 | # Utility functions 21 | height: -> @el.offsetHeight 22 | 23 | # Add a child on the Body element 24 | add: (element, weight) -> 25 | if weight 26 | if weight > @el.children.length 27 | @el.appendChild element 28 | else @el.insertBefore element, @el.children[weight] 29 | else @el.appendChild element 30 | 31 | return this 32 | colorPicker.element.add @element.el 33 | 34 | # Increase Color Picker height 35 | # --------------------------- 36 | setTimeout => 37 | _newHeight = colorPicker.element.height() + @element.height() 38 | colorPicker.element.setHeight _newHeight 39 | 40 | return this 41 | -------------------------------------------------------------------------------- /lib/extensions/Color.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Color 3 | # The element showing the current color 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | Emitter: (require '../modules/Emitter')() 8 | 9 | element: null 10 | color: null 11 | 12 | # ------------------------------------- 13 | # Set up events and handling 14 | # ------------------------------------- 15 | # Output format event 16 | emitOutputFormat: (format) -> 17 | @Emitter.emit 'outputFormat', format 18 | onOutputFormat: (callback) -> 19 | @Emitter.on 'outputFormat', callback 20 | 21 | # ------------------------------------- 22 | # Create and activate Color element 23 | # ------------------------------------- 24 | activate: -> 25 | @element = 26 | el: do -> 27 | _classPrefix = colorPicker.element.el.className 28 | _el = document.createElement 'div' 29 | _el.classList.add "#{ _classPrefix }-color" 30 | 31 | return _el 32 | # Utility functions 33 | addClass: (className) -> @el.classList.add className; return this 34 | removeClass: (className) -> @el.classList.remove className; return this 35 | 36 | height: -> @el.offsetHeight 37 | 38 | # Add a child on the Color element 39 | add: (element) -> 40 | @el.appendChild element 41 | return this 42 | 43 | # Set the Color element background color 44 | previousColor: null 45 | setColor: (smartColor) -> 46 | _color = smartColor.toRGBA() 47 | return if @previousColor and @previousColor is _color 48 | 49 | @el.style.backgroundColor = _color 50 | return @previousColor = _color 51 | colorPicker.element.add @element.el 52 | 53 | # Increase Color Picker height 54 | # --------------------------- 55 | setTimeout => 56 | _newHeight = colorPicker.element.height() + @element.height() 57 | colorPicker.element.setHeight _newHeight 58 | 59 | # Set or replace Color on click 60 | # --------------------------- 61 | hasChild = (element, child) -> 62 | if child and _parent = child.parentNode 63 | if child is element 64 | return true 65 | else return hasChild element, _parent 66 | return false 67 | 68 | _isClicking = no 69 | 70 | colorPicker.onMouseDown (e, isOnPicker) => 71 | return unless isOnPicker and hasChild @element.el, e.target 72 | e.preventDefault() 73 | _isClicking = yes 74 | 75 | colorPicker.onMouseMove (e) -> 76 | _isClicking = no 77 | 78 | colorPicker.onMouseUp (e) => 79 | return unless _isClicking 80 | colorPicker.replace @color 81 | colorPicker.element.close() 82 | 83 | # Set or replace Color on key press enter 84 | # --------------------------- 85 | colorPicker.onKeyDown (e) => 86 | return unless e.which is 13 87 | e.stopPropagation() 88 | colorPicker.replace @color 89 | 90 | # Set background element color on Alpha change 91 | # --------------------------- 92 | setTimeout => # wait for the DOM 93 | Alpha = colorPicker.getExtension 'Alpha' 94 | 95 | Alpha.onColorChanged (smartColor) => 96 | @element.setColor do -> 97 | if smartColor then return smartColor 98 | # Default to #f00 red 99 | else return colorPicker.SmartColor.HEX '#f00' 100 | return 101 | return 102 | 103 | # Create Color text element 104 | # --------------------------- 105 | setTimeout => 106 | Alpha = colorPicker.getExtension 'Alpha' 107 | Return = colorPicker.getExtension 'Return' 108 | Format = colorPicker.getExtension 'Format' 109 | 110 | # Create text element 111 | _text = document.createElement 'p' 112 | _text.classList.add "#{ @element.el.className }-text" 113 | 114 | # Reset before color picker open 115 | colorPicker.onBeforeOpen => @color = null 116 | 117 | # Keep track of the input color (for its format) 118 | _inputColor = null 119 | 120 | colorPicker.onInputColor (smartColor, wasFound) -> 121 | _inputColor = if wasFound 122 | smartColor 123 | else null 124 | 125 | # Keep track of the Format element format 126 | _formatFormat = null 127 | Format.onFormatChanged (format) -> _formatFormat = format 128 | colorPicker.onInputColor -> _formatFormat = null 129 | 130 | # Set the text element to contain the Color data 131 | setColor = (smartColor) => 132 | _preferredFormat = atom.config.get 'color-picker.preferredFormat' 133 | _format = _formatFormat or _inputColor?.format or _preferredFormat or 'RGB' 134 | 135 | # TODO: This is very fragile 136 | _function = if smartColor.getAlpha() < 1 || atom.config.get 'color-picker.alphaChannelAlways' 137 | (smartColor["to#{ _format }A"] or smartColor["to#{ _format }"]) 138 | else smartColor["to#{ _format }"] 139 | 140 | # If a color was input, and the value hasn't changed since, 141 | # show the inital value not to confuse the user, but only 142 | # if the input color format is still the same 143 | _outputColor = do -> 144 | if _inputColor and (_inputColor.format is _format or _inputColor.format is "#{ _format }A") 145 | if smartColor.equals _inputColor 146 | return _inputColor.value 147 | return _function.call smartColor 148 | 149 | # Finish here if the _outputColor is the same as the 150 | # current color 151 | return unless _outputColor isnt @color 152 | 153 | # Automatically replace color in editor if 154 | # `automaticReplace` is true, but only if there was an 155 | # input color and if it is different from before 156 | if _inputColor and atom.config.get 'color-picker.automaticReplace' 157 | colorPicker.replace _outputColor 158 | 159 | # Set and save the output color 160 | @color = _outputColor 161 | _text.innerText = _outputColor 162 | 163 | return @emitOutputFormat _format 164 | 165 | # Update on alpha change, keep track of current color 166 | _currentColor = null 167 | 168 | Alpha.onColorChanged (smartColor) => 169 | setColor _currentColor = do -> 170 | if smartColor then return smartColor 171 | # Default to #f00 red 172 | else return colorPicker.SmartColor.HEX '#f00' 173 | return 174 | 175 | # When Format is changed, update color 176 | Format.onFormatChanged -> setColor _currentColor 177 | 178 | # When the `Return` element is visible, add a class to allow 179 | # the text to be pushed up or down a bit 180 | Return.onVisibility (visibility) => 181 | if visibility then @element.addClass 'is--returnVisible' 182 | else @element.removeClass 'is--returnVisible' 183 | @element.add _text 184 | return this 185 | -------------------------------------------------------------------------------- /lib/extensions/Definition.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Definition 3 | # The element showing the current variable definition 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | element: null 8 | pointer: null 9 | 10 | # ------------------------------------- 11 | # Create and activate Definition element 12 | # ------------------------------------- 13 | activate: -> 14 | @element = 15 | el: do -> 16 | _classPrefix = colorPicker.element.el.className 17 | _el = document.createElement 'div' 18 | _el.classList.add "#{ _classPrefix }-definition" 19 | 20 | return _el 21 | # Utility functions 22 | height: -> @el.offsetHeight 23 | 24 | # Add a child on the Definition element 25 | add: (element) -> 26 | @el.appendChild element 27 | return this 28 | 29 | # Set the Definition element background color 30 | setColor: (smartColor) -> 31 | @el.style.backgroundColor = smartColor.toRGBA() 32 | colorPicker.element.add @element.el 33 | 34 | # Set Color Picker height 35 | # --------------------------- 36 | setTimeout => 37 | Arrow = colorPicker.getExtension 'Arrow' 38 | $colorPicker = colorPicker.element 39 | 40 | # Change view mode when a variable is input 41 | colorPicker.onInputVariable => 42 | _oldHeight = $colorPicker.height() 43 | $colorPicker.addClass 'view--definition' 44 | 45 | _newHeight = @element.height() + Arrow.element.height() 46 | $colorPicker.setHeight _newHeight 47 | 48 | # Reset current element background color 49 | @element.setColor colorPicker.SmartColor.RGBAArray [0, 0, 0, 0] 50 | 51 | # Reset picker on close, and clear the event 52 | # TODO handle this on the ColorPicker itself, maybe? 53 | onClose = -> 54 | colorPicker.canOpen = yes 55 | $colorPicker.setHeight _oldHeight 56 | $colorPicker.removeClass 'view--definition' 57 | 58 | # TODO: This kinda goes against the 'no strings' thing 59 | colorPicker.Emitter.off 'close', onClose 60 | colorPicker.onClose onClose 61 | 62 | # Make sure the class is never set when a color is input 63 | colorPicker.onInputColor -> 64 | $colorPicker.removeClass 'view--definition' 65 | return 66 | 67 | # Set background element color on change 68 | # --------------------------- 69 | colorPicker.onInputVariableColor (smartColor) => 70 | return unless smartColor 71 | @element.setColor smartColor 72 | 73 | # Set or replace selection on click 74 | # --------------------------- 75 | colorPicker.onInputVariableColor (..., pointer) => 76 | # Keep track of the current pointer for when the color is 77 | # supposed to be replaced 78 | @pointer = pointer 79 | 80 | hasChild = (element, child) -> 81 | if child and _parent = child.parentNode 82 | if child is element 83 | return true 84 | else return hasChild element, _parent 85 | return false 86 | 87 | _isClicking = no 88 | 89 | colorPicker.onMouseDown (e, isOnPicker) => 90 | return unless isOnPicker and hasChild @element.el, e.target 91 | e.preventDefault() 92 | _isClicking = yes 93 | 94 | colorPicker.onMouseMove (e) -> 95 | _isClicking = no 96 | 97 | colorPicker.onMouseUp (e) => 98 | return unless _isClicking and @pointer 99 | 100 | atom.workspace.open(@pointer.filePath).then => 101 | Editor = atom.workspace.getActiveTextEditor() 102 | Editor.clearSelections() 103 | Editor.setSelectedBufferRange @pointer.range 104 | Editor.scrollToCursorPosition() 105 | 106 | colorPicker.close() 107 | return 108 | 109 | # Create Definition definition text element 110 | # --------------------------- 111 | setTimeout => 112 | # Create definition text element 113 | _definition = document.createElement 'p' 114 | _definition.classList.add "#{ @element.el.className }-definition" 115 | 116 | # Remove the definition when a new variable is input 117 | colorPicker.onInputVariable -> 118 | _definition.innerText = '' 119 | 120 | # Set definition when the definition is found 121 | colorPicker.onInputVariableColor (color) -> 122 | # If a color definition is found 123 | if color then _definition.innerText = color.value 124 | # If no definition is found, show an error 125 | else _definition.innerText = 'No color found.' 126 | 127 | # Add to Definition element 128 | @element.add _definition 129 | 130 | # Create Definition variable text element 131 | # --------------------------- 132 | setTimeout => 133 | # Create variable text element 134 | _variable = document.createElement 'p' 135 | _variable.classList.add "#{ @element.el.className }-variable" 136 | 137 | # Set variable when the variable is input 138 | colorPicker.onInputVariable (match) -> 139 | _variable.innerText = match.match 140 | 141 | # Add to Definition element 142 | @element.add _variable 143 | return this 144 | -------------------------------------------------------------------------------- /lib/extensions/Format.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Format 3 | # The element providing UI to convert between color formats 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | Emitter: (require '../modules/Emitter')() 8 | 9 | element: null 10 | color: null 11 | 12 | # ------------------------------------- 13 | # Set up events and handling 14 | # ------------------------------------- 15 | # Format Changed event 16 | emitFormatChanged: (format) -> 17 | @Emitter.emit 'formatChanged', format 18 | onFormatChanged: (callback) -> 19 | @Emitter.on 'formatChanged', callback 20 | 21 | # ------------------------------------- 22 | # Create and activate Format element 23 | # ------------------------------------- 24 | activate: -> 25 | @element = 26 | el: do -> 27 | _classPrefix = colorPicker.element.el.className 28 | _el = document.createElement 'div' 29 | _el.classList.add "#{ _classPrefix }-format" 30 | 31 | return _el 32 | 33 | # Add a child on the Color element 34 | add: (element) -> 35 | @el.appendChild element 36 | return this 37 | colorPicker.element.add @element.el 38 | 39 | # Add conversion buttons #ff0 40 | # --------------------------- 41 | setTimeout => 42 | Color = colorPicker.getExtension 'Color' 43 | 44 | _buttons = [] 45 | _activeButton = null 46 | 47 | # On color picker open, reset 48 | colorPicker.onBeforeOpen -> for _button in _buttons 49 | _button.deactivate() 50 | 51 | # On Color element output format, activate applicable button 52 | Color.onOutputFormat (format) -> for _button in _buttons 53 | # TODO this is inefficient. There should be a way to easily 54 | # check if `format` is in `_button.format`, including the 55 | # alpha channel 56 | if format is _button.format or format is "#{ _button.format }A" 57 | _button.activate() 58 | _activeButton = _button 59 | else _button.deactivate() 60 | 61 | # Create formatting buttons 62 | # TODO same as setting, globalize 63 | for _format in ['RGB', 'HEX', 'HSL', 'HSV', 'VEC'] then do (_format) => 64 | Format = this 65 | 66 | # Create the button 67 | _button = 68 | el: do -> 69 | _el = document.createElement 'button' 70 | _el.classList.add "#{ Format.element.el.className }-button" 71 | _el.innerHTML = _format 72 | return _el 73 | format: _format 74 | 75 | # Utility functions 76 | addClass: (className) -> @el.classList.add className; return this 77 | removeClass: (className) -> @el.classList.remove className; return this 78 | 79 | activate: -> @addClass 'is--active' 80 | deactivate: -> @removeClass 'is--active' 81 | _buttons.push _button 82 | 83 | # Set initial format 84 | unless _activeButton 85 | if _format is atom.config.get 'color-picker.preferredFormat' 86 | _activeButton = _button 87 | _button.activate() 88 | 89 | # Change color format on click 90 | hasChild = (element, child) -> 91 | if child and _parent = child.parentNode 92 | if child is element 93 | return true 94 | else return hasChild element, _parent 95 | return false 96 | _isClicking = no 97 | 98 | colorPicker.onMouseDown (e, isOnPicker) => 99 | return unless isOnPicker and hasChild _button.el, e.target 100 | e.preventDefault() 101 | _isClicking = yes 102 | 103 | colorPicker.onMouseMove (e) -> 104 | _isClicking = no 105 | 106 | colorPicker.onMouseUp (e) => 107 | return unless _isClicking 108 | 109 | _activeButton.deactivate() if _activeButton 110 | _button.activate() 111 | _activeButton = _button 112 | 113 | @emitFormatChanged _format 114 | 115 | # Add button to the parent Format element 116 | @element.add _button.el 117 | return this 118 | -------------------------------------------------------------------------------- /lib/extensions/Hue.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Hue 3 | # Color Hue controller 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | Emitter: (require '../modules/Emitter')() 8 | 9 | element: null 10 | control: null 11 | canvas: null 12 | 13 | # ------------------------------------- 14 | # Utility function to get the current hue 15 | # ------------------------------------- 16 | getHue: -> 17 | if (@control and @control.selection) and @element 18 | return @control.selection.y / @element.getHeight() * 360 19 | else return 0 20 | 21 | # ------------------------------------- 22 | # Set up events and handling 23 | # ------------------------------------- 24 | # Selection Changed event 25 | emitSelectionChanged: -> 26 | @Emitter.emit 'selectionChanged', @control.selection 27 | onSelectionChanged: (callback) -> 28 | @Emitter.on 'selectionChanged', callback 29 | 30 | # Color Changed event 31 | emitColorChanged: -> 32 | @Emitter.emit 'colorChanged', @control.selection.color 33 | onColorChanged: (callback) -> 34 | @Emitter.on 'colorChanged', callback 35 | 36 | # ------------------------------------- 37 | # Create and activate Hue controller 38 | # ------------------------------------- 39 | activate: -> 40 | Body = colorPicker.getExtension 'Body' 41 | 42 | # Create the element 43 | # --------------------------- 44 | @element = 45 | el: do -> 46 | _classPrefix = Body.element.el.className 47 | _el = document.createElement 'div' 48 | _el.classList.add "#{ _classPrefix }-hue" 49 | 50 | return _el 51 | # Utility functions 52 | width: 0 53 | height: 0 54 | getWidth: -> return @width or @el.offsetWidth 55 | getHeight: -> return @height or @el.offsetHeight 56 | 57 | rect: null 58 | getRect: -> return @rect or @updateRect() 59 | updateRect: -> @rect = @el.getClientRects()[0] 60 | 61 | # Add a child on the Hue element 62 | add: (element) -> 63 | @el.appendChild element 64 | return this 65 | Body.element.add @element.el, 2 66 | 67 | # Update element rect when Color Picker opens 68 | # --------------------------- 69 | colorPicker.onOpen => 70 | return unless @element.updateRect() and _rect = @element.getRect() 71 | @width = _rect.width 72 | @height = _rect.height 73 | 74 | # Create and draw canvas 75 | # --------------------------- 76 | setTimeout => # wait for the DOM 77 | Hue = this 78 | 79 | # Prepare some variables 80 | _elementWidth = @element.getWidth() 81 | _elementHeight = @element.getHeight() 82 | 83 | # Red through all the main colors and back to red 84 | _hexes = ['#f00', '#ff0', '#0f0', '#0ff', '#00f', '#f0f', '#f00'] 85 | 86 | # Create canvas element 87 | @canvas = 88 | el: do -> 89 | _el = document.createElement 'canvas' 90 | _el.width = _elementWidth 91 | _el.height = _elementHeight 92 | _el.classList.add "#{ Hue.element.el.className }-canvas" 93 | 94 | return _el 95 | # Utility functions 96 | context: null 97 | getContext: -> @context or (@context = @el.getContext '2d') 98 | 99 | getColorAtPosition: (y) -> return colorPicker.SmartColor.HSVArray [ 100 | y / Hue.element.getHeight() * 360 101 | 100 102 | 100] 103 | 104 | # Draw gradient 105 | _context = @canvas.getContext() 106 | 107 | _step = 1 / (_hexes.length - 1) 108 | _gradient = _context.createLinearGradient 0, 0, 1, _elementHeight 109 | _gradient.addColorStop (_step * _i), _hex for _hex, _i in _hexes 110 | 111 | _context.fillStyle = _gradient 112 | _context.fillRect 0, 0, _elementWidth, _elementHeight 113 | 114 | # Add to Hue element 115 | @element.add @canvas.el 116 | 117 | # Create Hue control element 118 | # --------------------------- 119 | setTimeout => # wait for the DOM 120 | hasChild = (element, child) -> 121 | if child and _parent = child.parentNode 122 | if child is element 123 | return true 124 | else return hasChild element, _parent 125 | return false 126 | 127 | # Create element 128 | Hue = this 129 | 130 | @control = 131 | el: do -> 132 | _el = document.createElement 'div' 133 | _el.classList.add "#{ Hue.element.el.className }-control" 134 | 135 | return _el 136 | isGrabbing: no 137 | 138 | # Set control selection 139 | selection: 140 | y: 0 141 | color: null 142 | setSelection: (e, y=null, offset=null) -> 143 | return unless Hue.canvas and _rect = Hue.element.getRect() 144 | 145 | _width = Hue.element.getWidth() 146 | _height = Hue.element.getHeight() 147 | 148 | if e then _y = e.pageY - _rect.top 149 | # Set the y directly 150 | else if (typeof y is 'number') 151 | _y = y 152 | # Handle scroll 153 | else if (typeof offset is 'number') 154 | _y = @selection.y + offset 155 | # Default to top 156 | else _y = @selection.y 157 | 158 | _y = @selection.y = Math.max 0, (Math.min _height, _y) 159 | @selection.color = Hue.canvas.getColorAtPosition _y 160 | 161 | _position = y: Math.max 3, (Math.min (_height - 6), _y) 162 | 163 | requestAnimationFrame => 164 | @el.style.top = "#{ _position.y }px" 165 | return Hue.emitSelectionChanged() 166 | 167 | refreshSelection: -> @setSelection() 168 | @control.refreshSelection() 169 | 170 | # If the Color Picker is fed a color, set it 171 | colorPicker.onInputColor (smartColor) => 172 | _hue = smartColor.toHSVArray()[0] 173 | @control.setSelection null, (@element.getHeight() / 360) * _hue 174 | 175 | # When the selection changes, the color has changed 176 | Hue.onSelectionChanged -> Hue.emitColorChanged() 177 | 178 | # Reset 179 | colorPicker.onOpen => @control.refreshSelection() 180 | colorPicker.onOpen => @control.isGrabbing = no 181 | colorPicker.onClose => @control.isGrabbing = no 182 | 183 | # Bind controller events 184 | colorPicker.onMouseDown (e, isOnPicker) => 185 | return unless isOnPicker and hasChild Hue.element.el, e.target 186 | e.preventDefault() 187 | @control.isGrabbing = yes 188 | @control.setSelection e 189 | 190 | colorPicker.onMouseMove (e) => 191 | return unless @control.isGrabbing 192 | @control.setSelection e 193 | 194 | colorPicker.onMouseUp (e) => 195 | return unless @control.isGrabbing 196 | @control.isGrabbing = no 197 | @control.setSelection e 198 | 199 | colorPicker.onMouseWheel (e, isOnPicker) => 200 | return unless isOnPicker and hasChild Hue.element.el, e.target 201 | e.preventDefault() 202 | @control.setSelection null, null, (e.wheelDeltaY * .33) # make it a bit softer 203 | 204 | # Add to Hue element 205 | @element.add @control.el 206 | return this 207 | -------------------------------------------------------------------------------- /lib/extensions/Return.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Return 3 | # The element showing the initial color value, enabling the user to return 4 | # to it at any time 5 | # ---------------------------------------------------------------------------- 6 | 7 | module.exports = (colorPicker) -> 8 | Emitter: (require '../modules/Emitter')() 9 | 10 | element: null 11 | color: null 12 | 13 | # ------------------------------------- 14 | # Set up events and handling 15 | # ------------------------------------- 16 | # Visibility event 17 | emitVisibility: (visible=true) -> 18 | @Emitter.emit 'visible', visible 19 | onVisibility: (callback) -> 20 | @Emitter.on 'visible', callback 21 | 22 | # ------------------------------------- 23 | # Create and activate Return element 24 | # ------------------------------------- 25 | activate: -> 26 | View = this 27 | 28 | # Build the element 29 | # --------------------------- 30 | @element = 31 | el: do -> 32 | _classPrefix = colorPicker.element.el.className 33 | _el = document.createElement 'div' 34 | _el.classList.add "#{ _classPrefix }-return" 35 | 36 | return _el 37 | # Utility functions 38 | addClass: (className) -> @el.classList.add className; return this 39 | removeClass: (className) -> @el.classList.remove className; return this 40 | hasClass: (className) -> @el.classList.contains className 41 | 42 | hide: -> @removeClass 'is--visible'; View.emitVisibility false 43 | show: -> @addClass 'is--visible'; View.emitVisibility true 44 | 45 | # Add a child on the Return element 46 | add: (element) -> 47 | @el.appendChild element 48 | return this 49 | 50 | # Set the Return element background color 51 | setColor: (smartColor) -> 52 | @el.style.backgroundColor = smartColor.toRGBA() 53 | colorPicker.element.add @element.el 54 | 55 | # Return color on click 56 | # --------------------------- 57 | hasChild = (element, child) -> 58 | if child and _parent = child.parentNode 59 | if child is element 60 | return true 61 | else return hasChild element, _parent 62 | return false 63 | 64 | _isClicking = no 65 | 66 | colorPicker.onMouseDown (e, isOnPicker) => 67 | return unless isOnPicker and hasChild @element.el, e.target 68 | e.preventDefault() 69 | _isClicking = yes 70 | 71 | colorPicker.onMouseMove (e) -> 72 | _isClicking = no 73 | 74 | colorPicker.onMouseUp (e) => 75 | return unless _isClicking and @color 76 | colorPicker.emitInputColor @color 77 | 78 | # Show the element when the input color isn't the current color 79 | # --------------------------- 80 | setTimeout => 81 | Alpha = colorPicker.getExtension 'Alpha' 82 | 83 | # Reset on colorPicker open 84 | colorPicker.onBeforeOpen => 85 | @color = null 86 | 87 | # Save the current color 88 | colorPicker.onInputColor (smartColor, wasFound) => 89 | @color = smartColor if wasFound 90 | 91 | # Do the check on Alpha change 92 | Alpha.onColorChanged (smartColor) => 93 | return @element.hide() unless @color 94 | 95 | if smartColor.equals @color 96 | @element.hide() 97 | else @element.show() 98 | return 99 | 100 | # Set background element color on input color 101 | # --------------------------- 102 | setTimeout => 103 | colorPicker.onInputColor (smartColor, wasFound) => 104 | @element.setColor smartColor if wasFound 105 | return 106 | 107 | # Create Return text element 108 | # --------------------------- 109 | setTimeout => 110 | # Create text element 111 | _text = document.createElement 'p' 112 | _text.classList.add "#{ @element.el.className }-text" 113 | 114 | # Set the text element to contain the Return data 115 | setColor = (smartColor) => 116 | _text.innerText = smartColor.value 117 | 118 | colorPicker.onInputColor (smartColor, wasFound) -> 119 | setColor smartColor if wasFound 120 | @element.add _text 121 | return this 122 | -------------------------------------------------------------------------------- /lib/extensions/Saturation.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Color Picker/extensions: Saturation 3 | # Color Saturation controller 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = (colorPicker) -> 7 | Emitter: (require '../modules/Emitter')() 8 | 9 | element: null 10 | control: null 11 | canvas: null 12 | 13 | # ------------------------------------- 14 | # Set up events and handling 15 | # ------------------------------------- 16 | # Selection Changed event 17 | emitSelectionChanged: -> 18 | @Emitter.emit 'selectionChanged', @control.selection 19 | onSelectionChanged: (callback) -> 20 | @Emitter.on 'selectionChanged', callback 21 | 22 | # Color Changed event 23 | emitColorChanged: -> 24 | @Emitter.emit 'colorChanged', @control.selection.color 25 | onColorChanged: (callback) -> 26 | @Emitter.on 'colorChanged', callback 27 | 28 | # ------------------------------------- 29 | # Create and activate Saturation controller 30 | # ------------------------------------- 31 | activate: -> 32 | Body = colorPicker.getExtension 'Body' 33 | 34 | # Create element 35 | # --------------------------- 36 | @element = 37 | el: do -> 38 | _classPrefix = Body.element.el.className 39 | _el = document.createElement 'div' 40 | _el.classList.add "#{ _classPrefix }-saturation" 41 | 42 | return _el 43 | # Utility functions 44 | width: 0 45 | height: 0 46 | getWidth: -> return @width or @el.offsetWidth 47 | getHeight: -> return @height or @el.offsetHeight 48 | 49 | rect: null 50 | getRect: -> return @rect or @updateRect() 51 | updateRect: -> @rect = @el.getClientRects()[0] 52 | 53 | # Add a child on the Saturation element 54 | add: (element) -> 55 | @el.appendChild element 56 | return this 57 | Body.element.add @element.el, 0 58 | 59 | # Update element rect when Color Picker opens 60 | # --------------------------- 61 | colorPicker.onOpen => 62 | return unless @element.updateRect() and _rect = @element.getRect() 63 | @width = _rect.width 64 | @height = _rect.height 65 | 66 | # Create and draw canvas 67 | # --------------------------- 68 | setTimeout => # wait for the DOM 69 | Saturation = this 70 | Hue = colorPicker.getExtension 'Hue' 71 | 72 | # Prepare some variables 73 | _elementWidth = @element.getWidth() 74 | _elementHeight = @element.getHeight() 75 | 76 | # Create element 77 | @canvas = 78 | el: do -> 79 | _el = document.createElement 'canvas' 80 | _el.width = _elementWidth 81 | _el.height = _elementHeight 82 | _el.classList.add "#{ Saturation.element.el.className }-canvas" 83 | 84 | return _el 85 | # Utility functions 86 | context: null 87 | getContext: -> @context or (@context = @el.getContext '2d') 88 | 89 | getColorAtPosition: (x, y) -> return colorPicker.SmartColor.HSVArray [ 90 | Hue.getHue() 91 | x / Saturation.element.getWidth() * 100 92 | 100 - (y / Saturation.element.getHeight() * 100)] 93 | 94 | # Render Saturation canvas 95 | previousRender: null 96 | render: (smartColor) -> 97 | _hslArray = ( do -> 98 | unless smartColor 99 | return colorPicker.SmartColor.HEX '#f00' 100 | else return smartColor 101 | ).toHSLArray() 102 | 103 | _joined = _hslArray.join ',' 104 | return if @previousRender and @previousRender is _joined 105 | 106 | # Get context and clear it 107 | _context = @getContext() 108 | _context.clearRect 0, 0, _elementWidth, _elementHeight 109 | 110 | # Draw hue channel on top 111 | _gradient = _context.createLinearGradient 0, 0, _elementWidth, 1 112 | _gradient.addColorStop .01, 'hsl(0,100%,100%)' 113 | _gradient.addColorStop .99, "hsl(#{ _hslArray[0] },100%,50%)" 114 | 115 | _context.fillStyle = _gradient 116 | _context.fillRect 0, 0, _elementWidth, _elementHeight 117 | 118 | # Draw saturation channel on the bottom 119 | _gradient = _context.createLinearGradient 0, 0, 1, _elementHeight 120 | _gradient.addColorStop .01, 'rgba(0,0,0,0)' 121 | _gradient.addColorStop .99, 'rgba(0,0,0,1)' 122 | 123 | _context.fillStyle = _gradient 124 | _context.fillRect 0, 0, _elementWidth, _elementHeight 125 | return @previousRender = _joined 126 | 127 | # Render again on Hue selection change 128 | Hue.onColorChanged (smartColor) => 129 | @canvas.render smartColor 130 | @canvas.render() 131 | 132 | # Add to Saturation element 133 | @element.add @canvas.el 134 | 135 | # Create Saturation control element 136 | # --------------------------- 137 | setTimeout => # wait for the DOM 138 | hasChild = (element, child) -> 139 | if child and _parent = child.parentNode 140 | if child is element 141 | return true 142 | else return hasChild element, _parent 143 | return false 144 | 145 | # Create element 146 | Saturation = this 147 | Hue = colorPicker.getExtension 'Hue' 148 | 149 | @control = 150 | el: do -> 151 | _el = document.createElement 'div' 152 | _el.classList.add "#{ Saturation.element.el.className }-control" 153 | 154 | return _el 155 | isGrabbing: no 156 | 157 | previousControlPosition: null 158 | updateControlPosition: (x, y) -> 159 | _joined = "#{ x },#{ y }" 160 | return if @previousControlPosition and @previousControlPosition is _joined 161 | 162 | requestAnimationFrame => 163 | @el.style.left = "#{ x }px" 164 | @el.style.top = "#{ y }px" 165 | return @previousControlPosition = _joined 166 | 167 | selection: 168 | x: null 169 | y: 0 170 | color: null 171 | setSelection: (e, saturation=null, key=null) -> 172 | return unless Saturation.canvas and _rect = Saturation.element.getRect() 173 | 174 | _width = Saturation.element.getWidth() 175 | _height = Saturation.element.getHeight() 176 | 177 | if e 178 | _x = e.pageX - _rect.left 179 | _y = e.pageY - _rect.top 180 | # Set saturation and key directly 181 | else if (typeof saturation is 'number') and (typeof key is 'number') 182 | _x = _width * saturation 183 | _y = _height * key 184 | # Default to previous values 185 | else 186 | if (typeof @selection.x isnt 'number') 187 | @selection.x = _width 188 | _x = @selection.x 189 | _y = @selection.y 190 | 191 | _x = @selection.x = Math.max 0, (Math.min _width, Math.round _x) 192 | _y = @selection.y = Math.max 0, (Math.min _height, Math.round _y) 193 | 194 | _position = 195 | x: Math.max 6, (Math.min (_width - 7), _x) 196 | y: Math.max 6, (Math.min (_height - 7), _y) 197 | 198 | @selection.color = Saturation.canvas.getColorAtPosition _x, _y 199 | @updateControlPosition _position.x, _position.y 200 | return Saturation.emitSelectionChanged() 201 | 202 | refreshSelection: -> @setSelection() 203 | @control.refreshSelection() 204 | 205 | # If the Color Picker is fed a color, set it 206 | colorPicker.onInputColor (smartColor) => 207 | [h, s, v] = smartColor.toHSVArray() 208 | @control.setSelection null, s, (1 - v) 209 | 210 | # When the selection changes, the color has changed 211 | Saturation.onSelectionChanged -> Saturation.emitColorChanged() 212 | 213 | # Reset 214 | colorPicker.onOpen => @control.refreshSelection() 215 | colorPicker.onOpen => @control.isGrabbing = no 216 | colorPicker.onClose => @control.isGrabbing = no 217 | 218 | # Bind controller events 219 | Hue.onColorChanged => @control.refreshSelection() 220 | 221 | colorPicker.onMouseDown (e, isOnPicker) => 222 | return unless isOnPicker and hasChild Saturation.element.el, e.target 223 | e.preventDefault() 224 | @control.isGrabbing = yes 225 | @control.setSelection e 226 | 227 | colorPicker.onMouseMove (e) => 228 | return unless @control.isGrabbing 229 | @control.setSelection e 230 | 231 | colorPicker.onMouseUp (e) => 232 | return unless @control.isGrabbing 233 | @control.isGrabbing = no 234 | @control.setSelection e 235 | 236 | # Add to Saturation element 237 | @element.add @control.el 238 | return this 239 | -------------------------------------------------------------------------------- /lib/modules/Convert.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Convert 3 | # ---------------------------------------------------------------------------- 4 | 5 | module.exports = -> 6 | # TODO: I don't like this file. It's ugly and feels weird 7 | 8 | # ------------------------------------- 9 | # HEX to RGB 10 | # ------------------------------------- 11 | hexToRgb: (hex) -> 12 | hex = hex.replace '#', '' 13 | hex = hex.replace /(.)(.)(.)/, "$1$1$2$2$3$3" if hex.length is 3 14 | 15 | return [ 16 | parseInt (hex.substr 0, 2), 16 17 | parseInt (hex.substr 2, 2), 16 18 | parseInt (hex.substr 4, 2), 16] 19 | 20 | # ------------------------------------- 21 | # HEXA to RGB 22 | # ------------------------------------- 23 | hexaToRgb: (hexa) -> 24 | return @hexToRgb (hexa.match /rgba\((\#.+),/)[1] 25 | 26 | # ------------------------------------- 27 | # HEX to HSL 28 | # ------------------------------------- 29 | hexToHsl: (hex) -> 30 | return @rgbToHsl @hexToRgb hex.replace '#', '' 31 | 32 | # ------------------------------------- 33 | # RGB to HEX 34 | # ------------------------------------- 35 | rgbToHex: (rgb) -> 36 | _componentToHex = (component) -> 37 | _hex = component.toString 16 38 | return if _hex.length is 1 then "0#{ _hex }" else _hex 39 | 40 | return [ 41 | (_componentToHex rgb[0]) 42 | (_componentToHex rgb[1]) 43 | (_componentToHex rgb[2]) 44 | ].join '' 45 | 46 | # ------------------------------------- 47 | # RGB to HSL 48 | # ------------------------------------- 49 | rgbToHsl: ([r, g, b]) -> 50 | r /= 255 51 | g /= 255 52 | b /= 255 53 | 54 | _max = Math.max r, g, b 55 | _min = Math.min r, g, b 56 | 57 | _l = (_max + _min) / 2 58 | 59 | if _max is _min then return [0, 0, Math.floor _l * 100] 60 | 61 | _d = _max - _min 62 | _s = if _l > 0.5 then _d / (2 - _max - _min) else _d / (_max + _min) 63 | 64 | switch _max 65 | when r then _h = (g - b) / _d + (if g < b then 6 else 0) 66 | when g then _h = (b - r) / _d + 2 67 | when b then _h = (r - g) / _d + 4 68 | 69 | _h /= 6 70 | 71 | return [ 72 | Math.floor _h * 360 73 | Math.floor _s * 100 74 | Math.floor _l * 100] 75 | 76 | # ------------------------------------- 77 | # RGB to HSV 78 | # ------------------------------------- 79 | rgbToHsv: ([r, g, b]) -> 80 | computedH = 0 81 | computedS = 0 82 | computedV = 0 83 | 84 | if not r? or not g? or not b? or isNaN(r) or isNaN(g) or isNaN(b) 85 | return 86 | if r < 0 or g < 0 or b < 0 or r > 255 or g > 255 or b > 255 87 | return 88 | 89 | r = r / 255 90 | g = g / 255 91 | b = b / 255 92 | 93 | minRGB = Math.min(r, Math.min(g, b)) 94 | maxRGB = Math.max(r, Math.max(g, b)) 95 | 96 | # Black-gray-white 97 | if minRGB is maxRGB 98 | computedV = minRGB 99 | 100 | return [ 101 | 0 102 | 0 103 | computedV] 104 | 105 | # Colors other than black-gray-white: 106 | d = (if (r is minRGB) then g - b else ((if (b is minRGB) then r - g else b - r))) 107 | h = (if (r is minRGB) then 3 else ((if (b is minRGB) then 1 else 5))) 108 | 109 | computedH = 60 * (h - d / (maxRGB - minRGB)) 110 | computedS = (maxRGB - minRGB) / maxRGB 111 | computedV = maxRGB 112 | 113 | return [ 114 | computedH 115 | computedS 116 | computedV] 117 | 118 | # ------------------------------------- 119 | # HSV to HSL 120 | # ------------------------------------- 121 | hsvToHsl: ([h, s, v]) -> [ 122 | h 123 | s * v / (if (h = (2 - s) * v) < 1 then h else 2 - h) 124 | h / 2] 125 | 126 | # ------------------------------------- 127 | # HSV to RGB 128 | # ------------------------------------- 129 | hsvToRgb: ([h, s, v]) -> 130 | h /= 60 # 0 to 5 131 | s /= 100 132 | v /= 100 133 | 134 | # Achromatic grayscale 135 | if s is 0 then return [ 136 | Math.round v * 255 137 | Math.round v * 255 138 | Math.round v * 255] 139 | 140 | _i = Math.floor h 141 | _f = h - _i 142 | _p = v * (1 - s) 143 | _q = v * (1 - s * _f) 144 | _t = v * (1 - s * (1 - _f)) 145 | 146 | _result = switch _i 147 | when 0 then [v, _t, _p] 148 | when 1 then [_q, v, _p] 149 | when 2 then [_p, v, _t] 150 | when 3 then [_p, _q, v] 151 | when 4 then [_t, _p, v] 152 | when 5 then [v, _p, _q] 153 | else [v, _t, _p] 154 | 155 | return [ 156 | Math.round _result[0] * 255 157 | Math.round _result[1] * 255 158 | Math.round _result[2] * 255] 159 | 160 | # ------------------------------------- 161 | # HSL to HSV 162 | # ------------------------------------- 163 | hslToHsv: ([h, s, l]) -> 164 | s /= 100 165 | l /= 100 166 | 167 | s *= if l < .5 then l else 1 - l 168 | 169 | return [ 170 | h 171 | (2 * s / (l + s)) or 0 172 | l + s] 173 | 174 | # ------------------------------------- 175 | # HSL to RGB 176 | # ------------------------------------- 177 | hslToRgb: (input) -> 178 | [h, s, v] = @hslToHsv input 179 | return @hsvToRgb [h, (s * 100), (v * 100)] 180 | 181 | # ------------------------------------- 182 | # VEC to RGB 183 | # ------------------------------------- 184 | vecToRgb: (input) -> return [ 185 | (input[0] * 255) << 0 186 | (input[1] * 255) << 0 187 | (input[2] * 255) << 0] 188 | 189 | # ------------------------------------- 190 | # RGB to VEC 191 | # ------------------------------------- 192 | rgbToVec: (input) -> return [ 193 | (input[0] / 255).toFixed 2 194 | (input[1] / 255).toFixed 2 195 | (input[2] / 255).toFixed 2] 196 | -------------------------------------------------------------------------------- /lib/modules/Emitter.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # Emitter 3 | # a really lightweight take on an Emitter 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = -> 7 | bindings: {} 8 | 9 | emit: (event, args...) -> 10 | return unless _bindings = @bindings[event] 11 | _callback.apply null, args for _callback in _bindings 12 | return 13 | 14 | on: (event, callback) -> 15 | @bindings[event] = [] unless @bindings[event] 16 | @bindings[event].push callback 17 | return callback 18 | 19 | off: (event, callback) -> 20 | return unless _bindings = @bindings[event] 21 | 22 | _i = _bindings.length; while _i-- and _binding = _bindings[_i] 23 | if _binding is callback then _bindings.splice _i, 1 24 | return 25 | -------------------------------------------------------------------------------- /lib/modules/SmartColor.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # SmartColor 3 | # Easily find colors, and convert between color formats 4 | # ---------------------------------------------------------------------------- 5 | 6 | module.exports = -> 7 | Convert = (require './Convert')() 8 | 9 | # ------------------------------------- 10 | # Color Regexes 11 | # ------------------------------------- 12 | COLOR_REGEXES = 13 | # Matches HSL: eg 14 | # hsl(320, 100%, 100%) and hsl(26, 57, 32) and hsl( 36 , 67 , 16 ) 15 | HSL: /hsl\s*?\(\s*([0-9]|[1-9][0-9]|[1|2][0-9][0-9]|3[0-5][0-9]|360)\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*\)/i 16 | 17 | # Matches HSL + A: eg 18 | # hsla(320, 100%, 38%, 0.3) and hsla(26, 57, 32, .3) and hsla( 36 , 67 , 16 , 1.0 ) and hsla(0, 0%, 0%, 0.42) 19 | HSLA: /hsla\s*?\(\s*([0-9]|[1-9][0-9]|[1|2][0-9][0-9]|3[0-5][0-9]|360)\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*?,\s*?(0|1|1.0|0*\.\d+)\s*?\)/i 20 | 21 | # Matches HSV: eg 22 | # hsv(320, 100%, 100%) and hsv(26, 57, 32) and hsv( 36 , 67 , 16 ) 23 | HSV: /hsv\s*?\(\s*([0-9]|[1-9][0-9]|[1|2][0-9][0-9]|3[0-5][0-9]|360)\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*\)/i 24 | 25 | # Matches HSV + A: eg 26 | # hsva(320, 100%, 38%, 0.3) and hsva(26, 57, 32, .3) and hsva( 36 , 67 , 16 , 0.3 ) and hsva(0, 0%, 0%, 1.0) 27 | HSVA: /hsva\s*?\(\s*([0-9]|[1-9][0-9]|[1|2][0-9][0-9]|3[0-5][0-9]|360)\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*?,\s*?([0-9]|[1-9][0-9]|100)\%?\s*?,\s*?(0|1|1.0|0*\.\d+)\s*?\)/i 28 | 29 | # Matches VEC: eg 30 | # vec3(0.44f, 0.3, 0) and vec3(1.0, 0.42, .4) and vec3( 1f , 0.4 , 1.0 ) 31 | VEC: /vec3\s*?\(\s*?([0]?\.[0-9]*|1\.0|1|0)[f]?\s*?\,\s*?([0]?\.[0-9]*|1\.0|1|0)[f]?\s*?\,\s*?([0]?\.[0-9]*|1\.0|1|0)[f]?\s*?\)/i 32 | 33 | # Matches VECA: eg 34 | # vec4(0.4, 0.33, 0f, 0.5) and vec4(1.0, 0.4121231f, .4, 1.0f) and vec4( 1f , 0.4 , 1.0, 0 ) 35 | VECA: /vec4\s*?\(\s*?([0]?\.[0-9]*|1\.0|1|0)[f]?\s*?\,\s*?([0]?\.[0-9]*|1\.0|1|0)[f]?\s*?\,\s*?([0]?\.[0-9]*|1\.0|1|0)[f]?\s*?\,\s*?([0]?\.[0-9]*|1\.0|1|0)[f]?\s*?\)/i 36 | 37 | # Matches RGB: eg. 38 | # rgb(0, 99, 199) and rgb ( 255 , 180 , 255 ) 39 | RGB: /rgb\s*?\(\s*?([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*?,\s*?([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*?,\s*?([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*?\)/i 40 | 41 | # Matches RGB + A: eg. 42 | # rgba(0, 99, 199, 0.3) and rgba ( 82 , 121, 0, .68 ) 43 | RGBA: /rgba\s*?\(\s*?([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][<0-9]|25[0-5])\s*?,\s*?([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*?,\s*?([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\s*?,\s*?(0|1|1.0|0*\.\d+)\s*?\)/i 44 | 45 | # Matches HEX: 46 | # eg. #000 and #ffffff 47 | HEX: /(\#[a-f0-9]{6}|\#[a-f0-9]{3})/i 48 | 49 | # Matches HEX + A: eg 50 | # rgba(#fff, 0.3) and rgba(#000000, .8) and rgba ( #000 , .8) 51 | HEXA: /rgba\s*?\(\s*(\#[a-f0-9]{6}|\#[a-f0-9]{3})\s*?,\s*?(0|1|1.0|0*\.\d+)\s*?\)/i 52 | MATCH_ORDER = ['HSL', 'HSLA', 'HSV', 'HSVA', 'VEC', 'VECA', 'RGB', 'RGBA', 'HEXA', 'HEX'] 53 | 54 | # ------------------------------------- 55 | # Abbreviation functions 56 | # ------------------------------------- 57 | n = (number) -> 58 | number = "#{ number }" 59 | 60 | # Abbreviate if `abbreviateValues` option is true 61 | if atom.config.get 'color-picker.abbreviateValues' 62 | if number[0] is '0' and number[1] is '.' 63 | return number.substring 1 # TODO or `substr`? 64 | else if (parseFloat number, 10) is 1 65 | return '1' 66 | return number 67 | f = (number) -> 68 | number = "#{ number }" 69 | 70 | if number[3] and number[3] is '0' 71 | return number.substring 0, 3 # TODO or `substr`? 72 | return number 73 | 74 | s = (string) -> 75 | if atom.config.get 'color-picker.abbreviateValues' 76 | return string.replace /\s/g, '' 77 | return string 78 | 79 | # ------------------------------------- 80 | # Public functionality 81 | # ------------------------------------- 82 | return { 83 | # ------------------------------------- 84 | # Find colors in string 85 | # - string {String} 86 | # 87 | # @return String 88 | # ------------------------------------- 89 | find: (string) -> 90 | SmartColor = this 91 | _colors = [] 92 | 93 | for _format in MATCH_ORDER when _regExp = COLOR_REGEXES[_format] 94 | _matches = string.match (new RegExp _regExp.source, 'ig') 95 | continue unless _matches 96 | 97 | for _match in _matches then do (_format, _match) -> 98 | return if (_index = string.indexOf _match) is -1 99 | 100 | _colors.push 101 | match: _match 102 | format: _format 103 | start: _index 104 | end: _index + _match.length 105 | 106 | getSmartColor: -> SmartColor[_format](_match) 107 | isColor: true 108 | 109 | # Remove the match from the line content string to 110 | # “mark it” as having been “spent”. Be careful to keep the 111 | # correct amount of characters in the string as this is 112 | # later used to see which match fits best, if any 113 | string = string.replace _match, (new Array _match.length + 1).join ' ' 114 | return _colors 115 | 116 | # ------------------------------------- 117 | # Base color object, all colors are versions of this object 118 | # - format {String}: the color format 119 | # - value {String|Array}: The color value 120 | # - RGBAArray {Array}: The color value in RGBAArray format 121 | # ------------------------------------- 122 | color: (format, value, RGBAArray) -> 123 | format: format 124 | value: value 125 | RGBAArray: RGBAArray 126 | 127 | # Compare two smart colors 128 | equals: (smartColor) -> 129 | return false unless smartColor 130 | 131 | return smartColor.RGBAArray[0] is @RGBAArray[0] and smartColor.RGBAArray[1] is @RGBAArray[1] and 132 | smartColor.RGBAArray[2] is @RGBAArray[2] and 133 | smartColor.RGBAArray[3] is @RGBAArray[3] 134 | 135 | getAlpha: -> return @RGBAArray[3] 136 | 137 | # RGB 138 | # --------------------------- 139 | toRGB: -> return s "rgb(#{ @toRGBArray().join ', ' })" 140 | toRGBArray: -> [@RGBAArray[0], @RGBAArray[1], @RGBAArray[2]] 141 | 142 | # RGBA 143 | toRGBA: -> 144 | _rgbaArray = @toRGBAArray() 145 | return s "rgba(#{ _rgbaArray[0] }, #{ _rgbaArray[1] }, #{ _rgbaArray[2] }, #{ n _rgbaArray[3] })" 146 | toRGBAArray: -> @RGBAArray 147 | 148 | # HSL 149 | # --------------------------- 150 | toHSL: -> 151 | _hslArray = @toHSLArray() 152 | return s "hsl(#{ _hslArray[0] }, #{ _hslArray[1] }%, #{ _hslArray[2] }%)" 153 | toHSLArray: -> Convert.rgbToHsl @toRGBArray() 154 | 155 | # HSLA 156 | toHSLA: -> 157 | _hslaArray = @toHSLAArray() 158 | return s "hsla(#{ _hslaArray[0] }, #{ _hslaArray[1] }%, #{ _hslaArray[2] }%, #{ n _hslaArray[3] })" 159 | toHSLAArray: -> @toHSLArray().concat [@getAlpha()] 160 | 161 | # HSV 162 | # --------------------------- 163 | toHSV: -> 164 | _hsvArray = @toHSVArray() 165 | return s "hsv(#{ Math.round _hsvArray[0] }, #{ (_hsvArray[1] * 100) << 0 }%, #{ (_hsvArray[2] * 100) << 0 }%)" 166 | toHSVArray: -> Convert.rgbToHsv @toRGBArray() 167 | 168 | # HSVA 169 | toHSVA: -> 170 | _hsvaArray = @toHSVAArray() 171 | return s "hsva(#{ Math.round _hsvaArray[0] }, #{ (_hsvaArray[1] * 100) << 0 }%, #{ (_hsvaArray[2] * 100) << 0 }%, #{ n _hsvaArray[3] })" 172 | toHSVAArray: -> @toHSVArray().concat [@getAlpha()] 173 | 174 | # VEC 175 | # --------------------------- 176 | toVEC: -> 177 | _vecArray = @toVECArray() 178 | return s "vec3(#{ f _vecArray[0] }, #{ f _vecArray[1] }, #{ f _vecArray[2] })" 179 | toVECArray: -> Convert.rgbToVec @toRGBArray() 180 | 181 | # VECA 182 | toVECA: -> 183 | _vecaArray = @toVECAArray() 184 | return s "vec4(#{ f _vecaArray[0] }, #{ f _vecaArray[1] }, #{ f _vecaArray[2] }, #{ f _vecaArray[3] })" 185 | toVECAArray: -> @toVECArray().concat [(@getAlpha()).toFixed 2] 186 | 187 | # HEX 188 | # --------------------------- 189 | toHEX: -> 190 | _hex = Convert.rgbToHex @RGBAArray 191 | 192 | # Abbreviate if `abbreviateValues` option is true 193 | if atom.config.get 'color-picker.abbreviateValues' 194 | if _hex[0] is _hex[1] and _hex[2] is _hex[3] and _hex[4] is _hex[5] 195 | _hex = "#{ _hex[0] }#{ _hex[2] }#{ _hex[4] }" 196 | 197 | # Uppercase color values if `uppercaseColorValues` option is true 198 | if atom.config.get 'color-picker.uppercaseColorValues' 199 | _hex = _hex.toUpperCase() 200 | 201 | return '#' + _hex 202 | 203 | # HEXA 204 | toHEXA: -> s "rgba(#{ @toHEX() }, #{ n @getAlpha() })" 205 | 206 | # ------------------------------------- 207 | # Color input formats... 208 | # ------------------------------------- 209 | # RGB 210 | RGB: (value) -> @color 'RGB', value, do -> 211 | _match = value.match COLOR_REGEXES.RGB 212 | 213 | return ([ 214 | parseInt _match[1], 10 215 | parseInt _match[2], 10 216 | parseInt _match[3], 10 217 | ]).concat [1] # add default alpha 218 | RGBArray: (value) -> @color 'RGBArray', value, do -> 219 | return value.concat [1] 220 | 221 | # RGBA 222 | RGBA: (value) -> @color 'RGBA', value, do -> 223 | _match = value.match COLOR_REGEXES.RGBA 224 | 225 | return ([ 226 | parseInt _match[1], 10 227 | parseInt _match[2], 10 228 | parseInt _match[3], 10 229 | ]).concat [parseFloat _match[4], 10] 230 | RGBAArray: (value) -> @color 'RGBAArray', value, value 231 | 232 | # HSL 233 | HSL: (value) -> @color 'HSL', value, do -> 234 | _match = value.match COLOR_REGEXES.HSL 235 | 236 | return (Convert.hslToRgb [ 237 | parseInt _match[1], 10 238 | parseInt _match[2], 10 239 | parseInt _match[3], 10 240 | ]).concat [1] # add default alpha 241 | HSLArray: (value) -> @color 'HSLArray', value, do -> 242 | return (Convert.hslToRgb value).concat [1] 243 | 244 | # HSLA 245 | HSLA: (value) -> @color 'HSLA', value, do -> 246 | _match = value.match COLOR_REGEXES.HSLA 247 | 248 | return (Convert.hslToRgb [ 249 | parseInt _match[1], 10 250 | parseInt _match[2], 10 251 | parseInt _match[3], 10 252 | ]).concat [parseFloat _match[4], 10] 253 | HSLAArray: (value) -> @color 'HSLAArray', value, do -> 254 | return (Convert.hslToRgb value).concat [value[3]] 255 | 256 | # HSV 257 | HSV: (value) -> @color 'HSV', value, do -> 258 | _match = value.match COLOR_REGEXES.HSV 259 | 260 | return (Convert.hsvToRgb [ 261 | parseInt _match[1], 10 262 | parseInt _match[2], 10 263 | parseInt _match[3], 10 264 | ]).concat [1] 265 | HSVArray: (value) -> @color 'HSVArray', value, do -> 266 | return (Convert.hsvToRgb value).concat [1] 267 | 268 | # HSVA 269 | HSVA: (value) -> @color 'HSVA', value, do -> 270 | _match = value.match COLOR_REGEXES.HSVA 271 | 272 | return (Convert.hsvToRgb [ 273 | parseInt _match[1], 10 274 | parseInt _match[2], 10 275 | parseInt _match[3], 10 276 | ]).concat [parseFloat _match[4], 10] 277 | HSVAArray: (value) -> @color 'HSVAArray', value, do -> 278 | return (Convert.hsvToRgb value).concat [value[3]] 279 | 280 | # VEC 281 | VEC: (value) -> @color 'VEC', value, do -> 282 | _match = value.match COLOR_REGEXES.VEC 283 | 284 | return (Convert.vecToRgb [ 285 | (parseFloat _match[1], 10).toFixed 2 286 | (parseFloat _match[2], 10).toFixed 2 287 | (parseFloat _match[3], 10).toFixed 2 288 | ]).concat [1] 289 | VECArray: (value) -> @color 'VECArray', value, do -> 290 | return (Convert.vecToRgb value).concat [1] 291 | 292 | # VECA 293 | VECA: (value) -> @color 'VECA', value, do -> 294 | _match = value.match COLOR_REGEXES.VECA 295 | 296 | return (Convert.vecToRgb [ 297 | (parseFloat _match[1], 10).toFixed 2 298 | (parseFloat _match[2], 10).toFixed 2 299 | (parseFloat _match[3], 10).toFixed 2 300 | ]).concat [parseFloat _match[4], 10] 301 | VECAArray: (value) -> @color 'VECAArray', value, do -> 302 | return (Convert.vecToRgb value).concat [value[3]] 303 | 304 | # HEX 305 | HEX: (value) -> @color 'HEX', value, do -> 306 | return (Convert.hexToRgb value).concat [1] 307 | 308 | # HEXA 309 | HEXA: (value) -> @color 'HEXA', value, do -> 310 | _match = value.match COLOR_REGEXES.HEXA 311 | return (Convert.hexToRgb _match[1]).concat [parseFloat _match[2], 10] 312 | } 313 | -------------------------------------------------------------------------------- /lib/modules/SmartVariable.coffee: -------------------------------------------------------------------------------- 1 | # ---------------------------------------------------------------------------- 2 | # SmartVariable 3 | # ---------------------------------------------------------------------------- 4 | 5 | module.exports = -> 6 | path = require 'path' 7 | 8 | # ------------------------------------- 9 | # Variable Types 10 | # ------------------------------------- 11 | VARIABLE_PATTERN = '\\{{ VARIABLE }}[\\s]*[:=][\\s]*([^\\;\\n]+)[\\;|\\n]' 12 | 13 | VARIABLE_TYPES = [ 14 | # Matches Sass variable: eg. 15 | # $color-var 16 | { 17 | type: 'sass' 18 | extensions: ['.scss', '.sass'] 19 | regExp: /([\$])([\w0-9-_]+)/i 20 | } 21 | 22 | # Matches LESS variable: eg. 23 | # @color-var 24 | { 25 | type: 'less' 26 | extensions: ['.less'] 27 | regExp: /([\@])([\w0-9-_]+)/i 28 | } 29 | 30 | # Matches Stylus variable: eg. 31 | # $color-var 32 | { 33 | type: 'stylus' 34 | extensions: ['.stylus', '.styl'] 35 | regExp: /([\$])([\w0-9-_]+)/i 36 | } 37 | ] 38 | 39 | # ------------------------------------- 40 | # Definition storage 41 | # ------------------------------------- 42 | DEFINITIONS = {} 43 | 44 | # ------------------------------------- 45 | # Public functionality 46 | # ------------------------------------- 47 | return { 48 | # ------------------------------------- 49 | # Find variables in string 50 | # - string {String} 51 | # 52 | # @return String 53 | # ------------------------------------- 54 | find: (string, pathName) -> 55 | SmartVariable = this 56 | _variables = [] 57 | 58 | for {type, extensions, regExp} in VARIABLE_TYPES 59 | _matches = string.match (new RegExp regExp.source, 'ig') 60 | continue unless _matches 61 | 62 | # Make sure the file type matches possible extensions 63 | if pathName 64 | continue unless (path.extname pathName) in extensions 65 | 66 | for _match in _matches then do (type, extensions, _match) -> 67 | return if (_index = string.indexOf _match) is -1 68 | 69 | _variables.push 70 | match: _match 71 | type: type 72 | extensions: extensions 73 | start: _index 74 | end: _index + _match.length 75 | 76 | getDefinition: -> SmartVariable.getDefinition this 77 | isVariable: true 78 | 79 | # Remove the match from the line content string to 80 | # “mark it” as having been “spent”. Be careful to keep the 81 | # correct amount of characters in the string as this is 82 | # later used to see which match fits best, if any 83 | string = string.replace _match, (new Array _match.length + 1).join ' ' 84 | return _variables 85 | 86 | # ------------------------------------- 87 | # Find a variable definition in the project 88 | # - name {String} 89 | # - type {String} 90 | # 91 | # @return Promise 92 | # ------------------------------------- 93 | getDefinition: (variable, initial) -> 94 | {match, type, extensions} = variable 95 | 96 | # Figure out what to look for 97 | _regExp = new RegExp (VARIABLE_PATTERN.replace '{{ VARIABLE }}', match) 98 | 99 | # We already know where the definition is 100 | if _definition = DEFINITIONS[match] 101 | # Save initial pointer value, if it isn't set already 102 | initial ?= _definition 103 | _pointer = _definition.pointer 104 | 105 | # ... but check if it's still there 106 | return atom.project.bufferForPath _pointer.filePath 107 | .then (buffer) => 108 | _text = buffer.getTextInRange _pointer.range 109 | _match = _text.match _regExp 110 | 111 | # Definition not found, reset and try again 112 | unless _match 113 | DEFINITIONS[match] = null 114 | return @getDefinition variable, initial 115 | 116 | # Definition found, save it on the DEFINITION object 117 | _definition.value = _match[1] 118 | 119 | # ... but it might be another variable, in which 120 | # case we must keep digging to find what we're after 121 | _found = (@find _match[1], _pointer.filePath)[0] 122 | 123 | # Run the search again, but keep the initial pointer 124 | if _found and _found.isVariable 125 | return @getDefinition _found, initial 126 | 127 | return { 128 | value: _definition.value 129 | variable: _definition.variable 130 | type: _definition.type 131 | 132 | pointer: initial.pointer 133 | } 134 | .catch (error) => console.error error 135 | 136 | # ... we don't know where the definition is 137 | 138 | # Figure out where to look 139 | _options = paths: do -> 140 | "**/*#{ _extension }" for _extension in extensions 141 | _results = [] 142 | 143 | return atom.workspace.scan _regExp, _options, (result) -> 144 | _results.push result 145 | .then => 146 | # Figure out what file is holding the definition 147 | # Assume it's the one closest to the current path 148 | _targetPath = atom.workspace.getActivePaneItem().getPath() 149 | _targetFragments = _targetPath.split path.sep 150 | 151 | _bestMatch = null 152 | _bestMatchHits = 0 153 | 154 | for result in _results 155 | _thisMatchHits = 0 156 | _pathFragments = result.filePath.split path.sep 157 | _thisMatchHits++ for pathFragment, i in _pathFragments when pathFragment is _targetFragments[i] 158 | 159 | if _thisMatchHits > _bestMatchHits 160 | _bestMatch = result 161 | _bestMatchHits = _thisMatchHits 162 | return unless _bestMatch and _match = _bestMatch.matches[0] 163 | 164 | # Save the definition on the DEFINITION object so that it 165 | # can be accessed later 166 | DEFINITIONS[match] = { 167 | value: null 168 | variable: match 169 | type: type 170 | 171 | pointer: 172 | filePath: _bestMatch.filePath 173 | range: _match.range 174 | } 175 | 176 | # Save initial pointer value, if it isn't set already 177 | initial ?= DEFINITIONS[match] 178 | return @getDefinition variable, initial 179 | .catch (error) => console.error error 180 | } 181 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "color-picker", 3 | "main": "./lib/ColorPicker", 4 | "version": "2.3.0", 5 | "private": true, 6 | "description": "Right click or press CMD-SHIFT-C/CTRL-ALT-C to open it.", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/thomaslindstrom/color-picker" 10 | }, 11 | "keywords": [ 12 | "colors", 13 | "design", 14 | "ui" 15 | ], 16 | "license": "MIT", 17 | "engines": { 18 | "atom": ">=1.2.4" 19 | }, 20 | "readmeFilename": "README.md", 21 | "bugs": { 22 | "url": "https://github.com/thomaslindstrom/color-picker/issues" 23 | }, 24 | "homepage": "https://github.com/thomaslindstrom/color-picker", 25 | "providedServices": { 26 | "color-picker": { 27 | "versions": { 28 | "1.0.0": "provideColorPicker" 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thomaslindstrom/color-picker/7f95eb5e7b0ac685c3e4f0cd5857c0d52e87297a/preview.gif -------------------------------------------------------------------------------- /styles/ColorPicker.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker 3 | // --------------------------------------------------------------------------- 4 | 5 | .ColorPicker { 6 | height: 0; // Height is computed 7 | top: 0; // Top is computed 8 | left: 0; // Left is computed 9 | position: absolute; 10 | visibility: hidden; 11 | opacity: 0; 12 | pointer-events: none; 13 | user-select: none; 14 | z-index: 100; 15 | 16 | transition: 17 | opacity .15s, 18 | visibility 0 .15s; 19 | backface-visibility: hidden; 20 | transform-style: preserve-3d; 21 | 22 | // ------------------------------------- 23 | // : `is open` modifier 24 | // ------------------------------------- 25 | &.is--open { 26 | visibility: visible; 27 | opacity: 1; 28 | pointer-events: all; 29 | 30 | transition: opacity .037s; } 31 | 32 | // ------------------------------------- 33 | // @variables 34 | // ------------------------------------- 35 | @image-transparency: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAJElEQVQIW2PcvXv3fwYg6OzsBFEMjBgCLi4uYBXl5eUQFegCAIYVD/syLxKnAAAAAElFTkSuQmCC); 36 | 37 | @color-base: #ebebeb; 38 | @color-base2: @color-base; 39 | @color-base3: @color-base2; 40 | 41 | // ------------------------------------- 42 | // @imports 43 | // ------------------------------------- 44 | @import "extensions/Arrow.less"; 45 | @import "extensions/Color.less"; 46 | @import "extensions/Return.less"; 47 | @import "extensions/Definition.less"; 48 | @import "extensions/Body.less"; 49 | @import "extensions/Format.less"; 50 | } 51 | -------------------------------------------------------------------------------- /styles/extensions/Alpha.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Alpha 3 | // --------------------------------------------------------------------------- 4 | 5 | &-alpha { 6 | @radius-border: 2px; 7 | 8 | width: 20px; height: @size-saturation; 9 | cursor: -webkit-grab; 10 | cursor: grab; 11 | border: 1px solid #f2f2f2; 12 | border-radius: @radius-border; 13 | box-shadow: 0 0 1px rgba(0, 0, 0, .07); 14 | position: relative; 15 | 16 | &:active { 17 | cursor: -webkit-grabbing; 18 | cursor: grabbing; } 19 | 20 | &:before { 21 | content: ''; 22 | background: @image-transparency; 23 | background-size: 9px 9px; 24 | image-rendering: pixelated; 25 | border-radius: @radius-border - 1; 26 | position: absolute; 27 | top: 0; right: 0; bottom: 0; left: 0; 28 | z-index: 1; 29 | opacity: .1; } 30 | &:after { 31 | content: ''; 32 | border-radius: @radius-border; 33 | box-shadow: 34 | inset 0 0 0 1px rgba(0, 0, 0, .07), 35 | inset 0 0 18px rgba(0, 0, 0, .2); 36 | position: absolute; 37 | top: 0; right: 0; bottom: 0; left: 0; 38 | z-index: 3; } 39 | 40 | // ------------------------------------- 41 | // canvas 42 | // ------------------------------------- 43 | &-canvas { 44 | width: 100%; height: 100%; 45 | border-radius: 1px; 46 | position: relative; 47 | z-index: 2; } 48 | 49 | // ------------------------------------- 50 | // controls 51 | // ------------------------------------- 52 | &-control { 53 | height: 5px; 54 | margin-top: -2px; 55 | border: 2px solid #fff; 56 | border-radius: 1px; 57 | box-shadow: 58 | 0 0 1px rgba(0, 0, 0, .3), 59 | 0 1px 1px rgba(0, 0, 0, .2); 60 | position: absolute; 61 | top: 50%; right: 1px; left: 1px; 62 | z-index: 4; } 63 | } 64 | -------------------------------------------------------------------------------- /styles/extensions/Arrow.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Arrow 3 | // --------------------------------------------------------------------------- 4 | 5 | @size-arrow: 10px; 6 | 7 | &-arrow { 8 | // TODO: should have the transparency background 9 | width: @size-arrow * 2; height: @size-arrow; 10 | border-top-color: #ff0000; 11 | border-bottom-color: #ff0000; 12 | position: absolute; 13 | top: 0; bottom: 0; 14 | 15 | &:before, &:after { 16 | content: ''; 17 | width: 0; height: 0; 18 | border-left: @size-arrow solid transparent; 19 | border-right: @size-arrow solid transparent; 20 | border-bottom: @size-arrow solid transparent; 21 | position: absolute; 22 | 23 | // center the arrow 24 | transform: translate(-50%, 0); } 25 | 26 | &:before { 27 | border-bottom-color: #fff; 28 | opacity: .89; } 29 | &:after { 30 | border-bottom-color: #ff0000; 31 | border-bottom-color: inherit; } 32 | 33 | // ------------------------------------- 34 | // : `is flipped` modifier 35 | // ------------------------------------- 36 | .is--flipped & { 37 | top: auto; bottom: 0; 38 | 39 | &:before { 40 | border-bottom: none; 41 | border-top: @size-arrow solid #fff; } 42 | &:after { 43 | border-bottom: none; 44 | border-top: @size-arrow solid #ff0000; 45 | border-top-color: inherit; } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /styles/extensions/Body.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Body 3 | // --------------------------------------------------------------------------- 4 | 5 | &-body { 6 | @padding-body: 7px; 7 | 8 | background: @color-base; 9 | padding: @padding-body; 10 | border-radius: 0 0 1px 1px; 11 | box-shadow: 12 | 0 -1px 5px rgba(0, 0, 0, .14), 13 | 0 -1px 0 rgba(0, 0, 0, .09); 14 | position: relative; 15 | top: @size-arrow + @size-color; 16 | z-index: 30; 17 | display: flex; 18 | 19 | transform: translateZ(3px); // push layer forward 20 | 21 | & > div { margin-left: @padding-body - 1; } 22 | & > div:first-child { margin-left: 0; } 23 | 24 | // ------------------------------------- 25 | // : `is flipped` modifier 26 | // ------------------------------------- 27 | .is--flipped & { 28 | border-radius: 1px 1px 0 0; 29 | box-shadow: 30 | 0 1px 5px rgba(0, 0, 0, .14), 31 | 0 1px 0 rgba(0, 0, 0, .09); 32 | top: auto; } 33 | 34 | // ------------------------------------- 35 | // : `view definition` modifier 36 | // ------------------------------------- 37 | .view--definition & { 38 | display: none; } 39 | 40 | // ------------------------------------- 41 | // @imports 42 | // ------------------------------------- 43 | @import "./Hue.less"; 44 | @import "./Alpha.less"; 45 | @import "./Saturation.less"; 46 | } 47 | -------------------------------------------------------------------------------- /styles/extensions/Color.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Color 3 | // --------------------------------------------------------------------------- 4 | 5 | @size-color: 90px; 6 | 7 | &-color { 8 | background: red; 9 | width: 100%; height: @size-color; 10 | cursor: pointer; 11 | margin-top: @size-arrow; 12 | border-radius: 2px 2px 0 0; 13 | position: absolute; 14 | top: 0; bottom: auto; 15 | 16 | &:before, &:after { 17 | content: ''; 18 | border-radius: 2px 2px 0 0; 19 | position: absolute; 20 | top: 0; right: 0; bottom: 0; left: 0; 21 | z-index: -1; } 22 | 23 | &:before { 24 | background: #fff; 25 | opacity: .92; } 26 | &:after { 27 | background: @image-transparency; 28 | background-size: 9px 9px; 29 | image-rendering: pixelated; 30 | opacity: .1; } 31 | 32 | // ------------------------------------- 33 | // : `is flipped` modifier 34 | // ------------------------------------- 35 | .is--flipped & { 36 | margin-top: 0; 37 | margin-bottom: @size-arrow; 38 | border-radius: 0 0 2px 2px; 39 | top: auto; bottom: 0; 40 | 41 | &:before, &:after { 42 | border-radius: 0 0 2px 2px; } 43 | } 44 | 45 | // ------------------------------------- 46 | // : `view definition` modifier 47 | // ------------------------------------- 48 | .view--definition & { 49 | display: none; } 50 | 51 | // ------------------------------------- 52 | // text 53 | // TODO text is overflowing onto multiple lines 54 | // ------------------------------------- 55 | &-text { 56 | font-family: 'Source Code Pro', monospace, serif; 57 | font-weight: 600; 58 | font-size: 16px; 59 | -webkit-font-smoothing: antialiased; 60 | color: #fff; 61 | margin: 0; 62 | text-align: center; 63 | text-shadow: 64 | 0 1px 55px rgba(0, 0, 0, 1), 65 | 0 0 18px rgba(0, 0, 0, 0.36), 66 | 7px 0 30px rgba(0, 0, 0, 0.25), 67 | -7px 0 30px rgba(0, 0, 0, 0.21), 68 | 0 1px 1px rgba(0, 0, 0, 0.15), 69 | 0 1px 0 rgba(0, 0, 0, 0.03), 70 | 0 -1px 1px rgba(0, 0, 0, 0.04), 71 | 26px 1px 31px rgba(0, 0, 0, 0.43), 72 | -26px 1px 31px rgba(0, 0, 0, 0.47), 73 | 0 1px 0 rgba(0, 0, 0, 0.04), 74 | 0 -1px 0 rgba(0, 0, 0, 0.04), 75 | 0 2px 1px rgba(0, 0, 0, 0.06); 76 | position: absolute; 77 | top: 50%; right: 0; left: 0; 78 | 79 | transition: transform .2s; 80 | transform: translate(0, -50%); 81 | 82 | backface-visibility: hidden; 83 | transform-style: preserve-3d; } 84 | &:hover &-text { 85 | transition: transform .067s; 86 | transform: translate(0, -60%); } 87 | 88 | // ------------------------------------- 89 | // : `is flipped` modifier 90 | // ------------------------------------- 91 | .is--flipped &:hover &-text { 92 | transform: translate(0, -40%); } 93 | 94 | // ------------------------------------- 95 | // : `is return visible` modifier 96 | // ------------------------------------- 97 | &.is--returnVisible &-text { 98 | transform: translate(0, -95%); } 99 | &.is--returnVisible:hover &-text { 100 | transform: translate(0, -105%); } 101 | 102 | .is--flipped &.is--returnVisible &-text { 103 | transform: translate(0, -5%); } 104 | .is--flipped &.is--returnVisible:hover &-text { 105 | transform: translate(0, 5%); } 106 | } 107 | -------------------------------------------------------------------------------- /styles/extensions/Definition.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Definition 3 | // --------------------------------------------------------------------------- 4 | 5 | @size-definition: 90px; 6 | 7 | &-definition { 8 | background: rgba(0, 0, 0, 0); 9 | width: 200px; height: @size-definition; 10 | cursor: pointer; 11 | margin-top: @size-arrow; 12 | border-radius: 2px; 13 | position: relative; 14 | display: none; 15 | 16 | transition: background .1s; 17 | 18 | &:before, &:after { 19 | content: ''; 20 | border-radius: 2px; 21 | position: absolute; 22 | top: 0; right: 0; bottom: 0; left: 0; 23 | z-index: -1; } 24 | 25 | &:before { 26 | background: #fff; 27 | opacity: .92; } 28 | &:after { 29 | background: @image-transparency; 30 | background-size: 9px 9px; 31 | image-rendering: pixelated; 32 | opacity: .1; } 33 | 34 | // ------------------------------------- 35 | // : `is flipped` modifier 36 | // ------------------------------------- 37 | .is--flipped & { 38 | margin-top: 0; 39 | margin-bottom: @size-arrow; } 40 | 41 | // ------------------------------------- 42 | // : `view definition` modifier 43 | // ------------------------------------- 44 | .view--definition & { 45 | display: block; } 46 | 47 | // ------------------------------------- 48 | // definition 49 | // ------------------------------------- 50 | &-definition { 51 | font-family: 'Source Code Pro', monospace, serif; 52 | font-weight: 600; 53 | font-size: 17px; 54 | -webkit-font-smoothing: antialiased; 55 | color: #fff; 56 | margin: 0; 57 | text-align: center; 58 | text-shadow: 59 | 0 1px 55px rgba(0, 0, 0, 1), 60 | 0 0 18px rgba(0, 0, 0, 0.36), 61 | 7px 0 30px rgba(0, 0, 0, 0.25), 62 | -7px 0 30px rgba(0, 0, 0, 0.21), 63 | 0 1px 1px rgba(0, 0, 0, 0.15), 64 | 0 1px 0 rgba(0, 0, 0, 0.03), 65 | 0 -1px 1px rgba(0, 0, 0, 0.04), 66 | 26px 1px 31px rgba(0, 0, 0, 0.43), 67 | -26px 1px 31px rgba(0, 0, 0, 0.47), 68 | 0 1px 0 rgba(0, 0, 0, 0.04), 69 | 0 -1px 0 rgba(0, 0, 0, 0.04), 70 | 0 2px 1px rgba(0, 0, 0, 0.06); 71 | position: absolute; 72 | top: 40%; right: 0; left: 0; 73 | 74 | transition: transform .2s; 75 | transform: translate(0, -50%); 76 | 77 | backface-visibility: hidden; 78 | transform-style: preserve-3d; } 79 | &:hover &-definition { 80 | transition: transform .067s; 81 | transform: translate(0, -60%); } 82 | 83 | // ------------------------------------- 84 | // variable 85 | // ------------------------------------- 86 | &-variable { 87 | font-family: 'Source Code Pro', monospace, serif; 88 | font-weight: 500; 89 | font-size: 12px; 90 | -webkit-font-smoothing: antialiased; 91 | color: #fff; 92 | margin: 0; 93 | text-align: center; 94 | text-shadow: 95 | 0 1px 55px rgba(0, 0, 0, 1), 96 | 0 0 18px rgba(0, 0, 0, 0.36), 97 | 7px 0 30px rgba(0, 0, 0, 0.25), 98 | -7px 0 30px rgba(0, 0, 0, 0.21), 99 | 0 1px 1px rgba(0, 0, 0, 0.15), 100 | 0 1px 0 rgba(0, 0, 0, 0.03), 101 | 0 -1px 1px rgba(0, 0, 0, 0.04), 102 | 26px 1px 31px rgba(0, 0, 0, 0.43), 103 | -26px 1px 31px rgba(0, 0, 0, 0.47), 104 | 0 1px 0 rgba(0, 0, 0, 0.04), 105 | 0 -1px 0 rgba(0, 0, 0, 0.04), 106 | 0 2px 1px rgba(0, 0, 0, 0.06); 107 | position: absolute; 108 | top: 75%; right: 0; left: 0; 109 | 110 | transition: transform .2s; 111 | transform: translate(0, -50%); 112 | 113 | backface-visibility: hidden; 114 | transform-style: preserve-3d; } 115 | } 116 | -------------------------------------------------------------------------------- /styles/extensions/Format.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Format 3 | // --------------------------------------------------------------------------- 4 | 5 | @size-format: 22px; 6 | 7 | &-format { 8 | background: #c0c0c0; 9 | width: 100%; height: @size-format + 1px; 10 | border-radius: 1px; 11 | position: absolute; 12 | top: auto; bottom: -@size-format; 13 | display: flex; 14 | 15 | backface-visibility: hidden; 16 | 17 | // ------------------------------------- 18 | // : `is flipped` modifier 19 | // ------------------------------------- 20 | .is--flipped & { 21 | top: -@size-format; bottom: auto; } 22 | 23 | // ------------------------------------- 24 | // : `view definition` modifier 25 | // ------------------------------------- 26 | .view--definition & { 27 | display: none; } 28 | 29 | // ------------------------------------- 30 | // conversion button 31 | // ------------------------------------- 32 | &-button { 33 | background: #d6d6d6; 34 | width: 100%; height: 100%; 35 | font-size: 9px; 36 | color: #777; 37 | line-height: @size-format; 38 | text-shadow: 0 1px #e7e7e7; 39 | margin-right: 1px; 40 | border: 0; 41 | border-radius: 1px; 42 | box-shadow: 43 | inset 0 0 0 1px rgba(255, 255, 255, .3), 44 | inset 0 2px 0 #c3c3c3; 45 | -webkit-appearance: none; 46 | 47 | &:last-child { margin-right: 0; } 48 | &:hover { background: #ddd; } 49 | 50 | // : `is flipped` modifier 51 | // --------------------------- 52 | .is--flipped & { 53 | box-shadow: 54 | inset 0 0 0 1px rgba(255, 255, 255, .3), 55 | inset 0 -2px 0 #c3c3c3; 56 | } 57 | 58 | // : `is active` modifier 59 | // --------------------------- 60 | &.is--active { 61 | background: @color-base; 62 | color: #555; 63 | box-shadow: inset 0 1px 3px #fff; 64 | text-shadow: none; 65 | 66 | // : `is flipped` modifier 67 | .is--flipped & { box-shadow: inset 0 -1px 3px #fff; } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /styles/extensions/Hue.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Hue 3 | // --------------------------------------------------------------------------- 4 | 5 | &-hue { 6 | @radius-border: 2px; 7 | 8 | width: 20px; height: @size-saturation; 9 | cursor: -webkit-grab; 10 | cursor: grab; 11 | border: 1px solid #f2f2f2; 12 | border-radius: @radius-border; 13 | box-shadow: 0 0 1px rgba(0, 0, 0, .07); 14 | position: relative; 15 | 16 | &:active { 17 | cursor: -webkit-grabbing; 18 | cursor: grabbing; } 19 | 20 | &:after { 21 | content: ''; 22 | border-radius: @radius-border - 1; 23 | box-shadow: 24 | inset 0 0 0 1px rgba(0, 0, 0, .07), 25 | inset 0 0 18px rgba(0, 0, 0, .2); 26 | position: absolute; 27 | top: 0; right: 0; bottom: 0; left: 0; } 28 | 29 | // ------------------------------------- 30 | // canvas 31 | // ------------------------------------- 32 | &-canvas { 33 | width: 100%; height: 100%; 34 | border-radius: @radius-border - 1; } 35 | 36 | // ------------------------------------- 37 | // controls 38 | // ------------------------------------- 39 | &-control { 40 | height: 5px; 41 | margin-top: -2px; 42 | border: 2px solid #fff; 43 | border-radius: 1px; 44 | box-shadow: 45 | 0 0 1px rgba(0, 0, 0, .3), 46 | 0 1px 1px rgba(0, 0, 0, .2); 47 | position: absolute; 48 | top: 50%; right: 1px; left: 1px; 49 | z-index: 1; } 50 | } 51 | -------------------------------------------------------------------------------- /styles/extensions/Return.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Return 3 | // --------------------------------------------------------------------------- 4 | 5 | @size-return: 26px; 6 | 7 | &-return { 8 | background: red; // #ff0000 9 | width: 100%; height: @size-return; 10 | cursor: pointer; 11 | margin-top: @size-color + @size-arrow; 12 | border-radius: 2px 2px 0 0; 13 | box-shadow: 0 -1px 0 rgba(0, 0, 0, .07); 14 | position: absolute; 15 | top: 0; bottom: auto; left: 0; 16 | z-index: 1; 17 | visibility: hidden; 18 | 19 | backface-visibility: hidden; 20 | transform-style: preserve-3d; 21 | transition: transform .1s, visibility 0 .1s; 22 | 23 | transform: translateZ(1px); 24 | 25 | &:before, &:after { 26 | content: ''; 27 | border-radius: 2px 2px 0 0; 28 | position: absolute; 29 | top: 0; right: 0; bottom: 0; left: 0; 30 | z-index: -1; 31 | 32 | backface-visibility: hidden; 33 | transform: translateZ(-3px); } 34 | 35 | &:before { 36 | background: #fff; 37 | opacity: 1; } 38 | &:after { 39 | background: @image-transparency; 40 | background-size: 9px 9px; 41 | image-rendering: pixelated; 42 | opacity: .1; } 43 | 44 | // ------------------------------------- 45 | // : `is flipped` modifier 46 | // ------------------------------------- 47 | .is--flipped & { 48 | margin-top: 0; 49 | margin-bottom: @size-color + @size-arrow; 50 | border-radius: 0 0 2px 2px; 51 | box-shadow: 0 1px 0 rgba(0, 0, 0, .07); 52 | top: auto; bottom: 0; 53 | 54 | &:before, &:after { 55 | border-radius: 0 0 2px 2px; } 56 | } 57 | 58 | // ------------------------------------- 59 | // : `is visible` modifier 60 | // ------------------------------------- 61 | &.is--visible { 62 | transition: transform .2s; 63 | transform: translateY(-@size-return) translateZ(1px); 64 | visibility: visible; 65 | 66 | // : `is flipped` modifier 67 | // --------------------------- 68 | .is--flipped & { 69 | transform: translateY(@size-return) translateZ(1px); } 70 | } 71 | 72 | // ------------------------------------- 73 | // : `view definition` modifier 74 | // ------------------------------------- 75 | .view--definition & { 76 | display: none; } 77 | 78 | // ------------------------------------- 79 | // text 80 | // ------------------------------------- 81 | &-text { 82 | font-family: 'Source Code Pro', monospace, serif; 83 | font-weight: 500; 84 | font-size: 12px; 85 | -webkit-font-smoothing: antialiased; 86 | color: #fff; 87 | margin: 0; 88 | text-align: center; 89 | text-shadow: 90 | 0 1px 55px rgba(0, 0, 0, 1), 91 | 0 0 18px rgba(0, 0, 0, 0.36), 92 | 7px 0 30px rgba(0, 0, 0, 0.25), 93 | -7px 0 30px rgba(0, 0, 0, 0.21), 94 | 0 1px 1px rgba(0, 0, 0, 0.15), 95 | 0 1px 0 rgba(0, 0, 0, 0.03), 96 | 0 -1px 1px rgba(0, 0, 0, 0.04), 97 | 26px 1px 31px rgba(0, 0, 0, 0.43), 98 | -26px 1px 31px rgba(0, 0, 0, 0.47), 99 | 0 1px 0 rgba(0, 0, 0, 0.04), 100 | 0 -1px 0 rgba(0, 0, 0, 0.04), 101 | 0 2px 1px rgba(0, 0, 0, 0.06); 102 | position: absolute; 103 | top: 50%; right: 0; left: 0; 104 | 105 | transition: transform .2s; 106 | transform: translate(0, -50%); 107 | 108 | backface-visibility: hidden; 109 | transform-style: preserve-3d; } 110 | &:hover &-text { 111 | transition: transform .067s; 112 | transform: translate(0, -60%); } 113 | 114 | // ------------------------------------- 115 | // : `is flipped` modifier 116 | // ------------------------------------- 117 | .is--flipped &:hover &-text { 118 | transform: translate(0, -40%); } 119 | } 120 | -------------------------------------------------------------------------------- /styles/extensions/Saturation.less: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Color Picker/extensions: Saturation 3 | // --------------------------------------------------------------------------- 4 | 5 | @size-saturation: 200px; 6 | 7 | &-saturation { 8 | @radius-border: 2px; 9 | 10 | width: @size-saturation; height: @size-saturation; 11 | cursor: crosshair; 12 | border: 1px solid #f2f2f2; 13 | border-radius: @radius-border; 14 | box-shadow: 0 0 1px rgba(0, 0, 0, .07); 15 | position: relative; 16 | 17 | &:after { 18 | content: ''; 19 | border-radius: @radius-border - 1; 20 | box-shadow: 21 | inset 0 0 0 1px rgba(0, 0, 0, .07), 22 | inset 0 0 18px rgba(0, 0, 0, .2); 23 | position: absolute; 24 | top: 0; right: 0; bottom: 0; left: 0; } 25 | 26 | // ------------------------------------- 27 | // canvas 28 | // ------------------------------------- 29 | &-canvas { 30 | width: 100%; height: 100%; 31 | border-radius: 1px; } 32 | 33 | // ------------------------------------- 34 | // controls 35 | // ------------------------------------- 36 | &-control { 37 | width: 9px; height: 9px; 38 | margin-top: -5px; margin-left: -5px; 39 | border: 2px solid #fff; 40 | border-radius: 100%; 41 | box-shadow: 42 | inset 0 0 1px rgba(0, 0, 0, .3), 43 | 0 0 1px rgba(0, 0, 0, .3), 44 | 0 1px 1px rgba(0, 0, 0, .2); 45 | position: absolute; 46 | top: 50%; left: 50%; 47 | z-index: 1; } 48 | } 49 | --------------------------------------------------------------------------------