1
0
Fork 1
mirror of https://github.com/zen-browser/desktop.git synced 2025-07-07 23:00:01 +02:00

feat: Imroved color generation, b=no-bug, c=common, workspaces

This commit is contained in:
mr. m 2025-06-22 00:36:47 +02:00
parent 0461daec3e
commit fdf6c7eb0e
No known key found for this signature in database
GPG key ID: 928E01ED4C97749F
5 changed files with 153 additions and 60 deletions

View file

@ -37,7 +37,7 @@
<hbox id="PanelUI-zen-gradient-colors-wrapper">
<vbox flex="1">
<label data-l10n-id="zen-panel-ui-gradient-generator-opacity-text"></label>
<html:input type="range" min="0" max="1" value="0.5" step="0.05" id="PanelUI-zen-gradient-generator-opacity" />
<html:input type="range" min="0.15" max="0.85" value="0.5" step="0.05" id="PanelUI-zen-gradient-generator-opacity" />
</vbox>
<vbox id="PanelUI-zen-gradient-generator-texture-wrapper">
</vbox>

View file

@ -394,7 +394,6 @@ menuseparator {
}
& button {
color-scheme: dark;
width: min-content;
padding: 0 10px !important;
min-width: unset !important;

View file

@ -92,7 +92,7 @@
--focus-outline-color: var(--button-bgcolor) !important;
--toolbarbutton-icon-fill-attention: var(--zen-primary-color) !important;
--toolbarbutton-icon-fill: light-dark(rgba(57, 57, 58, 0.6), rgba(251, 251, 254, 0.6)) !important;
--toolbarbutton-icon-fill: currentColor !important;
--button-primary-bgcolor: var(--in-content-primary-button-background) !important;
--button-primary-hover-bgcolor: var(--in-content-primary-button-background-hover) !important;
@ -121,7 +121,10 @@
--zen-button-border-radius: 5px;
--zen-button-padding: 0.6rem 1.2rem;
--zen-toolbar-element-bg: color-mix(in srgb, currentColor 10%, transparent 90%) !important;
--zen-toolbar-element-bg: light-dark(
color-mix(in srgb, currentColor 5%, transparent 5%),
color-mix(in srgb, currentColor 10%, transparent 90%)
) !important;
/* Toolbar */
--zen-toolbar-height: 38px;
@ -177,6 +180,8 @@
--zen-themed-toolbar-bg-transparent: transparent;
}
--toolbar-field-color: var(--toolbox-textcolor) !important;
&[zen-private-window='true'] {
--zen-main-browser-background: linear-gradient(
130deg,
@ -233,12 +238,20 @@
#main-window:not([chromehidden~='toolbar']) {
min-height: 495px !important;
&[zen-should-be-dark-mode='true'] #browser {
color-scheme: dark !important;
}
&[zen-should-be-dark-mode] {
& body {
background: rgba(255, 255, 255, 0.3);
}
&[zen-should-be-dark-mode='false'] #browser {
color-scheme: light !important;
&[zen-should-be-dark-mode='true'] #browser {
color-scheme: dark;
--tab-selected-color-scheme: dark;
}
&[zen-should-be-dark-mode='false'] #browser {
color-scheme: light;
--tab-selected-color-scheme: light;
}
}
}

View file

