Codebase list ruby-fxruby / ddfc60e examples / bounce.rb
ddfc60e

Tree @ddfc60e (Download .tar.gz)

bounce.rb @ddfc60eraw · history · blame

require 'fox16'

include Fox

# How long to pause between updates (in milliseconds)
ANIMATION_TIME = 20

class Ball

  attr_reader :color
  attr_reader :center
  attr_reader :radius
  attr_reader :dir
  attr_reader :x, :y
  attr_reader :w, :h
  attr_accessor :worldWidth
  attr_accessor :worldHeight

  # Returns an initialized ball
  def initialize(r)
    @radius = r
    @w = 2*@radius
    @h = 2*@radius
    @center = FXPoint.new(50, 50)
    @x = @center.x - @radius
    @y = @center.y - @radius
    @color = FXRGB(255, 0, 0) # red
    @dir = FXPoint.new(-1, -1)
    setWorldSize(1000, 1000)
  end

  # Draw the ball into this device context
  def draw(dc)
    dc.setForeground(color)
    dc.fillArc(x, y, w, h, 0, 64*90)
    dc.fillArc(x, y, w, h, 64*90, 64*180)
    dc.fillArc(x, y, w, h, 64*180, 64*270)
    dc.fillArc(x, y, w, h, 64*270, 64*360)
  end

  def bounce_x
    @dir.x=-@dir.x
  end

  def bounce_y
    @dir.y=-@dir.y
  end

  def collision_y?
    (y<0 && dir.y<0) || (y+h>worldHeight && dir.y>0)
  end

  def collision_x?
    (x<0 && dir.x<0) || (x+w>worldWidth && dir.x>0)
  end

  def setWorldSize(ww, wh)
    @worldWidth = ww
    @worldHeight = wh
  end

  def move(units)
    dx = dir.x*units
    dy = dir.y*units
    center.x += dx
    center.y += dy
    @x += dx
    @y += dy
    if collision_x?
      bounce_x
      move(units)
    end
    if collision_y?
      bounce_y
      move(units)
    end
  end
end

class BounceWindow < FXMainWindow

  include Responder

  def initialize(app)
    # Initialize base class first
    super(app, "Bounce", :opts => DECOR_ALL, :width => 400, :height => 300)

    # Set up the canvas
    @canvas = FXCanvas.new(self, :opts => LAYOUT_FILL_X|LAYOUT_FILL_Y)

    # Set up the back buffer
    @backBuffer = FXImage.new(app, nil, IMAGE_KEEP)

    # Handle expose events (by blitting the image to the canvas)
    @canvas.connect(SEL_PAINT) { |sender, sel, evt|
      FXDCWindow.new(sender, evt) { |dc|
        dc.drawImage(@backBuffer, 0, 0)
      }
    }

    # Handle resize events
    @canvas.connect(SEL_CONFIGURE) { |sender, sel, evt|
      @backBuffer.create unless @backBuffer.created?
      @backBuffer.resize(sender.width, sender.height)
      @ball.setWorldSize(sender.width, sender.height)
      drawScene(@backBuffer)
    }

    @ball = Ball.new(20)
  end

  #
  # Draws the scene into the back buffer
  #
  def drawScene(drawable)
    FXDCWindow.new(drawable) { |dc|
      dc.setForeground(FXRGB(255, 255, 255))
      dc.fillRectangle(0, 0, drawable.width, drawable.height)
      @ball.draw(dc)
    }
  end

  def updateCanvas
    @ball.move(10)
    drawScene(@backBuffer)
    @canvas.update
  end

  #
  # Handle timeout events
  #
  def onTimeout(sender, sel, ptr)
    # Move the ball and re-draw the scene
    updateCanvas

    # Re-register the timeout
    getApp().addTimeout(ANIMATION_TIME, method(:onTimeout))

    # Done
    return 1
  end

  #
  # Create server-side resources
  #
  def create
    # Create base class
    super

    # Create the image used as the back-buffer
    @backBuffer.create

    # Draw the initial scene into the back-buffer
    drawScene(@backBuffer)

    # Register the timer used for animation
    getApp().addTimeout(ANIMATION_TIME, method(:onTimeout))

    # Show the main window
    show(PLACEMENT_SCREEN)
  end
end

if __FILE__ == $0
  FXApp.new("Bounce", "FXRuby") do |theApp|
    BounceWindow.new(theApp)
    theApp.create
    theApp.run
  end
end