├── .gitignore
├── screenshot.gif
├── spec
├── fixtures
│ ├── sample.sass
│ ├── sample.styl
│ ├── sample.less
│ ├── sample.scss
│ └── sample.css
├── spec-helper.coffee
├── bezier-curve-editor-spec.coffee
└── bezier-curve-editor-element-spec.coffee
├── resources
├── css-easing.woff
├── linear.svg
├── ease.svg
├── ease-in.svg
├── ease-in-out.svg
└── ease-out.svg
├── README.md
├── keymaps
└── bezier-curve-editor.cson
├── package.json
├── lib
├── bezier-timing-element.coffee
├── curve-control-element.coffee
├── bezier-functions.coffee
├── bezier-curve-editor.coffee
├── bezier-curve-editor-element.coffee
└── curve-element.coffee
├── LICENSE.md
├── styles
└── bezier-curve-editor.less
└── CHANGELOG.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | npm-debug.log
3 | node_modules
4 |
--------------------------------------------------------------------------------
/screenshot.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abe33/atom-bezier-curve-editor/HEAD/screenshot.gif
--------------------------------------------------------------------------------
/spec/fixtures/sample.sass:
--------------------------------------------------------------------------------
1 |
2 | .foo
3 | transition: all 0.3s cubic-bezier(0.35, 0.77, 0.62, 0.28)
4 |
--------------------------------------------------------------------------------
/spec/fixtures/sample.styl:
--------------------------------------------------------------------------------
1 |
2 | .foo
3 | transition all 0.3s cubic-bezier(0.35, 0.77, 0.62, 0.28)
4 |
--------------------------------------------------------------------------------
/spec/fixtures/sample.less:
--------------------------------------------------------------------------------
1 |
2 | .foo {
3 | transition: all 0.3s cubic-bezier(0.35, 0.77, 0.62, 0.28);
4 | }
5 |
--------------------------------------------------------------------------------
/spec/fixtures/sample.scss:
--------------------------------------------------------------------------------
1 |
2 | .foo {
3 | transition: all 0.3s cubic-bezier(0.35, 0.77, 0.62, 0.28);
4 | }
5 |
--------------------------------------------------------------------------------
/resources/css-easing.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abe33/atom-bezier-curve-editor/HEAD/resources/css-easing.woff
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bezier-curve-editor package
2 |
3 | Edit CSS Bezier curves directly in Atom. Either right click on a `cubic-bezier` declaration or trigger `ctrl + alt + B` when the cursor is in a declaration.
4 |
5 | 
6 |
--------------------------------------------------------------------------------
/keymaps/bezier-curve-editor.cson:
--------------------------------------------------------------------------------
1 | # Keybindings require three things to be fully defined: A selector that is
2 | # matched against the focused element, the keystroke and the command to
3 | # execute.
4 | #
5 | # Below is a basic keybinding which registers on all platforms by applying to
6 | # the root workspace element.
7 |
8 | # For more detailed documentation see
9 | # https://atom.io/docs/latest/advanced/keymaps
10 | 'atom-workspace':
11 | 'ctrl-alt-b': 'bezier-curve-editor:open'
12 |
--------------------------------------------------------------------------------
/spec/fixtures/sample.css:
--------------------------------------------------------------------------------
1 |
2 | .foo {
3 | transition: all 0.3s cubic-bezier(0.35, 0.77, 0.62, 0.28);
4 | }
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | .foo {
56 | transition: all 0.3s cubic-bezier(0.35, 0.77, 0.62, 0.28);
57 | }
58 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bezier-curve-editor",
3 | "main": "./lib/bezier-curve-editor",
4 | "version": "0.7.2",
5 | "description": "A bezier curve editor with common curves built-in.",
6 | "repository": "https://github.com/abe33/atom-bezier-curve-editor",
7 | "license": "MIT",
8 | "engines": {
9 | "atom": ">0.50.0"
10 | },
11 | "dependencies": {
12 | "delegato": "1.x",
13 | "mixto": "1.x",
14 | "emissary": "1.x",
15 | "prolix": "1.x",
16 | "atom-utils": "^0.5.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/spec/spec-helper.coffee:
--------------------------------------------------------------------------------
1 | module.exports =
2 | buildMouseEvent: (type, properties={}) ->
3 | defaults = {bubbles: true, cancelable: true}
4 | defaults[k] = v for k,v of properties
5 | properties.detail ?= 1
6 | event = new MouseEvent(type, properties)
7 | Object.defineProperty(event, 'which', get: -> properties.which) if properties.which?
8 | if properties.target?
9 | Object.defineProperty(event, 'target', get: -> properties.target)
10 | Object.defineProperty(event, 'srcObject', get: -> properties.target)
11 | event
12 |
--------------------------------------------------------------------------------
/resources/linear.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/resources/ease.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/resources/ease-in.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/resources/ease-in-out.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
--------------------------------------------------------------------------------
/resources/ease-out.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
--------------------------------------------------------------------------------
/lib/bezier-timing-element.coffee:
--------------------------------------------------------------------------------
1 | {SpacePenDSL, EventsDelegation} = require 'atom-utils'
2 |
3 | module.exports =
4 | class BezierTimingElement extends HTMLElement
5 | SpacePenDSL.includeInto(this)
6 |
7 | @content: ->
8 | @div class: 'bezier-timing-preview', =>
9 | @div class: 'bezier-timing-ramp', =>
10 | @div outlet: 'dummy', class: 'bezier-timing-dummy'
11 |
12 | duration: 500
13 |
14 | setSpline: (@spline...) -> @updateTiming()
15 |
16 | setDuration: (@duration) -> @updateTiming()
17 |
18 | updateTiming: ->
19 | @dummy.style.cssText = ''
20 | setTimeout =>
21 | @dummy.style.webkitAnimation = "preview #{@duration / 1000}s cubic-bezier(#{@spline.join ', '}) alternate both infinite"
22 | , 100
23 |
24 | getModel: -> {}
25 |
26 | module.exports = BezierTimingElement = document.registerElement 'bezier-curve-timing', prototype: BezierTimingElement.prototype
27 |
--------------------------------------------------------------------------------
/spec/bezier-curve-editor-spec.coffee:
--------------------------------------------------------------------------------
1 | BezierCurveEditor = require '../lib/bezier-curve-editor'
2 |
3 | describe "BezierCurveEditor", ->
4 | [workspaceElement, editor, editorView] = []
5 |
6 | afterEach -> BezierCurveEditor.deactivate()
7 |
8 | beforeEach ->
9 | waitsForPromise -> atom.workspace.open('sample.js')
10 |
11 | runs ->
12 | workspaceElement = atom.views.getView(atom.workspace)
13 | editor = atom.workspace.getActiveTextEditor()
14 | editorView = atom.views.getView(editor)
15 |
16 | editor.setText("cubic-bezier(0.3, 0, 0.7, 1)")
17 | editor.setCursorBufferPosition([4,0])
18 |
19 | waitsForPromise ->
20 | atom.packages.activatePackage('bezier-curve-editor')
21 |
22 | describe 'triggering the open command', ->
23 | beforeEach ->
24 | runs ->
25 | atom.commands.dispatch workspaceElement, 'bezier-curve-editor:open'
26 |
27 | it 'should have opened the view', ->
28 | expect(editorView.querySelector('.bezier-curve-editor')).toBeDefined()
29 |
--------------------------------------------------------------------------------
/spec/bezier-curve-editor-element-spec.coffee:
--------------------------------------------------------------------------------
1 | {buildMouseEvent} = require './spec-helper'
2 |
3 | describe "BezierCurveEditorElement", ->
4 | [workspaceElement, editor, editorView] = []
5 |
6 | beforeEach ->
7 | waitsForPromise -> atom.workspace.open('sample.js')
8 |
9 | runs ->
10 | workspaceElement = atom.views.getView(atom.workspace)
11 | editor = atom.workspace.getActiveTextEditor()
12 | editorView = atom.views.getView(editor)
13 |
14 | editor.setText("cubic-bezier(0.3, 0, 0.7, 1)")
15 | editor.setCursorBufferPosition([4,0])
16 |
17 | waitsForPromise ->
18 | atom.packages.activatePackage('bezier-curve-editor')
19 |
20 | runs ->
21 | atom.commands.dispatch(editorView, 'bezier-curve-editor:open')
22 |
23 | describe 'clicking outside the panel', ->
24 | beforeEach ->
25 | runs ->
26 | editorView.dispatchEvent(buildMouseEvent('mousedown'))
27 |
28 | it 'should have removed the view', ->
29 | expect(editorView.querySelector('bezier-curve-editor')).toBeDefined()
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014
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 |
--------------------------------------------------------------------------------
/lib/curve-control-element.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable, Emitter} = require 'atom'
2 | {EventsDelegation} = require 'atom-utils'
3 |
4 | module.exports =
5 | class CurveControlElement extends HTMLElement
6 | EventsDelegation.includeInto(this)
7 |
8 | createdCallback: ->
9 | @emitter = new Emitter
10 | @subscriptions = new CompositeDisposable
11 |
12 | initialize: ({@minX, @maxX, @minY, @maxY}) ->
13 |
14 | onDidStartDrag: (callback) ->
15 | @emitter.on 'did-start-drag', callback
16 |
17 | onDidDrag: (callback) ->
18 | @emitter.on 'did-drag', callback
19 |
20 | onDidEndDrag: (callback) ->
21 | @emitter.on 'did-end-drag', callback
22 |
23 | activate: ->
24 | @subscriptions.add @subscribeTo this, 'mousedown': (e) =>
25 | @onMouseDown(e)
26 |
27 | @subscriptions.add @subscribeTo document.body,
28 | 'mousemove': (e) => @onMouseMove(e)
29 | 'mouseup': (e) => @onMouseUp(e)
30 |
31 | deactivate: ->
32 | @subscriptions.dispose()
33 |
34 | destroy: ->
35 | @deactivate()
36 | @detach()
37 |
38 | onMouseDown: (e) ->
39 | @dragging = true
40 | @emitter.emit('did-start-drag', @getMousePosition(e))
41 |
42 | onMouseMove: (e) ->
43 | if @dragging
44 | @emitter.emit('did-drag', @getMousePosition(e))
45 |
46 | onMouseUp: (e) ->
47 | @emitter.emit('did-end-drag', @getMousePosition(e)) if @dragging
48 | @dragging = false
49 |
50 | getMousePosition: (e) ->
51 | offset = @parentNode.getBoundingClientRect()
52 | x = e.pageX - offset.left
53 | y = e.pageY - offset.top
54 | difX = @maxX - @minX
55 | difY = @maxY - @minY
56 |
57 | x = Math.max(@minX, Math.min(@maxX, x))
58 |
59 | {
60 | x: (x - @minX) / difX
61 | y: 1 - (y - @minY) / difY
62 | }
63 |
64 | module.exports = CurveControlElement = document.registerElement 'bezier-curve-control', prototype: CurveControlElement.prototype
65 |
--------------------------------------------------------------------------------
/lib/bezier-functions.coffee:
--------------------------------------------------------------------------------
1 | class module.exports.UnitBezier
2 | constructor: (p1x, p1y, p2x, p2y) ->
3 |
4 | # pre-calculate the polynomial coefficients
5 | # First and last control points are implied to be (0,0) and (1.0, 1.0)
6 | @cx = 3.0 * p1x
7 | @bx = 3.0 * (p2x - p1x) - @cx
8 | @ax = 1.0 - @cx - @bx
9 | @cy = 3.0 * p1y
10 | @by = 3.0 * (p2y - p1y) - @cy
11 | @ay = 1.0 - @cy - @by
12 | epsilon: 1e-6 # Precision
13 | sampleCurveX: (t) ->
14 | ((@ax * t + @bx) * t + @cx) * t
15 |
16 | sampleCurveY: (t) ->
17 | ((@ay * t + @by) * t + @cy) * t
18 |
19 | sampleCurveDerivativeX: (t) ->
20 | (3.0 * @ax * t + 2.0 * @bx) * t + @cx
21 |
22 | solveCurveX: (x, epsilon) ->
23 | t0 = undefined
24 | t1 = undefined
25 | t2 = undefined
26 | x2 = undefined
27 | d2 = undefined
28 | i = undefined
29 |
30 | # First try a few iterations of Newton's method -- normally very fast.
31 | t2 = x
32 | i = 0
33 |
34 | while i < 8
35 | x2 = @sampleCurveX(t2) - x
36 | return t2 if Math.abs(x2) < epsilon
37 | d2 = @sampleCurveDerivativeX(t2)
38 | break if Math.abs(d2) < epsilon
39 | t2 = t2 - x2 / d2
40 | i++
41 |
42 | # No solution found - use bi-section
43 | t0 = 0.0
44 | t1 = 1.0
45 | t2 = x
46 | return t0 if t2 < t0
47 | return t1 if t2 > t1
48 | while t0 < t1
49 | x2 = @sampleCurveX(t2)
50 | return t2 if Math.abs(x2 - x) < epsilon
51 | if x > x2
52 | t0 = t2
53 | else
54 | t1 = t2
55 | t2 = (t1 - t0) * .5 + t0
56 |
57 | # Give up
58 | t2
59 |
60 |
61 | # Find new T as a function of Y along curve X
62 | solve: (x, epsilon) ->
63 | @sampleCurveY @solveCurveX(x, epsilon)
64 |
65 | module.exports.easing =
66 | ease: [0.25, 0.1, 0.25, 1.0]
67 | linear: [0.00, 0.0, 1.00, 1.0]
68 | ease_in: [0.42, 0.0, 1.00, 1.0]
69 | ease_out: [0.00, 0.0, 0.58, 1.0]
70 | ease_in_out: [0.42, 0.0, 0.58, 1.0]
71 |
72 | module.exports.extraEasing =
73 | cubic:
74 | ease_in: [0.550, 0.055, 0.675, 0.190]
75 | ease_out: [0.215, 0.610, 0.355, 1.000]
76 | ease_in_out: [0.645, 0.045, 0.355, 1.000]
77 |
78 | circ:
79 | ease_in: [0.600, 0.040, 0.980, 0.335]
80 | ease_out: [0.075, 0.820, 0.165, 1.000]
81 | ease_in_out: [0.785, 0.135, 0.150, 0.860]
82 |
83 | expo:
84 | ease_in: [0.950, 0.050, 0.795, 0.035]
85 | ease_out: [0.190, 1.000, 0.220, 1.000]
86 | ease_in_out: [1.000, 0.000, 0.000, 1.000]
87 |
88 | quad:
89 | ease_in: [0.550, 0.085, 0.680, 0.530]
90 | ease_out: [0.250, 0.460, 0.450, 0.940]
91 | ease_in_out: [0.455, 0.030, 0.515, 0.955]
92 |
93 | quart:
94 | ease_in: [0.895, 0.030, 0.685, 0.220]
95 | ease_out: [0.165, 0.840, 0.440, 1.000]
96 | ease_in_out: [0.770, 0.000, 0.175, 1.000]
97 |
98 | quint:
99 | ease_in: [0.755, 0.050, 0.855, 0.060]
100 | ease_out: [0.230, 1.000, 0.320, 1.000]
101 | ease_in_out: [0.860, 0.000, 0.070, 1.000]
102 |
103 | sine:
104 | ease_in: [0.470, 0.000, 0.745, 0.715]
105 | ease_out: [0.390, 0.575, 0.565, 1.000]
106 | ease_in_out: [0.445, 0.050, 0.550, 0.950]
107 |
108 | back:
109 | ease_in: [0.600, -0.280, 0.735, 0.045]
110 | ease_out: [0.175, 0.885, 0.320, 1.275]
111 | ease_in_out: [0.680, -0.550, 0.265, 1.550]
112 |
--------------------------------------------------------------------------------
/styles/bezier-curve-editor.less:
--------------------------------------------------------------------------------
1 |
2 | @import "ui-variables";
3 |
4 | @font-face {
5 | font-family: 'css-easing';
6 | src:url('atom://bezier-curve-editor/resources/css-easing.woff') format('woff');
7 | font-weight: normal;
8 | font-style: normal;
9 | }
10 |
11 | @-webkit-keyframes preview {
12 | 0% {
13 | -webkit-transform: translate(0,0);
14 | }
15 | 100% {
16 | -webkit-transform: translate(90%,0);
17 | }
18 | }
19 |
20 | [class^="easing-"], [class*=" easing-"] {
21 | font-family: 'css-easing';
22 | speak: none;
23 | font-style: normal;
24 | font-weight: normal;
25 | font-variant: normal;
26 | text-transform: none;
27 | line-height: 1;
28 |
29 | -webkit-font-smoothing: antialiased;
30 | }
31 |
32 | .easing-linear:before {
33 | content: "\e600";
34 | }
35 | .easing-ease:before {
36 | content: "\e601";
37 | }
38 | .easing-ease-out:before {
39 | content: "\e602";
40 | }
41 | .easing-ease-in:before {
42 | content: "\e603";
43 | }
44 | .easing-ease-in-out:before {
45 | content: "\e604";
46 | }
47 |
48 |
49 | bezier-curve-editor {
50 | position: absolute;
51 | display: block;
52 | width: auto !important;
53 | padding: 0;
54 | margin-left: 0 !important;
55 | background: @app-background-color !important;
56 | -webkit-filter: drop-shadow(0 0 1px rgba(255, 255, 255, 0.07));
57 | padding: 5px;
58 | z-index: 2;
59 |
60 | .block {
61 | // clear for top-margin on the select element
62 | padding: 1px 0 0 0;
63 | clear: both;
64 | }
65 |
66 | select.form-control {
67 | width: 100%;
68 | margin: 4px 0 0;
69 | background: @input-background-color;
70 | border: 1px solid @input-border-color;
71 | color: @text-color;
72 | font-family: @font-family;
73 | }
74 |
75 | .btn {
76 | font-family: 'Lucida Grande', 'Segoe UI', sans-serif;
77 | }
78 |
79 | .btn-group {
80 | &:after {
81 | content: '';
82 | display: table;
83 | clear: both;
84 | }
85 |
86 | .btn-icon {
87 | height: 28px;
88 | padding: 3px 0 0;
89 | }
90 | }
91 |
92 | bezier-curve {
93 | position: relative;
94 | display: block;
95 | }
96 |
97 | bezier-curve-timing {
98 | display: block;
99 | margin: 0 4px;
100 |
101 | .bezier-timing-ramp {
102 | background: @base-background-color;
103 | border: 1px solid @base-border-color;
104 | height: 8px;
105 | box-sizing: border-box;
106 |
107 | .bezier-timing-dummy {
108 | height: 100%;
109 |
110 | &:before {
111 | content: '';
112 | display: block;
113 | width: 10%;
114 | height: 100%;
115 | background: red;
116 | }
117 | }
118 |
119 | }
120 | }
121 |
122 | .patterns {
123 | display: block;
124 | margin: 4px;
125 | overflow: hidden;
126 | .btn-icon {
127 | width: 20%;
128 |
129 | & + .btn-icon {
130 | width: -webkit-calc(~'20% + 1px');
131 | }
132 |
133 | i {
134 | font-size: 20px;
135 | font-weight: bold;
136 | color: red;
137 | border: 1px solid @text-color-subtle;
138 | }
139 | }
140 | }
141 |
142 | .actions {
143 | display: block;
144 | position: relative;
145 | z-index: 2;
146 | .btn {
147 | width: 50%;
148 | }
149 | }
150 |
151 | bezier-curve-control {
152 | position: absolute;
153 | width: 10px;
154 | height: 10px;
155 | display: block;
156 | -webkit-transform: translate(-50%, -50%);
157 | background: @text-color-highlight;
158 | border-radius: 50%;
159 | z-index: 3;
160 | cursor: -webkit-grab;
161 |
162 | &:active {
163 | cursor: -webkit-grabbing;
164 | }
165 | }
166 |
167 | canvas {
168 | width: 200px;
169 | height: 200px;
170 | display: block;
171 | position: relative;
172 | z-index: 2;
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/lib/bezier-curve-editor.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable, Disposable} = require 'atom'
2 | BezierCurveEditorElement = require './bezier-curve-editor-element'
3 |
4 | module.exports=
5 | view: null
6 | match: null
7 |
8 | activate: ->
9 | @subscriptions = new CompositeDisposable
10 | @subscriptions.add atom.commands.add 'atom-workspace',
11 | 'bezier-curve-editor:open': => @open true
12 |
13 | @subscriptions.add atom.contextMenu.add '.editor': [{
14 | label: 'Edit Bezier Curve',
15 | command: 'bezier-curve-editor:open',
16 | shouldDisplay: (event) =>
17 | return true if @match = @getMatchAtCursor()
18 | }]
19 |
20 | @view = new BezierCurveEditorElement
21 |
22 | @subscriptions.add @view.onDidCancel => @view.close()
23 | @subscriptions.add @view.onDidConfirm =>
24 | spline = @view.getSpline()
25 | @replaceMatch(spline)
26 | @view.close()
27 |
28 | deactivate: ->
29 | @view.destroy()
30 | @subscriptions.dispose()
31 |
32 | getMatchAtCursor: ->
33 | editor = atom.workspace.getActiveTextEditor()
34 | return unless editor?
35 |
36 | line = editor.getLastCursor().getCurrentBufferLine()
37 | cursorBuffer = editor.getCursorBufferPosition()
38 | cursorRow = cursorBuffer.row
39 | cursorColumn = cursorBuffer.column
40 |
41 | matches = @matchesOnLine(line, cursorRow)
42 | matchAtPos = @matchAtPosition cursorColumn, matches
43 |
44 | return matchAtPos
45 |
46 | matchesOnLine: (line, cursorRow) ->
47 | float = '(\\d+|\\d?\\.\\d+)'
48 | regex = ///
49 | cubic-bezier\s*
50 | \(\s*
51 | (#{float})
52 | \s*,\s*
53 | (-?#{float})
54 | \s*,\s*
55 | (#{float})
56 | \s*,\s*
57 | (-?#{float})
58 | \s*\)
59 | ///g
60 |
61 | filteredMatches = []
62 | matches = line.match regex
63 |
64 | return unless matches?
65 |
66 | for match in matches
67 | continue if (index = line.indexOf match) is -1
68 |
69 | filteredMatches.push
70 | match: match
71 | regexMatch: match.match RegExp regex.source, 'i'
72 | index: index
73 | end: index + match.length
74 | row: cursorRow
75 |
76 | line = line.replace match, (Array match.length + 1).join ' '
77 |
78 | return unless filteredMatches.length
79 | filteredMatches
80 |
81 | matchAtPosition: (column, matches) ->
82 | return unless column and matches
83 |
84 | for match in matches
85 | if match.index <= column and match.end >= column
86 | matchResults = match
87 | break
88 |
89 | return matchResults
90 |
91 | selectMatch: ->
92 | editor = atom.workspace.getActiveTextEditor()
93 |
94 | editor.clearSelections()
95 | editor.addSelectionForBufferRange
96 | start:
97 | column: @match.index
98 | row: @match.row
99 | end:
100 | column: @match.end
101 | row: @match.row
102 |
103 | replaceMatch: (spline) ->
104 | return unless @match?
105 |
106 | editor = atom.workspace.getActiveTextEditor()
107 | splineCSS = @getSplineCSS(spline)
108 | editor.replaceSelectedText null, -> splineCSS
109 |
110 | editor.clearSelections()
111 | editor.addSelectionForBufferRange
112 | start:
113 | column: @match.index
114 | row: @match.row
115 | end:
116 | column: @match.index + splineCSS.length
117 | row: @match.row
118 |
119 | getSplineCSS: (spline) ->
120 | precision = 100000
121 | spline = spline.map (n) -> Math.floor(n * precision) / precision
122 |
123 | "cubic-bezier(#{ spline.join ', ' })"
124 |
125 | open: (getMatch = false) ->
126 |
127 | @match = @getMatchAtCursor() if getMatch
128 |
129 | return unless @match?
130 |
131 | [m, a1, _, a2, _, a3, _, a4] = @match.regexMatch
132 | [a1, a2, a3, a4] = [
133 | parseFloat a1
134 | parseFloat a2
135 | parseFloat a3
136 | parseFloat a4
137 | ]
138 | @selectMatch()
139 |
140 | @view.setSpline(a1, a2, a3, a4)
141 | @view.renderSpline()
142 | @view.open()
143 |
--------------------------------------------------------------------------------
/lib/bezier-curve-editor-element.coffee:
--------------------------------------------------------------------------------
1 | {SpacePenDSL, EventsDelegation, AncestorsMethods} = require 'atom-utils'
2 | {CompositeDisposable, Emitter} = require 'atom'
3 | Delegator = require 'delegato'
4 | CurveElement = require './curve-element'
5 | BezierTimingElement = require './bezier-timing-element'
6 | {easing, extraEasing} = require './bezier-functions'
7 |
8 | humanize = (str) ->
9 | str
10 | .split('_')
11 | .map((s) -> s.replace(/^./, (m) -> m.toUpperCase()))
12 | .join(' ')
13 |
14 | module.exports =
15 | class BezierCurveEditorElement extends HTMLElement
16 | Delegator.includeInto(this)
17 | SpacePenDSL.includeInto(this)
18 | EventsDelegation.includeInto(this)
19 |
20 | @content: ->
21 | @div =>
22 | @tag 'bezier-curve', outlet: 'curveView'
23 | @tag 'bezier-curve-timing', outlet: 'timingView'
24 |
25 | @div class: 'patterns btn-group', =>
26 | for name,spline of easing
27 | @button outlet: name, class: 'btn btn-icon btn-sm', =>
28 | @i class: 'easing-' + name.replace(/_/g, '-')
29 |
30 | @div class: 'block', =>
31 | @tag 'select', class: 'form-control', outlet: 'easingSelect', =>
32 | @tag 'option', value: '', 'More easings...'
33 |
34 | for group, splines of extraEasing
35 | @tag 'optgroup', label: humanize(group), =>
36 | for name, spline of splines
37 | @tag 'option', value: group + ':' + name, humanize(group + ' ' + name)
38 |
39 | @div class: 'actions btn-group', =>
40 | @button outlet: 'cancelButton', class: 'btn', 'Cancel'
41 | @button outlet: 'validateButton', class: 'btn', 'Validate'
42 |
43 | @delegatesMethods 'getSpline', 'setSpline', 'renderSpline', toProperty: 'curveView'
44 |
45 | createdCallback: ->
46 | @classList.add 'overlay'
47 | @classList.add 'native-key-bindings'
48 | @setAttribute('tabindex', -1)
49 |
50 | @subscriptions = new CompositeDisposable
51 | @emitter = new Emitter
52 |
53 | @subscriptions.add @curveView.onDidChangeSpline =>
54 | @timingView.setSpline @curveView.getSpline()
55 |
56 | @subscriptions.add @subscribeTo @easingSelect, 'change': =>
57 | value = @easingSelect.value
58 | if value isnt ''
59 | [group, name] = value.split(':')
60 |
61 | @setSpline.apply this, extraEasing[group][name]
62 | @timingView.setSpline extraEasing[group][name]
63 | @renderSpline()
64 |
65 | @subscriptions.add @subscribeTo @cancelButton, 'click': =>
66 | @emitter.emit('did-cancel')
67 |
68 | @subscriptions.add @subscribeTo @validateButton, 'click': =>
69 | @emitter.emit('did-confirm')
70 |
71 | Object.keys(easing).forEach (name) =>
72 | button = @[name]
73 | # button.setTooltip(name.replace /_/g, '-')
74 | @subscriptions.add @subscribeTo button, 'click': =>
75 | @setSpline.apply this, easing[name]
76 | @timingView.setSpline easing[name]
77 | @renderSpline()
78 |
79 | onDidCancel: (callback) ->
80 | @emitter.on 'did-cancel', callback
81 |
82 | onDidConfirm: (callback) ->
83 | @emitter.on 'did-confirm', callback
84 |
85 | open: ->
86 | @attach()
87 |
88 | @subscribeToOutsideEvent()
89 |
90 | @timingView.setSpline @getSpline()
91 |
92 | @curveView.dummy1.activate()
93 | @curveView.dummy2.activate()
94 |
95 | subscribeToOutsideEvent: ->
96 | @subscriptions.add @subscribeTo this, 'mousedown': (e) ->
97 | e.stopImmediatePropagation()
98 | @subscriptions.add @subscribeTo document.body, 'mousedown': (e) =>
99 | @closeIfClickedOutside(e)
100 |
101 | closeIfClickedOutside: (e) ->
102 | if AncestorsMethods.parents(e.target, '.bezier-curve-editor').length is 0
103 | @close()
104 |
105 | attach: ->
106 | editor = @getActiveEditor()
107 | return if @active or not editor?
108 | @destroyOverlay()
109 |
110 | if marker = editor.getLastSelection()?.marker
111 | @overlayDecoration = editor.decorateMarker(marker, {type: 'overlay', item: this, position: 'tail'})
112 | @active = true
113 |
114 | getActiveEditor: -> atom.workspace.getActiveTextEditor()
115 |
116 | close: ->
117 | @detach()
118 | @curveView.dummy1.deactivate()
119 | @curveView.dummy2.deactivate()
120 |
121 | destroyOverlay: ->
122 | @active = false
123 | @overlayDecoration?.destroy()
124 |
125 | detach: ->
126 | @destroyOverlay()
127 |
128 | destroy: ->
129 | @close()
130 |
131 | module.exports = BezierCurveEditorElement = document.registerElement 'bezier-curve-editor', prototype: BezierCurveEditorElement.prototype
132 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | # v0.7.2 (2015-06-04)
3 |
4 | ## :bug: Bug Fixes
5 |
6 | - Fix broken overlay since v0.205.0 ([bdd658ee](https://github.com/abe33/atom-bezier-curve-editor/commit/bdd658ee5d3b6d6e930d85536ffb20fc9aa5c48f))
7 |
8 |
9 | # v0.7.1 (2015-05-28)
10 |
11 | ## :bug: Bug Fixes
12 |
13 | - Fix latest deprecations ([7f63d91c](https://github.com/abe33/atom-bezier-curve-editor/commit/7f63d91c933086dc1fd85d4d35da9600711747bc), [#10](https://github.com/abe33/atom-bezier-curve-editor/issues/10))
14 |
15 |
16 | # v0.7.0 (2015-05-20)
17 |
18 | ## :sparkles: Features
19 |
20 | - Implement all views as custom elements ([ccb867b4](https://github.com/abe33/atom-bezier-curve-editor/commit/ccb867b4041793d7a489ddd47e34d65665f07846))
21 |
22 |
23 | # 0.6.6 (2015-03-05)
24 |
25 | ## :bug: Bug Fixes
26 |
27 | - Fix positioning issues ([a77686db](https://github.com/abe33/atom-bezier-curve-editor/commit/a77686db867aaee800667584c7703425f109e3b2))
28 |
29 |
30 | # v0.6.5 (2015-02-06)
31 |
32 | ## :bug: Bug Fixes
33 |
34 | - Fix remaining deprecations
35 |
36 |
37 | # v0.6.4 (2015-01-26)
38 |
39 | ## :bug: Bug Fixes
40 |
41 | - Fix selectors deprecations ([adbbdbf4](https://github.com/abe33/atom-bezier-curve-editor/commit/adbbdbf4bd65c6a7744a1e17f23933e34eb4db5c))
42 |
43 |
44 |
45 | # v0.6.3 (2015-01-26)
46 |
47 | ## :bug: Bug Fixes
48 |
49 | - Fix deprecations ([95608d01](https://github.com/abe33/atom-bezier-curve-editor/commit/95608d01008bed4038bca17440954482a91e0168))
50 |
51 |
52 |
53 | # v0.6.2 (2014-12-08)
54 |
55 | ## :bug: Bug Fixes
56 |
57 | - Fix bad redraw of canvas background ([cdd5dbea](https://github.com/abe33/atom-bezier-curve-editor/commit/cdd5dbeae9e7fda02afbd4f3d35b9b2863332ebe))
58 | - Fix deprecations and broken API access.
59 |
60 |
61 | # v0.6.1 (2014-10-09)
62 |
63 | ## :bug: Bug Fixes
64 |
65 | - Fix all deprecations ([cc507c6c](https://github.com/abe33/atom-bezier-curve-editor/commit/cc507c6c26c6639eac7068f09d9dc8d2e91abae9))
66 | - Fix issue with floats without numerator ([289f9dc2](https://github.com/abe33/atom-bezier-curve-editor/commit/289f9dc2ec1f639cf9cb013f21374397f443d1d1), [#5](https://github.com/abe33/atom-bezier-curve-editor/issues/5))
67 |
68 |
69 | # v0.6.0 (2014-10-01)
70 |
71 | ## :sparkles: Features
72 |
73 | - Implement support for the new context menu shouldDisplay option ([96880e80](https://github.com/abe33/atom-bezier-curve-editor/commit/96880e802fc374db892abcf3b05d6d614905701d))
74 |
75 |
76 | # v0.5.0 (2014-05-29)
77 |
78 | ## :sparkles: Features
79 |
80 | - Add extra easing curves presets ([fe74b2b8](https://github.com/abe33/atom-bezier-curve-editor/commit/fe74b2b852baaa9aa18d7606cba85863367bf1f2), [#2](https://github.com/abe33/atom-bezier-curve-editor/issues/2))
81 |
Includes:
82 | - Cubic
83 | - Circ
84 | - Expo
85 | - Quad
86 | - Quart
87 | - Quint
88 | - Sine
89 | - Back
90 |
91 | ## :bug: Bug Fixes
92 |
93 | - Fix placement of the popup when editor content is small ([244e7a25](https://github.com/abe33/atom-bezier-curve-editor/commit/244e7a256f5cfce45ace3fcf3d20a1b47869df4d), [#1](https://github.com/abe33/atom-bezier-curve-editor/issues/1))
94 | - Fix typo leading to error in conditional context menu ([27c78c52](https://github.com/abe33/atom-bezier-curve-editor/commit/27c78c524daad005dc2f0666ed45848f139ff9ed))
95 |
96 |
97 | # v0.4.0 (2014-05-29)
98 |
99 | ## :bug: Bug Fixes
100 |
101 | - Fixes weird behavior when using mouse to select text ([8baa136](https://github.com/abe33/atom-bezier-curve-editor/commit/8baa136cb134f05a129b209fc19aae2f2785c9ff))
102 |
103 |
104 | # v0.3.0 (2014-05-29)
105 |
106 | ## :sparkles: Features
107 |
108 | - Adds an animated preview of the timing function ([f985edb0](https://github.com/abe33/atom-bezier-curve-editor/commit/f985edb060d743fad1faad88c3489b89036911fc))
109 |
110 |
111 |
112 | # v0.2.0 (2014-05-29)
113 |
114 | ## :sparkles: Features
115 |
116 | - Adds proper icons for presets buttons ([e2169bdc](https://github.com/abe33/atom-bezier-curve-editor/commit/e2169bdcb1fcbe513a8c446f71788e6c5143e63b))
117 | - Adds buttons to set spline using presets ([fa767067](https://github.com/abe33/atom-bezier-curve-editor/commit/fa7670674da77c9f76c385acc68b04ae9276b309))
118 |
The UI is quite broken though
119 | - Adds a getModel method on the view ([21cbcad6](https://github.com/abe33/atom-bezier-curve-editor/commit/21cbcad6ff9f61ebc5796d37c40bd37e9d50cfef))
120 |
Not used, but seems to be required for views in the editor.
121 |
--------------------------------------------------------------------------------
/lib/curve-element.coffee:
--------------------------------------------------------------------------------
1 | {CompositeDisposable, Emitter} = require 'atom'
2 | {SpacePenDSL, AncestorsMethods} = require 'atom-utils'
3 | {UnitBezier} = require './bezier-functions'
4 | CurveControlElement = require './curve-control-element'
5 |
6 | module.exports =
7 | class CurveElement extends HTMLElement
8 | SpacePenDSL.includeInto(this)
9 |
10 | canvasWidth = 200
11 | canvasHeight = 200
12 | canvasPadding = 50
13 |
14 | @content: ->
15 | @div class: 'curve-view', =>
16 | @tag 'canvas', {
17 | outlet: 'canvas'
18 | id: 'curve-canvas'
19 | width: canvasWidth
20 | height: canvasHeight
21 | }
22 |
23 | @tag 'bezier-curve-control', outlet: 'dummy1'
24 | @tag 'bezier-curve-control', outlet: 'dummy2'
25 |
26 | createdCallback: ->
27 | @subscriptions = new CompositeDisposable
28 | @emitter = new Emitter
29 |
30 | @context = @canvas.getContext('2d')
31 | @canvasWidth = canvasWidth
32 | @canvasHeight = canvasHeight
33 | @canvasPadding = canvasPadding
34 | @canvasWidthMinusPadding = @canvasWidth - @canvasPadding
35 | @canvasHeightMinusPadding = @canvasHeight - @canvasPadding
36 | @curveSpaceWidth = @canvasWidth - @canvasPadding * 2
37 | @curveSpaceHeight = @canvasHeight - @canvasPadding * 2
38 | @cleanCanvas()
39 |
40 | minX = @canvasPadding
41 | maxX = @canvasWidth - @canvasPadding
42 | minY = @canvasPadding
43 | maxY = @canvasHeight - @canvasPadding
44 |
45 | @dummy1.initialize {minX, maxX, minY, maxY}
46 | @dummy2.initialize {minX, maxX, minY, maxY}
47 |
48 | @subscriptions.add @dummy1.onDidDrag (e) => @control1Changed(e)
49 | @subscriptions.add @dummy2.onDidDrag (e) => @control2Changed(e)
50 |
51 | @dummy1.onDidEndDrag => @emitter.emit 'did-change-spline'
52 | @dummy2.onDidEndDrag => @emitter.emit 'did-change-spline'
53 |
54 | onDidChangeSpline: (callback) ->
55 | @emitter.on 'did-change-spline', callback
56 |
57 | getSpline: -> [@x1, @y1, @x2, @y2]
58 |
59 | setSpline: (@x1, @y1, @x2, @y2) ->
60 |
61 | cleanCanvas: ->
62 | styles = getComputedStyle(AncestorsMethods.parents(this, 'bezier-curve-editor')[0])
63 | @context.fillStyle = styles.backgroundColor
64 | @context.fillRect(0, 0, @canvasWidth, @canvasWidth)
65 |
66 | @context.strokeStyle = styles.color
67 | @context.beginPath()
68 | @context.moveTo(@canvasPadding, @canvasPadding)
69 | @context.lineTo(@canvasPadding, @canvasHeightMinusPadding)
70 | @context.lineTo(@canvasWidthMinusPadding, @canvasHeightMinusPadding)
71 | @context.lineTo(@canvasWidthMinusPadding, @canvasPadding)
72 | @context.lineTo(@canvasPadding, @canvasPadding)
73 | @context.stroke()
74 |
75 | updateDummies: ->
76 | @dummy1.style.top = (@canvasHeightMinusPadding - @y1 * @curveSpaceHeight) + 'px'
77 | @dummy1.style.left = (@canvasPadding + @x1 * @curveSpaceWidth) + 'px'
78 |
79 | @dummy2.style.top = (@canvasHeightMinusPadding - @y2 * @curveSpaceHeight) + 'px'
80 | @dummy2.style.left = (@canvasPadding + @x2 * @curveSpaceWidth) + 'px'
81 |
82 | renderControls: ->
83 | panel = document.querySelector('atom-panel')
84 | return unless panel?
85 |
86 | @context.strokeStyle = getComputedStyle(panel).color
87 |
88 | @context.beginPath()
89 | @context.moveTo(@canvasPadding, @canvasHeightMinusPadding)
90 | @context.lineTo(
91 | @canvasPadding + @x1 * @curveSpaceWidth,
92 | @canvasHeightMinusPadding - @y1 * @curveSpaceHeight
93 | )
94 | @context.stroke()
95 |
96 | @context.beginPath()
97 | @context.moveTo(@canvasWidthMinusPadding, @canvasPadding)
98 | @context.lineTo(
99 | @canvasPadding + @x2 * @curveSpaceWidth,
100 | @canvasHeightMinusPadding - @y2 * @curveSpaceHeight
101 | )
102 | @context.stroke()
103 |
104 | renderSpline: ->
105 | requestAnimationFrame =>
106 | @cleanCanvas()
107 | @updateDummies()
108 | @renderControls()
109 |
110 | @context.strokeStyle = '#ff0000'
111 | @context.beginPath()
112 | @context.moveTo(@canvasPadding, @canvasHeightMinusPadding)
113 |
114 | spline = new UnitBezier @x1, @y1, @x2, @y2
115 |
116 | for n in [0..50]
117 | r = n / 50
118 | x = @canvasPadding + r * @curveSpaceWidth
119 | curveY = spline.solve(r, spline.epsilon)
120 | y = @canvasHeightMinusPadding - curveY * @curveSpaceHeight
121 | @context.lineTo(x, y)
122 |
123 | @context.stroke()
124 |
125 | control1Changed: ({x, y}) =>
126 | @x1 = x
127 | @y1 = y
128 | @renderSpline()
129 |
130 | control2Changed: ({x, y}) =>
131 | @x2 = x
132 | @y2 = y
133 | @renderSpline()
134 |
135 | module.exports = CurveElement = document.registerElement 'bezier-curve', prototype: CurveElement.prototype
136 |
--------------------------------------------------------------------------------