Skip to content

Commit

Permalink
Add variable swatches to color picker
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=259120
rdar://112099603

Reviewed by Devin Rousso.

Include known color variables within the color picker. Select variables based on selected element and sort them based on the HSL values. The colors are shown using the InlineSwatch class allowing developers to quickly select variable colors.

Utilize the node styles of an element to gather color variable information and sort based on visual appearance. Future work could aggregate more sources of color data and add new sorting patterns.

Rewrote InlineSwatch to allow swapping between multiple swatch types. Added ColorSwatchDisplay to show all swatches. Altered ColorPicker communication pattern with InlineSwatch.

* Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js:
* Source/WebInspectorUI/UserInterface/Views/ColorPicker.css:
(.color-picker):
(.color-picker > .variable-color-swatches):
(.color-picker > .variable-color-swatches > ul):
(.color-picker > .variable-color-swatches > ul > li):
(.color-picker > .variable-color-swatches > ul > li > .inline-swatch):
(.color-picker > .variable-color-swatches > h2):
* Source/WebInspectorUI/UserInterface/Views/ColorPicker.js:
(WI.ColorPicker.prototype.async colorInputsWrapperElement):
(WI.ColorPicker.sortColorVariables.visualAppeal):
(WI.ColorPicker.sortColorVariables):
(WI.ColorPicker.prototype._updateColorForVariable):
(WI.ColorPicker):
(WI.ColorPicker.get element): Deleted.
(WI.ColorPicker.get colorSquare): Deleted.
(WI.ColorPicker.set opacity): Deleted.
(WI.ColorPicker.get color): Deleted.
(WI.ColorPicker.set color): Deleted.
* Source/WebInspectorUI/UserInterface/Views/InlineSwatch.css:
(.inline-swatch):
* Source/WebInspectorUI/UserInterface/Views/InlineSwatch.js:
(WI.InlineSwatch.prototype.switch):
(WI.InlineSwatch.prototype._updateSwatch):
(WI.InlineSwatch.prototype._valueEditorValueDidChange):
(WI.InlineSwatch.prototype._findMatchingColorVariable):
(WI.InlineSwatch):
* Source/WebInspectorUI/UserInterface/Views/SpreadsheetStyleProperty.js:
(WI.SpreadsheetStyleProperty.prototype.inlineSwatchGetColorVariables):
(WI.SpreadsheetStyleProperty.prototype._createInlineSwatch):
(WI.SpreadsheetStyleProperty.prototype._addVariableTokens):

Canonical link: https://commits.webkit.org/266794@main
  • Loading branch information
