JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
code cleanup
[crayon_mockup.git] / main.coffee
1 # Copyright 2015 Jason Woofenden
2 #
3 # This program is free software: you can redistribute it and/or modify it under
4 # the terms of the GNU General Public License as published by the Free Software
5 # Foundation, either version 3 of the License, or (at your option) any later
6 # version.
7 #
8 # This program is distributed in the hope that it will be useful, but WITHOUT
9 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10 # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
11 # details.
12 #
13 # You should have received a copy of the GNU General Public License along with
14 # this program.  If not, see <http://www.gnu.org/licenses/>.
15
16
17 # settings
18 width = 800
19 height = 600
20 supply_height = 96
21 CLICK_FUZ = 10 # this far away from things is close enough to be "clicked on"
22 PROX_MAX = CLICK_FUZ * CLICK_FUZ
23 PROX_TOO_FAR = PROX_MAX + 1 # no need to be precice when it's too far
24
25 # constants
26 STATES = {
27         NORMAL:   { txt: 'normal' }
28         SELECTED: { txt: 'selected' }
29         DRAGGING: { txt: 'dragging' }
30         EDITING:  { txt: 'editing' }
31 }
32
33 # json (compiled to javascript and minified) is ~8% smaller than the resulting xml
34 json_to_svg = (json) ->
35         for tag, attrs of json
36                 el = document.createElementNS 'http://www.w3.org/2000/svg', tag
37                 for k, v of attrs
38                         if k is 'children'
39                                 for child in v
40                                         el.appendChild json_to_svg child
41                         else if k is 'contents'
42                                 el.appendChild document.createTextNode v
43                         else
44                                 el.setAttribute k, v
45         return el
46
47 resizer_nw = (widget) ->
48         return (dxy) ->
49                 widget.resize w: widget.width - dxy.x, h: widget.height - dxy.y
50                 widget.move x: widget.x + dxy.x, y: widget.y + dxy.y
51 resizer_n = (widget) ->
52         return (dxy) ->
53                 widget.resize w: widget.width, h: widget.height - dxy.y
54                 widget.move x: widget.x, y: widget.y + dxy.y
55 resizer_ne = (widget) ->
56         return (dxy) ->
57                 widget.resize w: widget.width + dxy.x, h: widget.height - dxy.y
58                 widget.move x: widget.x, y: widget.y + dxy.y
59 resizer_e = (widget) ->
60         return (dxy) ->
61                 widget.resize w: widget.width + dxy.x, h: widget.height
62 resizer_se = (widget) ->
63         return (dxy) ->
64                 widget.resize w: widget.width + dxy.x, h: widget.height + dxy.y
65 resizer_s = (widget) ->
66         return (dxy) ->
67                 widget.resize w: widget.width, h: widget.height + dxy.y
68 resizer_sw = (widget) ->
69         return (dxy) ->
70                 widget.resize w: widget.width - dxy.x, h: widget.height + dxy.y
71                 widget.move x: widget.x + dxy.x, y: widget.y
72 resizer_w = (widget) ->
73         return (dxy) ->
74                 widget.resize w: widget.width - dxy.x, h: widget.height
75                 widget.move x: widget.x + dxy.x, y: widget.y
76 resizers = [
77         resizer_nw
78         resizer_n
79         resizer_ne
80         resizer_e
81         resizer_se
82         resizer_s
83         resizer_sw
84         resizer_w
85 ]
86 resizer_shapes = [
87         -> return "M#{@x - 5} #{@y - 5}h6l-2 2 4 4 2 -2v6h-6l2-2-4-4-2 2z"
88         -> return "M #{@x},#{@y - 7} l 4,4 -2.5,0 0,5 2.5,0 -4,4 -4,-4 2.5,0 0,-5 -2.5,0 z"
89         -> return "M#{@x + 5} #{@y - 5}v6l-2-2-4 4 2 2h-6v-6l2 2 4-4-2-2z"
90         -> return "M #{@x - 7},#{@y} l 4,-4 0,2.5 5,0 0,-2.5 4,4 -4,4 0,-2.5 -5,0 0,2.5 z"
91 ]
92 shape_node_move = -> "M#{@x} #{@y - 9}l-2.5 4.5h2v2.404a2.156 2.156 0 0 0-1.596 1.596h-2.404v-2l-4.5 2.5 4.5 2.5v-2h2.404a2.156 2.156 0 0 0 1.596 1.596v2.404h-2l2.5 4.5 2.5-4.5h-2v-2.404a2.156 2.156 0 0 0 1.596-1.596h2.404v2l4.5-2.5-4.5-2.5v2h-2.404a2.156 2.156 0 0 0-1.596-1.596v-2.404h2l-2.5-4.5z"
93
94 next_widget_id = 0
95 # public vars: x, y, width, height, el
96 class Visible
97         # required args: svg
98         constructor: (args) ->
99                 @id = next_widget_id
100                 next_widget_id += 1
101                 @svg = args.svg
102                 @x = args.x ? 1
103                 @y = args.y ? 1
104                 @width = args.width ? 50
105                 @height = args.height ? 34
106                 @state = args.state ? STATES.NORMAL
107                 @hover = false
108         destruct: ->
109         update_class: ->
110                 css_class = "#{@css_class} #{@state.txt}"
111                 if @hover
112                         css_class += " hover"
113                 @el.setAttribute 'class', css_class
114         set_hover: (bool) ->
115                 if bool != @hover
116                         @hover = bool
117                         @update_class()
118         move: (xy) -> # just move
119                 @x = xy.x
120                 @y = xy.y
121         drag: (dxy) -> # react to mouse drag (obey constraints, etc.)
122                 @move x: @x + dxy.x, y: @y + dxy.y
123         proximity: (xy) -> # return the square of the distance to your visible bits
124                 return PROX_TOO_FAR
125         set_state: (state) ->
126                 @state = state
127
128 class Control extends Visible
129         constructor: (args) ->
130                 super args
131                 @on_drag = args.drag
132                 @on_destruct = args.done ? null
133         destruct: ->
134                 super()
135                 if @on_destruct?
136                         @on_destruct @
137         drag: (args) -> # call this when control point is being manipulated directly
138                 @on_drag args
139         proximity: (xy) -> # return the square of the distance to your visible bits
140                 dx = xy.x - @x
141                 dy = xy.y - @y
142                 return dx * dx + dy * dy
143 class ControlPath extends Control
144         constructor: (args) ->
145                 super args
146                 @css_class = 'control_point'
147                 @make_path = args.shape
148                 @el = json_to_svg path:
149                         d: @make_path()
150                         class: 'control_point normal'
151                 @svg.appendChild @el
152         destruct: ->
153                 super()
154                 if @el?
155                         @svg.removeChild @el
156         move: (args) ->
157                 super args
158                 @el.setAttribute 'd', @make_path()
159
160 class Widget extends Visible
161         #sub-classes are expected to implement all of these:
162         constructor: (args) ->
163                 super args
164                 @controls = []
165         destruct: ->
166                 @kill_controls()
167         clone: ->
168                 return new Widget @
169         make_controls: -> # create controls, return them
170                 return []
171         kill_controls: ->
172                 for c in @controls
173                         c.destruct()
174                 @controls = []
175                 return
176         move: (xy) -> # just move
177                 dx = xy.x - @x
178                 dy = xy.y - @y
179                 super xy
180                 for c in @controls
181                         c.move x: c.x + dx, y: c.y + dy
182         set_state: (state) ->
183                 return if @state is state
184                 if @state is STATES.EDITING
185                         @kill_controls()
186                 super state
187
188 class RectWidget extends Widget
189         constructor: (args) ->
190                 super args
191                 @css_class = 'box'
192                 @el = json_to_svg rect:
193                         x: @x + 1
194                         y: @y + 1
195                         width: @width - 2
196                         height: @height - 2
197                         class: 'box normal'
198                 @svg.appendChild @el
199         destruct: ->
200                 super()
201                 if @el?
202                         @svg.removeChild @el
203         clone: ->
204                 return new RectWidget @
205         as_array: ->
206                 return [@x, @y, @width, @height]
207         from_array: (args, a) ->
208                 args.x = a[0]
209                 args.y = a[1]
210                 args.width = a[2]
211                 args.height = a[3]
212                 return new RectWidget args
213         set_state: (state) ->
214                 super state
215                 @update_class()
216         move: (args) ->
217                 super args
218                 @el.setAttribute 'x', @x + 1
219                 @el.setAttribute 'y', @y + 1
220                 @reposition_controls()
221         proximity: (xy) -> # return the square of the distance to your visible bits
222                 x = xy.x
223                 y = xy.y
224                 prox = PROX_TOO_FAR
225                 in_x = false
226                 in_y = false
227                 if x > @x - CLICK_FUZ and x < @x + @width + CLICK_FUZ
228                         in_x = true
229                         if y < @y + @height / 2
230                                 new_prox = @y - y
231                         else
232                                 new_prox = @y + @height - y
233                         new_prox *= new_prox
234                         if new_prox < prox
235                                 prox = new_prox
236                 if y > @y - CLICK_FUZ and y < @y + @height + CLICK_FUZ
237                         in_y = true
238                         if x < @x + @width / 2
239                                 new_prox = @x - x
240                         else
241                                 new_prox = @x + @width - x
242                         new_prox *= new_prox
243                         if new_prox < prox
244                                 prox = new_prox
245                 # "hit" anything inside
246                 #if in_x and in_y and prox > PROX_MAX
247                 #       prox = PROX_MAX - 1
248                 return prox
249         resize: (wh) ->
250                 dw = wh.w - @width
251                 dh = wh.h - @height
252                 @width = wh.w
253                 @el.setAttribute 'width', @width - 2
254                 @height = wh.h
255                 @el.setAttribute 'height', @height - 2
256                 @reposition_controls()
257         reposition_controls: ->
258                 if @controls.length > 1
259                         positions = @control_positions()
260                         for i in [0...positions.length]
261                                 @controls[i].move x: positions[i].x, y: positions[i].y
262         control_positions: ->
263                 gap = 7
264                 mgap = 9
265                 w2p = Math.floor(@width / 2) + 0.5
266                 h2p = Math.floor(@height / 2) + 0.5
267                 return [
268                         { x: @x - gap, y: @y - gap }
269                         { x: @x + w2p, y: @y - mgap }
270                         { x: @x + @width + gap, y: @y - gap }
271                         { x: @x + @width + mgap, y: @y + h2p }
272                         { x: @x + @width + gap, y: @y + @height + gap }
273                         { x: @x + w2p, y: @y + @height + mgap }
274                         { x: @x - gap, y: @y + @height + gap }
275                         { x: @x - mgap, y: @y + h2p }
276                 ]
277         make_controls: (args) -> # create controls, return them
278                 if @controls.length > 0
279                         if console?.log?
280                                 console.log "warning: re-adding controls"
281                         @kill_controls()
282                 positions = @control_positions()
283                 for i in [0...positions.length]
284                         @controls.push new ControlPath {
285                                 svg: @svg
286                                 x: positions[i].x
287                                 y: positions[i].y
288                                 done: args.done
289                                 drag: resizers[i] @
290                                 shape: resizer_shapes[i % resizer_shapes.length]
291                         }
292                 return @controls
293
294 class PolylineWidget extends Widget
295         constructor: (args) ->
296                 super args
297                 @css_class = 'polyline'
298                 @nodes = []
299                 for n in args.nodes
300                         @nodes.push x: n.x, y: n.y
301                 @el = json_to_svg path:
302                         d: @my_path_d()
303                         class: 'polyline normal'
304                 @svg.appendChild @el
305         destruct: ->
306                 super()
307                 if @el?
308                         @svg.removeChild @el
309         as_array: ->
310                 ret = []
311                 for n in @nodes
312                         ret.push @x + n.x
313                         ret.push @y + n.y
314                 return ret
315         from_array: (args, a) ->
316                 args.x = a[0]
317                 args.y = a[1]
318                 args.nodes = []
319                 while a.length
320                         xy = {}
321                         xy.x = a.shift() - args.x
322                         xy.y = a.shift() - args.y
323                         args.nodes.push xy
324                 return new PolylineWidget args
325         clone: ->
326                 return new PolylineWidget @
327         my_path_d: ->
328                 ret = ''
329                 for n in @nodes
330                         if ret is ''
331                                 ret += 'M'
332                         else
333                                 ret += 'L'
334                         ret += n.x + @x
335                         ret += ' '
336                         ret += n.y + @y
337                 return ret
338         set_state: (state) ->
339                 super state
340                 @update_class()
341         move: (args) ->
342                 super args
343                 @el.setAttribute 'd', @my_path_d()
344                 @reposition_controls()
345         proximity: (xy) -> # return the square of the distance to your visible bits
346                 prox = PROX_TOO_FAR
347                 for n, i in @nodes
348                         dx = @x + n.x - xy.x
349                         dy = @y + n.y - xy.y
350                         p = dx * dx + dy * dy
351                         if p < prox
352                                 prox = p
353                         if i > 0
354                                 l1x = @x + @nodes[i-1].x
355                                 l1y = @y + @nodes[i-1].y
356                                 l2x = @x + @nodes[i].x
357                                 l2y = @y + @nodes[i].y
358                                 ldx = l2x - l1x
359                                 ldy = l2y - l1y
360                                 if ldx is 0 # vertical line
361                                         if (xy.y < l1y) is (xy.y < l2y)
362                                                 continue
363                                         dx = l1x - xy.x
364                                         p = dx * dx
365                                 else if ldy is 0 # horizontal line
366                                         if (xy.x < l1x) is (xy.x < l2x)
367                                                 continue
368                                         dy = l1y - xy.y
369                                         p = dy * dy
370                                 else # slanty line
371                                         # https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
372                                         a = ldy
373                                         b = -1 * ldx
374                                         c = l2x * l1y - l2y * l1x
375                                         y_on_line = (a * (a * xy.y - b * xy.x) - b * c) / (a * a + b * b)
376                                         if (y_on_line < l1y) is (y_on_line < l2y)
377                                                 continue
378                                         p = (a * xy.x + b * xy.y + c) / Math.sqrt(a * a + b * b)
379                                         p *= p
380
381                                 if p < prox
382                                         prox = p
383                 return prox
384         resize: (wh) ->
385                 # FIXME (apply to more than just 2nd node)
386                 @nodes[1].x = wh.w
387                 @nodes[1].y = wh.h
388                 @width = wh.w
389                 @height = wh.h
390                 @el.setAttribute 'd', @my_path_d()
391                 @reposition_controls()
392                 return
393         node_dragger: (i) ->
394                 if i is 0
395                         return (dxy) =>
396                                 for j in [1...@nodes.length]
397                                         @nodes[j].x -= dxy.x
398                                         @nodes[j].y -= dxy.y
399                                 @move x: @x + dxy.x, y: @y + dxy.y
400                 return (dxy) =>
401                         @nodes[i].x += dxy.x
402                         @nodes[i].y += dxy.y
403                         @el.setAttribute 'd', @my_path_d()
404                         @reposition_controls()
405         reposition_controls: ->
406                 if @controls.length > 1
407                         positions = @control_positions()
408                         for i in [0...positions.length]
409                                 @controls[i].move x: positions[i].x, y: positions[i].y
410                 return
411         control_positions: ->
412                 ret = []
413                 for n in @nodes
414                         ret.push x: @x + n.x, y: @y + n.y
415                 return ret
416         make_controls: (args) -> # create controls, return them
417                 if @controls.length > 0
418                         if console?.log?
419                                 console.log "warning: re-adding line controls"
420                         @kill_controls()
421                 positions = @control_positions()
422                 for i in [0...positions.length]
423                         @controls.push new ControlPath {
424                                 svg: @svg
425                                 x: positions[i].x
426                                 y: positions[i].y
427                                 done: args.done
428                                 drag: @node_dragger i
429                                 shape: shape_node_move
430                         }
431                 return @controls
432
433 CSS_CLASS_TO_PICKLE_TYPE = {
434         box: '0'
435         polyline: '1'
436 }
437 PICKLE_TYPE_TO_WIDGET_CLASS = [
438         RectWidget
439         PolylineWidget
440 ]
441 # called automatically on domcontentloaded
442 init = ->
443         svg_offset = null
444         $container = $ '.crayon_mockup'
445         svg = json_to_svg svg: width: width, height: height, viewBox: "0 0 #{width} #{height}"
446         $svg = $ svg
447         $container.append $svg
448         svg.appendChild json_to_svg filter:
449                 id: 'crayon', filterUnits: 'userSpaceOnUse'
450                 x: '-5%', y: '-5%', height: '110%', width: '110%'
451                 children: [
452                         { feTurbulence: baseFrequency: '.3', numOctaves: '2', type: 'fractalNoise' }
453                         { feDisplacementMap: scale: '6', xChannelSelector: 'R', in: 'SourceGraphic' }
454                 ]
455         svg.appendChild json_to_svg style:
456                 type: 'text/css'
457                 contents: '.box.normal,.polyline.normal{filter: url(#crayon)}'
458
459         # create canvas border
460         svg.appendChild json_to_svg rect:
461                 x: 1
462                 y: supply_height + 1
463                 width: width - 2
464                 height: height - 2 - supply_height
465                 class: 'canvas_border'
466
467         supply = {}
468         supply_count = 0
469         supply_add = (type, args) ->
470                 args.x ?= 0
471                 args.y ?= 0
472                 args.x += 30 + supply_count * 90
473                 args.y += (supply_height - 50) / 2
474                 args.svg = svg
475                 w = new type args
476                 supply[w.id] = w
477                 supply_count += 1
478         supply_add RectWidget, width: 50, height: 50
479         supply_add PolylineWidget, y: 25, nodes: [{x: 0, y: 0}, {x: 50, y: 0}]
480         supply_add PolylineWidget, x: 25, nodes: [{x: 0, y: 0}, {x: 0, y: 50}]
481         supply_add PolylineWidget, x: 10, nodes: [{x: 0, y: 0}, {x: 15, y: 50}, {x: 30, y: 0}]
482         supply_add PolylineWidget, y: 50, nodes: [{x: 0, y: 0}, {x: 17, y: -50}, {x: 33, y: 0}, {x: 50, y: -50}]
483
484         # editor state
485         controls_layer = { all: {}, selected: {} }
486         widget_layer = { all: {}, selected: {}, editing: null }
487         layers = [controls_layer, widget_layer]
488         hovered = null # can be in any layer
489         dragging = false # mouse state
490         drag_layer = null
491         drag_from = x: 0, y: 0 # mouse was here at last frame of drag
492         shift_key_down = false
493
494         lc = "abcdefghijklmnopqrstuvwxyz"
495         uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
496         cc_lca = lc.charCodeAt 0
497         cc_lcz = lc.charCodeAt 25
498         cc_uca = uc.charCodeAt 0
499         cc_ucz = uc.charCodeAt 25
500         cc_0 = '0'.charCodeAt 0
501         cc_9 = '9'.charCodeAt 0
502         # takes an array of positive integers, and encodes it as a string
503         pickle = (a) ->
504                 ret = ''
505                 for i in a
506                         cs = lc
507                         r = ''
508                         while i > 0 or r is ''
509                                 digit = i % cs.length
510                                 i -= digit
511                                 i /= cs.length
512                                 r = cs.charAt(digit) + r
513                                 cs = uc
514                         ret += r
515                 return ret
516         pickle_widgets = ->
517                 ret = '0' # version of this encoding scheme
518                 for id, w of widget_layer.all
519                         if CSS_CLASS_TO_PICKLE_TYPE[w.css_class]?
520                                 ret += CSS_CLASS_TO_PICKLE_TYPE[w.css_class]
521                                 ret += pickle w.as_array()
522                 return ret
523         save = ->
524                 window.location.hash = pickle_widgets()
525         load = (str) ->
526                 return if str.charAt(1) isnt '0'
527                 wtype = null
528                 args = []
529                 ii = 0
530                 load_1 = (next_type) ->
531                         unless wtype?
532                                 if next_type?
533                                         wtype = next_type
534                                 return
535                         w = PICKLE_TYPE_TO_WIDGET_CLASS[wtype]::from_array svg: svg, args
536                         widget_layer.all[w.id] = w
537                         wtype = next_type
538                         args = []
539                         ii = 0
540                 for i in [2...str.length]
541                         c = str.charCodeAt(i)
542                         if cc_0 <= c <= cc_9
543                                 load_1 c - cc_0
544                         else if cc_lca <= c <= cc_lcz
545                                 ii *= lc.length
546                                 ii += c - cc_lca
547                                 args.push ii
548                                 ii = 0
549                         else if cc_uca <= c <= cc_ucz
550                                 ii *= lc.length
551                                 ii += c - cc_uca
552                 load_1 null
553         stop_editing = ->
554                 if widget_layer.editing
555                         widget_layer.editing.kill_controls()
556                         widget_layer.editing = null
557         deselect = (layer, s) ->
558                 return unless layer.selected[s.id]?
559                 s.set_state STATES.NORMAL
560                 delete layer.selected[s.id]
561                 if widget_layer.editing is s
562                         widget_layer.editing = null
563                 return
564         deselect_all = (layer, except = null) ->
565                 for id, s of layer.selected
566                         deselect layer, s
567                 return
568         _select = (layer, s) -> # don't call this directly, use select_only() or select_also()
569                 s.set_state STATES.SELECTED
570                 layer.selected[s.id] = s
571                 return
572         select_only = (layer, s) ->
573                 deselect_all layer, s
574                 return if layer.selected[s.id]?
575                 _select layer, s
576                 return
577         select_also = (layer, s) ->
578                 return if layer.selected[s.id]?
579                 if layer is widget_layer
580                         stop_editing()
581                 _select layer, s
582                 return
583         find_closest = (widgets, xy) ->
584                 prox = PROX_TOO_FAR
585                 closest = null
586                 for id, w of widgets
587                         new_prox = w.proximity xy
588                         if new_prox < prox
589                                 prox = new_prox
590                                 closest = w
591                 if prox > PROX_MAX
592                         return null
593                 return closest
594         svg_event_to_xy = (e) ->
595                 unless svg_offset?
596                         svg_offset = $svg.offset()
597                 return {
598                         x: Math.round(e.pageX - svg_offset.left)
599                         y: Math.round(e.pageY - svg_offset.top)
600                 }
601         closest_in_layers = (xy) ->
602                 for layer in layers
603                         s = find_closest layer.selected, xy
604                         return layer: layer, s: s if s?
605                         s = find_closest layer.all, xy
606                         return layer: layer, s: s if s?
607                 return null
608         mousedown = (e) ->
609                 hit = null
610                 closest = null
611                 layer = null
612                 mousemove e
613                 if dragging # two mousedowns in a row?! it happens
614                         return mouseup e
615                 xy = svg_event_to_xy e
616                 if xy.y < supply_height
617                         s = find_closest supply, xy
618                         if s?
619                                 hit = {
620                                         s: s.clone()
621                                         layer: widget_layer
622                                 }
623                                 widget_layer.all[hit.s.id] = hit.s
624                 else
625                         hit = closest_in_layers xy
626                 if hit?
627                         if hit.layer.selected[hit.s.id]
628                                 # already selected
629                                 # TODO start detection of a click that doesn't drag (to shrink selection)
630                         else if xy.y < supply_height
631                                 # dragging a new thing in
632                                 select_only hit.layer, hit.s
633                         else if shift_key_down
634                                 select_also hit.layer, hit.s
635                         else
636                                 select_only hit.layer, hit.s
637                         for id, s of hit.layer.selected
638                                 s.set_state STATES.DRAGGING
639                         dragging = true
640                         drag_layer = hit.layer
641                         drag_from = xy
642                 else
643                         deselect_all widget_layer
644                 return
645         mouseup = (e) ->
646                 save()
647                 mousemove e
648                 if dragging
649                         selected_count = 0
650                         for id, s of drag_layer.selected
651                                 if s.y < supply_height and drag_layer is widget_layer
652                                         deselect drag_layer, s
653                                         s.destruct()
654                                         delete drag_layer.all[id]
655                                 else
656                                         selected_count += 1
657                                         s.set_state STATES.SELECTED
658                         if drag_layer is widget_layer and selected_count is 1
659                                 for id, s of drag_layer.selected
660                                         s.set_state STATES.EDITING
661                                         cs = s.make_controls done: (c) ->
662                                                 deselect controls_layer, c
663                                                 delete controls_layer.all[c.id]
664                                         for c in cs
665                                                 controls_layer.all[c.id] = c
666                                         widget_layer.editing = s
667                 dragging = false
668                 return
669         mousemove = (e) ->
670                 xy = svg_event_to_xy e
671                 if dragging
672                         return if drag_from.x is xy.x and drag_from.y is xy.y
673                         rel_x = xy.x - drag_from.x
674                         rel_y = xy.y - drag_from.y
675                         drag_from = xy
676                         for id, w of drag_layer.selected
677                                 w.drag x: rel_x, y: rel_y
678                 else
679                         hit = closest_in_layers xy
680                         if hovered and hovered isnt hit?.s
681                                 hovered.set_hover false
682                         return unless hit?
683                         hovered = hit.s
684                         hovered.set_hover true
685                 return
686         $svg.mousedown (e) ->
687                 mousedown e
688                 return false
689         $svg.mouseup (e) ->
690                 mouseup e
691                 return false
692         $svg.mousemove (e) ->
693                 mousemove e
694                 return false
695         $(document).on 'keyup keydown', (e) ->
696                 shift_key_down = e.shiftKey
697                 return true
698         #($ document).keydown (e) ->
699
700         if window.location.hash
701                 load window.location.hash
702
703 $ init