2 # Copyright Anton Khodakivskiy 2012, 2013.
3 # Copyright Simon Lydell 2013, 2014, 2015, 2016.
5 # This file is part of VimFx.
7 # VimFx is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
12 # VimFx is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with VimFx. If not, see <http://www.gnu.org/licenses/>.
21 # This file contains an abstraction for hint markers. It creates the UI for a
22 # marker and provides methods to manipulate the markers.
24 utils = require('./utils')
27 # `@wrapper` is a stand-in for the element that the marker represents. See
28 # `injectHints` in hints.coffee for more information.
29 constructor: (@wrapper, @document) ->
30 @elementShape = @wrapper.shape
31 @markerElement = utils.createBox(@document, 'marker')
32 @markerElement.setAttribute('data-type', @wrapper.type)
33 @weight = @elementShape.area
41 @originalPosition = null
47 show: -> @setVisibility(true)
48 hide: -> @setVisibility(false)
49 setVisibility: (visible) ->
50 @markerElement.classList.toggle('marker--hidden', not visible)
52 # To be called when the marker has been both assigned a hint and inserted
53 # into the DOM, and thus gotten a width and height.
54 setPosition: (@viewport, @zoom) ->
56 markerElement: {clientWidth, clientHeight}
57 elementShape: {nonCoveredPoint: {x: left, y: top, offset}}
60 @width = clientWidth / @zoom
61 @height = clientHeight / @zoom
63 # Center the marker vertically on the non-covered point.
64 top -= Math.ceil(@height / 2)
66 # Make the position relative to the top frame.
70 @originalPosition = {left, top}
73 moveTo: (left, top) ->
74 # Make sure that the marker stays within the viewport.
75 left = Math.min(left, @viewport.right - @width)
76 top = Math.min(top, @viewport.bottom - @height)
77 left = Math.max(left, @viewport.left)
78 top = Math.max(top, @viewport.top)
80 # Take the current zoom into account.
81 left = Math.round(left * @zoom)
82 top = Math.round(top * @zoom)
84 # The positioning is absolute.
85 @markerElement.style.left = "#{left}px"
86 @markerElement.style.top = "#{top}px"
90 left, right: left + @width,
91 top, bottom: top + @height,
94 updatePosition: (dx, dy) ->
95 @moveTo(@originalPosition.left + dx, @originalPosition.top + dy)
99 @markerElement.textContent = ''
100 fragment = @document.createDocumentFragment()
101 utils.createBox(@document, 'marker-char', fragment, char) for char in @hint
102 @markerElement.appendChild(fragment)
104 matchHintChar: (char) ->
105 if char == @hint[@hintIndex]
106 @toggleLastHintChar(true)
114 @toggleLastHintChar(false)
116 toggleLastHintChar: (visible) ->
117 @markerElement.children[@hintIndex]
118 .classList.toggle('marker-char--matched', visible)
120 isMatched: -> (@hintIndex == @hint.length)
122 markMatched: (matched) ->
123 @markerElement.classList.toggle('marker--matched', matched)
125 # Finds all stacks of markers that overlap each other (by using `getStackFor`)
126 # (#1), and rotates their `z-index`:es (#2), thus alternating which markers are
128 rotateOverlappingMarkers = (originalMarkers, forward) ->
129 # Shallow working copy. This is necessary since `markers` will be mutated and
131 markers = originalMarkers[..]
134 stacks = (getStackFor(markers.pop(), markers) while markers.length > 0)
137 # Stacks of length 1 don't participate in any overlapping, and can therefore
139 for stack in stacks when stack.length > 1
140 # This sort is not required, but makes the rotation more predictable.
141 stack.sort((a, b) -> a.markerElement.style.zIndex -
142 b.markerElement.style.zIndex)
144 # Array of z-indices.
145 indexStack = (marker.markerElement.style.zIndex for marker in stack)
146 # Shift the array of indices one item forward or back.
148 indexStack.unshift(indexStack.pop())
150 indexStack.push(indexStack.shift())
152 for marker, index in stack
153 marker.markerElement.style.zIndex = indexStack[index]
157 # Get an array containing `marker` and all markers that overlap `marker`, if
158 # any, which is called a "stack". All markers in the returned stack are spliced
159 # out from `markers`, thus mutating it.
160 getStackFor = (marker, markers) ->
163 {top, bottom, left, right} = marker.position
166 while index < markers.length
167 nextMarker = markers[index]
169 next = nextMarker.position
170 overlapsVertically = (next.bottom >= top and next.top <= bottom)
171 overlapsHorizontally = (next.right >= left and next.left <= right)
173 if overlapsVertically and overlapsHorizontally
174 # Also get all markers overlapping this one.
175 markers.splice(index, 1)
176 stack = stack.concat(getStackFor(nextMarker, markers))
178 # Continue the search.
185 rotateOverlappingMarkers