JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
cleanup hover/filter stuff
[crayon_mockup.git] / auto.coffee
1 # settings
2 width = 800
3 height = 600
4 supply_height = 96
5 CLICK_FUZ = 10 # this far away from things is close enough to be "clicked on"
6 PROX_MAX = CLICK_FUZ * CLICK_FUZ
7
8 # constants
9 STYLE_NORMAL = 0
10 STYLE_SELECTED = 1
11 STYLE_HOVER = 2
12 STYLE_EDITING = 3
13 STYLE_DRAGGING = 4
14
15 STYLE_TO_CLASS = [
16         "normal"
17         "selected"
18         "hover"
19         "editing"
20         "dragging"
21 ]
22
23 set_style_class = (args) ->
24         args.el.setAttribute 'class', "#{args.class} #{STYLE_TO_CLASS[args.style]}"
25
26 # json (compiled to javascript and minified) is ~8% smaller than the resulting xml
27 json_to_svg = (json) ->
28         for tag, attrs of json
29                 el = document.createElementNS 'http://www.w3.org/2000/svg', tag
30                 for k, v of attrs
31                         if k is 'children'
32                                 for child in v
33                                         el.appendChild json_to_svg child
34                         else if k is 'contents'
35                                 el.appendChild document.createTextNode v
36                         else
37                                 el.setAttribute k, v
38         return el
39
40 next_widget_id = 0
41 # public vars: x, y, width, height, el
42 class Visible
43         # required args: svg
44         constructor: (args) ->
45                 @id = next_widget_id
46                 next_widget_id += 1
47                 @svg = args.svg
48                 @x = args.x ? 1
49                 @y = args.y ? 1
50                 @width = args.width ? 50
51                 @height = args.height ? 34
52                 @style = args.style ? STYLE_NORMAL
53         destruct: ->
54         move: (args) ->
55                 @x = args.x
56                 @y = args.y
57         proximity: (xy) -> # return the square of the distance to your visible bits
58                 return PROX_MAX + 1
59         set_style: (style) ->
60                 @style = style
61
62 class Control extends Visible
63
64 class Widget extends Visible
65         #sub-classes are expected to implement all of these:
66         clone: ->
67                 return new Widget @
68         controls: -> # create controls, return them
69                 return []
70         hide_controls: ->
71
72 class RectWidget extends Widget
73         constructor: (args) ->
74                 super args
75                 @css_class = 'box'
76                 @el = json_to_svg rect:
77                         x: @x + 1
78                         y: @y + 1
79                         width: @width - 2
80                         height: @height - 2
81                         class: 'box normal'
82                 @svg.appendChild @el
83         destruct: ->
84                 super()
85                 if @el?
86                         @svg.removeChild @el
87         clone: ->
88                 return new RectWidget @
89         set_style: (style) ->
90                 super style
91                 set_style_class el: @el, class: 'box', style: style
92         move: (args) ->
93                 super args
94                 @el.setAttribute 'x', @x + 1
95                 @el.setAttribute 'y', @y + 1
96         proximity: (xy) -> # return the square of the distance to your visible bits
97                 x = xy.x
98                 y = xy.y
99                 prox = PROX_MAX + 1
100                 in_x = false
101                 in_y = false
102                 if x > @x - CLICK_FUZ and x < @x + @width + CLICK_FUZ
103                         in_x = true
104                         if y < @y + @height / 2
105                                 new_prox = @y - y
106                         else
107                                 new_prox = @y + @height - y
108                         new_prox *= new_prox
109                         if new_prox < prox
110                                 prox = new_prox
111                 if y > @y - CLICK_FUZ and y < @y + @height + CLICK_FUZ
112                         in_y = true
113                         if x < @x + @width / 2
114                                 new_prox = @x - x
115                         else
116                                 new_prox = @x + @width - x
117                         new_prox *= new_prox
118                         if new_prox < prox
119                                 prox = new_prox
120                 if in_x and in_y and prox > PROX_MAX
121                         prox = PROX_MAX - 1
122                 return prox
123         controls: -> # create controls, return them
124                 return []
125         hide_controls: ->
126
127 # called automatically on domcontentloaded
128 init = ->
129         svg_offset = null
130         $container = $ '.crayon_mockup'
131         svg = json_to_svg svg: width: width, height: height, viewBox: "0 0 #{width} #{height}"
132         $svg = $ svg
133         $container.append $svg
134         svg.appendChild json_to_svg filter:
135                 id: 'crayon', filterUnits: 'userSpaceOnUse'
136                 x: '-5%', y: '-5%', height: '110%', width: '110%'
137                 children: [
138                         { feTurbulence: baseFrequency: '.3', numOctaves: '2', type: 'fractalNoise' }
139                         { feDisplacementMap: scale: '6', xChannelSelector: 'R', in: 'SourceGraphic' }
140                 ]
141         svg.appendChild json_to_svg style:
142                 type: 'text/css'
143                 contents: '.box.normal,.box.hover,.box.selected{filter: url(#crayon)}'
144
145         # create canvas border
146         svg.appendChild json_to_svg rect:
147                 x: 1
148                 y: supply_height + 1
149                 width: width - 2
150                 height: height - 2 - supply_height
151                 class: 'canvas_border'
152
153         supply = {}
154         for args, i in [
155                 { width: 40, height: 40 }
156                 { width: 12, height: 50 }
157                 { width: 70, height: 12 }
158         ]
159                 widget = new RectWidget {
160                         width: args.width
161                         height: args.height
162                         x: 30 + i * 90 + (70 - args.width) / 2
163                         y: (supply_height - args.height) / 2
164                         svg: svg
165                 }
166                 supply[widget.id] = widget
167
168         # editor state
169         on_canvas = {}
170         selected = {}
171         editing = {} # has controls
172         dragging = false
173         drag_from = x: 0, y: 0 # mouse was here at last frame of drag
174         shift_key_down = false
175
176         deselect_all = (args) ->
177                 except = args?.except ? null
178                 for id, w of selected
179                         w.set_style STYLE_NORMAL
180                         delete selected[id]
181         closest_widget = (widgets, xy) ->
182                 prox = PROX_MAX + 1
183                 closest = null
184                 for id, w of widgets
185                         new_prox = w.proximity xy
186                         if new_prox < prox
187                                 prox = new_prox
188                                 closest = w
189                 if prox < PROX_MAX
190                         return closest
191                 return null
192         svg_event_to_xy = (e) ->
193                 unless svg_offset?
194                         svg_offset = $svg.offset()
195                 return {
196                         x: Math.round(e.pageX - svg_offset.left)
197                         y: Math.round(e.pageY - svg_offset.top)
198                 }
199         mousedown = (e) ->
200                 mousemove e
201                 if dragging # two mousedowns in a row?! it happens
202                         return mouseup e
203                 xy = svg_event_to_xy e
204                 if xy.y < supply_height
205                         closest = closest_widget supply, xy
206                         if closest?
207                                 closest = closest.clone()
208                                 on_canvas[closest.id] = closest
209                 else
210                         closest = closest_widget on_canvas, xy
211                 if closest?
212                         unless (shift_key_down or selected[closest.id]?)
213                                 deselect_all except: closest
214                         selected[closest.id] = closest
215                         closest.set_style STYLE_DRAGGING
216                         dragging = true
217                         drag_from = xy
218                 else
219                         deselect_all()
220                 return false
221         mouseup = (e) ->
222                 mousemove e
223                 if dragging
224                         for id, w of selected
225                                 if w.y < supply_height
226                                         w.destruct()
227                                         delete selected[id]
228                                 else
229                                         w.set_style STYLE_SELECTED
230                 dragging = false
231                 return false
232         prev_hover = null
233         mousemove = (e) ->
234                 xy = svg_event_to_xy e
235                 if dragging
236                         return if drag_from.x is xy.x and drag_from.y is xy.y
237                         rel_x = xy.x - drag_from.x
238                         rel_y = xy.y - drag_from.y
239                         drag_from = xy
240                         for id, w of selected
241                                 w.move x: w.x + rel_x, y: w.y + rel_y
242                 else
243                         hover = closest_widget on_canvas, xy
244                         unless hover?
245                                 hover = closest_widget supply, xy
246                         if hover != prev_hover
247                                 if prev_hover?
248                                         # FIXME
249                                         if selected[prev_hover.id]?
250                                                 prev_hover.set_style STYLE_SELECTED
251                                         else
252                                                 prev_hover.set_style STYLE_NORMAL
253                                 if hover?
254                                         hover.set_style STYLE_HOVER
255                                 prev_hover = hover
256                 return false
257         $svg.mousedown mousedown
258         $svg.mouseup mouseup
259         $svg.mousemove mousemove
260         $(document).on 'keyup keydown', (e) ->
261                 shift_key_down = e.shiftKey
262                 return true
263         #($ document).keydown (e) ->
264
265 $ init