 #     continues scrolling at least until its corresponding keyup event is received.  We never initiate a new
 #     animator on keyboard repeat.
 
-# Scroll by a relative amount (a number) in some direction, possibly smoothly.
-doScrollBy = do ->
-  # This is logical time. Time is advanced each time an animator is activated, and on each keyup event.
-  time = 0
-  lastEvent = null
-  keyHandler = null
-  keyIsDown = false
-
-  (element, direction, amount) ->
+CoreScroller =
+  init: (frontendSettings) ->
+    @settings = frontendSettings
+    @time = 0
+    @lastEvent = null
+    @keyIsDown = false
+    handlerStack.push
+      keydown: (event) =>
+        @keyIsDown = true
+        @lastEvent = event
+      keyup: =>
+        @keyIsDown = false
+        @time += 1
+
+  # Scroll element by a relative amount (a number) in some direction.
+  scroll: (element, direction, amount) ->
     return unless amount
 
-    keyHandler ?= handlerStack.push
-      keydown: ->
-        keyIsDown = true unless event.repeat
-        lastEvent = event
-      keyup: ->
-        keyIsDown = false
-        time += 1
-
-    unless settings.get "smoothScroll"
+    unless @settings.get "smoothScroll"
       # Jump scrolling.
       performScroll element, direction, amount
       checkVisibility element
       return
 
     # We don't activate new animators on keyboard repeats.
-    return if lastEvent?.repeat
-
-    activationTime = ++time
-
-    isKeyStillDown = ->
-      time == activationTime and keyIsDown
+    return if @lastEvent?.repeat
+
+    activationTime = ++@time
+
+    isMyKeyStillDown = =>
+      @time == activationTime and @keyIsDown
 
     # Store amount's sign and make amount positive; the logic is clearer when amount is positive.
     sign = Math.sign amount


Scroller =
  init: (frontendSettings) ->
    settings = frontendSettings
    handlerStack.push DOMActivate: -> activatedElement = event.target

  # scroll the active element in :direction by :amount * :factor.
  # :factor is needed because :amount can take on string values, which scrollBy converts to element dimensions.

      # The constants in the duration calculation, above, are chosen to provide reasonable scroll speeds for
      # scrolls resulting from distinct keypresses.  For continuous scrolls (where the key remains depressed),
      # some scrolls are too slow, and others too fast.  Here, we compensate a bit.
      if isMyKeyStillDown() and 50 <= totalElapsed and 0.5 <= calibration <= 1.6
        calibration *= 1.05 if 1.05 * calibration * amount <= 150 # Speed up slow scrolls.
        calibration *= 0.95 if 150 <= 0.95 * calibration * amount # Slow down fast scrolls.

      # Calculate the initial delta, rounding up to ensure progress.  Then, adjust delta to account for the
      # current scroll state.
      delta = Math.ceil amount * (elapsed / duration) * calibration
      delta = if isMyKeyStillDown() then delta else Math.max 0, Math.min delta, amount - totalDelta

      if delta and performScroll element, direction, sign * delta
        totalDelta += delta
