diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..31be69d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: objective-c + +notifications: + email: + on_success: never + on_failure: change + +script: 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' diff --git a/README.md b/README.md index 835d68d..c4b4453 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# A Color Picker for Atom +# A Color Picker for Atom [](https://travis-ci.org/thomaslindstrom/color-picker.svg?branch=master) 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. diff --git a/lib/ColorPicker-view.coffee b/lib/ColorPicker-view.coffee deleted file mode 100644 index bff8b6d..0000000 --- a/lib/ColorPicker-view.coffee +++ /dev/null @@ -1,452 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker: view -# ---------------------------------------------------------------------------- - - module.exports = -> - Parent: null - - SmartColor: (require './modules/SmartColor')() - SmartVariable: (require './modules/SmartVariable')() - Emitter: (require './modules/Emitter')() - - extensions: {} - getExtension: (extensionName) -> @extensions[extensionName] - - isFirstOpen: yes - canOpen: yes - element: null - selection: null - - listeners: [] - - # ------------------------------------- - # Create and activate Color Picker view - # ------------------------------------- - activate: -> - _workspace = atom.workspace - _workspaceView = atom.views.getView _workspace - - # Create element - # --------------------------- - @element = - el: do -> - _el = document.createElement 'div' - _el.classList.add 'ColorPicker' - - return _el - # Utility functions - remove: -> @el.parentNode.removeChild @el - - addClass: (className) -> @el.classList.add className; return this - removeClass: (className) -> @el.classList.remove className; return this - hasClass: (className) -> @el.classList.contains className - - width: -> @el.offsetWidth - height: -> @el.offsetHeight - - setHeight: (height) -> @el.style.height = "#{ height }px" - - hasChild: (child) -> - if child and _parent = child.parentNode - if child is @el - return true - else return @hasChild _parent - return false - - # Open & Close the Color Picker - isOpen: -> @hasClass 'is--open' - open: -> @addClass 'is--open' - close: -> @removeClass 'is--open' - - # Flip & Unflip the Color Picker - isFlipped: -> @hasClass 'is--flipped' - flip: -> @addClass 'is--flipped' - unflip: -> @removeClass 'is--flipped' - - # Set Color Picker position - # - x {Number} - # - y {Number} - setPosition: (x, y) -> - @el.style.left = "#{ x }px" - @el.style.top = "#{ y }px" - return this - - # Add a child on the ColorPicker element - add: (element) -> - @el.appendChild element - return this - @loadExtensions() - - # Close the Color Picker on any activity unrelated to it - # but also emit events on the Color Picker - # --------------------------- - @listeners.push ['mousedown', onMouseDown = (e) => - return unless @element.isOpen() - - _isPickerEvent = @element.hasChild e.target - @emitMouseDown e, _isPickerEvent - return @close() unless _isPickerEvent] - window.addEventListener 'mousedown', onMouseDown, true - - @listeners.push ['mousemove', onMouseMove = (e) => - return unless @element.isOpen() - - _isPickerEvent = @element.hasChild e.target - @emitMouseMove e, _isPickerEvent] - window.addEventListener 'mousemove', onMouseMove, true - - @listeners.push ['mouseup', onMouseUp = (e) => - return unless @element.isOpen() - - _isPickerEvent = @element.hasChild e.target - @emitMouseUp e, _isPickerEvent] - window.addEventListener 'mouseup', onMouseUp, true - - @listeners.push ['mousewheel', onMouseWheel = (e) => - return unless @element.isOpen() - - _isPickerEvent = @element.hasChild e.target - @emitMouseWheel e, _isPickerEvent] - window.addEventListener 'mousewheel', onMouseWheel - - _workspaceView.addEventListener 'keydown', (e) => - return unless @element.isOpen() - - _isPickerEvent = @element.hasChild e.target - @emitKeyDown e, _isPickerEvent - return @close() - - # Close it on scroll also - atom.workspace.observeTextEditors (editor) => - _editorView = atom.views.getView editor - _subscriptionTop = _editorView.onDidChangeScrollTop => @close() - _subscriptionLeft = _editorView.onDidChangeScrollLeft => @close() - - editor.onDidDestroy -> - _subscriptionTop.dispose() - _subscriptionLeft.dispose() - @onBeforeDestroy -> - _subscriptionTop.dispose() - _subscriptionLeft.dispose() - return - - # Close it when the window resizes - @listeners.push ['resize', onResize = => - @close()] - window.addEventListener 'resize', onResize - - # Close it when the active item is changed - _workspace.getActivePane().onDidChangeActiveItem => @close() - - # Place the Color Picker element - # --------------------------- - @close() - - # TODO: Is this really the best way to do this? Hint: Probably not - (@Parent = (atom.views.getView atom.workspace).querySelector '.vertical') - .appendChild @element.el - return this - - # ------------------------------------- - # Destroy the view and unbind events - # ------------------------------------- - destroy: -> - @emitBeforeDestroy() - - for [_event, _listener] in @listeners - window.removeEventListener _event, _listener - @element.remove() - - # ------------------------------------- - # Load Color Picker extensions // more like dependencies - # ------------------------------------- - loadExtensions: -> - # TODO: This is really stupid. Should this be done with `fs` or something? - # TODO: Extension files have pretty much the same base. Simplify? - for _extension in ['Arrow', 'Color', 'Body', 'Saturation', 'Alpha', 'Hue', 'Definition', 'Return', 'Format'] - _requiredExtension = (require "./extensions/#{ _extension }")(this) - @extensions[_extension] = _requiredExtension - _requiredExtension.activate?() - return - - # ------------------------------------- - # Set up events and handling - # ------------------------------------- - # Mouse events - emitMouseDown: (e, isOnPicker) -> - @Emitter.emit 'mouseDown', e, isOnPicker - onMouseDown: (callback) -> - @Emitter.on 'mouseDown', callback - - emitMouseMove: (e, isOnPicker) -> - @Emitter.emit 'mouseMove', e, isOnPicker - onMouseMove: (callback) -> - @Emitter.on 'mouseMove', callback - - emitMouseUp: (e, isOnPicker) -> - @Emitter.emit 'mouseUp', e, isOnPicker - onMouseUp: (callback) -> - @Emitter.on 'mouseUp', callback - - emitMouseWheel: (e, isOnPicker) -> - @Emitter.emit 'mouseWheel', e, isOnPicker - onMouseWheel: (callback) -> - @Emitter.on 'mouseWheel', callback - - # Key events - emitKeyDown: (e, isOnPicker) -> - @Emitter.emit 'keyDown', e, isOnPicker - onKeyDown: (callback) -> - @Emitter.on 'keyDown', callback - - # Position Change - emitPositionChange: (position, colorPickerPosition) -> - @Emitter.emit 'positionChange', position, colorPickerPosition - onPositionChange: (callback) -> - @Emitter.on 'positionChange', callback - - # Opening - emitOpen: -> - @Emitter.emit 'open' - onOpen: (callback) -> - @Emitter.on 'open', callback - - # Before opening - emitBeforeOpen: -> - @Emitter.emit 'beforeOpen' - onBeforeOpen: (callback) -> - @Emitter.on 'beforeOpen', callback - - # Closing - emitClose: -> - @Emitter.emit 'close' - onClose: (callback) -> - @Emitter.on 'close', callback - - # Before destroying - emitBeforeDestroy: -> - @Emitter.emit 'beforeDestroy' - onBeforeDestroy: (callback) -> - @Emitter.on 'beforeDestroy', callback - - # Input Color - emitInputColor: (smartColor, wasFound=true) -> - @Emitter.emit 'inputColor', smartColor, wasFound - onInputColor: (callback) -> - @Emitter.on 'inputColor', callback - - # Input Variable - emitInputVariable: (match) -> - @Emitter.emit 'inputVariable', match - onInputVariable: (callback) -> - @Emitter.on 'inputVariable', callback - - # Input Variable Color - emitInputVariableColor: (smartColor, pointer) -> - @Emitter.emit 'inputVariableColor', smartColor, pointer - onInputVariableColor: (callback) -> - @Emitter.on 'inputVariableColor', callback - - # ------------------------------------- - # Open the Color Picker - # ------------------------------------- - open: (Editor=null, Cursor=null) -> - return unless @canOpen - @emitBeforeOpen() - - Editor = atom.workspace.getActiveTextEditor() unless Editor - EditorView = atom.views.getView Editor - - return unless EditorView - EditorRoot = EditorView.shadowRoot or EditorView - - # Reset selection - @selection = null - - # Find the current cursor - # --------------------------- - Cursor = Editor.getLastCursor() unless Cursor - - # Fail if the cursor isn't visible - _visibleRowRange = EditorView.getVisibleRowRange() - _cursorScreenRow = Cursor.getScreenRow() - _cursorBufferRow = Cursor.getBufferRow() - - return if (_cursorScreenRow < _visibleRowRange[0]) or (_cursorScreenRow > _visibleRowRange[1]) - - # Try matching the contents of the current line to color regexes - _lineContent = Cursor.getCurrentBufferLine() - - _colorMatches = @SmartColor.find _lineContent - _variableMatches = @SmartVariable.find _lineContent, Editor.getPath() - _matches = _colorMatches.concat _variableMatches - - # Figure out which of the matches is the one the user wants - _cursorColumn = Cursor.getBufferColumn() - _match = do -> for _match in _matches - return _match if _match.start <= _cursorColumn and _match.end >= _cursorColumn - - # If we've got a match, we should select it - if _match - Editor.clearSelections() - - _selection = Editor.addSelectionForBufferRange [ - [_cursorBufferRow, _match.start] - [_cursorBufferRow, _match.end]] - @selection = match: _match, row: _cursorBufferRow - # But if we don't have a match, center the Color Picker on last cursor - else - _cursorPosition = Cursor.getPixelRect() - @selection = column: Cursor.getBufferColumn(), row: _cursorBufferRow - - # Emit - # --------------------------- - if _match - # The match is a variable. Look up the definition - if _match.isVariable? - _match.getDefinition() - .then (definition) => - _smartColor = (@SmartColor.find definition.value)[0].getSmartColor() - @emitInputVariableColor _smartColor, definition.pointer - .catch (error) => - @emitInputVariableColor false - @emitInputVariable _match - # The match is a color - else @emitInputColor _match.getSmartColor() - # No match, but `randomColor` option is set - else if atom.config.get 'color-picker.randomColor' - _randomColor = @SmartColor.RGBArray [ - ((Math.random() * 255) + .5) << 0 - ((Math.random() * 255) + .5) << 0 - ((Math.random() * 255) + .5) << 0] - - # Convert to `preferredColor`, and then emit it - _preferredFormat = atom.config.get 'color-picker.preferredFormat' - _convertedColor = _randomColor["to#{ _preferredFormat }"]() - _randomColor = @SmartColor[_preferredFormat](_convertedColor) - - @emitInputColor _randomColor, false - # No match, and it's the first open - else if @isFirstOpen - _redColor = @SmartColor.HEX '#f00' - - # Convert to `preferredColor`, and then emit it - _preferredFormat = atom.config.get 'color-picker.preferredFormat' - - if _redColor.format isnt _preferredFormat - _convertedColor = _redColor["to#{ _preferredFormat }"]() - _redColor = @SmartColor[_preferredFormat](_convertedColor) - @isFirstOpen = no - - @emitInputColor _redColor, false - - # After (& if) having selected text (as this might change the scroll - # position) gather information about the Editor - # --------------------------- - PaneView = atom.views.getView atom.workspace.getActivePane() - _paneOffsetTop = PaneView.offsetTop - _paneOffsetLeft = PaneView.offsetLeft - - _editorOffsetTop = EditorView.parentNode.offsetTop - _editorOffsetLeft = EditorRoot.querySelector('.scroll-view').offsetLeft - _editorScrollTop = EditorView.getScrollTop() - - _lineHeight = Editor.getLineHeightInPixels() - _lineOffsetLeft = EditorRoot.querySelector('.line').offsetLeft - - # Center it on the middle of the selection range - # TODO: There can be lines over more than one row - if _match - _rect = EditorView.pixelRectForScreenRange(_selection.getScreenRange()) - _right = _rect.left + _rect.width - _cursorPosition = Cursor.getPixelRect() - _cursorPosition.left = _right - (_rect.width / 2) - - # Figure out where to place the Color Picker - # --------------------------- - _totalOffsetTop = _paneOffsetTop + _cursorPosition.height - _editorScrollTop + _editorOffsetTop - _totalOffsetLeft = _paneOffsetLeft + _editorOffsetLeft + _lineOffsetLeft - - _position = - x: _cursorPosition.left + _totalOffsetLeft - y: _cursorPosition.top + _totalOffsetTop - - # Figure out where to actually place the Color Picker by - # setting up boundaries and flipping it if necessary - # --------------------------- - _colorPickerPosition = - x: do => - _colorPickerWidth = @element.width() - _halfColorPickerWidth = (_colorPickerWidth / 2) << 0 - - # Make sure the Color Picker isn't too far to the left - _x = Math.max 10, _position.x - _halfColorPickerWidth - # Make sure the Color Picker isn't too far to the right - _x = Math.min (@Parent.offsetWidth - _colorPickerWidth - 10), _x - - return _x - y: do => - @element.unflip() - - # TODO: It's not really working out great - - # If the color picker is too far down, flip it - if @element.height() + _position.y > @Parent.offsetHeight - 32 - @element.flip() - return _position.y - _lineHeight - @element.height() - # But if it's fine, keep the Y position - else return _position.y - - # Set Color Picker position and emit events - @element.setPosition _colorPickerPosition.x, _colorPickerPosition.y - @emitPositionChange _position, _colorPickerPosition - - # Open the Color Picker - requestAnimationFrame => # wait for class delay - @element.open() - @emitOpen() - return true - - # ------------------------------------- - # Replace selected color - # ------------------------------------- - canReplace: yes - replace: (color) -> - return unless @canReplace - @canReplace = no - - Editor = atom.workspace.getActiveTextEditor() - Editor.clearSelections() - - if @selection.match - _cursorStart = @selection.match.start - _cursorEnd = @selection.match.end - else _cursorStart = _cursorEnd = @selection.column - - # Select the color we're going to replace - Editor.addSelectionForBufferRange [ - [@selection.row, _cursorStart] - [@selection.row, _cursorEnd]] - Editor.replaceSelectedText null, => color - - # Select the newly inserted color and move the cursor to it - setTimeout => - Editor.setCursorBufferPosition [ - @selection.row, _cursorStart] - Editor.clearSelections() - - # Update selection length - @selection.match?.end = _cursorStart + color.length - - Editor.addSelectionForBufferRange [ - [@selection.row, _cursorStart] - [@selection.row, _cursorStart + color.length]] - return setTimeout ( => @canReplace = yes), 100 - return - - # ------------------------------------- - # Close the Color Picker - # ------------------------------------- - close: -> - @element.close() - @emitClose() diff --git a/lib/ColorPicker.coffee b/lib/ColorPicker.coffee deleted file mode 100644 index a25a310..0000000 --- a/lib/ColorPicker.coffee +++ /dev/null @@ -1,99 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker -# ---------------------------------------------------------------------------- - - module.exports = - activate: -> - _command = 'color-picker:open' - - # Set key bindings - # --------------------------- - _triggerKey = (atom.config.get 'color-picker.triggerKey').toLowerCase() - _TriggerKey = _triggerKey.toUpperCase() - - # TODO this doesn't look too good - _macSelector = '.platform-darwin atom-workspace' - _windowsSelector = '.platform-win32 atom-workspace' - _linuxSelector = '.platform-linux atom-workspace' - - _keymap = {} - - # Mac OS X - _keymap["#{ _macSelector }"] = {} - _keymap["#{ _macSelector }"]["cmd-#{ _TriggerKey }"] = _command - # Windows - _keymap["#{ _windowsSelector }"] = {} - _keymap["#{ _windowsSelector }"]["ctrl-alt-#{ _triggerKey }"] = _command - # Linux - _keymap["#{ _linuxSelector }"] = {} - _keymap["#{ _linuxSelector }"]["ctrl-alt-#{ _triggerKey }"] = _command - - # Add the keymap - atom.keymaps.add 'color-picker:trigger', _keymap - - # Add context menu command - # --------------------------- - atom.contextMenu.add 'atom-text-editor': [ - label: 'Color Picker' - command: _command] - - # Add color-picker:open command - # --------------------------- - _commands = {}; _commands["#{ _command }"] = => @view?.open() - atom.commands.add 'atom-text-editor', _commands - - return @view.activate() - - deactivate: -> @view?.destroy() - - provideColorPicker: -> - return { - open: (Editor, Cursor) => - return unless @view - return @view.open Editor, Cursor - } - - config: - # Random color configuration: On Color Picker open, show a random color - randomColor: - title: 'Serve a random color on open' - description: 'If the Color Picker doesn\'t get an input color, it serves a completely random color.' - type: 'boolean' - default: true - # Automatic Replace configuration: Replace color value on change - automaticReplace: - title: 'Automatically Replace Color' - description: 'Replace selected color automatically on change. Works well with as-you-type CSS reloaders.' - type: 'boolean' - default: false - # Abbreviate values configuration: If possible, abbreviate color values. Eg. “0.3” to “.3” - # TODO: Can we abbreviate something else? - abbreviateValues: - title: 'Abbreviate Color Values' - 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)”.' - type: 'boolean' - default: false - # Uppercase color value configuration: Uppercase for example HEX color values - # TODO: Does it make sense to uppercase anything other than HEX colors? - uppercaseColorValues: - title: 'Uppercase Color Values' - description: 'If sensible, uppercase the color value. For example, “#aaa” becomes “#AAA”.' - type: 'boolean' - default: false - # Preferred color format configuration: Set what color format the color picker should display initially - preferredFormat: - title: 'Preferred Color Format' - description: 'On open, the Color Picker will show a color in this format.' - type: 'string' - enum: ['RGB', 'HEX', 'HSL', 'HSV', 'VEC'] - default: 'RGB' - # Trigger key: Set what trigger key opens the color picker - # TODO more options? - triggerKey: - title: 'Trigger key' - description: 'Decide what trigger key should open the Color Picker. `CMD-SHIFT-{TRIGGER_KEY}` and `CTRL-ALT-{TRIGGER_KEY}`. Requires a restart.' - type: 'string' - enum: ['C', 'E', 'H', 'K'] - default: 'C' - - view: (require './ColorPicker-view')() diff --git a/lib/color-picker.js b/lib/color-picker.js new file mode 100644 index 0000000..98989a1 --- /dev/null +++ b/lib/color-picker.js @@ -0,0 +1,73 @@ +/** @babel */ +// --------------------------------------------------------------------------- +// color-picker.js +// --------------------------------------------------------------------------- + + // ------------------------------------- + // Configuration + // ------------------------------------- + const config = { + // Random color on open + randomColor: { + title: 'Serve a random color on open', + description: 'If the Color Picker doesn\'t get an input color, it serves a completely random color.', + type: 'boolean', + default: true + }, + + // Automatically update color value when it changes + automaticReplace: { + title: 'Automatically Replace Color', + description: 'Replace selected color automatically on change. Works well with as-you-type CSS reloaders.', + type: 'boolean', + default: false + }, + + // Abbreviate colors if possible: “0.3” becomes “.3” + // TODO More abbreviation? + abbreviateValues: { + title: 'Abbreviate Color Values', + 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)”.', + type: 'boolean', + default: false + }, + + + // Uppercase color values + // TODO: Does it make sense to uppercase anything other than HEX colors? + uppercaseColorValues: { + title: 'Uppercase Color Values', + description: 'Uppercase the color value: “#aaa” becomes “#AAA”.', + type: 'boolean', + default: false + }, + + // Preferred initial color format + preferredFormat: { + title: 'Preferred Color Format', + description: 'On open, the Color Picker will show a color in this format.', + type: 'string', + enum: ['RGB', 'HEX', 'HSL', 'HSV', 'VEC'], + default: 'RGB' + }, + + // Select the key that opens the Color Picker + triggerKey: { + title: 'Trigger key', + description: 'Decide what trigger key should open the Color Picker. `CMD-SHIFT-{TRIGGER_KEY}` and `CTRL-ALT-{TRIGGER_KEY}`. Requires a restart.', + type: 'string', + enum: ['C', 'E', 'H', 'K'], + default: 'C' + } + }; + + // ------------------------------------- + // Activation function + // ------------------------------------- + function activate() { + console.log('color-picker'); + } + + export default { + activate + }; diff --git a/lib/extensions/Alpha.coffee b/lib/extensions/Alpha.coffee deleted file mode 100644 index 6791b51..0000000 --- a/lib/extensions/Alpha.coffee +++ /dev/null @@ -1,227 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Alpha -# Color Alpha controller -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - Emitter: (require '../modules/Emitter')() - - element: null - control: null - canvas: null - - # ------------------------------------- - # Set up events and handling - # ------------------------------------- - # Selection Changed event - emitSelectionChanged: -> - @Emitter.emit 'selectionChanged', @control.selection - onSelectionChanged: (callback) -> - @Emitter.on 'selectionChanged', callback - - # Color Changed event - emitColorChanged: -> - @Emitter.emit 'colorChanged', @control.selection.color - onColorChanged: (callback) -> - @Emitter.on 'colorChanged', callback - - # ------------------------------------- - # Create and activate Alpha controller - # ------------------------------------- - activate: -> - Body = colorPicker.getExtension 'Body' - - # Create element - # --------------------------- - @element = - el: do -> - _classPrefix = Body.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-alpha" - - return _el - # Utility functions - width: 0 - height: 0 - getWidth: -> return @width or @el.offsetWidth - getHeight: -> return @height or @el.offsetHeight - - rect: null - getRect: -> return @rect or @updateRect() - updateRect: -> @rect = @el.getClientRects()[0] - - # Add a child on the Alpha element - add: (element) -> - @el.appendChild element - return this - Body.element.add @element.el, 1 - - # Update element rect position when Color Picker opens - # --------------------------- - colorPicker.onOpen => @element.updateRect() - - # Create and draw canvas - # --------------------------- - setTimeout => # wait for the DOM - Alpha = this - Saturation = colorPicker.getExtension 'Saturation' - - # Prepare some variables - _elementWidth = @element.getWidth() - _elementHeight = @element.getHeight() - - # Create canvas element - @canvas = - el: do -> - _el = document.createElement 'canvas' - _el.width = _elementWidth - _el.height = _elementHeight - _el.classList.add "#{ Alpha.element.el.className }-canvas" - - return _el - # Utility functions - context: null - getContext: -> @context or (@context = @el.getContext '2d') - - # Render Alpha canvas - previousRender: null - render: (smartColor) -> - _rgb = ( do -> - unless smartColor - return colorPicker.SmartColor.HEX '#f00' - else return smartColor - ).toRGBArray().join ',' - - return if @previousRender and @previousRender is _rgb - - # Get context and clear it - _context = @getContext() - _context.clearRect 0, 0, _elementWidth, _elementHeight - - # Draw alpha channel - _gradient = _context.createLinearGradient 0, 0, 1, _elementHeight - _gradient.addColorStop .01, "rgba(#{ _rgb },1)" - _gradient.addColorStop .99, "rgba(#{ _rgb },0)" - - _context.fillStyle = _gradient - _context.fillRect 0, 0, _elementWidth, _elementHeight - return @previousRender = _rgb - - # Render again on Saturation color change - Saturation.onColorChanged (smartColor) => - @canvas.render smartColor - @canvas.render() - - # Add to Alpha element - @element.add @canvas.el - - # Create Alpha control element - # --------------------------- - setTimeout => # wait for the DOM - hasChild = (element, child) -> - if child and _parent = child.parentNode - if child is element - return true - else return hasChild element, _parent - return false - - # Create element - Alpha = this - Saturation = colorPicker.getExtension 'Saturation' - - @control = - el: do -> - _el = document.createElement 'div' - _el.classList.add "#{ Alpha.element.el.className }-control" - - return _el - isGrabbing: no - - previousControlPosition: null - updateControlPosition: (y) -> - _joined = ",#{ y }" - return if @previousControlPosition and @previousControlPosition is _joined - - requestAnimationFrame => - @el.style.top = "#{ y }px" - return @previousControlPosition = _joined - - selection: - y: 0 - color: null - alpha: null - setSelection: (e, alpha=null, offset=null) -> - _rect = Alpha.element.getRect() - _width = Alpha.element.getWidth() - _height = Alpha.element.getHeight() - - if e then _y = e.pageY - _rect.top - # Set the alpha directly - else if (typeof alpha is 'number') - _y = _height - (alpha * _height) # reversed, 1 is top - # Handle scroll - else if (typeof offset is 'number') - _y = @selection.y + offset - # Default to previous values - else _y = @selection.y - - _y = @selection.y = Math.max 0, (Math.min _height, _y) - - _alpha = 1 - (_y / _height) # reversed, 1 is top - @selection.alpha = (Math.round _alpha * 100) / 100 # 2 decimal precision - - # Update the smartColor (if any) - if _smartColor = @selection.color - _RGBAArray = _smartColor.toRGBAArray() - _RGBAArray[3] = @selection.alpha - - @selection.color = colorPicker.SmartColor.RGBAArray _RGBAArray - Alpha.emitColorChanged() - # Or set a default red - else @selection.color = colorPicker.SmartColor.RGBAArray [255, 0, 0, @selection.alpha] - - _position = - y: Math.max 3, (Math.min (_height - 6), _y) - @updateControlPosition _position.y - - return Alpha.emitSelectionChanged() - - refreshSelection: -> @setSelection() - @control.refreshSelection() - - # If the Color Picker is fed a color, set it - colorPicker.onInputColor (smartColor) => - @control.setSelection null, smartColor.getAlpha() - - # Reset - colorPicker.onOpen => @control.isGrabbing = no - colorPicker.onClose => @control.isGrabbing = no - - # Bind controller events - Saturation.onColorChanged (smartColor) => - @control.selection.color = smartColor - @control.refreshSelection() - - colorPicker.onMouseDown (e, isOnPicker) => - return unless isOnPicker and hasChild Alpha.element.el, e.target - e.preventDefault() - @control.isGrabbing = yes - @control.setSelection e - - colorPicker.onMouseMove (e) => - return unless @control.isGrabbing - @control.setSelection e - - colorPicker.onMouseUp (e) => - return unless @control.isGrabbing - @control.isGrabbing = no - @control.setSelection e - - colorPicker.onMouseWheel (e, isOnPicker) => - return unless isOnPicker and hasChild Alpha.element.el, e.target - e.preventDefault() - @control.setSelection null, null, (e.wheelDeltaY * .33) # make it a bit softer - - # Add to Alpha element - @element.add @control.el - return this diff --git a/lib/extensions/Arrow.coffee b/lib/extensions/Arrow.coffee deleted file mode 100644 index da01a9e..0000000 --- a/lib/extensions/Arrow.coffee +++ /dev/null @@ -1,85 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Arrow -# An arrow pointing at the current selection -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - element: null - - # ------------------------------------- - # Create and activate Arrow - # ------------------------------------- - activate: -> - _halfArrowWidth = null - - # Create element - # --------------------------- - @element = - el: do -> - _classPrefix = colorPicker.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-arrow" - - return _el - # Utility functions - addClass: (className) -> @el.classList.add className; return this - removeClass: (className) -> @el.classList.remove className; return this - hasClass: (className) -> @el.classList.contains className - - width: -> @el.offsetWidth - height: -> @el.offsetHeight - - # Set Arrow position - # - x {Number} - setPosition: (x) -> - @el.style.left = "#{ x }px" - return this - - # Set the Color element background color - previousColor: null - setColor: (smartColor) -> - _color = smartColor.toRGBA?() or 'none' - return if @previousColor and @previousColor is _color - - @el.style.borderTopColor = _color - @el.style.borderBottomColor = _color - return @previousColor = _color - colorPicker.element.add @element.el - - # Get and save arrow width - # --------------------------- - setTimeout => _halfArrowWidth = (@element.width() / 2) << 0 - - # Increase Color Picker height - # --------------------------- - setTimeout => - _newHeight = colorPicker.element.height() + @element.height() - colorPicker.element.setHeight _newHeight - - # Set Arrow color on Alpha change - # --------------------------- - setTimeout => # wait for the DOM - Alpha = colorPicker.getExtension 'Alpha' - - Alpha.onColorChanged (smartColor) => - if smartColor then @element.setColor smartColor - # Default to #f00 red - else colorPicker.SmartColor.HEX '#f00' - return - - # Set Arrow color to transparent when a variable is input - # --------------------------- - colorPicker.onInputVariable => - @element.setColor colorPicker.SmartColor.RGBAArray [0, 0, 0, 0] - - # ... but set it to the variable color when that is found - # --------------------------- - colorPicker.onInputVariableColor (smartColor) => - return unless smartColor - @element.setColor smartColor - - # Place the Arrow - # --------------------------- - colorPicker.onPositionChange (position, colorPickerPosition) => - @element.setPosition position.x - colorPickerPosition.x - return this diff --git a/lib/extensions/Body.coffee b/lib/extensions/Body.coffee deleted file mode 100644 index cb43394..0000000 --- a/lib/extensions/Body.coffee +++ /dev/null @@ -1,40 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Body -# The Color Picker Body, serves as the container for color controls -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - element: null - - # ------------------------------------- - # Create and activate Body - # ------------------------------------- - activate: -> - @element = - el: do -> - _classPrefix = colorPicker.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-body" - - return _el - # Utility functions - height: -> @el.offsetHeight - - # Add a child on the Body element - add: (element, weight) -> - if weight - if weight > @el.children.length - @el.appendChild element - else @el.insertBefore element, @el.children[weight] - else @el.appendChild element - - return this - colorPicker.element.add @element.el - - # Increase Color Picker height - # --------------------------- - setTimeout => - _newHeight = colorPicker.element.height() + @element.height() - colorPicker.element.setHeight _newHeight - - return this diff --git a/lib/extensions/Color.coffee b/lib/extensions/Color.coffee deleted file mode 100644 index 54c0976..0000000 --- a/lib/extensions/Color.coffee +++ /dev/null @@ -1,184 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Color -# The element showing the current color -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - Emitter: (require '../modules/Emitter')() - - element: null - color: null - - # ------------------------------------- - # Set up events and handling - # ------------------------------------- - # Output format event - emitOutputFormat: (format) -> - @Emitter.emit 'outputFormat', format - onOutputFormat: (callback) -> - @Emitter.on 'outputFormat', callback - - # ------------------------------------- - # Create and activate Color element - # ------------------------------------- - activate: -> - @element = - el: do -> - _classPrefix = colorPicker.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-color" - - return _el - # Utility functions - addClass: (className) -> @el.classList.add className; return this - removeClass: (className) -> @el.classList.remove className; return this - - height: -> @el.offsetHeight - - # Add a child on the Color element - add: (element) -> - @el.appendChild element - return this - - # Set the Color element background color - previousColor: null - setColor: (smartColor) -> - _color = smartColor.toRGBA() - return if @previousColor and @previousColor is _color - - @el.style.backgroundColor = _color - return @previousColor = _color - colorPicker.element.add @element.el - - # Increase Color Picker height - # --------------------------- - setTimeout => - _newHeight = colorPicker.element.height() + @element.height() - colorPicker.element.setHeight _newHeight - - # Set or replace Color on click - # --------------------------- - hasChild = (element, child) -> - if child and _parent = child.parentNode - if child is element - return true - else return hasChild element, _parent - return false - - _isClicking = no - - colorPicker.onMouseDown (e, isOnPicker) => - return unless isOnPicker and hasChild @element.el, e.target - e.preventDefault() - _isClicking = yes - - colorPicker.onMouseMove (e) -> - _isClicking = no - - colorPicker.onMouseUp (e) => - return unless _isClicking - colorPicker.replace @color - colorPicker.element.close() - - # Set or replace Color on key press enter - # --------------------------- - colorPicker.onKeyDown (e) => - return unless e.which is 13 - e.stopPropagation() - colorPicker.replace @color - - # Set background element color on Alpha change - # --------------------------- - setTimeout => # wait for the DOM - Alpha = colorPicker.getExtension 'Alpha' - - Alpha.onColorChanged (smartColor) => - @element.setColor do -> - if smartColor then return smartColor - # Default to #f00 red - else return colorPicker.SmartColor.HEX '#f00' - return - return - - # Create Color text element - # --------------------------- - setTimeout => - Alpha = colorPicker.getExtension 'Alpha' - Return = colorPicker.getExtension 'Return' - Format = colorPicker.getExtension 'Format' - - # Create text element - _text = document.createElement 'p' - _text.classList.add "#{ @element.el.className }-text" - - # Reset before color picker open - colorPicker.onBeforeOpen => @color = null - - # Keep track of the input color (for its format) - _inputColor = null - - colorPicker.onInputColor (smartColor, wasFound) -> - _inputColor = if wasFound - smartColor - else null - - # Keep track of the Format element format - _formatFormat = null - Format.onFormatChanged (format) -> _formatFormat = format - colorPicker.onInputColor -> _formatFormat = null - - # Set the text element to contain the Color data - setColor = (smartColor) => - _preferredFormat = atom.config.get 'color-picker.preferredFormat' - _format = _formatFormat or _inputColor?.format or _preferredFormat or 'RGB' - - # TODO: This is very fragile - _function = if smartColor.getAlpha() < 1 - (smartColor["to#{ _format }A"] or smartColor["to#{ _format }"]) - else smartColor["to#{ _format }"] - - # If a color was input, and the value hasn't changed since, - # show the inital value not to confuse the user, but only - # if the input color format is still the same - _outputColor = do -> - if _inputColor and (_inputColor.format is _format or _inputColor.format is "#{ _format }A") - if smartColor.equals _inputColor - return _inputColor.value - return _function.call smartColor - - # Finish here if the _outputColor is the same as the - # current color - return unless _outputColor isnt @color - - # Automatically replace color in editor if - # `automaticReplace` is true, but only if there was an - # input color and if it is different from before - if _inputColor and atom.config.get 'color-picker.automaticReplace' - colorPicker.replace _outputColor - - # Set and save the output color - @color = _outputColor - _text.innerText = _outputColor - - return @emitOutputFormat _format - - # Update on alpha change, keep track of current color - _currentColor = null - - Alpha.onColorChanged (smartColor) => - setColor _currentColor = do -> - if smartColor then return smartColor - # Default to #f00 red - else return colorPicker.SmartColor.HEX '#f00' - return - - # When Format is changed, update color - Format.onFormatChanged -> setColor _currentColor - - # When the `Return` element is visible, add a class to allow - # the text to be pushed up or down a bit - Return.onVisibility (visibility) => - if visibility then @element.addClass 'is--returnVisible' - else @element.removeClass 'is--returnVisible' - @element.add _text - return this diff --git a/lib/extensions/Definition.coffee b/lib/extensions/Definition.coffee deleted file mode 100644 index b39bd15..0000000 --- a/lib/extensions/Definition.coffee +++ /dev/null @@ -1,143 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Definition -# The element showing the current variable definition -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - element: null - pointer: null - - # ------------------------------------- - # Create and activate Definition element - # ------------------------------------- - activate: -> - @element = - el: do -> - _classPrefix = colorPicker.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-definition" - - return _el - # Utility functions - height: -> @el.offsetHeight - - # Add a child on the Definition element - add: (element) -> - @el.appendChild element - return this - - # Set the Definition element background color - setColor: (smartColor) -> - @el.style.backgroundColor = smartColor.toRGBA() - colorPicker.element.add @element.el - - # Set Color Picker height - # --------------------------- - setTimeout => - Arrow = colorPicker.getExtension 'Arrow' - $colorPicker = colorPicker.element - - # Change view mode when a variable is input - colorPicker.onInputVariable => - _oldHeight = $colorPicker.height() - $colorPicker.addClass 'view--definition' - - _newHeight = @element.height() + Arrow.element.height() - $colorPicker.setHeight _newHeight - - # Reset current element background color - @element.setColor colorPicker.SmartColor.RGBAArray [0, 0, 0, 0] - - # Reset picker on close, and clear the event - # TODO handle this on the ColorPicker itself, maybe? - onClose = -> - colorPicker.canOpen = yes - $colorPicker.setHeight _oldHeight - $colorPicker.removeClass 'view--definition' - - # TODO: This kinda goes against the 'no strings' thing - colorPicker.Emitter.off 'close', onClose - colorPicker.onClose onClose - - # Make sure the class is never set when a color is input - colorPicker.onInputColor -> - $colorPicker.removeClass 'view--definition' - return - - # Set background element color on change - # --------------------------- - colorPicker.onInputVariableColor (smartColor) => - return unless smartColor - @element.setColor smartColor - - # Set or replace selection on click - # --------------------------- - colorPicker.onInputVariableColor (..., pointer) => - # Keep track of the current pointer for when the color is - # supposed to be replaced - @pointer = pointer - - hasChild = (element, child) -> - if child and _parent = child.parentNode - if child is element - return true - else return hasChild element, _parent - return false - - _isClicking = no - - colorPicker.onMouseDown (e, isOnPicker) => - return unless isOnPicker and hasChild @element.el, e.target - e.preventDefault() - _isClicking = yes - - colorPicker.onMouseMove (e) -> - _isClicking = no - - colorPicker.onMouseUp (e) => - return unless _isClicking and @pointer - - atom.workspace.open(@pointer.filePath).then => - Editor = atom.workspace.getActiveTextEditor() - Editor.clearSelections() - Editor.setSelectedBufferRange @pointer.range - Editor.scrollToCursorPosition() - - colorPicker.close() - return - - # Create Definition definition text element - # --------------------------- - setTimeout => - # Create definition text element - _definition = document.createElement 'p' - _definition.classList.add "#{ @element.el.className }-definition" - - # Remove the definition when a new variable is input - colorPicker.onInputVariable -> - _definition.innerText = '' - - # Set definition when the definition is found - colorPicker.onInputVariableColor (color) -> - # If a color definition is found - if color then _definition.innerText = color.value - # If no definition is found, show an error - else _definition.innerText = 'No color found.' - - # Add to Definition element - @element.add _definition - - # Create Definition variable text element - # --------------------------- - setTimeout => - # Create variable text element - _variable = document.createElement 'p' - _variable.classList.add "#{ @element.el.className }-variable" - - # Set variable when the variable is input - colorPicker.onInputVariable (match) -> - _variable.innerText = match.match - - # Add to Definition element - @element.add _variable - return this diff --git a/lib/extensions/Format.coffee b/lib/extensions/Format.coffee deleted file mode 100644 index b157402..0000000 --- a/lib/extensions/Format.coffee +++ /dev/null @@ -1,117 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Format -# The element providing UI to convert between color formats -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - Emitter: (require '../modules/Emitter')() - - element: null - color: null - - # ------------------------------------- - # Set up events and handling - # ------------------------------------- - # Format Changed event - emitFormatChanged: (format) -> - @Emitter.emit 'formatChanged', format - onFormatChanged: (callback) -> - @Emitter.on 'formatChanged', callback - - # ------------------------------------- - # Create and activate Format element - # ------------------------------------- - activate: -> - @element = - el: do -> - _classPrefix = colorPicker.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-format" - - return _el - - # Add a child on the Color element - add: (element) -> - @el.appendChild element - return this - colorPicker.element.add @element.el - - # Add conversion buttons #ff0 - # --------------------------- - setTimeout => - Color = colorPicker.getExtension 'Color' - - _buttons = [] - _activeButton = null - - # On color picker open, reset - colorPicker.onBeforeOpen -> for _button in _buttons - _button.deactivate() - - # On Color element output format, activate applicable button - Color.onOutputFormat (format) -> for _button in _buttons - # TODO this is inefficient. There should be a way to easily - # check if `format` is in `_button.format`, including the - # alpha channel - if format is _button.format or format is "#{ _button.format }A" - _button.activate() - _activeButton = _button - else _button.deactivate() - - # Create formatting buttons - # TODO same as setting, globalize - for _format in ['RGB', 'HEX', 'HSL', 'HSV', 'VEC'] then do (_format) => - Format = this - - # Create the button - _button = - el: do -> - _el = document.createElement 'button' - _el.classList.add "#{ Format.element.el.className }-button" - _el.innerHTML = _format - return _el - format: _format - - # Utility functions - addClass: (className) -> @el.classList.add className; return this - removeClass: (className) -> @el.classList.remove className; return this - - activate: -> @addClass 'is--active' - deactivate: -> @removeClass 'is--active' - _buttons.push _button - - # Set initial format - unless _activeButton - if _format is atom.config.get 'color-picker.preferredFormat' - _activeButton = _button - _button.activate() - - # Change color format on click - hasChild = (element, child) -> - if child and _parent = child.parentNode - if child is element - return true - else return hasChild element, _parent - return false - _isClicking = no - - colorPicker.onMouseDown (e, isOnPicker) => - return unless isOnPicker and hasChild _button.el, e.target - e.preventDefault() - _isClicking = yes - - colorPicker.onMouseMove (e) -> - _isClicking = no - - colorPicker.onMouseUp (e) => - return unless _isClicking - - _activeButton.deactivate() if _activeButton - _button.activate() - _activeButton = _button - - @emitFormatChanged _format - - # Add button to the parent Format element - @element.add _button.el - return this diff --git a/lib/extensions/Hue.coffee b/lib/extensions/Hue.coffee deleted file mode 100644 index 0cbe823..0000000 --- a/lib/extensions/Hue.coffee +++ /dev/null @@ -1,206 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Hue -# Color Hue controller -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - Emitter: (require '../modules/Emitter')() - - element: null - control: null - canvas: null - - # ------------------------------------- - # Utility function to get the current hue - # ------------------------------------- - getHue: -> - if (@control and @control.selection) and @element - return @control.selection.y / @element.getHeight() * 360 - else return 0 - - # ------------------------------------- - # Set up events and handling - # ------------------------------------- - # Selection Changed event - emitSelectionChanged: -> - @Emitter.emit 'selectionChanged', @control.selection - onSelectionChanged: (callback) -> - @Emitter.on 'selectionChanged', callback - - # Color Changed event - emitColorChanged: -> - @Emitter.emit 'colorChanged', @control.selection.color - onColorChanged: (callback) -> - @Emitter.on 'colorChanged', callback - - # ------------------------------------- - # Create and activate Hue controller - # ------------------------------------- - activate: -> - Body = colorPicker.getExtension 'Body' - - # Create the element - # --------------------------- - @element = - el: do -> - _classPrefix = Body.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-hue" - - return _el - # Utility functions - width: 0 - height: 0 - getWidth: -> return @width or @el.offsetWidth - getHeight: -> return @height or @el.offsetHeight - - rect: null - getRect: -> return @rect or @updateRect() - updateRect: -> @rect = @el.getClientRects()[0] - - # Add a child on the Hue element - add: (element) -> - @el.appendChild element - return this - Body.element.add @element.el, 2 - - # Update element rect when Color Picker opens - # --------------------------- - colorPicker.onOpen => - return unless @element.updateRect() and _rect = @element.getRect() - @width = _rect.width - @height = _rect.height - - # Create and draw canvas - # --------------------------- - setTimeout => # wait for the DOM - Hue = this - - # Prepare some variables - _elementWidth = @element.getWidth() - _elementHeight = @element.getHeight() - - # Red through all the main colors and back to red - _hexes = ['#f00', '#ff0', '#0f0', '#0ff', '#00f', '#f0f', '#f00'] - - # Create canvas element - @canvas = - el: do -> - _el = document.createElement 'canvas' - _el.width = _elementWidth - _el.height = _elementHeight - _el.classList.add "#{ Hue.element.el.className }-canvas" - - return _el - # Utility functions - context: null - getContext: -> @context or (@context = @el.getContext '2d') - - getColorAtPosition: (y) -> return colorPicker.SmartColor.HSVArray [ - y / Hue.element.getHeight() * 360 - 100 - 100] - - # Draw gradient - _context = @canvas.getContext() - - _step = 1 / (_hexes.length - 1) - _gradient = _context.createLinearGradient 0, 0, 1, _elementHeight - _gradient.addColorStop (_step * _i), _hex for _hex, _i in _hexes - - _context.fillStyle = _gradient - _context.fillRect 0, 0, _elementWidth, _elementHeight - - # Add to Hue element - @element.add @canvas.el - - # Create Hue control element - # --------------------------- - setTimeout => # wait for the DOM - hasChild = (element, child) -> - if child and _parent = child.parentNode - if child is element - return true - else return hasChild element, _parent - return false - - # Create element - Hue = this - - @control = - el: do -> - _el = document.createElement 'div' - _el.classList.add "#{ Hue.element.el.className }-control" - - return _el - isGrabbing: no - - # Set control selection - selection: - y: 0 - color: null - setSelection: (e, y=null, offset=null) -> - return unless Hue.canvas and _rect = Hue.element.getRect() - - _width = Hue.element.getWidth() - _height = Hue.element.getHeight() - - if e then _y = e.pageY - _rect.top - # Set the y directly - else if (typeof y is 'number') - _y = y - # Handle scroll - else if (typeof offset is 'number') - _y = @selection.y + offset - # Default to top - else _y = @selection.y - - _y = @selection.y = Math.max 0, (Math.min _height, _y) - @selection.color = Hue.canvas.getColorAtPosition _y - - _position = y: Math.max 3, (Math.min (_height - 6), _y) - - requestAnimationFrame => - @el.style.top = "#{ _position.y }px" - return Hue.emitSelectionChanged() - - refreshSelection: -> @setSelection() - @control.refreshSelection() - - # If the Color Picker is fed a color, set it - colorPicker.onInputColor (smartColor) => - _hue = smartColor.toHSVArray()[0] - @control.setSelection null, (@element.getHeight() / 360) * _hue - - # When the selection changes, the color has changed - Hue.onSelectionChanged -> Hue.emitColorChanged() - - # Reset - colorPicker.onOpen => @control.refreshSelection() - colorPicker.onOpen => @control.isGrabbing = no - colorPicker.onClose => @control.isGrabbing = no - - # Bind controller events - colorPicker.onMouseDown (e, isOnPicker) => - return unless isOnPicker and hasChild Hue.element.el, e.target - e.preventDefault() - @control.isGrabbing = yes - @control.setSelection e - - colorPicker.onMouseMove (e) => - return unless @control.isGrabbing - @control.setSelection e - - colorPicker.onMouseUp (e) => - return unless @control.isGrabbing - @control.isGrabbing = no - @control.setSelection e - - colorPicker.onMouseWheel (e, isOnPicker) => - return unless isOnPicker and hasChild Hue.element.el, e.target - e.preventDefault() - @control.setSelection null, null, (e.wheelDeltaY * .33) # make it a bit softer - - # Add to Hue element - @element.add @control.el - return this diff --git a/lib/extensions/Return.coffee b/lib/extensions/Return.coffee deleted file mode 100644 index 1c9bbcd..0000000 --- a/lib/extensions/Return.coffee +++ /dev/null @@ -1,121 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Return -# The element showing the initial color value, enabling the user to return -# to it at any time -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - Emitter: (require '../modules/Emitter')() - - element: null - color: null - - # ------------------------------------- - # Set up events and handling - # ------------------------------------- - # Visibility event - emitVisibility: (visible=true) -> - @Emitter.emit 'visible', visible - onVisibility: (callback) -> - @Emitter.on 'visible', callback - - # ------------------------------------- - # Create and activate Return element - # ------------------------------------- - activate: -> - View = this - - # Build the element - # --------------------------- - @element = - el: do -> - _classPrefix = colorPicker.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-return" - - return _el - # Utility functions - addClass: (className) -> @el.classList.add className; return this - removeClass: (className) -> @el.classList.remove className; return this - hasClass: (className) -> @el.classList.contains className - - hide: -> @removeClass 'is--visible'; View.emitVisibility false - show: -> @addClass 'is--visible'; View.emitVisibility true - - # Add a child on the Return element - add: (element) -> - @el.appendChild element - return this - - # Set the Return element background color - setColor: (smartColor) -> - @el.style.backgroundColor = smartColor.toRGBA() - colorPicker.element.add @element.el - - # Return color on click - # --------------------------- - hasChild = (element, child) -> - if child and _parent = child.parentNode - if child is element - return true - else return hasChild element, _parent - return false - - _isClicking = no - - colorPicker.onMouseDown (e, isOnPicker) => - return unless isOnPicker and hasChild @element.el, e.target - e.preventDefault() - _isClicking = yes - - colorPicker.onMouseMove (e) -> - _isClicking = no - - colorPicker.onMouseUp (e) => - return unless _isClicking and @color - colorPicker.emitInputColor @color - - # Show the element when the input color isn't the current color - # --------------------------- - setTimeout => - Alpha = colorPicker.getExtension 'Alpha' - - # Reset on colorPicker open - colorPicker.onBeforeOpen => - @color = null - - # Save the current color - colorPicker.onInputColor (smartColor, wasFound) => - @color = smartColor if wasFound - - # Do the check on Alpha change - Alpha.onColorChanged (smartColor) => - return @element.hide() unless @color - - if smartColor.equals @color - @element.hide() - else @element.show() - return - - # Set background element color on input color - # --------------------------- - setTimeout => - colorPicker.onInputColor (smartColor, wasFound) => - @element.setColor smartColor if wasFound - return - - # Create Return text element - # --------------------------- - setTimeout => - # Create text element - _text = document.createElement 'p' - _text.classList.add "#{ @element.el.className }-text" - - # Set the text element to contain the Return data - setColor = (smartColor) => - _text.innerText = smartColor.value - - colorPicker.onInputColor (smartColor, wasFound) -> - setColor smartColor if wasFound - @element.add _text - return this diff --git a/lib/extensions/Saturation.coffee b/lib/extensions/Saturation.coffee deleted file mode 100644 index 67ce5c6..0000000 --- a/lib/extensions/Saturation.coffee +++ /dev/null @@ -1,238 +0,0 @@ -# ---------------------------------------------------------------------------- -# Color Picker/extensions: Saturation -# Color Saturation controller -# ---------------------------------------------------------------------------- - - module.exports = (colorPicker) -> - Emitter: (require '../modules/Emitter')() - - element: null - control: null - canvas: null - - # ------------------------------------- - # Set up events and handling - # ------------------------------------- - # Selection Changed event - emitSelectionChanged: -> - @Emitter.emit 'selectionChanged', @control.selection - onSelectionChanged: (callback) -> - @Emitter.on 'selectionChanged', callback - - # Color Changed event - emitColorChanged: -> - @Emitter.emit 'colorChanged', @control.selection.color - onColorChanged: (callback) -> - @Emitter.on 'colorChanged', callback - - # ------------------------------------- - # Create and activate Saturation controller - # ------------------------------------- - activate: -> - Body = colorPicker.getExtension 'Body' - - # Create element - # --------------------------- - @element = - el: do -> - _classPrefix = Body.element.el.className - _el = document.createElement 'div' - _el.classList.add "#{ _classPrefix }-saturation" - - return _el - # Utility functions - width: 0 - height: 0 - getWidth: -> return @width or @el.offsetWidth - getHeight: -> return @height or @el.offsetHeight - - rect: null - getRect: -> return @rect or @updateRect() - updateRect: -> @rect = @el.getClientRects()[0] - - # Add a child on the Saturation element - add: (element) -> - @el.appendChild element - return this - Body.element.add @element.el, 0 - - # Update element rect when Color Picker opens - # --------------------------- - colorPicker.onOpen => - return unless @element.updateRect() and _rect = @element.getRect() - @width = _rect.width - @height = _rect.height - - # Create and draw canvas - # --------------------------- - setTimeout => # wait for the DOM - Saturation = this - Hue = colorPicker.getExtension 'Hue' - - # Prepare some variables - _elementWidth = @element.getWidth() - _elementHeight = @element.getHeight() - - # Create element - @canvas = - el: do -> - _el = document.createElement 'canvas' - _el.width = _elementWidth - _el.height = _elementHeight - _el.classList.add "#{ Saturation.element.el.className }-canvas" - - return _el - # Utility functions - context: null - getContext: -> @context or (@context = @el.getContext '2d') - - getColorAtPosition: (x, y) -> return colorPicker.SmartColor.HSVArray [ - Hue.getHue() - x / Saturation.element.getWidth() * 100 - 100 - (y / Saturation.element.getHeight() * 100)] - - # Render Saturation canvas - previousRender: null - render: (smartColor) -> - _hslArray = ( do -> - unless smartColor - return colorPicker.SmartColor.HEX '#f00' - else return smartColor - ).toHSLArray() - - _joined = _hslArray.join ',' - return if @previousRender and @previousRender is _joined - - # Get context and clear it - _context = @getContext() - _context.clearRect 0, 0, _elementWidth, _elementHeight - - # Draw hue channel on top - _gradient = _context.createLinearGradient 0, 0, _elementWidth, 1 - _gradient.addColorStop .01, 'hsl(0,100%,100%)' - _gradient.addColorStop .99, "hsl(#{ _hslArray[0] },100%,50%)" - - _context.fillStyle = _gradient - _context.fillRect 0, 0, _elementWidth, _elementHeight - - # Draw saturation channel on the bottom - _gradient = _context.createLinearGradient 0, 0, 1, _elementHeight - _gradient.addColorStop .01, 'rgba(0,0,0,0)' - _gradient.addColorStop .99, 'rgba(0,0,0,1)' - - _context.fillStyle = _gradient - _context.fillRect 0, 0, _elementWidth, _elementHeight - return @previousRender = _joined - - # Render again on Hue selection change - Hue.onColorChanged (smartColor) => - @canvas.render smartColor - @canvas.render() - - # Add to Saturation element - @element.add @canvas.el - - # Create Saturation control element - # --------------------------- - setTimeout => # wait for the DOM - hasChild = (element, child) -> - if child and _parent = child.parentNode - if child is element - return true - else return hasChild element, _parent - return false - - # Create element - Saturation = this - Hue = colorPicker.getExtension 'Hue' - - @control = - el: do -> - _el = document.createElement 'div' - _el.classList.add "#{ Saturation.element.el.className }-control" - - return _el - isGrabbing: no - - previousControlPosition: null - updateControlPosition: (x, y) -> - _joined = "#{ x },#{ y }" - return if @previousControlPosition and @previousControlPosition is _joined - - requestAnimationFrame => - @el.style.left = "#{ x }px" - @el.style.top = "#{ y }px" - return @previousControlPosition = _joined - - selection: - x: null - y: 0 - color: null - setSelection: (e, saturation=null, key=null) -> - return unless Saturation.canvas and _rect = Saturation.element.getRect() - - _width = Saturation.element.getWidth() - _height = Saturation.element.getHeight() - - if e - _x = e.pageX - _rect.left - _y = e.pageY - _rect.top - # Set saturation and key directly - else if (typeof saturation is 'number') and (typeof key is 'number') - _x = _width * saturation - _y = _height * key - # Default to previous values - else - if (typeof @selection.x isnt 'number') - @selection.x = _width - _x = @selection.x - _y = @selection.y - - _x = @selection.x = Math.max 0, (Math.min _width, Math.round _x) - _y = @selection.y = Math.max 0, (Math.min _height, Math.round _y) - - _position = - x: Math.max 6, (Math.min (_width - 7), _x) - y: Math.max 6, (Math.min (_height - 7), _y) - - @selection.color = Saturation.canvas.getColorAtPosition _x, _y - @updateControlPosition _position.x, _position.y - return Saturation.emitSelectionChanged() - - refreshSelection: -> @setSelection() - @control.refreshSelection() - - # If the Color Picker is fed a color, set it - colorPicker.onInputColor (smartColor) => - [h, s, v] = smartColor.toHSVArray() - @control.setSelection null, s, (1 - v) - - # When the selection changes, the color has changed - Saturation.onSelectionChanged -> Saturation.emitColorChanged() - - # Reset - colorPicker.onOpen => @control.refreshSelection() - colorPicker.onOpen => @control.isGrabbing = no - colorPicker.onClose => @control.isGrabbing = no - - # Bind controller events - Hue.onColorChanged => @control.refreshSelection() - - colorPicker.onMouseDown (e, isOnPicker) => - return unless isOnPicker and hasChild Saturation.element.el, e.target - e.preventDefault() - @control.isGrabbing = yes - @control.setSelection e - - colorPicker.onMouseMove (e) => - return unless @control.isGrabbing - @control.setSelection e - - colorPicker.onMouseUp (e) => - return unless @control.isGrabbing - @control.isGrabbing = no - @control.setSelection e - - # Add to Saturation element - @element.add @control.el - return this diff --git a/lib/modules/Convert.coffee b/lib/modules/Convert.coffee deleted file mode 100644 index cd31c58..0000000 --- a/lib/modules/Convert.coffee +++ /dev/null @@ -1,195 +0,0 @@ -# ---------------------------------------------------------------------------- -# Convert -# ---------------------------------------------------------------------------- - - module.exports = -> - # TODO: I don't like this file. It's ugly and feels weird - - # ------------------------------------- - # HEX to RGB - # ------------------------------------- - hexToRgb: (hex) -> - hex = hex.replace '#', '' - hex = hex.replace /(.)(.)(.)/, "$1$1$2$2$3$3" if hex.length is 3 - - return [ - parseInt (hex.substr 0, 2), 16 - parseInt (hex.substr 2, 2), 16 - parseInt (hex.substr 4, 2), 16] - - # ------------------------------------- - # HEXA to RGB - # ------------------------------------- - hexaToRgb: (hexa) -> - return @hexToRgb (hexa.match /rgba\((\#.+),/)[1] - - # ------------------------------------- - # HEX to HSL - # ------------------------------------- - hexToHsl: (hex) -> - return @rgbToHsl @hexToRgb hex.replace '#', '' - - # ------------------------------------- - # RGB to HEX - # ------------------------------------- - rgbToHex: (rgb) -> - _componentToHex = (component) -> - _hex = component.toString 16 - return if _hex.length is 1 then "0#{ _hex }" else _hex - - return [ - (_componentToHex rgb[0]) - (_componentToHex rgb[1]) - (_componentToHex rgb[2]) - ].join '' - - # ------------------------------------- - # RGB to HSL - # ------------------------------------- - rgbToHsl: ([r, g, b]) -> - r /= 255 - g /= 255 - b /= 255 - - _max = Math.max r, g, b - _min = Math.min r, g, b - - _l = (_max + _min) / 2 - - if _max is _min then return [0, 0, Math.floor _l * 100] - - _d = _max - _min - _s = if _l > 0.5 then _d / (2 - _max - _min) else _d / (_max + _min) - - switch _max - when r then _h = (g - b) / _d + (if g < b then 6 else 0) - when g then _h = (b - r) / _d + 2 - when b then _h = (r - g) / _d + 4 - - _h /= 6 - - return [ - Math.floor _h * 360 - Math.floor _s * 100 - Math.floor _l * 100] - - # ------------------------------------- - # RGB to HSV - # ------------------------------------- - rgbToHsv: ([r, g, b]) -> - computedH = 0 - computedS = 0 - computedV = 0 - - if not r? or not g? or not b? or isNaN(r) or isNaN(g) or isNaN(b) - return - if r < 0 or g < 0 or b < 0 or r > 255 or g > 255 or b > 255 - return - - r = r / 255 - g = g / 255 - b = b / 255 - - minRGB = Math.min(r, Math.min(g, b)) - maxRGB = Math.max(r, Math.max(g, b)) - - # Black-gray-white - if minRGB is maxRGB - computedV = minRGB - - return [ - 0 - 0 - computedV] - - # Colors other than black-gray-white: - d = (if (r is minRGB) then g - b else ((if (b is minRGB) then r - g else b - r))) - h = (if (r is minRGB) then 3 else ((if (b is minRGB) then 1 else 5))) - - computedH = 60 * (h - d / (maxRGB - minRGB)) - computedS = (maxRGB - minRGB) / maxRGB - computedV = maxRGB - - return [ - computedH - computedS - computedV] - - # ------------------------------------- - # HSV to HSL - # ------------------------------------- - hsvToHsl: ([h, s, v]) -> [ - h - s * v / (if (h = (2 - s) * v) < 1 then h else 2 - h) - h / 2] - - # ------------------------------------- - # HSV to RGB - # ------------------------------------- - hsvToRgb: ([h, s, v]) -> - h /= 60 # 0 to 5 - s /= 100 - v /= 100 - - # Achromatic grayscale - if s is 0 then return [ - Math.round v * 255 - Math.round v * 255 - Math.round v * 255] - - _i = Math.floor h - _f = h - _i - _p = v * (1 - s) - _q = v * (1 - s * _f) - _t = v * (1 - s * (1 - _f)) - - _result = switch _i - when 0 then [v, _t, _p] - when 1 then [_q, v, _p] - when 2 then [_p, v, _t] - when 3 then [_p, _q, v] - when 4 then [_t, _p, v] - when 5 then [v, _p, _q] - else [v, _t, _p] - - return [ - Math.round _result[0] * 255 - Math.round _result[1] * 255 - Math.round _result[2] * 255] - - # ------------------------------------- - # HSL to HSV - # ------------------------------------- - hslToHsv: ([h, s, l]) -> - s /= 100 - l /= 100 - - s *= if l < .5 then l else 1 - l - - return [ - h - (2 * s / (l + s)) or 0 - l + s] - - # ------------------------------------- - # HSL to RGB - # ------------------------------------- - hslToRgb: (input) -> - [h, s, v] = @hslToHsv input - return @hsvToRgb [h, (s * 100), (v * 100)] - - # ------------------------------------- - # VEC to RGB - # ------------------------------------- - vecToRgb: (input) -> return [ - (input[0] * 255) << 0 - (input[1] * 255) << 0 - (input[2] * 255) << 0] - - # ------------------------------------- - # RGB to VEC - # ------------------------------------- - rgbToVec: (input) -> return [ - (input[0] / 255).toFixed 2 - (input[1] / 255).toFixed 2 - (input[2] / 255).toFixed 2] diff --git a/lib/modules/Emitter.coffee b/lib/modules/Emitter.coffee deleted file mode 100644 index 527cdac..0000000 --- a/lib/modules/Emitter.coffee +++ /dev/null @@ -1,24 +0,0 @@ -# ---------------------------------------------------------------------------- -# Emitter -# a really lightweight take on an Emitter -# ---------------------------------------------------------------------------- - - module.exports = -> - bindings: {} - - emit: (event, args...) -> - return unless _bindings = @bindings[event] - _callback.apply null, args for _callback in _bindings - return - - on: (event, callback) -> - @bindings[event] = [] unless @bindings[event] - @bindings[event].push callback - return callback - - off: (event, callback) -> - return unless _bindings = @bindings[event] - - _i = _bindings.length; while _i-- and _binding = _bindings[_i] - if _binding is callback then _bindings.splice _i, 1 - return diff --git a/lib/modules/SmartColor.coffee b/lib/modules/SmartColor.coffee deleted file mode 100644 index 04dc47f..0000000 --- a/lib/modules/SmartColor.coffee +++ /dev/null @@ -1,312 +0,0 @@ -# ---------------------------------------------------------------------------- -# SmartColor -# Easily find colors, and convert between color formats -# ---------------------------------------------------------------------------- - - module.exports = -> - Convert = (require './Convert')() - - # ------------------------------------- - # Color Regexes - # ------------------------------------- - COLOR_REGEXES = - # Matches HSL: eg - # hsl(320, 100%, 100%) and hsl(26, 57, 32) and hsl( 36 , 67 , 16 ) - 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 - - # Matches HSL + A: eg - # 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) - 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 - - # Matches HSV: eg - # hsv(320, 100%, 100%) and hsv(26, 57, 32) and hsv( 36 , 67 , 16 ) - 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 - - # Matches HSV + A: eg - # 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) - 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 - - # Matches VEC: eg - # vec3(0.44f, 0.3, 0) and vec3(1.0, 0.42, .4) and vec3( 1f , 0.4 , 1.0 ) - 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 - - # Matches VECA: eg - # 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 ) - 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 - - # Matches RGB: eg. - # rgb(0, 99, 199) and rgb ( 255 , 180 , 255 ) - 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 - - # Matches RGB + A: eg. - # rgba(0, 99, 199, 0.3) and rgba ( 82 , 121, 0, .68 ) - 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 - - # Matches HEX: - # eg. #000 and #ffffff - HEX: /(\#[a-f0-9]{6}|\#[a-f0-9]{3})/i - - # Matches HEX + A: eg - # rgba(#fff, 0.3) and rgba(#000000, .8) and rgba ( #000 , .8) - HEXA: /rgba\s*?\(\s*(\#[a-f0-9]{6}|\#[a-f0-9]{3})\s*?,\s*?(0|1|1.0|0*\.\d+)\s*?\)/i - MATCH_ORDER = ['HSL', 'HSLA', 'HSV', 'HSVA', 'VEC', 'VECA', 'RGB', 'RGBA', 'HEXA', 'HEX'] - - # ------------------------------------- - # Abbreviation functions - # ------------------------------------- - n = (number) -> - number = "#{ number }" - - # Abbreviate if `abbreviateValues` option is true - if atom.config.get 'color-picker.abbreviateValues' - if number[0] is '0' and number[1] is '.' - return number.substring 1 # TODO or `substr`? - else if (parseFloat number, 10) is 1 - return '1' - return number - f = (number) -> - number = "#{ number }" - - if number[3] and number[3] is '0' - return number.substring 0, 3 # TODO or `substr`? - return number - - s = (string) -> - if atom.config.get 'color-picker.abbreviateValues' - return string.replace /\s/g, '' - return string - - # ------------------------------------- - # Public functionality - # ------------------------------------- - return { - # ------------------------------------- - # Find colors in string - # - string {String} - # - # @return String - # ------------------------------------- - find: (string) -> - SmartColor = this - _colors = [] - - for _format in MATCH_ORDER when _regExp = COLOR_REGEXES[_format] - _matches = string.match (new RegExp _regExp.source, 'ig') - continue unless _matches - - for _match in _matches then do (_format, _match) -> - return if (_index = string.indexOf _match) is -1 - - _colors.push - match: _match - format: _format - start: _index - end: _index + _match.length - - getSmartColor: -> SmartColor[_format](_match) - isColor: true - - # Remove the match from the line content string to - # “mark it” as having been “spent”. Be careful to keep the - # correct amount of characters in the string as this is - # later used to see which match fits best, if any - string = string.replace _match, (new Array _match.length + 1).join ' ' - return _colors - - # ------------------------------------- - # Base color object, all colors are versions of this object - # - format {String}: the color format - # - value {String|Array}: The color value - # - RGBAArray {Array}: The color value in RGBAArray format - # ------------------------------------- - color: (format, value, RGBAArray) -> - format: format - value: value - RGBAArray: RGBAArray - - # Compare two smart colors - equals: (smartColor) -> - return false unless smartColor - - return smartColor.RGBAArray[0] is @RGBAArray[0] and smartColor.RGBAArray[1] is @RGBAArray[1] and - smartColor.RGBAArray[2] is @RGBAArray[2] and - smartColor.RGBAArray[3] is @RGBAArray[3] - - getAlpha: -> return @RGBAArray[3] - - # RGB - # --------------------------- - toRGB: -> return s "rgb(#{ @toRGBArray().join ', ' })" - toRGBArray: -> [@RGBAArray[0], @RGBAArray[1], @RGBAArray[2]] - - # RGBA - toRGBA: -> - _rgbaArray = @toRGBAArray() - return s "rgba(#{ _rgbaArray[0] }, #{ _rgbaArray[1] }, #{ _rgbaArray[2] }, #{ n _rgbaArray[3] })" - toRGBAArray: -> @RGBAArray - - # HSL - # --------------------------- - toHSL: -> - _hslArray = @toHSLArray() - return s "hsl(#{ _hslArray[0] }, #{ _hslArray[1] }%, #{ _hslArray[2] }%)" - toHSLArray: -> Convert.rgbToHsl @toRGBArray() - - # HSLA - toHSLA: -> - _hslaArray = @toHSLAArray() - return s "hsla(#{ _hslaArray[0] }, #{ _hslaArray[1] }%, #{ _hslaArray[2] }%, #{ n _hslaArray[3] })" - toHSLAArray: -> @toHSLArray().concat [@getAlpha()] - - # HSV - # --------------------------- - toHSV: -> - _hsvArray = @toHSVArray() - return s "hsv(#{ Math.round _hsvArray[0] }, #{ (_hsvArray[1] * 100) << 0 }%, #{ (_hsvArray[2] * 100) << 0 }%)" - toHSVArray: -> Convert.rgbToHsv @toRGBArray() - - # HSVA - toHSVA: -> - _hsvaArray = @toHSVAArray() - return s "hsva(#{ Math.round _hsvaArray[0] }, #{ (_hsvaArray[1] * 100) << 0 }%, #{ (_hsvaArray[2] * 100) << 0 }%, #{ n _hsvaArray[3] })" - toHSVAArray: -> @toHSVArray().concat [@getAlpha()] - - # VEC - # --------------------------- - toVEC: -> - _vecArray = @toVECArray() - return s "vec3(#{ f _vecArray[0] }, #{ f _vecArray[1] }, #{ f _vecArray[2] })" - toVECArray: -> Convert.rgbToVec @toRGBArray() - - # VECA - toVECA: -> - _vecaArray = @toVECAArray() - return s "vec4(#{ f _vecaArray[0] }, #{ f _vecaArray[1] }, #{ f _vecaArray[2] }, #{ f _vecaArray[3] })" - toVECAArray: -> @toVECArray().concat [@getAlpha()] - - # HEX - # --------------------------- - toHEX: -> - _hex = Convert.rgbToHex @RGBAArray - - # Abbreviate if `abbreviateValues` option is true - if atom.config.get 'color-picker.abbreviateValues' - if _hex[0] is _hex[1] and _hex[2] is _hex[3] and _hex[4] is _hex[5] - _hex = "#{ _hex[0] }#{ _hex[2] }#{ _hex[4] }" - - # Uppercase color values if `uppercaseColorValues` option is true - if atom.config.get 'color-picker.uppercaseColorValues' - _hex = _hex.toUpperCase() - - return '#' + _hex - - # HEXA - toHEXA: -> s "rgba(#{ @toHEX() }, #{ n @getAlpha() })" - - # ------------------------------------- - # Color input formats... - # ------------------------------------- - # RGB - RGB: (value) -> @color 'RGB', value, do -> - _match = value.match COLOR_REGEXES.RGB - - return ([ - parseInt _match[1], 10 - parseInt _match[2], 10 - parseInt _match[3], 10 - ]).concat [1] # add default alpha - RGBArray: (value) -> @color 'RGBArray', value, do -> - return value.concat [1] - - # RGBA - RGBA: (value) -> @color 'RGBA', value, do -> - _match = value.match COLOR_REGEXES.RGBA - - return ([ - parseInt _match[1], 10 - parseInt _match[2], 10 - parseInt _match[3], 10 - ]).concat [parseFloat _match[4], 10] - RGBAArray: (value) -> @color 'RGBAArray', value, value - - # HSL - HSL: (value) -> @color 'HSL', value, do -> - _match = value.match COLOR_REGEXES.HSL - - return (Convert.hslToRgb [ - parseInt _match[1], 10 - parseInt _match[2], 10 - parseInt _match[3], 10 - ]).concat [1] # add default alpha - HSLArray: (value) -> @color 'HSLArray', value, do -> - return (Convert.hslToRgb value).concat [1] - - # HSLA - HSLA: (value) -> @color 'HSLA', value, do -> - _match = value.match COLOR_REGEXES.HSLA - - return (Convert.hslToRgb [ - parseInt _match[1], 10 - parseInt _match[2], 10 - parseInt _match[3], 10 - ]).concat [parseFloat _match[4], 10] - HSLAArray: (value) -> @color 'HSLAArray', value, do -> - return (Convert.hslToRgb value).concat [value[3]] - - # HSV - HSV: (value) -> @color 'HSV', value, do -> - _match = value.match COLOR_REGEXES.HSV - - return (Convert.hsvToRgb [ - parseInt _match[1], 10 - parseInt _match[2], 10 - parseInt _match[3], 10 - ]).concat [1] - HSVArray: (value) -> @color 'HSVArray', value, do -> - return (Convert.hsvToRgb value).concat [1] - - # HSVA - HSVA: (value) -> @color 'HSVA', value, do -> - _match = value.match COLOR_REGEXES.HSVA - - return (Convert.hsvToRgb [ - parseInt _match[1], 10 - parseInt _match[2], 10 - parseInt _match[3], 10 - ]).concat [parseFloat _match[4], 10] - HSVAArray: (value) -> @color 'HSVAArray', value, do -> - return (Convert.hsvToRgb value).concat [value[3]] - - # VEC - VEC: (value) -> @color 'VEC', value, do -> - _match = value.match COLOR_REGEXES.VEC - - return (Convert.vecToRgb [ - (parseFloat _match[1], 10).toFixed 2 - (parseFloat _match[2], 10).toFixed 2 - (parseFloat _match[3], 10).toFixed 2 - ]).concat [1] - VECArray: (value) -> @color 'VECArray', value, do -> - return (Convert.vecToRgb value).concat [1] - - # VECA - VECA: (value) -> @color 'VECA', value, do -> - _match = value.match COLOR_REGEXES.VECA - - return (Convert.vecToRgb [ - (parseFloat _match[1], 10).toFixed 2 - (parseFloat _match[2], 10).toFixed 2 - (parseFloat _match[3], 10).toFixed 2 - ]).concat [parseFloat _match[4], 10] - VECAArray: (value) -> @color 'VECAArray', value, do -> - return (Convert.vecToRgb value).concat [value[3]] - - # HEX - HEX: (value) -> @color 'HEX', value, do -> - return (Convert.hexToRgb value).concat [1] - - # HEXA - HEXA: (value) -> @color 'HEXA', value, do -> - _match = value.match COLOR_REGEXES.HEXA - return (Convert.hexToRgb _match[1]).concat [parseFloat _match[2], 10] - } diff --git a/lib/modules/SmartVariable.coffee b/lib/modules/SmartVariable.coffee deleted file mode 100644 index f0521a6..0000000 --- a/lib/modules/SmartVariable.coffee +++ /dev/null @@ -1,180 +0,0 @@ -# ---------------------------------------------------------------------------- -# SmartVariable -# ---------------------------------------------------------------------------- - - module.exports = -> - path = require 'path' - - # ------------------------------------- - # Variable Types - # ------------------------------------- - VARIABLE_PATTERN = '\\{{ VARIABLE }}[\\s]*\\:[\\s]*([^\\;\\n]+)[\\;|\\n]' - - VARIABLE_TYPES = [ - # Matches Sass variable: eg. - # $color-var - { - type: 'sass' - extensions: ['.scss', '.sass'] - regExp: /([\$])([\w0-9-_]+)/i - } - - # Matches LESS variable: eg. - # @color-var - { - type: 'less' - extensions: ['.less'] - regExp: /([\@])([\w0-9-_]+)/i - } - - # Matches Stylus variable: eg. - # $color-var - { - type: 'stylus' - extensions: ['.stylus', '.styl'] - regExp: /([\$])([\w0-9-_]+)/i - } - ] - - # ------------------------------------- - # Definition storage - # ------------------------------------- - DEFINITIONS = {} - - # ------------------------------------- - # Public functionality - # ------------------------------------- - return { - # ------------------------------------- - # Find variables in string - # - string {String} - # - # @return String - # ------------------------------------- - find: (string, pathName) -> - SmartVariable = this - _variables = [] - - for {type, extensions, regExp} in VARIABLE_TYPES - _matches = string.match (new RegExp regExp.source, 'ig') - continue unless _matches - - # Make sure the file type matches possible extensions - if pathName - continue unless (path.extname pathName) in extensions - - for _match in _matches then do (type, extensions, _match) -> - return if (_index = string.indexOf _match) is -1 - - _variables.push - match: _match - type: type - extensions: extensions - start: _index - end: _index + _match.length - - getDefinition: -> SmartVariable.getDefinition this - isVariable: true - - # Remove the match from the line content string to - # “mark it” as having been “spent”. Be careful to keep the - # correct amount of characters in the string as this is - # later used to see which match fits best, if any - string = string.replace _match, (new Array _match.length + 1).join ' ' - return _variables - - # ------------------------------------- - # Find a variable definition in the project - # - name {String} - # - type {String} - # - # @return Promise - # ------------------------------------- - getDefinition: (variable, initial) -> - {match, type, extensions} = variable - - # Figure out what to look for - _regExp = new RegExp (VARIABLE_PATTERN.replace '{{ VARIABLE }}', match) - - # We already know where the definition is - if _definition = DEFINITIONS[match] - # Save initial pointer value, if it isn't set already - initial ?= _definition - _pointer = _definition.pointer - - # ... but check if it's still there - return atom.project.bufferForPath _pointer.filePath - .then (buffer) => - _text = buffer.getTextInRange _pointer.range - _match = _text.match _regExp - - # Definition not found, reset and try again - unless _match - DEFINITIONS[match] = null - return @getDefinition variable, initial - - # Definition found, save it on the DEFINITION object - _definition.value = _match[1] - - # ... but it might be another variable, in which - # case we must keep digging to find what we're after - _found = (@find _match[1], _pointer.filePath)[0] - - # Run the search again, but keep the initial pointer - if _found and _found.isVariable - return @getDefinition _found, initial - - return { - value: _definition.value - variable: _definition.variable - type: _definition.type - - pointer: initial.pointer - } - .catch (error) => console.error error - - # ... we don't know where the definition is - - # Figure out where to look - _options = paths: do -> - "**/*#{ _extension }" for _extension in extensions - _results = [] - - return atom.workspace.scan _regExp, _options, (result) -> - _results.push result - .then => - # Figure out what file is holding the definition - # Assume it's the one closest to the current path - _targetPath = atom.workspace.getActivePaneItem().getPath() - _targetFragments = _targetPath.split path.sep - - _bestMatch = null - _bestMatchHits = 0 - - for result in _results - _thisMatchHits = 0 - _pathFragments = result.filePath.split path.sep - _thisMatchHits++ for pathFragment, i in _pathFragments when pathFragment is _targetFragments[i] - - if _thisMatchHits > _bestMatchHits - _bestMatch = result - _bestMatchHits = _thisMatchHits - return unless _bestMatch and _match = _bestMatch.matches[0] - - # Save the definition on the DEFINITION object so that it - # can be accessed later - DEFINITIONS[match] = { - value: null - variable: match - type: type - - pointer: - filePath: _bestMatch.filePath - range: _match.range - } - - # Save initial pointer value, if it isn't set already - initial ?= DEFINITIONS[match] - return @getDefinition variable, initial - .catch (error) => console.error error - } diff --git a/package.json b/package.json index 1cd2cfc..788284a 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "color-picker", - "main": "./lib/ColorPicker", - "version": "2.1.0", + "main": "./lib/color-picker", + "version": "3.0.0", "private": true, - "description": "Right click or press CMD-SHIFT-C/CTRL-ALT-C to open it.", + "description": "Inspect and manipulate colors in Atom on the fly.", "repository": { "type": "git", "url": "https://github.com/thomaslindstrom/color-picker" @@ -15,7 +15,7 @@ ], "license": "MIT", "engines": { - "atom": ">=1.2.4" + "atom": ">=1.4.0" }, "readmeFilename": "README.md", "bugs": { diff --git a/spec/color-picker-spec.js b/spec/color-picker-spec.js new file mode 100644 index 0000000..79bf8be --- /dev/null +++ b/spec/color-picker-spec.js @@ -0,0 +1,11 @@ +/** @babel */ +// --------------------------------------------------------------------------- +// color-picker-spec.js +// --------------------------------------------------------------------------- + + describe('when the color-picker is activated', function () { + it('should be present in the DOM'); + it('should have event listeners in place'); + + // ... + });