AnujPanta1 authored and dcrousso committed Aug 10, 2023
1 parent 2e4cd29 commit 3afcee6
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1781,6 +1781,8 @@ localizedStrings["Valid From"] = "Valid From";
localizedStrings["Valid Until"] = "Valid Until";
localizedStrings["Value"] = "Value";
localizedStrings["Variables"] = "Variables";
/* Title of swatches section in Color Picker */
localizedStrings["Variables @ Color Picker"] = "Variables";
/* Section title for font variation properties. */
localizedStrings["Variation Properties @ Font Details Sidebar Section"] = "Variation Properties";
localizedStrings["Verbose"] = "Verbose";
Expand Down
31 changes: 30 additions & 1 deletion Source/WebInspectorUI/UserInterface/Views/ColorPicker.css
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
.color-picker {
position: relative;
width: var(--color-picker-width);
height: 236px;
padding: 5px;

--color-picker-hue-offset-start: 41px;
Expand Down Expand Up @@ -108,3 +107,33 @@
.color-picker > .color-inputs-wrapper > .pick-color-from-screen.active {
color: var(--glyph-color-active);
}

.color-picker > .variable-color-swatches {
padding: 8px 0 0;
}

.color-picker > .variable-color-swatches > ul{
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 2px;
padding: 0;
margin: 2px 0 0;
max-height: 45px;
overflow-y: auto;
}

.color-picker > .variable-color-swatches > ul > li {
height: 1em;
}

.color-picker > .variable-color-swatches > ul > li > .inline-swatch {
--inline-swatch-margin-right-override: 0;
--inline-swatch-vertical-align-override: 0;
}

.color-picker > .variable-color-swatches > h2 {
margin: 0;
font-size: 1.15em;
}
62 changes: 60 additions & 2 deletions Source/WebInspectorUI/UserInterface/Views/ColorPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

WI.ColorPicker = class ColorPicker extends WI.Object
{
constructor({preventChangingColorFormats} = {})
constructor({preventChangingColorFormats, colorVariables} = {})
{
super();

Expand Down Expand Up @@ -87,7 +87,27 @@ WI.ColorPicker = class ColorPicker extends WI.Object
this._opacity = 0;
this._opacityPattern = "url(Images/Checkers.svg)";

this._color = WI.Color.fromString("white");
this.color = WI.Color.fromString("white");

if (colorVariables?.length) {
let variableColorSwatchesContainer = this._element.appendChild(document.createElement("div"));
variableColorSwatchesContainer.classList.add("variable-color-swatches");

let swatchesTitle = variableColorSwatchesContainer.appendChild(document.createElement("h2"));
swatchesTitle.textContent = WI.UIString("Variables", "Variables @ Color Picker", "Title of swatches section in Color Picker");

let variableColorSwatchesListElement = variableColorSwatchesContainer.appendChild(document.createElement("ul"));
let sortedColorVariables = WI.ColorPicker.sortColorVariables(colorVariables);

for (let variable of sortedColorVariables) {
let computedColor = WI.Color.fromString(variable.value)
let swatch = new WI.InlineSwatch(WI.InlineSwatch.Type.Color, computedColor, {readOnly: true, tooltip: variable.name});
swatch.element.addEventListener("click", (event) => {
this._updateColorForVariable(computedColor, variable.name);
});
variableColorSwatchesListElement.appendChild(document.createElement("li")).appendChild(swatch.element);
}
}

this._dontUpdateColor = false;
}
Expand Down Expand Up @@ -119,6 +139,38 @@ WI.ColorPicker = class ColorPicker extends WI.Object
return WI.Color.fromStringBestMatchingSuggestedFormatAndGamut(pickedColorCSSString, {suggestedFormat, suggestedGamut, forceSuggestedFormatAndGamut});
}

// Static

static sortColorVariables(colorVariables)
{
const rotation = 2;
const weights = [Math.E / 11.279, Math.E / 3.934, Math.E / 39.975];

function visualAppeal(variable) {
let color = WI.Color.fromString(variable.value);
let [h, s, l] = color.hsl;

let luminosity = Math.floor(color.rgb.reduce((total, component, i) => total + component * weights[i]));

let [modifiedH, modifiedLuminosity, modifiedLight] = [h, luminosity, l].map((component) => Math.floor(component * rotation));

if (modifiedH % 2 === 1) {
modifiedLight = rotation - modifiedLight;
luminosity = rotation - luminosity;
}

return {lightScore: modifiedLight, luminosityScore: luminosity, hueScore: modifiedH};
}

let colorVariableVisualAppealScores = new Map(colorVariables.map((variable) => [variable, visualAppeal(variable)]));

return colorVariables.sort((variableA, variableB) => {
let appealA = colorVariableVisualAppealScores.get(variableA);
let appealB = colorVariableVisualAppealScores.get(variableB);
return appealB.hueScore - appealA.hueScore || appealB.luminosityScore - appealA.luminosityScore || appealB.lightScore - appealA.lightScore;
});
}

// Public

get element() { return this._element; }
Expand Down Expand Up @@ -343,6 +395,12 @@ WI.ColorPicker = class ColorPicker extends WI.Object
this.color = new WI.Color(this._color.format, components, this._color.gamut);
this.dispatchEventToListeners(WI.ColorPicker.Event.ColorChanged, {color: this._color});
}

_updateColorForVariable(resolvedColor, variableName)
{
this.color = resolvedColor;
this.dispatchEventToListeners(WI.ColorPicker.Event.ColorChanged, {color: this._color, variableName});
}
};

