# Pen Pal Pixel Painter
# version 3
# for Shoes
# written by chiisaitsu in July 2008 <chiisaitsu@gmail.com>
# current version in December 2008 (tested on Shoes 2 Raisins 0.r1134)
#
# Pen Pal Pixel Painter = dRuby + pixel art

# Tweak the constants below to control the display.

# squares in canvas and palette
SQUARE_SIZE       = 30
SQUARE_MARGIN     = 3
SQUARE_CURVE      = 5
SQUARE_HALO_SIZE  = SQUARE_MARGIN + 1 # selected color in palette has halo
SQUARE_HALO_COLOR = '#fff'

# palette
NUM_COLORS        = 16                # not counting black and white
PAL_SATURATION    = 1
PAL_LIGHTNESS     = 0.5
PAL_GAP           = 15                # pixels between palette and canvas

# canvas
CANVAS_ROWS       = 9
CANVAS_COLS       = 9
INITIAL_COLOR     = NUM_COLORS        # black

# other colors
BACKGROUND_COLOR  = '#333'
TEXT_COLOR        = '#ddd'

require 'drb'

# don't modify below
PAL_COLS     = ((NUM_COLORS + 2) / CANVAS_ROWS.to_f).ceil
PAL_WIDTH    = (PAL_COLS * (SQUARE_SIZE + SQUARE_MARGIN)) + PAL_GAP
CANVAS_HEIGHT= (CANVAS_ROWS * SQUARE_SIZE) + ((CANVAS_ROWS + 1) * SQUARE_MARGIN)
CANVAS_WIDTH = (CANVAS_COLS * SQUARE_SIZE) + ((CANVAS_COLS + 1) * SQUARE_MARGIN)

APP_NAME    = 'Pen Pal Pixel Painter'
APP_VERSION = '3'

class Server

  def initialize serving_app
    @apps= [serving_app]
  end

  def add_app app
    @apps << app
    @apps.first.instance_eval { @canvas }
  end

  def lay_paint calling_app, row, col, color_idx
    @apps.each_with_index do |app, i|
      begin
        app.lay_paint(row, col, color_idx) if app != calling_app
      rescue DRb::DRbError
        @apps.delete_at(i)
      end
    end
  end

end

