JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
rect control points working a bit
[crayon_mockup.git] / main.coffee
index 270b3cd..906228d 100644 (file)
@@ -20,6 +20,7 @@ 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
@@ -27,7 +28,6 @@ STYLE_SELECTED = 1
 STYLE_HOVER = 2
 STYLE_EDITING = 3
 STYLE_DRAGGING = 4
-
 STYLE_TO_CLASS = [
        "normal"
        "selected"
@@ -35,6 +35,8 @@ STYLE_TO_CLASS = [
        "editing"
        "dragging"
 ]
+TYPE_WIDGET = 1
+TYPE_CONTROL = 2
 
 set_style_class = (args) ->
        args.el.setAttribute 'class', "#{args.class} #{STYLE_TO_CLASS[args.style]}"
@@ -67,23 +69,75 @@ class Visible
                @height = args.height ? 34
                @style = args.style ? STYLE_NORMAL
        destruct: ->
-       move: (args) ->
-               @x = args.x
-               @y = args.y
+       move: (xy) -> # just move
+               @x = xy.x
+               @y = xy.y
+       drag: (xy) -> # react to mouse drag (obey constraints, etc.)
+               @move xy
        proximity: (xy) -> # return the square of the distance to your visible bits
-               return PROX_MAX + 1
+               return PROX_TOO_FAR
        set_style: (style) ->
                @style = style
 
 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
+               @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
 
 class RectWidget extends Widget
        constructor: (args) ->
@@ -112,7 +166,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 +190,31 @@ 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: (xy) ->
+                               dx = xy.x - @x
+                               dy = xy.y - @y
+                               w.resize w: w.width - dx, h: w.height - dy
+                               w.move x: w.x + dx, y: w.y + dy
+                       new ControlPoint svg: @svg, x: @x + @width, y: @y + @height, done: args.done, drag: (xy) ->
+                               dx = xy.x - @x
+                               dy = xy.y - @y
+                               w.resize w: w.width + dx, h: w.height + dy
+               ]
+               return @controls
 
 # called automatically on domcontentloaded
 init = ->
@@ -184,27 +260,49 @@ init = ->
        # editor state
        on_canvas = {}
        selected = {}
+       controls = {}
        editing = {} # has controls
        dragging = false
        drag_from = x: 0, y: 0 # mouse was here at last frame of drag
        shift_key_down = false
 
+       selected_type = ->
+               for s of selected
+                       return s.type
+               return null
+       deselect = (s) ->
+               s.set_style STYLE_NORMAL
+               if s.type is TYPE_WIDGET
+                       s.kill_controls()
+               delete selected[s.id]
+               return
        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
+               for id, s of selected
+                       deselect s
+               return
+       select_only = (sel) ->
+               deselect_all except: sel
+               return if selected[sel.id]?
+               selected[sel.id] = sel
+       select_also = (sel) ->
+               return if selected[sel.id]?
+               sel_type = selected_type()
+               if sel_type isnt sel.type
+                       deselect_all()
+               selected[sel.id] = sel
+               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()
@@ -218,17 +316,28 @@ init = ->
                        return mouseup e
                xy = svg_event_to_xy e
                if xy.y < supply_height
-                       closest = closest_widget supply, xy
+                       closest = find_closest supply, xy
                        if closest?
                                closest = closest.clone()
                                on_canvas[closest.id] = closest
                else
-                       closest = closest_widget on_canvas, xy
+                       closest = find_closest controls, xy
+                       unless closest?
+                               closest = find_closest 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
+                       console.log closest
+                       if selected[closest.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 closest
+                       else if shift_key_down
+                               select_also closest
+                       else
+                               select_only closest
+                       for id, s of selected
+                               s.set_style STYLE_DRAGGING
                        dragging = true
                        drag_from = xy
                else
@@ -239,10 +348,19 @@ init = ->
                if dragging
                        for id, w of selected
                                if w.y < supply_height
+                                       deselect w
                                        w.destruct()
-                                       delete selected[id]
+                                       delete on_canvas[id]
                                else
                                        w.set_style STYLE_SELECTED
+                                       if w.type is TYPE_WIDGET
+                                               cs = w.make_controls done: (c) ->
+                                                       if controls[c.id]?
+                                                               delete controls[c.id]
+                                               for c in cs
+                                                       controls[c.id] = c
+                                               editing[w.id] = w
+                                               delete selected[w.id]
                dragging = false
                return false
        prev_hover = null
@@ -254,11 +372,11 @@ init = ->
                        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
+                               w.drag x: w.x + rel_x, y: w.y + rel_y
                else
-                       hover = closest_widget on_canvas, xy
+                       hover = find_closest on_canvas, xy
                        unless hover?
-                               hover = closest_widget supply, xy
+                               hover = find_closest supply, xy
                        if hover != prev_hover
                                if prev_hover?
                                        # FIXME