From 9f2771aff667d2fb1204a17822d5112f76c7f7b7 Mon Sep 17 00:00:00 2001 From: "mr. m" <91018726+mr-cheffy@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:28:23 +0200 Subject: [PATCH] gh-15005: Improve feel of theme colors (gh-15004) --- prefs/firefox/browser.yaml | 3 ++ prefs/zen/workspaces.yaml | 2 +- src/zen/media/ZenMediaController.mjs | 70 ++++++++++++++----------- src/zen/media/zen-media-controls.css | 3 ++ src/zen/spaces/ZenGradientGenerator.mjs | 32 ++++++----- src/zen/spaces/zen-workspaces.css | 2 +- src/zen/tests/media/head.js | 2 +- 7 files changed, 69 insertions(+), 45 deletions(-) diff --git a/prefs/firefox/browser.yaml b/prefs/firefox/browser.yaml index 3afce41ea..cd66abc63 100644 --- a/prefs/firefox/browser.yaml +++ b/prefs/firefox/browser.yaml @@ -106,3 +106,6 @@ - name: browser.referrals.enabled value: false locked: true + +- name: browser.shell.customIcon.enabled + value: false # For now? diff --git a/prefs/zen/workspaces.yaml b/prefs/zen/workspaces.yaml index dfa8b7d6c..156672855 100644 --- a/prefs/zen/workspaces.yaml +++ b/prefs/zen/workspaces.yaml @@ -21,7 +21,7 @@ value: 100 - name: zen.workspaces.switch-animation-duration - value: 200 + value: 250 - name: zen.workspaces.wrap-around-navigation value: true diff --git a/src/zen/media/ZenMediaController.mjs b/src/zen/media/ZenMediaController.mjs index dea2cd493..eacee1cf1 100644 --- a/src/zen/media/ZenMediaController.mjs +++ b/src/zen/media/ZenMediaController.mjs @@ -494,39 +494,49 @@ class nsZenMediaController { window.addEventListener("TabClose", onTabDiscardedOrClosed); window.addEventListener("TabBrowserDiscarded", onTabDiscardedOrClosed); - window.addEventListener("DOMAudioPlaybackStarted", event => { - const browser = event.target; - // The card is created right away (while the controller is fresh) - // but only shown if the media is still playing a moment later, so - // short sounds (e.g. notification pings) never reveal it. - setTimeout(() => { - const card = this.#cardForBrowser(browser); - if (!card || card.isSharing) { - return; - } - if (card.controller.isPlaying) { - card.refreshVisibility(); - } else if (card.element.hidden) { - // The sound was over before ever being shown (e.g. a - // notification ping): drop the card instead of keeping a ghost - // around. - card.destroy(); - } - }, 1000); - - this.activateMediaControls( - browser.browsingContext.mediaController, - browser - ); - }); - - window.addEventListener("DOMAudioPlaybackStopped", () => { - for (const card of this.#cards.values()) { - card.updateMuteState(); + window.addEventListener("TabAttrModified", event => { + if (!event.detail.changed.includes("soundplaying")) { + return; } + const tab = event.target; + if (!tab.hasAttribute("soundplaying")) { + this.onAudioPlaybackStopped(); + return; + } + this.onAudioPlaybackStarted(tab.linkedBrowser); }); } + onAudioPlaybackStarted(browser) { + // The card is created right away (while the controller is fresh) but + // only shown if the media is still playing a moment later, so short + // sounds (e.g. notification pings) never reveal it. + setTimeout(() => { + const card = this.#cardForBrowser(browser); + if (!card || card.isSharing) { + return; + } + if (card.controller.isPlaying) { + card.refreshVisibility(); + } else if (card.element.hidden) { + // The sound was over before ever being shown (e.g. a notification + // ping): drop the card instead of keeping a ghost around. + card.destroy(); + } + }, 1000); + + this.activateMediaControls( + browser.browsingContext?.mediaController, + browser + ); + } + + onAudioPlaybackStopped() { + for (const card of this.#cards.values()) { + card.updateMuteState(); + } + } + get frontCard() { return this.#orderedCards.find(card => !card.element.hidden) ?? null; } @@ -660,7 +670,7 @@ class nsZenMediaController { this.#cards.set(key, card); if (controller) { // Media cards start hidden and appear via the delayed check in - // DOMAudioPlaybackStarted or on tab switch, like the original bar; + // onAudioPlaybackStarted or on tab switch, like the original bar; // sharing cards show right away. card.element.hidden = true; } else { diff --git a/src/zen/media/zen-media-controls.css b/src/zen/media/zen-media-controls.css index 2302155a2..a3c0c9d7e 100644 --- a/src/zen/media/zen-media-controls.css +++ b/src/zen/media/zen-media-controls.css @@ -138,6 +138,9 @@ width: 100%; padding: 4px 6px; border-radius: var(--border-radius-medium); + @media (-moz-platform: macos) { + border-radius: calc(var(--border-radius-medium) - 2px); + } outline: var(--zen-sidebar-notification-outline); outline-offset: -1.5px; box-shadow: var(--zen-sidebar-notification-shadow); diff --git a/src/zen/spaces/ZenGradientGenerator.mjs b/src/zen/spaces/ZenGradientGenerator.mjs index b73266457..20b66511f 100644 --- a/src/zen/spaces/ZenGradientGenerator.mjs +++ b/src/zen/spaces/ZenGradientGenerator.mjs @@ -542,22 +542,22 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature { const centerX = rect.width / 2; const centerY = rect.height / 2; const radius = (rect.width - padding) / 2; - const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2); + const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2) - 0.2; let angle = Math.atan2(y - centerY, x - centerX); - angle = (angle * 180) / Math.PI; // Convert to degrees + angle = (angle * 180) / Math.PI; if (angle < 0) { - angle += 360; // Normalize to [0, 360) + angle += 360; } - const normalizedDistance = 1 - Math.min(distance / radius, 1); // Normalize distance to [0, 1] - let hue = (angle / 360) * 360; // Normalize angle to [0, 360) - let saturation = normalizedDistance * 100; // stays high even in center + const normalizedDistance = 1 - Math.min(distance / radius, 1); + let hue = (angle / 360) * 360; + let saturation = normalizedDistance * 100; if (type !== EXPLICIT_LIGHTNESS_TYPE) { saturation = 90 + (1 - normalizedDistance) * 10; // Set the current lightness to how far we are from the center of the circle // For example, moving the dot outside will have higher lightness, while moving it inside will have lower lightness this.#currentLightness = Math.round((1 - normalizedDistance) * 100); } - let lightness = this.#currentLightness; // Fixed lightness for simplicity + let lightness = this.#currentLightness; if (type === EXPLICIT_BLACKWHITE_TYPE) { // We can only get grayscales from white to black saturation = 0; @@ -1259,10 +1259,10 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature { let colorToBlendOpacity; if (this.isMica) { colorToBlend = this.isDarkMode ? [0, 0, 0] : [255, 255, 255]; - colorToBlendOpacity = 0.12; + colorToBlendOpacity = 0.3; } else if (AppConstants.platform === "macosx") { colorToBlend = [255, 255, 255]; - colorToBlendOpacity = 0.18; + colorToBlendOpacity = 0.6; } if (colorToBlend) { const blendedAlpha = Math.min( @@ -1495,9 +1495,17 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature { return color; } - getToolbarColor(isDarkMode = false) { + getToolbarColor(isDarkMode = false, accentColor = undefined) { const opacity = 0.8; let baseColor = isDarkMode ? [255, 255, 255, opacity] : [0, 0, 0, opacity]; // Default toolbar + if (accentColor && this.canBeTransparent) { + // Blend a bit with the accent color to make it more visible + baseColor = this.blendColors( + accentColor, + baseColor.slice(0, 3), + 10 + ).concat(opacity); + } return baseColor; } @@ -1763,7 +1771,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature { docElement.style.setProperty("--zen-primary-color", primaryColor); // Set `--toolbox-textcolor` to have a contrast with the primary color - let textColor = this.getToolbarColor(isDarkMode); + let textColor = this.getToolbarColor(isDarkMode, dominantColor); docElement.style.setProperty( "--toolbox-textcolor", `rgba(${textColor[0]}, ${textColor[1]}, ${textColor[2]}, ${textColor[3]})` @@ -2009,7 +2017,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature { grain: theme.texture ?? 0, isDarkMode, isExplicitMode, - toolbarColor: this.getToolbarColor(isDarkMode), + toolbarColor: this.getToolbarColor(isDarkMode, dominantColor), primaryColor: this.getAccentColorForUI(dominantColor, isDarkMode), }; this.currentOpacity = previousOpacity; diff --git a/src/zen/spaces/zen-workspaces.css b/src/zen/spaces/zen-workspaces.css index d80ecb2b2..be30c7fd8 100644 --- a/src/zen/spaces/zen-workspaces.css +++ b/src/zen/spaces/zen-workspaces.css @@ -321,7 +321,7 @@ zen-workspace { position: absolute; height: 100%; overflow: hidden; - color: color-mix(in srgb, var(--toolbox-textcolor) 95%, var(--zen-primary-color)); + color: var(--toolbox-textcolor); --tab-background-color-selected: color-mix(in srgb, light-dark(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.18)) 95%, var(--zen-primary-color)) !important; --tab-box-shadow-selected: 0 0.8px 1.5px 0px light-dark(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.05)) !important; diff --git a/src/zen/tests/media/head.js b/src/zen/tests/media/head.js index de7674a1c..50dfaa974 100644 --- a/src/zen/tests/media/head.js +++ b/src/zen/tests/media/head.js @@ -30,7 +30,7 @@ async function playVideoIn(tab) { } ); // Wait for the browser to actually consider the tab "playing" — this is - // what drives DOMAudioPlaybackStarted into the media controller. + // what drives the soundplaying TabAttrModified into the media controller. await BrowserTestUtils.waitForCondition( () => tab.soundPlaying, "tab reports soundplaying"