# settings 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) -> for tag, attrs of json el = document.createElementNS 'http://www.w3.org/2000/svg', tag for k, v of attrs if k is 'children' for child in v el.appendChild json_to_svg child else el.setAttribute k, v return el next_widget_id = 0 # public vars: x, y, width, height, el class Widget # required args: svg constructor: (args) -> @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: @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 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 = -> svg_offset = null $container = $ '.crayon_mockup' svg = json_to_svg svg: width: width, height: height, viewBox: "0 0 #{width} #{height}" $svg = $ svg $container.append $svg svg.appendChild json_to_svg filter: id: 'crayon', filterUnits: 'userSpaceOnUse' x: '-5%', y: '-5%', height: '110%', width: '110%' children: [ { feTurbulence: baseFrequency: '.3', numOctaves: '2', type: 'fractalNoise' } { feDisplacementMap: scale: '6', xChannelSelector: 'R', in: 'SourceGraphic' } ] # create canvas border svg.appendChild json_to_svg rect: x: 1 y: supply_height + 1 width: width - 2 height: height - 2 - supply_height class: 'canvas_border' supply = [ 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 } # 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) -> prox = PROX_MAX + 1 closest = null for w in widgets 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 svg_offset? svg_offset = $svg.offset() return { x: Math.round(e.pageX - svg_offset.left) y: Math.round(e.pageY - svg_offset.top) } 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 if closest? closest = closest.clone() on_canvas.push closest else closest = closest_widget on_canvas, xy if closest? deselect_all except: closest selected = [closest] closest.set_style STYLE_DRAGGING dragging = true dragging_offset.x = closest.x - xy.x dragging_offset.y = closest.y - xy.y else deselect_all() return false 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 return false prev_hover = null mousemove = (e) -> xy = svg_event_to_xy e if dragging 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) -> $ init