<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Circle Class</title>
<style type='text/css'>
* {
  margin: 0;
  padding: 0;
  position: absolute;
}
</style>
<script type='text/javascript'>
/*
  This program is free software. It comes without any warranty, to
  the extent permitted by applicable law. You can redistribute it
  and/or modify it under the terms of the Do What The Fuck You Want
  To Public License, Version 2, as published by Sam Hocevar. See
  http://sam.zoy.org/wtfpl/COPYING for more details.
*/

/*
  This JS class attempts to make an interactive site with the canvas element.
  In time this will not only be a cirlce class, but more like an object class.
  The fun stuff happens at the end of this file.
*/

function Circle(posX, posY, radius, fillColor, borderWidth, borderColor, visible) {
  // Default to defaults if nothing is specified
  if (posX == undefined) var posX = 0
  if (posY == undefined) var posY = 0
  if (radius == undefined) var radius = 50
  if (fillColor == undefined) var fillColor = '#ddd'
  if (borderWidth == undefined) var borderWidth = 2
  if (borderColor == undefined) var borderColor = '#444'
  if (visible == undefined) var visible = true
  this.visible = visible
  
  // Get number in order of creation
  this.num = objects.length
  
  // Create canvas element
  this.element = document.createElement('canvas')
  if (!this.visible) this.hide()
  this.element.style.zIndex = this.num + 2
  wrapper.appendChild(this.element)
  this.context = this.element.getContext('2d')
  
  // Append to global objects
  objects.splice(objects.length, objects.length, this)
  
  this.state = 'normal'
  this.globalstate = 'normal'
  this.savedstate = 'hover'

  this.x = posX
  this.y = posY
  this.radius = radius
  this.fillColor = fillColor
  this.borderWidth = borderWidth
  this.borderColor = borderColor
  
  var i
  
  for (i in style_states) {
    this[style_states[i]] = {}
    this[style_states[i]].x = false
    this[style_states[i]].y = false
    this[style_states[i]].radius = false
    this[style_states[i]].fillColor = false
    this[style_states[i]].borderWidth = false
    this[style_states[i]].borderColor = false
  }
  
  for (i in event_states) {
    this[event_states[i]] = '' //'dbg.innerHTML+=" '+event_states[i]+'"'
  }
  
  // Keep track of mousedown events
  this.mousedown_done = false
    
  // Keep track of hovering
  this.hovering = false
    
  this.create()
}

Circle.prototype.create = function() {
  var posX, posY, radius, fillColor, borderWidth, borderColor, i
  
  posX = this.get('x')
  posY = this.get('y')
  radius = this.get('radius')
  fillColor = this.get('fillColor')
  borderWidth = this.get('borderWidth')
  borderColor = this.get('borderColor')
  
  var mn = radius * 2 + borderWidth
  var mnh = mn / 2
  
  this.element.width = mn
  this.element.height = mn
  this.element.style.width = mn + 'px'
  this.element.style.height = mn + 'px'
  this.element.style.left = posX - mnh + 'px'
  this.element.style.top = posY - mnh + 'px'
  
  // Create the circle
  this.context.beginPath()
  this.context.arc(mnh, mnh, radius, 0, Math.PI*2, true)
  
  if (typeof(fillColor) == 'string')
    this.context.fillStyle = fillColor
  else {
    if (fillColor[0] == 'linear') {
      var x1, y1, x2, y2
      if (fillColor.length > 2) {
        var angle = (fillColor[2] + 45) % 360 // Center it
        
        if (angle >= 0 && angle < 90) {
          x1 = (angle / 90) * mn
          y1 = 0
          x2 = ((90 - angle) / 90) * mn
          y2 = mn
        }
        else if (angle >= 90 && angle < 180) {
          x1 = mn
          y1 = ((angle - 90) / 90) * mn
          x2 = 0
          y2 = ((90 - (angle - 90)) / 90) * mn
        }
        else if (angle >= 180 && angle < 270) {
          x1 = ((90 - (angle - 180)) / 90) * mn
          y1 = mn
          x2 = ((angle - 180) / 90) * mn
          y2 = 0
        }
        else if (angle >= 270 && angle < 360) {
          x1 = 0
          y1 = ((90 - (angle - 270)) / 90) * mn
          x2 = mn
          y2 = ((angle - 270) / 90) * mn
        }
      }
      else {
        x1 = mnh
        y1 = 0
        x2 = mnh
        y2 = mn
      }
      var grad = this.context.createLinearGradient(x1, y1, x2, y2)
    }
    else {
      var lenpos, reallen, angle, x1, y1
      if (fillColor.length > 2) {
        lenpos = (fillColor[2])
        if (fillColor.length > 3) {
          angle = (fillColor[3] + 45)
          while (angle < 0) {
            angle += 360
          }
          angle = angle % 360
        }
        else angle = 0
      }
      else {
        lenpos = 0
        angle = 0
      }
      
      reallen = lenpos * radius
      
      if (angle >= 0 && angle < 90) {
          x1 = mnh + Math.sin((angle - 45) * Math.PI / 180) * reallen
          y1 = mnh - Math.cos((angle - 45) * Math.PI / 180) * reallen
        }
        else if (angle >= 90 && angle < 180) {
          x1 = mnh + Math.cos((angle - 135) * Math.PI / 180) * reallen
          y1 = mnh + Math.sin((angle - 135) * Math.PI / 180) * reallen
        }
        else if (angle >= 180 && angle < 270) {
          x1 = mnh - Math.sin((angle - 225) * Math.PI / 180) * reallen
          y1 = mnh + Math.cos((angle - 225) * Math.PI / 180) * reallen
        }
        else if (angle >= 270 && angle < 360) {
          x1 = mnh - Math.cos((angle - 315) * Math.PI / 180) * reallen
          y1 = mnh - Math.sin((angle - 315) * Math.PI / 180) * reallen
        }
      
      var grad = this.context.createRadialGradient(x1, y1, 0, mnh, mnh, radius)
    }
    
    for (i in fillColor[1]) {
      grad.addColorStop(i / (fillColor[1].length - 1), fillColor[1][i])
    }

    this.context.fillStyle = grad
  }
  this.context.lineWidth = borderWidth
  this.context.strokeStyle = borderColor
  
  // Clear the screen
  this.context.clearRect(0, 0, mn, mn)
  this.context.stroke()
  this.context.fill()
}

