display transform methods

display transform methods

module Yeah
module Web
class Display
  attr_reader :font_type, :font_size

  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.font_type = DEFAULT_DISPLAY_FONT_TYPE
    self.font_size = DEFAULT_DISPLAY_FONT_SIZE
    @transform = [1, 0, 0, 1, 0, 0]
    @transforms = []
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke
    C[`#@context.strokeStyle`]
  end
  def stroke=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def line_width
    `#@context.lineWidth`
  end
  def line_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def transform
    @transform + [0, 0, 1] # appendage to fulfill signature
  end

  def translate(distance)
    @transform[4] += `#{@transform[0]} * #{distance.x} + #{@transform[2]} * #{distance.y}`
    @transform[5] += `#{@transform[1]} * #{distance.x} + #{@transform[3]} * #{distance.y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale(multiplier)
    %x{
      #{@transform} = [#{@transform[0]} * #{multiplier.x},
                       #{@transform[1]} * #{multiplier.x},
                       #{@transform[2]} * #{multiplier.y},
                       #{@transform[3]} * #{multiplier.y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def rotate(radians)
    %x{
      var cos = Math.cos(#{radians}),
          sin = Math.sin(#{radians}),
          e0 = #{@transform[0]} * cos + #{@transform[2]} * sin,
          e1 = #{@transform[1]} * cos + #{@transform[3]} * sin,
          e2 = #{@transform[0]} * -sin + #{@transform[2]} * cos,
          e3 = #{@transform[1]} * -sin + #{@transform[3]} * cos;

      #@transform = [e0, e1, e2, e3, #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def push
    @transforms.push(@transform.dup)
  end

  def pop
    @transform = @transforms.pop

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(position)
    `#@context.moveTo(#{position.x}, #{position.y})`
  end

  def line_to(position)
    `#@context.lineTo(#{position.x}, #{position.y})`
  end

  def stroke
    `#@context.stroke()`
  end

  def fill
    `#@context.fill()`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(position, image)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(position, image, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end

  def font_type=(type)
    @font_type = type
    update_font
  end

  def font_size=(size)
    @font_size = size
    update_font
  end

  def text(position, value)
    `#@context.fillText(#{value}, #{position.x}, #{position.y})`
    `#@context.strokeText(#{value}, #{position.x}, #{position.y})`
  end

  private

  def update_font
    font = "#{font_size}px #{font_type}"
    `#@context.font = #{font}`
  end
end
end
end

module Yeah
module Web
class Display
  attr_reader :font_type, :font_size

  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.font_type = DEFAULT_DISPLAY_FONT_TYPE
    self.font_size = DEFAULT_DISPLAY_FONT_SIZE
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke
    C[`#@context.strokeStyle`]
  end
  def stroke=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def line_width
    `#@context.lineWidth`
  end
  def line_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(position)
    `#@context.moveTo(#{position.x}, #{position.y})`
  end

  def line_to(position)
    `#@context.lineTo(#{position.x}, #{position.y})`
  end

  def stroke
    `#@context.stroke()`
  end

  def fill
    `#@context.fill()`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(position, image)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(position, image, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end

  def font_type=(type)
    @font_type = type
    update_font
  end

  def font_size=(size)
    @font_size = size
    update_font
  end

  def text(position, value)
    `#@context.fillText(#{value}, #{position.x}, #{position.y})`
    `#@context.strokeText(#{value}, #{position.x}, #{position.y})`
  end

  private

  def update_font
    font = "#{font_size}px #{font_type}"
    `#@context.font = #{font}`
  end
end
end
end


removed .opal file

removed .opal file

OPAL-1.0 Object
MODBUS::Block {
  name=Modbus_device
  device=modbus_slv
}
MODBUS::Slave {
  name=modbus_slv
  proto_name=TCP
  slave_id=17
  ip=192.168.10.30
  port=1050
  verbose=0
  nb_bits=1
  nb_input_bits=1
  nb_registers=2002
  nb_input_registers=5
  pinout {
		incoming_bits {
			pins {
				item {
					addr=0
				}
			}
	   }
	   	incoming_input_bits {
			pins {
				item {
					addr=0
				}
			}
	   }
	   	incoming_registers {
			pins {
				item {
					addr=0
				}
			}
	   }
	   	incoming_input_registers {
			pins {
				item {
					addr=0
				}
				item {
					addr=1
				}
				item {
					addr=2
				}
				item {
					addr=3
				}
				item {
					addr=4
				}
			}
	   }
	   	outgoing_bits {
			pins {
				item {
					addr=0
				}
			}
	   }
	   	outgoing_registers{
			pins {
				item {
					addr=2000
				}
				item {
					addr=2001
				}
			}
	   }
  }
  tasks {
    MODBUS::Task {
      type=mailbox
      deadline=100
    }
  }
}


display #line -> #stroke_line

display #line -> #stroke_line

module Yeah
module Web
class Display
  attr_reader :font_type, :font_size

  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.font_type = DEFAULT_DISPLAY_FONT_TYPE
    self.font_size = DEFAULT_DISPLAY_FONT_SIZE
    @transform = [1, 0, 0, 1, 0, 0]
    @transforms = []
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def width
    `#@canvas.width`
  end
  def width=(value)
    `#@canvas.width =  #{value.x}`
  end

  def height
    `#@canvas.height`
  end
  def height=(value)
    `#@canvas.height =  #{value.x}`
  end

  def fill_color
    C[`#@context.fillStyle`]
  end
  def fill_color=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke_color
    C[`#@context.strokeStyle`]
  end
  def stroke_color=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def stroke_width
    `#@context.lineWidth`
  end
  def stroke_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def font_type=(type)
    @font_type = type

    font = "#{@font_size}px #{@font_type}"
    `#@context.font = #{font}`
  end

  def font_size=(size)
    @font_size = size

    font = "#{@font_size}px #{@font_type}"
    `#@context.font = #{font}`
  end

  def transform
    @transform + [0, 0, 1] # appendage to fulfill signature
  end

  def translate(distance)
    @transform[4] += `#{@transform[0]} * #{distance.x} + #{@transform[2]} * #{distance.y}`
    @transform[5] += `#{@transform[1]} * #{distance.x} + #{@transform[3]} * #{distance.y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale(multiplier)
    %x{
      #{@transform} = [#{@transform[0]} * #{multiplier.x},
                       #{@transform[1]} * #{multiplier.x},
                       #{@transform[2]} * #{multiplier.y},
                       #{@transform[3]} * #{multiplier.y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def rotate(radians)
    %x{
      var cos = Math.cos(#{radians}),
          sin = Math.sin(#{radians}),
          e0 = #{@transform[0]} * cos + #{@transform[2]} * sin,
          e1 = #{@transform[1]} * cos + #{@transform[3]} * sin,
          e2 = #{@transform[0]} * -sin + #{@transform[2]} * cos,
          e3 = #{@transform[1]} * -sin + #{@transform[3]} * cos;

      #@transform = [e0, e1, e2, e3, #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def push
    @transforms.push(@transform.dup)
  end

  def pop
    @transform = @transforms.pop

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def stroke_line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_rectangle(position, size)
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def fill_rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(position)
    `#@context.moveTo(#{position.x}, #{position.y})`
  end

  def line_to(position)
    `#@context.lineTo(#{position.x}, #{position.y})`
  end

  def stroke_shape
    `#@context.stroke()`
  end

  def fill_shape
    `#@context.fill()`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(image, position, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end

  def fill_text(value, position)
    `#@context.fillText(#{value}, #{position.x}, #{position.y})`
  end

  def stroke_text(value, position)
    `#@context.strokeText(#{value}, #{position.x}, #{position.y})`
  end
end
end
end

module Yeah
module Web
class Display
  attr_reader :font_type, :font_size

  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.font_type = DEFAULT_DISPLAY_FONT_TYPE
    self.font_size = DEFAULT_DISPLAY_FONT_SIZE
    @transform = [1, 0, 0, 1, 0, 0]
    @transforms = []
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def width
    `#@canvas.width`
  end
  def width=(value)
    `#@canvas.width =  #{value.x}`
  end

  def height
    `#@canvas.height`
  end
  def height=(value)
    `#@canvas.height =  #{value.x}`
  end

  def fill_color
    C[`#@context.fillStyle`]
  end
  def fill_color=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke_color
    C[`#@context.strokeStyle`]
  end
  def stroke_color=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def stroke_width
    `#@context.lineWidth`
  end
  def stroke_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def font_type=(type)
    @font_type = type

    font = "#{@font_size}px #{@font_type}"
    `#@context.font = #{font}`
  end

  def font_size=(size)
    @font_size = size

    font = "#{@font_size}px #{@font_type}"
    `#@context.font = #{font}`
  end

  def transform
    @transform + [0, 0, 1] # appendage to fulfill signature
  end

  def translate(distance)
    @transform[4] += `#{@transform[0]} * #{distance.x} + #{@transform[2]} * #{distance.y}`
    @transform[5] += `#{@transform[1]} * #{distance.x} + #{@transform[3]} * #{distance.y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale(multiplier)
    %x{
      #{@transform} = [#{@transform[0]} * #{multiplier.x},
                       #{@transform[1]} * #{multiplier.x},
                       #{@transform[2]} * #{multiplier.y},
                       #{@transform[3]} * #{multiplier.y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def rotate(radians)
    %x{
      var cos = Math.cos(#{radians}),
          sin = Math.sin(#{radians}),
          e0 = #{@transform[0]} * cos + #{@transform[2]} * sin,
          e1 = #{@transform[1]} * cos + #{@transform[3]} * sin,
          e2 = #{@transform[0]} * -sin + #{@transform[2]} * cos,
          e3 = #{@transform[1]} * -sin + #{@transform[3]} * cos;

      #@transform = [e0, e1, e2, e3, #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def push
    @transforms.push(@transform.dup)
  end

  def pop
    @transform = @transforms.pop

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_rectangle(position, size)
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def fill_rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(position)
    `#@context.moveTo(#{position.x}, #{position.y})`
  end

  def line_to(position)
    `#@context.lineTo(#{position.x}, #{position.y})`
  end

  def stroke_shape
    `#@context.stroke()`
  end

  def fill_shape
    `#@context.fill()`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(image, position, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end

  def fill_text(value, position)
    `#@context.fillText(#{value}, #{position.x}, #{position.y})`
  end

  def stroke_text(value, position)
    `#@context.strokeText(#{value}, #{position.x}, #{position.y})`
  end
end
end
end


Web::Context#color

Web::Context#color

module Yeah
module Web

class Context
  include Opal

  VERTEX_SHADER = <<-glsl
    attribute vec2 a_position;

    uniform vec2 u_resolution;

    void main(void) {
      vec2 clipspace = a_position / u_resolution * 2.0 - 1.0;

      gl_Position = vec4(clipspace, 0, 1);
    }
  glsl

  FRAGMENT_SHADER = <<-glsl
    precision mediump float;

    uniform vec3 u_color;

    void main(void) {
      gl_FragColor = vec4(u_color, 1.0);
    }
  glsl

  def initialize
    @window = Native::Object.new(`window`)
    @canvas = Native::Object.new(`document.getElementsByTagName('canvas')[0]`)
    @gl = @canvas.getContext('webgl')

    setup_shaders
  end

  def each_tick(&block)
    @window.requestAnimationFrame do
      yield
      each_tick(&block)
    end
  end

  def resolution
    V[@canvas.width, @canvas.height]
  end
  def resolution=(value)
    value = V[value]

    @canvas.width = value[0]
    @canvas.height = value[1]
  end

  def render(space)
    @gl.viewport(0, 0, @canvas.width, @canvas.height)

    background(space.background)

    space.things.each do |thing|
      color(thing.visual.color)
      rectangle(thing.position, thing.visual.size)
    end
  end

  def background(*color)
    rgb = Color[*color].rgb

    @gl.clearColor(*rgb, 1)
    @gl.clear(@gl.COLOR_BUFFER_BIT)
  end

  def color(color)
    @gl.uniform3f(@col_loc, *color.rgb)
  end

  def rectangle(position, size)
    res = @gl.getUniformLocation(@shader_program, 'u_resolution')
    @gl.uniform2f(res, resolution[0], resolution[1])

    rect = {
      bottom: position[0],
      left: position[1],
      width: size[0],
      height: size[1]
    }
    vertices = [
      rect[:left], rect[:bottom],
      rect[:left], rect[:bottom] + rect[:height],
      rect[:left] + rect[:width], rect[:bottom],
      rect[:left] + rect[:width], rect[:bottom] + rect[:height]
    ]

    pos_buffer = @gl.createBuffer
    @gl.bindBuffer(@gl.ARRAY_BUFFER, pos_buffer)
    gl_vertices = Native::Object.new(`new Float32Array(#{vertices})`)
    @gl.bufferData(@gl.ARRAY_BUFFER, gl_vertices, @gl.STATIC_DRAW)
    @gl.vertexAttribPointer(@pos_loc, 2, @gl.FLOAT, false, 0, 0)

    @gl.drawArrays(@gl.TRIANGLE_STRIP, 0, 4)
  end

  private

  def setup_shaders
    @shader_program = @gl.createProgram

    vertex_shader = @gl.createShader(@gl.VERTEX_SHADER)
    @gl.shaderSource(vertex_shader, VERTEX_SHADER)
    @gl.compileShader(vertex_shader)
    @gl.attachShader(@shader_program, vertex_shader)

    fragment_shader = @gl.createShader(@gl.FRAGMENT_SHADER)
    @gl.shaderSource(fragment_shader, FRAGMENT_SHADER)
    @gl.compileShader(fragment_shader)
    @gl.attachShader(@shader_program, fragment_shader)

    @gl.linkProgram(@shader_program)
    linked = @gl.getProgramParameter(@shader_program, @gl.LINK_STATUS)
    if !linked
      error = @gl.getProgramInfoLog(@shader_program)
      puts "GL program link error: #{error}"
      return
    end

    @gl.useProgram(@shader_program)

    @pos_loc = @gl.getAttribLocation(@shader_program, 'a_position')
    @gl.enableVertexAttribArray(@pos_loc)

    @col_loc = @gl.getUniformLocation(@shader_program, 'u_color')
  end
end

end
end

module Yeah
module Web

class Context
  include Opal

  VERTEX_SHADER = <<-glsl
    attribute vec2 a_position;

    uniform vec2 u_resolution;

    void main(void) {
      vec2 clipspace = a_position / u_resolution * 2.0 - 1.0;

      gl_Position = vec4(clipspace, 0, 1);
    }
  glsl

  FRAGMENT_SHADER = <<-glsl
    precision mediump float;

    uniform vec3 u_color;

    void main(void) {
      gl_FragColor = vec4(u_color, 1.0);
    }
  glsl

  def initialize
    @window = Native::Object.new(`window`)
    @canvas = Native::Object.new(`document.getElementsByTagName('canvas')[0]`)
    @gl = @canvas.getContext('webgl')

    setup_shaders
  end

  def each_tick(&block)
    @window.requestAnimationFrame do
      yield
      each_tick(&block)
    end
  end

  def resolution
    V[@canvas.width, @canvas.height]
  end
  def resolution=(value)
    value = V[value]

    @canvas.width = value[0]
    @canvas.height = value[1]
  end

  def render(space)
    @gl.viewport(0, 0, @canvas.width, @canvas.height)

    background(space.background)

    space.things.each do |thing|
      rectangle(thing.position, thing.visual.size, thing.visual.color)
    end
  end

  def background(*color)
    rgb = Color[*color].rgb

    @gl.clearColor(*rgb, 1)
    @gl.clear(@gl.COLOR_BUFFER_BIT)
  end

  def rectangle(position, size, color)
    res = @gl.getUniformLocation(@shader_program, 'u_resolution')
    @gl.uniform2f(res, resolution[0], resolution[1])

    rect = {
      bottom: position[0],
      left: position[1],
      width: size[0],
      height: size[1]
    }
    vertices = [
      rect[:left], rect[:bottom],
      rect[:left], rect[:bottom] + rect[:height],
      rect[:left] + rect[:width], rect[:bottom],
      rect[:left] + rect[:width], rect[:bottom] + rect[:height]
    ]

    pos_buffer = @gl.createBuffer
    @gl.bindBuffer(@gl.ARRAY_BUFFER, pos_buffer)
    gl_vertices = Native::Object.new(`new Float32Array(#{vertices})`)
    @gl.bufferData(@gl.ARRAY_BUFFER, gl_vertices, @gl.STATIC_DRAW)
    @gl.vertexAttribPointer(@pos_loc, 2, @gl.FLOAT, false, 0, 0)

    @gl.uniform3f(@col_loc, *color.rgb)

    @gl.drawArrays(@gl.TRIANGLE_STRIP, 0, 4)
  end

  private

  def setup_shaders
    @shader_program = @gl.createProgram

    vertex_shader = @gl.createShader(@gl.VERTEX_SHADER)
    @gl.shaderSource(vertex_shader, VERTEX_SHADER)
    @gl.compileShader(vertex_shader)
    @gl.attachShader(@shader_program, vertex_shader)

    fragment_shader = @gl.createShader(@gl.FRAGMENT_SHADER)
    @gl.shaderSource(fragment_shader, FRAGMENT_SHADER)
    @gl.compileShader(fragment_shader)
    @gl.attachShader(@shader_program, fragment_shader)

    @gl.linkProgram(@shader_program)
    linked = @gl.getProgramParameter(@shader_program, @gl.LINK_STATUS)
    if !linked
      error = @gl.getProgramInfoLog(@shader_program)
      puts "GL program link error: #{error}"
      return
    end

    @gl.useProgram(@shader_program)

    @pos_loc = @gl.getAttribLocation(@shader_program, 'a_position')
    @gl.enableVertexAttribArray(@pos_loc)

    @col_loc = @gl.getUniformLocation(@shader_program, 'u_color')
  end
end

end
end


キーワード追加後のフォームクリア後にtextfieldが空なのに追加ボタンがdisableにならなかった問題を修正

Document.ready? do
  if Element.find('body.keywords').size > 0
    def append_keyword(event)
      form = event.current_target.parents.map{|parent| parent if parent.has_class?('new_keyword')}.compact.first
      # 通信中はボタンをdisableにする
      button = event.current_target
      button.add_class('disabled')

      HTTP.post(form['action'], :payload => form.serialize) do |response|
        # 通信終了したのでボタンを元に戻す
        button.remove_class('disabled')

        if response.ok?
          data = response.json
          # リストにキーワード追加
          list_item = Element.parse(data['list_item'])
          form.parent.find('.keywords').append(list_item)
          # プレビューモーダル追加
          modal = Element.parse(data['modal'])
          Element.find('#preview-modal-list').append(modal)
          # 追加したキーワードのプレビューイベント
          Modal.init_open_button(list_item.find('.button[data-modal]'))
          Modal.init_dialog(modal.find('.button.close'))
          # 削除イベント
          list_item.find('.button.delete').on('click') do |event|
            delete_keyword(event)
            false
          end
          # フォームクリア
          form.find('input[type="text"]').value = ''
          button.add_class('disabled')
        else
          alert('追加に失敗しました。')
        end
      end
    end

    def delete_keyword(event)
      keyword_id = event.current_target.attr('data-id')
      confirm('このキーワードを削除します') do
        HTTP.delete("/keywords/#{keyword_id}") do |response|
          if response.ok?
            # リストから削除
            Element.find("li[data-id='#{keyword_id}']").fade_out
          else
            alert('削除に失敗しました。')
          end
        end
      end
    end

    # 既存キーワード一覧にイベントハンドラ設定
    Element.find('ul.keywords>li .button.delete').on('click') do |event|
      delete_keyword(event)
      false
    end

    # 追加ボタンにイベントハンドラ設定
    Element.find('form.new_keyword .button').on('click') do |event|
      append_keyword(event) unless event.current_target.has_class?('disabled')
      false
    end

    # 追加ボタンはキーワードが入力され、重複していない場合のみenable
    Element.find('input[name="keyword[value]"]').on('keyup') do |event|
      text_field = event.current_target
      form = text_field.parent.parent
      button = form.find('a.button')
      keywords = form.parent.find('.keywords .value a.button').map{|e| e.text.gsub(/\n/, '').strip }
      if text_field.value.to_s.strip == '' or keywords.include?(text_field.value)
        button.add_class('disabled')
      else
        button.remove_class('disabled')
      end

      false
    end

    # ユーザ設定
    Element.find('#user-settings input[type="checkbox"]').on('change') do |event|
      form = Element.find('#user-settings')
      HTTP.post(form['action'], :payload => form.serialize) do |response|
        if response.ok?
          # do nothing
        else
          alert('設定の変更に失敗しました。')
        end
      end
    end

    Element.find('#user_private').on('change') do |event|
      # 非公開の場合のみランダムURLのチェックボックスを有効にする
      if Element.find('#user_private').is(':checked')
        Element.find('#random-url-container').remove_class('disabled')
        Element.find('#user_random_url').remove_attr('disabled')
      else
        Element.find('#random-url-container').add_class('disabled')
        Element.find('#user_random_url')['disabled'] = 'disabled'
      end
    end
  end
end

Document.ready? do
  if Element.find('body.keywords').size > 0
    def append_keyword(event)
      form = event.current_target.parents.map{|parent| parent if parent.has_class?('new_keyword')}.compact.first
      # 通信中はボタンをdisableにする
      button = event.current_target
      button.add_class('disabled')

      HTTP.post(form['action'], :payload => form.serialize) do |response|
        # 通信終了したのでボタンを元に戻す
        button.remove_class('disabled')

        if response.ok?
          data = response.json
          # リストにキーワード追加
          list_item = Element.parse(data['list_item'])
          form.parent.find('.keywords').append(list_item)
          # プレビューモーダル追加
          modal = Element.parse(data['modal'])
          Element.find('#preview-modal-list').append(modal)
          # 追加したキーワードのプレビューイベント
          Modal.init_open_button(list_item.find('.button[data-modal]'))
          Modal.init_dialog(modal.find('.button.close'))
          # 削除イベント
          list_item.find('.button.delete').on('click') do |event|
            delete_keyword(event)
            false
          end
          # フォームクリア
          form.find('input[type="text"]').value = ''

        else
          alert('追加に失敗しました。')
        end
      end
    end

    def delete_keyword(event)
      keyword_id = event.current_target.attr('data-id')
      confirm('このキーワードを削除します') do
        HTTP.delete("/keywords/#{keyword_id}") do |response|
          if response.ok?
            # リストから削除
            Element.find("li[data-id='#{keyword_id}']").fade_out
          else
            alert('削除に失敗しました。')
          end
        end
      end
    end

    # 既存キーワード一覧にイベントハンドラ設定
    Element.find('ul.keywords>li .button.delete').on('click') do |event|
      delete_keyword(event)
      false
    end

    # 追加ボタンにイベントハンドラ設定
    Element.find('form.new_keyword .button').on('click') do |event|
      append_keyword(event) unless event.current_target.has_class?('disabled')
      false
    end

    # 追加ボタンはキーワードが入力され、重複していない場合のみenable
    Element.find('input[name="keyword[value]"]').on('keyup') do |event|
      text_field = event.current_target
      form = text_field.parent.parent
      button = form.find('a.button')
      keywords = form.parent.find('.keywords .value a.button').map{|e| e.text.gsub(/\n/, '').strip }
      if text_field.value.to_s.strip == '' or keywords.include?(text_field.value)
        button.add_class('disabled')
      else
        button.remove_class('disabled')
      end

      false
    end

    # ユーザ設定
    Element.find('#user-settings input[type="checkbox"]').on('change') do |event|
      form = Element.find('#user-settings')
      HTTP.post(form['action'], :payload => form.serialize) do |response|
        if response.ok?
          # do nothing
        else
          alert('設定の変更に失敗しました。')
        end
      end
    end

    Element.find('#user_private').on('change') do |event|
      # 非公開の場合のみランダムURLのチェックボックスを有効にする
      if Element.find('#user_private').is(':checked')
        Element.find('#random-url-container').remove_class('disabled')
        Element.find('#user_random_url').remove_attr('disabled')
      else
        Element.find('#random-url-container').add_class('disabled')
        Element.find('#user_random_url')['disabled'] = 'disabled'
      end
    end
  end
end


fixed web ticker elapsed time

fixed web ticker elapsed time

module Yeah
module Web
class Ticker
  attr_accessor :rate
  attr_reader :tick_count, :actual_rate

  def initialize(args = {})
    @rate = args.fetch(:rate, DEFAULT_TICKER_RATE)
    @tick_count = 0
    @actual_rate = 0
    @next_actual_rate = 0
  end

  def on_tick(&block)
    # TODO: add to ticks before yielding
    %x{
      var lastTime = (new Date()).getTime(),
          lastMeasureTime = lastTime,
          elapsed = 0,
          interval,
          currentTime;

      var loop = function() {
        interval = 1.0 / #{@rate};
        currentTime = new Date().getTime();
        elapsed = (currentTime - lastTime) / 1000.0;

        if (elapsed > interval) {
          #{yield `elapsed`}

          #@tick_count += 1;
          #@next_actual_rate += 1;

          lastTime = currentTime - (elapsed % interval);
        }

        if (currentTime - lastMeasureTime > 1000) {
          #@actual_rate = #@next_actual_rate;
          #@next_actual_rate = 0;

          lastMeasureTime = currentTime - (currentTime - lastMeasureTime) % 1000;
        }

        window.requestAnimationFrame(loop);
      };

      window.requestAnimationFrame(loop);
    }
  end
end
end
end

module Yeah
module Web
class Ticker
  attr_accessor :rate
  attr_reader :tick_count, :actual_rate

  def initialize(args = {})
    @rate = args.fetch(:rate, DEFAULT_TICKER_RATE)
    @tick_count = 0
    @actual_rate = 0
    @next_actual_rate = 0
  end

  def on_tick(&block)
    # TODO: add to ticks before yielding
    %x{
      var lastTime = (new Date()).getTime(),
          lastMeasureTime = lastTime,
          elapsed = 0,
          interval,
          currentTime;

      var loop = function() {
        interval = 1000.0 / #{@rate};
        currentTime = new Date().getTime();
        elapsed = currentTime - lastTime;

        if (elapsed > interval) {
          #{yield `elapsed`}

          #@tick_count += 1;
          #@next_actual_rate += 1;

          lastTime = currentTime - (elapsed % interval);
        }

        if (currentTime - lastMeasureTime > 1000) {
          #@actual_rate = #@next_actual_rate;
          #@next_actual_rate = 0;

          lastMeasureTime = currentTime - (currentTime - lastMeasureTime) % 1000;
        }

        window.requestAnimationFrame(loop);
      };

      window.requestAnimationFrame(loop);
    }
  end
end
end
end


キーワード入力フォームのtextfieldでエンターを押すとJSONが表示される問題を修正

Document.ready? do
  if Element.find('body.keywords').size > 0
    def append_keyword(event)
      form = event.current_target.parents.map{|parent| parent if parent.has_class?('new_keyword')}.compact.first
      # 通信中はボタンをdisableにする
      button = form.find('.button')
      button.add_class('disabled')

      HTTP.post(form['action'], :payload => form.serialize) do |response|
        # 通信終了したのでボタンを元に戻す
        button.remove_class('disabled')

        if response.ok?
          data = response.json
          # リストにキーワード追加
          list_item = Element.parse(data['list_item'])
          form.parent.find('.keywords').append(list_item)
          # プレビューモーダル追加
          modal = Element.parse(data['modal'])
          Element.find('#preview-modal-list').append(modal)
          # 追加したキーワードのプレビューイベント
          Modal.init_open_button(list_item.find('.button[data-modal]'))
          Modal.init_dialog(modal.find('.button.close'))
          # 削除イベント
          list_item.find('.button.delete').on('click') do |event|
            delete_keyword(event)
            false
          end
          # フォームクリア
          form.find('input[type="text"]').value = ''
          button.add_class('disabled')
        else
          alert('追加に失敗しました。')
        end
      end
    end

    def delete_keyword(event)
      keyword_id = event.current_target.attr('data-id')
      confirm('このキーワードを削除します') do
        HTTP.delete("/keywords/#{keyword_id}") do |response|
          if response.ok?
            # リストから削除
            Element.find("li[data-id='#{keyword_id}']").fade_out
          else
            alert('削除に失敗しました。')
          end
        end
      end
    end

    # 既存キーワード一覧にイベントハンドラ設定
    Element.find('ul.keywords>li .button.delete').on('click') do |event|
      delete_keyword(event)
      false
    end

    # 追加ボタンにイベントハンドラ設定
    Element.find('form.new_keyword .button').on('click') do |event|
      append_keyword(event) unless event.current_target.has_class?('disabled')
      false
    end

    # textfieldでエンター
    Element.find('input[name="keyword[value]"]').on('keypress') do |event|
      false if event.which == 13
    end

    # 追加ボタンはキーワードが入力され、重複していない場合のみenable
    Element.find('input[name="keyword[value]"]').on('keyup') do |event|
      text_field = event.current_target
      form = text_field.parent.parent
      button = form.find('a.button')
      keywords = form.parent.find('.keywords .value a.button').map{|e| e.text.gsub(/\n/, '').strip }
      if text_field.value.to_s.strip == '' or keywords.include?(text_field.value)
        button.add_class('disabled')
      else
        button.remove_class('disabled')
      end

      if event.which == 13
        append_keyword(event) unless event.current_target.has_class?('disabled')
      end

      false
    end

    # ユーザ設定
    Element.find('#user-settings input[type="checkbox"]').on('change') do |event|
      form = Element.find('#user-settings')
      HTTP.post(form['action'], :payload => form.serialize) do |response|
        if response.ok?
          # do nothing
        else
          alert('設定の変更に失敗しました。')
        end
      end
    end

    Element.find('#user_private').on('change') do |event|
      # 非公開の場合のみランダムURLのチェックボックスを有効にする
      if Element.find('#user_private').is(':checked')
        Element.find('#random-url-container').remove_class('disabled')
        Element.find('#user_random_url').remove_attr('disabled')
      else
        Element.find('#random-url-container').add_class('disabled')
        Element.find('#user_random_url')['disabled'] = 'disabled'
      end
    end
  end
end

Document.ready? do
  if Element.find('body.keywords').size > 0
    def append_keyword(event)
      form = event.current_target.parents.map{|parent| parent if parent.has_class?('new_keyword')}.compact.first
      # 通信中はボタンをdisableにする
      button = event.current_target
      button.add_class('disabled')

      HTTP.post(form['action'], :payload => form.serialize) do |response|
        # 通信終了したのでボタンを元に戻す
        button.remove_class('disabled')

        if response.ok?
          data = response.json
          # リストにキーワード追加
          list_item = Element.parse(data['list_item'])
          form.parent.find('.keywords').append(list_item)
          # プレビューモーダル追加
          modal = Element.parse(data['modal'])
          Element.find('#preview-modal-list').append(modal)
          # 追加したキーワードのプレビューイベント
          Modal.init_open_button(list_item.find('.button[data-modal]'))
          Modal.init_dialog(modal.find('.button.close'))
          # 削除イベント
          list_item.find('.button.delete').on('click') do |event|
            delete_keyword(event)
            false
          end
          # フォームクリア
          form.find('input[type="text"]').value = ''
          button.add_class('disabled')
        else
          alert('追加に失敗しました。')
        end
      end
    end

    def delete_keyword(event)
      keyword_id = event.current_target.attr('data-id')
      confirm('このキーワードを削除します') do
        HTTP.delete("/keywords/#{keyword_id}") do |response|
          if response.ok?
            # リストから削除
            Element.find("li[data-id='#{keyword_id}']").fade_out
          else
            alert('削除に失敗しました。')
          end
        end
      end
    end

    # 既存キーワード一覧にイベントハンドラ設定
    Element.find('ul.keywords>li .button.delete').on('click') do |event|
      delete_keyword(event)
      false
    end

    # 追加ボタンにイベントハンドラ設定
    Element.find('form.new_keyword .button').on('click') do |event|
      append_keyword(event) unless event.current_target.has_class?('disabled')
      false
    end

    # 追加ボタンはキーワードが入力され、重複していない場合のみenable
    Element.find('input[name="keyword[value]"]').on('keyup') do |event|
      text_field = event.current_target
      form = text_field.parent.parent
      button = form.find('a.button')
      keywords = form.parent.find('.keywords .value a.button').map{|e| e.text.gsub(/\n/, '').strip }
      if text_field.value.to_s.strip == '' or keywords.include?(text_field.value)
        button.add_class('disabled')
      else
        button.remove_class('disabled')
      end

      false
    end

    # ユーザ設定
    Element.find('#user-settings input[type="checkbox"]').on('change') do |event|
      form = Element.find('#user-settings')
      HTTP.post(form['action'], :payload => form.serialize) do |response|
        if response.ok?
          # do nothing
        else
          alert('設定の変更に失敗しました。')
        end
      end
    end

    Element.find('#user_private').on('change') do |event|
      # 非公開の場合のみランダムURLのチェックボックスを有効にする
      if Element.find('#user_private').is(':checked')
        Element.find('#random-url-container').remove_class('disabled')
        Element.find('#user_random_url').remove_attr('disabled')
      else
        Element.find('#random-url-container').add_class('disabled')
        Element.find('#user_random_url')['disabled'] = 'disabled'
      end
    end
  end
end


web mouse todo

web mouse todo

module Yeah
module Web

class Mouse
  def initialize(screen)
    @screen = screen

    setup_position_listener
    setup_click_listeners
  end

  def position
    @position ||= V[]
  end

  def left_clicked?
    @left_clicked || false
  end

  def middle_clicked?
    @middle_clicked || false
  end

  def right_clicked?
    @right_clicked || false
  end

  private

  # TODO: cleanup
  def setup_position_listener
    @screen.canvas.addEventListener('mousemove') do |event|
      canvas_bounds = @screen.canvas.getBoundingClientRect
      canvas_size = V[canvas_bounds.width, canvas_bounds.height]
      canvas_offset = V[canvas_bounds.left, canvas_bounds.top]
      real_mouse_pos = V[`event.clientX`, `event.clientY`]
      canvas_mouse_pos = real_mouse_pos - canvas_offset
      canvas_mouse_pos.y = canvas_size.y - canvas_mouse_pos.y

      @position = canvas_mouse_pos * @screen.resolution / canvas_size
    end
  end

  # TODO: cleanup
  def setup_click_listeners
    @screen.canvas.addEventListener('mousedown') do |event|
      case `event.button`
      when 0:
        @left_clicked = true
      when 1:
        @middle_clicked = true
      when 2:
        @right_clicked = true
      end
    end

    @screen.canvas.addEventListener('mouseup') do |event|
      case `event.button`
      when 0:
        @left_clicked = false
      when 1:
        @middle_clicked = false
      when 2:
        @right_clicked = false
      end
    end
  end
end

end
end

module Yeah
module Web

class Mouse
  def initialize(screen)
    @screen = screen

    setup_position_listener
    setup_click_listeners
  end

  def position
    @position ||= V[]
  end

  def left_clicked?
    @left_clicked || false
  end

  def middle_clicked?
    @middle_clicked || false
  end

  def right_clicked?
    @right_clicked || false
  end

  private

  # TODO: cleanup
  def setup_position_listener
    @screen.canvas.addEventListener('mousemove') do |event|
      canvas_bounds = @screen.canvas.getBoundingClientRect
      canvas_size = V[canvas_bounds.width, canvas_bounds.height]
      canvas_offset = V[canvas_bounds.left, canvas_bounds.top]
      real_mouse_pos = V[`event.clientX`, `event.clientY`]
      canvas_mouse_pos = real_mouse_pos - canvas_offset
      canvas_mouse_pos.y = canvas_size.y - canvas_mouse_pos.y

      @position = canvas_mouse_pos * @screen.resolution / canvas_size
    end
  end

  def setup_click_listeners
    @screen.canvas.addEventListener('mousedown') do |event|
      case `event.button`
      when 0:
        @left_clicked = true
      when 1:
        @middle_clicked = true
      when 2:
        @right_clicked = true
      end
    end

    @screen.canvas.addEventListener('mouseup') do |event|
      case `event.button`
      when 0:
        @left_clicked = false
      when 1:
        @middle_clicked = false
      when 2:
        @right_clicked = false
      end
    end
  end
end

end
end


early web screen matrix stuff :)

early web screen matrix stuff :)

module Yeah
module Web

class Screen
  include Opal

  VERTEX_SHADER = <<-glsl
    attribute vec2 a_position;

    uniform vec2 u_resolution;
    uniform mat4 u_mv_matrix;
    uniform mat4 u_p_matrix;

    void main(void) {
      vec2 clipspace = a_position / u_resolution * 2.0 - 1.0;

      //gl_Position = vec4(clipspace, 0, 1);
      gl_Position = u_p_matrix * u_mv_matrix * vec4(clipspace, 1.0, 1.0);
    }
  glsl

  FRAGMENT_SHADER = <<-glsl
    precision mediump float;

    uniform vec3 u_color;

    void main(void) {
      gl_FragColor = vec4(u_color, 1.0);
    }
  glsl

  def initialize
    @canvas = Native::Object.new(`document.getElementsByTagName('canvas')[0]`)
    @gl = @canvas.getContext('webgl')

    setup_matrices
    setup_shaders
  end

  attr_reader :canvas

  def resolution
    V[@canvas.width, @canvas.height]
  end
  def resolution=(value)
    value = V[value]

    @canvas.width = value[0]
    @canvas.height = value[1]
  end

  def render(space)
    @gl.viewport(0, 0, @canvas.width, @canvas.height)

    background(space.background)

    @mat4.perspective(45, @canvas.width / @canvas.height, 0.1, 100, @p_matrix)
    @mat4.identity(@mv_matrix)

    # todo: test stuff, remove :)
    translate [`Math.random()`, 0, 0]

    space.things.each do |thing|
      thing.visual.render(self, thing.position)
    end
  end

  def background(*color)
    rgb = Color[*color].rgb

    @gl.clearColor(*rgb, 1)
    @gl.clear(@gl.COLOR_BUFFER_BIT)
  end

  def color(color)
    @gl.uniform3f(@col_loc, *color.rgb)
  end

  def rectangle(position, size)
    res = @gl.getUniformLocation(@shader_program, 'u_resolution')
    @gl.uniform2f(res, resolution[0], resolution[1])

    rect = {
      left: position[0],
      bottom: position[1],
      width: size[0],
      height: size[1]
    }
    vertices = [
      rect[:left], rect[:bottom],
      rect[:left], rect[:bottom] + rect[:height],
      rect[:left] + rect[:width], rect[:bottom],
      rect[:left] + rect[:width], rect[:bottom] + rect[:height]
    ]

    pos_buffer = @gl.createBuffer
    @gl.bindBuffer(@gl.ARRAY_BUFFER, pos_buffer)
    gl_vertices = Native::Object.new(`new Float32Array(#{vertices})`)
    @gl.bufferData(@gl.ARRAY_BUFFER, gl_vertices, @gl.STATIC_DRAW)
    @gl.vertexAttribPointer(@pos_loc, 2, @gl.FLOAT, false, 0, 0)

    set_matrix_uniforms
    @gl.drawArrays(@gl.TRIANGLE_STRIP, 0, 4)
  end

  def push
  end

  def pop
  end

  def translate(*amount)
    amount = V[amount].to_a

    @mat4.translate(@mv_matrix, @mv_matrix, amount)
  end

  private

  def setup_matrices
    @mat4 = Native::Object.new(`mat4`)

    @mv_matrix = @mat4.create
    @p_matrix = @mat4.create

    @mv_matrix_stack = []
  end

  def setup_shaders
    @shader_program = @gl.createProgram

    vertex_shader = @gl.createShader(@gl.VERTEX_SHADER)
    @gl.shaderSource(vertex_shader, VERTEX_SHADER)
    @gl.compileShader(vertex_shader)
    @gl.attachShader(@shader_program, vertex_shader)

    fragment_shader = @gl.createShader(@gl.FRAGMENT_SHADER)
    @gl.shaderSource(fragment_shader, FRAGMENT_SHADER)
    @gl.compileShader(fragment_shader)
    @gl.attachShader(@shader_program, fragment_shader)

    @gl.linkProgram(@shader_program)
    linked = @gl.getProgramParameter(@shader_program, @gl.LINK_STATUS)
    if !linked
      error = @gl.getProgramInfoLog(@shader_program)
      puts "GL program link error: #{error}"
      return
    end

    @gl.useProgram(@shader_program)

    @pos_loc = @gl.getAttribLocation(@shader_program, 'a_position')
    @gl.enableVertexAttribArray(@pos_loc)

    @col_loc = @gl.getUniformLocation(@shader_program, 'u_color')

    @p_mtx_loc = @gl.getUniformLocation(@shader_program, 'u_p_matrix')
    @mv_mtx_loc = @gl.getUniformLocation(@shader_program, 'u_mv_matrix')
  end

  def set_matrix_uniforms
    @gl.uniformMatrix4fv(@p_mtx_loc, false, @p_matrix)
    @gl.uniformMatrix4fv(@mv_mtx_loc, false, @mv_matrix)
  end
end

end
end

module Yeah
module Web

class Screen
  include Opal

  VERTEX_SHADER = <<-glsl
    attribute vec2 a_position;

    uniform vec2 u_resolution;

    void main(void) {
      vec2 clipspace = a_position / u_resolution * 2.0 - 1.0;

      gl_Position = vec4(clipspace, 0, 1);
    }
  glsl

  FRAGMENT_SHADER = <<-glsl
    precision mediump float;

    uniform vec3 u_color;

    void main(void) {
      gl_FragColor = vec4(u_color, 1.0);
    }
  glsl

  def initialize
    @canvas = Native::Object.new(`document.getElementsByTagName('canvas')[0]`)
    @gl = @canvas.getContext('webgl')

    setup_shaders
  end

  attr_reader :canvas

  def resolution
    V[@canvas.width, @canvas.height]
  end
  def resolution=(value)
    value = V[value]

    @canvas.width = value[0]
    @canvas.height = value[1]
  end

  def render(space)
    @gl.viewport(0, 0, @canvas.width, @canvas.height)

    background(space.background)

    space.things.each do |thing|
      thing.visual.render(self, thing.position)
    end
  end

  def background(*color)
    rgb = Color[*color].rgb

    @gl.clearColor(*rgb, 1)
    @gl.clear(@gl.COLOR_BUFFER_BIT)
  end

  def color(color)
    @gl.uniform3f(@col_loc, *color.rgb)
  end

  def rectangle(position, size)
    res = @gl.getUniformLocation(@shader_program, 'u_resolution')
    @gl.uniform2f(res, resolution[0], resolution[1])

    rect = {
      left: position[0],
      bottom: position[1],
      width: size[0],
      height: size[1]
    }
    vertices = [
      rect[:left], rect[:bottom],
      rect[:left], rect[:bottom] + rect[:height],
      rect[:left] + rect[:width], rect[:bottom],
      rect[:left] + rect[:width], rect[:bottom] + rect[:height]
    ]

    pos_buffer = @gl.createBuffer
    @gl.bindBuffer(@gl.ARRAY_BUFFER, pos_buffer)
    gl_vertices = Native::Object.new(`new Float32Array(#{vertices})`)
    @gl.bufferData(@gl.ARRAY_BUFFER, gl_vertices, @gl.STATIC_DRAW)
    @gl.vertexAttribPointer(@pos_loc, 2, @gl.FLOAT, false, 0, 0)

    @gl.drawArrays(@gl.TRIANGLE_STRIP, 0, 4)
  end

  private

  def setup_shaders
    @shader_program = @gl.createProgram

    vertex_shader = @gl.createShader(@gl.VERTEX_SHADER)
    @gl.shaderSource(vertex_shader, VERTEX_SHADER)
    @gl.compileShader(vertex_shader)
    @gl.attachShader(@shader_program, vertex_shader)

    fragment_shader = @gl.createShader(@gl.FRAGMENT_SHADER)
    @gl.shaderSource(fragment_shader, FRAGMENT_SHADER)
    @gl.compileShader(fragment_shader)
    @gl.attachShader(@shader_program, fragment_shader)

    @gl.linkProgram(@shader_program)
    linked = @gl.getProgramParameter(@shader_program, @gl.LINK_STATUS)
    if !linked
      error = @gl.getProgramInfoLog(@shader_program)
      puts "GL program link error: #{error}"
      return
    end

    @gl.useProgram(@shader_program)

    @pos_loc = @gl.getAttribLocation(@shader_program, 'a_position')
    @gl.enableVertexAttribArray(@pos_loc)

    @col_loc = @gl.getUniformLocation(@shader_program, 'u_color')
  end
end

end
end


web context vertex shader takes pixel coords

module Yeah
module Web

class Context
  include Opal

  VERTEX_SHADER = <<-glsl
    attribute vec2 a_position;

    uniform vec2 u_resolution;

    void main(void) {
      vec2 clipspace = a_position / u_resolution * 2.0 - 1.0;

      gl_Position = vec4(clipspace, 0, 1);
    }
  glsl

  FRAGMENT_SHADER = <<-glsl
    precision mediump float;

    void main(void) {
      gl_FragColor = vec4(0.2, 0.8, 0.2, 1.0);
    }
  glsl

  def initialize
    @canvas = Native::Object.new(`document.getElementsByTagName('canvas')[0]`)
    @gl = @canvas.getContext('webgl')

    setup_shaders
  end

  def resolution
    V[@canvas.width, @canvas.height]
  end
  def resolution=(value)
    value = V[value]

    @canvas.width = value[0]
    @canvas.height = value[1]
  end

  def each_tick(&block)
    yield
  end

  def render(level)
    @gl.viewport(0, 0, @canvas.width, @canvas.height)

    background(level.background)

    test
  end

  def test
    res = @gl.getUniformLocation(@shader_program, 'u_resolution')
    @gl.uniform2f(res, resolution[0], resolution[1])

    pos_buffer = @gl.createBuffer
    @gl.bindBuffer(@gl.ARRAY_BUFFER, pos_buffer)
    rect = {
      top: 100,
      bottom: 50,
      left: 150,
      right: 200
    }
    vertices = [
      rect[:left], rect[:bottom],
      rect[:left], rect[:top],
      rect[:right], rect[:bottom],
      rect[:right], rect[:top]
    ]
    gl_vertices = Native::Object.new(`new Float32Array(#{vertices})`)
    @gl.bufferData(@gl.ARRAY_BUFFER, gl_vertices, @gl.STATIC_DRAW)
    @gl.vertexAttribPointer(@pos_attr, 2, @gl.FLOAT, false, 0, 0)
    @gl.drawArrays(@gl.TRIANGLE_STRIP, 0, 4)
  end

  def background(*color)
    rgb = Color[*color].rgb

    @gl.clearColor(*rgb, 1)
    @gl.clear(@gl.COLOR_BUFFER_BIT)
  end

  private

  def setup_shaders
    @shader_program = @gl.createProgram

    vertex_shader = @gl.createShader(@gl.VERTEX_SHADER)
    @gl.shaderSource(vertex_shader, VERTEX_SHADER)
    @gl.compileShader(vertex_shader)
    @gl.attachShader(@shader_program, vertex_shader)

    fragment_shader = @gl.createShader(@gl.FRAGMENT_SHADER)
    @gl.shaderSource(fragment_shader, FRAGMENT_SHADER)
    @gl.compileShader(fragment_shader)
    @gl.attachShader(@shader_program, fragment_shader)

    @gl.linkProgram(@shader_program)
    linked = @gl.getProgramParameter(@shader_program, @gl.LINK_STATUS)
    if !linked
      error = @gl.getProgramInfoLog(@shader_program)
      puts "GL program link error: #{error}"
      return
    end

    @gl.useProgram(@shader_program)

    @pos_attr = @gl.getAttribLocation(@shader_program, 'a_position')
    @gl.enableVertexAttribArray(@pos_attr)
  end
end

end
end

module Yeah
module Web

class Context
  include Opal

  VERTEX_SHADER = <<-glsl
    attribute vec2 a_position;

    void main(void) {
      gl_Position = vec4(a_position, 0, 1);
    }
  glsl

  FRAGMENT_SHADER = <<-glsl
    precision mediump float;

    void main(void) {
      gl_FragColor = vec4(0.2, 0.8, 0.2, 1.0);
    }
  glsl

  def initialize
    @canvas = Native::Object.new(`document.getElementsByTagName('canvas')[0]`)
    @gl = @canvas.getContext('webgl')

    setup_shaders
  end

  def resolution
    V[@canvas.width, @canvas.height]
  end
  def resolution=(value)
    value = V[value]

    @canvas.width = value[0]
    @canvas.height = value[1]
  end

  def each_tick(&block)
    yield
  end

  def render(level)
    @gl.viewport(0, 0, @canvas.width, @canvas.height)

    background(level.background)

    test
  end

  def test
    pos_buffer = @gl.createBuffer
    @gl.bindBuffer(@gl.ARRAY_BUFFER, pos_buffer)
    vertices = [
       1,  1,
      -1,  1,
       1, -1,
      -1, -1
    ]
    gl_vertices = Native::Object.new(`new Float32Array(#{vertices})`)
    @gl.bufferData(@gl.ARRAY_BUFFER, gl_vertices, @gl.STATIC_DRAW)
    @gl.vertexAttribPointer(@pos_attr, 2, @gl.FLOAT, false, 0, 0)
    @gl.drawArrays(@gl.TRIANGLE_STRIP, 0, 4)
  end

  def background(*color)
    rgb = Color[*color].rgb

    @gl.clearColor(*rgb, 1)
    @gl.clear(@gl.COLOR_BUFFER_BIT)
  end

  private

  def setup_shaders
    @shader_program = @gl.createProgram

    vertex_shader = @gl.createShader(@gl.VERTEX_SHADER)
    @gl.shaderSource(vertex_shader, VERTEX_SHADER)
    @gl.compileShader(vertex_shader)
    @gl.attachShader(@shader_program, vertex_shader)

    fragment_shader = @gl.createShader(@gl.FRAGMENT_SHADER)
    @gl.shaderSource(fragment_shader, FRAGMENT_SHADER)
    @gl.compileShader(fragment_shader)
    @gl.attachShader(@shader_program, fragment_shader)

    @gl.linkProgram(@shader_program)
    linked = @gl.getProgramParameter(@shader_program, @gl.LINK_STATUS)
    if !linked
      error = @gl.getProgramInfoLog(@shader_program)
      puts "GL program link error: #{error}"
      return
    end

    @gl.useProgram(@shader_program)

    @pos_attr = @gl.getAttribLocation(@shader_program, 'a_position')
    @gl.enableVertexAttribArray(@pos_attr)
  end
end

end
end


Removed derived file

Removed derived file


display basic shape methods

display basic shape methods

module Yeah
module Web
class Display
  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke
    C[`#@context.strokeStyle`]
  end
  def stroke=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def line_width
    `#@context.lineWidth`
  end
  def line_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(position)
    `#@context.moveTo(#{position.x}, #{position.y})`
  end

  def line_to(position)
    `#@context.lineTo(#{position.x}, #{position.y})`
  end

  def stroke
    `#@context.stroke()`
  end

  def fill
    `#@context.fill()`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(image, position, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end
end
end
end

module Yeah
module Web
class Display
  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke
    C[`#@context.strokeStyle`]
  end
  def stroke=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def line_width
    `#@context.lineWidth`
  end
  def line_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(image, position, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end
end
end
end


display #stroke[=]

display #stroke[=]

module Yeah
module Web
class Display
  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke
    C[`#@context.strokeStyle`]
  end
  def stroke=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(image, position, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end
end
end
end

module Yeah
module Web
class Display
  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(image, position, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end
end
end
end


improved display#draw_image_cropped signature

module Yeah
module Web
class Display
  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def draw_image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def draw_image_cropped(image, position, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end
end
end
end

module Yeah
module Web
class Display
  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def draw_image(image, position)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def draw_image_cropped(image, position, crop_size, crop_position = V[0, 0])
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end
end
end
end


Web::Sound based on Web Audio API (resolves #10)

module Yeah
module Web
class Sound < Asset
  `window.AudioContext = window.AudioContext || window.webkitAudioContext`
  CONTEXT = `new AudioContext()`

  def initialize(path)
    super

    %x{
      var request = new XMLHttpRequest();
      request.open('GET', #{full_path}, true);
      request.responseType = 'arraybuffer';

      request.onload = function() {
        #{CONTEXT}.decodeAudioData(request.response, function(buffer) {
          #@buffer = buffer;
        });
      }

      request.send();
    }
  end

  def play
    %x{
      var source = #{CONTEXT}.createBufferSource();
      source.buffer = #@buffer;
      source.connect(#{CONTEXT}.destination);
      source.start(0);
    }
  end
end
end
end

module Yeah
module Web
class Sound < Asset
  def initialize(path)
    super

    %x{
      #@native = new Audio();
      #@native.src = #{full_path};
    }
  end

  def play
    `#@native.play()`
  end
end
end
end


optimized font style setters

optimized font style setters

module Yeah
module Web
class Display
  attr_reader :font_type, :font_size

  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.font_type = DEFAULT_DISPLAY_FONT_TYPE
    self.font_size = DEFAULT_DISPLAY_FONT_SIZE
    @transform = [1, 0, 0, 1, 0, 0]
    @transforms = []
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke
    C[`#@context.strokeStyle`]
  end
  def stroke=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def line_width
    `#@context.lineWidth`
  end
  def line_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def transform
    @transform + [0, 0, 1] # appendage to fulfill signature
  end

  def translate(distance)
    @transform[4] += `#{@transform[0]} * #{distance.x} + #{@transform[2]} * #{distance.y}`
    @transform[5] += `#{@transform[1]} * #{distance.x} + #{@transform[3]} * #{distance.y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale(multiplier)
    %x{
      #{@transform} = [#{@transform[0]} * #{multiplier.x},
                       #{@transform[1]} * #{multiplier.x},
                       #{@transform[2]} * #{multiplier.y},
                       #{@transform[3]} * #{multiplier.y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def rotate(radians)
    %x{
      var cos = Math.cos(#{radians}),
          sin = Math.sin(#{radians}),
          e0 = #{@transform[0]} * cos + #{@transform[2]} * sin,
          e1 = #{@transform[1]} * cos + #{@transform[3]} * sin,
          e2 = #{@transform[0]} * -sin + #{@transform[2]} * cos,
          e3 = #{@transform[1]} * -sin + #{@transform[3]} * cos;

      #@transform = [e0, e1, e2, e3, #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def push
    @transforms.push(@transform.dup)
  end

  def pop
    @transform = @transforms.pop

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(position)
    `#@context.moveTo(#{position.x}, #{position.y})`
  end

  def line_to(position)
    `#@context.lineTo(#{position.x}, #{position.y})`
  end

  def stroke_shape
    `#@context.stroke()`
  end

  def fill_shape
    `#@context.fill()`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(position, image)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(position, image, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end

  def font_type=(type)
    @font_type = type

    font = "#{@font_size}px #{@font_type}"
    `#@context.font = #{font}`
  end

  def font_size=(size)
    @font_size = size

    font = "#{@font_size}px #{@font_type}"
    `#@context.font = #{font}`
  end

  def text(position, value)
    `#@context.fillText(#{value}, #{position.x}, #{position.y})`
    `#@context.strokeText(#{value}, #{position.x}, #{position.y})`
  end
end
end
end

module Yeah
module Web
class Display
  attr_reader :font_type, :font_size

  def initialize(args = {})
    canvas_selector = args.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = args.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.font_type = DEFAULT_DISPLAY_FONT_TYPE
    self.font_size = DEFAULT_DISPLAY_FONT_SIZE
    @transform = [1, 0, 0, 1, 0, 0]
    @transforms = []
  end

  def size
    V[`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value.x}`
    `#@canvas.height = #{value.y}`
  end

  def fill
    C[`#@context.fillStyle`]
  end
  def fill=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke
    C[`#@context.strokeStyle`]
  end
  def stroke=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def line_width
    `#@context.lineWidth`
  end
  def line_width=(number)
    `#@context.lineWidth = #{number}`
  end

  def color_at(position)
    data = `#@context.getImageData(#{position.x}, #{position.y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def transform
    @transform + [0, 0, 1] # appendage to fulfill signature
  end

  def translate(distance)
    @transform[4] += `#{@transform[0]} * #{distance.x} + #{@transform[2]} * #{distance.y}`
    @transform[5] += `#{@transform[1]} * #{distance.x} + #{@transform[3]} * #{distance.y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale(multiplier)
    %x{
      #{@transform} = [#{@transform[0]} * #{multiplier.x},
                       #{@transform[1]} * #{multiplier.x},
                       #{@transform[2]} * #{multiplier.y},
                       #{@transform[3]} * #{multiplier.y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def rotate(radians)
    %x{
      var cos = Math.cos(#{radians}),
          sin = Math.sin(#{radians}),
          e0 = #{@transform[0]} * cos + #{@transform[2]} * sin,
          e1 = #{@transform[1]} * cos + #{@transform[3]} * sin,
          e2 = #{@transform[0]} * -sin + #{@transform[2]} * cos,
          e3 = #{@transform[1]} * -sin + #{@transform[3]} * cos;

      #@transform = [e0, e1, e2, e3, #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def push
    @transforms.push(@transform.dup)
  end

  def pop
    @transform = @transforms.pop

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def line(pos1, pos2)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{pos1.x}, #{pos1.y});
      #@context.lineTo(#{pos2.x}, #{pos2.y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def rectangle(position, size)
    `#@context.fillRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
    `#@context.strokeRect(#{position.x}, #{position.y}, #{size.x}, #{size.y})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(position)
    `#@context.moveTo(#{position.x}, #{position.y})`
  end

  def line_to(position)
    `#@context.lineTo(#{position.x}, #{position.y})`
  end

  def stroke_shape
    `#@context.stroke()`
  end

  def fill_shape
    `#@context.fill()`
  end

  def clear
    `#@context.fillRect(0, 0, #{size.x}, #{size.y})`
  end

  def image(position, image)
    `#@context.drawImage(#{image.to_n}, #{position.x}, #{position.y})`
  end

  def image_cropped(position, image, crop_position, crop_size)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_position.x}, #{crop_position.y},
                           #{crop_size.x}, #{crop_size.y},
                           #{position.x}, #{position.y},
                           #{crop_size.x}, #{crop_size.y})}
  end

  def font_type=(type)
    @font_type = type
    update_font
  end

  def font_size=(size)
    @font_size = size
    update_font
  end

  def text(position, value)
    `#@context.fillText(#{value}, #{position.x}, #{position.y})`
    `#@context.strokeText(#{value}, #{position.x}, #{position.y})`
  end

  private

  def update_font
    font = "#{font_size}px #{font_type}"
    `#@context.font = #{font}`
  end
end
end
end


fixed Web::Display usage of Color

fixed Web::Display usage of Color

module Yeah
module Web
class Display
  attr_reader :text_font, :text_size

  def initialize(options = {})
    canvas_selector = options.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = options.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.text_font = Font['']
    self.text_size = DEFAULT_DISPLAY_TEXT_SIZE
    @transform = [1, 0, 0, 1, 0, 0]
    @transforms = []
  end

  def width
    `#@canvas.width`
  end
  def width=(value)
    `#@canvas.width =  #{value}`
  end

  def height
    `#@canvas.height`
  end
  def height=(value)
    `#@canvas.height =  #{value}`
  end

  def size
    [`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value[0]}`
    `#@canvas.height = #{value[1]}`
  end

  def fill_color
    Color[`#@context.fillStyle`]
  end
  def fill_color=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke_color
    Color[`#@context.strokeStyle`]
  end
  def stroke_color=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def stroke_width
    `#@context.lineWidth`
  end
  def stroke_width=(numeric)
    `#@context.lineWidth = #{numeric}`
  end

  def text_font=(font)
    @text_font = font

    font = "#{@text_size}px \"#{@text_font.path}\""
    `#@context.font = #{font}`
  end

  def text_size=(size)
    @text_size = size

    font = "#{@text_size}px \"#{@text_font.path}\""
    `#@context.font = #{font}`
  end

  def color_at(x, y)
    data = `#@context.getImageData(#{x}, #{y}, 1, 1).data`
    Color[`data[0]`, `data[1]`, `data[2]`]
  end

  def translate(x, y)
    @transform[4] += `#{@transform[0]} * #{x} + #{@transform[2]} * #{y}`
    @transform[5] += `#{@transform[1]} * #{x} + #{@transform[3]} * #{y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def translate_x(x)
    @transform[4] += `#{@transform[0]} * #{x} + #{@transform[2]}`
    @transform[5] += `#{@transform[1]} * #{x} + #{@transform[3]}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def translate_y(y)
    @transform[4] += `#{@transform[0]} + #{@transform[2]} * #{y}`
    @transform[5] += `#{@transform[1]} + #{@transform[3]} * #{y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale(x, y)
    %x{
      #{@transform} = [#{@transform[0]} * #{x},
                       #{@transform[1]} * #{x},
                       #{@transform[2]} * #{y},
                       #{@transform[3]} * #{y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale_x(x)
    %x{
      #{@transform} = [#{@transform[0]} * #{x},
                       #{@transform[1]} * #{x},
                       #{@transform[2]}, #{@transform[3]},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale_y(y)
    %x{
      #{@transform} = [#{@transform[0]}, #{@transform[1]},
                       #{@transform[2]} * #{y},
                       #{@transform[3]} * #{y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def rotate(radians)
    %x{
      var cos = Math.cos(#{radians}),
          sin = Math.sin(#{radians}),
          e0 = #{@transform[0]} * cos + #{@transform[2]} * sin,
          e1 = #{@transform[1]} * cos + #{@transform[3]} * sin,
          e2 = #{@transform[0]} * -sin + #{@transform[2]} * cos,
          e3 = #{@transform[1]} * -sin + #{@transform[3]} * cos;

      #@transform = [e0, e1, e2, e3, #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def push
    @transforms.push(@transform.dup)
  end

  def pop
    @transform = @transforms.pop

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def stroke_line(start_x, start_y, end_x, end_y)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{start_x}, #{start_y});
      #@context.lineTo(#{end_x}, #{end_y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_curve(start_x, start_y, end_x, end_y, control_x, control_y)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{start_x}, #{start_y});
      #@context.quadraticCurveTo(#{control_x}, #{control_y},
                                 #{end_x}, #{end_y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_curve(start_x, start_y, end_x, end_y, control1_x, control1_y, control2_x, control2_y)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{start_x}, #{start_y});
      #@context.bezierCurveTo(#{control1_x}, #{control1_y},
                              #{control2_x}, #{control2_y},
                              #{end_x}, #{end_y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_rectangle(x, y, width, height)
    `#@context.strokeRect(#{x}, #{y}, #{width}, #{height})`
  end

  def fill_rectangle(x, y, width, height)
    `#@context.fillRect(#{x}, #{y}, #{width}, #{height})`
  end

  def stroke_ellipse(center_x, center_y, radius_x, radius_y)
    %x{
      #@context.beginPath();
      #@context.save();
      #@context.beginPath();
      #@context.translate(#{center_x} - #{radius_x},
                          #{center_y} - #{radius_y});
      #@context.scale(#{radius_x}, #{radius_y});
      #@context.arc(1, 1, 1, 0, 2 * Math.PI, false);
      #@context.restore();
      #@context.stroke();
    }
  end

  def fill_ellipse(center_x, center_y, radius_x, radius_y)
    %x{
      #@context.beginPath();
      #@context.save();
      #@context.beginPath();
      #@context.translate(#{center_x} - #{radius_x},
                          #{center_y} - #{radius_y});
      #@context.scale(#{radius_x}, #{radius_y});
      #@context.arc(1, 1, 1, 0, 2 * Math.PI, false);
      #@context.restore();
      #@context.fill();
    }
  end

  def clear
    `#@context.fillRect(0, 0, #{width}, #{height})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(x, y)
    `#@context.moveTo(#{x}, #{y})`
  end

  def line_to(x, y)
    `#@context.lineTo(#{x}, #{y})`
  end

  def curve_to(x, y, control_x, control_y)
    `#@context.quadraticCurveTo(#{control_x}, #{control_y},
                                #{x}, #{y})`
  end

  def curve2_to(x, y, control1_x, control1_y, control2_x, control2_y)
    `#@context.bezierCurveTo(#{control1_x}, #{control1_y},
                             #{control2_x}, #{control2_y},
                             #{x}, #{y})`
  end

  def stroke_shape
    `#@context.stroke()`
  end

  def fill_shape
    `#@context.fill()`
  end

  def draw_image(image, x, y)
    `#@context.drawImage(#{image.to_n}, #{x}, #{y})`
  end

  def draw_image_cropped(image, x, y, crop_x, crop_y, crop_width, crop_height)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_x}, #{crop_y},
                           #{crop_width}, #{crop_height},
                           #{x}, #{y},
                           #{crop_x}, #{crop_y})}
  end

  def fill_text(text, x, y)
    `#@context.fillText(#{text}, #{x}, #{y})`
  end

  def stroke_text(text, x, y)
    `#@context.strokeText(#{text}, #{x}, #{y})`
  end
end
end
end

module Yeah
module Web
class Display
  attr_reader :text_font, :text_size

  def initialize(options = {})
    canvas_selector = options.fetch(:canvas_selector, DEFAULT_CANVAS_SELECTOR)

    @canvas = `document.querySelectorAll(#{canvas_selector})[0]`
    @context = `#@canvas.getContext('2d')`
    self.size = options.fetch(:size, DEFAULT_DISPLAY_SIZE)
    self.text_font = Font['']
    self.text_size = DEFAULT_DISPLAY_TEXT_SIZE
    @transform = [1, 0, 0, 1, 0, 0]
    @transforms = []
  end

  def width
    `#@canvas.width`
  end
  def width=(value)
    `#@canvas.width =  #{value}`
  end

  def height
    `#@canvas.height`
  end
  def height=(value)
    `#@canvas.height =  #{value}`
  end

  def size
    [`#@canvas.width`, `#@canvas.height`]
  end
  def size=(value)
    `#@canvas.width =  #{value[0]}`
    `#@canvas.height = #{value[1]}`
  end

  def fill_color
    C[`#@context.fillStyle`]
  end
  def fill_color=(color)
    `#@context.fillStyle = #{color.to_hex}`
  end

  def stroke_color
    C[`#@context.strokeStyle`]
  end
  def stroke_color=(color)
    `#@context.strokeStyle = #{color.to_hex}`
  end

  def stroke_width
    `#@context.lineWidth`
  end
  def stroke_width=(numeric)
    `#@context.lineWidth = #{numeric}`
  end

  def text_font=(font)
    @text_font = font

    font = "#{@text_size}px \"#{@text_font.path}\""
    `#@context.font = #{font}`
  end

  def text_size=(size)
    @text_size = size

    font = "#{@text_size}px \"#{@text_font.path}\""
    `#@context.font = #{font}`
  end

  def color_at(x, y)
    data = `#@context.getImageData(#{x}, #{y}, 1, 1).data`
    C[`data[0]`, `data[1]`, `data[2]`]
  end

  def translate(x, y)
    @transform[4] += `#{@transform[0]} * #{x} + #{@transform[2]} * #{y}`
    @transform[5] += `#{@transform[1]} * #{x} + #{@transform[3]} * #{y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def translate_x(x)
    @transform[4] += `#{@transform[0]} * #{x} + #{@transform[2]}`
    @transform[5] += `#{@transform[1]} * #{x} + #{@transform[3]}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def translate_y(y)
    @transform[4] += `#{@transform[0]} + #{@transform[2]} * #{y}`
    @transform[5] += `#{@transform[1]} + #{@transform[3]} * #{y}`

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale(x, y)
    %x{
      #{@transform} = [#{@transform[0]} * #{x},
                       #{@transform[1]} * #{x},
                       #{@transform[2]} * #{y},
                       #{@transform[3]} * #{y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale_x(x)
    %x{
      #{@transform} = [#{@transform[0]} * #{x},
                       #{@transform[1]} * #{x},
                       #{@transform[2]}, #{@transform[3]},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def scale_y(y)
    %x{
      #{@transform} = [#{@transform[0]}, #{@transform[1]},
                       #{@transform[2]} * #{y},
                       #{@transform[3]} * #{y},
                       #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def rotate(radians)
    %x{
      var cos = Math.cos(#{radians}),
          sin = Math.sin(#{radians}),
          e0 = #{@transform[0]} * cos + #{@transform[2]} * sin,
          e1 = #{@transform[1]} * cos + #{@transform[3]} * sin,
          e2 = #{@transform[0]} * -sin + #{@transform[2]} * cos,
          e3 = #{@transform[1]} * -sin + #{@transform[3]} * cos;

      #@transform = [e0, e1, e2, e3, #{@transform[4]}, #{@transform[5]}];

      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def push
    @transforms.push(@transform.dup)
  end

  def pop
    @transform = @transforms.pop

    %x{
      #@context.setTransform(#{@transform[0]}, #{@transform[1]},
                             #{@transform[2]}, #{@transform[3]},
                             #{@transform[4]}, #{@transform[5]}); }
  end

  def stroke_line(start_x, start_y, end_x, end_y)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{start_x}, #{start_y});
      #@context.lineTo(#{end_x}, #{end_y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_curve(start_x, start_y, end_x, end_y, control_x, control_y)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{start_x}, #{start_y});
      #@context.quadraticCurveTo(#{control_x}, #{control_y},
                                 #{end_x}, #{end_y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_curve(start_x, start_y, end_x, end_y, control1_x, control1_y, control2_x, control2_y)
    %x{
      #@context.beginPath();
      #@context.moveTo(#{start_x}, #{start_y});
      #@context.bezierCurveTo(#{control1_x}, #{control1_y},
                              #{control2_x}, #{control2_y},
                              #{end_x}, #{end_y});
      #@context.closePath();
      #@context.stroke();
    }
  end

  def stroke_rectangle(x, y, width, height)
    `#@context.strokeRect(#{x}, #{y}, #{width}, #{height})`
  end

  def fill_rectangle(x, y, width, height)
    `#@context.fillRect(#{x}, #{y}, #{width}, #{height})`
  end

  def stroke_ellipse(center_x, center_y, radius_x, radius_y)
    %x{
      #@context.beginPath();
      #@context.save();
      #@context.beginPath();
      #@context.translate(#{center_x} - #{radius_x},
                          #{center_y} - #{radius_y});
      #@context.scale(#{radius_x}, #{radius_y});
      #@context.arc(1, 1, 1, 0, 2 * Math.PI, false);
      #@context.restore();
      #@context.stroke();
    }
  end

  def fill_ellipse(center_x, center_y, radius_x, radius_y)
    %x{
      #@context.beginPath();
      #@context.save();
      #@context.beginPath();
      #@context.translate(#{center_x} - #{radius_x},
                          #{center_y} - #{radius_y});
      #@context.scale(#{radius_x}, #{radius_y});
      #@context.arc(1, 1, 1, 0, 2 * Math.PI, false);
      #@context.restore();
      #@context.fill();
    }
  end

  def clear
    `#@context.fillRect(0, 0, #{width}, #{height})`
  end

  def begin_shape
    `#@context.beginPath()`
  end

  def end_shape
    `#@context.closePath()`
  end

  def move_to(x, y)
    `#@context.moveTo(#{x}, #{y})`
  end

  def line_to(x, y)
    `#@context.lineTo(#{x}, #{y})`
  end

  def curve_to(x, y, control_x, control_y)
    `#@context.quadraticCurveTo(#{control_x}, #{control_y},
                                #{x}, #{y})`
  end

  def curve2_to(x, y, control1_x, control1_y, control2_x, control2_y)
    `#@context.bezierCurveTo(#{control1_x}, #{control1_y},
                             #{control2_x}, #{control2_y},
                             #{x}, #{y})`
  end

  def stroke_shape
    `#@context.stroke()`
  end

  def fill_shape
    `#@context.fill()`
  end

  def draw_image(image, x, y)
    `#@context.drawImage(#{image.to_n}, #{x}, #{y})`
  end

  def draw_image_cropped(image, x, y, crop_x, crop_y, crop_width, crop_height)
    %x{#@context.drawImage(#{image.to_n},
                           #{crop_x}, #{crop_y},
                           #{crop_width}, #{crop_height},
                           #{x}, #{y},
                           #{crop_x}, #{crop_y})}
  end

  def fill_text(text, x, y)
    `#@context.fillText(#{text}, #{x}, #{y})`
  end

  def stroke_text(text, x, y)
    `#@context.strokeText(#{text}, #{x}, #{y})`
  end
end
end
end


web assets path is relative

web assets path is relative

module Yeah
module Web
class Asset
  ASSETS_PATH = "./assets"

  def initialize(path)
    setup_native

    self.path = path
  end

  def path
    `#@native.src`.sub(/.*:\/\/.*#{ASSETS_PATH}\//i, '')
  end
  def path=(val)
    # TODO: use Pathname#join if it is implemented in Opal
    full_path = "#{ASSETS_PATH}/#{val}"
    `#@native.src = #{full_path};`
  end

  def to_n
    @native
  end

  private

  def setup_native
    raise NotImplementedError
  end
end
end
end

module Yeah
module Web
class Asset
  ASSETS_PATH = "/assets"

  def initialize(path)
    setup_native

    self.path = path
  end

  def path
    `#@native.src`.sub(/.*:\/\/.*#{ASSETS_PATH}\//i, '')
  end
  def path=(val)
    # TODO: use Pathname#join if it is implemented in Opal
    full_path = "#{ASSETS_PATH}/#{val}"
    `#@native.src = #{full_path};`
  end

  def to_n
    @native
  end

  private

  def setup_native
    raise NotImplementedError
  end
end
end
end


fixed Web::Ticker#ticks_per_second

fixed Web::Ticker#ticks_per_second

module Yeah
module Web
class Ticker
  attr_accessor :rate
  attr_reader :tick_count, :ticks_per_second

  def initialize(args = {})
    @rate = args.fetch(:rate, DEFAULT_TICKER_RATE)
    @tick_count = 0
    @ticks_per_second = 0
    @next_tps = 0
  end

  def on_tick(&block)
    # TODO: add to ticks before yielding
    %x{
      var lastTime = (new Date()).getTime(),
          lastMeasureTime = lastTime,
          elapsed = 0,
          interval,
          currentTime;

      var loop = function() {
        interval = 1.0 / #{@rate};
        currentTime = new Date().getTime();
        elapsed = (currentTime - lastTime) / 1000.0;

        if (elapsed > interval) {
          #{yield `elapsed`}

          #@tick_count += 1;
          #@next_tps += 1;

          lastTime = currentTime - (elapsed % interval);
        }

        if (currentTime - lastMeasureTime > 1000) {
          #@ticks_per_second = #@next_tps;
          #@next_tps = 0;

          lastMeasureTime = currentTime - (currentTime - lastMeasureTime) % 1000;
        }

        window.requestAnimationFrame(loop);
      };

      window.requestAnimationFrame(loop);
    }
  end
end
end
end

module Yeah
module Web
class Ticker
  attr_accessor :rate
  attr_reader :tick_count, :ticks_per_second

  def initialize(args = {})
    @rate = args.fetch(:rate, DEFAULT_TICKER_RATE)
    @tick_count = 0
    @ticks_per_second = 0
    @next_tps = 0
  end

  def on_tick(&block)
    # TODO: add to ticks before yielding
    %x{
      var lastTime = (new Date()).getTime(),
          lastMeasureTime = lastTime,
          elapsed = 0,
          interval,
          currentTime;

      var loop = function() {
        interval = 1.0 / #{@rate};
        currentTime = new Date().getTime();
        elapsed = (currentTime - lastTime) / 1000.0;

        if (elapsed > interval) {
          #{yield `elapsed`}

          #@tick_count += 1;
          #@next_tps += 1;

          lastTime = currentTime - (elapsed % interval);
        }

        if (currentTime - lastMeasureTime > 1000) {
          #@ticks_per_second = #@next_tps;
          #@next_actual_rate = 0;

          lastMeasureTime = currentTime - (currentTime - lastMeasureTime) % 1000;
        }

        window.requestAnimationFrame(loop);
      };

      window.requestAnimationFrame(loop);
    }
  end
end
end
end


Ticker#tick_count

module Yeah
module Web
class Ticker
  attr_reader :rate, :tick_count

  def initialize(args = {})
    @rate = args.fetch(:rate, DEFAULT_TICKER_RATE)
    @tick_count = 0
  end

  def on_tick(&block)
    # TODO: add to ticks before yielding
    %x{
      var lastTime = (new Date()).getTime(),
          elapsed = 0,
          interval = 1/#{rate},
          currentTime;

      var loop = function() {
        currentTime = new Date().getTime();
        elapsed = (currentTime - lastTime) / 1000;

        if (elapsed > interval) {
          #{yield `elapsed`}

          #{@tick_count += 1}

          lastTime = currentTime - (elapsed % interval);
        }

        window.requestAnimationFrame(loop);
      };

      window.requestAnimationFrame(loop);
    }
  end
end
end
end

module Yeah
module Web
class Ticker
  attr_reader :rate

  def initialize(args = {})
    @rate = args.fetch(:rate, DEFAULT_TICKER_RATE)
  end

  def on_tick(&block)
    %x{
      var lastTime = (new Date()).getTime(),
          elapsed = 0,
          interval = 1/#{rate},
          currentTime;

      var loop = function() {
        currentTime = new Date().getTime();
        elapsed = (currentTime - lastTime) / 1000;

        if (elapsed > interval) {
          #{yield `elapsed`}

          lastTime = currentTime - (elapsed % interval);
        }

        window.requestAnimationFrame(loop);
      };

      window.requestAnimationFrame(loop);
    }
  end
end
end
end