
function Scroller(scrollerFrame, scrollerContent) {

  function DummyScrollButton() {
    this.press = function () { };
    this.release = function () { };
    this.enable = function () { };
    this.disable = function () { };
  }

  var MIN_SPEED = 10,  // px/sec
      MAX_SPEED = 400, // px/sec
      FRAME_RATE = 50, // Hz (average)
      ACCELERATION = 10, // px/sec^2 -- acceleration per second
      FRAME_DELAY = Math.round(1000/FRAME_RATE),
      MIN_INCREMENT = FRAME_DELAY/1000*MIN_SPEED,
      MAX_INCREMENT = FRAME_DELAY/1000*MAX_SPEED,
      INCREMENT_ACCELERATION = FRAME_DELAY/1000*ACCELERATION;

  var context = this,
      xPos = 0,
      yPos = 0,
      hScrollIncrement,
      vScrollIncrement,
      hScrollTimerId,
      vScrollTimerId,
      bttnLeft = new DummyScrollButton(),
      bttnRight = new DummyScrollButton(),
      bttnUp = new DummyScrollButton(),
      bttnDown = new DummyScrollButton();

  function updateHScrollButtons() {

    var c1 = xPos == 0,
        c2 = xPos == scrollerFrame.offsetWidth - scrollerContent.offsetWidth,
        c3 = scrollerFrame.offsetWidth >= scrollerContent.offsetWidth;

    if (c1 || c2 || c3) {
      context.stopHScrolling();
    }

    if (c1) {
      bttnLeft.disable();
    }
    else {
      bttnLeft.enable();
    }    
    if (c2 || c3) {
      bttnRight.disable();
    }
    else {
      bttnRight.enable();
    }

  }

  function updateVScrollButtons() {

    var c1 = yPos == 0,
        c2 = yPos == scrollerFrame.offsetHeight - scrollerContent.offsetHeight,
        c3 = scrollerFrame.offsetHeight >= scrollerContent.offsetHeight;

    if (c1 || c2 || c3) {
      context.stopVScrolling();
    }
    
    if (c1) {
      bttnUp.disable();
    }
    else {
      bttnUp.enable();
    }
    if (c2 || c3) {
      bttnDown.disable();
    }
    else {
      bttnDown.enable();
    }

  };

  // Dummy methods

  this.scrollUp = function () { };
  this.scrollDown = function () { };
  this.scrollLeft = function () { };
  this.scrollRight = function () { };
  this.stopHScrolling = function () { };
  this.stopVScrolling = function () { };
  this.scrollToX = function () { };
  this.scrollToY = function () { };
  this.scrollToXY = function () { };

  this.setHScrollButtons = function (bLeft, bRight) {
    bttnLeft = bLeft;
    bttnRight = bRight;
    updateHScrollButtons();
  }

  this.setVScrollButtons = function (bUp, bDown) {
    bttnUp = bUp;
    bttnDown = bDown;
    updateVScrollButtons();
  }

  if (scrollerFrame && scrollerContent) {

    this.scrollToX = function (x) {

      x = x > 0 ? 0 : x;
      x = (x < scrollerFrame.offsetWidth - scrollerContent.offsetWidth) && (scrollerFrame.offsetWidth < scrollerContent.offsetWidth) ? scrollerFrame.offsetWidth - scrollerContent.offsetWidth : x;

      xPos = x;
      
      scrollerContent.style.left = Math.round(x) + "px";

      updateHScrollButtons();

    }

    this.scrollToY = function (y) {

      y = y > 0 ? 0 : y;
      y = (y < scrollerFrame.offsetHeight - scrollerContent.offsetHeight) && (scrollerFrame.offsetHeight < scrollerContent.offsetHeight) ? scrollerFrame.offsetHeight - scrollerContent.offsetHeight : y;

      yPos = y;

      scrollerContent.style.top = Math.round(y) + "px";

      updateVScrollButtons();

    }

    this.scrollToXY = function (x, y) {

      context.scrollToX(x);
      context.scrollToY(y);

    }

    this.scrollUp = function () {
      context.stopVScrolling();

      vScrollTimerId = setInterval(function () {
        yPos += vScrollIncrement;
        context.scrollToY(yPos);
        vScrollIncrement = (vScrollIncrement + INCREMENT_ACCELERATION > MAX_INCREMENT) ? MAX_INCREMENT : vScrollIncrement + INCREMENT_ACCELERATION;
      }, FRAME_DELAY);
    }

    this.scrollDown = function () {
      context.stopVScrolling();

      vScrollTimerId = setInterval(function () {
        yPos -= vScrollIncrement;
        context.scrollToY(yPos);
        vScrollIncrement = (vScrollIncrement + INCREMENT_ACCELERATION > MAX_INCREMENT) ? MAX_INCREMENT : vScrollIncrement + INCREMENT_ACCELERATION;
      }, FRAME_DELAY);
    }

    this.scrollLeft = function () {
      context.stopHScrolling();

      hScrollTimerId = setInterval(function () {
        xPos += hScrollIncrement;
        context.scrollToX(xPos);
        hScrollIncrement = (hScrollIncrement + INCREMENT_ACCELERATION > MAX_INCREMENT) ? MAX_INCREMENT : hScrollIncrement + INCREMENT_ACCELERATION;
      }, FRAME_DELAY);
    }

    this.scrollRight = function () {
      context.stopHScrolling();

      hScrollTimerId = setInterval(function () {
        xPos -= hScrollIncrement;
        context.scrollToX(xPos);
        hScrollIncrement = (hScrollIncrement + INCREMENT_ACCELERATION > MAX_INCREMENT) ? MAX_INCREMENT : hScrollIncrement + INCREMENT_ACCELERATION;
      }, FRAME_DELAY);
    }

    this.stopHScrolling = function () {
      clearInterval(hScrollTimerId);
      hScrollIncrement = MIN_INCREMENT;
    }

    this.stopVScrolling = function () {
      clearInterval(vScrollTimerId);
      vScrollIncrement = MIN_INCREMENT;
    }

    scrollerFrame.style.overflow = "hidden";
    scrollerContent.style.position = "relative";
    
  }

}

