X-Git-Url: https://jasonwoof.com/gitweb/?p=crayon_mockup.git;a=blobdiff_plain;f=auto.coffee;h=89dfb86fb41b6b69018983ce0364e85a01662b44;hp=8a9033eab630edf3933961325a46dba6a94896bf;hb=af4cde3b88f14635f09f2faf00162b017a2ee7bc;hpb=0b766216669a849a44850a2fd634660e2659abb9 diff --git a/auto.coffee b/auto.coffee index 8a9033e..89dfb86 100644 --- a/auto.coffee +++ b/auto.coffee @@ -1,280 +1,258 @@ # settings -width = 500 -height = 500 +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 -# globals -$svg = null # jquery object for svg element -svg = null # dom object for svg element -$tool_options = null # jquery object for tool options line -selection = [] -svg_ns = 'http://www.w3.org/2000/svg' -cur_tool = null -cur_tool_name = null -mouse = x: 0, y: 0, buttons: [0,0,0] +# constants +STYLE_NORMAL = 0 +STYLE_SELECTED = 1 +STYLE_HOVER = 2 +STYLE_EDITING = 3 +STYLE_DRAGGING = 4 -class EditableThing - constructor: -> - @el = null - click_proximity: (x, y) -> - return null - clicked: -> - update: -> - destructor: -> - if @el? - svg.removeChild @el - @el = null - -class PolyLine extends EditableThing - constructor: (args) -> - super args - @drawing = args?.drawing ? false - @el = document.createElementNS svg_ns, "path" - svg.appendChild @el - @points = args?.points ? [] - @update() - update: (args) -> - if args?.drawing? - @drawing = args.drawing - d = '' - l = '' - sep = 'M' - for loc, i in @points - d += l = "#{sep} #{loc[0]} #{loc[1]}" - sep = ' L' - if args?.to_mouse? - d += l = "#{sep} #{args.mouse_x} #{args.mouse_y}" - if args?.close # FIXME ?remove - d += l = ' z' - if @points.length > 0 - if l is " L #{@points[0][0]} #{@points[0][1]}" - d = d.substr 0, d.length - l.length - d += ' z' - @el.setAttribute "d", d - add_point: (x, y) -> - @points.push [x, y] - close_loop: -> - if @points.length > 2 - @add_point @points[0][0], @points[0][1] - -class Tool - constructor: (args) -> - @button = args.button - if @button? - @button.addClass 'disabled' - @tool_options = args.tool_options - click: (x, y) -> - mousemove: (x, y) -> - keydown: (keycode) -> - disable: -> - if @button? - @button.removeClass 'disabled' - @tool_options.empty() - -class TutorialTool extends Tool - constructor: (args) -> - super args - @paths = [] - choose = [ - [[219,34],[53,141],[96,143],[92,255],[365,257],[362,145],[407,144,'z']], - [[161,118],[144,106],[130,115],[128,152],[140,160],[156,150]], - [[173,107],[169,159],[180,137],[189,133],[193,160]], - [[218,135],[205,144],[205,158],[215,164],[225,156],[225,144,'z']], - [[242,135],[233,145],[233,157],[244,168],[256,158],[254,144,'z']], - [[278,141],[269,135],[261,142],[264,151],[278,153],[281,162],[271,170],[264,163]], - [[291,151],[305,151],[312,143],[299,135],[288,140],[293,160],[302,167],[313,158]], - [[136,208],[121,206],[116,226],[128,233],[136,229],[137,209],[140,233]], - [[160,207],[191,205]], - [[176,184],[174,228],[180,238]], - [[198,216],[187,223],[189,236],[200,242],[210,235],[209,224,'z']], - [[227,216],[216,222],[216,236],[224,244],[237,240],[237,226,'z']], - [[247,187],[249,241],[254,243]] - ] - for c in choose - path = data: c, element: document.createElementNS svg_ns, "path" - update_path path, close: c[c.length - 1][2] is 'z' - svg.appendChild path.element - @paths.push path - @tip = $ " " - @tool_options.append @tip - disable: -> - super() - for p in @paths - svg.removeChild p.element +# 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 -class DrawTool extends Tool +next_widget_id = 0 +# public vars: x, y, width, height, el +class Widget + # required args: svg constructor: (args) -> - super args - @tool_options.append $ "Draw tool helpers:" - @stop_button = $ 'finish line' - @stop_close_button = $ 'close (loop)' - @cancel_button = $ 'cancel line' - @tool_options.append @stop_button - @tool_options.append @stop_close_button - @tool_options.append @cancel_button - @stop_button.click @stop_drawing.bind @ - @stop_close_button.click @stop_close_drawing.bind @ - @cancel_button.click @cancel_drawing.bind @ - @update_helper_buttons() - click: (x, y) -> - if selection.length and not selection[0].drawing? - selection = [] - unless selection.length - selection.push new PolyLine drawing: true - selection[0].add_point x, y - selection[0].update() - @update_helper_buttons() - update_helper_buttons: -> - if selection[0]?.drawing? and selection[0]?.points.length > 0 - @cancel_button.removeClass 'disabled' - else - @cancel_button.addClass 'disabled' - if selection[0]?.drawing? and selection[0]?.points.length > 1 - @stop_button.removeClass 'disabled' + @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 - @stop_button.addClass 'disabled' - if selection[0]?.drawing? and selection[0]?.points.length > 2 - @stop_close_button.removeClass 'disabled' - else - @stop_close_button.addClass 'disabled' - cancel_drawing: -> - if selection[0]?.drawing? - selection[0].destructor() - selection = [] - @update_helper_buttons() - return false - stop_drawing: -> - if selection[0]?.drawing? - if selection[0]?.points.length < 2 - return @cancel_drawing() - if selection[0]? - selection[0].update() - selection = [] - @update_helper_buttons() - return false - stop_close_drawing: -> - if selection[0]?.drawing? - if selection[0]?.points.length < 3 - return @stop_drawing() - selection[0].close_loop() - selection[0].update drawing: false - selection = [] - @update_helper_buttons() - return false - mousemove: (x, y) -> - mouse.x = x - mouse.y = y - if selection[0]?.drawing? - selection[0].update to_mouse: true, mouse_x: x, mouse_y: y - keydown: (keycode) -> - switch keycode - when ('O'.charCodeAt 0), ('0'.charCodeAt 0) - return @stop_close_drawing() - when (' '.charCodeAt 0), 13, 10 - return @stop_drawing() - when 27 - return @cancel_drawing() - disable: -> - super() - @stop_drawing() + 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 EditTool extends Tool +class RectWidget extends Widget constructor: (args) -> super args - args.tool_options.append $ "Oops, the edit tool isn't implemented yet" - -class DeleteTool extends Tool - constructor: (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: -> + super() + if @el? + @svg.removeChild @el + clone: -> + return new RectWidget @ + move: (args) -> super args - args.tool_options.append $ "To delete: click on a line below" - click: (x, y) -> - $svg.find('path:hover').remove() - return false - -update_path = (path, flags) -> - d = '' - sep = 'M' - for loc, i in path.data - d += "#{sep} #{loc[0]} #{loc[1]}" - sep = ' L' - if flags?.to_mouse? - d += "#{sep} #{mouse.x} #{mouse.y}" - if flags?.close - d += " z" - path.element.setAttribute "d", d - -switch_to_tool = (tool_class) -> + @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' - $toolbar = $ '
Tools:
' - $tool_options = $ '
' - $container.append $toolbar - $container.append $tool_options - svg = document.createElementNS svg_ns, 'svg' - svg.setAttribute 'width', width - svg.setAttribute 'height', height - svg.setAttribute 'viewBox', "0 0 #{width} #{height}" + svg = json_to_svg svg: width: width, height: height, viewBox: "0 0 #{width} #{height}" $svg = $ svg $container.append $svg - filter = document.createElementNS svg_ns, 'filter' - filter.setAttribute 'id', 'crayon' - filter.setAttribute 'filterUnits', 'userSpaceOnUse' - filter.setAttribute 'x', '-5%' - filter.setAttribute 'y', '-5%' - filter.setAttribute 'height', '110%' - filter.setAttribute 'width', '110%' - filter_inner = document.createElementNS svg_ns, 'feTurbulence' - filter_inner.setAttribute 'baseFrequency', '.3' - filter_inner.setAttribute 'numOctaves', '2' - filter_inner.setAttribute 'type', 'fractalNoise' - filter.appendChild filter_inner - filter_inner = document.createElementNS svg_ns, 'feDisplacementMap' - filter_inner.setAttribute 'scale', '6' - filter_inner.setAttribute 'xChannelSelector', 'R' - filter_inner.setAttribute 'in', 'SourceGraphic' - filter.appendChild filter_inner - svg.appendChild filter + 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' } + ] - tool_buttons = - tutorial: default: true, factory: TutorialTool - draw: button_text: 'draw', factory: DrawTool - edit: button_text: 'edit', factory: EditTool - delete: button_text: 'delete', factory: DeleteTool - for k, t of tool_buttons - if t.button_text? - t.button = $ "" - t.button.text t.button_text - $toolbar.append t.button - do (k, t) -> - activate = -> - if cur_tool? - cur_tool.disable() - $container.removeClass "#{cur_tool_name}_tool" - ($toolbar.find '.button').removeClass 'disabled' - cur_tool_name = k - $container.addClass "#{cur_tool_name}_tool" - $tool_options.empty() - cur_tool = new t.factory button: t.button, tool_options: $tool_options - if t.button? - t.button.click activate - if t.default - activate() + # 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' - $svg.mousedown (e) -> - offset = $svg.offset() - if cur_tool? - mouse_x = Math.round(e.pageX - offset.left) - mouse_y = Math.round(e.pageY - offset.top) - return cur_tool.click mouse_x, mouse_y - $svg.mousemove (e) -> - offset = $svg.offset() - if cur_tool? - mouse_x = Math.round(e.pageX - offset.left) - mouse_y = Math.round(e.pageY - offset.top) - return cur_tool.mousemove mouse_x, mouse_y - ($ document).keydown (e) -> - if cur_tool? - return cur_tool.keydown e.keyCode + supply = {} + for args, i in [ + { width: 40, height: 40 } + { width: 12, height: 50 } + { width: 70, height: 12 } + ] + widget = new RectWidget { + width: args.width + height: args.height + x: 30 + i * 90 + (70 - args.width) / 2 + y: (supply_height - args.height) / 2 + svg: svg + } + supply[widget.id] = widget + + # editor state + on_canvas = {} + selected = {} + editing = {} # has controls + dragging = false + drag_from = x: 0, y: 0 # mouse was here at last frame of drag + shift_key_down = false + + deselect_all = (args) -> + except = args?.except ? null + for id, w of selected + w.set_style STYLE_NORMAL + delete selected[id] + closest_widget = (widgets, xy) -> + prox = PROX_MAX + 1 + closest = null + for id, w of 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[closest.id] = closest + else + closest = closest_widget on_canvas, xy + if closest? + unless (shift_key_down or selected[closest.id]?) + deselect_all except: closest + selected[closest.id] = closest + closest.set_style STYLE_DRAGGING + dragging = true + drag_from = xy + else + deselect_all() + return false + mouseup = (e) -> + mousemove e + if dragging + for id, w of selected + if w.y < supply_height + w.destruct() + delete selected[id] + else + w.set_style STYLE_SELECTED + dragging = false + return false + prev_hover = null + mousemove = (e) -> + xy = svg_event_to_xy e + if dragging + return if drag_from.x is xy.x and drag_from.y is xy.y + rel_x = xy.x - drag_from.x + rel_y = xy.y - drag_from.y + drag_from = xy + for id, w of selected + w.move x: w.x + rel_x, y: w.y + rel_y + else + hover = closest_widget on_canvas, xy + unless hover? + hover = closest_widget supply, xy + if hover != prev_hover + if prev_hover? + # FIXME + if selected[prev_hover.id]? + prev_hover.set_style STYLE_SELECTED + else + prev_hover.set_style STYLE_NORMAL + if hover? + hover.set_style STYLE_HOVER + prev_hover = hover + return false + $svg.mousedown mousedown + $svg.mouseup mouseup + $svg.mousemove mousemove + $(document).on 'keyup keydown', (e) -> + shift_key_down = e.shiftKey + return true + #($ document).keydown (e) -> $ init