WI.ColorPicker.Event = {
Expand Down
7 changes: 5 additions & 2 deletions Source/WebInspectorUI/UserInterface/Views/InlineSwatch.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@
position: relative;
width: 1em;
height: 1em;
margin-right: 3px;
vertical-align: -2px;
margin-right: var(--inline-swatch-margin-right-override, var(--inline-swatch-margin-right-default));
vertical-align: var(--inline-swatch-vertical-align-override, var(--inline-swatch-vertical-align-default));
border-radius: 2px;
overflow: hidden;
cursor: default;

--inline-swatch-margin-right-default: 3px;
--inline-swatch-vertical-align-override: -2px;
}

.inline-swatch:not(.box-shadow),
Expand Down
89 changes: 76 additions & 13 deletions Source/WebInspectorUI/UserInterface/Views/InlineSwatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@

WI.InlineSwatch = class InlineSwatch extends WI.Object
{
constructor(type, value, {readOnly, preventChangingColorFormats} = {})
constructor(type, value, {readOnly, preventChangingColorFormats, variableType, tooltip, delegate} = {})
{
super();

this._type = type;
this._variableType = variableType || null;

console.assert(!preventChangingColorFormats || type === WI.InlineSwatch.Type.Color);
this._preventChangingColorFormats = !!preventChangingColorFormats;

switch (this._type) {
switch (this._variableType || this._type) {
case WI.InlineSwatch.Type.CubicBezierTimingFunction:
case WI.InlineSwatch.Type.SpringTimingFunction:
this._swatchElement = WI.ImageUtilities.useSVGSymbol("Images/CubicBezier.svg");
Expand All @@ -54,7 +55,7 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
break;
}

this._swatchElement.classList.add("inline-swatch", this._type.replace("inline-swatch-type-", ""));
this._swatchElement.classList.add("inline-swatch", (this._variableType || this._type).replace("inline-swatch-type-", ""));

if (readOnly && this._type !== WI.InlineSwatch.Type.Variable)
this._swatchElement.classList.add("read-only");
Expand Down Expand Up @@ -110,6 +111,8 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
this._readOnly = !!readOnly;
this._popover = null;

this._delegate = delegate || null;
this._tooltip = tooltip || "";
if (this._allowChangingColorFormats())
this._swatchElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this));

Expand Down Expand Up @@ -194,22 +197,29 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
let value = this.value;

switch (this._type) {
case WI.InlineSwatch.Type.Color:
case WI.InlineSwatch.Type.Color: {
let title = this._tooltip;
if (!this._readOnly) {
let title = WI.UIString("Click to select a color.");
title = WI.UIString("Click to select a color.");
if (this._allowChangingColorFormats())
title += "\n" + WI.UIString("Shift-click to switch color formats.");
if (InspectorFrontendHost.canPickColorFromScreen())
title += "\n" + WI.UIString("Option-click to pick color from screen.");

this._swatchElement.title = title;
}
this._swatchElement.title = title;
// fallthrough

}
case WI.InlineSwatch.Type.Gradient:
this._swatchInnerElement.style.background = value ? value.toString() : null;
break;

case WI.InlineSwatch.Type.Variable: {
if (this._variableType === WI.InlineSwatch.Type.Color) {
let colorVariable = this._findMatchingColorVariable(value.toString());
this._swatchInnerElement.style.background = colorVariable?.value || "";
}
break;
}
case WI.InlineSwatch.Type.Image:
this._swatchInnerElement.style.setProperty("background-image", `url(${value.src})`);
break;
Expand All @@ -220,6 +230,9 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
break;
}

if (this._variableType)
value = `var(${value})`;

if (!dontFireEvents)
this.dispatchEventToListeners(WI.InlineSwatch.Event.ValueChanged, {value});
}
Expand All @@ -236,12 +249,23 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
let value = this.value;

