
H=600
W=600

class UpDown < Widget
  attr_accessor :value
  def initialize(label, start_value, lower_limit = nil, upper_limit = nil)
    @lower_limit, @upper_limit = lower_limit, upper_limit
    @value = start_value
    @action = Proc.new { yield self if block_given? }
    flow :width => 200 do
      border black, :curve => 10
      flow  do
        button("<") { decrement }
        @display = para "#{@value}"
        button(">") { increment }
        para label
      end
    end
  end

  def increment
    unless @upper_limit and @value >= @upper_limit
      @value += 1; @display.text = "#{@value}"
      @action.call
    end
  end
  def decrement
    unless @lower_limit and @value <= @lower_limit
      @value -= 1; @display.text = "#{@value}"
      @action.call
    end
  end
end

Shoes.app :width => W + 200, :height => H + 20 do
  @blur = 0
  @drawn_rects = []
  @drawn_images = []

  def restart_dots
    @drawn_images.each { | i | i.remove }
    draw_rects
    @button_down = nil
    @dots = 0
#    @spot = [rand(W), rand(H)]
    @spot = [rand(@stack.width), rand(@stack.height)]
  end

  def start_fresh
    @rects = []
    restart_dots
  end

  # turns a rectangle with possibly negative width and height into a 
  # normal one for drawing purposes
  def absolute_rect(this_rect)
    return {
      :top =>  [ this_rect[:top], this_rect[:top] + this_rect[:height] ].min,
      :left => [ this_rect[:left], this_rect[:left] + this_rect[:width] ].min,
      :width => this_rect[:width].abs,
      :height => this_rect[:height].abs    
    }
  end

  def draw_rects
    if @show_rects.checked?
      @drawn_rects.each { |r| r.remove }
      @stack.append do
        @rects.each do | this_rect |
          nofill
          stroke rgb(1.0,1.0,1.0, 0.2)
          @drawn_rects << rect(absolute_rect(this_rect))
        end
      end
    else
      @drawn_rects.each { |r| r.remove }
    end
  end

  # called when you are done drawing a rectangle
  def finish_rect(left, top)
    @drawing_rect.remove if @drawing_rect
    rect_top, rect_left = @button_down
    @button_down = nil
    rect_bottom = top
    rect_right = left
    rect_width = rect_right - rect_left
    rect_height = rect_bottom - rect_top
    @rects << {:top => rect_top, :left => rect_left, 
      :width => rect_width, :height => rect_height}
    restart_dots
  end

  flow :height => '100%' do
    stack :width => 200 do
      up_down("Blur", 0, 0) do
        |moi| @blur = moi.value 
        restart_dots
      end
      flow do
        @show_rects = check( :checked => true ) { draw_rects }
        para( "Show rectangles") 
      end
      button( "Clear and restart" ) do
        start_fresh
      end
      button( "Randomize color" ) do
        @dot_color=rgb(rand*0.5 + 0.5, rand*0.5 + 0.5, rand*0.5 + 0.5, 0.8)
        restart_dots
      end

      para "draw rectangles with your left mouse button; clear with a right click"
    end
#    @stack = stack :height => H, :width => W do
    @stack = stack :width => -200, :height => '100%' do
      background black
    end
  end

  start_fresh
  batch = 400
  speed = 10
  animate speed do
    next if @button_down
    if @rects.length > 0 and @dots < batch * speed * 5
      @dot_color ||=rgb(rand*0.5 + 0.5, rand*0.5 + 0.5, rand*0.5 + 0.5, 0.8)
      @dots += batch
      @stack.append do
        stroke @dot_color
        strokewidth 1
        if @blur > 0
          @drawn_images << image( :left => 0, :top => 0 ) do
            batch.times do
              rect( :left => @spot[0], :top => @spot[1], 
                    :width => 1, :height => 1, :center => true )
              move_the_spot
            end
            blur @blur
          end
        else
          @drawn_images << shape( :left => 0, :top => 0 ) do
            batch.times do
              rect( :left => @spot[0], :top => @spot[1], 
                    :width => 1, :height => 1, :center => true )
              move_the_spot
            end
          end
        end
      end
    end
  end

  def move_the_spot
    x, y = @spot
    the_box = @rects[rand(@rects.length)]
    box_proportion_w = the_box[:width].to_f / @stack.width.to_f
    box_proportion_h = the_box[:height].to_f / @stack.height.to_f
    new_x = ( x * box_proportion_w  + the_box[:left]).to_i
    new_y = ( y * box_proportion_h + the_box[:top]).to_i
    @spot = [ new_x, new_y ]
  end

  # set button_down.  if button is already down,
  # something funny happened, like you drew a rectangle
  # bigger than the screen.  So release it.
  # while button_down is set, you'll see a 
  @stack.click do | button, left, top |
    left -= @stack.left
    if @button_down
      finish_rect(left, top)
    else
      @button_down = [top, left]
    end
  end

  # if button_down is set, we're in the process of
  # drawing a rectangle.  Outline in blue the
  # rectangle you'd get if you stopped drawing here.
  # if there's already a blue rectangle, remove the old one
  # first.
  @stack.motion do | left, top |
    left -= @stack.left
    if @button_down
      @drawing_rect.remove if @drawing_rect
      t1, l1 = @button_down
      r = absolute_rect( :left => l1,
                         :top => t1,
                         :width => left - l1,
                         :height => top - t1 )
      @stack.append do
        stroke blue
        @drawing_rect = rect( r )
      end
    end
  end

  @stack.release do | button, left, top |
    left -= @stack.left

    # right click = clear and start over

    if button == 2
      start_fresh
    else

      # left button = if we were drawing a rectangle,
      # we're done now

      if @button_down
        finish_rect(left, top)
      end
    end
  end
end

