1 # Copyright 2015 Jason Woofenden
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
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
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/>.
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
41 set_style_class = (args) ->
42 args.el.setAttribute 'class', "#{args.class} #{STYLE_TO_CLASS[args.style]}"
44 # json (compiled to javascript and minified) is ~8% smaller than the resulting xml
45 json_to_svg = (json) ->
46 for tag, attrs of json
47 el = document.createElementNS 'http://www.w3.org/2000/svg', tag
51 el.appendChild json_to_svg child
52 else if k is 'contents'
53 el.appendChild document.createTextNode v
59 # public vars: x, y, width, height, el
62 constructor: (args) ->
68 @width = args.width ? 50
69 @height = args.height ? 34
70 @style = args.style ? STYLE_NORMAL
72 move: (xy) -> # just move
75 drag: (xy) -> # react to mouse drag (obey constraints, etc.)
77 proximity: (xy) -> # return the square of the distance to your visible bits
82 class Control extends Visible
83 constructor: (args) ->
87 @on_destruct = args.done ? null
92 drag: (args) -> # call this when control point is being manipulated directly
94 proximity: (xy) -> # return the square of the distance to your visible bits
97 return dx * dx + dy * dy
99 class ControlPoint extends Control
100 constructor: (args) ->
102 @el = json_to_svg circle:
106 class: 'control_point normal'
114 @el.setAttribute 'cx', @x
115 @el.setAttribute 'cy', @y
117 class Widget extends Visible
118 #sub-classes are expected to implement all of these:
119 constructor: (args) ->
127 make_controls: -> # create controls, return them
130 console.log 'kill_controls'
135 move: (xy) -> # just move
140 c.move x: c.x + dx, y: c.y + dy
142 class RectWidget extends Widget
143 constructor: (args) ->
146 @el = json_to_svg rect:
158 return new RectWidget @
159 set_style: (style) ->
161 set_style_class el: @el, class: 'box', style: style
164 @el.setAttribute 'x', @x + 1
165 @el.setAttribute 'y', @y + 1
166 proximity: (xy) -> # return the square of the distance to your visible bits
172 if x > @x - CLICK_FUZ and x < @x + @width + CLICK_FUZ
174 if y < @y + @height / 2
177 new_prox = @y + @height - y
181 if y > @y - CLICK_FUZ and y < @y + @height + CLICK_FUZ
183 if x < @x + @width / 2
186 new_prox = @x + @width - x
190 if in_x and in_y and prox > PROX_MAX
195 @el.setAttribute 'width', @width
197 @el.setAttribute 'height', @height
198 if @controls.length > 1
199 @controls[1].move x: @x + @width, y: @y + @height
200 make_controls: (args) -> # create controls, return them
201 console.log 'make_controls'
202 if @controls.length > 0
203 console.log "warning: re-adding controls"
207 new ControlPoint svg: @svg, x: @x, y: @y, done: args.done, drag: (xy) ->
210 w.resize w: w.width - dx, h: w.height - dy
211 w.move x: w.x + dx, y: w.y + dy
212 new ControlPoint svg: @svg, x: @x + @width, y: @y + @height, done: args.done, drag: (xy) ->
215 w.resize w: w.width + dx, h: w.height + dy
219 # called automatically on domcontentloaded
222 $container = $ '.crayon_mockup'
223 svg = json_to_svg svg: width: width, height: height, viewBox: "0 0 #{width} #{height}"
225 $container.append $svg
226 svg.appendChild json_to_svg filter:
227 id: 'crayon', filterUnits: 'userSpaceOnUse'
228 x: '-5%', y: '-5%', height: '110%', width: '110%'
230 { feTurbulence: baseFrequency: '.3', numOctaves: '2', type: 'fractalNoise' }
231 { feDisplacementMap: scale: '6', xChannelSelector: 'R', in: 'SourceGraphic' }
233 svg.appendChild json_to_svg style:
235 contents: '.box.normal,.box.hover,.box.selected{filter: url(#crayon)}'
237 # create canvas border
238 svg.appendChild json_to_svg rect:
242 height: height - 2 - supply_height
243 class: 'canvas_border'
247 { width: 40, height: 40 }
248 { width: 12, height: 50 }
249 { width: 70, height: 12 }
251 widget = new RectWidget {
254 x: 30 + i * 90 + (70 - args.width) / 2
255 y: (supply_height - args.height) / 2
258 supply[widget.id] = widget
264 editing = {} # has controls
266 drag_from = x: 0, y: 0 # mouse was here at last frame of drag
267 shift_key_down = false
274 s.set_style STYLE_NORMAL
275 if s.type is TYPE_WIDGET
277 delete selected[s.id]
279 deselect_all = (args) ->
280 except = args?.except ? null
281 for id, s of selected
284 select_only = (sel) ->
285 deselect_all except: sel
286 return if selected[sel.id]?
287 selected[sel.id] = sel
288 select_also = (sel) ->
289 return if selected[sel.id]?
290 sel_type = selected_type()
291 if sel_type isnt sel.type
293 selected[sel.id] = sel
295 find_closest = (widgets, xy) ->
299 new_prox = w.proximity xy
306 svg_event_to_xy = (e) ->
308 svg_offset = $svg.offset()
310 x: Math.round(e.pageX - svg_offset.left)
311 y: Math.round(e.pageY - svg_offset.top)
315 if dragging # two mousedowns in a row?! it happens
317 xy = svg_event_to_xy e
318 if xy.y < supply_height
319 closest = find_closest supply, xy
321 closest = closest.clone()
322 on_canvas[closest.id] = closest
324 closest = find_closest controls, xy
326 closest = find_closest on_canvas, xy
329 if selected[closest.id]
331 # TODO start detection of a click that doesn't drag (to shrink selection)
332 else if xy.y < supply_height
333 # dragging a new thing in
335 else if shift_key_down
339 for id, s of selected
340 s.set_style STYLE_DRAGGING
349 for id, w of selected
350 if w.y < supply_height
355 w.set_style STYLE_SELECTED
356 if w.type is TYPE_WIDGET
357 cs = w.make_controls done: (c) ->
359 delete controls[c.id]
363 delete selected[w.id]
368 xy = svg_event_to_xy e
370 return if drag_from.x is xy.x and drag_from.y is xy.y
371 rel_x = xy.x - drag_from.x
372 rel_y = xy.y - drag_from.y
374 for id, w of selected
375 w.drag x: w.x + rel_x, y: w.y + rel_y
377 hover = find_closest on_canvas, xy
379 hover = find_closest supply, xy
380 if hover != prev_hover
383 if selected[prev_hover.id]?
384 prev_hover.set_style STYLE_SELECTED
386 prev_hover.set_style STYLE_NORMAL
388 hover.set_style STYLE_HOVER
391 $svg.mousedown mousedown
393 $svg.mousemove mousemove
394 $(document).on 'keyup keydown', (e) ->
395 shift_key_down = e.shiftKey
397 #($ document).keydown (e) ->