width = 800
height = 600
supply_height = 96
+CLICK_FUZ = 10 # this far away from things is close enough to be "clicked on"
+PROX_MAX = CLICK_FUZ * CLICK_FUZ
+
+# constants
+STYLE_NORMAL = 0
+STYLE_SELECTED = 1
+STYLE_HOVER = 2
+STYLE_EDITING = 3
+STYLE_DRAGGING = 4
# json (compiled to javascript and minified) is ~8% smaller than the resulting xml
json_to_svg = (json) ->
el.setAttribute k, v
return el
+next_widget_id = 0
# public vars: x, y, width, height, el
class Widget
+ # required args: svg
constructor: (args) ->
- @x = args?.x ? 1
- @y = args?.y ? 1
- @width = args?.width ? 50
- @height = args?.height ? 34
+ @id = next_widget_id
+ next_widget_id += 1
+ @svg = args.svg
+ @style = args.style ? STYLE_NORMAL
+ @x = args.x ? 1
+ @y = args.y ? 1
+ @width = args.width ? 50
+ @height = args.height ? 34
+ destruct: ->
+ clone: ->
+ return new Widget @
move: (args) ->
@x = args.x
@y = args.y
+ proximity: (xy) -> # return the square of the distance to your visible bits
+ return PROX_MAX + 1
+ set_style: (style) ->
+ return if @style is style
+ if style is STYLE_NORMAL
+ @el.setAttribute 'style', 'filter: url(#crayon)'
+ else
+ if @style is STYLE_NORMAL
+ @el.setAttribute 'style', ''
+ switch style
+ when STYLE_NORMAL
+ @el.setAttribute 'class', "#{@css_class}"
+ when STYLE_SELECTED
+ @el.setAttribute 'class', "#{@css_class} selected"
+ when STYLE_HOVER
+ @el.setAttribute 'class', "#{@css_class} hover"
+ when STYLE_DRAGGING
+ @el.setAttribute 'class', "#{@css_class} dragging"
+ # FIXME when STYLE_EDITING
+ @style = style
+ controls: -> # create controls, return them
+ return []
+ hide_controls: ->
+
class RectWidget extends Widget
constructor: (args) ->
super args
+ @css_class = 'box'
@el = json_to_svg rect:
x: @x + 1
- y: @x + 1
+ y: @y + 1
width: @width - 2
height: @height - 2
class: 'box'
+ style: if @style is STYLE_NORMAL then 'filter: url(#crayon)' else ''
+ @svg.appendChild @el
+ destruct: ->
+ if @el?
+ @svg.removeChild @el
+ clone: ->
+ return new RectWidget @
move: (args) ->
super args
@el.setAttribute 'x', @x + 1
@el.setAttribute 'y', @y + 1
- clone: ->
- return new RectWidget @
+ proximity: (xy) -> # return the square of the distance to your visible bits
+ x = xy.x
+ y = xy.y
+ prox = PROX_MAX + 1
+ in_x = false
+ in_y = false
+ if x > @x - CLICK_FUZ and x < @x + @width + CLICK_FUZ
+ in_x = true
+ if y < @y + @height / 2
+ new_prox = @y - y
+ else
+ new_prox = @y + @height - y
+ new_prox *= new_prox
+ if new_prox < prox
+ prox = new_prox
+ if y > @y - CLICK_FUZ and y < @y + @height + CLICK_FUZ
+ in_y = true
+ if x < @x + @width / 2
+ new_prox = @x - x
+ else
+ new_prox = @x + @width - x
+ new_prox *= new_prox
+ if new_prox < prox
+ prox = new_prox
+ if in_x and in_y and prox > PROX_MAX
+ prox = PROX_MAX - 1
+ return prox
+ controls: -> # create controls, return them
+ return []
+ hide_controls: ->
# called automatically on domcontentloaded
init = ->
- offset = null
- selected = []
- on_canvas = []
- dragging = false
+ svg_offset = null
$container = $ '.crayon_mockup'
svg = json_to_svg svg: width: width, height: height, viewBox: "0 0 #{width} #{height}"
$svg = $ svg
class: 'canvas_border'
supply = [
- new RectWidget()
- new RectWidget width: 12, height: 50
- new RectWidget width: 70, height: 12
+ new RectWidget svg: svg
+ new RectWidget svg: svg, width: 12, height: 50
+ new RectWidget svg: svg, width: 70, height: 12
]
for widget, i in supply
widget.move {
x: 30 + i * 90 + (70 - widget.width) / 2
y: (supply_height - widget.height) / 2
}
- svg.appendChild widget.el
- # todo ask widgets
+ # editor state
+ on_canvas = []
+ selected = []
+ editing = [] # has controls
+ dragging = false
+ dragging_offset = x: 0, y: 0 # from mouse x,y -> widget x,y
+
+ deselect_all = (args) ->
+ except = args?.except ? null
+ found = false
+ for w in selected
+ if w is except
+ found = true
+ else
+ w.set_style STYLE_NORMAL
+ if found
+ selected = [except]
+ else
+ selected = []
closest_widget = (widgets, xy) ->
- closest = [10000, null]
+ prox = PROX_MAX + 1
+ closest = null
for w in widgets
- x = w.x + w.width / 2 - xy.x
- y = w.y + w.height / 2 - xy.y
- dist2 = x * x + y * y
- if dist2 < closest[0]
- closest = [dist2, w]
- if closest[1]?
+ new_prox = w.proximity xy
+ if new_prox < prox
+ prox = new_prox
+ closest = w
+ if prox < PROX_MAX
return closest
return null
svg_event_to_xy = (e) ->
- unless offset?
- offset = $svg.offset()
+ unless svg_offset?
+ svg_offset = $svg.offset()
return {
- x: Math.round(e.pageX - offset.left)
- y: Math.round(e.pageY - offset.top)
+ x: Math.round(e.pageX - svg_offset.left)
+ y: Math.round(e.pageY - svg_offset.top)
}
- $svg.mousedown (e) ->
+ mousedown = (e) ->
+ mousemove e
+ if dragging # two mousedowns in a row?! it happens
+ return mouseup e
xy = svg_event_to_xy e
if xy.y < supply_height
closest = closest_widget supply, xy
- console.log closest
if closest?
- closest[1] = closest[1].clone()
- svg.appendChild closest[1].el
+ closest = closest.clone()
+ on_canvas.push closest
else
closest = closest_widget on_canvas, xy
if closest?
- selected = [closest[1]]
+ deselect_all except: closest
+ selected = [closest]
+ closest.set_style STYLE_DRAGGING
dragging = true
- mousemove e
+ dragging_offset.x = closest.x - xy.x
+ dragging_offset.y = closest.y - xy.y
+ else
+ deselect_all()
return false
- $svg.mouseup (e) ->
+ mouseup = (e) ->
mousemove e
+ if dragging
+ for w in selected
+ if w.y < supply_height
+ w.destruct()
+ else
+ w.set_style STYLE_SELECTED
dragging = false
- if selected[0]?
- if selected[0].y > supply_height
- on_canvas.push selected[0]
- else
- svg.removeChild selected[0].el
- selected = []
return false
+ prev_hover = null
mousemove = (e) ->
+ xy = svg_event_to_xy e
if dragging
- xy = svg_event_to_xy e
- xy.x -= selected[0].width / 2
- xy.y -= selected[0].height / 2
+ xy.x += dragging_offset.x
+ xy.y += dragging_offset.y
selected[0].move xy
+ else
+ hover = closest_widget on_canvas, xy
+ unless hover?
+ hover = closest_widget supply, xy
+ if hover != prev_hover
+ prev_hover = hover
+ for w in selected
+ if w.style is STYLE_HOVER and w isnt hover
+ w.set_style STYLE_SELECTED
+ for w in supply
+ if w.style is STYLE_HOVER and w isnt hover
+ w.set_style STYLE_NORMAL
+ for w in on_canvas
+ if w.style is STYLE_HOVER and w isnt hover
+ w.set_style STYLE_NORMAL
+ if hover
+ hover.set_style STYLE_HOVER
return false
+ $svg.mousedown mousedown
+ $svg.mouseup mouseup
$svg.mousemove mousemove
#($ document).keydown (e) ->