if (event.shiftKey && value) {
if (this._type === WI.InlineSwatch.Type.Color) {
if ((this._variableType || this._type) === WI.InlineSwatch.Type.Color) {
if (!this._allowChangingColorFormats()) {
InspectorFrontendHost.beep();
return;
}


if (this._variableType === WI.InlineSwatch.Type.Color) {
let colorVariable = this._findMatchingColorVariable(value.toString());
if (!colorVariable)
return;

value = colorVariable.value;

this._variableType = null;
this._type = WI.InlineSwatch.Type.Color;
}

let nextFormat = value.nextFormat();
console.assert(nextFormat);
if (nextFormat) {
Expand All @@ -260,7 +284,7 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
}

if (event.altKey && value) {
if (this._type === WI.InlineSwatch.Type.Color) {
if ((this._variableType || this._type) === WI.InlineSwatch.Type.Color) {
WI.ColorPicker.pickColorFromScreen({
suggestedFormat: value.format,
suggestedGamut: value.gamut,
Expand All @@ -269,6 +293,9 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
if (!pickedColor)
return;

this._variableType = null;
this._type = WI.InlineSwatch.Type.Color;

this._value = pickedColor;
this._updateSwatch();
});
Expand All @@ -289,7 +316,7 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
};

this._valueEditor = null;
switch (this._type) {
switch (this._variableType || this._type) {
case WI.InlineSwatch.Type.CubicBezierTimingFunction:
this._valueEditor = new WI.CubicBezierTimingFunctionEditor;
this._valueEditor.addEventListener(WI.CubicBezierTimingFunctionEditor.Event.CubicBezierTimingFunctionChanged, this._valueEditorValueDidChange, this);
Expand All @@ -301,7 +328,7 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
break;

case WI.InlineSwatch.Type.Color:
this._valueEditor = new WI.ColorPicker({preventChangingColorFormats: this._preventChangingColorFormats});
this._valueEditor = new WI.ColorPicker({preventChangingColorFormats: this._preventChangingColorFormats, colorVariables: this._delegate?.inlineSwatchGetColorVariables?.(this)});
this._valueEditor.addEventListener(WI.ColorPicker.Event.ColorChanged, this._valueEditorValueDidChange, this);
break;

Expand Down Expand Up @@ -384,6 +411,14 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
break;

case WI.InlineSwatch.Type.Variable: {
if (this._variableType === WI.InlineSwatch.Type.Color) {
let colorVariable = this._findMatchingColorVariable(value.toString());
if (colorVariable) {
this._valueEditor.color = WI.Color.fromString(colorVariable.value);
this._valueEditor.focus();
}
break;
}
let codeMirror = this._valueEditor.codeMirror;
codeMirror.setValue(value);

Expand All @@ -410,6 +445,16 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object

_valueEditorValueDidChange(event)
{
if (this._variableType && event.data.color) {
this._variableType = null;
this._type = WI.InlineSwatch.Type.Color;
}

if (event.data.variableName) {
this._variableType = WI.InlineSwatch.Type.Color;
this._type = WI.InlineSwatch.Type.Variable;
}

switch (this._type) {
case WI.InlineSwatch.Type.BoxShadow:
this._value = event.data.boxShadow;
Expand All @@ -434,6 +479,10 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
case WI.InlineSwatch.Type.Alignment:
this._value = event.data.alignment;
break;

case WI.InlineSwatch.Type.Variable:
this._value = event.data.variableName;
break;
}

this._updateSwatch();
Expand Down Expand Up @@ -573,6 +622,20 @@ WI.InlineSwatch = class InlineSwatch extends WI.Object
}
return hexFormats[0];
}

_findMatchingColorVariable(variableName)
{
let colorVariables = this._delegate?.inlineSwatchGetColorVariables?.(this) || [];

const variableNameStart = 4;
const variableNameEnd = 1;
for (let colorVariable of colorVariables) {
let index = variableName.indexOf(colorVariable.name);
if ((!index && colorVariable.name.length === variableName.length) || (index === variableNameStart && variableNameStart + colorVariable.name.length === variableName.length - variableNameEnd))
return colorVariable;
}
return null;
}
};

WI.InlineSwatch.Type = {
Expand Down
Loading

0 comments on commit 3afcee6

Please sign in to comment.