]> git.gir.st - VimFx.git/blob - extension/lib/marker.coffee
Change license to MIT
[VimFx.git] / extension / lib / marker.coffee
1 # This file contains an abstraction for hint markers. It creates the UI for a
2 # marker and provides methods to manipulate the markers.
3
4 utils = require('./utils')
5
6 class Marker
7 # `@wrapper` is a stand-in for the element that the marker represents. See
8 # `MarkerContainer::injectHints` for more information.
9 constructor: (options) ->
10 {
11 @wrapper, @document, @viewport, @zoom = 1, @isComplementary = false
12 } = options
13 @elementShape = @wrapper.shape
14 @markerElement = utils.createBox(@document, 'marker')
15 @markerElement.setAttribute('data-type', @wrapper.type)
16 @weight = @wrapper.combinedArea
17 @hint = ''
18 @originalHint = null
19 @text = @wrapper.text?.toLowerCase() ? ''
20 @visible = true
21 @highlighted = false
22 @position = null
23 @dx = 0
24 @dy = 0
25
26 reset: ->
27 @setHint(@originalHint)
28 @show()
29 @refreshPosition()
30
31 show: -> @setVisibility(true)
32 hide: -> @setVisibility(false)
33
34 setVisibility: (@visible) ->
35 @markerElement.classList.toggle('marker--hidden', not @visible)
36
37 # To be called when the marker has been both assigned a hint and inserted
38 # into the DOM, and thus gotten a width and height.
39 setPosition: ->
40 {
41 textOffset, isBlock
42 width: elementWidth
43 nonCoveredPoint: {x: left, y: top, offset}
44 } = @elementShape
45
46 rect = @markerElement.getBoundingClientRect()
47
48 # Take movements into account.
49 left += @dx
50 top += @dy
51
52 # Make the position relative to the top frame.
53 left += offset.left
54 top += offset.top
55
56 # Take the current zoom into account.
57 left *= @zoom
58 top *= @zoom
59
60 if textOffset?
61 # Move the marker just to the left of the text of its element, unless the
62 # element is a “block” (see `getElementShape` in markable-elements.coffee)
63 # and the marker wouldn’t cover text.
64 unless isBlock and rect.width < textOffset
65 left += textOffset * @zoom - rect.width
66 else
67 # Otherwise make sure that it doesn’t flow outside the right side of its
68 # element. This is to avoid the following situation (where `+` is a small
69 # button, `Link text` is a (larger) link and `DAG` and `E` are the hints
70 # placed on top of them.) This makes it clearer which hint does what.
71 # Example site: Hackernews.
72 #
73 # z-layer before after
74 # bottom +Link text +Link text
75 # middle DAG DAG
76 # top E E
77 left -= Math.max(0, rect.width - elementWidth * @zoom)
78
79 # Center the marker vertically on the non-covered point.
80 top -= Math.round(rect.height / 2)
81
82 # Make sure that the marker stays within the viewport.
83 left = Math.min(left, @zoom * @viewport.right - rect.width)
84 top = Math.min(top, @zoom * @viewport.bottom - rect.height)
85 left = Math.max(left, @zoom * @viewport.left)
86 top = Math.max(top, @zoom * @viewport.top)
87
88 # Use whole numbers for more deterministic positioning.
89 left = Math.round(left)
90 top = Math.round(top)
91
92 # The positioning is absolute.
93 @markerElement.style.left = "#{left}px"
94 @markerElement.style.top = "#{top}px"
95
96 # For quick access.
97 @position = {
98 left, right: Math.round(left + rect.width)
99 top, bottom: Math.round(top + rect.height)
100 }
101
102 updatePosition: (@dx, @dy) ->
103 @setPosition()
104
105 refreshPosition: ->
106 @setPosition()
107
108 setHint: (@hint) ->
109 @originalHint ?= @hint
110 @markerElement.textContent = ''
111 fragment = @document.createDocumentFragment()
112 utils.createBox(@document, 'marker-char', fragment, char) for char in @hint
113 @markerElement.appendChild(fragment)
114
115 matchHint: (hint) ->
116 return @hint.startsWith(hint)
117
118 matchText: (strings) ->
119 return strings.every((string) => @text.includes(string))
120
121 markMatchedPart: (hint) ->
122 matchEnd = if @matchHint(hint) then hint.length else 0
123 for child, index in @markerElement.children
124 child.classList.toggle('marker-char--matched', index < matchEnd)
125 return
126
127 markMatched: (matched) ->
128 @markerElement.classList.toggle('marker--matched', matched)
129
130 markHighlighted: (@highlighted) ->
131 @markerElement.classList.toggle('marker--highlighted', @highlighted)
132
133 module.exports = Marker
Imprint / Impressum