/* ---------------------------------------------- */

function ScrollButton(htmlElement) {

  var enabled = false,
      pressAction = function () { },
      releaseAction = function () { };

  this.press = function () {
    if (enabled) {

      htmlElement.style.backgroundPosition = "top";

      pressAction();
    }
  }
  this.release = function () {
    if (enabled) {

      htmlElement.style.backgroundPosition = "center";

      releaseAction();
    }
  }
  this.enable = function () {
    if (!enabled) {

      htmlElement.style.backgroundPosition = "center";

      enabled = true;
    }
  }
  this.disable = function () {
    if (enabled) {

      htmlElement.style.backgroundPosition = "bottom";

      enabled = false;
    }
  }
  this.isEnabled = function () {
    return enabled;
  }
  this.setPressAction = function (fp) {
    pressAction = fp;
  }
  this.getPressAction = function () {
    return pressAction;
  }
  this.setReleaseAction = function (fp) {
    releaseAction = fp;
  }
  this.getReleaseAction = function () {
    return releaseAction;
  }

  htmlElement.onmousedown = this.press;
  htmlElement.onmouseup = this.release;

}

/* ---------------------------------------------- */

if (document.getElementById) {

  var mainFrameElement = document.getElementById("mainFrame"),
      mainContentElement = document.getElementById("mainContent"),
      mainScrollUpElement = document.getElementById("mainScrollUp"),
      mainScrollDownElement = document.getElementById("mainScrollDown");

  var mainScroller = new Scroller(mainFrameElement, mainContentElement),
      mainScrollUpButton = new ScrollButton(mainScrollUpElement),
      mainScrollDownButton = new ScrollButton(mainScrollDownElement);

  // Preparing scroller

  mainFrameElement.style.height = "300px";

  with (mainScrollUpElement) {
    style.visibility = "visible";
    alt = title = "Scroll Up";
  }
  with (mainScrollDownElement) {
    style.visibility = "visible";
    alt = title = "Scroll Down";
  }

  with (mainScrollUpButton) {
    setPressAction(mainScroller.scrollUp);
    setReleaseAction(mainScroller.stopVScrolling);
  }
  with (mainScrollDownButton) {
    setPressAction(mainScroller.scrollDown);
    setReleaseAction(mainScroller.stopVScrolling);
  }

  mainScroller.setVScrollButtons(mainScrollUpButton, mainScrollDownButton);

}