4 |
5 |
6 |
7 |
8 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/snippet.coffee:
--------------------------------------------------------------------------------
1 | # To see a full working demo of the DesignComponent module, please download the demo file at:
2 |
3 | # https://github.com/sebcglbailey/framer-DesignComponents/tree/master/Demo.framer
4 |
5 | # The demo file includes some pre-made components, and shows you how to edit and use the Design Components
6 |
7 |
8 | ###
9 |
10 | ---------------------------------------
11 | REQUIRING THE MODULE
12 | ---------------------------------------
13 |
14 | The usual line of code for requiring the module will be:
15 |
16 | Design = require "DesignComponents"
17 |
18 | ###
19 |
20 |
21 |
22 | ###
23 |
24 | ---------------------------------------
25 | NAMING CONVENTIONS
26 | ---------------------------------------
27 |
28 | For native components:
29 |
30 | Prefix the group of layers with an underscore
31 |
32 | Examples:
33 | _SliderComponent
34 | _RangeSliderComponent
35 |
36 | These will then be accessible within the code view by typing:
37 |
38 | slider = new Design.SliderComponent
39 | rangeSlider = new Design.RangeSliderComponent
40 |
41 |
42 | -------------------
43 |
44 |
45 | For custom classes:
46 |
47 | Prefix the custom component name with "Custom_"
48 |
49 | Example:
50 | Custom_Card
51 |
52 | This will be built in code using:
53 |
54 | card = new Design.Card
55 |
56 | Child layers are accessible by name on initiating the class, and also after initilisation.
57 |
58 | card = new Design.Card
59 | avatar:
60 | image: "image_url"
61 | title:
62 | text: "Title text here"
63 |
64 |
65 | -------------------
66 |
67 | To add states to your custom classes:
68 |
69 | Prefix the group name with "State_", and append the name with an event (optional)
70 | To give the state a name, prefix the entire group name again with the name of the state. (optional)
71 |
72 | Examples:
73 | Custom_Button
74 | Custom_Input
75 | Custom_Emoji
76 |
77 | State_Button_MouseDown
78 | Success_State_Input_Tap
79 | Error_State_Input_Tap
80 | Congrats_State_Emoji
81 |
82 | ###
--------------------------------------------------------------------------------
/Demo.framer/framer/framer.init.js:
--------------------------------------------------------------------------------
1 | (function() {
2 |
3 | function isFileLoadingAllowed() {
4 | return (window.location.protocol.indexOf("file") == -1)
5 | }
6 |
7 | function isHomeScreened() {
8 | return ("standalone" in window.navigator) && window.navigator.standalone == true
9 | }
10 |
11 | function isCompatibleBrowser() {
12 | return Utils.isWebKit()
13 | }
14 |
15 | var alertNode;
16 |
17 | function dismissAlert() {
18 | alertNode.parentElement.removeChild(alertNode)
19 | loadProject()
20 | }
21 |
22 | function showAlert(html) {
23 |
24 | alertNode = document.createElement("div")
25 |
26 | alertNode.classList.add("framerAlertBackground")
27 | alertNode.innerHTML = html
28 |
29 | document.addEventListener("DOMContentLoaded", function(event) {
30 | document.body.appendChild(alertNode)
31 | })
32 |
33 | window.dismissAlert = dismissAlert;
34 | }
35 |
36 | function showBrowserAlert() {
37 | var html = ""
38 | html += "
"
39 | html += "Error: Not A WebKit Browser"
40 | html += "Your browser is not supported. Please use Safari or Chrome. "
41 | html += "Try anyway"
42 | html += "
"
43 |
44 | showAlert(html)
45 | }
46 |
47 | function showFileLoadingAlert() {
48 | var html = ""
49 | html += "
"
50 | html += "Error: Local File Restrictions"
51 | html += "Preview this prototype with Framer Mirror or learn more about "
52 | html += "file restrictions. "
53 | html += "Try anyway"
54 | html += "
"
55 |
56 | showAlert(html)
57 | }
58 |
59 | function loadProject(callback) {
60 | CoffeeScript.load("app.coffee", callback)
61 | }
62 |
63 | function setDefaultPageTitle() {
64 | // If no title was set we set it to the project folder name so
65 | // you get a nice name on iOS if you bookmark to desktop.
66 | document.addEventListener("DOMContentLoaded", function() {
67 | if (document.title == "") {
68 | if (window.FramerStudioInfo && window.FramerStudioInfo.documentTitle) {
69 | document.title = window.FramerStudioInfo.documentTitle
70 | } else {
71 | document.title = window.location.pathname.replace(/\//g, "")
72 | }
73 | }
74 | })
75 | }
76 |
77 | function init() {
78 |
79 | if (Utils.isFramerStudio()) {
80 | return
81 | }
82 |
83 | setDefaultPageTitle()
84 |
85 | if (!isCompatibleBrowser()) {
86 | return showBrowserAlert()
87 | }
88 |
89 | if (!isFileLoadingAllowed()) {
90 | return showFileLoadingAlert()
91 | }
92 |
93 | loadProject(function(){
94 | // CoffeeScript: Framer?.CurrentContext?.emit?("loaded:project")
95 | var context;
96 | if (typeof Framer !== "undefined" && Framer !== null) {
97 | if ((context = Framer.CurrentContext) != null) {
98 | if (typeof context.emit === "function") {
99 | context.emit("loaded:project");
100 | }
101 | }
102 | }
103 | })
104 | }
105 |
106 | init()
107 |
108 | })()
109 |
--------------------------------------------------------------------------------
/Demo.framer/app.coffee:
--------------------------------------------------------------------------------
1 | Design = require "DesignComponents"
2 |
3 | # DEMO SETUP
4 |
5 | Layer.select(".UIKit").x = Screen.width
6 |
7 | Screen.backgroundColor = "#886AE2"
8 |
9 | scroll = new ScrollComponent
10 | size: Screen
11 | scrollHorizontal: false
12 | mouseWheelEnabled: true
13 | mouseWheelSpeedMultiplier: 0.125
14 | scroll.content.clip = false
15 |
16 |
17 |
18 |
19 |
20 |
21 | # Designing sliders in the design tab, and using the class
22 | # SLIDERS - NATIVE COMPONENTS
23 |
24 | slider = new Design.SliderComponent
25 | parent: scroll.content
26 | y: 50
27 | x: Align.center
28 |
29 |
30 |
31 | rangeSlider = new Design.RangeSliderComponent
32 | parent: scroll.content
33 | y: slider.maxY + 75
34 | x: Align.center
35 |
36 | # Designing a custom component in the design tab, and using it as a class
37 | # CARDS - CUSTOM CLASS
38 |
39 | # Recreate the card without editing the content
40 | card1 = new Design.Card
41 | parent: scroll.content
42 | name: "card1"
43 | y: rangeSlider.maxY + 50
44 |
45 |
46 | # Recreate the card and edit the content
47 | card2 = new Design.Card
48 | parent: scroll.content
49 | name: "card2"
50 | y: card1.maxY + 10
51 | avatar:
52 | borderRadius: 0
53 | image: Utils.randomImage()
54 | subheader:
55 | text: "This is a new subheader"
56 | content:
57 | text: "This is some content that has changed"
58 |
59 |
60 | # Recreate the card, edit the content, and apply some constraints to the child layers
61 | card3 = new Design.Card
62 | parent: scroll.content
63 | name: "card3"
64 | y: card2.maxY + 10
65 | avatar:
66 | borderRadius: 0
67 | image: Utils.randomImage()
68 | subheader:
69 | text: "Lorem ipsum"
70 | content:
71 | constraints:
72 | pushDown: true
73 |
74 | textString = "This is a string that can type itself\nTo show it animating over multiple lines\nWhich is really cool!"
75 |
76 | for i in [0..textString.length]
77 | do (i) ->
78 | Utils.delay i*0.1, ->
79 | card3.content.text = textString.slice 0, i
80 |
81 | # Using states on a custom class to add events
82 | # # STATES WITH CUSTOM CLASSES
83 |
84 | # # Button will have a "MouseDown" and "MouseUp" event listener
85 | button = new Design.Button
86 | parent: scroll.content
87 | name: "button"
88 | y: card3.maxY + 50
89 | x: Align.center
90 |
91 | # # # Toggle will have multiple states that it will cycle through on "Tap"
92 | toggle = new Design.Toggle
93 | parent: scroll.content
94 | name: "toggle"
95 | y: button.maxY + 50
96 | x: Align.center
97 |
98 | # Using states on a custom class without an event
99 | # STATES WITHOUT AN EVENT LISTENER
100 |
101 |
102 | # Setting the state upon initilisation
103 | emoji = new Design.Emoji
104 | parent: scroll.content
105 | y: toggle.maxY + 50
106 | x: Align.center
107 | state: "Angel"
108 |
109 | # Setting a state after initilisation
110 | emoji.state = "Wink"
111 | emoji.state = Utils.randomChoice ["Smile", "Angel", "Wink", "Unamused"]
112 |
113 | # # Animating between states
114 | emoji.onTap ->
115 | currentState = @states.current.name
116 |
117 | if currentState == "default" || currentState == "Smile"
118 | @animateState "Wink", true
119 | else if currentState == "Wink"
120 | @animateState "Angel", true
121 | else if currentState == "Angel"
122 | @animateState "Unamused", true
123 | else if currentState == "Unamused"
124 | @animateState "Smile", true
125 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # framer-DesignComponents
2 | A module for converting your designs in design mode into full scalable classes.
3 |
4 | **Supported native components:**
5 |
6 | * SliderComponent
7 | * RangeSliderComponent
8 |
9 |
10 | ### Links
11 |
12 | * [Installation](#installation)
13 | * [Native components](#native-components)
14 | * [Custom classes](#custom-classes)
15 | * [Constraints](#constraints)
16 | * [States](#states)
17 |
18 |
19 | ## Installation
20 |
21 | ### Automatic installation with [Framer Modules](https://www.framermodules.com/)
22 |
23 |
24 |
26 |
27 |
28 | ### Manual installation
29 |
30 | Download and move the Constraints.coffee and DesignComponents.coffee files to your modules folder.
31 |
32 | At the top of your file, place this line of code:
33 |
34 | ```coffeescript
35 | Design = require "DesignComponents"
36 | ```
37 |
38 | Done! That is all the code you need to do to get this module started. Now onto the fun stuff.
39 |
40 |
41 |
42 | ## Native components
43 |
44 | For the native slider components, all you need to do is design a slider with the correct child names inside, and prefix the whole group with an _.
45 |
46 | For example the layer structure will look like this:
47 |
48 | 
49 |
50 | The _SliderComponent or _RangeSliderComponent layers are the background fill for the slider.
51 |
52 |
53 | ## Custom classes
54 |
55 | A custom class will be generated from your design, *including all of the layer's children*. You will then be able to add this class as many times as you want in the code view, without having to declare a class extension and constructor.
56 |
57 | To create a custom class in design mode, simply name the layer in design mode with the following syntax:
58 |
59 | ```
60 | Custom_{className}
61 | ```
62 |
63 | where {className} is replaced by the name of your class.
64 |
65 | For example:
66 |
67 | 
68 | 
69 |
70 | **Note:** You *do not* have to create a target for these layers.
71 |
72 |
73 | ### Using your class in your prototype
74 |
75 | To add the class to your prototype, simply create a new instance of the class as you would a layer:
76 |
77 | ```coffeescript
78 | button = new Design.Button
79 |
80 | card = new Design.Card
81 | ```
82 |
83 | **The layers within your class are also accessible within the the creation of the new instance, and oyu can change their properties as oyu would any other layer in code.**
84 |
85 | #### Card structure in Design mode
86 |
87 | 
88 |
89 | > **IMPORTANT:** Any SVG Layer in Design mode **must** be wrapped inside of a frame for this module to work.
90 |
91 | #### Editing the contents of the class in code
92 |
93 | ```coffeescript
94 | card = new Design.Card
95 | avatar:
96 | borderRadius: 0
97 | image: Utils.randomImage()
98 | subheader:
99 | text: "This is a new subheader"
100 | content:
101 | text: "This is some content that has changed"
102 | ```
103 |
104 | 
105 |
106 |
107 | ## Constraints
108 |
109 | With this module comes the ability to set constraints to your layers in code as well as design.
110 |
111 | When oyu initiate an instance of your symbol, it will automatically copy over the constraints of the design layer, but you are now able to override these upon initialisation, and later.
112 |
113 | ### Setting constraints on a new layer
114 |
115 | #### Arguments
116 |
117 | * top – the distance of the top edge from the top of the parent layer.
118 | * left – the distance of the left edge from the left of the parent layer.
119 | * bottom – the distance of the bottom edge from the bottom of the parent layer.
120 | * right – the distance of the right edge from the right of the parent layer.
121 | * centerX – the position of the layer within its parent on horizontal axis as a ratio between 0 - 1.
122 | * centerY – the position of the layer within its parent on the vertical axis as a ratio between 0 - 1.
123 | * scaleX – the width of the layer relative to its parent width as a ratio between 0 - 1.
124 | * scaleY – the height of the layer relative to its parent height as a ratio between 0 - 1.
125 | * aspectRatioLocked – the original ratio of width/height of the layer stays the same if set to true.
126 | * pushDown – If you want the layer to resize its parent when it resizes or moves, keeping the same original margin. I.e. when you add multiple lines to a text layer. NOTE: The layer cannot have a bottom constraint set for this to work.
127 | * pushRight – The same as pushDown but with the width of the parent. NOTE: The layer cannot have a right constraints set for this to work.
128 |
129 | ```coffeescript
130 | layer = new Layer
131 | size: 50
132 |
133 | layer.constraints =
134 | top: null
135 | left: 20
136 | bottom: null
137 | right: 20
138 | centerX: null
139 | centerY: 0.5
140 | scaleX: null
141 | scaleY: 0.8
142 | aspectRatioLocked: true
143 | pushDown: true
144 | pushRight: null
145 | ```
146 |
147 |
148 | ### Setting constraints on a layer within a custom class
149 |
150 | If you are using a custom class from the Design mode, you can set the constraints upon initilisation.
151 |
152 | ```coffeescript
153 | card = new Design.Card
154 | constraints:
155 | left: 10
156 | right: 10
157 | avatar:
158 | image: Utils.randomImage()
159 | constraints:
160 | right: 15
161 | top: 15
162 | aspectRatioLocked: true
163 | content:
164 | text: "Lorem ipsum dolor sit amet.\nNew content\nMore new content"
165 | constraints:
166 | pushDown: true
167 | ```
168 |
169 | See it in action:
170 |
171 | 
172 |
173 |
174 |
175 | ## States
176 |
177 | ### Single states
178 |
179 | The syntax for adding a single state on an event is:
180 |
181 | ```
182 | State_{className}_{event}
183 | ```
184 |
185 | eg:
186 |
187 | 
188 |
189 | ### Multiple states
190 |
191 | Adding multiple states for a event (toggling):
192 |
193 | ```
194 | {stateName}_State_{className}_{event}
195 | ```
196 |
197 | eg:
198 |
199 | 
200 |
201 | This will automatically add a switch event to the layer to switch the properties of the layer and all of its children upon the triggering of the event.
202 |
203 | ### Animating states
204 |
205 | By default, the design states will not animate between each other. To add default animation options to the events, simply append _Animate to the end of the layer name.
206 |
207 | **For example:**
208 |
209 | ```coffeescript
210 | {stateName}_State_{className}_{eventName}_Animate
211 | ```
212 |
213 | With your custom classes, there is a new animate function. This will enable the class to animate itself, and all of its descendants at the same time.
214 |
215 | ```coffeescript
216 | animateState(stateName, animate, options)
217 | ```
218 |
219 | #### Arguments
220 |
221 | * stateName – the name of the state to animate to.
222 | * animate – A boolean, set to override the default animation option for the class. (Optional)
223 | * options – animation options. (Optional)
224 |
225 | ```coffeescript
226 | layer = new Design.Class
227 |
228 | layer.animateState "Error",
229 | time: 1
230 | curve: Spring(damping: 0.8)
231 | ```
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
--------------------------------------------------------------------------------
/Demo.framer/modules/Constraints.coffee:
--------------------------------------------------------------------------------
1 |
2 | moveFromRef = (layer, reference, moveRef, layerRef, refType) ->
3 |
4 | originalConstraints = layer.constraintValues
5 |
6 | originalRefValue = reference[layerRef]
7 | originalLayerValue = layer[moveRef]
8 |
9 | layer[moveRef] = reference[layerRef] + layer.constraintValues[refType].value
10 |
11 | # reference.onChange layerRef, (value) ->
12 | # layer[moveRef] = originalLayerValue + (value - originalRefValue)
13 |
14 | layer.constraintValues = originalConstraints
15 |
16 |
17 | pushParent = (layer, direction) ->
18 |
19 | if direction == "down"
20 | originalY = layer.y
21 | originalHeight = layer.height
22 |
23 | layer.onChange "y", (value) ->
24 | @parent.height += value - originalY
25 | originalY = value
26 | originalHeight = @height
27 | layer.onChange "height", (value) ->
28 | @parent.height += value - originalHeight
29 | originalY = @y
30 | originalHeight = value
31 |
32 | if direction == "right"
33 | originalX = layer.x
34 | originalWidth - layer.width
35 |
36 | layer.onChange "x", (value) ->
37 | @parent.width += value - originalX
38 | originalX = value
39 | originalWidth = @width
40 | layer.onChange "width", (value) ->
41 | @parent.width += value - originalWidth
42 | originalX = @x
43 | originalWidth = value
44 |
45 |
46 | addReferenceEvents = (layer) ->
47 |
48 | originalConstraints = layer.constraintValues
49 |
50 | if layer.constraintValues?.topRef?.layer? || layer.constraintValues?.bottomRef?.layer?
51 |
52 | reference = layer.constraintValues?.topRef?.layer || layer.constraintValues?.bottomRef?.layer
53 |
54 | originalYRef = reference.y
55 | originalHeightRef = reference.height
56 | originalY = layer.y
57 |
58 | reference.onChange "y", (value) ->
59 | layer.y = originalY + (value - originalYRef)
60 | originalYRef = value
61 | originalY = layer.y
62 |
63 | unless layer.constraintValues?.topRef?.align == "y"
64 | reference.onChange "height", (value) ->
65 | layer.y = originalY + (value - originalHeightRef)
66 | originalHeightRef = value
67 | originalY = layer.y
68 |
69 | if layer.constraintValues?.topRef? && layer.constraintValues?.bottomRef?
70 | reference.onChange "height", (value) ->
71 | layer.height = value - layer.constraintValues?.topRef.value - layer.constraintValues?.bottomRef.value
72 | layer.y = reference.y + layer.constraintValues?.topRef.value
73 | originalHeightRef = value
74 |
75 | if layer.constraintValues?.leftRef?.layer? || layer.constraintValues?.rightRef?.layer?
76 | reference = layer.constraintValues?.leftRef?.layer || layer.constraintValues?.rightRef?.layer
77 |
78 | originalXRef = reference.x
79 | originalWidthRef = reference.width
80 | originalX = layer.x
81 |
82 | reference.onChange "x", (value) ->
83 | layer.x = originalX + (value - originalXRef)
84 | originalXRef = value
85 | originalX = layer.x
86 |
87 | unless layer.constraintValues?.left?.align == "x"
88 | reference.onChange "width", (value) ->
89 | layer.x = originalX + (value - originalWidthRef)
90 | originalWidthRef = value
91 | originalX = layer.x
92 |
93 | if layer.constraintValues?.leftRef? && layer.constraintValues?.rightRef?
94 | reference.onChange "width", (value) ->
95 | layer.width = value - layer.constraintValues?.leftRef.value - layer.constraintValues?.rightRef.value
96 | layer.x = reference.x + layer.constraintValues?.leftRef.value
97 | originalWidthRef = value
98 |
99 |
100 | buildConstraintsProtos = (constructorName) ->
101 |
102 | constructorName = eval constructorName
103 |
104 | constructorName::setConstraints = (options={}, origin) ->
105 |
106 | @constraintValues =
107 | top: if typeof options.top == "object" then null else if options.top? then options.top else if origin?.constraintValues? then origin.constraintValues.top else null
108 | left: if typeof options.left == "object" then null else if options.left? then options.left else if origin?.constraintValues? then origin.constraintValues.left else null
109 | bottom: if typeof options.bottom == "object" then null else if options.pushDown then null else if options.bottom? then options.bottom else if origin?.constraintValues? then origin.constraintValues.bottom else null
110 | right: if typeof options.right == "object" then null else if options.pushRight then null else if options.right? then options.right else if origin?.constraintValues? then origin.constraintValues.right else null
111 | width: @width
112 | height: @height
113 | widthFactor: if options.scaleX? then options.scaleX else if options.widthFactor? then options.widthFactor else null
114 | heightFactor: if options.scaleY? then options.scaleY else if options.heightFactor? then options.heightFactor else null
115 | centerAnchorX: if options.centerX? then options.centerX else if options.centerAnchorX? then options.centerAnchorX else null
116 | centerAnchorY: if options.centerY? then options.centerY else if options.centerAnchorY? then options.centerAnchorY else null
117 | aspectRatioLocked: if options.aspectRatioLocked? then options.aspectRatioLocked else if origin?.constraintValues then origin.constraintValues.aspectRatioLocked else false
118 |
119 | # resets
120 | values = @constraintValues
121 | if values.top? && values.bottom?
122 | @constraintValues.heightFactor = null
123 | @constraintValues.centerAnchorY = null
124 | if values.left? && values.right?
125 | @constraintValues.widthFactor = null
126 | @constraintValues.centerAnchorX = null
127 | if values.left? && values.right? && values.top? && values.bottom?
128 | @constraintValues.aspectRatioLocked = false
129 |
130 | for ref in [["top", "y", "maxY", "topRef", "bottom"], ["left", "x", "maxX", "leftRef", "right"], ["bottom", "maxY", "y", "bottomRef", "top"], ["right", "maxX", "x", "rightRef", "left"]]
131 |
132 | if typeof options[ref[0]] == "object" && options[ref[0]] != null && !options[ref[3]]?
133 |
134 | if options[ref[0]].layer?
135 | if @parent? && @parent.selectChild(options[ref[0]].layer)?
136 | layer = @parent.selectChild options[ref[0]].layer
137 | else
138 | layer = Layer.select options[ref[0]].layer
139 | else layer = @parent
140 |
141 | align = null
142 |
143 | if !options[ref[0]].value? && layer == @parent
144 | value = @[ref[1]]
145 | else if options[ref[0]].align? && options[ref[0]].value?
146 | value = options[ref[0]].value
147 | align = options[ref[0]].align
148 | else if options[ref[0]].align?
149 | value = 0
150 | align = options[ref[0]].align
151 | else if !options[ref[0]].value? && !options[ref[0]].align?
152 | value = @[ref[1]] - layer[ref[2]]
153 | align = ref[4]
154 | else
155 | value = options[ref[0]].value
156 | align = ref[4]
157 |
158 | if align == "left" then align = "x"
159 | else if align == "right" then align = "maxX"
160 | else if align == "top" then align = "y"
161 | else if align == "bottom" then align = "maxY"
162 |
163 | @constraintValues[ref[3]] =
164 | layer: layer
165 | value: value
166 | align: align
167 |
168 | @constraintValues[ref[0]] = null
169 | @constraintValues[ref[4]] = null
170 |
171 | # moveFromRef @, layer, ref[1], ref[2], ref[3]
172 |
173 | if options.pushDown?
174 | @constraintValues.bottom = null
175 | pushParent @, "down"
176 | if options.pushRight?
177 | @constraintValues.right = null
178 | pushParent @, "right"
179 |
180 | unless options.pushDown || @constraintValues.topRef || @constraintValues.bottomRef
181 | @constraintValues.bottom = if options.bottom? then options.bottom else if origin?.constraintValues? then origin.constraintValues.bottom else null
182 | unless options.pushRight || @constraintValues.leftRef || @constraintValues.rightRef
183 | @constraintValues.right = if options.right? then options.right else if origin?.constraintValues? then origin.constraintValues.right else null
184 |
185 | if @constraintValues.top == null && @constraintValues.bottom == null && @constraintValues.centerAnchorY == null && !@constraintValues.topRef && !@constraintValues.bottomRef
186 | @constraintValues.top = @y
187 | if @constraintValues.left == null && @constraintValues.right == null && @constraintValues.centerAnchorX == null && !@constraintValues.leftRef && !@constraintValues.rightRef
188 | @constraintValues.left = @x
189 |
190 | @applyConstraints()
191 |
192 |
193 | constructorName::applyConstraints = ->
194 |
195 | return if !@constraintValues
196 |
197 | values = @constraintValues
198 |
199 | if !@parent then parent = Screen else parent = @parent
200 |
201 | aspectRatio = @width / @height
202 |
203 | # position
204 | if values.top? && typeof values.top != "object"
205 | @y = values.top
206 | else if values.top == null && values.topRef?.layer?
207 | @y = values.topRef.layer[values.topRef.align] + values.topRef.value
208 |
209 | if values.left? && typeof values.left != "object"
210 | @x = values.left
211 | else if values.left == null && values.leftRef?.layer?
212 | @x = values.leftRef.layer[values.leftRef.align] + values.leftRef.value
213 |
214 | # size
215 | if values.left? && values.right?
216 | @width = parent.width - @x - values.right
217 | if values.aspectRatioLocked
218 | @height = @width / aspectRatio
219 | if values.top? && values.bottom?
220 | @height = parent.height - @y - values.bottom
221 | if values.aspectRatioLocked
222 | @width = @height * aspectRatio
223 |
224 | # if values.leftRef? && values.rightRef?
225 | # @width = parent.width - values.leftRef.value - values.rightRef.value
226 | # if values.topRef? && values.bottomRef?
227 | # @height = parent.height - values.topRef.value - values.bottomRef.value
228 |
229 | if values.widthFactor?
230 | @width = parent.width * values.widthFactor
231 | if values.heightFactor?
232 | @height = parent.height * values.heightFactor
233 |
234 | # max position
235 | if values.right?
236 | @maxX = parent.width - values.right
237 | else if values.right == null && values.rightRef?.layer?
238 | @maxX = values.rightRef.layer[values.rightRef.align] - values.rightRef.value
239 | if values.bottom?
240 | @maxY = parent.height - values.bottom
241 | else if values.bottom == null && values.bottomRef?.layer?
242 | @maxY = values.bottomRef.layer[values.bottomRef.align] - values.bottomRef.value
243 |
244 | # center position
245 | if !values.left? && !values.right? && values.centerAnchorX?
246 | @midX = parent.width * values.centerAnchorX
247 | if !values.top? && !values.bottom? && values.centerAnchorY?
248 | @midY = parent.height * values.centerAnchorY
249 |
250 | @constraintValues = values
251 |
252 | addReferenceEvents(@)
253 |
254 |
255 | layerTypes = ["Layer", "TextLayer", "ScrollComponent", "PageComponent", "SliderComponent", "RangeSliderComponent", "SVGLayer", "BackgroundLayer", "SVGPath", "SVGGroup"]
256 | for type in layerTypes
257 | buildConstraintsProtos(type)
258 |
259 |
260 |
261 | Object.defineProperty(Layer.prototype, "constraints", {
262 |
263 | get: -> return @_constraints
264 | set: (value) ->
265 | @_constraints = value
266 | @emit "change:constraints", value
267 | @setConstraints value
268 |
269 | })
--------------------------------------------------------------------------------
/DesignComponents.coffee:
--------------------------------------------------------------------------------
1 |
2 | ###
3 | ------------------
4 | CUSTOM CLASSES
5 | ------------------
6 | ###
7 |
8 | kit = Layer.select "*UIKit*"
9 | if kit? then kit.x = Screen.width * 1000; kit.name = ".UIKit"
10 |
11 | for layer in Layer.selectAll "@*"
12 | parent = layer.parent
13 | name = layer.name.replace "@", ""
14 | parent[name] = layer
15 |
16 | customComponents = Layer.selectAll "Custom_*"
17 |
18 | Layer::addDesignChildren = (origin) ->
19 | if !origin? then origin = @
20 | for child in origin.selectAllChildren ("*")
21 | parent = child.parent
22 | parent[child.name] = child
23 |
24 |
25 | stateChangeProps = [
26 | "width", "height",
27 | "opacity",
28 | "scaleX", "scaleY", "scaleZ", "scale",
29 | "skewX", "skewY", "skew",
30 | "rotationX", "rotationY", "rotationZ", "rotation",
31 | "blur",
32 | "brightness", "saturate", "hueRotate", "contrast", "invert", "grayscale", "sepia", "blending",
33 | "backgroundBlur", "backgroundBrightness", "backgroundSaturate", "backgroundHueRotate", "backgroundContrast", "backgroundInvert", "backgroundGrayscale", "backgroundSepia",
34 | "shadow1", "shadow2", "shadow3", "shadow4", "shadow5", "shadow6", "shadow7", "shadow8", "shadow9",
35 | "shadowX", "shadowY", "shadowBlur", "shadowSpread", "shadowColor", "shadowType",
36 | "shadows",
37 | "backgroundColor", "color",
38 | "borderRadius", "borderColor", "borderWidth", "borderStyle",
39 | "image", "gradient",
40 | "text",
41 | "fill", "stroke", "strokeWidth", "strokeWidthMultiplier"
42 | ]
43 |
44 |
45 | pushParent = (layer, direction) ->
46 | layer.pushValues =
47 | marginBottom: layer.parent.height - layer.maxY
48 | marginRight: layer.parent.width - layer.maxX
49 |
50 | if direction == "down"
51 | layer.onChange "y", ->
52 | @parent.height = layer.maxY + @pushValues.marginBottom
53 | layer.onChange "height", ->
54 | @parent.height = layer.maxY + @pushValues.marginBottom
55 |
56 | buildConstraintsProtos = (constructorName) ->
57 |
58 | constructorName = eval constructorName
59 |
60 | constructorName::setConstraints = (options={}, origin) ->
61 | @constraintValues =
62 | top: if typeof options?.top == "object" then null else if options?.top? && typeof options.top == "number" then options.top else origin?.constraintValues?.top || null
63 | left: if typeof options?.left == "object" then null else if options?.left? && typeof options.top == "number" then options.top else origin?.constraintValues?.left || null
64 | bottom: if typeof options?.bottom == "object" then null else if options?.pushDown then null else if options?.bottom? && typeof options.bottom == "number" then options.bottom else origin?.constraintValues?.bottom || null
65 | right: if typeof options?.right == "object" then null else if options?.pushRight then null else if options?.right? && typeof options.right == "number" then options.right else origin?.constraintValues?.right || null
66 | width: @width
67 | height: @height
68 | widthFactor: options?.scaleX || options?.widthFactor || null
69 | heightFactor: options?.scaleY ||options?.heightFactor || null
70 | centerAnchorX: options?.centerX || options?.centerAnchorX || null
71 | centerAnchorY: options?.centerY || options?.centerAnchorY || null
72 | aspectRatioLocked: if options?.aspectRatioLocked? then options.aspectRatioLocked else if origin?.constraintValues?.aspectRatioLocked? then origin.constraintValues.aspectRatioLocked else false
73 |
74 | if options.pushDown?
75 | @constraintValues.bottom = null
76 | pushParent @, "down"
77 | if options.pushRight?
78 | @constraintValues.right = null
79 | pushParent @, "right"
80 |
81 | constraints = @constraintValues
82 | textLayerAutoSize = typeof @ == TextLayer && @autoSize
83 |
84 | @onChange "y", ->
85 | @constraintValues = constraints
86 | @onChange "x", ->
87 | @constraintValues = constraints
88 | @onChange "height", ->
89 | @constraintValues = constraints
90 | @onChange "width", ->
91 | @constraintValues = constraints
92 |
93 | @applyConstraints()
94 |
95 | constructorName::applyConstraints = ->
96 |
97 | return if !@constraintValues
98 |
99 | values = @constraintValues
100 |
101 | if !@parent then parent = Screen else parent = @parent
102 |
103 | aspectRatio = @width / @height
104 |
105 | # position
106 | if values.top? && typeof values.top != "object"
107 | @y = values.top
108 | else if values.top == null && values.topRef?.layer?
109 | @y = values.topRef.layer[values.topRef.align] + values.topRef.value
110 |
111 | if values.left? && typeof values.left != "object"
112 | @x = values.left
113 | else if values.left == null && values.leftRef?.layer?
114 | @x = values.leftRef.layer[values.leftRef.align] + values.leftRef.value
115 |
116 | # size
117 | if values.left? && values.right?
118 | @width = parent.width - @x - values.right
119 | if values.aspectRatioLocked
120 | @height = @width / aspectRatio
121 | if values.top? && values.bottom?
122 | @height = parent.height - @y - values.bottom
123 | if values.aspectRatioLocked
124 | @width = @height * aspectRatio
125 |
126 | if values.widthFactor?
127 | @width = parent.width * values.widthFactor
128 | if values.heightFactor?
129 | @height = parent.height * values.heightFactor
130 |
131 | # max position
132 | if values.right?
133 | @maxX = parent.width - values.right
134 | else if values.right == null && values.rightRef?.layer?
135 | @maxX = values.rightRef.layer[values.rightRef.align] - values.rightRef.value
136 | if values.bottom?
137 | @maxY = parent.height - values.bottom
138 | else if values.bottom == null && values.bottomRef?.layer?
139 | @maxY = values.bottomRef.layer[values.bottomRef.align] - values.bottomRef.value
140 |
141 | # center position
142 | if !values.left? && !values.right? && values.centerAnchorX?
143 | @midX = parent.width * values.centerAnchorX
144 | if !values.top? && !values.bottom? && values.centerAnchorY?
145 | @midY = parent.height * values.centerAnchorY
146 |
147 | @constraintValues = values
148 |
149 |
150 | layerTypes = ["Layer", "TextLayer", "ScrollComponent", "PageComponent", "SliderComponent", "RangeSliderComponent", "SVGLayer", "BackgroundLayer", "SVGPath", "SVGGroup"]
151 | for type in layerTypes
152 | buildConstraintsProtos(type)
153 |
154 | Object.defineProperty(Layer.prototype, "constraints", {
155 | get: -> return @constraintValues
156 | set: (value) ->
157 | @_constraints = value
158 | @emit "change:constraints", value
159 | @setConstraints value
160 | })
161 |
162 | for component in customComponents
163 | name = component.name.replace "Custom_", ""
164 |
165 | do (component, name) ->
166 | class exports[name] extends Layer
167 | constructor: (@options={}) ->
168 | super @options
169 | @props = Object.assign component.props, {parent: @options.parent || null}
170 |
171 | if @options.constraints?
172 | @constraints = @options.constraints
173 | @props = @options
174 |
175 | @addChildren()
176 | @assignChildren()
177 | @setDescendantProps()
178 | @setDescendantConstraints()
179 |
180 | @stateComponents = Layer.selectAll "*State_#{name}*"
181 | @addStates()
182 |
183 | if @options.state?
184 | @animateState @options.state, false
185 |
186 | addChildren: ->
187 | newParent = component.copy()
188 | for child in newParent.children
189 | if child instanceof SVGPath or child instanceof SVGGroup
190 | Utils.throwInStudioOrWarnInProduction "SVG '#{child.name}' in 'Custom_#{name}' must be wrapped in a Frame in order to create a Design Component Symbol"
191 | else
192 | child.parent = @
193 | if child instanceof TextLayer && component.selectChild(child.name)?
194 | child.autoSize = true
195 | newParent.destroy()
196 |
197 | assignChildren: ->
198 | for descendant in @descendants
199 | @[descendant.name] = descendant
200 |
201 | setDescendantProps: ->
202 | for descendant in @descendants
203 | do (descendant) =>
204 | if @options[descendant.name]
205 | @[descendant.name].constraints = @options[descendant.name].constraints || undefined
206 | @[descendant.name].props = @options[descendant.name]
207 |
208 | setDescendantConstraints: ->
209 | for descendant in @descendants
210 | decName = descendant.name
211 | origin = component.selectChild decName
212 | descendant.setConstraints(
213 | @options[decName]?.constraints || {},
214 | origin
215 | )
216 |
217 | addStates: ->
218 | @customStates =
219 | array: []
220 |
221 | for state in @stateComponents
222 | do (state) =>
223 | stateIndex = state.name.indexOf "State"
224 | if stateIndex > 0
225 | stateName = state.name.slice 0, stateIndex-1
226 | else
227 | stateName = state.name.split("State_#{name}_")[1]
228 |
229 | @customStates[stateName] = {}
230 | @customStates.array.push stateName
231 |
232 | addProps = (layer, origin) ->
233 | stateProps = {}
234 | for prop in stateChangeProps
235 | stateProps[prop] = origin[prop]
236 |
237 | if origin instanceof SVGLayer
238 | split1 = origin['html'].split(/opacity/)[1]
239 | if (split1[0] != ':')
240 | split2 = split1.split(/"/)[1]
241 | stateProps['opacity'] = split2
242 | else
243 | stateProps['opacity'] = "1.0"
244 |
245 | layer.states[stateName] = stateProps
246 |
247 | addProps @, state
248 |
249 | for descendant in state.descendants
250 | do (descendant) =>
251 | addProps @[descendant.name], descendant
252 | if descendant instanceof SVGPath
253 | addProps @[descendant.name].parent._children[0], descendant.parent._children[0]
254 |
255 | @addStateEvents()
256 |
257 | addStateEvents: ->
258 | events = []
259 |
260 | for state in @stateComponents
261 | if state.name.includes "_State_#{name}"
262 | stateName = state.name.split("_State_#{name}")[0]
263 | eventName = state.name.split("_State_#{name}")[1]
264 | else
265 | eventName = state.name.replace "State_#{name}_", ""
266 | stateName = eventName
267 |
268 | if eventName.includes "_Animate"
269 | animate = true
270 | eventName = eventName.split("_Animate")[0]
271 | else animate = false
272 |
273 | eventName = eventName.replace "_", ""
274 |
275 | @customStates[stateName].animate = animate
276 |
277 | unless events.includes eventName then events.push eventName
278 |
279 | for eventName in events
280 |
281 | do (eventName) =>
282 |
283 | if Events[eventName]? && @customStates.array.includes eventName
284 |
285 | @on Events[eventName], (event, layer) ->
286 |
287 | animateBool = @customStates[eventName].animate
288 |
289 | @stateSwitch(eventName, {animate: animateBool})
290 | @animateChildren()
291 |
292 | else if Events[eventName]?
293 |
294 | @on Events[eventName], ->
295 | currentIndex = @customStates.array.indexOf(@states.current.name)
296 | nextIndex = currentIndex + 1
297 | if nextIndex == @customStates.array.length then nextIndex = 0
298 | animateBool = @customStates[@customStates.array[nextIndex]].animate
299 | nextState = @customStates.array[nextIndex]
300 |
301 | @stateSwitch(nextState, {animate: animateBool})
302 | @animateChildren()
303 |
304 | animateChildren: (stateName, animate, options={}) ->
305 | unless stateName? then stateName = @states.current.name
306 | unless animate? then animate = @customStates[stateName].animate
307 | if !animate then options.time = 0
308 | for dec in @descendants
309 | do (dec) =>
310 | if dec instanceof SVGPath
311 | # dec.stateSwitch(stateName, {animate: animate, options: options})
312 |
313 | dec.parent._children[0].stateSwitch(stateName, {animate: animate, options: options})
314 | else
315 | dec.stateSwitch(stateName, {animate: animate, options: options})
316 |
317 | animateState: (state, animate, options={}) ->
318 |
319 | if !state? || !@customStates?[state]? then return
320 | if !animate? && @customStates?[state]? then animate = @customStates[state].animate else if animate? then animate = animate else animate = false
321 | if !animate then options.time = 0
322 |
323 | @stateSwitch(state, {animate: animate, options: options})
324 | @animateChildren(state, animate, options)
325 |
326 | @define "state",
327 | get: -> @options.state
328 | set: (value) ->
329 | @options.state = value
330 | @emit("change:state", @options.state)
331 | @animateState value
332 |
333 |
334 |
335 | customStates = Layer.selectAll "State_*"
336 |
337 | for componentState in customStates
338 |
339 | classEventName = componentState.name.replace "State_", ""
340 | if classEventName.includes "_"
341 | className = classEventName.split("_")[0]
342 | eventName = classEventName.split("_")[1]
343 | else
344 | className = classEventName
345 |
346 |
347 | ###
348 | ------------------
349 | EXISTING CLASSES
350 | ------------------
351 | ###
352 |
353 |
354 | components = Layer.selectAll "_*"
355 |
356 | extendSlider = (origin) ->
357 | class exports.SliderComponent extends SliderComponent
358 |
359 | constructor: (@options={}) ->
360 | super @options
361 |
362 | @knob.props =
363 | shadows: origin.knob.shadows
364 | backgroundColor: origin.knob.backgroundColor
365 | borderRadius: origin.knob.borderRadius
366 | frame: origin.knob.frame
367 |
368 | @fill.props =
369 | shadows: origin.fill.shadows
370 | backgroundColor: origin.fill.backgroundColor
371 | borderRadius: origin.fill.borderRadius
372 | frame: origin.fill.frame
373 |
374 | @props =
375 | shadows: origin.shadows
376 | backgroundColor: origin.backgroundColor
377 | borderRadius: origin.borderRadius
378 | size: origin.size
379 |
380 | @value = Utils.modulate origin.knob.midX, [0, origin.width], [@min, @max]
381 |
382 |
383 | extendRangeSlider = (origin) ->
384 | class exports.RangeSliderComponent extends RangeSliderComponent
385 |
386 | constructor: (@options={}) ->
387 | super @options
388 |
389 | @minKnob.props =
390 | shadows: origin.minKnob.shadows
391 | backgroundColor: origin.minKnob.backgroundColor
392 | borderRadius: origin.minKnob.borderRadius
393 | frame: origin.minKnob.frame
394 | @maxKnob.props =
395 | shadows: origin.maxKnob.shadows
396 | backgroundColor: origin.maxKnob.backgroundColor
397 | borderRadius: origin.maxKnob.borderRadius
398 | frame: origin.maxKnob.frame
399 |
400 | @fill.props =
401 | shadows: origin.fill.shadows
402 | backgroundColor: origin.fill.backgroundColor
403 | borderRadius: origin.fill.borderRadius
404 | frame: origin.fill.frame
405 |
406 | @props =
407 | shadows: origin.shadows
408 | backgroundColor: origin.backgroundColor
409 | borderRadius: origin.borderRadius
410 | size: origin.size
411 |
412 | @minValue = Utils.modulate origin.minKnob.midX, [0, origin.width], [@min, @max]
413 | @maxValue = Utils.modulate origin.maxKnob.midX, [0, origin.width], [@min, @max]
414 |
415 |
416 |
417 | for component in components
418 |
419 | type = component.name.replace "_", ""
420 |
421 | do (component) ->
422 |
423 | component.addDesignChildren()
424 |
425 | if type == "SliderComponent"
426 | extendSlider component
427 | else if type == "RangeSliderComponent"
428 | extendRangeSlider component
429 |
--------------------------------------------------------------------------------
/Demo.framer/modules/DesignComponents.coffee:
--------------------------------------------------------------------------------
1 |
2 | ###
3 | ------------------
4 | CUSTOM CLASSES
5 | ------------------
6 | ###
7 |
8 | kit = Layer.select "*UIKit*"
9 | if kit? then kit.x = Screen.width * 1000; kit.name = ".UIKit"
10 |
11 | for layer in Layer.selectAll "@*"
12 | parent = layer.parent
13 | name = layer.name.replace "@", ""
14 | parent[name] = layer
15 |
16 | customComponents = Layer.selectAll "Custom_*"
17 |
18 | Layer::addDesignChildren = (origin) ->
19 | if !origin? then origin = @
20 | for child in origin.selectAllChildren ("*")
21 | parent = child.parent
22 | parent[child.name] = child
23 |
24 |
25 | stateChangeProps = [
26 | "width", "height",
27 | "opacity",
28 | "scaleX", "scaleY", "scaleZ", "scale",
29 | "skewX", "skewY", "skew",
30 | "rotationX", "rotationY", "rotationZ", "rotation",
31 | "blur",
32 | "brightness", "saturate", "hueRotate", "contrast", "invert", "grayscale", "sepia", "blending",
33 | "backgroundBlur", "backgroundBrightness", "backgroundSaturate", "backgroundHueRotate", "backgroundContrast", "backgroundInvert", "backgroundGrayscale", "backgroundSepia",
34 | "shadow1", "shadow2", "shadow3", "shadow4", "shadow5", "shadow6", "shadow7", "shadow8", "shadow9",
35 | "shadowX", "shadowY", "shadowBlur", "shadowSpread", "shadowColor", "shadowType",
36 | "shadows",
37 | "backgroundColor", "color",
38 | "borderRadius", "borderColor", "borderWidth", "borderStyle",
39 | "image", "gradient",
40 | "text",
41 | "fill", "stroke", "strokeWidth", "strokeWidthMultiplier"
42 | ]
43 |
44 |
45 | pushParent = (layer, direction) ->
46 | layer.pushValues =
47 | marginBottom: layer.parent.height - layer.maxY
48 | marginRight: layer.parent.width - layer.maxX
49 |
50 | if direction == "down"
51 | layer.onChange "y", ->
52 | @parent.height = layer.maxY + @pushValues.marginBottom
53 | layer.onChange "height", ->
54 | @parent.height = layer.maxY + @pushValues.marginBottom
55 |
56 | buildConstraintsProtos = (constructorName) ->
57 |
58 | constructorName = eval constructorName
59 |
60 | constructorName::setConstraints = (options={}, origin) ->
61 | @constraintValues =
62 | top: if typeof options?.top == "object" then null else if options?.top? && typeof options.top == "number" then options.top else origin?.constraintValues?.top || null
63 | left: if typeof options?.left == "object" then null else if options?.left? && typeof options.top == "number" then options.top else origin?.constraintValues?.left || null
64 | bottom: if typeof options?.bottom == "object" then null else if options?.pushDown then null else if options?.bottom? && typeof options.bottom == "number" then options.bottom else origin?.constraintValues?.bottom || null
65 | right: if typeof options?.right == "object" then null else if options?.pushRight then null else if options?.right? && typeof options.right == "number" then options.right else origin?.constraintValues?.right || null
66 | width: @width
67 | height: @height
68 | widthFactor: options?.scaleX || options?.widthFactor || null
69 | heightFactor: options?.scaleY ||options?.heightFactor || null
70 | centerAnchorX: options?.centerX || options?.centerAnchorX || null
71 | centerAnchorY: options?.centerY || options?.centerAnchorY || null
72 | aspectRatioLocked: if options?.aspectRatioLocked? then options.aspectRatioLocked else if origin?.constraintValues?.aspectRatioLocked? then origin.constraintValues.aspectRatioLocked else false
73 |
74 | if options.pushDown?
75 | @constraintValues.bottom = null
76 | pushParent @, "down"
77 | if options.pushRight?
78 | @constraintValues.right = null
79 | pushParent @, "right"
80 |
81 | constraints = @constraintValues
82 | textLayerAutoSize = typeof @ == TextLayer && @autoSize
83 |
84 | @onChange "y", ->
85 | @constraintValues = constraints
86 | @onChange "x", ->
87 | @constraintValues = constraints
88 | @onChange "height", ->
89 | @constraintValues = constraints
90 | @onChange "width", ->
91 | @constraintValues = constraints
92 |
93 | @applyConstraints()
94 |
95 | constructorName::applyConstraints = ->
96 |
97 | return if !@constraintValues
98 |
99 | values = @constraintValues
100 |
101 | if !@parent then parent = Screen else parent = @parent
102 |
103 | aspectRatio = @width / @height
104 |
105 | # position
106 | if values.top? && typeof values.top != "object"
107 | @y = values.top
108 | else if values.top == null && values.topRef?.layer?
109 | @y = values.topRef.layer[values.topRef.align] + values.topRef.value
110 |
111 | if values.left? && typeof values.left != "object"
112 | @x = values.left
113 | else if values.left == null && values.leftRef?.layer?
114 | @x = values.leftRef.layer[values.leftRef.align] + values.leftRef.value
115 |
116 | # size
117 | if values.left? && values.right?
118 | @width = parent.width - @x - values.right
119 | if values.aspectRatioLocked
120 | @height = @width / aspectRatio
121 | if values.top? && values.bottom?
122 | @height = parent.height - @y - values.bottom
123 | if values.aspectRatioLocked
124 | @width = @height * aspectRatio
125 |
126 | if values.widthFactor?
127 | @width = parent.width * values.widthFactor
128 | if values.heightFactor?
129 | @height = parent.height * values.heightFactor
130 |
131 | # max position
132 | if values.right?
133 | @maxX = parent.width - values.right
134 | else if values.right == null && values.rightRef?.layer?
135 | @maxX = values.rightRef.layer[values.rightRef.align] - values.rightRef.value
136 | if values.bottom?
137 | @maxY = parent.height - values.bottom
138 | else if values.bottom == null && values.bottomRef?.layer?
139 | @maxY = values.bottomRef.layer[values.bottomRef.align] - values.bottomRef.value
140 |
141 | # center position
142 | if !values.left? && !values.right? && values.centerAnchorX?
143 | @midX = parent.width * values.centerAnchorX
144 | if !values.top? && !values.bottom? && values.centerAnchorY?
145 | @midY = parent.height * values.centerAnchorY
146 |
147 | @constraintValues = values
148 |
149 |
150 | layerTypes = ["Layer", "TextLayer", "ScrollComponent", "PageComponent", "SliderComponent", "RangeSliderComponent", "SVGLayer", "BackgroundLayer", "SVGPath", "SVGGroup"]
151 | for type in layerTypes
152 | buildConstraintsProtos(type)
153 |
154 | Object.defineProperty(Layer.prototype, "constraints", {
155 | get: -> return @constraintValues
156 | set: (value) ->
157 | @_constraints = value
158 | @emit "change:constraints", value
159 | @setConstraints value
160 | })
161 |
162 | for component in customComponents
163 | name = component.name.replace "Custom_", ""
164 |
165 | do (component, name) ->
166 | class exports[name] extends Layer
167 | constructor: (@options={}) ->
168 | super @options
169 | @props = Object.assign component.props, {parent: @options.parent || null}
170 |
171 | if @options.constraints?
172 | @constraints = @options.constraints
173 | @props = @options
174 |
175 | @addChildren()
176 | @assignChildren()
177 | @setDescendantProps()
178 | @setDescendantConstraints()
179 |
180 | @stateComponents = Layer.selectAll "*State_#{name}*"
181 | @addStates()
182 |
183 | if @options.state?
184 | @animateState @options.state, false
185 |
186 | addChildren: ->
187 | newParent = component.copy()
188 | for child in newParent.children
189 | if child instanceof SVGPath or child instanceof SVGGroup
190 | Utils.throwInStudioOrWarnInProduction "SVG '#{child.name}' in 'Custom_#{name}' must be wrapped in a Frame in order to create a Design Component Symbol"
191 | else
192 | child.parent = @
193 | if child instanceof TextLayer && component.selectChild(child.name)?
194 | child.autoSize = true
195 | newParent.destroy()
196 |
197 | assignChildren: ->
198 | for descendant in @descendants
199 | @[descendant.name] = descendant
200 |
201 | setDescendantProps: ->
202 | for descendant in @descendants
203 | do (descendant) =>
204 | if @options[descendant.name]
205 | @[descendant.name].constraints = @options[descendant.name].constraints || undefined
206 | @[descendant.name].props = @options[descendant.name]
207 |
208 | setDescendantConstraints: ->
209 | for descendant in @descendants
210 | decName = descendant.name
211 | origin = component.selectChild decName
212 | descendant.setConstraints(
213 | @options[decName]?.constraints || {},
214 | origin
215 | )
216 |
217 | addStates: ->
218 | @customStates =
219 | array: []
220 |
221 | for state in @stateComponents
222 | do (state) =>
223 | stateIndex = state.name.indexOf "State"
224 | if stateIndex > 0
225 | stateName = state.name.slice 0, stateIndex-1
226 | else
227 | stateName = state.name.split("State_#{name}_")[1]
228 |
229 | @customStates[stateName] = {}
230 | @customStates.array.push stateName
231 |
232 | addProps = (layer, origin) ->
233 | stateProps = {}
234 | for prop in stateChangeProps
235 | stateProps[prop] = origin[prop]
236 |
237 | if origin instanceof SVGLayer
238 | split1 = origin['html'].split(/opacity/)[1]
239 | if (split1[0] != ':')
240 | split2 = split1.split(/"/)[1]
241 | stateProps['opacity'] = split2
242 | else
243 | stateProps['opacity'] = "1.0"
244 |
245 | layer.states[stateName] = stateProps
246 |
247 | addProps @, state
248 |
249 | for descendant in state.descendants
250 | do (descendant) =>
251 | addProps @[descendant.name], descendant
252 | if descendant instanceof SVGPath
253 | addProps @[descendant.name].parent._children[0], descendant.parent._children[0]
254 |
255 | @addStateEvents()
256 |
257 | addStateEvents: ->
258 | events = []
259 |
260 | for state in @stateComponents
261 | if state.name.includes "_State_#{name}"
262 | stateName = state.name.split("_State_#{name}")[0]
263 | eventName = state.name.split("_State_#{name}")[1]
264 | else
265 | eventName = state.name.replace "State_#{name}_", ""
266 | stateName = eventName
267 |
268 | if eventName.includes "_Animate"
269 | animate = true
270 | eventName = eventName.split("_Animate")[0]
271 | else animate = false
272 |
273 | eventName = eventName.replace "_", ""
274 |
275 | @customStates[stateName].animate = animate
276 |
277 | unless events.includes eventName then events.push eventName
278 |
279 | for eventName in events
280 |
281 | do (eventName) =>
282 |
283 | if Events[eventName]? && @customStates.array.includes eventName
284 |
285 | @on Events[eventName], (event, layer) ->
286 |
287 | animateBool = @customStates[eventName].animate
288 |
289 | @stateSwitch(eventName, {animate: animateBool})
290 | @animateChildren()
291 |
292 | else if Events[eventName]?
293 |
294 | @on Events[eventName], ->
295 | currentIndex = @customStates.array.indexOf(@states.current.name)
296 | nextIndex = currentIndex + 1
297 | if nextIndex == @customStates.array.length then nextIndex = 0
298 | animateBool = @customStates[@customStates.array[nextIndex]].animate
299 | nextState = @customStates.array[nextIndex]
300 |
301 | @stateSwitch(nextState, {animate: animateBool})
302 | @animateChildren()
303 |
304 | animateChildren: (stateName, animate, options={}) ->
305 | unless stateName? then stateName = @states.current.name
306 | unless animate? then animate = @customStates[stateName].animate
307 | if !animate then options.time = 0
308 | for dec in @descendants
309 | do (dec) =>
310 | if dec instanceof SVGPath
311 | # dec.stateSwitch(stateName, {animate: animate, options: options})
312 |
313 | dec.parent._children[0].stateSwitch(stateName, {animate: animate, options: options})
314 | else
315 | dec.stateSwitch(stateName, {animate: animate, options: options})
316 |
317 | animateState: (state, animate, options={}) ->
318 |
319 | if !state? || !@customStates?[state]? then return
320 | if !animate? && @customStates?[state]? then animate = @customStates[state].animate else if animate? then animate = animate else animate = false
321 | if !animate then options.time = 0
322 |
323 | @stateSwitch(state, {animate: animate, options: options})
324 | @animateChildren(state, animate, options)
325 |
326 | @define "state",
327 | get: -> @options.state
328 | set: (value) ->
329 | @options.state = value
330 | @emit("change:state", @options.state)
331 | @animateState value
332 |
333 |
334 |
335 | customStates = Layer.selectAll "State_*"
336 |
337 | for componentState in customStates
338 |
339 | classEventName = componentState.name.replace "State_", ""
340 | if classEventName.includes "_"
341 | className = classEventName.split("_")[0]
342 | eventName = classEventName.split("_")[1]
343 | else
344 | className = classEventName
345 |
346 |
347 | ###
348 | ------------------
349 | EXISTING CLASSES
350 | ------------------
351 | ###
352 |
353 |
354 | components = Layer.selectAll "_*"
355 |
356 | extendSlider = (origin) ->
357 | class exports.SliderComponent extends SliderComponent
358 |
359 | constructor: (@options={}) ->
360 | super @options
361 |
362 | @knob.props =
363 | shadows: origin.knob.shadows
364 | backgroundColor: origin.knob.backgroundColor
365 | borderRadius: origin.knob.borderRadius
366 | frame: origin.knob.frame
367 |
368 | @fill.props =
369 | shadows: origin.fill.shadows
370 | backgroundColor: origin.fill.backgroundColor
371 | borderRadius: origin.fill.borderRadius
372 | frame: origin.fill.frame
373 |
374 | @props =
375 | shadows: origin.shadows
376 | backgroundColor: origin.backgroundColor
377 | borderRadius: origin.borderRadius
378 | size: origin.size
379 |
380 | @value = Utils.modulate origin.knob.midX, [0, origin.width], [@min, @max]
381 |
382 |
383 | extendRangeSlider = (origin) ->
384 | class exports.RangeSliderComponent extends RangeSliderComponent
385 |
386 | constructor: (@options={}) ->
387 | super @options
388 |
389 | @minKnob.props =
390 | shadows: origin.minKnob.shadows
391 | backgroundColor: origin.minKnob.backgroundColor
392 | borderRadius: origin.minKnob.borderRadius
393 | frame: origin.minKnob.frame
394 | @maxKnob.props =
395 | shadows: origin.maxKnob.shadows
396 | backgroundColor: origin.maxKnob.backgroundColor
397 | borderRadius: origin.maxKnob.borderRadius
398 | frame: origin.maxKnob.frame
399 |
400 | @fill.props =
401 | shadows: origin.fill.shadows
402 | backgroundColor: origin.fill.backgroundColor
403 | borderRadius: origin.fill.borderRadius
404 | frame: origin.fill.frame
405 |
406 | @props =
407 | shadows: origin.shadows
408 | backgroundColor: origin.backgroundColor
409 | borderRadius: origin.borderRadius
410 | size: origin.size
411 |
412 | @minValue = Utils.modulate origin.minKnob.midX, [0, origin.width], [@min, @max]
413 | @maxValue = Utils.modulate origin.maxKnob.midX, [0, origin.width], [@min, @max]
414 |
415 |
416 |
417 | for component in components
418 |
419 | type = component.name.replace "_", ""
420 |
421 | do (component) ->
422 |
423 | component.addDesignChildren()
424 |
425 | if type == "SliderComponent"
426 | extendSlider component
427 | else if type == "RangeSliderComponent"
428 | extendRangeSlider component
429 |
--------------------------------------------------------------------------------
/Demo.framer/framer/framer.vekter.js:
--------------------------------------------------------------------------------
1 | (function(scope) {var __layer_0__ = new Layer({"name":".UIKit","backgroundColor":"hsl(0, 0%, 81%)","width":375,"x":717,"height":1408,"constraintValues":null,"blending":"normal","clip":true,"borderStyle":"solid"});var Custom_Card = new Layer({"parent":__layer_0__,"name":"Custom_Card","shadows":[{"spread":0,"x":0,"type":"box","y":4,"blur":6,"color":"rgba(0, 0, 0, 0.15)"}],"backgroundColor":"rgb(252, 252, 252)","width":355,"x":10,"height":117,"constraintValues":{"left":10,"height":117,"centerAnchorX":0.5,"width":355,"right":10,"top":13,"centerAnchorY":0.1071964017991004},"blending":"normal","borderRadius":5,"clip":false,"borderStyle":"solid","y":13});var __layer_1__ = new Layer({"parent":Custom_Card,"name":"arrow","backgroundColor":null,"width":11,"x":323,"height":21,"constraintValues":{"left":null,"height":21,"centerAnchorX":0.9253521126760563,"width":11,"bottom":17,"right":21,"top":null,"centerAnchorY":0.7649572649572649},"blending":"normal","clip":false,"borderStyle":"solid","y":79});var __layer_2__ = new SVGLayer({"parent":__layer_1__,"name":".SVGLayer","backgroundColor":null,"width":10.5,"stroke":"#AAA","strokeWidth":2,"htmlIntrinsicSize":{"height":21,"width":10.5},"rotation":null,"height":21,"opacity":null,"y":-0.5,"svg":"