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