X-Git-Url: https://jasonwoof.com/gitweb/?p=crayon_mockup.git;a=blobdiff_plain;f=main.coffee;h=3b896ddba53a0b2123ac541af61397c2d65b7895;hp=de2e86b8c8d6560203cc1d5c7544cd581c166d53;hb=98a90989ba295d3a712821c592ac12a9f15c4d06;hpb=a48101a8bbd94f47e8966e2a10971d53940dc09e diff --git a/main.coffee b/main.coffee index de2e86b..3b896dd 100644 --- a/main.coffee +++ b/main.coffee @@ -29,8 +29,6 @@ STATES = { DRAGGING: { txt: 'dragging' } EDITING: { txt: 'editing' } } -TYPE_WIDGET = 1 -TYPE_CONTROL = 2 # json (compiled to javascript and minified) is ~8% smaller than the resulting xml json_to_svg = (json) -> @@ -86,11 +84,12 @@ resizers = [ resizer_w ] resizer_shapes = [ - (xy) -> return "M#{@x - 5} #{@y - 5}h6l-2 2 4 4 2 -2v6h-6l2-2-4-4-2 2z" - (xy) -> 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" - (xy) -> return "M#{@x + 5} #{@y - 5}v6l-2-2-4 4 2 2h-6v-6l2 2 4-4-2-2z" - (xy) -> 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" + -> return "M#{@x - 5} #{@y - 5}h6l-2 2 4 4 2 -2v6h-6l2-2-4-4-2 2z" + -> 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" + -> return "M#{@x + 5} #{@y - 5}v6l-2-2-4 4 2 2h-6v-6l2 2 4-4-2-2z" + -> 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" ] +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" next_widget_id = 0 # public vars: x, y, width, height, el @@ -129,7 +128,6 @@ class Visible class Control extends Visible constructor: (args) -> super args - @type = TYPE_CONTROL @on_drag = args.drag @on_destruct = args.done ? null destruct: -> @@ -164,7 +162,6 @@ class Widget extends Visible constructor: (args) -> super args @controls = [] - @type = TYPE_WIDGET destruct: -> @kill_controls() clone: -> @@ -205,6 +202,14 @@ class RectWidget extends Widget @svg.removeChild @el clone: -> return new RectWidget @ + as_array: -> + return [@x, @y, @width, @height] + from_array: (args, a) -> + args.x = a[0] + args.y = a[1] + args.width = a[2] + args.height = a[3] + return new RectWidget args set_state: (state) -> super state @update_class() @@ -237,8 +242,9 @@ class RectWidget extends Widget 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 + # "hit" anything inside + #if in_x and in_y and prox > PROX_MAX + # prox = PROX_MAX - 1 return prox resize: (wh) -> dw = wh.w - @width @@ -285,6 +291,153 @@ class RectWidget extends Widget } return @controls +class PolylineWidget extends Widget + constructor: (args) -> + super args + @css_class = 'polyline' + @nodes = [] + for n in args.nodes + @nodes.push x: n.x, y: n.y + @el = json_to_svg path: + d: @my_path_d() + class: 'polyline normal' + @svg.appendChild @el + destruct: -> + super() + if @el? + @svg.removeChild @el + as_array: -> + ret = [] + for n in @nodes + ret.push @x + n.x + ret.push @y + n.y + return ret + from_array: (args, a) -> + args.x = a[0] + args.y = a[1] + args.nodes = [] + while a.length + xy = {} + xy.x = a.shift() - args.x + xy.y = a.shift() - args.y + args.nodes.push xy + return new PolylineWidget args + clone: -> + return new PolylineWidget @ + my_path_d: -> + ret = '' + for n in @nodes + if ret is '' + ret += 'M' + else + ret += 'L' + ret += n.x + @x + ret += ' ' + ret += n.y + @y + return ret + set_state: (state) -> + super state + @update_class() + move: (args) -> + super args + @el.setAttribute 'd', @my_path_d() + @reposition_controls() + proximity: (xy) -> # return the square of the distance to your visible bits + prox = PROX_TOO_FAR + for n, i in @nodes + dx = @x + n.x - xy.x + dy = @y + n.y - xy.y + p = dx * dx + dy * dy + if p < prox + prox = p + if i > 0 + l1x = @x + @nodes[i-1].x + l1y = @y + @nodes[i-1].y + l2x = @x + @nodes[i].x + l2y = @y + @nodes[i].y + ldx = l2x - l1x + ldy = l2y - l1y + if ldx is 0 # vertical line + if (xy.y < l1y) is (xy.y < l2y) + continue + dx = l1x - xy.x + p = dx * dx + else if ldy is 0 # horizontal line + if (xy.x < l1x) is (xy.x < l2x) + continue + dy = l1y - xy.y + p = dy * dy + else # slanty line + # https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line + a = ldy + b = -1 * ldx + c = l2x * l1y - l2y * l1x + y_on_line = (a * (a * xy.y - b * xy.x) - b * c) / (a * a + b * b) + if (y_on_line < l1y) is (y_on_line < l2y) + continue + p = (a * xy.x + b * xy.y + c) / Math.sqrt(a * a + b * b) + p *= p + + if p < prox + prox = p + return prox + resize: (wh) -> + # FIXME (apply to more than just 2nd node) + @nodes[1].x = wh.w + @nodes[1].y = wh.h + @width = wh.w + @height = wh.h + @el.setAttribute 'd', @my_path_d() + @reposition_controls() + return + node_dragger: (i) -> + if i is 0 + return (dxy) => + for j in [1...@nodes.length] + @nodes[j].x -= dxy.x + @nodes[j].y -= dxy.y + @move x: @x + dxy.x, y: @y + dxy.y + return (dxy) => + @nodes[i].x += dxy.x + @nodes[i].y += dxy.y + @el.setAttribute 'd', @my_path_d() + @reposition_controls() + reposition_controls: -> + if @controls.length > 1 + positions = @control_positions() + for i in [0...positions.length] + @controls[i].move x: positions[i].x, y: positions[i].y + return + control_positions: -> + ret = [] + for n in @nodes + ret.push x: @x + n.x, y: @y + n.y + return ret + make_controls: (args) -> # create controls, return them + if @controls.length > 0 + if console?.log? + console.log "warning: re-adding line controls" + @kill_controls() + positions = @control_positions() + for i in [0...positions.length] + @controls.push new ControlPath { + svg: @svg + x: positions[i].x + y: positions[i].y + done: args.done + drag: @node_dragger i + shape: shape_node_move + } + return @controls + +CSS_CLASS_TO_PICKLE_TYPE = { + box: '0' + polyline: '1' +} +PICKLE_TYPE_TO_WIDGET_CLASS = [ + RectWidget + PolylineWidget +] # called automatically on domcontentloaded init = -> svg_offset = null @@ -301,7 +454,7 @@ init = -> ] svg.appendChild json_to_svg style: type: 'text/css' - contents: '.box.normal{filter: url(#crayon)}' + contents: '.box.normal,.polyline.normal{filter: url(#crayon)}' # create canvas border svg.appendChild json_to_svg rect: @@ -312,19 +465,21 @@ init = -> class: 'canvas_border' 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 + supply_count = 0 + supply_add = (type, args) -> + args.x ?= 0 + args.y ?= 0 + args.x += 30 + supply_count * 90 + args.y += (supply_height - 50) / 2 + args.svg = svg + w = new type args + supply[w.id] = w + supply_count += 1 + supply_add RectWidget, width: 50, height: 50 + supply_add PolylineWidget, y: 25, nodes: [{x: 0, y: 0}, {x: 50, y: 0}] + supply_add PolylineWidget, x: 25, nodes: [{x: 0, y: 0}, {x: 0, y: 50}] + supply_add PolylineWidget, x: 10, nodes: [{x: 0, y: 0}, {x: 15, y: 50}, {x: 30, y: 0}] + supply_add PolylineWidget, y: 50, nodes: [{x: 0, y: 0}, {x: 17, y: -50}, {x: 33, y: 0}, {x: 50, y: -50}] # editor state controls_layer = { all: {}, selected: {} } @@ -336,6 +491,65 @@ init = -> drag_from = x: 0, y: 0 # mouse was here at last frame of drag shift_key_down = false + lc = "abcdefghijklmnopqrstuvwxyz" + uc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + cc_lca = lc.charCodeAt 0 + cc_lcz = lc.charCodeAt 25 + cc_uca = uc.charCodeAt 0 + cc_ucz = uc.charCodeAt 25 + cc_0 = '0'.charCodeAt 0 + cc_9 = '9'.charCodeAt 0 + # takes an array of positive integers, and encodes it as a string + pickle = (a) -> + ret = '' + for i in a + cs = lc + r = '' + while i > 0 or r is '' + digit = i % cs.length + i -= digit + i /= cs.length + r = cs.charAt(digit) + r + cs = uc + ret += r + return ret + pickle_widgets = -> + ret = '0' # version of this encoding scheme + for id, w of widget_layer.all + if CSS_CLASS_TO_PICKLE_TYPE[w.css_class]? + ret += CSS_CLASS_TO_PICKLE_TYPE[w.css_class] + ret += pickle w.as_array() + return ret + save = -> + window.location.hash = pickle_widgets() + load = (str) -> + return if str.charAt(1) isnt '0' + wtype = null + args = [] + ii = 0 + load_1 = (next_type) -> + unless wtype? + if next_type? + wtype = next_type + return + w = PICKLE_TYPE_TO_WIDGET_CLASS[wtype]::from_array svg: svg, args + widget_layer.all[w.id] = w + wtype = next_type + args = [] + ii = 0 + for i in [2...str.length] + c = str.charCodeAt(i) + if cc_0 <= c <= cc_9 + load_1 c - cc_0 + else if cc_lca <= c <= cc_lcz + ii *= lc.length + ii += c - cc_lca + args.push ii + ii = 0 + else if cc_uca <= c <= cc_ucz + ii *= lc.length + ii += c - cc_uca + load_1 null stop_editing = -> if widget_layer.editing widget_layer.editing.kill_controls() @@ -429,6 +643,7 @@ init = -> deselect_all widget_layer return mouseup = (e) -> + save() mousemove e if dragging selected_count = 0 @@ -482,4 +697,7 @@ init = -> return true #($ document).keydown (e) -> + if window.location.hash + load window.location.hash + $ init