mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 14:18:04 +08:00
Wrap serialized gradient stops in a new GradientRamp struct and unify FillChoice (#4400)
* Introduce the GradientRamp exchange struct as the serialized TaggedValue::Gradient payload * Unify FillChoice and FillChoiceUI into one enum generic over color format, carrying GradientRamp stops * Rename the TaggedValue::Gradient variant to GradientRamp to match its payload * Move the Color variant into the tagged_value macro list since its stored and wire forms match
This commit is contained in:
committed by
Dennis Kobert
parent
4d8729d87c
commit
360321e0ce
@@ -5,14 +5,14 @@
|
||||
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
||||
import WidgetLayout from "/src/components/widgets/WidgetLayout.svelte";
|
||||
import type { ColorPickerCallbacks, ColorPickerStore } from "/src/stores/color-picker";
|
||||
import type { EditorWrapper, FillChoiceUI, MenuDirection } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { EditorWrapper, FillChoice, MenuDirection, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoiceUI; startHistoryTransaction: undefined; commitHistoryTransaction: undefined }>();
|
||||
const dispatch = createEventDispatcher<{ colorOrGradient: FillChoice<SRGBA8>; startHistoryTransaction: undefined; commitHistoryTransaction: undefined }>();
|
||||
|
||||
const editor = getContext<EditorWrapper>("editor");
|
||||
const colorPickerStore = getContext<ColorPickerStore>("colorPicker");
|
||||
|
||||
export let colorOrGradient: FillChoiceUI;
|
||||
export let colorOrGradient: FillChoice<SRGBA8>;
|
||||
export let allowNone = false;
|
||||
// export let allowTransparency = false; // TODO: Implement
|
||||
export let disabled = false;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import type { DocumentStore } from "/src/stores/document";
|
||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||
import type { MessageBody } from "/src/subscriptions-router";
|
||||
import { fillChoiceUIColor, createSRgba8 } from "/src/utility-functions/colors";
|
||||
import { fillChoiceColor, createSRgba8 } from "/src/utility-functions/colors";
|
||||
import { pasteFile } from "/src/utility-functions/files";
|
||||
import { textInputCleanup } from "/src/utility-functions/keyboard-entry";
|
||||
import { rasterizeSVGCanvas } from "/src/utility-functions/rasterization";
|
||||
@@ -680,7 +680,7 @@
|
||||
}}
|
||||
colorOrGradient={{ Solid: gradientStopPickerColor || createSRgba8(0, 0, 0, 255) }}
|
||||
on:colorOrGradient={({ detail }) => {
|
||||
const color = fillChoiceUIColor(detail);
|
||||
const color = fillChoiceColor(detail);
|
||||
if (color) editor.updateGradientStopColor(color);
|
||||
}}
|
||||
on:startHistoryTransaction={() => editor.startGradientStopColorTransaction()}
|
||||
|
||||
@@ -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 { parseFillChoiceUI } from "/src/utility-functions/colors";
|
||||
import { parseFillChoice } 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" | ...)
|
||||
@@ -138,7 +138,7 @@
|
||||
component: ColorInput,
|
||||
getProps: (props, index) => ({
|
||||
...props,
|
||||
value: parseFillChoiceUI(props.value),
|
||||
value: parseFillChoice(props.value),
|
||||
$$events: {
|
||||
value: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
||||
startHistoryTransaction: () => widgetValueCommit(index, props.value),
|
||||
@@ -150,7 +150,7 @@
|
||||
getProps: (props, index) => ({
|
||||
...props,
|
||||
$$events: {
|
||||
// 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).
|
||||
// The widget dispatches `"None"` or a bare `SRGBA8`, wrap the color in `{ Solid: ... }` so the payload matches the Rust `FillChoice` shape 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),
|
||||
},
|
||||
|
||||
@@ -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, fillChoiceUIColor, fillChoiceUIGradient } from "/src/utility-functions/colors";
|
||||
import type { FillChoiceUI, MenuDirection, ActionShortcut } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import { contrastingOutlineFactor, fillChoiceColor, fillChoiceGradient } from "/src/utility-functions/colors";
|
||||
import type { FillChoice, MenuDirection, ActionShortcut, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
const dispatch = createEventDispatcher<{ value: FillChoiceUI; startHistoryTransaction: undefined }>();
|
||||
const dispatch = createEventDispatcher<{ value: FillChoice<SRGBA8>; startHistoryTransaction: undefined }>();
|
||||
|
||||
// Content
|
||||
export let value: FillChoiceUI;
|
||||
export let value: FillChoice<SRGBA8>;
|
||||
export let chosenGradient: string | undefined = undefined;
|
||||
export let allowNone = false;
|
||||
// export let allowTransparency = false; // TODO: Implement
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
$: outlineFactor = contrastingOutlineFactor(value, "--color-3-darkgray", 0.01);
|
||||
$: outlined = outlineFactor > 0.0001;
|
||||
$: gradient = fillChoiceUIGradient(value);
|
||||
$: solidColor = fillChoiceUIColor(value);
|
||||
$: gradient = fillChoiceGradient(value);
|
||||
$: solidColor = fillChoiceColor(value);
|
||||
$: none = value === "None";
|
||||
$: transparency = gradient ? gradient.color.some((color) => color.alpha < 255) : solidColor ? solidColor.alpha < 255 : false;
|
||||
</script>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
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 { fillChoiceUIColor, sRgba8ToRgbaCSS } from "/src/utility-functions/colors";
|
||||
import { fillChoiceColor, sRgba8ToRgbaCSS } from "/src/utility-functions/colors";
|
||||
import type { SRGBA8, EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
const editor = getContext<EditorWrapper>("editor");
|
||||
@@ -42,7 +42,7 @@
|
||||
on:open={({ detail }) => (primaryOpen = detail)}
|
||||
colorOrGradient={{ Solid: primary }}
|
||||
on:colorOrGradient={({ detail }) => {
|
||||
const color = fillChoiceUIColor(detail);
|
||||
const color = fillChoiceColor(detail);
|
||||
if (color) primaryColorChanged(color);
|
||||
}}
|
||||
direction="Right"
|
||||
@@ -55,7 +55,7 @@
|
||||
on:open={({ detail }) => (secondaryOpen = detail)}
|
||||
colorOrGradient={{ Solid: secondary }}
|
||||
on:colorOrGradient={({ detail }) => {
|
||||
const color = fillChoiceUIColor(detail);
|
||||
const color = fillChoiceColor(detail);
|
||||
if (color) secondaryColorChanged(color);
|
||||
}}
|
||||
direction="Right"
|
||||
|
||||
@@ -2,10 +2,10 @@ import { writable } from "svelte/store";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||
import { patchLayout } from "/src/utility-functions/widgets";
|
||||
import type { FillChoiceUI, Layout } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { FillChoice, Layout, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
export type ColorPickerCallbacks = {
|
||||
onColorChanged?: (value: FillChoiceUI) => void;
|
||||
onColorChanged?: (value: FillChoice<SRGBA8>) => void;
|
||||
onStartTransaction?: () => void;
|
||||
onCommitTransaction?: () => void;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FillChoiceUI, GradientStops, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { FillChoice, GradientRamp, GradientStops, SRGBA8 } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
// Channels can have any range (0-1, 0-255, 0-100, 0-360) in the context they are being used in, these are just containers for the numbers
|
||||
export type HSV = { h: number; s: number; v: number };
|
||||
@@ -150,7 +150,7 @@ export function sRgba8ContrastingColor(color: SRGBA8 | undefined): "black" | "wh
|
||||
return luminance > Math.sqrt(1.05 * 0.05) - 0.05 ? "black" : "white";
|
||||
}
|
||||
|
||||
export function contrastingOutlineFactor(value: FillChoiceUI, proximityColor: string | [string, string], proximityRange: number): number {
|
||||
export function contrastingOutlineFactor(value: FillChoice<SRGBA8>, proximityColor: string | [string, string], proximityRange: number): number {
|
||||
const pair = Array.isArray(proximityColor) ? [proximityColor[0], proximityColor[1]] : [proximityColor, proximityColor];
|
||||
const [range1, range2] = pair.map((color) => sRgba8FromCSS(window.getComputedStyle(document.body).getPropertyValue(color)));
|
||||
|
||||
@@ -167,7 +167,7 @@ export function contrastingOutlineFactor(value: FillChoiceUI, proximityColor: st
|
||||
return (1 - Math.min(distance / proximityRange, 1)) * (1 - sRgba8ToHSV(color).s);
|
||||
};
|
||||
|
||||
const gradient = fillChoiceUIGradient(value);
|
||||
const gradient = fillChoiceGradient(value);
|
||||
if (gradient) {
|
||||
if (gradient.color.length === 0) return 0;
|
||||
|
||||
@@ -177,7 +177,7 @@ export function contrastingOutlineFactor(value: FillChoiceUI, proximityColor: st
|
||||
return Math.min(first, last);
|
||||
}
|
||||
|
||||
return contrast(fillChoiceUIColor(value));
|
||||
return contrast(fillChoiceColor(value));
|
||||
}
|
||||
|
||||
// GRADIENT UTILITY FUNCTIONS
|
||||
@@ -186,21 +186,25 @@ export function isGradientStops(value: unknown): value is GradientStops<SRGBA8>
|
||||
return typeof value === "object" && value !== null && "color" in value && Array.isArray(value.color);
|
||||
}
|
||||
|
||||
export function isGradientRamp(value: unknown): value is GradientRamp<SRGBA8> {
|
||||
return typeof value === "object" && value !== null && "stops" in value && isGradientStops(value.stops);
|
||||
}
|
||||
|
||||
// FILL CHOICE UTILITY FUNCTIONS
|
||||
|
||||
export function fillChoiceUIColor(value: FillChoiceUI): SRGBA8 | undefined {
|
||||
export function fillChoiceColor(value: FillChoice<SRGBA8>): SRGBA8 | undefined {
|
||||
if (typeof value === "object" && "Solid" in value) return value.Solid;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function fillChoiceUIGradient(value: FillChoiceUI): GradientStops<SRGBA8> | undefined {
|
||||
if (typeof value === "object" && "Gradient" in value) return value.Gradient;
|
||||
export function fillChoiceGradient(value: FillChoice<SRGBA8>): GradientStops<SRGBA8> | undefined {
|
||||
if (typeof value === "object" && "Gradient" in value) return value.Gradient.stops;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function parseFillChoiceUI(value: unknown): FillChoiceUI {
|
||||
export function parseFillChoice(value: unknown): FillChoice<SRGBA8> {
|
||||
if (value === "None" || value === undefined || value === null) return "None";
|
||||
if (typeof value === "object" && value !== null && "Solid" in value && isSRgba8(value.Solid)) return { Solid: value.Solid };
|
||||
if (typeof value === "object" && value !== null && "Gradient" in value && isGradientStops(value.Gradient)) return { Gradient: value.Gradient };
|
||||
if (typeof value === "object" && value !== null && "Gradient" in value && isGradientRamp(value.Gradient)) return { Gradient: value.Gradient };
|
||||
return "None";
|
||||
}
|
||||
|
||||
@@ -390,9 +390,9 @@ mod editor_commands {
|
||||
}
|
||||
|
||||
/// Initialize the Rust color picker handler with a starting value (used when the frontend `<ColorPicker />` opens).
|
||||
fn open_color_picker(initial_value: FillChoiceUI, allow_none: bool, disabled: bool) -> Message {
|
||||
fn open_color_picker(initial_value: FillChoiceSRGBA8, allow_none: bool, disabled: bool) -> Message {
|
||||
ColorPickerMessage::Open {
|
||||
initial_value: FillChoice::from(&initial_value),
|
||||
initial_value: FillChoice::from(&initial_value.0),
|
||||
allow_none,
|
||||
disabled,
|
||||
}
|
||||
@@ -659,6 +659,17 @@ mod editor_commands {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "editor")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct FillChoiceSRGBA8(
|
||||
/// Concrete wasm boundary form of the generic [`FillChoice`], since a `#[wasm_bindgen]` argument's TS declaration names its type without the generic's argument.
|
||||
#[tsify(type = "FillChoice<SRGBA8>")]
|
||||
pub graphene_std::vector::style::FillChoice<graphene_std::color::SRGBA8>,
|
||||
);
|
||||
#[cfg(not(feature = "editor"))]
|
||||
pub type FillChoiceSRGBA8 = Any;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
|
||||
#[tsify(from_wasm_abi)]
|
||||
pub struct Any(#[tsify(type = "any")] serde_json::Value);
|
||||
@@ -685,5 +696,4 @@ editor_proxy_types! {
|
||||
DockingSplitDirection = editor::messages::portfolio::utility_types::DockingSplitDirection;
|
||||
PanelTypes = Vec<editor::messages::portfolio::utility_types::PanelType>;
|
||||
SRGBA8 = graphene_std::color::SRGBA8;
|
||||
FillChoiceUI = graphene_std::vector::style::FillChoiceUI;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user