From 29e6012768f480cc5ce922f7a88c612ea4b5accf Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Thu, 16 Jan 2025 23:38:48 +0100 Subject: [PATCH] [tree-view] Zoom to mouse fixed --- packages/image-view/keymaps/image-view.cson | 3 + packages/image-view/lib/image-editor-view.js | 187 +++++++++++-------- packages/image-view/lib/main.js | 2 +- packages/image-view/styles/image-view.less | 8 + 4 files changed, 125 insertions(+), 75 deletions(-) diff --git a/packages/image-view/keymaps/image-view.cson b/packages/image-view/keymaps/image-view.cson index ad616eae31..de56e1827b 100644 --- a/packages/image-view/keymaps/image-view.cson +++ b/packages/image-view/keymaps/image-view.cson @@ -3,6 +3,7 @@ 'cmd-=': 'image-view:zoom-in' 'cmd--': 'image-view:zoom-out' 'cmd-_': 'image-view:zoom-out' + 'cmd-8': 'image-view:center' 'cmd-9': 'image-view:zoom-to-fit' 'cmd-0': 'image-view:reset-zoom' @@ -11,6 +12,7 @@ 'ctrl-=': 'image-view:zoom-in' 'ctrl--': 'image-view:zoom-out' 'ctrl-_': 'image-view:zoom-out' + 'ctrl-8': 'image-view:center' 'ctrl-9': 'image-view:zoom-to-fit' 'ctrl-0': 'image-view:reset-zoom' @@ -19,5 +21,6 @@ 'ctrl-=': 'image-view:zoom-in' 'ctrl--': 'image-view:zoom-out' 'ctrl-_': 'image-view:zoom-out' + 'ctrl-8': 'image-view:center' 'ctrl-9': 'image-view:zoom-to-fit' 'ctrl-0': 'image-view:reset-zoom' diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index 5c12fac635..ffb11afaf2 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -11,10 +11,14 @@ class ImageEditorView { this.disposables = new CompositeDisposable() this.imageSize = fs.statSync(this.editor.getPath()).size this.loaded = false - this.steps = [0.1, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2, 3, 4, 5, 7.5, 10] - this.zoomFactor = 1.00 + this.levels = [0.05, 0.1, 0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2, 3, 4, 5, 7.5, 10] + this.zoom = 1.00 ; this.step = null ; this.auto = false etch.initialize(this) + this.defaultBackgroundColor = atom.config.get('image-view.defaultBackgroundColor') + this.refs.imageContainer.setAttribute('background', this.defaultBackgroundColor) + + this.refs.image.style.display = 'none' this.updateImageURI() this.disposables.add(this.editor.onDidChange(() => this.updateImageURI())) @@ -24,6 +28,7 @@ class ImageEditorView { 'image-view:zoom-out': () => this.zoomOut(), 'image-view:reset-zoom': () => this.resetZoom(), 'image-view:zoom-to-fit': () => this.zoomToFit(), + 'image-view:center': () => this.centerImage(), 'core:move-up': () => this.scrollUp(), 'core:move-down': () => this.scrollDown(), 'core:move-left': () => this.scrollLeft(), @@ -34,21 +39,10 @@ class ImageEditorView { 'core:move-to-bottom': () => this.scrollToBottom() })) - this.refs.image.onload = () => { - this.refs.image.onload = null - this.originalHeight = this.refs.image.naturalHeight - this.originalWidth = this.refs.image.naturalWidth - this.loaded = true - this.refs.image.style.display = '' - this.defaultBackgroundColor = atom.config.get('image-view.defaultBackgroundColor') - this.refs.imageContainer.setAttribute('background', this.defaultBackgroundColor) - this.zoomToFit(1) - this.emitter.emit('did-load') - } - - this.disposables.add(atom.tooltips.add(this.refs.whiteTransparentBackgroundButton, {title: 'Use white transparent background'})) - this.disposables.add(atom.tooltips.add(this.refs.blackTransparentBackgroundButton, {title: 'Use black transparent background'})) - this.disposables.add(atom.tooltips.add(this.refs.transparentTransparentBackgroundButton, {title: 'Use transparent background'})) + this.disposables.add(atom.tooltips.add(this.refs.whiteTransparentBackgroundButton, { title: 'Use white transparent background' })) + this.disposables.add(atom.tooltips.add(this.refs.blackTransparentBackgroundButton, { title: 'Use black transparent background' })) + this.disposables.add(atom.tooltips.add(this.refs.transparentTransparentBackgroundButton, { title: 'Use transparent background' })) + this.disposables.add(atom.tooltips.add(this.refs.nativeBackgroundButton, { title: 'Use native background' })) const clickHandler = (event) => { event.preventDefault() @@ -62,6 +56,8 @@ class ImageEditorView { this.disposables.add(new Disposable(() => { this.refs.blackTransparentBackgroundButton.removeEventListener('click', clickHandler) })) this.refs.transparentTransparentBackgroundButton.addEventListener('click', clickHandler) this.disposables.add(new Disposable(() => { this.refs.transparentTransparentBackgroundButton.removeEventListener('click', clickHandler) })) + this.refs.nativeBackgroundButton.addEventListener('click', clickHandler) + this.disposables.add(new Disposable(() => { this.refs.nativeBackgroundButton.removeEventListener('click', clickHandler) })) const zoomInClickHandler = () => { this.zoomIn() @@ -81,26 +77,44 @@ class ImageEditorView { this.refs.resetZoomButton.addEventListener('click', resetZoomClickHandler) this.disposables.add(new Disposable(() => { this.refs.resetZoomButton.removeEventListener('click', resetZoomClickHandler) })) + const centerClickHandler = () => { + this.centerImage() + } + this.refs.centerButton.addEventListener('click', centerClickHandler) + this.disposables.add(new Disposable(() => { this.refs.centerButton.removeEventListener('click', centerClickHandler) })) + const zoomToFitClickHandler = () => { this.zoomToFit() } this.refs.zoomToFitButton.addEventListener('click', zoomToFitClickHandler) this.disposables.add(new Disposable(() => { this.refs.zoomToFitButton.removeEventListener('click', zoomToFitClickHandler) })) - const wheelHandler = (event) => { + const wheelContainerHandler = (event) => { + if (event.ctrlKey) { + event.stopPropagation() + const factor = event.wheelDeltaY>0 ? 1.2/1 : 1/1.2 + this.zoomToCenterPoint(factor*this.zoom) + } + } + this.refs.imageContainer.addEventListener('wheel', wheelContainerHandler) + this.disposables.add(new Disposable(() => { this.refs.imageContainer.removeEventListener('wheel', wheelContainerHandler) })) + + const wheelImageHandler = (event) => { if (event.ctrlKey) { - const zoomOld = this.zoomFactor - if (event.wheelDeltaY>0) { - this.zoomFactor += 0.025 - } else { - this.zoomFactor -= 0.025 - } - this.zoomFactor = Math.round(Math.max(this.zoomFactor, 0.025)/0.025)*0.025 - this.updateSize() + event.stopPropagation() + const factor = event.wheelDeltaY>0 ? 1.2/1 : 1/1.2 + this.zoomToMousePosition(factor*this.zoom, event) } } - this.refs.imageContainer.addEventListener('wheel', wheelHandler) - this.disposables.add(new Disposable(() => { this.refs.imageContainer.removeEventListener('wheel', wheelHandler) })) + this.refs.image.addEventListener('wheel', wheelImageHandler) + this.disposables.add(new Disposable(() => { this.refs.image.removeEventListener('wheel', wheelImageHandler) })) + + this.resizeObserver = new ResizeObserver(() => { + if (this.auto) { + this.zoomToFit() + } + }) + this.resizeObserver.observe(this.refs.imageContainer) } onDidLoad (callback) { @@ -112,6 +126,7 @@ class ImageEditorView { destroy() { this.disposables.dispose() this.emitter.dispose() + this.resizeObserver.disconnect() etch.destroy(this) } @@ -128,6 +143,9 @@ class ImageEditorView { ), $.a({className: 'image-controls-color-transparent', value: 'transparent', ref: 'transparentTransparentBackgroundButton'}, 'transparent' + ), + $.a({className: 'image-controls-color-native', value: 'native', ref: 'nativeBackgroundButton'}, + 'native' ) ), $.div({className: 'image-controls-group btn-group'}, @@ -142,6 +160,9 @@ class ImageEditorView { ) ), $.div({className: 'image-controls-group btn-group'}, + $.button({className: 'btn center-button', ref: 'centerButton'}, + 'Center' + ), $.button({className: 'btn zoom-to-fit-button', ref: 'zoomToFitButton'}, 'Zoom to fit' ) @@ -161,7 +182,11 @@ class ImageEditorView { this.originalHeight = this.refs.image.naturalHeight this.originalWidth = this.refs.image.naturalWidth this.imageSize = fs.statSync(this.editor.getPath()).size + this.loaded = true + this.zoomToFit(1) + this.refs.image.style.display = '' this.emitter.emit('did-update') + this.emitter.emit('did-load') } } @@ -169,65 +194,79 @@ class ImageEditorView { return this.emitter.on('did-update', callback) } - zoomOut() { - for (let i = this.steps.length-1; i >= 0; i--) { - if (this.steps[i]this.zoomFactor) { - this.zoomFactor = this.steps[i] - this.updateSize() - break - } - } + centerImage() { + this.refs.imageContainer.scrollTop = this.zoom * this.refs.image.naturalHeight / 2 - this.refs.imageContainer.offsetHeight / 2 + this.refs.imageContainer.scrollLeft = this.zoom *this.refs.image.naturalWidth / 2 - this.refs.imageContainer.offsetWidth / 2 } - resetZoom() { - if (!this.loaded || this.element.offsetHeight === 0) { - return - } - this.zoomFactor = 1 - this.updateSize() + zoomToMousePosition(zoom, event) { + this.updateSize(zoom) + this.refs.imageContainer.scrollLeft = this.step * event.offsetX - event.layerX + this.refs.imageContainer.scrollTop = this.step * event.offsetY - (event.layerY - this.refs.imageControls.offsetHeight) + } + + zoomToCenterPoint(zoom) { + const coorX = this.refs.imageContainer.scrollLeft + this.refs.imageContainer.offsetWidth / 2 + const coorY = this.refs.imageContainer.scrollTop + this.refs.imageContainer.offsetHeight / 2 + this.updateSize(zoom) + this.refs.imageContainer.scrollLeft = this.step * coorX - this.refs.imageContainer.offsetWidth / 2 + this.refs.imageContainer.scrollTop = this.step * coorY - this.refs.imageContainer.offsetHeight / 2 } - zoomToFit(zoomLimit) { + zoomToFit(limit) { if (!this.loaded || this.element.offsetHeight === 0) { return } - this.zoomFactor = Math.min( - this.refs.imageContainer.offsetWidth/this.refs.image.naturalWidth, - this.refs.imageContainer.offsetHeight/this.refs.image.naturalHeight, + let zoom = Math.min( + this.refs.imageContainer.offsetWidth / this.refs.image.naturalWidth, + this.refs.imageContainer.offsetHeight / this.refs.image.naturalHeight, ) - if (zoomLimit) { this.zoomFactor = Math.min(this.zoomFactor, zoomLimit) } - this.updateSize() + if (limit) { zoom = Math.min(zoom, limit) } + this.updateSize(zoom) + this.auto = true + this.refs.zoomToFitButton.classList.add('selected') } - updateSize() { - if (!this.loaded || this.element.offsetHeight === 0) { - return + zoomOut() { + for (let i = this.levels.length-1; i >= 0; i--) { + if (this.levels[i]this.zoom) { + this.zoomToCenterPoint(this.levels[i]) + break + } + } } - centerImage() { - this.refs.imageContainer.scrollTop = this.zoomFactor*this.refs.image.naturalHeight/2-this.refs.imageContainer.offsetHeight/2 - this.refs.imageContainer.scrollLeft = this.zoomFactor*this.refs.image.naturalWidth/2-this.refs.imageContainer.offsetWidth/2 + resetZoom() { + if (!this.loaded || this.element.offsetHeight === 0) { + return + } + this.zoomToCenterPoint(1) } changeBackground (color) { @@ -237,19 +276,19 @@ class ImageEditorView { } scrollUp() { - this.refs.imageContainer.scrollTop -= this.refs.imageContainer.offsetHeight / 20 + this.refs.imageContainer.scrollTop -= this.refs.imageContainer.offsetHeight / 10 } scrollDown() { - this.refs.imageContainer.scrollTop += this.refs.imageContainer.offsetHeight / 20 + this.refs.imageContainer.scrollTop += this.refs.imageContainer.offsetHeight / 10 } scrollLeft() { - this.refs.imageContainer.scrollLeft -= this.refs.imageContainer.offsetWidth / 20 + this.refs.imageContainer.scrollLeft -= this.refs.imageContainer.offsetWidth / 10 } scrollRight() { - this.refs.imageContainer.scrollLeft += this.refs.imageContainer.offsetWidth / 20 + this.refs.imageContainer.scrollLeft += this.refs.imageContainer.offsetWidth / 10 } pageUp() { diff --git a/packages/image-view/lib/main.js b/packages/image-view/lib/main.js index ba4819008d..0cf3103966 100644 --- a/packages/image-view/lib/main.js +++ b/packages/image-view/lib/main.js @@ -9,7 +9,7 @@ module.exports = { config: { defaultBackgroundColor: { type: 'string', - enum: ['white', 'black', 'transparent'], + enum: ['white', 'black', 'transparent', 'native'], default: 'transparent' } }, diff --git a/packages/image-view/styles/image-view.less b/packages/image-view/styles/image-view.less index 1306590ef6..860b5a885b 100644 --- a/packages/image-view/styles/image-view.less +++ b/packages/image-view/styles/image-view.less @@ -54,6 +54,10 @@ background-image: url(@transparent-background-image); } + &-color-native { + background-color: @app-background-color; + } + .btn-group { margin: @spacing; } @@ -75,6 +79,7 @@ display: block; flex: none; margin: auto; + -webkit-user-drag: none; } } @@ -87,5 +92,8 @@ background-color: black; background-image: url(@transparent-background-image); } + [background="native"] { + background-color: @app-background-color; + } }