From ccaad9b677b3aee710d92a1a988c48222c53be72 Mon Sep 17 00:00:00 2001 From: barsdeveloper Date: Thu, 9 Sep 2021 18:06:49 +0200 Subject: [PATCH] selector wip --- css/ueblueprint-style.css | 56 ++++++++++++++++++++++- js/UEBlueprint.js | 52 +++++++++++++++++---- js/UEBlueprintDragScroll.js | 13 ++++-- js/UEBlueprintSelect.js | 36 ++++++++++----- ueblueprint.html | 15 ------ ueblueprint.js | 91 +++++++++++++++++++++++++++++-------- 6 files changed, 202 insertions(+), 61 deletions(-) diff --git a/css/ueblueprint-style.css b/css/ueblueprint-style.css index 93c2691..621a8b4 100644 --- a/css/ueblueprint-style.css +++ b/css/ueblueprint-style.css @@ -14,11 +14,26 @@ url('../font/roboto-regular.woff') format('woff'); } +:root { + --ueb-fron-size : 13px; + --ueb-viewport-height : 30rem; + --ueb-viewport-width : 100%; + --ueb-grid-size : 16px; + --ueb-grid-line-width : 2px; + --ueb-grid-line-color : #353535; + --ueb-grid-set : 8; + --ueb-grid-set-line-color : #161616; + --ueb-grid-axis-line-color: black; + --ueb-grid-snap : 16px; + --ueb-node-radius : 7px; +} + u-blueprint { display : block; position : relative; font-family: Roboto, Noto, Oxygen, Ubuntu, 'Open Sans', 'Helvetica Neue', sans-serif; font-size : var(--ueb-fron-size); + user-select: none; } .ueb-viewport-header { @@ -280,5 +295,44 @@ u-blueprint { left : min(var(--ueb-select-from-x) * 1px, var(--ueb-select-to-x) * 1px); width : calc(max(var(--ueb-select-from-x) - var(--ueb-select-to-x), var(--ueb-select-to-x) - var(--ueb-select-from-x)) * 1px); height : calc(max(var(--ueb-select-from-y) - var(--ueb-select-to-y), var(--ueb-select-to-y) - var(--ueb-select-from-y)) * 1px); - border : 2px dashed gray; + background-image: + /* Top */ + repeating-linear-gradient(90deg, transparent, transparent 1px, white 2px, white 7px, transparent 7px, transparent 11px), + repeating-linear-gradient(90deg, black, black 8px, transparent 9px, transparent 11px), + /* Bottom */ + repeating-linear-gradient(90deg, transparent, transparent 1px, white 2px, white 7px, transparent 7px, transparent 11px), + repeating-linear-gradient(90deg, black, black 8px, transparent 9px, transparent 11px), + /* Left */ + repeating-linear-gradient(180deg, transparent, transparent 1px, white 1px, white 7px, transparent 7px, transparent 11px), + repeating-linear-gradient(180deg, black, black 8px, transparent 9px, transparent 11px), + /* Right */ + repeating-linear-gradient(0deg, transparent, transparent 1px, white 2px, white 7px, transparent 7px, transparent 11px), + repeating-linear-gradient(0deg, black, black 8px, transparent 9px, transparent 11px); + background-size: + /* Top */ + 100% 1px, + 100% 3px, + /* Bottom */ + 100% 1px, + 100% 3px, + /* Left */ + 1px 100%, + 3px 100%, + /* Right */ + 1px 100%, + 3px 100%; + background-position: + /* Top */ + 0 1px, + 0 0, + /* Bottom */ + 0 calc(100% - 1px), + 0 100%, + /* Left */ + 1px 0, + 0 0, + /* Right */ + calc(100% - 1px) 0, + 100% 0; + background-repeat: no-repeat; } \ No newline at end of file diff --git a/js/UEBlueprint.js b/js/UEBlueprint.js index 325e983..9338603 100644 --- a/js/UEBlueprint.js +++ b/js/UEBlueprint.js @@ -49,12 +49,14 @@ export default class UEBlueprint extends HTMLElement { constructor() { super() + /** @type {Set}" */ this.nodes = new Set() this.expandGridSize = 400 this.gridElement = null this.viewportElement = null this.overlayElement = null this.selectorElement = null + this.selectorObserver = null this.dragObject = null this.selectObject = null this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0] @@ -102,6 +104,10 @@ export default class UEBlueprint extends HTMLElement { this.selectObject.unlistenDOMElement() } + getScroll() { + return [this.viewportElement.scrollLeft, this.viewportElement.scrollTop] + } + setScroll(value, smooth = false) { this.scroll = value if (!smooth) { @@ -151,10 +157,6 @@ export default class UEBlueprint extends HTMLElement { this.setScroll(finalScroll, smooth) } - getScroll() { - return [this.viewportElement.scrollLeft, this.viewportElement.scrollTop] - } - scrollCenter() { const scroll = this.getScroll() const offset = [ @@ -278,26 +280,56 @@ export default class UEBlueprint extends HTMLElement { return parseFloat(getComputedStyle(this.gridElement).getPropertyValue('--ueb-grid-scale')) } - startSelecting(x, y) { + compensateTranslation(position) { + position[0] -= this.translateValue[0] + position[1] -= this.translateValue[1] + return position + } + + /** + * Create a selection rectangle starting from the specified position + * @param {number[]} initialPosition - Selection rectangle initial position (relative to the .ueb-grid element) + */ + startSelecting(initialPosition) { if (this.selectorElement) { this.finishSelecting() } + initialPosition = this.compensateTranslation(initialPosition) this.selectorElement = this.constructor.getElement(this.selectorTemplate()) this.querySelector('[data-nodes]').appendChild(this.selectorElement) - this.selectorElement.style.setProperty('--ueb-select-from-x', x) - this.selectorElement.style.setProperty('--ueb-select-from-y', y) + this.selectorElement.style.setProperty('--ueb-select-from-x', initialPosition[0]) + this.selectorElement.style.setProperty('--ueb-select-from-y', initialPosition[1]) + this.selectorObserver = new IntersectionObserver( + (entries, observer) => { + entries.map(entry => { + /** @type {import("./UEBlueprintObject.js").default;}" */ + let target = entry.target + target.setSelected(entry.isIntersecting) + }) + }, { + threshold: [0.01], + root: this.selectorElement + }) + this.nodes.forEach(element => this.selectorObserver.observe(element)) } finishSelecting() { if (this.selectorElement) { this.selectorElement.remove() this.selectorElement = null + this.selectorObserver.disconnect() + this.selectorObserver = null } } - doSelecting(x, y) { - this.selectorElement.style.setProperty('--ueb-select-to-x', x) - this.selectorElement.style.setProperty('--ueb-select-to-y', y) + /** + * Move selection rectagle to the specified final position. The initial position was specified by startSelecting() + * @param {number[]} finalPosition - Selection rectangle final position (relative to the .ueb-grid element) + */ + doSelecting(finalPosition) { + finalPosition = this.compensateTranslation(finalPosition) + this.selectorElement.style.setProperty('--ueb-select-to-x', finalPosition[0]) + this.selectorElement.style.setProperty('--ueb-select-to-y', finalPosition[1]) } addNode(...blueprintNodes) { diff --git a/js/UEBlueprintDragScroll.js b/js/UEBlueprintDragScroll.js index dfb4480..4a6d03c 100644 --- a/js/UEBlueprintDragScroll.js +++ b/js/UEBlueprintDragScroll.js @@ -4,7 +4,8 @@ export default class UEBlueprintDragScroll extends UEBlueprintDrag { constructor(scrolledEntity, options) { super(scrolledEntity, options) this.minZoom = options?.minZoom ?? -12 - let self = this; + let self = this + this.mouseMoveHandler = function (e) { let mousePosition = self.snapToGrid(e.clientX, e.clientY) @@ -16,7 +17,8 @@ export default class UEBlueprintDragScroll extends UEBlueprintDrag { // Reassign the position of mouse self.mousePosition = mousePosition - }; + } + this.mouseWheelHandler = function (e) { e.preventDefault() let zoomLevel = self.blueprintNode.getZoom() @@ -24,10 +26,13 @@ export default class UEBlueprintDragScroll extends UEBlueprintDrag { let scaleCorrection = 1 / self.blueprintNode.getScale() const targetOffset = e.target.getBoundingClientRect() const currentTargetOffset = e.currentTarget.getBoundingClientRect() - let offset = [e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection, e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection] + let offset = [ + e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection, + e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection + ] self.blueprintNode.setZoom(zoomLevel, offset) - } + this.blueprintNode.getGridDOMElement().addEventListener('wheel', this.mouseWheelHandler, false) this.blueprintNode.getGridDOMElement().parentElement.addEventListener('wheel', e => e.preventDefault()) } diff --git a/js/UEBlueprintSelect.js b/js/UEBlueprintSelect.js index 56b11c3..3f12f02 100644 --- a/js/UEBlueprintSelect.js +++ b/js/UEBlueprintSelect.js @@ -1,14 +1,16 @@ export default class UEBlueprintSelect { constructor(blueprintNode, options) { + /** @type {import("./UEBlueprint.js").default;}" */ this.blueprintNode = blueprintNode; this.mousePosition = [0, 0]; this.clickButton = options?.clickButton ?? 0 this.exitSelectAnyButton = options?.exitSelectAnyButton ?? true - let self = this; + let self = this + this.mouseDownHandler = function (e) { switch (e.button) { case self.clickButton: - self.clicked(e.clientX, e.clientY) + self.clicked([e.offsetX, e.offsetY]) break default: if (!self.exitSelectAnyButton) { @@ -16,30 +18,42 @@ export default class UEBlueprintSelect { } break } - }; - this.mouseMoveHandler = function (e) { - self.blueprintNode.doSelecting(e.clientX, e.clientY) } + + this.mouseMoveHandler = function (e) { + e.preventDefault() + let scaleCorrection = 1 / self.blueprintNode.getScale() + const targetOffset = e.target.getBoundingClientRect() + const currentTargetOffset = e.currentTarget.getBoundingClientRect() + let offset = [ + e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection, + e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection + ] + self.blueprintNode.doSelecting(offset) + } + this.mouseUpHandler = function (e) { if (!self.exitSelectAnyButton || e.button == self.clickButton) { // Remove the handlers of `mousemove` and `mouseup` - document.removeEventListener('mousemove', self.mouseMoveHandler) + self.blueprintNode.getGridDOMElement().removeEventListener('mousemove', self.mouseMoveHandler) document.removeEventListener('mouseup', self.mouseUpHandler) } } - this.blueprintNode.addEventListener('mousedown', this.mouseDownHandler) - this.blueprintNode.addEventListener('contextmenu', e => e.preventDefault()) + + let gridElement = this.blueprintNode.getGridDOMElement() + gridElement.addEventListener('mousedown', this.mouseDownHandler) + gridElement.addEventListener('contextmenu', e => e.preventDefault()) } unlistenDOMElement() { this.blueprintNode.removeEventListener('mousedown', this.mouseDownHandler) } - clicked(x, y) { + clicked(position) { // Attach the listeners to `document` - document.addEventListener('mousemove', this.mouseMoveHandler) + this.blueprintNode.getGridDOMElement().addEventListener('mousemove', this.mouseMoveHandler) document.addEventListener('mouseup', this.mouseUpHandler) // Start selecting - this.blueprintNode.startSelecting(x, y) + this.blueprintNode.startSelecting(position) } } \ No newline at end of file diff --git a/ueblueprint.html b/ueblueprint.html index fd30c8c..461abd2 100644 --- a/ueblueprint.html +++ b/ueblueprint.html @@ -6,21 +6,6 @@ diff --git a/ueblueprint.js b/ueblueprint.js index 6c83ae7..ddb4f9c 100644 --- a/ueblueprint.js +++ b/ueblueprint.js @@ -70,6 +70,7 @@ class UEBlueprintDragScroll extends UEBlueprintDrag { super(scrolledEntity, options); this.minZoom = options?.minZoom ?? -12; let self = this; + this.mouseMoveHandler = function (e) { let mousePosition = self.snapToGrid(e.clientX, e.clientY); @@ -82,6 +83,7 @@ class UEBlueprintDragScroll extends UEBlueprintDrag { // Reassign the position of mouse self.mousePosition = mousePosition; }; + this.mouseWheelHandler = function (e) { e.preventDefault(); let zoomLevel = self.blueprintNode.getZoom(); @@ -89,10 +91,13 @@ class UEBlueprintDragScroll extends UEBlueprintDrag { let scaleCorrection = 1 / self.blueprintNode.getScale(); const targetOffset = e.target.getBoundingClientRect(); const currentTargetOffset = e.currentTarget.getBoundingClientRect(); - let offset = [e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection, e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection]; + let offset = [ + e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection, + e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection + ]; self.blueprintNode.setZoom(zoomLevel, offset); - }; + this.blueprintNode.getGridDOMElement().addEventListener('wheel', this.mouseWheelHandler, false); this.blueprintNode.getGridDOMElement().parentElement.addEventListener('wheel', e => e.preventDefault()); } @@ -101,15 +106,17 @@ class UEBlueprintDragScroll extends UEBlueprintDrag { class UEBlueprintSelect { constructor(blueprintNode, options) { + /** @type {import("./UEBlueprint.js").default;}" */ this.blueprintNode = blueprintNode; this.mousePosition = [0, 0]; this.clickButton = options?.clickButton ?? 0; this.exitSelectAnyButton = options?.exitSelectAnyButton ?? true; let self = this; + this.mouseDownHandler = function (e) { switch (e.button) { case self.clickButton: - self.clicked(e.clientX, e.clientY); + self.clicked([e.offsetX, e.offsetY]); break default: if (!self.exitSelectAnyButton) { @@ -118,30 +125,42 @@ class UEBlueprintSelect { break } }; + this.mouseMoveHandler = function (e) { - self.blueprintNode.doSelecting(e.clientX, e.clientY); + e.preventDefault(); + let scaleCorrection = 1 / self.blueprintNode.getScale(); + const targetOffset = e.target.getBoundingClientRect(); + const currentTargetOffset = e.currentTarget.getBoundingClientRect(); + let offset = [ + e.offsetX + targetOffset.x * scaleCorrection - currentTargetOffset.x * scaleCorrection, + e.offsetY + targetOffset.y * scaleCorrection - currentTargetOffset.y * scaleCorrection + ]; + self.blueprintNode.doSelecting(offset); }; + this.mouseUpHandler = function (e) { if (!self.exitSelectAnyButton || e.button == self.clickButton) { // Remove the handlers of `mousemove` and `mouseup` - document.removeEventListener('mousemove', self.mouseMoveHandler); + self.blueprintNode.getGridDOMElement().removeEventListener('mousemove', self.mouseMoveHandler); document.removeEventListener('mouseup', self.mouseUpHandler); } }; - this.blueprintNode.addEventListener('mousedown', this.mouseDownHandler); - this.blueprintNode.addEventListener('contextmenu', e => e.preventDefault()); + + let gridElement = this.blueprintNode.getGridDOMElement(); + gridElement.addEventListener('mousedown', this.mouseDownHandler); + gridElement.addEventListener('contextmenu', e => e.preventDefault()); } unlistenDOMElement() { this.blueprintNode.removeEventListener('mousedown', this.mouseDownHandler); } - clicked(x, y) { + clicked(position) { // Attach the listeners to `document` - document.addEventListener('mousemove', this.mouseMoveHandler); + this.blueprintNode.getGridDOMElement().addEventListener('mousemove', this.mouseMoveHandler); document.addEventListener('mouseup', this.mouseUpHandler); // Start selecting - this.blueprintNode.startSelecting(x, y); + this.blueprintNode.startSelecting(position); } } @@ -193,12 +212,14 @@ class UEBlueprint extends HTMLElement { constructor() { super(); + /** @type {Set}" */ this.nodes = new Set(); this.expandGridSize = 400; this.gridElement = null; this.viewportElement = null; this.overlayElement = null; this.selectorElement = null; + this.selectorObserver = null; this.dragObject = null; this.selectObject = null; this.additional = /*[2 * this.expandGridSize, 2 * this.expandGridSize]*/[0, 0]; @@ -246,6 +267,10 @@ class UEBlueprint extends HTMLElement { this.selectObject.unlistenDOMElement(); } + getScroll() { + return [this.viewportElement.scrollLeft, this.viewportElement.scrollTop] + } + setScroll(value, smooth = false) { this.scroll = value; if (!smooth) { @@ -295,10 +320,6 @@ class UEBlueprint extends HTMLElement { this.setScroll(finalScroll, smooth); } - getScroll() { - return [this.viewportElement.scrollLeft, this.viewportElement.scrollTop] - } - scrollCenter() { const scroll = this.getScroll(); const offset = [ @@ -422,26 +443,56 @@ class UEBlueprint extends HTMLElement { return parseFloat(getComputedStyle(this.gridElement).getPropertyValue('--ueb-grid-scale')) } - startSelecting(x, y) { + compensateTranslation(position) { + position[0] -= this.translateValue[0]; + position[1] -= this.translateValue[1]; + return position + } + + /** + * Create a selection rectangle starting from the specified position + * @param {number[]} initialPosition - Selection rectangle initial position (relative to the .ueb-grid element) + */ + startSelecting(initialPosition) { if (this.selectorElement) { this.finishSelecting(); } + initialPosition = this.compensateTranslation(initialPosition); this.selectorElement = this.constructor.getElement(this.selectorTemplate()); this.querySelector('[data-nodes]').appendChild(this.selectorElement); - this.selectorElement.style.setProperty('--ueb-select-from-x', x); - this.selectorElement.style.setProperty('--ueb-select-from-y', y); + this.selectorElement.style.setProperty('--ueb-select-from-x', initialPosition[0]); + this.selectorElement.style.setProperty('--ueb-select-from-y', initialPosition[1]); + this.selectorObserver = new IntersectionObserver( + (entries, observer) => { + entries.map(entry => { + /** @type {import("./UEBlueprintObject.js").default;}" */ + let target = entry.target; + target.setSelected(entry.isIntersecting); + }); + }, { + threshold: [0.01], + root: this.selectorElement + }); + this.nodes.forEach(element => this.selectorObserver.observe(element)); } finishSelecting() { if (this.selectorElement) { this.selectorElement.remove(); this.selectorElement = null; + this.selectorObserver.disconnect(); + this.selectorObserver = null; } } - doSelecting(x, y) { - this.selectorElement.style.setProperty('--ueb-select-to-x', x); - this.selectorElement.style.setProperty('--ueb-select-to-y', y); + /** + * Move selection rectagle to the specified final position. The initial position was specified by startSelecting() + * @param {number[]} finalPosition - Selection rectangle final position (relative to the .ueb-grid element) + */ + doSelecting(finalPosition) { + finalPosition = this.compensateTranslation(finalPosition); + this.selectorElement.style.setProperty('--ueb-select-to-x', finalPosition[0]); + this.selectorElement.style.setProperty('--ueb-select-to-y', finalPosition[1]); } addNode(...blueprintNodes) {