Skip to content

Commit

Permalink
Allow preset hex values to set color arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
flatpickles committed Oct 19, 2023
1 parent 4dc9358 commit 4e4b72d
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 19 deletions.
5 changes: 3 additions & 2 deletions src/lib/base/ProjectLoading/ParamInference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export default class ParamInference {

// Assign numeric array value tokens via numeric array or hex string
const hexMetaString = intentions?.metaStrings?.find((meta) =>
meta.match(/^#([0-9a-f]{6})$/)
meta.match(/^#([0-9a-f]{6})$/i)
);
if (ParamGuards.isNumericArrayParamConfig(newConfig)) {
if (intentions?.numericArrayValues?.length) {
Expand Down Expand Up @@ -208,7 +208,8 @@ export default class ParamInference {
token.match(/^\[(\s*-?\d*\.{0,1}\d+\s*,\s*)+(\s*-?\d*\.{0,1}\d+\s*)\]$/)
);
const potentialMetaTokens = stringTokens.filter(
(token) => token.match(/(^[a-zA-Z]+$)|(^#([0-9a-f]{6})$)/) && !token.match(/true|false/)
(token) =>
token.match(/(^[a-zA-Z]+$)|(^#([0-9a-fA-F]{6})$)/) && !token.match(/true|false/)
);

// Return an object with adapted tokens, if any
Expand Down
73 changes: 61 additions & 12 deletions src/lib/base/ProjectLoading/PresetUtil.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { NumericArrayParamStyle } from '../ConfigModels/ParamConfigs/NumericArrayParamConfig';
import {
ParamGuards,
type AnyParamValueType,
type ParamValueType
} from '../ConfigModels/ParamTypes';
import ColorConversions from '../Util/ColorConversions';
import ParamValueProvider from './ParamValueProvider';
import { defaultPresetKey, type Preset } from './PresetLoader';
import type { ProjectTuple } from './ProjectLoader';
Expand Down Expand Up @@ -49,29 +51,54 @@ export default class PresetUtil {
const paramConfig = projectTuple.params.find((param) => param.key === paramKey);
if (!paramConfig) throw new Error(`Param ${paramKey} not found`);

// Don't continue for parameters that can't have values
// Don't proceed with parameters that can't have values
if (!ParamGuards.isConfigTypeWithDefault(paramConfig)) continue;

// Type value assertion with config type, apologies
let typedValue = paramValue as ParamValueType<typeof paramConfig>;

// Allow hex values to be used for color numeric array params
if (
ParamGuards.isNumericArrayParamConfig(paramConfig) &&
typeof typedValue === 'string' &&
typedValue.match(/^#[0-9a-f]{6}$/i)
) {
if (
[NumericArrayParamStyle.ByteColor, NumericArrayParamStyle.UnitColor].includes(
paramConfig.style
)
) {
typedValue = ColorConversions.hexToRgb(
typedValue,
paramConfig.style === NumericArrayParamStyle.UnitColor
);
} else {
throw new Error(
`Preset value type mismatch for param ${paramKey}: hex strings can only be assigned for numeric arrays with color style.`
);
}
}

// Do some basic type checking
const currentValue = Object.getOwnPropertyDescriptor(
projectTuple.project,
paramConfig.key
)?.value;
if (typeof paramValue !== typeof currentValue) {
if (typeof typedValue !== typeof currentValue) {
throw new Error(
`Preset value type mismatch for param ${paramKey}: ${typeof paramValue} vs ${typeof currentValue}`
`Preset value type mismatch for param ${paramKey}: ${typeof typedValue} vs ${typeof currentValue}`
);
} else if (Array.isArray(paramValue) !== Array.isArray(currentValue)) {
} else if (Array.isArray(typedValue) !== Array.isArray(currentValue)) {
throw new Error(
`Preset value type mismatch for param ${paramKey}: array vs non-array`
);
} else if (Array.isArray(paramValue) && Array.isArray(currentValue)) {
if (paramValue.length !== currentValue.length) {
} else if (Array.isArray(typedValue) && Array.isArray(currentValue)) {
if (typedValue.length !== currentValue.length) {
throw new Error(
`Preset value type mismatch for param ${paramKey}: array lengths ${paramValue.length} vs ${currentValue.length}`
`Preset value type mismatch for param ${paramKey}: array lengths ${typedValue.length} vs ${currentValue.length}`
);
}
const arrayTypeMismatch = paramValue.some(
const arrayTypeMismatch = typedValue.some(
(v: number, i: number) => typeof v !== typeof currentValue[i]
);
if (arrayTypeMismatch) {
Expand All @@ -80,9 +107,9 @@ export default class PresetUtil {
);
}
}
const typedValue = (
Array.isArray(paramValue) ? [...paramValue] : paramValue
) as ParamValueType<typeof paramConfig>; // sorry

// Copy array to avoid aliasing if need be
typedValue = Array.isArray(typedValue) ? [...typedValue] : typedValue;

// Set object and stored values if they've changed
if (JSON.stringify(typedValue) !== JSON.stringify(currentValue)) {
Expand Down Expand Up @@ -116,7 +143,29 @@ export default class PresetUtil {
projectTuple.project,
paramKey
)?.value;
if (JSON.stringify(paramValue) !== JSON.stringify(currentValue)) return false;
const possibleValues: (typeof paramValue)[] = [paramValue];

// Allow hex values to be used for three-element numeric array params
// More permissive than applyPreset, because we don't need user-legible errors here
if (
Array.isArray(currentValue) &&
currentValue.length === 3 &&
typeof paramValue === 'string' &&
paramValue.match(/^#[0-9a-f]{6}$/i)
) {
possibleValues.push(ColorConversions.hexToRgb(paramValue, false));
possibleValues.push(ColorConversions.hexToRgb(paramValue, true));
}

// Check if any of the possible values match the current value
let anyMatch = false;
for (const possibleValue of possibleValues) {
if (JSON.stringify(possibleValue) === JSON.stringify(currentValue)) {
anyMatch = true;
break;
}
}
if (!anyMatch) return false;
}
return true;
}
Expand Down
6 changes: 3 additions & 3 deletions tests/unit/ParamInference.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ describe('ParamInference.paramWithInference', () => {
const inference2 = ParamInference.paramWithInference(
NumericArrayParamConfigDefaults,
InferenceMode.ShaderFile,
'23, origin, true, -4, #9933cc'
'23, origin, true, -4, #9933CC'
) as NumericArrayParamConfig;
expect(inference2.default).toEqual([0.6, 0.2, 0.8]);
});
Expand Down Expand Up @@ -609,14 +609,14 @@ describe('ParamInference.intentionsFrom', () => {
});

it('includes hex strings in metaStrings', () => {
const intentions1 = ParamInference.intentionsFrom('#dd00ff');
const intentions1 = ParamInference.intentionsFrom('#dd00FF');
expect(intentions1.name).toBeUndefined();
expect(intentions1.range).toBeUndefined();
expect(intentions1.step).toBeUndefined();
expect(intentions1.numberValues.length).toBe(0);
expect(intentions1.booleanValues.length).toBe(0);
expect(intentions1.numericArrayValues.length).toBe(0);
expect(intentions1.metaStrings).toEqual(['#dd00ff']);
expect(intentions1.metaStrings).toEqual(['#dd00FF']);

const intentions2 = ParamInference.intentionsFrom(
'#ff00ff, step, step 01, true, testDeux, false, 100.3 to 3'
Expand Down
70 changes: 68 additions & 2 deletions tests/unit/PresetUtil.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import { NumberParamConfigDefaults } from '$lib/base/ConfigModels/ParamConfigs/N
import { defaultPresetKey } from '$lib/base/ProjectLoading/PresetLoader';
import type { ProjectTuple } from '$lib/base/ProjectLoading/ProjectLoader';
import { ProjectConfigDefaults } from '$lib/base/ConfigModels/ProjectConfig';
import { NumericArrayParamConfigDefaults } from '$lib/base/ConfigModels/ParamConfigs/NumericArrayParamConfig';
import {
NumericArrayParamConfigDefaults,
NumericArrayParamStyle
} from '$lib/base/ConfigModels/ParamConfigs/NumericArrayParamConfig';
import { FunctionParamConfigDefaults } from '$lib/base/ConfigModels/ParamConfigs/FunctionParamConfig';

class TestProject extends Project {
testNumberKey = 42;
testArrayKey = [42, 21, 10.5];
functionParamKey = () => 42;
colorArrayUnit = [0.5, 0.5, 0.5];
colorArrayByte = [100, 100, 100];
}

const testProject = new TestProject();
Expand All @@ -33,6 +38,16 @@ const projectTuple: ProjectTuple = {
{
...FunctionParamConfigDefaults,
key: 'functionParamKey'
},
{
...NumericArrayParamConfigDefaults,
key: 'colorArrayUnit',
style: NumericArrayParamStyle.UnitColor
},
{
...NumericArrayParamConfigDefaults,
key: 'colorArrayByte',
style: NumericArrayParamStyle.ByteColor
}
],
presets: {
Expand Down Expand Up @@ -80,6 +95,21 @@ const projectTuple: ProjectTuple = {
'values': {
testArrayKey: [42, 84]
}
},
'arrayColorsWithHex': {
'title': 'Test Preset',
'key': 'arrayColorsWithHex',
'values': {
colorArrayUnit: '#ffffff',
colorArrayByte: '#ffffff'
}
},
'badColor': {
'title': 'Test Preset',
'key': 'badColors',
'values': {
testArrayKey: '#ffffff'
}
}
}
};
Expand Down Expand Up @@ -115,6 +145,8 @@ describe('Preset application via PresetUtil.applyPreset', () => {
vi.clearAllMocks();
testProject.testNumberKey = 42;
testProject.testArrayKey = [42, 21, 10.5];
testProject.colorArrayUnit = [0.5, 0.5, 0.5];
testProject.colorArrayByte = [100, 100, 100];
});

it("applies a preset to a project's values", () => {
Expand Down Expand Up @@ -177,6 +209,18 @@ describe('Preset application via PresetUtil.applyPreset', () => {
'Preset value type mismatch for param testArrayKey: array lengths 2 vs 3'
);
});

it('assigns color strings as numeric arrays with color style', () => {
PresetUtil.applyPreset(projectTuple, 'arrayColorsWithHex');
expect(testProject.colorArrayUnit).toEqual([1, 1, 1]);
expect(testProject.colorArrayByte).toEqual([255, 255, 255]);
});

it('throws an error if a color string is assigned to a non-color array', () => {
expect(() => PresetUtil.applyPreset(projectTuple, 'badColor')).toThrowError(
'Preset value type mismatch for param testArrayKey: hex strings can only be assigned for numeric arrays with color style.'
);
});
});

describe('Checking if presets are applied via PresetUtil.presetIsApplied', () => {
Expand All @@ -186,6 +230,8 @@ describe('Checking if presets are applied via PresetUtil.presetIsApplied', () =>
vi.clearAllMocks();
testProject.testNumberKey = 42;
testProject.testArrayKey = [42, 21, 10.5];
testProject.colorArrayUnit = [0.5, 0.5, 0.5];
testProject.colorArrayByte = [100, 100, 100];
});

it('returns true if preset has been applied', () => {
Expand All @@ -194,6 +240,12 @@ describe('Checking if presets are applied via PresetUtil.presetIsApplied', () =>
expect(isApplied).toBe(true);
});

it('returns true if hex color preset has been applied', () => {
PresetUtil.applyPreset(projectTuple, 'arrayColorsWithHex');
const isApplied = PresetUtil.presetIsApplied(projectTuple, 'arrayColorsWithHex');
expect(isApplied).toBe(true);
});

it('returns true if default is applied', () => {
const isApplied = PresetUtil.presetIsApplied(projectTuple, defaultPresetKey);
expect(isApplied).toBe(true);
Expand All @@ -213,6 +265,16 @@ describe('Checking if presets are applied via PresetUtil.presetIsApplied', () =>
expect(isApplied).toBe(false);
});

it('returns false if hex color preset has been applied then modified, true if changed back', () => {
PresetUtil.applyPreset(projectTuple, 'arrayColorsWithHex');
testProject.colorArrayUnit[1] = 0.3;
const isApplied = PresetUtil.presetIsApplied(projectTuple, 'arrayColorsWithHex');
expect(isApplied).toBe(false);
testProject.colorArrayUnit[1] = 1.0;
const isApplied2 = PresetUtil.presetIsApplied(projectTuple, 'arrayColorsWithHex');
expect(isApplied2).toBe(true);
});

it('throws an error if the preset is not found', () => {
expect(() => PresetUtil.presetIsApplied(projectTuple, 'ghostPreset')).toThrowError(
'Preset ghostPreset not found'
Expand All @@ -227,6 +289,8 @@ describe('Preset export via PresetUtil.exportPresetFile', () => {
vi.clearAllMocks();
testProject.testNumberKey = 42;
testProject.testArrayKey = [42, 21, 10.5];
testProject.colorArrayUnit = [0.5, 0.5, 0.5];
testProject.colorArrayByte = [100, 100, 100];
});

it('should create a preset file', () => {
Expand Down Expand Up @@ -255,7 +319,9 @@ describe('Preset export via PresetUtil.exportPresetFile', () => {
key: 'TestPreset',
values: {
testNumberKey: 85,
testArrayKey: [18, 19, 21]
testArrayKey: [18, 19, 21],
colorArrayUnit: [0.5, 0.5, 0.5],
colorArrayByte: [100, 100, 100]
}
},
null,
Expand Down

0 comments on commit 4e4b72d

Please sign in to comment.