Deprecate all usages of the Color struct representing gamma space values, fixing round-trip precision bugs (#4149)

* Deprecate all usages of the Color struct representing gamma space values, fixing round-trip precision bugs

* Code review fixes
This commit is contained in:
Keavon Chambers
2026-05-14 22:48:33 -07:00
committed by GitHub
parent 456a7c868d
commit a56746c6bf
67 changed files with 1210 additions and 941 deletions
@@ -26,7 +26,7 @@
import ShortcutLabel from "/src/components/widgets/labels/ShortcutLabel.svelte";
import TextLabel from "/src/components/widgets/labels/TextLabel.svelte";
import type { ColorPickerStore } from "/src/stores/color-picker";
import { parseFillChoice } from "/src/utility-functions/colors";
import { parseFillChoiceUI } from "/src/utility-functions/colors";
import type { EditorWrapper, LayoutTarget, Widget, WidgetInstance } from "/wrapper/pkg/graphite_wasm_wrapper";
// Extract the discriminant key names from the Widget tagged enum union (e.g. "TextButton" | "CheckboxInput" | ...)
@@ -134,7 +134,7 @@
component: ColorInput,
getProps: (props, index) => ({
...props,
value: parseFillChoice(props.value),
value: parseFillChoiceUI(props.value),
$$events: {
value: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
startHistoryTransaction: () => widgetValueCommit(index, props.value),
@@ -146,7 +146,7 @@
getProps: (props, index) => ({
...props,
$$events: {
// The widget dispatches `"None"` or a bare `Color`, wrap the color in `{ Solid: ... }` so the payload matches Rust's `FillChoice` shape (which the `Preset` variant expects).
// The widget dispatches `"None"` or a bare `SRGBA8`, wrap the color in `{ Solid: ... }` so the payload matches Rust's `FillChoiceUI` shape (which the `Preset` variant expects).
preset: (e: CustomEvent) => widgetValueCommitAndUpdate(index, { Preset: e.detail === "None" ? "None" : { Solid: e.detail } }, true),
eyedropperColorCode: (e: CustomEvent) => widgetValueCommitAndUpdate(index, { EyedropperColorCode: e.detail }, true),
},
@@ -4,12 +4,12 @@
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
import IconButton from "/src/components/widgets/buttons/IconButton.svelte";
import TextLabel from "/src/components/widgets/labels/TextLabel.svelte";
import type { Color } from "/wrapper/pkg/graphite_wasm_wrapper";
import type { SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
const dispatch = createEventDispatcher<{ swap: undefined }>();
export let newColor: Color | undefined;
export let oldColor: Color | undefined;
export let newColor: SRGBA8 | undefined;
export let oldColor: SRGBA8 | undefined;
export let newColorCSS: string;
export let newColorContrasting: string;
export let oldColorCSS: string;
@@ -21,7 +21,7 @@
export let outlineAmount: number;
$: outlined = outlineAmount > 0.0001;
$: transparency = (newColor?.alpha ?? 1) < 1 || (oldColor?.alpha ?? 1) < 1;
$: transparency = (newColor?.alpha ?? 255) < 255 || (oldColor?.alpha ?? 255) < 255;
</script>
<LayoutRow
@@ -2,13 +2,13 @@
import { createEventDispatcher } from "svelte";
import ColorPicker from "/src/components/floating-menus/ColorPicker.svelte";
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
import { contrastingOutlineFactor, fillChoiceColor, fillChoiceGradientStops } from "/src/utility-functions/colors";
import type { FillChoice, MenuDirection, ActionShortcut } from "/wrapper/pkg/graphite_wasm_wrapper";
import { contrastingOutlineFactor, fillChoiceUIColor, fillChoiceUIGradientStops } from "/src/utility-functions/colors";
import type { FillChoiceUI, MenuDirection, ActionShortcut } from "/wrapper/pkg/graphite_wasm_wrapper";
const dispatch = createEventDispatcher<{ value: FillChoice; startHistoryTransaction: undefined }>();
const dispatch = createEventDispatcher<{ value: FillChoiceUI; startHistoryTransaction: undefined }>();
// Content
export let value: FillChoice;
export let value: FillChoiceUI;
export let chosenGradient: string | undefined = undefined;
export let allowNone = false;
// export let allowTransparency = false; // TODO: Implement
@@ -29,10 +29,10 @@
$: outlineFactor = contrastingOutlineFactor(value, "--color-3-darkgray", 0.01);
$: outlined = outlineFactor > 0.0001;
$: gradientStops = fillChoiceGradientStops(value);
$: solidColor = fillChoiceColor(value);
$: gradientStops = fillChoiceUIGradientStops(value);
$: solidColor = fillChoiceUIColor(value);
$: none = value === "None";
$: transparency = gradientStops ? gradientStops.color.some((color) => color.alpha < 1) : solidColor ? solidColor.alpha < 1 : false;
$: transparency = gradientStops ? gradientStops.color.some((color) => color.alpha < 255) : solidColor ? solidColor.alpha < 255 : false;
</script>
<LayoutCol
@@ -3,20 +3,20 @@
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
import IconButton from "/src/components/widgets/buttons/IconButton.svelte";
import Separator from "/src/components/widgets/labels/Separator.svelte";
import { createColor } from "/src/utility-functions/colors";
import type { Color } from "/wrapper/pkg/graphite_wasm_wrapper";
import { createSRgba8 } from "/src/utility-functions/colors";
import type { SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
type PresetColor = "Black" | "White" | "Red" | "Yellow" | "Green" | "Cyan" | "Blue" | "Magenta";
const PURE_COLORS: Record<PresetColor, [number, number, number]> = {
Black: [0, 0, 0],
White: [1, 1, 1],
Red: [1, 0, 0],
Yellow: [1, 1, 0],
Green: [0, 1, 0],
Cyan: [0, 1, 1],
Blue: [0, 0, 1],
Magenta: [1, 0, 1],
White: [255, 255, 255],
Red: [255, 0, 0],
Yellow: [255, 255, 0],
Green: [0, 255, 0],
Cyan: [0, 255, 255],
Blue: [0, 0, 255],
Magenta: [255, 0, 255],
};
const PURE_COLORS_GRAYABLE: [PresetColor, string, string][] = [
["Red", "#ff0000", "#4c4c4c"],
@@ -28,7 +28,7 @@
];
const dispatch = createEventDispatcher<{
preset: Color | "None";
preset: SRGBA8 | "None";
eyedropperColorCode: string;
}>();
@@ -37,7 +37,7 @@
function pickPreset(preset: PresetColor | "None") {
if (disabled) return;
dispatch("preset", preset === "None" ? "None" : createColor(...PURE_COLORS[preset], 1));
dispatch("preset", preset === "None" ? "None" : createSRgba8(...PURE_COLORS[preset], 255));
}
// TODO: Replace this temporary usage of the browser eyedropper API, that only works in Chromium-based browsers, with the custom color sampler system used by the Eyedropper tool
@@ -3,7 +3,7 @@
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
import type { TooltipStore } from "/src/stores/tooltip";
import { colorContrastingColor, colorOpaque, colorToHexNoAlpha, colorToRgbCSS, createColor, createColorFromHSVA } from "/src/utility-functions/colors";
import { sRgba8ContrastingColor, sRgba8Opaque, sRgba8ToHexNoAlpha, sRgba8ToRgbCSS, createSRgba8, createSRgba8FromHsva } from "/src/utility-functions/colors";
const dispatch = createEventDispatcher<{
update: { hue: number; saturation: number; value: number; alpha: number };
@@ -184,20 +184,20 @@
removeEvents();
});
$: newColor = isNone ? undefined : createColorFromHSVA(hue, saturation, value, alpha);
$: opaqueHueColor = createColorFromHSVA(hue, 1, 1, 1);
$: opaqueColorOnly = newColor ? colorOpaque(newColor) : createColor(0, 0, 0, 1);
$: newColor = isNone ? undefined : createSRgba8FromHsva(hue, saturation, value, alpha);
$: opaqueHueColor = createSRgba8FromHsva(hue, 1, 1, 1);
$: opaqueColorOnly = newColor ? sRgba8Opaque(newColor) : createSRgba8(0, 0, 0, 255);
</script>
<LayoutRow
class="visual-color-pickers-input"
classes={{ disabled }}
styles={{
"--hue-color": colorToRgbCSS(opaqueHueColor),
"--hue-color-contrasting": colorContrastingColor(opaqueHueColor),
"--opaque-color": colorToHexNoAlpha(opaqueColorOnly),
"--opaque-color-contrasting": colorContrastingColor(opaqueColorOnly),
"--new-color-contrasting": colorContrastingColor(newColor),
"--hue-color": sRgba8ToRgbCSS(opaqueHueColor),
"--hue-color-contrasting": sRgba8ContrastingColor(opaqueHueColor),
"--opaque-color": sRgba8ToHexNoAlpha(opaqueColorOnly),
"--opaque-color-contrasting": sRgba8ContrastingColor(opaqueColorOnly),
"--new-color-contrasting": sRgba8ContrastingColor(newColor),
}}
>
{@const hueDescription = "The shade along the spectrum of the rainbow."}
@@ -3,14 +3,14 @@
import ColorPicker from "/src/components/floating-menus/ColorPicker.svelte";
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
import { fillChoiceColor, colorToRgbaCSS } from "/src/utility-functions/colors";
import type { Color, EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
import { fillChoiceUIColor, sRgba8ToRgbaCSS } from "/src/utility-functions/colors";
import type { SRGBA8, EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
const editor = getContext<EditorWrapper>("editor");
// Content
export let primary: Color;
export let secondary: Color;
export let primary: SRGBA8;
export let secondary: SRGBA8;
let primaryOpen = false;
let secondaryOpen = false;
@@ -25,37 +25,37 @@
secondaryOpen = true;
}
function primaryColorChanged(color: Color) {
editor.updatePrimaryColor(color.red, color.green, color.blue, color.alpha);
function primaryColorChanged(color: SRGBA8) {
editor.updatePrimaryColor(color);
}
function secondaryColorChanged(color: Color) {
editor.updateSecondaryColor(color.red, color.green, color.blue, color.alpha);
function secondaryColorChanged(color: SRGBA8) {
editor.updateSecondaryColor(color);
}
</script>
<LayoutCol class="working-colors-button">
<LayoutRow class="primary swatch">
<button on:click={clickPrimarySwatch} class:open={primaryOpen} style:--swatch-color={colorToRgbaCSS(primary)} data-floating-menu-spawner data-block-hover-transfer tabindex="0"></button>
<button on:click={clickPrimarySwatch} class:open={primaryOpen} style:--swatch-color={sRgba8ToRgbaCSS(primary)} data-floating-menu-spawner data-block-hover-transfer tabindex="0"></button>
<ColorPicker
open={primaryOpen}
on:open={({ detail }) => (primaryOpen = detail)}
colorOrGradient={{ Solid: primary }}
on:colorOrGradient={({ detail }) => {
const color = fillChoiceColor(detail);
const color = fillChoiceUIColor(detail);
if (color) primaryColorChanged(color);
}}
direction="Right"
/>
</LayoutRow>
<LayoutRow class="secondary swatch">
<button on:click={clickSecondarySwatch} class:open={secondaryOpen} style:--swatch-color={colorToRgbaCSS(secondary)} data-floating-menu-spawner data-block-hover-transfer tabindex="0"></button>
<button on:click={clickSecondarySwatch} class:open={secondaryOpen} style:--swatch-color={sRgba8ToRgbaCSS(secondary)} data-floating-menu-spawner data-block-hover-transfer tabindex="0"></button>
<ColorPicker
open={secondaryOpen}
on:open={({ detail }) => (secondaryOpen = detail)}
colorOrGradient={{ Solid: secondary }}
on:colorOrGradient={({ detail }) => {
const color = fillChoiceColor(detail);
const color = fillChoiceUIColor(detail);
if (color) secondaryColorChanged(color);
}}
direction="Right"