Circle.prototype.standby = function() {
  if (!this.visible) return false
  
  var posX, posY, radius, borderWidth
  
  posX = this.get('x')
  posY = this.get('y')
  radius = this.get('radius')
  borderWidth = this.get('borderWidth')
  
  if (!object_hover && Math.sqrt(Math.pow((x - posX), 2) + Math.pow((y - posY), 2)) < radius + borderWidth / 2) {
    // If the cursor is inside the circle
    
    // No more objects may react
    object_hover = true
    
    if (!this.hovering)
      this.eval(this.onmouseover)
    else if (x != ox || y != oy)
      this.eval(this.onmousemove)
    
    // We're hovering!
    this.hovering = true
    
    if (this.globalstate == 'onmousedown') {
      this.state = 'active'
      this.savedstate = 'active'
      if (!this.onmousedown_done) {
        this.onmousedown_done = true
        this.eval(this.onmousedown)
      }
    }
    else if (this.globalstate == 'onmouseup') {
      this.state = 'hover'
      this.savedstate = 'hover'
      this.eval(this.onmouseup)
      if (this.onmousedown_done)
        this.eval(this.onclick)
      
      this.onmousedown_done = false
    }
    else {
      // Last resort
      this.state = this.savedstate
    }
  }
  else {
    // If the cursor is outside the circle
    
    if (this.hovering) {
      // No more hovering..
      this.hovering = false
      this.eval(this.onmouseout)
    }
    // Reset everything
    this.state = 'normal'
    
    if (this.globalstate == 'onmouseup') {
      this.savedstate = 'hover'
      this.onmousedown_done = false
    }
  }
  
  this.globalstate = 'normal'
  
  // Create it and draw it to the screen
  this.create()
  
  return true
}

Circle.prototype.eval = function(code) {
  var codetype = typeof(code)
  if (codetype == 'function')
    code = (code + '').split('function ')[1].split('{')[0]
  try {
    eval(code)
    return true
  }
  catch(e) {
    return false
  }
}

Circle.prototype.getobj = function() {
  if (this.state == 'normal')
    return this
  else
    return this[this.state]
}

Circle.prototype.get = function(circlevar) {
  var cobj = this.getobj()
  if (cobj[circlevar] === false) {
  if (this.state == 'active' && this.hover[circlevar] !== false)
    return this.hover[circlevar]
  else return this[circlevar]
  }
  else return cobj[circlevar]
}

Circle.prototype.set = function(circle_var, value) {
  this[circle_var] = value
  for (var i in event_states) {
    this[event_states[i]][circle_var] = value
  }
}

