X-Git-Url: https://jasonwoof.com/gitweb/?p=crayon_mockup.git;a=blobdiff_plain;f=main.coffee;h=3ed08db238cf25e350972065ca6660d996341d05;hp=270b3cd7a08f511b21773a5023b342d10a9b9207;hb=67a786e6d0b589a4ae7e2833d3f1d5d1963a5edc;hpb=2d902775d3b9f916b5a38ac8874a734ca586e6b3 diff --git a/main.coffee b/main.coffee index 270b3cd..3ed08db 100644 --- a/main.coffee +++ b/main.coffee @@ -20,24 +20,17 @@ 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 +PROX_TOO_FAR = PROX_MAX + 1 # no need to be precice when it's too far # constants -STYLE_NORMAL = 0 -STYLE_SELECTED = 1 -STYLE_HOVER = 2 -STYLE_EDITING = 3 -STYLE_DRAGGING = 4 - -STYLE_TO_CLASS = [ - "normal" - "selected" - "hover" - "editing" - "dragging" -] - -set_style_class = (args) -> - args.el.setAttribute 'class', "#{args.class} #{STYLE_TO_CLASS[args.style]}" +STATES = { + NORMAL: { txt: 'normal' } + SELECTED: { txt: 'selected' } + 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) -> @@ -65,25 +58,93 @@ class Visible @y = args.y ? 1 @width = args.width ? 50 @height = args.height ? 34 - @style = args.style ? STYLE_NORMAL + @state = args.state ? STATES.NORMAL + @hover = false destruct: -> - move: (args) -> - @x = args.x - @y = args.y + update_class: -> + css_class = "#{@css_class} #{@state.txt}" + if @hover + css_class += " hover" + @el.setAttribute 'class', css_class + set_hover: (tf) -> + if tf != @hover + @hover = tf + @update_class() + move: (xy) -> # just move + @x = xy.x + @y = xy.y + drag: (dxy) -> # react to mouse drag (obey constraints, etc.) + @move x: @x + dxy.x, y: @y + dxy.y proximity: (xy) -> # return the square of the distance to your visible bits - return PROX_MAX + 1 - set_style: (style) -> - @style = style + return PROX_TOO_FAR + set_state: (state) -> + @state = state class Control extends Visible + constructor: (args) -> + super args + @type = TYPE_CONTROL + @on_drag = args.drag + @on_destruct = args.done ? null + destruct: -> + super() + if @on_destruct? + @on_destruct @ + drag: (args) -> # call this when control point is being manipulated directly + @on_drag args + proximity: (xy) -> # return the square of the distance to your visible bits + dx = xy.x - @x + dy = xy.y - @y + return dx * dx + dy * dy + +class ControlPoint extends Control + constructor: (args) -> + super args + @css_class = 'control_point' + @el = json_to_svg circle: + cx: @x + 1 + cy: @y + 1 + r: 6 + class: 'control_point normal' + @svg.appendChild @el + destruct: -> + super() + if @el? + @svg.removeChild @el + move: (args) -> + super args + @el.setAttribute 'cx', @x + @el.setAttribute 'cy', @y class Widget extends Visible #sub-classes are expected to implement all of these: + constructor: (args) -> + super args + @controls = [] + @type = TYPE_WIDGET + destruct: -> + @kill_controls() clone: -> return new Widget @ - controls: -> # create controls, return them + make_controls: -> # create controls, return them return [] - hide_controls: -> + kill_controls: -> + console.log 'kill_controls' + for c in @controls + c.destruct() + @controls = [] + return + move: (xy) -> # just move + dx = xy.x - @x + dy = xy.y - @y + super xy + for c in @controls + c.move x: c.x + dx, y: c.y + dy + set_state: (state) -> + return if @state is state + if @state is STATES.EDITING + @kill_controls() + super state class RectWidget extends Widget constructor: (args) -> @@ -102,9 +163,9 @@ class RectWidget extends Widget @svg.removeChild @el clone: -> return new RectWidget @ - set_style: (style) -> - super style - set_style_class el: @el, class: 'box', style: style + set_state: (state) -> + super state + @update_class() move: (args) -> super args @el.setAttribute 'x', @x + 1 @@ -112,7 +173,7 @@ class RectWidget extends Widget proximity: (xy) -> # return the square of the distance to your visible bits x = xy.x y = xy.y - prox = PROX_MAX + 1 + prox = PROX_TOO_FAR in_x = false in_y = false if x > @x - CLICK_FUZ and x < @x + @width + CLICK_FUZ @@ -136,9 +197,27 @@ class RectWidget extends Widget if in_x and in_y and prox > PROX_MAX prox = PROX_MAX - 1 return prox - controls: -> # create controls, return them - return [] - hide_controls: -> + resize: (wh) -> + @width = wh.w + @el.setAttribute 'width', @width + @height = wh.h + @el.setAttribute 'height', @height + if @controls.length > 1 + @controls[1].move x: @x + @width, y: @y + @height + make_controls: (args) -> # create controls, return them + console.log 'make_controls' + if @controls.length > 0 + console.log "warning: re-adding controls" + @kill_controls() + w = @ + @controls = [ + new ControlPoint svg: @svg, x: @x, y: @y, done: args.done, drag: (dxy) -> + w.resize w: w.width - dxy.x, h: w.height - dxy.y + w.move x: w.x + dxy.x, y: w.y + dxy.y + new ControlPoint svg: @svg, x: @x + @width, y: @y + @height, done: args.done, drag: (dxy) -> + w.resize w: w.width + dxy.x, h: w.height + dxy.y + ] + return @controls # called automatically on domcontentloaded init = -> @@ -156,7 +235,7 @@ init = -> ] svg.appendChild json_to_svg style: type: 'text/css' - contents: '.box.normal,.box.hover,.box.selected{filter: url(#crayon)}' + contents: '.box.normal,.box.selected{filter: url(#crayon)}' # create canvas border svg.appendChild json_to_svg rect: @@ -182,29 +261,56 @@ init = -> supply[widget.id] = widget # editor state - on_canvas = {} - selected = {} - editing = {} # has controls - dragging = false + controls_layer = { all: {}, selected: {} } + widget_layer = { all: {}, selected: {}, editing: null } + layers = [controls_layer, widget_layer] + hovered = null # can be in any layer + dragging = false # mouse state + drag_layer = null 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 + stop_editing = -> + if widget_layer.editing + widget_layer.editing.kill_controls() + widget_layer.editing = null + deselect = (layer, s) -> + return unless layer.selected[s.id]? + s.set_state STATES.NORMAL + delete layer.selected[s.id] + if widget_layer.editing is s + widget_layer.editing = null + return + deselect_all = (layer, except = null) -> + for id, s of layer.selected + deselect layer, s + return + _select = (layer, s) -> # don't call this directly, use select_only() or select_also() + s.set_state STATES.SELECTED + layer.selected[s.id] = s + return + select_only = (layer, s) -> + deselect_all layer, s + return if layer.selected[s.id]? + _select layer, s + return + select_also = (layer, s) -> + return if layer.selected[s.id]? + if layer is widget_layer + stop_editing() + _select layer, s + return + find_closest = (widgets, xy) -> + prox = PROX_TOO_FAR 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 + if prox > PROX_MAX + return null + return closest svg_event_to_xy = (e) -> unless svg_offset? svg_offset = $svg.offset() @@ -212,40 +318,74 @@ init = -> x: Math.round(e.pageX - svg_offset.left) y: Math.round(e.pageY - svg_offset.top) } + closest_in_layers = (xy) -> + for layer in layers + s = find_closest layer.selected, xy + return layer: layer, s: s if s? + s = find_closest layer.all, xy + return layer: layer, s: s if s? + return null mousedown = (e) -> + hit = null + closest = null + layer = null 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 + s = find_closest supply, xy + if s? + hit = { + s: s.clone() + layer: widget_layer + } + widget_layer.all[hit.s.id] = hit.s 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 + hit = closest_in_layers xy + if hit? + if hit.layer.selected[hit.s.id] + # already selected + # TODO start detection of a click that doesn't drag (to shrink selection) + else if xy.y < supply_height + # dragging a new thing in + select_only hit.layer, hit.s + else if shift_key_down + select_also hit.layer, hit.s + else + select_only hit.layer, hit.s + for id, s of hit.layer.selected + s.set_state STATES.DRAGGING dragging = true + drag_layer = hit.layer drag_from = xy + console.log hit else - deselect_all() - return false + deselect_all widget_layer + return mouseup = (e) -> mousemove e if dragging - for id, w of selected - if w.y < supply_height - w.destruct() - delete selected[id] + selected_count = 0 + for id, s of drag_layer.selected + if s.y < supply_height and drag_layer is widget_layer + deselect drag_layer, s + s.destruct() + delete drag_layer.all[id] else - w.set_style STYLE_SELECTED + selected_count += 1 + s.set_state STATES.SELECTED + if drag_layer is widget_layer and selected_count is 1 + for id, s of drag_layer.selected + s.set_state STATES.EDITING + cs = s.make_controls done: (c) -> + deselect controls_layer, c + delete controls_layer.all[c.id] + for c in cs + controls_layer.all[c.id] = c + widget_layer.editing = s dragging = false - return false - prev_hover = null + return mousemove = (e) -> xy = svg_event_to_xy e if dragging @@ -253,26 +393,26 @@ init = -> 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 + for id, w of drag_layer.selected + w.drag x: rel_x, 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 + hit = closest_in_layers xy + return unless hit? + return if hit.s is hovered + if hovered + hovered.set_hover false + hovered = hit.s + hovered.set_hover true + return + $svg.mousedown (e) -> + mousedown e + return false + $svg.mouseup (e) -> + mouseup e + return false + $svg.mousemove (e) -> + mousemove e return false - $svg.mousedown mousedown - $svg.mouseup mouseup - $svg.mousemove mousemove $(document).on 'keyup keydown', (e) -> shift_key_down = e.shiftKey return true