 
   onMouseEnter: (e) ->
     return unless @controlsEnabled and @spells.length
-    @showSpells()
+    @clearTimeouts()
+    @showSpellsTimeout = _.delay @showSpells, 100
 
   onMouseLeave: (e) ->
     return unless @controlsEnabled and @spells.length
+    @clearTimeouts()
+    @hideSpellsTimeout = _.delay @hideSpells, 100
+
+  clearTimeouts: ->
+    clearTimeout @showSpellsTimeout if @showSpellsTimeout
     clearTimeout @hideSpellsTimeout if @hideSpellsTimeout
-    @hideSpellsTimeout = _.delay @hideSpells, 100
+    @showSpellsTimeout = @hideSpellsTimeout = null
 
   onThangListEntryPopoverShown: (e) ->
     # I couldn't figure out how to get the mouseenter / mouseleave to always work, so this is a fallback

  onSetLetterbox: (e) ->
    if e.on then @reasonsToBeDisabled.letterbox = true else delete @reasonsToBeDisabled.letterbox
    @updateControls()
  onDisableControls: (e) ->
    return if e.controls and not ('surface' in e.controls)  # disable selection?
    @reasonsToBeDisabled.controls = true
    @updateControls()
  onEnableControls: (e) ->
    delete @reasonsToBeDisabled.controls
    @updateControls()
  updateControls: ->
    enabled = _.keys(@reasonsToBeDisabled).length is 0
    return if enabled is @controlsEnabled

    'surface:frame-changed': "onFrameChanged"
    'level-set-letterbox': 'onSetLetterbox'
    'tome:thang-list-entry-popover-shown': 'onThangListEntryPopoverShown'
    'surface:coordinates-shown': 'onSurfaceCoordinatesShown'

  events:
    'click': 'onClick'