Circle.prototype.hide = function() {
  this.element.style.display = 'none'
  this.visible = false
}

Circle.prototype.show = function() {
  this.element.style.display = 'block'
  this.visible = true
}


Circle.prototype.raiseIndex = function(amount) {
  if (amount == undefined) var amount = 1
  return this.changeIndex(amount)
}

Circle.prototype.lowerIndex = function(amount) {
  if (amount == undefined) var amount = 1
  return this.changeIndex(-amount)
}

Circle.prototype.toTop = function() {
  return this.changeIndex(objects.length - this.num - 1)
}

Circle.prototype.toBottom = function() {
  return this.changeIndex(-this.num)
}

Circle.prototype.changeIndex = function(crement) {
  if (objects.length > this.num + crement && 0 <= this.num + crement) {
    objects.splice(this.num, 1)
    objects.splice(this.num + crement, 0, this)
    this.update_indexes()
    return true
  }
  else
    return false
}

Circle.prototype.remove = function() {
  objects.splice(this.num, 1)
  this.update_indexes()
  wrapper.removeChild(this.element)
  delete this
}

Circle.prototype.update_indexes = function() {
  for (var i in objects) {
    objects[i].num = i * 1
    objects[i].element.style.zIndex = i * 1 + 2
  }
}

function standby() {
  for (var i = objects.length - 1; i > -1; i--) {
    objects[i].standby()
  }
  object_hover = false
  ox = x
  oy = y
  timer = setTimeout('standby()')
}

window.onload = function() {
  // Globals
  x = 0
  y = 0
  ox = 0
  oy = 0
  objects = new Array()
  object_hover = false
  getwh()
  event_states = ['onmouseover', 'onmouseout', 'onmousemove', 'onmousedown', 'onmouseup', 'onclick']
  style_states = ['hover', 'active']
  globalstate = 'normal'
  
  // Creating the wrapper
  wrapper = document.createElement('div')
  wrapper.style.position = 'absolute'
  document.body.appendChild(wrapper)
  /*dbg = document.createElement('div')
  dbg.style.position = 'absolute'
  document.body.appendChild(dbg)*/
  
  // Adjust on resize
  window.onresize = function() {
    getwh()
  }
  
  window.onmousedown = function() {
    for (var i in objects) {
      objects[i].globalstate = 'onmousedown'
    }
  }
  
  window.onmouseup = function() {
    for (var i in objects) {
      objects[i].globalstate = 'onmouseup'
    }
  }
  
  standby()
  
  loadobjects()
}

window.onmousemove = function(e) {
  if (!e) var e = window.event
  if (e.pageX || e.pageY) {
    x = e.pageX
    y = e.pageY
  }
  else if (e.clientX || e.clientY) {
    x = e.clientX + document.body.scrollLeft
    y = e.clientY + document.body.scrollTop
  }
}

function getwh() {
  if (self.innerHeight) {
    h = self.innerHeight
    w = self.innerWidth
  }
  else if (document.documentElement && document.documentElement.clientHeight) {
    h = document.documentElement.clientHeight
    w = document.documentElement.clientHeight
  }
  else if (document.body) {
    h = document.body.clientHeight
    w = document.body.clientWidth
  }
}




function loadobjects() {
  circ = new Circle(250, 250, 50, ['radial', ['#00ff00', '#dd33ee', '#888', '#00ffff']], 0, '#000')
  circ.hover.fillColor = ['radial', ['#0000ff', '#dd33ee', '#333', '#00ffff']]
  circ.active.fillColor = ['linear', ['#fff', '#000'], -45]
  
  circ2 = new Circle(270, 280, 30, '#eee', 5, '#333')
  circ2.hover.fillColor = '#ff00ff'
  circ2.active.fillColor = '#ff0000'
  
  circ3 = new Circle(260, 220, 25, ['radial', ['#fff', '#000']], 7, '#333')
  circ3.hover.fillColor = ['radial', ['#000', '#fff']]
  
  circ4 = new Circle(340, 290, 60, ['radial', ['rgb(221, 87, 42)', 'rgb(155, 60, 28)'], .6, -10], 0, '#333')
  circ4.hover.fillColor = ['radial', ['#fff', '#000'], .4, 40]
  
  // Below is a list of functions that can be used on a circle.
  /*
  changeIndex
  raiseIndex
  lowerIndex
  toTop
  toBottom
  hide
  show
  remove
  */
}
</script>
</head>
<body></body>
</html>