@ -315,6 +315,23 @@
return [round(r * 255), round(g * 255), round(b * 255)];
}
rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
let max = Math.max(r, g, b);
let min = Math.min(r, g, b);
let d = max - min;
let h;
if (d === 0) h = 0;
else if (max === r) h = ((g - b) / d) % 6;
else if (max === g) h = (b - r) / d + 2;
else if (max === b) h = (r - g) / d + 4;
let l = (min + max) / 2;
let s = d === 0 ? 0 : d / (1 - Math.abs(2 * l - 1));
return [h * 60, s, l];
}
hueToRgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
@ -324,27 +341,36 @@
return p;
}
calculateInitialPosition(color) {
const [r, g, b] = color.c;
const gradient = this.panel.querySelector('.zen-theme-picker-gradient');
const rect = gradient.getBoundingClientRect();
const padding = 20; // each side
calculateInitialPosition([r, g, b]) {
// This function is called before the picker is even rendered, so we hard code the dimensions
// important: If any sort of sizing is changed, make sure changes are reflected here
const padding = 20;
const rect = {
width: 308,
height: 308,
};
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const radius = (rect.width - padding) / 2;
const angle = (Math.atan2(g - centerY, r - centerX) * 180) / Math.PI; // Convert to degrees
const normalizedAngle = (angle + 360) % 360; // Normalize to [0, 360)
const normalizedDistance = Math.sqrt(((r - centerX) ** 2 + (g - centerY) ** 2) / radius ** 2); // Normalize distance to [0, 1]
const hue = (normalizedAngle / 360) * 360; // Normalize angle to [0, 360)
const saturation = normalizedDistance * 100; // Scale distance to [0, 100]
const lightness = this.#currentLightness; // Fixed lightness for simplicity
const rgbColor = this.hslToRgb(hue / 360, saturation / 100, lightness / 100);
const x = ((rgbColor[0] / 255) * radius + centerX - rect.left) / (rect.width - padding);
const y = ((rgbColor[1] / 255) * radius + centerY - rect.top) / (rect.height - padding);
return {
x: Math.min(Math.max(x, 0), 1), // Ensure x is between 0 and 1
y: Math.min(Math.max(y, 0), 1), // Ensure y is between 0 and 1
};
// Convert RGB to HSL
const [h, s, l] = this.rgbToHsl(r, g, b);
// We assume lightness is fixed; if not, add a warning or threshold check
// const targetLightness = this.#currentLightness / 100;
// if (Math.abs(l - targetLightness) > 0.01) return null;
// Convert hue (0-1) to angle in radians
const angleRad = (h * 360 * Math.PI) / 180;
// Convert saturation to radial distance
const distance = (1 - s) * radius;
// Convert polar to cartesian
const x = centerX + Math.cos(angleRad) * distance;
const y = centerY + Math.sin(angleRad) * distance;
return { x, y };
}
getColorFromPosition(x, y) {
@ -352,6 +378,8 @@
const gradient = this.panel.querySelector('.zen-theme-picker-gradient');
const rect = gradient.getBoundingClientRect();
const padding = 20; // each side
rect.width += padding * 2;
rect.height += padding * 2;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const radius = (rect.width - padding) / 2;
@ -387,13 +415,13 @@
dot.style.opacity = 0;
dot.style.setProperty('--zen-theme-picker-dot-color', color.c);
} else {
const { x, y } = this.calculateInitialPosition(color);
const { x, y } = this.calculateInitialPosition(color.c);
const dotPad = this.panel.querySelector('.zen-theme-picker-gradient');
dot.classList.add('zen-theme-picker-dot');
dot.style.left = `${x * 100}%`;
dot.style.top = `${y * 100}%`;
dot.style.left = `${x}px`;
dot.style.top = `${y}px`;
if (this.dots.length < 1) {
dot.classList.add('primary');
@ -930,7 +958,11 @@
}
onOpacityChange(event) {
this.currentOpacity = event.target.value;
this.currentOpacity = parseFloat(event.target.value);
// If we reached a whole number (e.g., 0.1, 0.2, etc.), send a haptic feedback
if (Math.round(this.currentOpacity % 0.1) === 0) {
Services.zen.playHapticFeedback();
}
this.updateCurrentWorkspace();
}
@ -945,7 +977,7 @@
if (color.isCustom) {
return color.c;
}
const opacity = Math.min(0.9, Math.max(0.1, this.currentOpacity));
const opacity = this.currentOpacity;
if (forToolbar) {
const toolbarBg = this.getToolbarModifiedBase();
return `color-mix(in srgb, rgb(${color.c[0]}, ${color.c[1]}, ${color.c[2]}) ${opacity * 100}%, ${toolbarBg} ${(1 - opacity) * 100}%)`;
@ -953,6 +985,52 @@
return `rgba(${color.c[0]}, ${color.c[1]}, ${color.c[2]}, ${opacity})`;
}
luminance([r, g, b]) {
const a = [r, g, b].map((v) => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
contrastRatio(rgb1, rgb2) {
const lum1 = this.luminance(rgb1);
const lum2 = this.luminance(rgb2);
const brightest = Math.max(lum1, lum2);
const darkest = Math.min(lum1, lum2);
return (brightest + 0.05) / (darkest + 0.05);
}
blendColors(rgb1, rgb2, percentage) {
const p = percentage / 100;
return [
Math.round(rgb1[0] * p + rgb2[0] * (1 - p)),
Math.round(rgb1[1] * p + rgb2[1] * (1 - p)),
Math.round(rgb1[2] * p + rgb2[2] * (1 - p)),
];
}
findOptimalBlend(dominantColor, blendTarget, minContrast = 4.5) {
let low = 0;
let high = 100;
let bestMatch = null;
for (let i = 0; i < 10; i++) {
const mid = (low + high) / 2;
const blended = this.blendColors(dominantColor, blendTarget, mid);
const contrast = this.contrastRatio(blended, blendTarget);
if (contrast >= minContrast) {
bestMatch = blended;
high = mid;
} else {
low = mid;
}
}
return bestMatch || this.blendColors(dominantColor, blendTarget, 10); // fallback
}
getGradient(colors, forToolbar = false) {
const themedColors = this.themedColors(colors);
this.#useAlgo = themedColors[0]?.algorithm ?? '';
@ -966,8 +1044,8 @@
return this.getSingleRGBColor(themedColors[0], forToolbar);
} else if (themedColors.length === 2) {
return [
`linear-gradient(${this.currentRotation}deg, ${this.getSingleRGBColor(themedColors[0], forToolbar)} -20%, transparent 100%)`,
`linear-gradient(${this.currentRotation + 180}deg, ${this.getSingleRGBColor(themedColors[1], forToolbar)} -20%, transparent 100%)`,
`linear-gradient(${this.currentRotation}deg, ${this.getSingleRGBColor(themedColors[0], forToolbar)} -30%, transparent 100%)`,
`linear-gradient(${this.currentRotation + 180}deg, ${this.getSingleRGBColor(themedColors[1], forToolbar)} -30%, transparent 100%)`,
].join(', ');
} else {
let color1 = this.getSingleRGBColor(themedColors[2], forToolbar);
@ -982,23 +1060,7 @@
}
shouldBeDarkMode(accentColor) {
const luminance = (r, g, b) => {
// Convert RGB from [0, 255] to [0, 1]
r /= 255;
g /= 255;
b /= 255;
// Apply sRGB companding
const toLinear = (c) => {
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
};
r = toLinear(r);
g = toLinear(g);
b = toLinear(b);
// Calculate relative luminance
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
const [r, g, b] = accentColor;
const lum = luminance(r, g, b);
const lum = this.luminance(accentColor);
// Return true if background is dark enough that white text is preferred
return lum < 0.5;
}
@ -1139,6 +1201,16 @@
return color;
}
blendColors(rgb1, rgb2, percentage) {
const p = percentage / 100;
const blended = [
Math.round(rgb1[0] * p + rgb2[0] * (1 - p)),
Math.round(rgb1[1] * p + rgb2[1] * (1 - p)),
Math.round(rgb1[2] * p + rgb2[2] * (1 - p)),
];
return `rgb(${blended[0]}, ${blended[1]}, ${blended[2]})`;
}
async onWorkspaceChange(workspace, skipUpdate = false, theme = null) {
const uuid = workspace.uuid;
// Use theme from workspace object or passed theme
@ -1170,7 +1242,6 @@
}
}
const appBackground = browser.document.getElementById('zen-browser-background');
if (!skipUpdate) {
browser.document.documentElement.style.setProperty(
'--zen-main-browser-background-old',
@ -1275,7 +1346,7 @@
const rotationLine = browser.document.getElementById(
'PanelUI-zen-gradient-generator-rotation-line'
);
if (numberOfColors != 1 && numberOfColors != 3) {
if (numberOfColors > 1 && numberOfColors != 3) {
rotationDot.style.opacity = 1;
rotationLine.style.opacity = 1;
rotationDot.style.removeProperty('pointer-events');
@ -1329,14 +1400,25 @@
? dominantColor
: `rgb(${dominantColor[0]}, ${dominantColor[1]}, ${dominantColor[2]})`
);
let isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (dominantColor !== this.getNativeAccentColor()) {
browser.document.documentElement.setAttribute(
'zen-should-be-dark-mode',
browser.gZenThemePicker.shouldBeDarkMode(dominantColor)
);
isDarkMode = browser.gZenThemePicker.shouldBeDarkMode(dominantColor);
browser.document.documentElement.setAttribute('zen-should-be-dark-mode', isDarkMode);
} else {
browser.document.documentElement.removeAttribute('zen-should-be-dark-mode');
}
// Set `--toolbox-textcolor` to have a contrast with the primary color
const blendTarget = isDarkMode ? [255, 255, 255] : [0, 0, 0];
const blendedColor = this.blendColors(dominantColor, blendTarget, 15); // 15% dominantColor, 85% target
await gZenUIManager.motion.animate(
browser.document.documentElement,
{
'--toolbox-textcolor': blendedColor,
},
{
duration: 0.1,
}
);
}
if (!skipUpdate) {

View file

@ -5,7 +5,7 @@
*/
#PanelUI-zen-gradient-generator {
--panel-width: 320px;
--panel-width: 330px;
--panel-padding: 10px;
min-width: var(--panel-width);
}
@ -194,11 +194,10 @@
.zen-theme-picker-gradient {
position: relative;
border-radius: calc(var(--zen-border-radius) - 2px);
overflow: hidden;
border-radius: var(--zen-border-radius);
min-height: calc(var(--panel-width) - var(--panel-padding) * 2);
min-height: calc(var(--panel-width) - var(--panel-padding) * 2 - 2px);
margin-bottom: 20px;
background: var(--zen-toolbar-element-bg);