mirror of
https://github.com/zen-browser/desktop.git
synced 2026-05-20 03:51:20 +00:00
no-bug: Add secondary color dot, Rearrange buttons & Editor UI Updates (gh-13708)
Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com>
This commit is contained in:
@@ -11,6 +11,7 @@ zen-boost-edit-reset =
|
||||
zen-boost-edit-delete =
|
||||
.label = Delete Boost
|
||||
zen-boost-size = Size
|
||||
zen-boost-case = Case
|
||||
zen-boost-zap = Zap
|
||||
zen-boost-code = Code
|
||||
zen-boost-back = Back
|
||||
|
||||
@@ -1005,15 +1005,19 @@
|
||||
fill-opacity: 0.65;
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle {
|
||||
#zen-boost-case[case-mode="none"] {
|
||||
list-style-image: none;
|
||||
}
|
||||
|
||||
#zen-boost-case[case-mode="capitalize"] {
|
||||
list-style-image: url("text-title-case.svg");
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="uppercase"] {
|
||||
#zen-boost-case[case-mode="uppercase"] {
|
||||
list-style-image: url("text-uppercase.svg");
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="lowercase"] {
|
||||
#zen-boost-case[case-mode="lowercase"] {
|
||||
list-style-image: url("text-lowercase.svg");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +1,35 @@
|
||||
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
|
||||
index 4e20d7b602932621baf9082f6d28911701b7aa5b..8a59b81f0c82c94bac9d5c536a4b12f69f260dcf 100644
|
||||
index 4e20d7b602932621baf9082f6d28911701b7aa5b..d6e141b235ce60be5db86bc40578d0741b79b014 100644
|
||||
--- a/docshell/base/BrowsingContext.h
|
||||
+++ b/docshell/base/BrowsingContext.h
|
||||
@@ -265,6 +265,8 @@ struct EmbedderColorSchemes {
|
||||
@@ -265,6 +265,9 @@ struct EmbedderColorSchemes {
|
||||
FIELD(HistoryEntryCount, uint32_t) \
|
||||
FIELD(HasRestoreData, bool) \
|
||||
FIELD(SessionStoreEpoch, uint32_t) \
|
||||
+ FIELD(ZenBoostsData, nscolor) \
|
||||
+ FIELD(ZenBoostsComplementaryRotation, float) \
|
||||
+ FIELD(IsZenBoostsInverted, bool) \
|
||||
/* Whether we can execute scripts in this BrowsingContext. Has no effect \
|
||||
* unless scripts are also allowed in the parent WindowContext. */ \
|
||||
FIELD(AllowJavascript, bool) \
|
||||
@@ -680,6 +682,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
@@ -680,6 +683,11 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
|
||||
bool FullscreenAllowed() const;
|
||||
|
||||
+ auto ZenBoostsData() const { return GetZenBoostsData(); }
|
||||
+ auto ZenBoostsComplementaryRotation() const {
|
||||
+ return GetZenBoostsComplementaryRotation();
|
||||
+ }
|
||||
+ auto IsZenBoostsInverted() const { return GetIsZenBoostsInverted(); }
|
||||
float FullZoom() const { return GetFullZoom(); }
|
||||
float TextZoom() const { return GetTextZoom(); }
|
||||
|
||||
@@ -1284,6 +1288,8 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
@@ -1284,6 +1292,9 @@ class BrowsingContext : public nsILoadContext, public nsWrapperCache {
|
||||
}
|
||||
|
||||
void DidSet(FieldIndex<IDX_SessionStoreEpoch>, uint32_t aOldValue);
|
||||
+ void DidSet(FieldIndex<IDX_ZenBoostsData>, nscolor aOldValue);
|
||||
+ void DidSet(FieldIndex<IDX_ZenBoostsComplementaryRotation>, float aOldValue);
|
||||
+ void DidSet(FieldIndex<IDX_IsZenBoostsInverted>, bool aOldValue);
|
||||
|
||||
using CanSetResult = syncedcontext::CanSetResult;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
diff --git a/dom/chrome-webidl/BrowsingContext.webidl b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
index 43f28a6aad9d0dd3e3c908e8472244fc6aa32a74..60bfd83a9da2923ab3811f363320db474323e636 100644
|
||||
index 43f28a6aad9d0dd3e3c908e8472244fc6aa32a74..a6d47522f89589c1e69a8e7570c17b7e05330bb6 100644
|
||||
--- a/dom/chrome-webidl/BrowsingContext.webidl
|
||||
+++ b/dom/chrome-webidl/BrowsingContext.webidl
|
||||
@@ -179,6 +179,9 @@ interface BrowsingContext {
|
||||
@@ -179,6 +179,10 @@ interface BrowsingContext {
|
||||
|
||||
[SetterThrows] attribute float textZoom;
|
||||
|
||||
+ [SetterThrows] attribute long zenBoostsData;
|
||||
+ [SetterThrows] attribute float zenBoostsComplementaryRotation;
|
||||
+ [SetterThrows] attribute boolean isZenBoostsInverted;
|
||||
+
|
||||
// Override the dots-per-CSS-pixel scaling factor in this BrowsingContext
|
||||
|
||||
@@ -39,6 +39,7 @@ export class nsZenBoostEditor {
|
||||
|
||||
this.isMouseDown = false;
|
||||
this.wasDragging = false;
|
||||
this.dragTarget = "";
|
||||
this.mouseDownPosition = { x: 0, y: 0 };
|
||||
this.lastDotSetPos = { x: 0, y: 0 };
|
||||
this.currentBoostData = null;
|
||||
@@ -79,8 +80,11 @@ export class nsZenBoostEditor {
|
||||
.addEventListener("input", this.onColorOptionChange.bind(this));
|
||||
|
||||
this.doc
|
||||
.getElementById("zen-boost-text-case-toggle")
|
||||
.getElementById("zen-boost-case")
|
||||
.addEventListener("click", this.onBoostCasePressed.bind(this));
|
||||
this.doc
|
||||
.getElementById("zen-boost-size")
|
||||
.addEventListener("click", this.onBoostSizePressed.bind(this));
|
||||
this.doc
|
||||
.getElementById("zen-boost-zap")
|
||||
.addEventListener("click", this.onZapButtonPressed.bind(this));
|
||||
@@ -277,6 +281,11 @@ export class nsZenBoostEditor {
|
||||
"Impact",
|
||||
"Palatino Linotype",
|
||||
"Tahoma",
|
||||
"Helvetica",
|
||||
"Garamond",
|
||||
"Century Gothic",
|
||||
"Arial Black",
|
||||
"Papyrus",
|
||||
];
|
||||
return cFonts;
|
||||
}
|
||||
@@ -291,7 +300,7 @@ export class nsZenBoostEditor {
|
||||
|
||||
const fontButtonGroup = this.doc.getElementById("zen-boost-font-grid");
|
||||
const fontList = this.doc.getElementById("zen-boost-font-select");
|
||||
const buttonCount = 10;
|
||||
const buttonCount = 15;
|
||||
|
||||
for (let i = 0; i < Math.min(commonFonts.length, buttonCount); i++) {
|
||||
let font = fonts[i]; // Fallback
|
||||
@@ -561,7 +570,9 @@ ${cssSelector} {
|
||||
this.wasDragging = true;
|
||||
event.preventDefault();
|
||||
|
||||
if (event.target.id != "zen-boost-magic-theme") {
|
||||
if (this.dragTarget == "zen-boost-color-picker-dot-secondary") {
|
||||
this.setSecondaryDotPos(event.clientX, event.clientY);
|
||||
} else if (event.target.id != "zen-boost-magic-theme") {
|
||||
this.setDotPos(event.clientX, event.clientY, false);
|
||||
}
|
||||
}
|
||||
@@ -579,6 +590,7 @@ ${cssSelector} {
|
||||
|
||||
this.mouseDownPosition = { x: event.clientX, y: event.clientY };
|
||||
this.isMouseDown = true;
|
||||
this.dragTarget = event.target.id;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -600,18 +612,40 @@ ${cssSelector} {
|
||||
* (none, lower, upper) and updating the UI accordingly.
|
||||
*/
|
||||
onBoostCasePressed() {
|
||||
if (this.currentBoostData.textCaseOverride == "lowercase") {
|
||||
this.currentBoostData.textCaseOverride = "uppercase";
|
||||
} else if (this.currentBoostData.textCaseOverride == "uppercase") {
|
||||
if (this.currentBoostData.textCaseOverride == "uppercase") {
|
||||
this.currentBoostData.textCaseOverride = "lowercase";
|
||||
} else if (this.currentBoostData.textCaseOverride == "lowercase") {
|
||||
this.currentBoostData.textCaseOverride = "capitalize";
|
||||
} else if (this.currentBoostData.textCaseOverride == "capitalize") {
|
||||
this.currentBoostData.textCaseOverride = "none";
|
||||
} else {
|
||||
this.currentBoostData.textCaseOverride = "lowercase";
|
||||
this.currentBoostData.textCaseOverride = "uppercase";
|
||||
}
|
||||
|
||||
this.updateCaseButtonVisuals();
|
||||
this.updateCurrentBoost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the size toggle button press, cycling through size override options
|
||||
*/
|
||||
onBoostSizePressed() {
|
||||
if (this.currentBoostData.sizeOverride == 1) {
|
||||
this.currentBoostData.sizeOverride = 1.1;
|
||||
} else if (this.currentBoostData.sizeOverride == 1.1) {
|
||||
this.currentBoostData.sizeOverride = 1.25;
|
||||
} else if (this.currentBoostData.sizeOverride == 1.25) {
|
||||
this.currentBoostData.sizeOverride = 1.5;
|
||||
} else if (this.currentBoostData.sizeOverride == 1.5) {
|
||||
this.currentBoostData.sizeOverride = 0.9;
|
||||
} else if (this.currentBoostData.sizeOverride == 0.9) {
|
||||
this.currentBoostData.sizeOverride = 1;
|
||||
}
|
||||
|
||||
this.updateSizeButtonVisuals();
|
||||
this.updateCurrentBoost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles changes to color option sliders (contrast, brightness, saturation)
|
||||
* and updates the current boost data accordingly.
|
||||
@@ -649,6 +683,13 @@ ${cssSelector} {
|
||||
this.setDotPos(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the secondary color picker dot to the center position (default state).
|
||||
*/
|
||||
resetSecondaryDotPosition() {
|
||||
this.setSecondaryDotPos(null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles clicks on the theme picker gradient or magic theme button.
|
||||
* Updates the dot position or toggles auto-theme mode based on the click target.
|
||||
@@ -665,7 +706,7 @@ ${cssSelector} {
|
||||
this.currentBoostData.autoTheme = !this.currentBoostData.autoTheme;
|
||||
this.updateButtonToggleVisuals();
|
||||
this.updateCurrentBoost();
|
||||
} else {
|
||||
} else if (this.dragTarget != "zen-boost-color-picker-dot-secondary") {
|
||||
this.setDotPos(event.clientX, event.clientY, !this.wasDragging);
|
||||
}
|
||||
this.wasDragging = false;
|
||||
@@ -681,7 +722,10 @@ ${cssSelector} {
|
||||
*/
|
||||
setDotPos(pixelX, pixelY, animate = true) {
|
||||
const gradient = this.doc.querySelector(".zen-boost-color-picker-gradient");
|
||||
const dot = this.doc.querySelector(".zen-boost-color-picker-dot");
|
||||
const dot = this.doc.querySelector("#zen-boost-color-picker-dot-primary");
|
||||
const dotSec = this.doc.querySelector(
|
||||
"#zen-boost-color-picker-dot-secondary"
|
||||
);
|
||||
|
||||
const rect = gradient.getBoundingClientRect();
|
||||
const padding = 50;
|
||||
@@ -690,6 +734,9 @@ ${cssSelector} {
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const radius = (rect.width - padding) / 2;
|
||||
|
||||
let pixelXSec = pixelX;
|
||||
let pixelYSec = pixelY;
|
||||
|
||||
if (!animate) {
|
||||
let nDistance = Math.sqrt(
|
||||
(pixelX - this.lastDotSetPos.x) ** 2 +
|
||||
@@ -710,6 +757,8 @@ ${cssSelector} {
|
||||
if (pixelX == null || pixelY == null) {
|
||||
pixelX = centerX;
|
||||
pixelY = centerY;
|
||||
pixelXSec = centerX;
|
||||
pixelYSec = centerY;
|
||||
|
||||
this.currentBoostData.dotAngleDeg = 0;
|
||||
this.currentBoostData.dotDistance = 0;
|
||||
@@ -719,8 +768,9 @@ ${cssSelector} {
|
||||
);
|
||||
distance = Math.min(distance, radius); // Clamp distance
|
||||
|
||||
const angle = Math.atan2(pixelY - centerY, pixelX - centerX);
|
||||
// Primary dot
|
||||
|
||||
const angle = Math.atan2(pixelY - centerY, pixelX - centerX);
|
||||
pixelX = centerX + Math.cos(angle) * distance;
|
||||
pixelY = centerY + Math.sin(angle) * distance;
|
||||
|
||||
@@ -736,6 +786,15 @@ ${cssSelector} {
|
||||
// Map to 0-1 range
|
||||
this.currentBoostData.dotDistance = distance / radius;
|
||||
|
||||
// Secondary dot
|
||||
|
||||
const angleSec =
|
||||
(angle +
|
||||
(this.currentBoostData.secondaryDotAngleDegDelta * Math.PI) / 180) %
|
||||
(Math.PI * 2);
|
||||
pixelXSec = centerX + Math.cos(angleSec) * distance;
|
||||
pixelYSec = centerY + Math.sin(angleSec) * distance;
|
||||
|
||||
// Enable color boosting again
|
||||
if (!this.currentBoostData.enableColorBoost) {
|
||||
this.onToggleDisable(false);
|
||||
@@ -745,18 +804,26 @@ ${cssSelector} {
|
||||
|
||||
const relativeX = pixelX - rect.left;
|
||||
const relativeY = pixelY - rect.top;
|
||||
const relativeXSec = pixelXSec - rect.left;
|
||||
const relativeYSec = pixelYSec - rect.top;
|
||||
|
||||
// Capture normalized position of dot for restoring it correctly later
|
||||
this.currentBoostData.dotPos.x = relativeX / rect.width;
|
||||
this.currentBoostData.dotPos.y = relativeY / rect.height;
|
||||
this.currentBoostData.secondaryDotPos ||= {};
|
||||
this.currentBoostData.secondaryDotPos.x = relativeXSec / rect.width;
|
||||
this.currentBoostData.secondaryDotPos.y = relativeYSec / rect.height;
|
||||
|
||||
dot.setAttribute("animated", animate ? "true" : "false");
|
||||
dot.style.left = `${relativeX}px`;
|
||||
dot.style.top = `${relativeY}px`;
|
||||
dotSec.setAttribute("animated", animate ? "true" : "false");
|
||||
dotSec.style.left = `${relativeXSec}px`;
|
||||
dotSec.style.top = `${relativeYSec}px`;
|
||||
|
||||
this.updateButtonToggleVisuals();
|
||||
this.updateDot();
|
||||
this.updateCircleRadius(animate);
|
||||
this.updateCircleRadius();
|
||||
this.updateCurrentBoost();
|
||||
}
|
||||
|
||||
@@ -765,29 +832,209 @@ ${cssSelector} {
|
||||
* based on the current boost data's angle and distance values.
|
||||
*/
|
||||
updateDot() {
|
||||
const dot = this.doc.querySelector(".zen-boost-color-picker-dot");
|
||||
const dot = this.doc.querySelector("#zen-boost-color-picker-dot-primary");
|
||||
const dotSec = this.doc.querySelector(
|
||||
"#zen-boost-color-picker-dot-secondary"
|
||||
);
|
||||
dot.style.setProperty(
|
||||
"--zen-theme-picker-dot-color",
|
||||
`hsl(${this.currentBoostData.dotAngleDeg}deg, ${this.currentBoostData.dotDistance * 100}%, 55%)`
|
||||
);
|
||||
dotSec.style.setProperty(
|
||||
"--zen-theme-picker-dot-color",
|
||||
`hsl(${this.currentBoostData.dotAngleDeg + this.currentBoostData.secondaryDotAngleDegDelta}deg, ${this.currentBoostData.dotDistance * 100}%, 20%)`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the secondary color picker dot on the gradient and updates
|
||||
* the boost data with the corresponding angle values.
|
||||
*
|
||||
* @param {number|null} pixelX - The X coordinate in pixels.
|
||||
* @param {number|null} pixelY - The Y coordinate in pixels.
|
||||
*/
|
||||
setSecondaryDotPos(pixelX, pixelY) {
|
||||
const gradient = this.doc.querySelector(".zen-boost-color-picker-gradient");
|
||||
const dotSec = this.doc.querySelector(
|
||||
"#zen-boost-color-picker-dot-secondary"
|
||||
);
|
||||
|
||||
const rect = gradient.getBoundingClientRect();
|
||||
const padding = 50;
|
||||
|
||||
const centerX = rect.left + rect.width / 2;
|
||||
const centerY = rect.top + rect.height / 2;
|
||||
const radius = (rect.width - padding) / 2;
|
||||
|
||||
let angle = null;
|
||||
if (!pixelX || !pixelY) {
|
||||
pixelX = centerX;
|
||||
pixelY = centerY;
|
||||
angle = 32; // Default angle
|
||||
} else {
|
||||
angle = Math.atan2(pixelY - centerY, pixelX - centerX);
|
||||
pixelX =
|
||||
centerX + Math.cos(angle) * this.currentBoostData.dotDistance * radius;
|
||||
pixelY =
|
||||
centerY + Math.sin(angle) * this.currentBoostData.dotDistance * radius;
|
||||
}
|
||||
|
||||
// Rad to degree
|
||||
this.currentBoostData.secondaryDotAngleDegDelta =
|
||||
((angle * 180) / Math.PI + 100 - this.currentBoostData.dotAngleDeg) % 360;
|
||||
if (this.currentBoostData.secondaryDotAngleDegDelta < 0) {
|
||||
this.currentBoostData.secondaryDotAngleDegDelta += 360;
|
||||
}
|
||||
|
||||
const relativeX = pixelX - rect.left;
|
||||
const relativeY = pixelY - rect.top;
|
||||
|
||||
// Capture normalized position of dot for restoring it correctly later
|
||||
this.currentBoostData.secondaryDotPos.x = relativeX / rect.width;
|
||||
this.currentBoostData.secondaryDotPos.y = relativeY / rect.height;
|
||||
|
||||
dotSec.setAttribute("animated", "false");
|
||||
dotSec.style.left = `${relativeX}px`;
|
||||
dotSec.style.top = `${relativeY}px`;
|
||||
|
||||
this.updateButtonToggleVisuals();
|
||||
this.updateDot();
|
||||
this.updateCircleRadius();
|
||||
this.updateCurrentBoost();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the radius of the circle based on the dot's position.
|
||||
*
|
||||
* @param {boolean} animate - Whether to animate the radius change (default: true).
|
||||
*/
|
||||
updateCircleRadius(animate = true) {
|
||||
updateCircleRadius() {
|
||||
const gradient = this.doc.querySelector(".zen-boost-color-picker-gradient");
|
||||
const rect = gradient.getBoundingClientRect();
|
||||
const padding = 50;
|
||||
const radius = (rect.width - padding) / 2;
|
||||
const cx = rect.width / 2;
|
||||
const cy = rect.height / 2;
|
||||
|
||||
// Updating the circle size to match the distance of the point
|
||||
const circle = this.doc.querySelector(".zen-boost-color-picker-circle");
|
||||
circle.setAttribute("animated", animate ? "true" : "false");
|
||||
circle.setAttribute("animated", "false");
|
||||
circle.style.width = `${this.currentBoostData.dotDistance * radius * 2}px`;
|
||||
circle.style.height = `${this.currentBoostData.dotDistance * radius * 2}px`;
|
||||
|
||||
const dotColor = `hsl(${this.currentBoostData.dotAngleDeg}deg, ${this.currentBoostData.dotDistance * 100}%, 55%)`;
|
||||
const dotColorSec = `hsl(${this.currentBoostData.dotAngleDeg + this.currentBoostData.secondaryDotAngleDegDelta}deg, ${this.currentBoostData.dotDistance * 100}%, 20%)`;
|
||||
|
||||
this.updateArcFill(cx, cy, radius, dotColor, dotColorSec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the filled gradient arc between both color dots
|
||||
*
|
||||
* @param {number} cx - Half width of the gradient area
|
||||
* @param {number} cy - Half height of the gradient area
|
||||
* @param {number} radius - The target radius of the circle
|
||||
* @param {string} color1 - Primary css color
|
||||
* @param {string} color2 - Secondary css color
|
||||
*/
|
||||
updateArcFill(cx, cy, radius, color1, color2) {
|
||||
const svg = this.doc.querySelector(".zen-boost-color-picker-arc-svg");
|
||||
|
||||
// Create SVG if it doesn't exist
|
||||
if (!svg) {
|
||||
this.initArcSVG();
|
||||
this.updateArcFill(cx, cy, radius, color1, color2);
|
||||
return;
|
||||
}
|
||||
|
||||
const angle1 = this.currentBoostData.dotAngleDeg;
|
||||
const angle2 =
|
||||
this.currentBoostData.dotAngleDeg +
|
||||
this.currentBoostData.secondaryDotAngleDegDelta;
|
||||
const dist = this.currentBoostData.dotDistance;
|
||||
const r = dist * radius;
|
||||
const thickness = 2;
|
||||
|
||||
const toXY = (deg, ra) => {
|
||||
const rad = ((deg - 90) * Math.PI) / 180;
|
||||
return [cx + ra * Math.cos(rad), cy + ra * Math.sin(rad)];
|
||||
};
|
||||
|
||||
const [x1, y1] = toXY(angle1, r);
|
||||
const [x2, y2] = toXY(angle2, r);
|
||||
|
||||
// Gradient endpoints for matched dot positions
|
||||
const grad = svg.querySelector("#arc-gradient");
|
||||
grad.querySelector("#ag-stop1").setAttribute("stop-color", color1);
|
||||
grad.querySelector("#ag-stop2").setAttribute("stop-color", color2);
|
||||
grad.setAttribute("x1", x1);
|
||||
grad.setAttribute("y1", y1);
|
||||
grad.setAttribute("x2", x2);
|
||||
grad.setAttribute("y2", y2);
|
||||
|
||||
// Ring sector path
|
||||
const outerR = r + thickness / 2;
|
||||
const innerR = Math.max(r - thickness / 2, 1);
|
||||
const delta = (angle2 - angle1 + 360) % 360;
|
||||
const large = delta > 180 ? 1 : 0;
|
||||
const [ox1, oy1] = toXY(angle1, outerR);
|
||||
const [ox2, oy2] = toXY(angle2, outerR);
|
||||
const [ix2, iy2] = toXY(angle2, innerR);
|
||||
const [ix1, iy1] = toXY(angle1, innerR);
|
||||
|
||||
const d = `M ${ox1} ${oy1} A ${outerR} ${outerR} 0 ${large} 1 ${ox2} ${oy2} L ${ix2} ${iy2} A ${innerR} ${innerR} 0 ${large} 0 ${ix1} ${iy1} Z`;
|
||||
svg.querySelector(".arc-fill").setAttribute("d", d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the filled gradient arc between both color picker dots in form of a svg
|
||||
*/
|
||||
initArcSVG() {
|
||||
const NS = "http://www.w3.org/2000/svg";
|
||||
const container = this.doc.querySelector(
|
||||
".zen-boost-color-picker-gradient"
|
||||
);
|
||||
|
||||
if (!container.clientWidth || !container.clientHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const w = container.clientWidth;
|
||||
const h = container.clientHeight;
|
||||
|
||||
const svg = this.doc.createElementNS(NS, "svg");
|
||||
svg.classList.add("zen-boost-color-picker-arc-svg");
|
||||
svg.setAttribute("width", w);
|
||||
svg.setAttribute("height", h);
|
||||
svg.setAttribute("viewBox", `0 0 ${w} ${h}`);
|
||||
svg.style.cssText =
|
||||
"position:absolute; top:0; left:0; pointer-events:none; z-index:3;";
|
||||
|
||||
const defs = this.doc.createElementNS(NS, "defs");
|
||||
const grad = this.doc.createElementNS(NS, "linearGradient");
|
||||
grad.setAttribute("id", "arc-gradient");
|
||||
grad.setAttribute("gradientUnits", "userSpaceOnUse");
|
||||
|
||||
const stop1 = this.doc.createElementNS(NS, "stop");
|
||||
stop1.setAttribute("id", "ag-stop1");
|
||||
stop1.setAttribute("offset", "0%");
|
||||
|
||||
const stop2 = this.doc.createElementNS(NS, "stop");
|
||||
stop2.setAttribute("id", "ag-stop2");
|
||||
stop2.setAttribute("offset", "100%");
|
||||
|
||||
grad.appendChild(stop1);
|
||||
grad.appendChild(stop2);
|
||||
defs.appendChild(grad);
|
||||
svg.appendChild(defs);
|
||||
|
||||
// Arc fill path
|
||||
const arcFill = this.doc.createElementNS(NS, "path");
|
||||
arcFill.classList.add("arc-fill");
|
||||
arcFill.setAttribute("fill", "url(#arc-gradient)");
|
||||
arcFill.setAttribute("opacity", "0.65");
|
||||
svg.appendChild(arcFill);
|
||||
|
||||
container.style.position = "relative";
|
||||
container.appendChild(svg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -829,8 +1076,72 @@ ${cssSelector} {
|
||||
* text case override value (none, upper, or lower).
|
||||
*/
|
||||
updateCaseButtonVisuals() {
|
||||
const sizeValue = this.doc.getElementById("zen-boost-text-case-toggle");
|
||||
sizeValue.setAttribute("case-mode", this.currentBoostData.textCaseOverride);
|
||||
const caseButton = this.doc.getElementById("zen-boost-case");
|
||||
const caseText = this.doc.getElementById("zen-boost-case-text");
|
||||
caseButton.setAttribute(
|
||||
"case-mode",
|
||||
this.currentBoostData.textCaseOverride
|
||||
);
|
||||
|
||||
switch (this.currentBoostData.textCaseOverride) {
|
||||
case "uppercase":
|
||||
caseButton.setAttribute("mode", "orange");
|
||||
caseText.style.display = "none";
|
||||
break;
|
||||
case "lowercase":
|
||||
caseButton.setAttribute("mode", "orange-red");
|
||||
caseText.style.display = "none";
|
||||
break;
|
||||
case "capitalize":
|
||||
caseButton.setAttribute("mode", "red");
|
||||
caseText.style.display = "none";
|
||||
break;
|
||||
default:
|
||||
caseButton.setAttribute("mode", "none");
|
||||
caseText.style.display = "initial";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visual state of the text case toggle button based on the current
|
||||
* text case override value (none, upper, or lower).
|
||||
*/
|
||||
updateSizeButtonVisuals() {
|
||||
const sizeButton = this.doc.getElementById("zen-boost-size");
|
||||
const sizeText = this.doc.getElementById("zen-boost-size-text");
|
||||
const sizeValue = this.doc.getElementById("zen-boost-size-value");
|
||||
|
||||
switch (this.currentBoostData.sizeOverride) {
|
||||
case 1:
|
||||
sizeButton.setAttribute("mode", "none");
|
||||
sizeText.style.display = "initial";
|
||||
sizeValue.style.display = "none";
|
||||
break;
|
||||
case 1.1:
|
||||
sizeButton.setAttribute("mode", "orange");
|
||||
sizeText.style.display = "none";
|
||||
sizeValue.style.display = "initial";
|
||||
break;
|
||||
case 1.25:
|
||||
sizeButton.setAttribute("mode", "orange-red");
|
||||
sizeText.style.display = "none";
|
||||
sizeValue.style.display = "initial";
|
||||
break;
|
||||
case 1.5:
|
||||
sizeButton.setAttribute("mode", "red");
|
||||
sizeText.style.display = "none";
|
||||
sizeValue.style.display = "initial";
|
||||
break;
|
||||
case 0.9:
|
||||
sizeButton.setAttribute("mode", "blue");
|
||||
sizeText.style.display = "none";
|
||||
sizeValue.style.display = "initial";
|
||||
break;
|
||||
}
|
||||
sizeValue.setHTML(
|
||||
`${Math.round(this.currentBoostData.sizeOverride * 100)}%`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1199,7 +1510,7 @@ ${cssSelector} {
|
||||
Math.round(rect.top + Math.random() * rect.height),
|
||||
true
|
||||
);
|
||||
|
||||
this.currentBoostData.secondaryDotAngleDegDelta = Math.random() * 360;
|
||||
this.currentBoostData.changeWasMade = true;
|
||||
|
||||
this.updateCurrentBoost();
|
||||
@@ -1260,7 +1571,22 @@ ${cssSelector} {
|
||||
updateAllVisuals() {
|
||||
this.doc.getElementById("zen-boost-name-text").textContent =
|
||||
this.currentBoostData.boostName;
|
||||
const dot = this.doc.querySelector(".zen-boost-color-picker-dot");
|
||||
const dot = this.doc.querySelector("#zen-boost-color-picker-dot-primary");
|
||||
const dotSec = this.doc.querySelector(
|
||||
"#zen-boost-color-picker-dot-secondary"
|
||||
);
|
||||
|
||||
if (!this.currentBoostData.sizeOverride) {
|
||||
this.currentBoostData.sizeOverride = 1;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.currentBoostData.secondaryDotPos ||
|
||||
!this.currentBoostData.secondaryDotPos.x ||
|
||||
!this.currentBoostData.secondaryDotPos.y
|
||||
) {
|
||||
this.resetSecondaryDotPosition();
|
||||
}
|
||||
|
||||
if (
|
||||
this.currentBoostData.dotPos.x == null ||
|
||||
@@ -1290,16 +1616,22 @@ ${cssSelector} {
|
||||
// Convert normalized position to relative position
|
||||
const xPos = this.currentBoostData.dotPos.x * rect.width;
|
||||
const yPos = this.currentBoostData.dotPos.y * rect.height;
|
||||
const xPosSec = this.currentBoostData.secondaryDotPos.x * rect.width;
|
||||
const yPosSec = this.currentBoostData.secondaryDotPos.y * rect.height;
|
||||
|
||||
dot.setAttribute("animated", "true");
|
||||
dot.style.left = `${xPos}px`;
|
||||
dot.style.top = `${yPos}px`;
|
||||
dotSec.setAttribute("animated", "true");
|
||||
dotSec.style.left = `${xPosSec}px`;
|
||||
dotSec.style.top = `${yPosSec}px`;
|
||||
}
|
||||
|
||||
this.editorWindow._editor.setText(this.currentBoostData.customCSS || "");
|
||||
|
||||
this.updateFontButtonVisuals();
|
||||
this.updateCaseButtonVisuals();
|
||||
this.updateSizeButtonVisuals();
|
||||
this.updateColorControlSliderVisuals();
|
||||
this.updateButtonToggleVisuals();
|
||||
this.updateDot();
|
||||
|
||||
@@ -118,6 +118,9 @@ class nsZenBoostsManager {
|
||||
dotPos: { x: null, y: null },
|
||||
dotDistance: 0,
|
||||
|
||||
secondaryDotAngleDegDelta: 32,
|
||||
secondaryDotPos: { x: null, y: null },
|
||||
|
||||
brightness: 0.5,
|
||||
saturation: 0.5,
|
||||
contrast: 0.75,
|
||||
@@ -131,6 +134,7 @@ class nsZenBoostsManager {
|
||||
autoTheme: false,
|
||||
|
||||
textCaseOverride: "none",
|
||||
sizeOverride: 1,
|
||||
|
||||
zapSelectors: [],
|
||||
customCSS: "",
|
||||
|
||||
@@ -96,6 +96,25 @@ export class ZenBoostsChild extends JSWindowActorChild {
|
||||
return ((contrast << 24) | (b << 16) | (g << 8) | r) >>> 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the packed primary accent NSColor for a boost, with the boost
|
||||
* contrast stored in the alpha byte. The complementary accent is not a
|
||||
* separate color: the backend derives it by rotating this accent's hue by
|
||||
* the boost's `secondaryDotAngleDegDelta`, which is sent separately.
|
||||
*
|
||||
* @param {number} hueDeg - Primary hue in degrees.
|
||||
* @param {number} sat - Saturation in [0, 1].
|
||||
* @param {number} light - Lightness in [0, 1].
|
||||
* @param {object} boostData - The current boost data.
|
||||
* @returns {number} The packed primary NSColor.
|
||||
*/
|
||||
#buildBoostColor(hueDeg, sat, light, boostData) {
|
||||
return this.#rgbToNSColor(
|
||||
this.#hslToRgb(hueDeg / 360, sat, light),
|
||||
(1 - boostData.contrast) * 255
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* From ZenGradientGenerator.mjs
|
||||
* Converts an HSL color value to RGB. Conversion formula
|
||||
@@ -345,6 +364,7 @@ export class ZenBoostsChild extends JSWindowActorChild {
|
||||
|
||||
browsingContext.isZenBoostsInverted = boostData.smartInvert;
|
||||
if (boostData.enableColorBoost) {
|
||||
let primaryColor;
|
||||
if (boostData.autoTheme) {
|
||||
// Workspace color is converted to the HSL color space
|
||||
let primaryGradientColor = boost.workspaceGradient[0]?.c ?? [
|
||||
@@ -362,40 +382,34 @@ export class ZenBoostsChild extends JSWindowActorChild {
|
||||
|
||||
// Workspace color is converted back to rgb
|
||||
// using the same modifiers as the color above
|
||||
primaryGradientColor = this.#hslToRgb(
|
||||
primaryGradientColor[0] / 360,
|
||||
primaryColor = this.#buildBoostColor(
|
||||
primaryGradientColor[0],
|
||||
primaryGradientColor[1] * (1 - boostData.saturation),
|
||||
0.1 + primaryGradientColor[2] * 0.9 * boostData.brightness
|
||||
0.1 + primaryGradientColor[2] * 0.9 * boostData.brightness,
|
||||
boostData
|
||||
);
|
||||
|
||||
const rgbColor = primaryGradientColor;
|
||||
const nsColor = this.#rgbToNSColor(
|
||||
rgbColor,
|
||||
(1 - boostData.contrast) * 255
|
||||
);
|
||||
browsingContext.zenBoostsData = nsColor;
|
||||
} else {
|
||||
let colorWheelColor = this.#hslToRgb(
|
||||
boostData.dotAngleDeg / 360,
|
||||
primaryColor = this.#buildBoostColor(
|
||||
boostData.dotAngleDeg,
|
||||
/* already is [0, 1] */
|
||||
boostData.dotDistance * (1 - boostData.saturation),
|
||||
/* lightness range from [0.1, 0.9] */
|
||||
0.1 + boostData.dotDistance * 0.8 * boostData.brightness
|
||||
0.1 + boostData.dotDistance * 0.8 * boostData.brightness,
|
||||
boostData
|
||||
);
|
||||
|
||||
const rgbColor = colorWheelColor;
|
||||
const nsColor = this.#rgbToNSColor(
|
||||
rgbColor,
|
||||
(1 - boostData.contrast) * 255
|
||||
);
|
||||
browsingContext.zenBoostsData = nsColor;
|
||||
}
|
||||
browsingContext.zenBoostsData = primaryColor;
|
||||
// The complementary accent is derived in the backend by rotating the
|
||||
// primary accent's hue by this delta (in degrees).
|
||||
browsingContext.zenBoostsComplementaryRotation =
|
||||
boostData.secondaryDotAngleDegDelta ?? 0;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
browsingContext.isZenBoostsInverted = false;
|
||||
}
|
||||
browsingContext.zenBoostsData = 0;
|
||||
browsingContext.zenBoostsComplementaryRotation = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,6 +66,24 @@ void BrowsingContext::DidSet(FieldIndex<IDX_ZenBoostsData>,
|
||||
TRIGGER_PRES_CONTEXT_RESTYLE();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called when the ZenBoostsComplementaryRotation field is set on a
|
||||
* browsing context. This is the hue rotation (in degrees) applied to the base
|
||||
* accent to derive the complementary accent that light page colors are tinted
|
||||
* toward. Triggers a restyle if it has changed.
|
||||
* @param aOldValue The previous rotation value.
|
||||
*/
|
||||
void BrowsingContext::DidSet(FieldIndex<IDX_ZenBoostsComplementaryRotation>,
|
||||
float aOldValue) {
|
||||
MOZ_ASSERT(IsTop());
|
||||
if (ZenBoostsComplementaryRotation() == aOldValue) {
|
||||
return;
|
||||
}
|
||||
RefreshBoostCacheIfMatchesCurrent(this);
|
||||
PresContextAffectingFieldChanged();
|
||||
TRIGGER_PRES_CONTEXT_RESTYLE();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Called when the IsZenBoostsInverted field is set on a browsing
|
||||
* context. Triggers a restyle if the value has changed.
|
||||
|
||||
@@ -47,6 +47,8 @@
|
||||
namespace zen {
|
||||
|
||||
nsZenAccentOklab nsZenBoostsBackend::mCachedAccent{0};
|
||||
nsZenAccentOklab nsZenBoostsBackend::mCachedComplementary{0};
|
||||
float nsZenBoostsBackend::mCachedComplementaryRotationDeg = 0.0f;
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -128,17 +130,48 @@ inline static auto zenPrecomputeAccent(nscolor aAccentColor) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a color filter to transform an original color toward an accent
|
||||
* color. Preserves the original color's perceived luminance while shifting
|
||||
* hue/chroma toward the accent. Uses the alpha channel of the accent color to
|
||||
* store contrast information.
|
||||
* @brief Derives the complementary accent from the base accent by rotating its
|
||||
* hue in the Oklab a/b plane by the given angle. Lightness, contrast and the
|
||||
* source nscolor are kept; only the hue changes. A zero rotation returns the
|
||||
* base accent unchanged so the duotone collapses to a single-accent tint.
|
||||
* @param aBase The precomputed base accent.
|
||||
* @param aRotationDeg The hue rotation to apply, in degrees.
|
||||
* @return The complementary accent.
|
||||
*/
|
||||
ZEN_HOT_FUNCTION
|
||||
inline static nsZenAccentOklab zenRotateAccent(const nsZenAccentOklab& aBase,
|
||||
float aRotationDeg) {
|
||||
constexpr float kDegToRad = 3.14159265358979323846f / 180.0f;
|
||||
const float angle = aRotationDeg * kDegToRad;
|
||||
const float cosR = std::cos(angle);
|
||||
const float sinR = std::sin(angle);
|
||||
return nsZenAccentOklab{
|
||||
.accentNS = aBase.accentNS,
|
||||
.accL = aBase.accL,
|
||||
.accA = aBase.accA * cosR - aBase.accB * sinR,
|
||||
.accB = aBase.accA * sinR + aBase.accB * cosR,
|
||||
.contrastFactor = aBase.contrastFactor,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Applies a duotone color filter to transform an original color toward
|
||||
* one of two accent colors. The original color's perceived lightness decides
|
||||
* which accent it is tinted toward: dark colors are pulled to the base accent,
|
||||
* light colors to the complementary accent, with a smooth crossfade between
|
||||
* them. The contrast value (stored in the accent's alpha channel) controls both
|
||||
* the overall tint strength and how hard that dark/light split is. The
|
||||
* original color's perceived luminance is otherwise preserved.
|
||||
* @param aOriginalColor The original color to filter.
|
||||
* @param aAccentColor The accent color to filter toward (alpha channel contains
|
||||
* contrast value).
|
||||
* @param aAccent The base accent, tinted toward by dark colors (alpha channel
|
||||
* contains the contrast value).
|
||||
* @param aComplementary The complementary accent, tinted toward by light
|
||||
* colors.
|
||||
* @return The filtered color with transformations applied.
|
||||
*/
|
||||
[[nodiscard]] ZEN_HOT_FUNCTION static inline nscolor zenFilterColorChannel(
|
||||
nscolor aOriginalColor, const nsZenAccentOklab& aAccent) {
|
||||
nscolor aOriginalColor, const nsZenAccentOklab& aAccent,
|
||||
const nsZenAccentOklab& aComplementary) {
|
||||
const uint8_t oL = NS_GET_A(aOriginalColor);
|
||||
const uint8_t contrast = NS_GET_CONTRAST(aAccent.accentNS);
|
||||
if (oL == 0) {
|
||||
@@ -168,23 +201,40 @@ inline static auto zenPrecomputeAccent(nscolor aAccentColor) {
|
||||
const float origB =
|
||||
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_;
|
||||
|
||||
// Blend chroma toward accent
|
||||
const float bA = origA + (aAccent.accA - origA) * blendFactor;
|
||||
const float bB = origB + (aAccent.accB - origB) * blendFactor;
|
||||
// Duotone selection. origL is the original color's Oklab lightness (~0..1).
|
||||
// A smoothstep around a fixed mid-lightness pivot crossfades from the base
|
||||
// accent (dark colors, t=0) to the complementary accent (light colors, t=1).
|
||||
// A stronger tint (higher blendFactor) narrows the crossfade band toward a
|
||||
// hard two-tone split; a weaker one keeps it a gentle gradient.
|
||||
constexpr float kPivot = 0.5f;
|
||||
const float halfWidth = std::clamp(0.5f - blendFactor * 0.45f, 0.05f, 0.5f);
|
||||
float t = std::clamp((origL - (kPivot - halfWidth)) / (2.0f * halfWidth),
|
||||
0.0f, 1.0f);
|
||||
t = t * t * (3.0f - 2.0f * t);
|
||||
|
||||
const float selA = aAccent.accA + (aComplementary.accA - aAccent.accA) * t;
|
||||
const float selB = aAccent.accB + (aComplementary.accB - aAccent.accB) * t;
|
||||
const float selL = aAccent.accL + (aComplementary.accL - aAccent.accL) * t;
|
||||
const float selContrastFactor =
|
||||
aAccent.contrastFactor +
|
||||
(aComplementary.contrastFactor - aAccent.contrastFactor) * t;
|
||||
|
||||
// Blend chroma toward the selected accent
|
||||
const float bA = origA + (selA - origA) * blendFactor;
|
||||
const float bB = origB + (selB - origB) * blendFactor;
|
||||
|
||||
// Luminance: at low contrast stay near the original, the higher the contrast,
|
||||
// the more we shift toward the accent luminance, but we never go fully to
|
||||
// the accent luminance to preserve some of the original color's character.
|
||||
const float lumDelta = aAccent.accL - origL;
|
||||
const float fL =
|
||||
origL + lumDelta * (blendFactor * aAccent.contrastFactor * 0.5f);
|
||||
const float lumDelta = selL - origL;
|
||||
const float fL = origL + lumDelta * (blendFactor * selContrastFactor * 0.5f);
|
||||
|
||||
// Rotate hue in the Oklab a/b plane. Direction follows the luminance shift:
|
||||
// pushing darker rotates clockwise ("right"), pushing lighter rotates the
|
||||
// other way. Magnitude scales with blend strength so subtle accents stay
|
||||
// subtle.
|
||||
const float rotAngle = (lumDelta > 0.0f ? -1.0f : 1.0f) * blendFactor *
|
||||
aAccent.contrastFactor * 0.25f;
|
||||
selContrastFactor * 0.25f;
|
||||
const float cosR = std::cos(rotAngle);
|
||||
const float sinR = std::sin(rotAngle);
|
||||
const float fA = bA * cosR - bB * sinR;
|
||||
@@ -267,7 +317,7 @@ inline static nscolor zenInvertColorChannel(nscolor aColor) {
|
||||
*/
|
||||
ZEN_HOT_FUNCTION
|
||||
inline static void GetZenBoostsDataFromBrowsingContext(
|
||||
ZenBoostData* aData, bool* aIsInverted,
|
||||
ZenBoostData* aData, float* aComplementaryRotation, bool* aIsInverted,
|
||||
nsPresContext* aPresContext = nullptr) {
|
||||
auto zenBoosts = nsZenBoostsBackend::GetInstance();
|
||||
if (!zenBoosts || (zenBoosts->mCurrentFrameIsAnonymousContent &&
|
||||
@@ -276,6 +326,7 @@ inline static void GetZenBoostsDataFromBrowsingContext(
|
||||
}
|
||||
if (!aPresContext) {
|
||||
*aData = zenBoosts->mCachedCurrentAccent;
|
||||
*aComplementaryRotation = zenBoosts->mCachedCurrentComplementaryRotation;
|
||||
*aIsInverted = zenBoosts->mCachedCurrentInverted;
|
||||
return;
|
||||
}
|
||||
@@ -288,6 +339,7 @@ inline static void GetZenBoostsDataFromBrowsingContext(
|
||||
}
|
||||
browsingContext = browsingContext->Top();
|
||||
*aData = browsingContext->ZenBoostsData();
|
||||
*aComplementaryRotation = browsingContext->ZenBoostsComplementaryRotation();
|
||||
*aIsInverted = browsingContext->IsZenBoostsInverted();
|
||||
}
|
||||
|
||||
@@ -329,11 +381,13 @@ auto nsZenBoostsBackend::onPresShellEntered(mozilla::dom::Document* aDocument)
|
||||
auto nsZenBoostsBackend::RefreshCachedBoostState() -> void {
|
||||
if (!mCurrentBrowsingContext) {
|
||||
mCachedCurrentAccent = 0;
|
||||
mCachedCurrentComplementaryRotation = 0.0f;
|
||||
mCachedCurrentInverted = false;
|
||||
return;
|
||||
}
|
||||
auto top = mCurrentBrowsingContext->Top();
|
||||
mCachedCurrentAccent = top->ZenBoostsData();
|
||||
mCachedCurrentComplementaryRotation = top->ZenBoostsComplementaryRotation();
|
||||
mCachedCurrentInverted = top->IsZenBoostsInverted();
|
||||
}
|
||||
|
||||
@@ -342,19 +396,31 @@ nsZenBoostsBackend::FilterColorFromPresContext(nscolor aColor,
|
||||
nsPresContext* aPresContext)
|
||||
-> nscolor {
|
||||
ZenBoostData accentNS = 0;
|
||||
float complementaryRotation = 0.0f;
|
||||
bool invertColors = false;
|
||||
GetZenBoostsDataFromBrowsingContext(&accentNS, &invertColors, aPresContext);
|
||||
GetZenBoostsDataFromBrowsingContext(&accentNS, &complementaryRotation,
|
||||
&invertColors, aPresContext);
|
||||
if (accentNS) {
|
||||
if (mCachedAccent.accentNS != accentNS) {
|
||||
mCachedAccent = zenPrecomputeAccent(accentNS);
|
||||
// Trigger a recompute of the complementary accent since
|
||||
/ it depends on the base accent.mCachedComplementary.accentNS = 0;
|
||||
}
|
||||
// Derive the complementary accent by rotating the base accent's hue by the
|
||||
// boost's complementary rotation. Cached so the per-color hot path only
|
||||
// recomputes it when the base accent or rotation changes.
|
||||
if (mCachedComplementary.accentNS != accentNS ||
|
||||
mCachedComplementaryRotationDeg != complementaryRotation) {
|
||||
mCachedComplementary =
|
||||
zenRotateAccent(mCachedAccent, complementaryRotation);
|
||||
mCachedComplementaryRotationDeg = complementaryRotation;
|
||||
}
|
||||
// Apply a filter-like tint:
|
||||
// - Preserve the original color's perceived luminance
|
||||
// - Map hue/chroma toward the accent by scaling the accent's RGB
|
||||
// to match the original luminance
|
||||
// - Map hue/chroma toward the base or complementary accent depending on
|
||||
// the original color's lightness
|
||||
// - Keep the original alpha
|
||||
// Convert both colors to nscolor to access channels
|
||||
aColor = zenFilterColorChannel(aColor, mCachedAccent);
|
||||
aColor = zenFilterColorChannel(aColor, mCachedAccent, mCachedComplementary);
|
||||
}
|
||||
if (invertColors) {
|
||||
aColor = zenInvertColorChannel(aColor);
|
||||
|
||||
@@ -83,6 +83,10 @@ class nsZenBoostsBackend final {
|
||||
* resolve.
|
||||
*/
|
||||
ZenBoostData mCachedCurrentAccent = 0;
|
||||
// Hue rotation in degrees applied to the base accent to derive the
|
||||
// complementary accent. Zero means the complementary accent equals the base
|
||||
// accent (the duotone collapses to a single-accent tint).
|
||||
float mCachedCurrentComplementaryRotation = 0.0f;
|
||||
bool mCachedCurrentInverted = false;
|
||||
|
||||
private:
|
||||
@@ -92,6 +96,10 @@ class nsZenBoostsBackend final {
|
||||
RefPtr<mozilla::dom::BrowsingContext> mCurrentBrowsingContext;
|
||||
|
||||
static nsZenAccentOklab mCachedAccent;
|
||||
// Base accent with its Oklab hue rotated by mCachedComplementaryRotationDeg,
|
||||
// recomputed only when the base accent or rotation changes.
|
||||
static nsZenAccentOklab mCachedComplementary;
|
||||
static float mCachedComplementaryRotationDeg;
|
||||
|
||||
public:
|
||||
/**
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
<vbox flex="1" id="zen-boost-filter-wrapper">
|
||||
<hbox class="zen-boost-color-picker-gradient zen-boost-panel-disabled">
|
||||
<button data-l10n-id="zen-boost-magic-theme" id="zen-boost-magic-theme" class="subviewbutton mod-button"></button>
|
||||
<html:div class="zen-boost-color-picker-dot"></html:div>
|
||||
<html:div id="zen-boost-color-picker-dot-primary" class="zen-boost-color-picker-dot"></html:div>
|
||||
<html:div id="zen-boost-color-picker-dot-secondary" class="zen-boost-color-picker-dot"></html:div>
|
||||
<html:div class="zen-boost-color-picker-circle"></html:div>
|
||||
</hbox>
|
||||
|
||||
@@ -69,17 +70,26 @@
|
||||
|
||||
<html:div id="zen-boost-font-wrapper">
|
||||
<vbox id="zen-boost-font-grid">
|
||||
<!-- Font buttons will be injected here -->
|
||||
# Font buttons will be injected here
|
||||
</vbox>
|
||||
<html:div class="visible-separator"></html:div>
|
||||
<hbox flex="1" id="zen-boost-font-toolbar">
|
||||
<html:select name="font" id="zen-boost-font-select" class="mod-button">
|
||||
<!-- Additional font options will be injected here -->
|
||||
# Additional font options will be injected here
|
||||
</html:select>
|
||||
<button data-l10n-id="zen-boost-text-case-toggle" id="zen-boost-text-case-toggle" class="subviewbutton mod-button"></button>
|
||||
</hbox>
|
||||
</html:div>
|
||||
|
||||
<hbox flex="1" id="zen-boost-toolbar-wrapper">
|
||||
<button id="zen-boost-size" class="subviewbutton mod-button big-button med">
|
||||
<html:p data-l10n-id="zen-boost-size" id="zen-boost-size-text"></html:p>
|
||||
<html:p id="zen-boost-size-value"></html:p>
|
||||
</button>
|
||||
<button id="zen-boost-case" class="subviewbutton mod-button big-button toggleable-button med">
|
||||
<html:p data-l10n-id="zen-boost-case" id="zen-boost-case-text"></html:p>
|
||||
</button>
|
||||
</hbox>
|
||||
|
||||
<button id="zen-boost-zap" class="subviewbutton mod-button big-button toggleable-button">
|
||||
<html:p data-l10n-id="zen-boost-zap" id="zen-boost-zap-text"></html:p>
|
||||
<html:p id="zen-boost-zap-value"></html:p>
|
||||
@@ -89,11 +99,11 @@
|
||||
<html:p data-l10n-id="zen-boost-code" id="zen-boost-code-text"></html:p>
|
||||
</button>
|
||||
|
||||
<hbox flex="1" id="zen-boost-toolbar-wrapper">
|
||||
</vbox>
|
||||
<hbox flex="1" class="footer" id="zen-boost-toolbar-wrapper">
|
||||
<button data-l10n-id="zen-boost-save" id="zen-boost-save" class="subviewbutton mod-button med"></button>
|
||||
<button data-l10n-id="zen-boost-load" id="zen-boost-load" class="subviewbutton mod-button med"></button>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<vbox flex="1" id="zen-boost-code-editor-root">
|
||||
<hbox id="zen-boost-code-top-bar">
|
||||
|
||||
@@ -302,17 +302,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-size {
|
||||
list-style-type: none;
|
||||
|
||||
& #zen-boost-size-value {
|
||||
text-align: right;
|
||||
right: 8px;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.zen-boost-panel-disabled {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
@@ -348,18 +337,19 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle[case-mode="none"] {
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
opacity: 0.6 !important;
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-text-case-toggle:not([case-mode="none"]) {
|
||||
#zen-boost-case:not([case-mode="none"]) {
|
||||
background-color: #ebebed;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
#zen-boost-case[case-mode="uppercase"] {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#zen-boost-case[case-mode="lowercase"] {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
#zen-boost-code-top-bar .mod-button {
|
||||
height: auto !important;
|
||||
}
|
||||
@@ -434,6 +424,11 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #F6F6F8;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#zen-boost-toolbar-wrapper,
|
||||
#zen-boost-toolbar-wrapper-colors {
|
||||
width: 100%;
|
||||
@@ -450,11 +445,18 @@ body {
|
||||
& .med {
|
||||
margin: 0;
|
||||
flex: 1 1 50%;
|
||||
|
||||
& p {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
text-indent: initial;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#zen-boost-font-select {
|
||||
width: 95px;
|
||||
width: 120px;
|
||||
height: 20px !important;
|
||||
|
||||
transition: 0.2s opacity ease-in-out;
|
||||
@@ -693,10 +695,15 @@ body {
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
& .zen-boost-color-picker-arc-svg {
|
||||
opacity: 0;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
|
||||
& .zen-boost-color-picker-dot {
|
||||
box-shadow: 0 2px 4px #00000022;
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
z-index: 5;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
@@ -707,16 +714,15 @@ body {
|
||||
cursor: pointer;
|
||||
border: 3px solid #ffffff;
|
||||
transform: translate(-50%, -50%);
|
||||
pointer-events: none;
|
||||
transform-origin: top left;
|
||||
pointer-events: all;
|
||||
|
||||
&:first-of-type {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-width: 3px;
|
||||
pointer-events: all;
|
||||
transition: transform 0.2s;
|
||||
z-index: 999;
|
||||
z-index: 4;
|
||||
&:hover {
|
||||
transform: scale(1.05) translate(-50%, -50%);
|
||||
}
|
||||
@@ -729,12 +735,11 @@ body {
|
||||
}
|
||||
|
||||
#zen-boost-editor-root:hover {
|
||||
& .zen-boost-color-picker-circle {
|
||||
& .zen-boost-color-picker-circle, .zen-boost-color-picker-arc-svg {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.zen-boost-font-button-active {
|
||||
background-color: #5454572f !important;
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ skip-if = [
|
||||
|
||||
["browser_sandbox_test.js"]
|
||||
skip-if = [
|
||||
"os == 'linux' && os_version == '22.04' && arch == 'x86_64' && display == 'wayland' && artifact && debug", # bug 1945658
|
||||
"os == 'linux' && os_version == '24.04' && arch == 'x86_64' && display == 'x11' && artifact && debug", # bug 1945658
|
||||
"os == 'win' && os_version == '11.26200' && arch == 'x86' && debug", # bug 2028636
|
||||
"os == 'win' && os_version == '11.26200' && arch == 'x86_64' && debug", # bug 2028636
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user