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_HOVER = 2
STYLE_EDITING = 3
STYLE_DRAGGING = 4
-
STYLE_TO_CLASS = [
"normal"
"selected"
"editing"
"dragging"
]
+TYPE_WIDGET = 1
+TYPE_CONTROL = 2
set_style_class = (args) ->
args.el.setAttribute 'class', "#{args.class} #{STYLE_TO_CLASS[args.style]}"
@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) ->
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
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 = ->
# 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()
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
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
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