From 89c741a6b06765b71eb1195d986641add4b024e5 Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Wed, 15 Jan 2025 06:26:59 +0100 Subject: [PATCH 01/11] [image-view] Add scroll support --- packages/image-view/lib/image-editor-view.js | 156 +++++++++---------- packages/image-view/styles/image-view.less | 29 +--- 2 files changed, 74 insertions(+), 111 deletions(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index be9a8c9a6d..adbd813c86 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -3,21 +3,17 @@ const {Emitter, CompositeDisposable, Disposable} = require('atom') const etch = require('etch') const $ = etch.dom -// View that renders the image of an {ImageEditor}. module.exports = class ImageEditorView { constructor (editor) { this.editor = editor this.emitter = new Emitter() this.disposables = new CompositeDisposable() - this.imageSize = fs.statSync(this.editor.getPath()).size this.loaded = false - this.mode = 'zoom-to-fit' - this.percentageStep = 4 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 etch.initialize(this) - this.refs.image.style.display = 'none' this.updateImageURI() this.disposables.add(this.editor.onDidChange(() => this.updateImageURI())) @@ -25,24 +21,23 @@ class ImageEditorView { 'image-view:reload': () => this.updateImageURI(), 'image-view:zoom-in': () => this.zoomIn(), 'image-view:zoom-out': () => this.zoomOut(), - 'image-view:zoom-to-fit': () => this.zoomToFit(), 'image-view:reset-zoom': () => this.resetZoom(), - 'core:move-up': () => { this.scrollUp() }, - 'core:move-down': () => { this.scrollDown() }, - 'core:page-up': () => { this.pageUp() }, - 'core:page-down': () => { this.pageDown() }, - 'core:move-to-top': () => { this.scrollToTop() }, - 'core:move-to-bottom': () => { this.scrollToBottom() } + 'image-view:zoom-to-fit': () => this.zoomToFit(), + 'core:move-up': () => this.scrollUp(), + 'core:move-down': () => this.scrollDown(), + 'core:page-up': () => this.pageUp(), + 'core:page-down': () => this.pageDown(), + 'core:move-to-top': () => this.scrollToTop(), + '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') } @@ -86,21 +81,36 @@ class ImageEditorView { } this.refs.zoomToFitButton.addEventListener('click', zoomToFitClickHandler) this.disposables.add(new Disposable(() => { this.refs.zoomToFitButton.removeEventListener('click', zoomToFitClickHandler) })) + + const wheelHandler = (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() + } + } + this.refs.imageContainer.addEventListener('wheel', wheelHandler) + this.disposables.add(new Disposable(() => { this.refs.imageContainer.removeEventListener('wheel', wheelHandler) })) } onDidLoad (callback) { return this.emitter.on('did-load', callback) } - update () {} + update() {} - destroy () { + destroy() { this.disposables.dispose() this.emitter.dispose() etch.destroy(this) } - render () { + render() { return ( $.div({className: 'image-view', tabIndex: -1}, $.div({className: 'image-controls', ref: 'imageControls'}, @@ -127,25 +137,22 @@ class ImageEditorView { ) ), $.div({className: 'image-controls-group btn-group'}, - $.button({className: 'btn zoom-to-fit-button selected', ref: 'zoomToFitButton'}, + $.button({className: 'btn zoom-to-fit-button', ref: 'zoomToFitButton'}, 'Zoom to fit' ) ) ), - $.div({className: 'image-container zoom-to-fit', ref: 'imageContainer'}, + $.div({className: 'image-container', ref: 'imageContainer'}, $.img({ref: 'image'}) ) ) ) } - updateImageURI () { + updateImageURI() { this.refs.image.src = `${this.editor.getEncodedURI()}?time=${Date.now()}` this.refs.image.onload = () => { this.refs.image.onload = null - this.originalHeight = this.refs.image.naturalHeight - this.originalWidth = this.refs.image.naturalWidth - this.imageSize = fs.statSync(this.editor.getPath()).size this.emitter.emit('did-update') } } @@ -154,111 +161,94 @@ class ImageEditorView { return this.emitter.on('did-update', callback) } - // Zooms the image out by 25%. - zoomOut () { - this.percentageStep = Math.max(0, --this.percentageStep) - this.adjustSize(this.percentageStep) + 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 + } + } } - // Zooms the image to its normal width and height. - resetZoom () { + resetZoom() { if (!this.loaded || this.element.offsetHeight === 0) { return } - - this.mode = 'reset-zoom' - this.refs.imageContainer.classList.remove('zoom-to-fit') - this.refs.zoomToFitButton.classList.remove('selected') - this.refs.image.style.width = this.originalWidth + 'px' - this.refs.image.style.height = this.originalHeight + 'px' - this.refs.resetZoomButton.textContent = '100%' - this.percentageStep = 4 + this.zoomFactor = 1 + this.updateSize() } - // Zooms to fit the image, doesn't scale beyond actual size - zoomToFit () { + zoomToFit(zoomLimit) { if (!this.loaded || this.element.offsetHeight === 0) { return } - - this.mode = 'zoom-to-fit' - this.refs.imageContainer.classList.add('zoom-to-fit') - this.refs.zoomToFitButton.classList.add('selected') - this.refs.image.style.width = '' - this.refs.image.style.height = '' - this.refs.resetZoomButton.textContent = 'Auto' - this.percentageStep = 4 + this.zoomFactor = 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() } - // Adjust the size of the image by the given multiplying factor. - // - // factor - A {Number} to multiply against the current size. - adjustSize (percentageStep) { + updateSize() { if (!this.loaded || this.element.offsetHeight === 0) { return } - if (this.mode === 'zoom-to-fit') { - this.mode = 'zoom-manual' - this.refs.imageContainer.classList.remove('zoom-to-fit') - this.refs.zoomToFitButton.classList.remove('selected') - } else if (this.mode === 'reset-zoom') { - this.mode = 'zoom-manual' - } - - const factor = this.steps[percentageStep] - const newWidth = Math.round(this.originalWidth * factor) - const newHeight = Math.round(this.originalHeight * factor) - const percent = Math.max(1, Math.round((newWidth / this.originalWidth) * 100)) - - // Switch to pixelated rendering when image is bigger than 200% - if (newWidth > (this.originalWidth * 2)) { - this.refs.image.style.imageRendering = 'pixelated' - } else { - this.refs.image.style.imageRendering = '' - } + const newWidth = Math.round(this.refs.image.naturalWidth * this.zoomFactor) + const newHeight = Math.round(this.refs.image.naturalHeight * this.zoomFactor) + const percent = Math.round((newWidth / this.refs.image.naturalWidth) * 1000)/10 this.refs.image.style.width = newWidth + 'px' this.refs.image.style.height = newHeight + 'px' this.refs.resetZoomButton.textContent = percent + '%' + + this.centerImage() + } + + 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 } - // Changes the background color of the image view. - // - // color - A {String} that gets used as class name. changeBackground (color) { if (this.loaded && this.element.offsetHeight > 0 && color) { this.refs.imageContainer.setAttribute('background', color) } } - scrollUp () { + scrollUp() { this.refs.imageContainer.scrollTop -= document.body.offsetHeight / 20 } - scrollDown () { + scrollDown() { this.refs.imageContainer.scrollTop += document.body.offsetHeight / 20 } - pageUp () { + pageUp() { this.refs.imageContainer.scrollTop -= this.element.offsetHeight } - pageDown () { + pageDown() { this.refs.imageContainer.scrollTop += this.element.offsetHeight } - scrollToTop () { + scrollToTop() { this.refs.imageContainer.scrollTop = 0 } - scrollToBottom () { + scrollToBottom() { this.refs.imageContainer.scrollTop = this.refs.imageContainer.scrollHeight } } diff --git a/packages/image-view/styles/image-view.less b/packages/image-view/styles/image-view.less index 68ca307600..1306590ef6 100644 --- a/packages/image-view/styles/image-view.less +++ b/packages/image-view/styles/image-view.less @@ -61,12 +61,6 @@ .reset-zoom-button { min-width: 5em; } - - // disabled once the button is selected - .zoom-to-fit-button.selected { - pointer-events: none; - cursor: default; - } } @@ -78,7 +72,7 @@ overflow: auto; img { - display: block !important; + display: block; flex: none; margin: auto; } @@ -94,25 +88,4 @@ background-image: url(@transparent-background-image); } - - - // Zoom to fit ------------------- - // Scales the image to fit the available space. - - .zoom-to-fit { - &.image-container { - padding: @component-padding; - - img { - flex: 1 1 0; - min-width: 0; - margin: 0; - - // Alternative: object-fit: contain; - // then it would also scale larger than its original size - object-fit: scale-down; - } - } - } - } From fea937fa8efe9f17a683fcf5b3d853d72984d78f Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Wed, 15 Jan 2025 06:29:53 +0100 Subject: [PATCH 02/11] [image-view] adjust size doesnt exists --- packages/image-view/spec/image-editor-view-spec.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/image-view/spec/image-editor-view-spec.js b/packages/image-view/spec/image-editor-view-spec.js index 7eed47934e..20d58bd3f7 100644 --- a/packages/image-view/spec/image-editor-view-spec.js +++ b/packages/image-view/spec/image-editor-view-spec.js @@ -101,13 +101,6 @@ describe('ImageEditorView', () => { }) }) - describe('.adjustSize(factor)', () => { - it('does not allow a zoom percentage lower than 10%', () => { - view.adjustSize(0) - expect(view.refs.resetZoomButton.textContent).toBe('10%') - }) - }) - describe('when special characters are used in the file name', () => { describe("when '?' exists in the file name", () => { it('is replaced with %3F', () => { From 4a110933ef4ea535309e51f53f094458a28592fa Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Wed, 15 Jan 2025 06:42:55 +0100 Subject: [PATCH 03/11] [image-view] Fix status bar --- packages/image-view/lib/image-editor-view.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index adbd813c86..3d95c72917 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -9,6 +9,7 @@ class ImageEditorView { this.editor = editor this.emitter = new Emitter() 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 @@ -33,6 +34,8 @@ class ImageEditorView { 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') @@ -153,6 +156,9 @@ class ImageEditorView { this.refs.image.src = `${this.editor.getEncodedURI()}?time=${Date.now()}` this.refs.image.onload = () => { this.refs.image.onload = null + this.originalHeight = this.refs.image.naturalHeight + this.originalWidth = this.refs.image.naturalWidth + this.imageSize = fs.statSync(this.editor.getPath()).size this.emitter.emit('did-update') } } @@ -229,11 +235,11 @@ class ImageEditorView { } scrollUp() { - this.refs.imageContainer.scrollTop -= document.body.offsetHeight / 20 + this.refs.imageContainer.scrollTop -= this.refs.image.naturalHeight / 20 } scrollDown() { - this.refs.imageContainer.scrollTop += document.body.offsetHeight / 20 + this.refs.imageContainer.scrollTop += this.refs.image.naturalHeight / 20 } pageUp() { From b53787f0cfd819d49f0a973caf01d19c2db4768a Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Wed, 15 Jan 2025 07:08:30 +0100 Subject: [PATCH 04/11] [tree-view] Scroll movement reference to imageContainer size --- packages/image-view/lib/image-editor-view.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index 3d95c72917..5c12fac635 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -26,6 +26,8 @@ class ImageEditorView { 'image-view:zoom-to-fit': () => this.zoomToFit(), 'core:move-up': () => this.scrollUp(), 'core:move-down': () => this.scrollDown(), + 'core:move-left': () => this.scrollLeft(), + 'core:move-right': () => this.scrollRight(), 'core:page-up': () => this.pageUp(), 'core:page-down': () => this.pageDown(), 'core:move-to-top': () => this.scrollToTop(), @@ -157,7 +159,7 @@ class ImageEditorView { this.refs.image.onload = () => { this.refs.image.onload = null this.originalHeight = this.refs.image.naturalHeight - this.originalWidth = this.refs.image.naturalWidth + this.originalWidth = this.refs.image.naturalWidth this.imageSize = fs.statSync(this.editor.getPath()).size this.emitter.emit('did-update') } @@ -235,11 +237,19 @@ class ImageEditorView { } scrollUp() { - this.refs.imageContainer.scrollTop -= this.refs.image.naturalHeight / 20 + this.refs.imageContainer.scrollTop -= this.refs.imageContainer.offsetHeight / 20 } scrollDown() { - this.refs.imageContainer.scrollTop += this.refs.image.naturalHeight / 20 + this.refs.imageContainer.scrollTop += this.refs.imageContainer.offsetHeight / 20 + } + + scrollLeft() { + this.refs.imageContainer.scrollLeft -= this.refs.imageContainer.offsetWidth / 20 + } + + scrollRight() { + this.refs.imageContainer.scrollLeft += this.refs.imageContainer.offsetWidth / 20 } pageUp() { From 29e6012768f480cc5ce922f7a88c612ea4b5accf Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Thu, 16 Jan 2025 23:38:48 +0100 Subject: [PATCH 05/11] [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; + } } From 7433f61be2f5e51f01f37a171fab4cd2450d548c Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Fri, 17 Jan 2025 02:57:50 +0100 Subject: [PATCH 06/11] [image-view] restore spec & fix resizeObserver --- packages/image-view/lib/image-editor-view.js | 2 +- packages/image-view/spec/image-editor-view-spec.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index ffb11afaf2..7afbb50e6a 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -111,7 +111,7 @@ class ImageEditorView { this.resizeObserver = new ResizeObserver(() => { if (this.auto) { - this.zoomToFit() + this.zoomToFit(1) } }) this.resizeObserver.observe(this.refs.imageContainer) diff --git a/packages/image-view/spec/image-editor-view-spec.js b/packages/image-view/spec/image-editor-view-spec.js index 20d58bd3f7..dd813ea529 100644 --- a/packages/image-view/spec/image-editor-view-spec.js +++ b/packages/image-view/spec/image-editor-view-spec.js @@ -101,6 +101,13 @@ describe('ImageEditorView', () => { }) }) + describe('.updateSize(zoom)', () => { + it('does not allow a zoom percentage lower than 0.1%', () => { + view.updateSize(0) + expect(view.refs.resetZoomButton.textContent).toBe('0.1%') + }) + }) + describe('when special characters are used in the file name', () => { describe("when '?' exists in the file name", () => { it('is replaced with %3F', () => { From f3032b0635c4ec204c06e9705a9453e45071863d Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Fri, 17 Jan 2025 03:09:39 +0100 Subject: [PATCH 07/11] [image-view] fix resizeObserver v2 --- packages/image-view/lib/image-editor-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index 7afbb50e6a..adfe429c06 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -111,7 +111,7 @@ class ImageEditorView { this.resizeObserver = new ResizeObserver(() => { if (this.auto) { - this.zoomToFit(1) + this.zoomToFit(typeof this.auto === "number" ? this.auto : false) } }) this.resizeObserver.observe(this.refs.imageContainer) @@ -240,7 +240,7 @@ class ImageEditorView { ) if (limit) { zoom = Math.min(zoom, limit) } this.updateSize(zoom) - this.auto = true + this.auto = limit ? limit : true this.refs.zoomToFitButton.classList.add('selected') } From 61d6ddfbc84309dcedb9caa4cf95bd08bd0edcd6 Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Fri, 17 Jan 2025 04:05:43 +0100 Subject: [PATCH 08/11] [image-view] change calculation zoomToMousePosition to more reliable --- packages/image-view/lib/image-editor-view.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index adfe429c06..bf85f8e23e 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -218,8 +218,9 @@ class ImageEditorView { 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) + const {left, top} = this.refs.imageContainer.getBoundingClientRect() + this.refs.imageContainer.scrollLeft = this.step * event.offsetX - (event.pageX - left) + this.refs.imageContainer.scrollTop = this.step * event.offsetY - (event.pageY - top) } zoomToCenterPoint(zoom) { From 6964af00f21e019f05870a4c7c7273d6bd3aab8c Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Fri, 17 Jan 2025 04:18:37 +0100 Subject: [PATCH 09/11] [image-view] move zoomToFit(1) to new function zoomTo100() --- packages/image-view/lib/image-editor-view.js | 33 +++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index bf85f8e23e..40f8d550ee 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -28,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:zoom-to-100': () => this.zoomTo100(), 'image-view:center': () => this.centerImage(), 'core:move-up': () => this.scrollUp(), 'core:move-down': () => this.scrollDown(), @@ -89,6 +90,12 @@ class ImageEditorView { this.refs.zoomToFitButton.addEventListener('click', zoomToFitClickHandler) this.disposables.add(new Disposable(() => { this.refs.zoomToFitButton.removeEventListener('click', zoomToFitClickHandler) })) + const zoomTo100ClickHandler = () => { + this.zoomTo100() + } + this.refs.zoomTo100Button.addEventListener('click', zoomTo100ClickHandler) + this.disposables.add(new Disposable(() => { this.refs.zoomTo100Button.removeEventListener('click', zoomTo100ClickHandler) })) + const wheelContainerHandler = (event) => { if (event.ctrlKey) { event.stopPropagation() @@ -110,8 +117,10 @@ class ImageEditorView { this.disposables.add(new Disposable(() => { this.refs.image.removeEventListener('wheel', wheelImageHandler) })) this.resizeObserver = new ResizeObserver(() => { - if (this.auto) { - this.zoomToFit(typeof this.auto === "number" ? this.auto : false) + if (this.auto===1) { + this.zoomTo100() + } else if (this.auto) { + this.zoomToFit() } }) this.resizeObserver.observe(this.refs.imageContainer) @@ -165,6 +174,9 @@ class ImageEditorView { ), $.button({className: 'btn zoom-to-fit-button', ref: 'zoomToFitButton'}, 'Zoom to fit' + ), + $.button({className: 'btn zoom-to-100-button', ref: 'zoomTo100Button'}, + 'Zoom to 100' ) ) ), @@ -183,7 +195,7 @@ class ImageEditorView { this.originalWidth = this.refs.image.naturalWidth this.imageSize = fs.statSync(this.editor.getPath()).size this.loaded = true - this.zoomToFit(1) + this.zoomTo100() this.refs.image.style.display = '' this.emitter.emit('did-update') this.emitter.emit('did-load') @@ -200,6 +212,7 @@ class ImageEditorView { } this.auto = false this.refs.zoomToFitButton.classList.remove('selected') + this.refs.zoomTo100Button.classList.remove('selected') const prev = this.zoom this.zoom = Math.min(Math.max(zoom, 0.001), 100) this.step = this.zoom/prev @@ -231,7 +244,7 @@ class ImageEditorView { this.refs.imageContainer.scrollTop = this.step * coorY - this.refs.imageContainer.offsetHeight / 2 } - zoomToFit(limit) { + _zoomToFit(limit) { if (!this.loaded || this.element.offsetHeight === 0) { return } @@ -241,10 +254,20 @@ class ImageEditorView { ) if (limit) { zoom = Math.min(zoom, limit) } this.updateSize(zoom) - this.auto = limit ? limit : true + } + + zoomToFit() { + this._zoomToFit() + this.auto = true this.refs.zoomToFitButton.classList.add('selected') } + zoomTo100() { + this._zoomToFit(1) + this.auto = 1 + this.refs.zoomTo100Button.classList.add('selected') + } + zoomOut() { for (let i = this.levels.length-1; i >= 0; i--) { if (this.levels[i] Date: Fri, 17 Jan 2025 04:20:55 +0100 Subject: [PATCH 10/11] [image-view] fix _zoomToFit verify --- packages/image-view/lib/image-editor-view.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index 40f8d550ee..cbce11bf21 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -244,7 +244,7 @@ class ImageEditorView { this.refs.imageContainer.scrollTop = this.step * coorY - this.refs.imageContainer.offsetHeight / 2 } - _zoomToFit(limit) { + _zoomToFit(limit, auto, element) { if (!this.loaded || this.element.offsetHeight === 0) { return } @@ -254,18 +254,16 @@ class ImageEditorView { ) if (limit) { zoom = Math.min(zoom, limit) } this.updateSize(zoom) + this.auto = auto + element.classList.add('selected') } zoomToFit() { - this._zoomToFit() - this.auto = true - this.refs.zoomToFitButton.classList.add('selected') + this._zoomToFit(false, true, this.refs.zoomToFitButton) } zoomTo100() { - this._zoomToFit(1) - this.auto = 1 - this.refs.zoomTo100Button.classList.add('selected') + this._zoomToFit(1, 1, this.refs.zoomTo100Button) } zoomOut() { From 7380c6993568f281f29cd664c77f1d36a7c4d61b Mon Sep 17 00:00:00 2001 From: Sebastian Balcerowiak Date: Wed, 22 Jan 2025 02:44:40 +0100 Subject: [PATCH 11/11] [image-view] turn off text before pic loading --- packages/image-view/lib/image-editor-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/image-view/lib/image-editor-view.js b/packages/image-view/lib/image-editor-view.js index cbce11bf21..44fcc2e8b3 100644 --- a/packages/image-view/lib/image-editor-view.js +++ b/packages/image-view/lib/image-editor-view.js @@ -162,7 +162,7 @@ class ImageEditorView { '-' ), $.button({className: 'btn reset-zoom-button', ref: 'resetZoomButton'}, - 'Auto' + '' ), $.button({className: 'btn', ref: 'zoomInButton'}, '+'