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