diff --git a/l10n b/l10n index 19e2af33c..83464a5d3 160000 --- a/l10n +++ b/l10n @@ -1 +1 @@ -Subproject commit 19e2af33c4d901a4edece2b95c4372b40d50a942 +Subproject commit 83464a5d3596558a8d15c7fd7f495f61726a0f05 diff --git a/src/browser/base/content/zen-styles/zen-gradient-generator.css b/src/browser/base/content/zen-styles/zen-gradient-generator.css index c9a0844ae..411523b32 100644 --- a/src/browser/base/content/zen-styles/zen-gradient-generator.css +++ b/src/browser/base/content/zen-styles/zen-gradient-generator.css @@ -97,8 +97,8 @@ #PanelUI-zen-gradient-generator-color-custom-add { position: absolute; - right: 5px; - top: 5px; + right: 0px; + top: 0px; cursor: pointer; } @@ -239,13 +239,13 @@ & .zen-theme-picker-dot { position: absolute; z-index: 2; - width: 20px; - height: 20px; + width: 28px; + height: 28px; border-radius: 50%; background: var(--zen-theme-picker-dot-color); box-shadow: 0 0 0 2px var(--zen-themed-toolbar-bg); cursor: pointer; - border: 2px solid #fff; + border: 3px solid #fff; animation: zen-theme-picker-dot-animation 0.5s; transition: transform 0.2s; transform: translate(-50%, -50%); diff --git a/src/browser/base/zen-components/ZenGradientGenerator.mjs b/src/browser/base/zen-components/ZenGradientGenerator.mjs index 59a19671a..8c3c4160b 100644 --- a/src/browser/base/zen-components/ZenGradientGenerator.mjs +++ b/src/browser/base/zen-components/ZenGradientGenerator.mjs @@ -6,8 +6,7 @@ currentOpacity = 0.5; currentRotation = 45; - - numberOfDots = 0; + dots = []; constructor() { super(); @@ -171,10 +170,13 @@ } onCustomColorKeydown(event) { - //checks for enter key for custom colors + // Check for Enter key to add custom colors if (event.key === 'Enter') { event.preventDefault(); this.addCustomColor(); + + let colorPositions = this.calculateCompliments(this.dots); + this.handleColorPositions(colorPositions); } } @@ -183,6 +185,7 @@ themePicker.style.setProperty('--zen-theme-picker-gradient-image', `url(${ZenThemePicker.GRADIENT_DISPLAY_URL})`); themePicker.addEventListener('mousemove', this.onDotMouseMove.bind(this)); themePicker.addEventListener('mouseup', this.onDotMouseUp.bind(this)); + themePicker.addEventListener('mousedown', this.onDotMouseDown.bind(this)); themePicker.addEventListener('click', this.onThemePickerClick.bind(this)); } @@ -233,67 +236,31 @@ dot.style.opacity = 0; dot.style.setProperty('--zen-theme-picker-dot-color', color.c); } else { - dot.style.setProperty('--zen-theme-picker-dot-color', `rgb(${r}, ${g}, ${b})`); const { x, y } = this.calculateInitialPosition(color); + const dotPad = this.panel.querySelector('.zen-theme-picker-gradient'); + + const dot = document.createElement('div'); + dot.classList.add('zen-theme-picker-dot'); + dot.style.left = `${x * 100}%`; dot.style.top = `${y * 100}%`; - dot.addEventListener('mousedown', this.onDotMouseDown.bind(this)); + + dotPad.appendChild(dot); + let id = this.dots.length; + + dot.style.setProperty('--zen-theme-picker-dot-color', `rgb(${r}, ${g}, ${b})`); + + this.dots.push({ + ID: id, + Element: dot, + Position: { x: parseFloat(dot.style.left), y: parseFloat(dot.style.top) }, + }); } - this.panel.querySelector('.zen-theme-picker-gradient').appendChild(dot); if (!fromWorkspace) { this.updateCurrentWorkspace(true); } } - onDotMouseDown(event) { - event.preventDefault(); - if (event.button === 2) { - return; - } - this.dragging = true; - this.draggedDot = event.target; - this.draggedDot.style.zIndex = 1; - this.draggedDot.classList.add('dragging'); - - // Store the starting position of the drag - this.dragStartPosition = { - x: event.clientX, - y: event.clientY, - }; - } - - onDotMouseMove(event) { - if (this.dragging) { - event.preventDefault(); - const rect = this.panel.querySelector('.zen-theme-picker-gradient').getBoundingClientRect(); - const padding = 90; // each side - // do NOT let the ball be draged outside of an imaginary circle. You can drag it anywhere inside the circle - // if the distance between the center of the circle and the dragged ball is bigger than the radius, then the ball - // should be placed on the edge of the circle. If it's inside the circle, then the ball just follows the mouse - - const centerX = rect.left + rect.width / 2; - const centerY = rect.top + rect.height / 2; - const radius = (rect.width - padding) / 2; - let pixelX = event.clientX; - let pixelY = event.clientY; - const distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); - if (distance > radius) { - const angle = Math.atan2(pixelY - centerY, pixelX - centerX); - pixelX = centerX + Math.cos(angle) * radius; - pixelY = centerY + Math.sin(angle) * radius; - } - - // set the location of the dot in pixels - const relativeX = pixelX - rect.left; - const relativeY = pixelY - rect.top; - this.draggedDot.style.left = `${relativeX}px`; - this.draggedDot.style.top = `${relativeY}px`; - const color = this.getColorFromPosition(relativeX, relativeY); - this.draggedDot.style.setProperty('--zen-theme-picker-dot-color', `rgb(${color[0]}, ${color[1]}, ${color[2]})`); - this.updateCurrentWorkspace(); - } - } - addColorToCustomList(color) { const listItems = window.MozXULElement.parseXULToFragment(` @@ -331,14 +298,162 @@ await this.updateCurrentWorkspace(); } + spawnDot(relativePosition, primary = false) { + const dotPad = this.panel.querySelector('.zen-theme-picker-gradient'); + + const dot = document.createElement('div'); + dot.classList.add('zen-theme-picker-dot'); + + dot.style.left = `${relativePosition.x}px`; + dot.style.top = `${relativePosition.y}px`; + + dotPad.appendChild(dot); + + let id = this.dots.length; + + if (primary === true) { + id = 0; + const existingPrimaryDot = this.dots.find((d) => d.ID === 0); + if (existingPrimaryDot) { + existingPrimaryDot.ID = this.dots.length; + } + } + + const colorFromPos = this.getColorFromPosition(relativePosition.x, relativePosition.y); + dot.style.setProperty('--zen-theme-picker-dot-color', `rgb(${colorFromPos[0]}, ${colorFromPos[1]}, ${colorFromPos[2]})`); + + this.dots.push({ + ID: id, + Element: dot, + Position: { x: parseFloat(dot.style.left), y: parseFloat(dot.style.top) }, + }); + } + + calculateCompliments(dots, dotRemoved = false) { + const colorHarmonies = [ + { type: 'complementary', angles: [180] }, + { type: 'splitComplementary', angles: [150, 210] }, + { type: 'analogous', angles: [30, 330] }, + { type: 'triadic', angles: [120, 240] }, + ]; + + function getColorHarmonyType(numDots) { + if (dotRemoved) { + return colorHarmonies.find((harmony) => harmony.angles.length === numDots - 1); + } + return colorHarmonies.find((harmony) => harmony.angles.length === numDots); + } + + // rule: if the data will be inputed into an argument it should be stored as an object else not + function getAngleFromPosition(position, centerPosition) { + let deltaX = position.x - centerPosition.x; + let deltaY = position.y - centerPosition.y; + let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI); + return (angle + 360) % 360; + } + + function getDistanceFromCenter(position, centerPosition) { + const deltaX = position.x - centerPosition.x; + const deltaY = position.y - centerPosition.y; + return Math.sqrt(deltaX * deltaX + deltaY * deltaY); + } + + const dotPad = this.panel.querySelector('.zen-theme-picker-gradient'); + const rect = dotPad.getBoundingClientRect(); + const padding = 90; + + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + const radius = (rect.width - padding) / 2; + const centerPosition = { x: rect.width / 2, y: rect.height / 2 }; + + const harmonyAngles = getColorHarmonyType(dots.length); + if (!harmonyAngles || harmonyAngles.angles.length === 0) return []; + + let primaryDot = dots.find((dot) => dot.ID === 0); + + if (!primaryDot) return []; + + const baseAngle = getAngleFromPosition(primaryDot.Position, centerPosition); + + let distance = getDistanceFromCenter(primaryDot.Position, centerPosition); + + if (distance > radius) { + distance = radius; + } + + dots.sort((a, b) => a.ID - b.ID); + + let updatedDots = []; + harmonyAngles.angles.forEach((angleOffset, index) => { + let newAngle = (baseAngle + angleOffset) % 360; + let radian = (newAngle * Math.PI) / 180; + + let newPosition = { + x: centerPosition.x + distance * Math.cos(radian), + y: centerPosition.y + distance * Math.sin(radian), + }; + + updatedDots.push({ ID: index + 1, Position: newPosition }); + }); + + return updatedDots; + } + + handleColorPositions(colorPositions) { + colorPositions.sort((a, b) => a.ID - b.ID); + + const existingPrimaryDot = this.dots.find((d) => d.ID === 0); + + if (existingPrimaryDot) { + existingPrimaryDot.Element.style.zIndex = 999; + const colorFromPos = this.getColorFromPosition(existingPrimaryDot.Position.x, existingPrimaryDot.Position.y); + existingPrimaryDot.Element.style.setProperty( + '--zen-theme-picker-dot-color', + `rgb(${colorFromPos[0]}, ${colorFromPos[1]}, ${colorFromPos[2]})` + ); + } + colorPositions.forEach((dotPosition) => { + const existingDot = this.dots.find((dot) => dot.ID === dotPosition.ID); + + if (existingDot) { + existingDot.Position = dotPosition.Position; + existingDot.Element.style.left = `${dotPosition.Position.x}px`; + existingDot.Element.style.top = `${dotPosition.Position.y}px`; + const colorFromPos = this.getColorFromPosition(dotPosition.Position.x, dotPosition.Position.y); + existingDot.Element.style.setProperty( + '--zen-theme-picker-dot-color', + `rgb(${colorFromPos[0]}, ${colorFromPos[1]}, ${colorFromPos[2]})` + ); + + if (!this.dragging) { + gZenUIManager.motion.animate( + existingDot.Element, + { + left: `${dotPosition.Position.x}px`, + top: `${dotPosition.Position.y}px`, + }, + { + duration: 0.4, + type: 'spring', + bounce: 0.3, + } + ); + } + } else { + this.spawnDot(dotPosition.Position); + } + }); + } + onThemePickerClick(event) { event.preventDefault(); - if (event.button !== 0 || this.dragging) return; + if (event.button !== 0 || this.dragging || this.recentlyDragged) return; const gradient = this.panel.querySelector('.zen-theme-picker-gradient'); const rect = gradient.getBoundingClientRect(); - const padding = 90; // each side + const padding = 90; const centerX = rect.left + rect.width / 2; const centerY = rect.top + rect.height / 2; @@ -346,32 +461,60 @@ let pixelX = event.clientX; let pixelY = event.clientY; - // Check if the click is within the circle - const distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); - if (distance > radius) { + const clickedElement = event.target; + let clickedDot = null; + const existingPrimaryDot = this.dots.find((d) => d.ID === 0); + clickedDot = this.dots.find((dot) => dot.Element === clickedElement); + if (clickedDot) { + existingPrimaryDot.ID = clickedDot.ID; + clickedDot.ID = 0; + clickedDot.Element.style.zIndex = 999; + let colorPositions = this.calculateCompliments(this.dots, true); + this.handleColorPositions(colorPositions); return; } - const clickedElement = event.target; - const isExistingDot = clickedElement.classList.contains('zen-theme-picker-dot'); + const distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); + if (distance > radius) { + const angle = Math.atan2(pixelY - centerY, pixelX - centerX); + pixelX = centerX + Math.cos(angle) * radius; + pixelY = centerY + Math.sin(angle) * radius; + } - if (!isExistingDot && this.numberOfDots < ZenThemePicker.MAX_DOTS) { - const relativeX = event.clientX - rect.left; - const relativeY = event.clientY - rect.top; + const relativeX = pixelX - rect.left; + const relativeY = pixelY - rect.top; - const color = this.getColorFromPosition(relativeX, relativeY); - - const dot = document.createElement('div'); - dot.classList.add('zen-theme-picker-dot'); - dot.addEventListener('mousedown', this.onDotMouseDown.bind(this)); - - dot.style.left = `${relativeX}px`; - dot.style.top = `${relativeY}px`; - dot.style.setProperty('--zen-theme-picker-dot-color', `rgb(${color[0]}, ${color[1]}, ${color[2]})`); - - gradient.appendChild(dot); + if (!clickedDot && this.dots.length < 1) { + if (this.dots.length === 0) { + this.spawnDot({ x: relativeX, y: relativeY }, true); + } else { + this.spawnDot({ x: relativeX, y: relativeY }); + } this.updateCurrentWorkspace(true); + } else if (!clickedDot && existingPrimaryDot) { + existingPrimaryDot.Element.style.left = `${relativeX}px`; + existingPrimaryDot.Element.style.top = `${relativeY}px`; + existingPrimaryDot.Position = { + x: relativeX, + y: relativeY, + }; + + let colorPositions = this.calculateCompliments(this.dots, true); + this.handleColorPositions(colorPositions); + + gZenUIManager.motion.animate( + existingPrimaryDot.Element, + { + left: `${relativeX}px`, + top: `${relativeY}px`, + }, + { + duration: 0.4, + type: 'spring', + bounce: 0.3, + } + ); } } @@ -380,10 +523,12 @@ if (event.button === 2) { return; } - this.dragging = true; - this.draggedDot = event.target; - this.draggedDot.style.zIndex = 1; - this.draggedDot.classList.add('dragging'); + const draggedDot = this.dots.find((dot) => dot.Element === event.target); + if (draggedDot) { + this.dragging = true; + this.draggedDot = event.target; + this.draggedDot.classList.add('dragging'); + } // Store the starting position of the drag this.dragStartPosition = { @@ -397,9 +542,21 @@ if (!event.target.classList.contains('zen-theme-picker-dot')) { return; } + this.dots = this.dots.filter((dot) => dot.Element !== event.target); event.target.remove(); + + this.dots.sort((a, b) => a.ID - b.ID); + + // Reassign the IDs after sorting + this.dots.forEach((dot, index) => { + dot.ID = index; + }); + + console.log(this.dots); + let colorPositions = this.calculateCompliments(this.dots, true); + this.handleColorPositions(colorPositions); + this.updateCurrentWorkspace(); - this.numberOfDots--; return; } @@ -407,14 +564,55 @@ event.preventDefault(); event.stopPropagation(); this.dragging = false; - this.draggedDot.style.zIndex = 1; this.draggedDot.classList.remove('dragging'); this.draggedDot = null; this.dragStartPosition = null; // Reset the drag start position + + this.recentlyDragged = true; + setTimeout(() => { + this.recentlyDragged = false; + }, 100); return; } + } - this.numberOfDots = this.panel.querySelectorAll('.zen-theme-picker-dot').length; + onDotMouseMove(event) { + if (this.dragging) { + event.preventDefault(); + const rect = this.panel.querySelector('.zen-theme-picker-gradient').getBoundingClientRect(); + const padding = 90; // each side + // do NOT let the ball be draged outside of an imaginary circle. You can drag it anywhere inside the circle + // if the distance between the center of the circle and the dragged ball is bigger than the radius, then the ball + // should be placed on the edge of the circle. If it's inside the circle, then the ball just follows the mouse + + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + const radius = (rect.width - padding) / 2; + let pixelX = event.clientX; + let pixelY = event.clientY; + const distance = Math.sqrt((pixelX - centerX) ** 2 + (pixelY - centerY) ** 2); + if (distance > radius) { + const angle = Math.atan2(pixelY - centerY, pixelX - centerX); + pixelX = centerX + Math.cos(angle) * radius; + pixelY = centerY + Math.sin(angle) * radius; + } + + // set the location of the dot in pixels + const relativeX = pixelX - rect.left; + const relativeY = pixelY - rect.top; + + const draggedDot = this.dots.find((dot) => dot.Element === this.draggedDot); + draggedDot.Element.style.left = `${relativeX}px`; + draggedDot.Element.style.top = `${relativeY}px`; + draggedDot.Position = { + x: relativeX, + y: relativeY, + }; + let colorPositions = this.calculateCompliments(this.dots, true); + this.handleColorPositions(colorPositions); + + this.updateCurrentWorkspace(); + } } themedColors(colors) { @@ -632,8 +830,6 @@ browser.gZenThemePicker.currentRotation = workspaceTheme.rotation ?? 45; browser.gZenThemePicker.currentTexture = workspaceTheme.texture ?? 0; - browser.gZenThemePicker.numberOfDots = workspaceTheme.gradientColors.length; - browser.document.getElementById('PanelUI-zen-gradient-generator-opacity').value = browser.gZenThemePicker.currentOpacity; browser.document.getElementById('PanelUI-zen-gradient-generator-texture').value = @@ -662,6 +858,7 @@ } if (!skipUpdate) { + this.dots = []; browser.gZenThemePicker.recalculateDots(workspaceTheme.gradientColors); } }); @@ -702,7 +899,6 @@ } recalculateDots(colors) { - //THIS IS PART OF THE ISSUE for (const color of colors) { this.createDot(color, true); }