From 8b0b3a723c187dd292219a82c4a237018b187f0e Mon Sep 17 00:00:00 2001 From: Jason Woofenden Date: Fri, 16 Oct 2015 23:19:14 -0400 Subject: [PATCH] rect control points working a bit --- main.coffee | 176 +++++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 147 insertions(+), 29 deletions(-) diff --git a/main.coffee b/main.coffee index 270b3cd..906228d 100644 --- a/main.coffee +++ b/main.coffee @@ -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 -- 1.7.10.4