diff --git a/keymaps/minimap.cson b/keymaps/minimap.cson index ccb3b832..6c42f7c4 100644 --- a/keymaps/minimap.cson +++ b/keymaps/minimap.cson @@ -11,5 +11,5 @@ # 'ctrl-k ctrl-m': 'minimap:toggle' # 'ctrl-k ctrl-s': 'minimap:open-quick-settings' -'.minimap-quick-settings': - 'space': 'core:validate' +'minimap-quick-settings': + 'space': 'core:confirm' diff --git a/lib/main.coffee b/lib/main.coffee index c6d0a177..31fc1c8c 100644 --- a/lib/main.coffee +++ b/lib/main.coffee @@ -82,6 +82,12 @@ class Main type: 'boolean' default: false description: "If this option is enabled then when you click the minimap it will scroll to the destination with animation" + devicePixelRatio: + type: 'number' + minimum: 1 + maximum: 3 + default: window.devicePixelRatio + description: 'The device pixel ratio used to draw the canvas on high-DPI device.' # Internal: The activation state of the minimap package. active: false diff --git a/lib/minimap-element.coffee b/lib/minimap-element.coffee index d67e97ac..e7578bfc 100644 --- a/lib/minimap-element.coffee +++ b/lib/minimap-element.coffee @@ -1,14 +1,16 @@ {debounce} = require 'underscore-plus' {CompositeDisposable, Disposable} = require 'event-kit' +{EventsDelegation} = require 'atom-utils' DOMStylesReader = require './mixins/dom-styles-reader' CanvasDrawer = require './mixins/canvas-drawer' -MinimapQuickSettingsView = null +MinimapQuickSettingsElement = null # Public: class MinimapElement extends HTMLElement DOMStylesReader.includeInto(this) CanvasDrawer.includeInto(this) + EventsDelegation.includeInto(this) ### Public ### @@ -16,6 +18,7 @@ class MinimapElement extends HTMLElement domPollingIntervalId: null domPollingPaused: false displayMinimapOnLeft: false + devicePixelRatio: 1 # ## ## ####### ####### ## ## ###### # ## ## ## ## ## ## ## ## ## ## @@ -68,6 +71,9 @@ class MinimapElement extends HTMLElement 'minimap.useHardwareAcceleration': (@useHardwareAcceleration) => @requestUpdate() if @attached + 'minimap.devicePixelRatio': (@devicePixelRatio) => + @requestUpdate() if @attached + attachedCallback: -> @domPollingIntervalId = setInterval((=> @pollDOM()), @domPollingInterval) @measureHeightAndWidth() @@ -163,31 +169,39 @@ class MinimapElement extends HTMLElement @scrollIndicator = undefined initializeOpenQuickSettings: -> + return if @openQuickSettings? + @openQuickSettings = document.createElement('div') @openQuickSettings.classList.add 'open-minimap-quick-settings' @controls.appendChild(@openQuickSettings) - @openQuickSettings.addEventListener 'mousedown', (e) => - e.preventDefault() - e.stopPropagation() - - if @quickSettingsView? - @quickSettingsView.destroy() - @quickSettingsSubscription.dispose() - else - MinimapQuickSettingsView ?= require './minimap-quick-settings-view' - @quickSettingsView = new MinimapQuickSettingsView(this) - @quickSettingsSubscription = @quickSettingsView.onDidDestroy => - @quickSettingsView = null - - @quickSettingsView.attach() - {top, left} = @getBoundingClientRect() - @quickSettingsView.css({ - top: top + 'px' - left: (left - @quickSettingsView.width()) + 'px' - }) + @openQuickSettingSubscription = @subscribeTo @openQuickSettings, + 'mousedown': (e) => + e.preventDefault() + e.stopPropagation() + + if @quickSettingsElement? + @quickSettingsElement.destroy() + @quickSettingsSubscription.dispose() + else + MinimapQuickSettingsElement ?= require './minimap-quick-settings-element' + @quickSettingsElement = new MinimapQuickSettingsElement + @quickSettingsElement.setModel(this) + @quickSettingsSubscription = @quickSettingsElement.onDidDestroy => + @quickSettingsElement = null + + @quickSettingsElement.attach() + {top, left, right} = @canvas.getBoundingClientRect() + @quickSettingsElement.style.top = top + 'px' + + if @displayMinimapOnLeft + @quickSettingsElement.style.left = (right) + 'px' + else + @quickSettingsElement.style.left = (left - @quickSettingsElement.clientWidth) + 'px' disposeOpenQuickSettings: -> + return unless @openQuickSettings? @controls.removeChild(@openQuickSettings) + @openQuickSettingSubscription.dispose() @openQuickSettings = undefined getTextEditor: -> @minimap.getTextEditor() @@ -266,12 +280,12 @@ class MinimapElement extends HTMLElement transform: @makeTranslate(visibleAreaLeft, visibleAreaTop) @applyStyles @controls, - width: Math.min(@canvas.width, @width) + 'px' + width: Math.min(@canvas.width / devicePixelRatio, @width) + 'px' canvasTop = @minimap.getFirstVisibleScreenRow() * @minimap.getLineHeight() - @minimap.getScrollTop() canvasTransform = @makeTranslate(0, canvasTop) - canvasTransform += " " + @makeScale(1/devicePixelRatio) if devicePixelRatio isnt 1 + canvasTransform += " " + @makeScale(1 / devicePixelRatio) if devicePixelRatio isnt 1 @applyStyles @canvas, transform: canvasTransform if @minimapScrollIndicator and @minimap.canScroll() and not @scrollIndicator @@ -332,8 +346,8 @@ class MinimapElement extends HTMLElement delete @marginRight if canvasWidth isnt @canvas.width or @height isnt @canvas.height - @canvas.width = canvasWidth * devicePixelRatio - @canvas.height = (@height + @minimap.getLineHeight()) * devicePixelRatio + @canvas.width = canvasWidth * @devicePixelRatio + @canvas.height = (@height + @minimap.getLineHeight()) * @devicePixelRatio # ######## ## ## ######## ## ## ######## ###### # ## ## ## ## ### ## ## ## ## diff --git a/lib/minimap-open-quick-settings-view.coffee b/lib/minimap-open-quick-settings-view.coffee deleted file mode 100644 index 30dbcb97..00000000 --- a/lib/minimap-open-quick-settings-view.coffee +++ /dev/null @@ -1,32 +0,0 @@ -{View} = require 'atom-space-pen-views' -MinimapQuickSettingsView = require './minimap-quick-settings-view' - -module.exports = -class MinimapOpenQuickSettingsView extends View - @content: -> - @div class: 'open-minimap-quick-settings' - - dropdown: null - - initialize: (minimap) -> - @on 'mousedown', (e) => - e.preventDefault() - e.stopPropagation() - - if @dropdown? - @dropdown.destroy() - else - offset = minimap.offset() - @dropdown = new MinimapQuickSettingsView(minimap) - - css = top: offset.top - if atom.config.get('minimap.displayMinimapOnLeft') - css.left = offset.left + minimap.width() - else - css.right = window.innerWidth - offset.left - - @dropdown.css(css).attach() - - @dropdown.onDidDestroy => - @dropdown.off() - @dropdown = null diff --git a/lib/minimap-quick-settings-element.coffee b/lib/minimap-quick-settings-element.coffee new file mode 100644 index 00000000..72bd9f5d --- /dev/null +++ b/lib/minimap-quick-settings-element.coffee @@ -0,0 +1,144 @@ +{EventsDelegation, SpacePenDSL} = require 'atom-utils' +{CompositeDisposable, Emitter} = require 'event-kit' + +Main = require './main' + +module.exports = +class MinimapQuickSettingsElement extends HTMLElement + SpacePenDSL.includeInto(this) + EventsDelegation.includeInto(this) + + @content: -> + @div class: 'select-list popover-list minimap-quick-settings', => + @input type: 'text', class: 'hidden-input', outlet: 'hiddenInput' + @ol class: 'list-group mark-active', outlet: 'list', => + @li class: 'separator', outlet: 'separator' + @li class: '', outlet: 'codeHighlights', 'code-highlights' + @div class: 'btn-group', => + @button class: 'btn btn-default', outlet: 'onLeftButton', 'On Left' + @button class: 'btn btn-default', outlet: 'onRightButton', 'On Right' + + selectedItem: null + + setModel: (@minimap) -> + @emitter = new Emitter + @subscriptions = new CompositeDisposable + @plugins = {} + @itemsActions = new WeakMap + + @subscriptions.add Main.onDidAddPlugin ({name, plugin}) => + @addItemFor(name, plugin) + @subscriptions.add Main.onDidRemovePlugin ({name, plugin}) => + @removeItemFor(name, plugin) + @subscriptions.add Main.onDidActivatePlugin ({name, plugin}) => + @activateItem(name, plugin) + @subscriptions.add Main.onDidDeactivatePlugin ({name, plugin}) => + @deactivateItem(name, plugin) + + @subscriptions.add atom.commands.add 'minimap-quick-settings', + 'core:move-up': => @selectPreviousItem() + 'core:move-down': => @selectNextItem() + 'core:move-left': => atom.config.set('minimap.displayMinimapOnLeft', true) + 'core:move-right': => atom.config.set('minimap.displayMinimapOnLeft', false) + 'core:cancel': => @destroy() + 'core:confirm': => @toggleSelectedItem() + + @codeHighlights.classList.toggle('active', @minimap.displayCodeHighlights) + @subscriptions.add @subscribeTo @codeHighlights, + 'mousedown': (e) => + e.preventDefault() + atom.config.set('minimap.displayCodeHighlights', !@minimap.displayCodeHighlights) + + @itemsActions.set @codeHighlights, => + atom.config.set('minimap.displayCodeHighlights', !@minimap.displayCodeHighlights) + + @subscriptions.add @subscribeTo @hiddenInput, + 'focusout': (e) => + @destroy() + + @subscriptions.add @subscribeTo @onLeftButton, + 'mousedown': (e) -> + e.preventDefault() + atom.config.set('minimap.displayMinimapOnLeft', true) + + @subscriptions.add @subscribeTo @onRightButton, + 'mousedown': (e) -> + e.preventDefault() + atom.config.set('minimap.displayMinimapOnLeft', false) + + @subscriptions.add atom.config.observe 'minimap.displayCodeHighlights', (bool) => + @codeHighlights.classList.toggle('active', bool) + + @subscriptions.add atom.config.observe 'minimap.displayMinimapOnLeft', (bool) => + @onLeftButton.classList.toggle('selected', bool) + @onRightButton.classList.toggle('selected', not bool) + + @initList() + + onDidDestroy: (callback) -> + @emitter.on 'did-destroy', callback + + attach: -> + workspaceElement = atom.views.getView(atom.workspace) + workspaceElement.appendChild this + @hiddenInput.focus() + + destroy: -> + @emitter.emit('did-destroy') + @subscriptions.dispose() + @parentNode.removeChild(this) + + initList: -> + @itemsDisposables = new WeakMap + @addItemFor(name, plugin) for name, plugin of Main.plugins + + toggleSelectedItem: => @itemsActions.get(@selectedItem)?() + + selectNextItem: -> + @selectedItem.classList.remove('selected') + if @selectedItem.nextSibling? + @selectedItem = @selectedItem.nextSibling + @selectedItem = @selectedItem.nextSibling if @selectedItem.matches('.separator') + else + @selectedItem = @list.firstChild + @selectedItem.classList.add('selected') + + selectPreviousItem: -> + @selectedItem.classList.remove('selected') + if @selectedItem.previousSibling? + @selectedItem = @selectedItem.previousSibling + @selectedItem = @selectedItem.previousSibling if @selectedItem.matches('.separator') + else + @selectedItem = @list.lastChild + @selectedItem.classList.add('selected') + + addItemFor: (name, plugin) -> + item = document.createElement('li') + item.classList.add('active') if plugin.isActive() + item.textContent = name + + action = => Main.togglePluginActivation(name) + + @itemsActions.set(item, action) + @itemsDisposables.set item, @addDisposableEventListener item, 'mousedown', (e) => + e.preventDefault() + action() + + @plugins[name] = item + @list.insertBefore item, @separator + + unless @selectedItem? + @selectedItem = item + @selectedItem.classList.add('selected') + + removeItemFor: (name, plugin) -> + try @list.removeChild(@plugins[name]) + delete @plugins[name] + + activateItem: (name, plugin) -> + @plugins[name].classList.add('active') + + deactivateItem: (name, plugin) -> + @plugins[name].classList.remove('active') + +module.exports = MinimapQuickSettingsElement = document.registerElement 'minimap-quick-settings', prototype: MinimapQuickSettingsElement.prototype diff --git a/lib/minimap-quick-settings-view.coffee b/lib/minimap-quick-settings-view.coffee deleted file mode 100644 index 02f199c9..00000000 --- a/lib/minimap-quick-settings-view.coffee +++ /dev/null @@ -1,107 +0,0 @@ -{$, View} = require 'atom-space-pen-views' -{CompositeDisposable, Emitter} = require 'event-kit' - -Minimap = require './main' - -module.exports = -class MinimapQuickSettingsView extends View - @content: -> - @div class: 'select-list popover-list minimap-quick-settings', => - @input type: 'text', class: 'hidden-input', outlet: 'hiddenInput' - @ol class: 'list-group mark-active', outlet: 'list', => - @li class: 'separator', outlet: 'separator' - @li class: '', outlet: 'codeHighlights', 'code-highlights' - - selectedItem: null - - initialize: (@minimapView) -> - @emitter = new Emitter - @subscriptions = new CompositeDisposable - @plugins = {} - @subscriptions.add Minimap.onDidAddPlugin ({name, plugin}) => - @addItemFor(name, plugin) - @subscriptions.add Minimap.onDidRemovePlugin ({name, plugin}) => - @removeItemFor(name, plugin) - @subscriptions.add Minimap.onDidActivatePlugin ({name, plugin}) => - @activateItem(name, plugin) - @subscriptions.add Minimap.onDidDeactivatePlugin ({name, plugin}) => - @deactivateItem(name, plugin) - - @subscriptions.add atom.commands.add '.minimap-quick-settings', - 'core:move-up': => @selectPreviousItem() - 'core:move-down': => @selectNextItem() - 'core:cancel': => @destroy() - 'core:confirm': => @toggleSelectedItem() - - @codeHighlights.toggleClass('active', @minimapView.displayCodeHighlights) - @codeHighlights.on 'mousedown', (e) => - e.preventDefault() - @minimapView.setDisplayCodeHighlights(!@minimapView.displayCodeHighlights) - @codeHighlights.toggleClass('active', @minimapView.displayCodeHighlights) - - @hiddenInput.on 'focusout', @destroy - - @initList() - - onDidDestroy: (callback) -> - @emitter.on 'did-destroy', callback - - attach: -> - workspaceElement = atom.views.getView(atom.workspace) - workspaceElement.appendChild @element - @hiddenInput.focus() - - destroy: => - @emitter.emit('did-destroy') - @off() - @hiddenInput.off() - @codeHighlights.off() - @subscriptions.dispose() - @detach() - - initList: -> - @addItemFor(name, plugin) for name, plugin of Minimap.plugins - - toggleSelectedItem: => - @selectedItem.mousedown() - - selectNextItem: => - @selectedItem.removeClass('selected') - if @selectedItem.index() isnt @list.children().length - 1 - @selectedItem = @selectedItem.next() - @selectedItem = @selectedItem.next() if @selectedItem.is('.separator') - else - @selectedItem = @list.children().first() - @selectedItem.addClass('selected') - - selectPreviousItem: => - @selectedItem.removeClass('selected') - if @selectedItem.index() isnt 0 - @selectedItem = @selectedItem.prev() - @selectedItem = @selectedItem.prev() if @selectedItem.is('.separator') - else - @selectedItem = @list.children().last() - @selectedItem.addClass('selected') - - addItemFor: (name, plugin) -> - cls = if plugin.isActive() then 'active' else '' - item = $("
  • #{name}
  • ") - item.on 'mousedown', (e) => - e.preventDefault() - atom.commands.dispatch item[0], "minimap:toggle-#{name}" - - @plugins[name] = item - @separator.before item - unless @selectedItem? - @selectedItem = item - @selectedItem.addClass('selected') - - removeItemFor: (name, plugin) -> - try @list.remove(@plugins[name]) - delete @plugins[name] - - activateItem: (name, plugin) -> - @plugins[name].addClass('active') - - deactivateItem: (name, plugin) -> - @plugins[name].removeClass('active') diff --git a/lib/mixins/canvas-drawer.coffee b/lib/mixins/canvas-drawer.coffee index 4bf1e2c4..a289bc25 100644 --- a/lib/mixins/canvas-drawer.coffee +++ b/lib/mixins/canvas-drawer.coffee @@ -129,9 +129,9 @@ class CanvasDrawer extends Mixin return if firstRow > lastRow lines = @getTextEditor().tokenizedLinesForScreenRows(firstRow, lastRow) - lineHeight = @minimap.getLineHeight() * devicePixelRatio - charHeight = @minimap.getCharHeight() * devicePixelRatio - charWidth = @minimap.getCharWidth() * devicePixelRatio + lineHeight = @minimap.getLineHeight() * @devicePixelRatio + charHeight = @minimap.getCharHeight() * @devicePixelRatio + charWidth = @minimap.getCharWidth() * @devicePixelRatio canvasWidth = @canvas.width displayCodeHighlights = @displayCodeHighlights decorations = @minimap.decorationsForScreenRowRange(firstRow, lastRow) @@ -260,7 +260,7 @@ class CanvasDrawer extends Mixin # destRow - The row {Number} on the destination bitmap. # rowCount - The {Number} of rows to copy. copyBitmapPart: (context, bitmapCanvas, srcRow, destRow, rowCount) -> - lineHeight = @minimap.getLineHeight() * devicePixelRatio + lineHeight = @minimap.getLineHeight() * @devicePixelRatio context.drawImage(bitmapCanvas, 0, srcRow * lineHeight, bitmapCanvas.width, rowCount * lineHeight, diff --git a/lib/mixins/plugin-management.coffee b/lib/mixins/plugin-management.coffee index 53c876b3..ddfc1256 100644 --- a/lib/mixins/plugin-management.coffee +++ b/lib/mixins/plugin-management.coffee @@ -46,6 +46,14 @@ class PluginManagement extends Mixin event = {name, plugin} @emitter.emit('did-remove-plugin', event) + togglePluginActivation: (name, boolean=undefined) -> + settingsKey = "minimap.plugins.#{name}" + if boolean? + atom.config.set settingsKey, boolean + else + atom.config.set settingsKey, not atom.config.get(settingsKey) + + @updatesPluginActivationState(name) # Internal: Updates the plugin activation state according to the current # config. @@ -84,9 +92,7 @@ class PluginManagement extends Mixin @updatesPluginActivationState(name) commands = {} - commands["minimap:toggle-#{name}"] = => - atom.config.set settingsKey, not atom.config.get(settingsKey) - @updatesPluginActivationState(name) + commands["minimap:toggle-#{name}"] = => @togglePluginActivation(name) @pluginsSubscriptions[name].add atom.commands.add 'atom-workspace', commands diff --git a/package.json b/package.json index 5f75e667..7cf26e81 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "fs-plus": "2.x", "mixto": "1.x", "semver": "^4.3.0", - "underscore-plus": "1.x" + "underscore-plus": "1.x", + "atom-utils": "0.5.x" } } diff --git a/spec/minimap-element-spec.coffee b/spec/minimap-element-spec.coffee index d0046da9..eba81d2e 100644 --- a/spec/minimap-element-spec.coffee +++ b/spec/minimap-element-spec.coffee @@ -15,7 +15,8 @@ realOffsetLeft = (o) -> transform = new WebKitCSSMatrix window.getComputedStyle(o).transform o.offsetLeft + transform.m41 -devicePixelRatio = window.devicePixelRatio || 1 +# Modify the global `devicePixelRatio` variable. +window.devicePixelRatio = 2 sleep = (duration) -> t = new Date @@ -29,6 +30,7 @@ describe 'MinimapElement', -> atom.config.set 'minimap.charWidth', 2 atom.config.set 'minimap.interline', 1 atom.config.set 'minimap.textOpacity', 1 + atom.config.set 'minimap.devicePixelRatio', 2 MinimapElement.registerViewProvider() @@ -72,6 +74,8 @@ describe 'MinimapElement', -> [noAnimationFrame, nextAnimationFrame, canvas, visibleArea] = [] beforeEach -> + # Comment after body below to leave the created text editor and minimap + # on DOM after the test run. jasmineContent = document.body.querySelector('#jasmine-content') noAnimationFrame = -> throw new Error('No animation frame requested') @@ -99,6 +103,10 @@ describe 'MinimapElement', -> background: rgba(0,255,0,0.3); opacity: 1; } + + atom-text-editor::shadow atom-text-editor-minimap::shadow .open-minimap-quick-settings { + opacity: 1 !important; + } """ jasmineContent.appendChild(styleNode) @@ -121,8 +129,8 @@ describe 'MinimapElement', -> expect(minimapElement.offsetWidth).toBeCloseTo(editorElement.clientWidth / 11, 0) it 'resizes the canvas to fit the minimap', -> - expect(canvas.offsetHeight / devicePixelRatio).toEqual(minimapElement.offsetHeight + minimap.getLineHeight()) - expect(canvas.offsetWidth / devicePixelRatio).toEqual(minimapElement.offsetWidth) + expect(canvas.offsetHeight / devicePixelRatio).toBeCloseTo(minimapElement.offsetHeight + minimap.getLineHeight(), 0) + expect(canvas.offsetWidth / devicePixelRatio).toBeCloseTo(minimapElement.offsetWidth, 0) it 'requests an update', -> expect(minimapElement.frameRequested).toBeTruthy() @@ -204,8 +212,8 @@ describe 'MinimapElement', -> expect(minimapElement.offsetWidth).toBeCloseTo(editorElement.offsetWidth / 11, 0) expect(minimapElement.offsetHeight).toEqual(editorElement.offsetHeight) - expect(canvas.offsetWidth / devicePixelRatio).toEqual(minimapElement.offsetWidth) - expect(canvas.offsetHeight / devicePixelRatio).toEqual(minimapElement.offsetHeight + minimap.getLineHeight()) + expect(canvas.offsetWidth / devicePixelRatio).toBeCloseTo(minimapElement.offsetWidth, 0) + expect(canvas.offsetHeight / devicePixelRatio).toBeCloseTo(minimapElement.offsetHeight + minimap.getLineHeight(), 0) describe 'when the editor visible content is changed', -> beforeEach -> @@ -406,7 +414,7 @@ describe 'MinimapElement', -> # ## ## ## ## ## ### ## ## ## ## # ###### ####### ## ## ## #### ###### - describe 'when the atom them is changed', -> + describe 'when the atom themes are changed', -> beforeEach -> nextAnimationFrame() spyOn(minimapElement, 'requestForcedUpdate').andCallThrough() @@ -640,7 +648,7 @@ describe 'MinimapElement', -> # ###### ######## ## ## #### ## ## ###### ###### describe 'when minimap.displayPluginsControls setting is true', -> - [openQuickSettings, quickSettingsView, workspaceElement] = [] + [openQuickSettings, quickSettingsElement, workspaceElement] = [] beforeEach -> atom.config.set 'minimap.displayPluginsControls', true @@ -655,20 +663,105 @@ describe 'MinimapElement', -> openQuickSettings = minimapElement.shadowRoot.querySelector('.open-minimap-quick-settings') mousedown(openQuickSettings) - quickSettingsView = workspaceElement.querySelector('.minimap-quick-settings') + quickSettingsElement = workspaceElement.querySelector('minimap-quick-settings') afterEach -> - minimapElement.quickSettingsView.destroy() + minimapElement.quickSettingsElement.destroy() it 'opens the quick settings view', -> - expect(quickSettingsView).toExist() + expect(quickSettingsElement).toExist() it 'positions the quick settings view next to the minimap', -> - minimapBounds = minimapElement.getBoundingClientRect() - settingsBounds = quickSettingsView.getBoundingClientRect() + minimapBounds = minimapElement.canvas.getBoundingClientRect() + settingsBounds = quickSettingsElement.getBoundingClientRect() + + expect(realOffsetTop(quickSettingsElement)).toBeCloseTo(minimapBounds.top, 0) + expect(realOffsetLeft(quickSettingsElement)).toBeCloseTo(minimapBounds.left - settingsBounds.width, 0) + + describe 'when the displayMinimapOnLeft setting is enabled', -> + describe 'clicking on the div', -> + beforeEach -> + atom.config.set('minimap.displayMinimapOnLeft', true) + + workspaceElement = atom.views.getView(atom.workspace) + jasmineContent.appendChild(workspaceElement) + + openQuickSettings = minimapElement.shadowRoot.querySelector('.open-minimap-quick-settings') + mousedown(openQuickSettings) + + quickSettingsElement = workspaceElement.querySelector('minimap-quick-settings') + + afterEach -> + minimapElement.quickSettingsElement.destroy() + + it 'positions the quick settings view next to the minimap', -> + minimapBounds = minimapElement.canvas.getBoundingClientRect() + settingsBounds = quickSettingsElement.getBoundingClientRect() + + expect(realOffsetTop(quickSettingsElement)).toBeCloseTo(minimapBounds.top, 0) + expect(realOffsetLeft(quickSettingsElement)).toBeCloseTo(minimapBounds.right, 0) + + describe 'when the adjustMinimapWidthToSoftWrap setting is enabled', -> + [controls] = [] + beforeEach -> + atom.config.set 'editor.softWrap', true + atom.config.set 'editor.softWrapAtPreferredLineLength', true + atom.config.set 'editor.preferredLineLength', 2 + + atom.config.set('minimap.adjustMinimapWidthToSoftWrap', true) + nextAnimationFrame() + + controls = minimapElement.shadowRoot.querySelector('.minimap-controls') + openQuickSettings = minimapElement.shadowRoot.querySelector('.open-minimap-quick-settings') + + editorElement.style.width = '1024px' + + sleep(150) + waitsFor -> minimapElement.frameRequested + runs -> nextAnimationFrame() + + it 'adjusts the size of the control div to fit in the minimap', -> + expect(controls.clientWidth).toEqual(minimapElement.canvas.clientWidth / devicePixelRatio) + + it 'positions the controls div over the canvas', -> + controlsRect = controls.getBoundingClientRect() + canvasRect = minimapElement.canvas.getBoundingClientRect() + expect(controlsRect.left).toEqual(canvasRect.left) + expect(controlsRect.right).toEqual(canvasRect.right) + + describe 'when the displayMinimapOnLeft setting is enabled', -> + beforeEach -> + atom.config.set('minimap.displayMinimapOnLeft', true) + nextAnimationFrame() + + it 'adjusts the size of the control div to fit in the minimap', -> + expect(controls.clientWidth).toEqual(minimapElement.canvas.clientWidth / devicePixelRatio) + + it 'positions the controls div over the canvas', -> + controlsRect = controls.getBoundingClientRect() + canvasRect = minimapElement.canvas.getBoundingClientRect() + expect(controlsRect.left).toEqual(canvasRect.left) + expect(controlsRect.right).toEqual(canvasRect.right) + + describe 'clicking on the div', -> + beforeEach -> + workspaceElement = atom.views.getView(atom.workspace) + jasmineContent.appendChild(workspaceElement) - expect(realOffsetTop(quickSettingsView)).toBeCloseTo(minimapBounds.top, 0) - expect(realOffsetLeft(quickSettingsView)).toBeCloseTo(minimapBounds.left - settingsBounds.width, 0) + openQuickSettings = minimapElement.shadowRoot.querySelector('.open-minimap-quick-settings') + mousedown(openQuickSettings) + + quickSettingsElement = workspaceElement.querySelector('minimap-quick-settings') + + afterEach -> + minimapElement.quickSettingsElement.destroy() + + it 'positions the quick settings view next to the minimap', -> + minimapBounds = minimapElement.canvas.getBoundingClientRect() + settingsBounds = quickSettingsElement.getBoundingClientRect() + + expect(realOffsetTop(quickSettingsElement)).toBeCloseTo(minimapBounds.top, 0) + expect(realOffsetLeft(quickSettingsElement)).toBeCloseTo(minimapBounds.right, 0) describe 'when the quick settings view is open', -> beforeEach -> @@ -678,11 +771,14 @@ describe 'MinimapElement', -> openQuickSettings = minimapElement.shadowRoot.querySelector('.open-minimap-quick-settings') mousedown(openQuickSettings) - quickSettingsView = workspaceElement.querySelector('.minimap-quick-settings') + quickSettingsElement = workspaceElement.querySelector('minimap-quick-settings') + + it 'sets the on right button active', -> + expect(quickSettingsElement.querySelector('.btn.selected:last-child')).toExist() describe 'clicking on the code highlight item', -> beforeEach -> - item = quickSettingsView.querySelector('li:last-child') + item = quickSettingsElement.querySelector('li:last-child') mousedown(item) it 'toggles the code highlights on the minimap element', -> @@ -691,22 +787,58 @@ describe 'MinimapElement', -> it 'requests an update', -> expect(minimapElement.frameRequested).toBeTruthy() + describe 'clicking on the on left button', -> + beforeEach -> + item = quickSettingsElement.querySelector('.btn:first-child') + mousedown(item) + + it 'toggles the displayMinimapOnLeft setting', -> + expect(atom.config.get('minimap.displayMinimapOnLeft')).toBeTruthy() + + it 'changes the buttons activation state', -> + expect(quickSettingsElement.querySelector('.btn.selected:last-child')).not.toExist() + expect(quickSettingsElement.querySelector('.btn.selected:first-child')).toExist() + + describe 'core:move-left', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:move-left' + + it 'toggles the displayMinimapOnLeft setting', -> + expect(atom.config.get('minimap.displayMinimapOnLeft')).toBeTruthy() + + it 'changes the buttons activation state', -> + expect(quickSettingsElement.querySelector('.btn.selected:last-child')).not.toExist() + expect(quickSettingsElement.querySelector('.btn.selected:first-child')).toExist() + + describe 'core:move-right when the minimap is on the right', -> + beforeEach -> + atom.config.set('minimap.displayMinimapOnLeft', true) + atom.commands.dispatch quickSettingsElement, 'core:move-right' + + it 'toggles the displayMinimapOnLeft setting', -> + expect(atom.config.get('minimap.displayMinimapOnLeft')).toBeFalsy() + + it 'changes the buttons activation state', -> + expect(quickSettingsElement.querySelector('.btn.selected:first-child')).not.toExist() + expect(quickSettingsElement.querySelector('.btn.selected:last-child')).toExist() + + describe 'clicking on the open settings button again', -> beforeEach -> mousedown(openQuickSettings) it 'closes the quick settings view', -> - expect(workspaceElement.querySelector('.minimap-quick-settings')).not.toExist() + expect(workspaceElement.querySelector('minimap-quick-settings')).not.toExist() it 'removes the view from the element', -> - expect(minimapElement.quickSettingsView).toBeNull() + expect(minimapElement.quickSettingsElement).toBeNull() describe 'when an external event destroys the view', -> beforeEach -> - minimapElement.quickSettingsView.destroy() + minimapElement.quickSettingsElement.destroy() it 'removes the view reference from the element', -> - expect(minimapElement.quickSettingsView).toBeNull() + expect(minimapElement.quickSettingsElement).toBeNull() describe 'then disabling it', -> beforeEach -> @@ -714,3 +846,104 @@ describe 'MinimapElement', -> it 'removes the div', -> expect(minimapElement.shadowRoot.querySelector('.open-minimap-quick-settings')).not.toExist() + + describe 'with plugins registered in the package', -> + [minimapPackage, pluginA, pluginB] = [] + beforeEach -> + waitsForPromise -> + atom.packages.activatePackage('minimap').then (pkg) -> + minimapPackage = pkg.mainModule + + runs -> + class Plugin + active: false + activatePlugin: -> @active = true + deactivatePlugin: -> @active = false + isActive: -> @active + + pluginA = new Plugin + pluginB = new Plugin + + minimapPackage.registerPlugin('dummyA', pluginA) + minimapPackage.registerPlugin('dummyB', pluginB) + + workspaceElement = atom.views.getView(atom.workspace) + jasmineContent.appendChild(workspaceElement) + + openQuickSettings = minimapElement.shadowRoot.querySelector('.open-minimap-quick-settings') + mousedown(openQuickSettings) + + quickSettingsElement = workspaceElement.querySelector('minimap-quick-settings') + + it 'creates one list item for each registered plugin', -> + expect(quickSettingsElement.querySelectorAll('li').length).toEqual(4) + + it 'selects the first item of the list', -> + expect(quickSettingsElement.querySelector('li.selected:first-child')).toExist() + + describe 'core:confirm', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:confirm' + + it 'disable the plugin of the selected item', -> + expect(pluginA.isActive()).toBeFalsy() + + describe 'triggered a second time', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:confirm' + + it 'enable the plugin of the selected item', -> + expect(pluginA.isActive()).toBeTruthy() + + describe 'on the code highlight item', -> + [initial] = [] + beforeEach -> + initial = minimapElement.displayCodeHighlights + atom.commands.dispatch quickSettingsElement, 'core:move-down' + atom.commands.dispatch quickSettingsElement, 'core:move-down' + atom.commands.dispatch quickSettingsElement, 'core:confirm' + + it 'toggles the code highlights on the minimap element', -> + expect(minimapElement.displayCodeHighlights).toEqual(not initial) + + describe 'core:move-down', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:move-down' + + it 'selects the second item', -> + expect(quickSettingsElement.querySelector('li.selected:nth-child(2)')).toExist() + + describe 'reaching a separator', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:move-down' + + it 'moves past the separator', -> + expect(quickSettingsElement.querySelector('li.selected:last-child')).toExist() + + describe 'then core:move-up', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:move-up' + + it 'selects again the first item of the list', -> + expect(quickSettingsElement.querySelector('li.selected:first-child')).toExist() + + describe 'core:move-up', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:move-up' + + it 'selects the last item', -> + expect(quickSettingsElement.querySelector('li.selected:last-child')).toExist() + + describe 'reaching a separator', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:move-up' + + it 'moves past the separator', -> + expect(quickSettingsElement.querySelector('li.selected:nth-child(2)')).toExist() + + describe 'then core:move-down', -> + beforeEach -> + atom.commands.dispatch quickSettingsElement, 'core:move-down' + + it 'selects again the first item of the list', -> + expect(quickSettingsElement.querySelector('li.selected:first-child')).toExist() diff --git a/styles/minimap.less b/styles/minimap.less index e46582cc..a993f619 100644 --- a/styles/minimap.less +++ b/styles/minimap.less @@ -4,14 +4,6 @@ atom-notifications:empty { pointer-events: none } -// ## ## ## -// ## ## ## ## -// ## ## ## ## -// ## ## ## ## -// ## ## ######### -// ## ## ## -// ### ## - atom-text-editor::shadow, atom-text-editor { atom-text-editor-minimap { display: block; @@ -75,8 +67,6 @@ atom-text-editor::shadow, atom-text-editor { font-family: 'Octicons Regular'; } } - - } &:hover::shadow .open-minimap-quick-settings { opacity: 1; @@ -85,37 +75,7 @@ atom-text-editor::shadow, atom-text-editor { } } -// ## ## ####### -// ## ## ## ## -// ## ## ## -// ## ## ####### -// ## ## ## -// ## ## ## ## -// ### ####### - -atom-pane.with-minimap { - atom-text-editor, :host, atom-text-editor, atom-text-editor::shadow { - - &:not(.minimap-editor):not(.mini) { - padding-right: 10%; - } - .vertical-scrollbar { - right: 0 !important; - } - } -} - -.zen atom-pane.with-minimap { - atom-text-editor, atom-text-editor::shadow { - padding-right: 0 !important; - - .vertical-scrollbar { - right: 0 !important; - } - } -} - -.minimap-quick-settings { +minimap-quick-settings { position: absolute !important; .hidden-input { @@ -142,125 +102,12 @@ atom-pane.with-minimap { background: @background-color-highlight; } } -} - -.open-minimap-quick-settings { - opacity: 0; - position: absolute; - z-index: 1; - cursor: pointer; - color: @text-color; - font-size: 20px; - text-align: center; - line-height: 24px; - width: 24px; - height: 24px; - display: block; - right: 4px; - transition: opacity 0.4s; - - &:before { - content: '\f02f'; - font-family: 'Octicons Regular'; - } -} - -.minimap:hover .open-minimap-quick-settings { - opacity: 1; - transition: opacity 0.1s; -} - -.minimap-on-left { - atom-pane.with-minimap { - atom-text-editor, :host, atom-text-editor, atom-text-editor::shadow { - &:not(.minimap-editor):not(.mini) { - padding-right: 0; - padding-left: 10%; - } - } - - .vertical-scrollbar { - right: 0; - } - - .minimap { - left: 0 !important; - } - } -} - -.minimap { - position: absolute; - right: 0; - bottom: 0; - left: initial !important; - - width: 10%; - overflow: hidden; - box-sizing: border-box; - - -webkit-user-select: none; - - atom-text-editor { - padding: 0 !important; - background: transparent; - } - - .minimap-scroller { - position: absolute; - display: none; - right: 0; - width: 2px; - min-height: 2px; - z-index: 10; - background: @background-color-selected; - - &.visible { - display: block; - } - } - .minimap-wrapper { - position: absolute; + .btn-group { width: 100%; - height: 100%; - -webkit-transform-origin: 0 0; - .accelerated; - } - - .minimap-underlayer, - .minimap-overlayer { - -webkit-transform-origin: 0 0; - } - .minimap-visible-area { - position: absolute; - width: 100%; - background-color: #888; - opacity: .2; - cursor: -webkit-grab; - } - .minimap-visible-area:active{ - cursor: -webkit-grabbing; - } - - .minimap-editor { - position: absolute; - cursor: default; - height: 100%; - width: 100%; - } - - .minimap-canvas { - width: 100%; - height: 100%; - } - - // Mixins - .accelerated { - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - -webkit-transform: translateZ(0); - transform: translateZ(0); + .btn { + width: 50%; + } } }