Shoes.app(:title     => "#{APP_NAME} #{APP_VERSION}",
          :width     => CANVAS_WIDTH + PAL_WIDTH,
          :height    => CANVAS_HEIGHT + 62,
          :resizable => false) do

  def canvas_square row, col, color_idx
    x= PAL_WIDTH + (col * SQUARE_SIZE) + ((col + 1) * SQUARE_MARGIN)
    y= (row * SQUARE_SIZE) + ((row + 1) * SQUARE_MARGIN)
    square(x, y, color_idx)
  end

  def check_canvas_click button, x, y
    row= y / (SQUARE_SIZE + SQUARE_MARGIN)
    col= (x - PAL_WIDTH) / (SQUARE_SIZE + SQUARE_MARGIN)
    if 0 <= col && col < CANVAS_COLS && 0 <= row && row < CANVAS_ROWS
      if button == 1
        lay_paint(row, col, @color_idx)
        begin
          @server.lay_paint(self, row, col, @color_idx)
        rescue DRb::DRbError
          on_disconnect
        end
      else
        select_color(@canvas[row][col])
      end
    end
  end

  def hsl h, s, l
    h, s, l= h.to_f, s.to_f, l.to_f
    q= l < 0.5 ? l * (1 + s) : l + s - (l * s)
    p= (2 * l) - q
    hk= h / 360
    tc= [hk + (1.0 / 3.0), hk, hk - (1.0 / 3.0)].map do |c|
      if c < 0:    c + 1
      elsif c > 1: c - 1
      else         c
      end
    end
    rgb(*tc.map do |c|
      if c < 1.0 / 6.0:                  p + ((q - p) * 6 * c)
      elsif 1.0 / 6.0 <= c && c < 0.5:   q
      elsif 0.5 <= c && c < (2.0 / 3.0): p + ((q - p) * 6 * ((2.0 / 3.0) - c))
      else                               p
      end
    end)
  end

  def join_server uri
    begin
      @server= DRbObject.new_with_uri(uri)
      @canvas= @server.add_app(self)
      repaint_canvas
      @join_entry.remove
      @join_button.remove
      @joined_para= para("and connected to #{uri}", :align => 'center',
                         :top => CANVAS_HEIGHT + 30, :stroke => TEXT_COLOR)
    rescue StandardError => err
      alert("Error connecting to server:\n#{err}")
    end
  end

  def join_widgets
    @join_entry= edit_line(:text => 'druby://127.0.0.1:',
                           :top => CANVAS_HEIGHT + 30, :left => 10,
                           :width => CANVAS_WIDTH + PAL_WIDTH - 80)
    @join_button= button('Join', :top => CANVAS_HEIGHT + 30, :right => 10,
                         :width => 50) { join_server(@join_entry.text.to_s) }
  end

  def lay_paint row, col, color_idx
    square= @canvas_squares[row][col]
    set_square_color(square, color_idx)
    @canvas[row][col]= color_idx
  end

  def make_canvas
    @canvas= Array.new(CANVAS_ROWS) { Array.new(CANVAS_COLS) { INITIAL_COLOR }}
    @canvas_squares= Array.new(CANVAS_ROWS) do |row|
      Array.new(CANVAS_COLS) { |col| canvas_square(row, col, INITIAL_COLOR) }
    end
  end

  def make_palette
    color_skip= 360.0 / NUM_COLORS
    @color_table= Array.new(NUM_COLORS) do |i|
      [i * color_skip, PAL_SATURATION, PAL_LIGHTNESS]
    end
    @palette_squares= Array.new(NUM_COLORS) { |i| palette_square(i) }
    # black @ index NUM_COLORS
    @color_table << [0, 0, 0.1]
    @palette_squares << palette_square(NUM_COLORS)
    # white @ index NUM_COLORS + 1
    @color_table << [0, 0, 0.9]
    @palette_squares << palette_square(NUM_COLORS + 1)
  end

  def on_disconnect
    alert('And down goes the server.')
    @server= @my_server
    @joined_para.remove
    join_widgets
  end

  def palette_square color_idx
    x= ((color_idx % PAL_COLS) * (SQUARE_SIZE + SQUARE_MARGIN)) + SQUARE_MARGIN
    y= ((color_idx / PAL_COLS) * (SQUARE_SIZE + SQUARE_MARGIN)) + SQUARE_MARGIN
    s= square(x, y, color_idx)
    s.click { select_color(color_idx) }
    s
  end

  def repaint_canvas
    CANVAS_ROWS.times do |row|
      CANVAS_COLS.times do |col|
        set_square_color(@canvas_squares[row][col], @canvas[row][col])
      end
    end
  end

  def select_color color_idx
    set_square_color(@palette_squares[@color_idx]) unless @color_idx.nil?
    @color_idx= color_idx
    set_square_color(@palette_squares[color_idx], @color_idx, true)
  end

  def set_square_color square, color_idx=nil, stroke=false
    styles= {
      :strokewidth => stroke ? SQUARE_HALO_SIZE : 0,
      :stroke      => SQUARE_HALO_COLOR
    }
    styles[:fill]= hsl(*@color_table[color_idx]) unless color_idx.nil?
    square.style(styles)
  end

  def square x, y, color_idx
    rect(:left        => x,
         :top         => y,
         :width       => SQUARE_SIZE,
         :height      => SQUARE_SIZE,
         :curve       => SQUARE_CURVE,
         :fill        => hsl(*@color_table[color_idx]),
         :strokewidth => 0)
  end

  ###############

  @my_server= Server.new(self)
  @server= @my_server
  drb= DRb.start_service('druby://127.0.0.1:0', @server)

  background(BACKGROUND_COLOR)
  make_palette
  make_canvas
  select_color(0)

  para('You are @ ', strong(drb.uri.to_s),
       :align => 'center', :top => CANVAS_HEIGHT + 3, :stroke => TEXT_COLOR)
  join_widgets

  click { |button, x, y| check_canvas_click(button, x, y) }

end
