aboutsummaryrefslogtreecommitdiffstats
path: root/content_scripts/hud.coffee
blob: 2b3748bfcf77eb762a75e1a5cb242d8dc61b0fee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#
# A heads-up-display (HUD) for showing Vimium page operations.
# Note: you cannot interact with the HUD until document.body is available.
#
HUD =
  tween: null
  hudUI: null
  _displayElement: null

  # This HUD is styled to precisely mimick the chrome HUD on Mac. Use the "has_popup_and_link_hud.html"
  # test harness to tweak these styles to match Chrome's. One limitation of our HUD display is that
  # it doesn't sit on top of horizontal scrollbars like Chrome's HUD does.

  init: ->
    @hudUI = new UIComponent "pages/hud.html", "vimiumHUDFrame", ({data}) =>
      this[data.name]? data
    @tween = new Tween "iframe.vimiumHUDFrame.vimiumUIComponentVisible", @hudUI.shadowDOM

  showForDuration: (text, duration) ->
    @show(text)
    @_showForDurationTimerId = setTimeout((=> @hide()), duration)

  show: (text) ->
    return unless @enabled()
    clearTimeout(@_showForDurationTimerId)
    @hudUI.show {name: "show", text}
    @tween.fade 1.0, 150

  showFindMode: (text = "") ->
    return unless @enabled()
    @hudUI.show {name: "showFindMode", text}
    @tween.fade 1.0, 150

  updateMatchesCount: (matchCount, showMatchText = true) ->
    @hudUI.postMessage {name: "updateMatchesCount", matchCount, showMatchText}

  search: (data) ->
    findModeQuery.rawQuery = data.query
    updateFindModeQuery()
    performFindInPlace()
    showFindModeHUDForQuery()

  # Hide the HUD.
  # If :immediate is falsy, then the HUD is faded out smoothly (otherwise it is hidden immediately).
  # If :updateIndicator is truthy, then we also refresh the mode indicator.  The only time we don't update the
  # mode indicator, is when hide() is called for the mode indicator itself.
  hide: (immediate = false, updateIndicator = true) ->
    return unless @tween?
    clearTimeout(@_showForDurationTimerId)
    @tween.stop()
    if immediate
      unless updateIndicator
        @hudUI.hide()
        @hudUI.postMessage {name: "hide"}
      Mode.setIndicator() if updateIndicator
    else
      @tween.fade 0, 150, => @hide true, updateIndicator

  hideFindMode: (data) ->
    # An element element won't receive a focus event if the search landed on it while we were in the HUD
    # iframe. To end up with the correct modes active, we create a focus/blur event manually after refocusing
    # this window.
    window.focus()

    sel = window.getSelection()
    focusNode = if not sel.focusNode?
      null
    else if sel.focusNode == sel.anchorNode and sel.focusOffset == sel.anchorOffset
      # The selection either *is* an element, or is inside an opaque element (eg. <input>).
      sel.focusNode.childNodes[sel.focusOffset]
    else if sel.focusNode.nodeType != sel.focusNode.ELEMENT_NODE
      sel.focusNode.parentElement
    else
      sel.focusNode
    document.activeElement?.blur()
    focusNode?.focus()

    findModeQuery.rawQuery = data.query
    handlerStack.bubbleEvent "keydown", data.event

  isReady: do ->
    ready = false
    DomUtils.documentReady -> ready = true
    -> ready and document.body != null

  # A preference which can be toggled in the Options page. */
  enabled: -> !Settings.get("hideHud")

class Tween
  opacity: 0
  intervalId: -1
  styleElement: null

  constructor: (@cssSelector, insertionPoint = document.documentElement) ->
    @styleElement = document.createElement "style"

    unless @styleElement.style
      # We're in an XML document, so we shouldn't inject any elements. See the comment in UIComponent.
      Tween::fade = Tween::stop = Tween::updateStyle = ->
      return

    @styleElement.type = "text/css"
    @styleElement.innerHTML = ""
    insertionPoint.appendChild @styleElement

  fade: (toAlpha, duration, onComplete) ->
    clearInterval @intervalId
    startTime = (new Date()).getTime()
    fromAlpha = @opacity
    alphaStep = toAlpha - fromAlpha

    performStep = =>
      elapsed = (new Date()).getTime() - startTime
      if (elapsed >= duration)
        clearInterval @intervalId
        @updateStyle toAlpha
        onComplete?()
      else
        value = (elapsed / duration) * alphaStep + fromAlpha
        @updateStyle value

    @updateStyle @opacity
    @intervalId = setInterval performStep, 50

  stop: -> clearInterval @intervalId

  updateStyle: (@opacity) ->
    @styleElement.innerHTML = """
      #{@cssSelector} {
        opacity: #{@opacity};
      }
    """

root = exports ? window
root.HUD = HUD