From f293a2804df7d1299bcfbe34421569a4b13c0654 Mon Sep 17 00:00:00 2001 From: cocopon Date: Sun, 29 Jan 2023 17:34:43 +0900 Subject: [PATCH] Refactor color types, #450 --- .../core/src/blade/folder/api/folder-test.ts | 6 +- .../core/src/common/converter/parser-test.ts | 19 + packages/core/src/common/converter/parser.ts | 11 + packages/core/src/index.ts | 2 + .../color/controller/a-palette.ts | 10 +- .../color/controller/color-picker-test.ts | 6 +- .../color/controller/color-picker.ts | 8 +- .../color/controller/color-swatch.ts | 6 +- .../color/controller/color-text-test.ts | 6 +- .../color/controller/color-text.ts | 19 +- .../input-binding/color/controller/color.ts | 14 +- .../color/controller/h-palette.ts | 10 +- .../color/controller/sv-palette.ts | 13 +- .../color/converter/color-number-test.ts | 52 +-- .../color/converter/color-number.ts | 38 +- .../color/converter/color-object-test.ts | 89 ++++ .../color/converter/color-object.ts | 30 ++ .../color/converter/color-string-test.ts | 87 +--- .../color/converter/color-string.ts | 229 +++++----- .../color/converter/writer-test.ts | 26 +- .../input-binding/color/converter/writer.ts | 23 +- .../input-binding/color/model/color-test.ts | 410 ++---------------- .../src/input-binding/color/model/color.ts | 128 ++---- .../input-binding/color/model/colors-test.ts | 166 +++++++ .../src/input-binding/color/model/colors.ts | 98 +++++ .../color/model/float-color-test.ts | 86 ++++ .../input-binding/color/model/float-color.ts | 43 ++ .../color/model/int-color-test.ts | 256 +++++++++++ .../input-binding/color/model/int-color.ts | 47 ++ .../input-binding/color/plugin-number-test.ts | 9 +- .../src/input-binding/color/plugin-number.ts | 13 +- .../input-binding/color/plugin-object-test.ts | 27 +- .../src/input-binding/color/plugin-object.ts | 41 +- .../src/input-binding/color/plugin-string.ts | 14 +- .../src/input-binding/color/view/a-palette.ts | 10 +- .../input-binding/color/view/color-swatch.ts | 6 +- .../src/input-binding/color/view/h-palette.ts | 8 +- .../input-binding/color/view/sv-palette.ts | 6 +- packages/tweakpane/src/doc/ts/panepaint.ts | 12 +- packages/tweakpane/src/doc/ts/route/index.ts | 8 +- .../tweakpane/src/main/ts/pane/input-test.ts | 6 +- 41 files changed, 1235 insertions(+), 863 deletions(-) create mode 100644 packages/core/src/common/converter/parser-test.ts create mode 100644 packages/core/src/input-binding/color/converter/color-object-test.ts create mode 100644 packages/core/src/input-binding/color/converter/color-object.ts create mode 100644 packages/core/src/input-binding/color/model/colors-test.ts create mode 100644 packages/core/src/input-binding/color/model/colors.ts create mode 100644 packages/core/src/input-binding/color/model/float-color-test.ts create mode 100644 packages/core/src/input-binding/color/model/float-color.ts create mode 100644 packages/core/src/input-binding/color/model/int-color-test.ts create mode 100644 packages/core/src/input-binding/color/model/int-color.ts diff --git a/packages/core/src/blade/folder/api/folder-test.ts b/packages/core/src/blade/folder/api/folder-test.ts index 182d1c455..ff28a9657 100644 --- a/packages/core/src/blade/folder/api/folder-test.ts +++ b/packages/core/src/blade/folder/api/folder-test.ts @@ -3,7 +3,7 @@ import {describe, it} from 'mocha'; import {ValueMap} from '../../../common/model/value-map'; import {ViewProps} from '../../../common/model/view-props'; -import {Color} from '../../../input-binding/color/model/color'; +import {IntColor} from '../../../input-binding/color/model/int-color'; import {createTestWindow} from '../../../misc/dom-test-util'; import {createDefaultPluginPool} from '../../../plugin/plugins'; import {testBladeContainer} from '../../common/api/blade-rack-test'; @@ -195,14 +195,14 @@ describe(FolderApi.name, () => { expected: '#224488', params: { propertyValue: '#123', - newInternalValue: new Color([0x22, 0x44, 0x88], 'rgb'), + newInternalValue: new IntColor([0x22, 0x44, 0x88], 'rgb'), }, }, { expected: 'rgb(0, 127, 255)', params: { propertyValue: 'rgb(10, 20, 30)', - newInternalValue: new Color([0, 127, 255], 'rgb'), + newInternalValue: new IntColor([0, 127, 255], 'rgb'), }, }, ].forEach(({expected, params}) => { diff --git a/packages/core/src/common/converter/parser-test.ts b/packages/core/src/common/converter/parser-test.ts new file mode 100644 index 000000000..7be924d5e --- /dev/null +++ b/packages/core/src/common/converter/parser-test.ts @@ -0,0 +1,19 @@ +import * as assert from 'assert'; +import {describe, it} from 'mocha'; + +import {composeParsers} from './parser'; + +describe(composeParsers.name, () => { + it('should use the first parser', () => { + const p = composeParsers([ + (t) => parseInt(t) * 10, + (t) => parseInt(t) * 100, + ]); + assert.strictEqual(p('123'), 1230); + }); + + it('should delegate a value to the next parser', () => { + const p = composeParsers([(_) => null, (t) => parseInt(t) * 100]); + assert.strictEqual(p('123'), 12300); + }); +}); diff --git a/packages/core/src/common/converter/parser.ts b/packages/core/src/common/converter/parser.ts index 7dd5066f9..8ac4043ad 100644 --- a/packages/core/src/common/converter/parser.ts +++ b/packages/core/src/common/converter/parser.ts @@ -1 +1,12 @@ export type Parser = (text: string) => T | null; + +export function composeParsers(parsers: Parser[]): Parser { + return (text) => { + return parsers.reduce((result: T | null, parser) => { + if (result !== null) { + return result; + } + return parser(text); + }, null); + }; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 94f605c7f..67ab38d58 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -109,6 +109,8 @@ export * from './input-binding/color/controller/color'; export * from './input-binding/color/converter/color-number'; export * from './input-binding/color/converter/color-string'; export * from './input-binding/color/model/color'; +export * from './input-binding/color/model/float-color'; +export * from './input-binding/color/model/int-color'; export * from './input-binding/color/plugin-number'; export * from './input-binding/color/plugin-object'; export * from './input-binding/color/plugin-string'; diff --git a/packages/core/src/input-binding/color/controller/a-palette.ts b/packages/core/src/input-binding/color/controller/a-palette.ts index ffeefbe4e..0d956f53a 100644 --- a/packages/core/src/input-binding/color/controller/a-palette.ts +++ b/packages/core/src/input-binding/color/controller/a-palette.ts @@ -7,12 +7,12 @@ import { PointerHandler, PointerHandlerEvents, } from '../../../common/view/pointer-handler'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; import {getBaseStepForColor} from '../util'; import {APaletteView} from '../view/a-palette'; interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -20,7 +20,7 @@ interface Config { * @hidden */ export class APaletteController implements Controller { - public readonly value: Value; + public readonly value: Value; public readonly view: APaletteView; public readonly viewProps: ViewProps; private readonly ptHandler_: PointerHandler; @@ -58,7 +58,7 @@ export class APaletteController implements Controller { const c = this.value.rawValue; const [h, s, v] = c.getComponents('hsv'); - this.value.setRawValue(new Color([h, s, v, alpha], 'hsv'), opts); + this.value.setRawValue(new IntColor([h, s, v, alpha], 'hsv'), opts); } private onPointerDown_(ev: PointerHandlerEvents['down']): void { @@ -93,7 +93,7 @@ export class APaletteController implements Controller { const c = this.value.rawValue; const [h, s, v, a] = c.getComponents('hsv'); - this.value.setRawValue(new Color([h, s, v, a + step], 'hsv'), { + this.value.setRawValue(new IntColor([h, s, v, a + step], 'hsv'), { forceEmit: false, last: false, }); diff --git a/packages/core/src/input-binding/color/controller/color-picker-test.ts b/packages/core/src/input-binding/color/controller/color-picker-test.ts index 1596e5b1e..5959b8f0d 100644 --- a/packages/core/src/input-binding/color/controller/color-picker-test.ts +++ b/packages/core/src/input-binding/color/controller/color-picker-test.ts @@ -5,12 +5,12 @@ import {createValue} from '../../../common/model/values'; import {ViewProps} from '../../../common/model/view-props'; import {createTestWindow} from '../../../misc/dom-test-util'; import {TestUtil} from '../../../misc/test-util'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; import {ColorPickerController} from './color-picker'; describe(ColorPickerController.name, () => { it('should set initial color mode', () => { - const value = createValue(new Color([0, 0, 0], 'hsv')); + const value = createValue(new IntColor([0, 0, 0], 'hsv')); const win = createTestWindow(); const doc = win.document; const c = new ColorPickerController(doc, { @@ -24,7 +24,7 @@ describe(ColorPickerController.name, () => { }); it('should change hue of black in HSL', () => { - const value = createValue(new Color([0, 0, 0], 'rgb')); + const value = createValue(new IntColor([0, 0, 0], 'rgb')); const win = createTestWindow(); const doc = win.document; const c = new ColorPickerController(doc, { diff --git a/packages/core/src/input-binding/color/controller/color-picker.ts b/packages/core/src/input-binding/color/controller/color-picker.ts index d1df926d8..31e469475 100644 --- a/packages/core/src/input-binding/color/controller/color-picker.ts +++ b/packages/core/src/input-binding/color/controller/color-picker.ts @@ -10,8 +10,8 @@ import {connectValues} from '../../../common/model/value-sync'; import {createValue} from '../../../common/model/values'; import {ViewProps} from '../../../common/model/view-props'; import {NumberTextController} from '../../../common/number/controller/number-text'; -import {Color} from '../model/color'; import {ColorType} from '../model/color-model'; +import {IntColor} from '../model/int-color'; import {ColorPickerView} from '../view/color-picker'; import {APaletteController} from './a-palette'; import {ColorTextController} from './color-text'; @@ -21,7 +21,7 @@ import {SvPaletteController} from './sv-palette'; interface Config { colorType: ColorType; supportsAlpha: boolean; - value: Value; + value: Value; viewProps: ViewProps; } @@ -29,7 +29,7 @@ interface Config { * @hidden */ export class ColorPickerController implements Controller { - public readonly value: Value; + public readonly value: Value; public readonly view: ColorPickerView; public readonly viewProps: ViewProps; private readonly alphaIcs_: { @@ -82,7 +82,7 @@ export class ColorPickerController implements Controller { backward: (p, s) => { const comps = p.rawValue.getComponents(); comps[3] = s.rawValue; - return new Color(comps, p.rawValue.mode); + return new IntColor(comps, p.rawValue.mode); }, }); } diff --git a/packages/core/src/input-binding/color/controller/color-swatch.ts b/packages/core/src/input-binding/color/controller/color-swatch.ts index 476ba7e1a..a8d83ae6a 100644 --- a/packages/core/src/input-binding/color/controller/color-swatch.ts +++ b/packages/core/src/input-binding/color/controller/color-swatch.ts @@ -1,11 +1,11 @@ import {Controller} from '../../../common/controller/controller'; import {Value} from '../../../common/model/value'; import {ViewProps} from '../../../common/model/view-props'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; import {ColorSwatchView} from '../view/color-swatch'; interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -13,7 +13,7 @@ interface Config { * @hidden */ export class ColorSwatchController implements Controller { - public readonly value: Value; + public readonly value: Value; public readonly view: ColorSwatchView; public readonly viewProps: ViewProps; diff --git a/packages/core/src/input-binding/color/controller/color-text-test.ts b/packages/core/src/input-binding/color/controller/color-text-test.ts index da13f5dbb..996ff35f3 100644 --- a/packages/core/src/input-binding/color/controller/color-text-test.ts +++ b/packages/core/src/input-binding/color/controller/color-text-test.ts @@ -6,8 +6,8 @@ import {createValue} from '../../../common/model/values'; import {ViewProps} from '../../../common/model/view-props'; import {createTestWindow} from '../../../misc/dom-test-util'; import {TestUtil} from '../../../misc/test-util'; -import {Color} from '../model/color'; import {ColorComponents3} from '../model/color-model'; +import {IntColor} from '../model/int-color'; import {ColorTextController} from './color-text'; describe(ColorTextController.name, () => { @@ -42,7 +42,7 @@ describe(ColorTextController.name, () => { testCase.expected, )}`, (done) => { const value = createValue( - new Color(testCase.params.components as ColorComponents3, 'rgb'), + new IntColor(testCase.params.components as ColorComponents3, 'rgb'), ); value.emitter.on('change', () => { assert.deepStrictEqual( @@ -108,7 +108,7 @@ describe(ColorTextController.name, () => { testCase.expected, )}`, (done) => { const value = createValue( - new Color(testCase.params.components as ColorComponents3, 'rgb'), + new IntColor(testCase.params.components as ColorComponents3, 'rgb'), ); value.emitter.on('change', () => { assert.deepStrictEqual( diff --git a/packages/core/src/input-binding/color/controller/color-text.ts b/packages/core/src/input-binding/color/controller/color-text.ts index af4853246..28ddc5046 100644 --- a/packages/core/src/input-binding/color/controller/color-text.ts +++ b/packages/core/src/input-binding/color/controller/color-text.ts @@ -11,7 +11,6 @@ import {createValue} from '../../../common/model/values'; import {ViewProps} from '../../../common/model/view-props'; import {NumberTextController} from '../../../common/number/controller/number-text'; import {Tuple3} from '../../../misc/type-util'; -import {Color} from '../model/color'; import { appendAlphaComponent, ColorMode, @@ -19,13 +18,15 @@ import { getColorMaxComponents, removeAlphaComponent, } from '../model/color-model'; +import {createColor, mapColorType} from '../model/colors'; +import {IntColor} from '../model/int-color'; import {getBaseStepForColor} from '../util'; import {ColorTextView} from '../view/color-text'; interface Config { colorType: ColorType; parser: Parser; - value: Value; + value: Value; viewProps: ViewProps; } @@ -75,7 +76,7 @@ function createComponentController( */ export class ColorTextController implements Controller { public readonly colorMode: Value; - public readonly value: Value; + public readonly value: Value; public readonly view: ColorTextView; public readonly viewProps: ViewProps; private readonly parser_: Parser; @@ -123,20 +124,20 @@ export class ColorTextController implements Controller { primary: this.value, secondary: cs.value, forward: (p) => { - return p.rawValue.getComponents( - this.colorMode.rawValue, - this.colorType_, - )[index]; + const mc = mapColorType(p.rawValue, this.colorType_); + return mc.getComponents(this.colorMode.rawValue)[index]; }, backward: (p, s) => { const pickedMode = this.colorMode.rawValue; - const comps = p.rawValue.getComponents(pickedMode, this.colorType_); + const mc = mapColorType(p.rawValue, this.colorType_); + const comps = mc.getComponents(pickedMode); comps[index] = s.rawValue; - return new Color( + const c = createColor( appendAlphaComponent(removeAlphaComponent(comps), comps[3]), pickedMode, this.colorType_, ); + return mapColorType(c, 'int'); }, }); }); diff --git a/packages/core/src/input-binding/color/controller/color.ts b/packages/core/src/input-binding/color/controller/color.ts index 0904ea538..a47126fee 100644 --- a/packages/core/src/input-binding/color/controller/color.ts +++ b/packages/core/src/input-binding/color/controller/color.ts @@ -11,8 +11,8 @@ import {connectValues} from '../../../common/model/value-sync'; import {ViewProps} from '../../../common/model/view-props'; import {PickerLayout} from '../../../common/params'; import {forceCast} from '../../../misc/type-util'; -import {Color} from '../model/color'; import {ColorType} from '../model/color-model'; +import {IntColor} from '../model/int-color'; import {ColorView} from '../view/color'; import {ColorPickerController} from './color-picker'; import {ColorSwatchController} from './color-swatch'; @@ -20,11 +20,11 @@ import {ColorSwatchController} from './color-swatch'; interface Config { colorType: ColorType; expanded: boolean; - formatter: Formatter; - parser: Parser; + formatter: Formatter; + parser: Parser; pickerLayout: PickerLayout; supportsAlpha: boolean; - value: Value; + value: Value; viewProps: ViewProps; } @@ -32,11 +32,11 @@ interface Config { * @hidden */ export class ColorController implements Controller { - public readonly value: Value; + public readonly value: Value; public readonly view: ColorView; public readonly viewProps: ViewProps; private readonly swatchC_: ColorSwatchController; - private readonly textC_: TextController; + private readonly textC_: TextController; private readonly pickerC_: ColorPickerController; private readonly popC_: PopupController | null; private readonly foldable_: Foldable; @@ -112,7 +112,7 @@ export class ColorController implements Controller { } } - get textController(): TextController { + get textController(): TextController { return this.textC_; } diff --git a/packages/core/src/input-binding/color/controller/h-palette.ts b/packages/core/src/input-binding/color/controller/h-palette.ts index c03d1871f..6c1aba663 100644 --- a/packages/core/src/input-binding/color/controller/h-palette.ts +++ b/packages/core/src/input-binding/color/controller/h-palette.ts @@ -8,12 +8,12 @@ import { PointerHandler, PointerHandlerEvents, } from '../../../common/view/pointer-handler'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; import {getBaseStepForColor} from '../util'; import {HPaletteView} from '../view/h-palette'; interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -21,7 +21,7 @@ interface Config { * @hidden */ export class HPaletteController implements Controller { - public readonly value: Value; + public readonly value: Value; public readonly view: HPaletteView; public readonly viewProps: ViewProps; private readonly ptHandler_: PointerHandler; @@ -65,7 +65,7 @@ export class HPaletteController implements Controller { const c = this.value.rawValue; const [, s, v, a] = c.getComponents('hsv'); - this.value.setRawValue(new Color([hue, s, v, a], 'hsv'), opts); + this.value.setRawValue(new IntColor([hue, s, v, a], 'hsv'), opts); } private onPointerDown_(ev: PointerHandlerEvents['down']): void { @@ -100,7 +100,7 @@ export class HPaletteController implements Controller { const c = this.value.rawValue; const [h, s, v, a] = c.getComponents('hsv'); - this.value.setRawValue(new Color([h + step, s, v, a], 'hsv'), { + this.value.setRawValue(new IntColor([h + step, s, v, a], 'hsv'), { forceEmit: false, last: false, }); diff --git a/packages/core/src/input-binding/color/controller/sv-palette.ts b/packages/core/src/input-binding/color/controller/sv-palette.ts index ce1e89016..9a625b761 100644 --- a/packages/core/src/input-binding/color/controller/sv-palette.ts +++ b/packages/core/src/input-binding/color/controller/sv-palette.ts @@ -13,12 +13,12 @@ import { PointerHandler, PointerHandlerEvents, } from '../../../common/view/pointer-handler'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; import {getBaseStepForColor} from '../util'; import {SvPaletteView} from '../view/sv-palette'; interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -26,7 +26,7 @@ interface Config { * @hidden */ export class SvPaletteController implements Controller { - public readonly value: Value; + public readonly value: Value; public readonly view: SvPaletteView; public readonly viewProps: ViewProps; private readonly ptHandler_: PointerHandler; @@ -64,7 +64,10 @@ export class SvPaletteController implements Controller { const value = mapRange(d.point.y, 0, d.bounds.height, 100, 0); const [h, , , a] = this.value.rawValue.getComponents('hsv'); - this.value.setRawValue(new Color([h, saturation, value, a], 'hsv'), opts); + this.value.setRawValue( + new IntColor([h, saturation, value, a], 'hsv'), + opts, + ); } private onPointerDown_(ev: PointerHandlerEvents['down']): void { @@ -101,7 +104,7 @@ export class SvPaletteController implements Controller { return; } - this.value.setRawValue(new Color([h, s + ds, v + dv, a], 'hsv'), { + this.value.setRawValue(new IntColor([h, s + ds, v + dv, a], 'hsv'), { forceEmit: false, last: false, }); diff --git a/packages/core/src/input-binding/color/converter/color-number-test.ts b/packages/core/src/input-binding/color/converter/color-number-test.ts index a765d6788..d4671a3a8 100644 --- a/packages/core/src/input-binding/color/converter/color-number-test.ts +++ b/packages/core/src/input-binding/color/converter/color-number-test.ts @@ -1,9 +1,8 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; import { - colorFromObject, colorFromRgbaNumber, colorFromRgbNumber, colorToRgbaNumber, @@ -11,45 +10,20 @@ import { numberToRgbColor, } from './color-number'; -describe('converter/color-number', () => { +describe(colorToRgbaNumber.name, () => { [ { - input: {r: 0x00, g: 0x78, b: 0xff}, - expected: { - r: 0x00, - g: 0x78, - b: 0xff, - a: 1, - }, - }, - { - input: {foo: 'bar'}, - expected: {r: 0, g: 0, b: 0, a: 1}, - }, - ].forEach((testCase) => { - context(`when input = ${JSON.stringify(testCase.input)}`, () => { - it('should convert object to color', () => { - assert.deepStrictEqual( - colorFromObject(testCase.input).toRgbaObject(), - testCase.expected, - ); - }); - }); - }); - - [ - { - input: new Color([0, 0, 0, 1], 'rgb'), + input: new IntColor([0, 0, 0, 1], 'rgb'), frgba: 'rgba(0, 0, 0, 1.00)', number: 0x000000ff, }, { - input: new Color([0, 127, 255, 0.5], 'rgb'), + input: new IntColor([0, 127, 255, 0.5], 'rgb'), frgba: 'rgba(0, 127, 255, 0.50)', number: 0x007fff7f, }, { - input: new Color([255, 255, 255, 0], 'rgb'), + input: new IntColor([255, 255, 255, 0], 'rgb'), frgba: 'rgba(255, 255, 255, 0.00)', number: 0xffffff00, }, @@ -60,22 +34,24 @@ describe('converter/color-number', () => { }); }); }); +}); +describe(colorToRgbNumber.name, () => { [ { - input: new Color([0, 0, 0], 'rgb'), + input: new IntColor([0, 0, 0], 'rgb'), expected: 0x000000, }, { - input: new Color([0, 127, 255], 'rgb'), + input: new IntColor([0, 127, 255], 'rgb'), expected: 0x007fff, }, { - input: new Color([0.1, 127.2, 255.4], 'rgb'), + input: new IntColor([0.1, 127.2, 255.4], 'rgb'), expected: 0x007fff, }, { - input: new Color([255, 255, 255], 'rgb'), + input: new IntColor([255, 255, 255], 'rgb'), expected: 0xffffff, }, ].forEach((testCase) => { @@ -85,7 +61,9 @@ describe('converter/color-number', () => { }); }); }); +}); +describe(numberToRgbColor.name, () => { [ { expected: {r: 0x11, g: 0x22, b: 0x33, a: 1}, @@ -106,7 +84,9 @@ describe('converter/color-number', () => { }); }); }); +}); +describe(colorFromRgbNumber.name, () => { [ { input: 0x0078ff, @@ -131,7 +111,9 @@ describe('converter/color-number', () => { }); }); }); +}); +describe(colorFromRgbaNumber.name, () => { [ { input: 0x9abcde33, diff --git a/packages/core/src/input-binding/color/converter/color-number.ts b/packages/core/src/input-binding/color/converter/color-number.ts index 04086952b..8b10b0d2f 100644 --- a/packages/core/src/input-binding/color/converter/color-number.ts +++ b/packages/core/src/input-binding/color/converter/color-number.ts @@ -1,22 +1,11 @@ import {mapRange} from '../../../common/number-util'; -import {Color} from '../model/color'; -import {ColorType, removeAlphaComponent} from '../model/color-model'; +import {removeAlphaComponent} from '../model/color-model'; +import {IntColor} from '../model/int-color'; -// TODO: Make type required in the next major version /** * @hidden */ -export function colorFromObject(value: unknown, opt_type?: ColorType): Color { - if (Color.isColorObject(value)) { - return Color.fromObject(value, opt_type); - } - return Color.black(opt_type); -} - -/** - * @hidden - */ -export function colorToRgbNumber(value: Color): number { +export function colorToRgbNumber(value: IntColor): number { return removeAlphaComponent(value.getComponents('rgb')).reduce( (result, comp) => { return (result << 8) | (Math.floor(comp) & 0xff); @@ -28,7 +17,7 @@ export function colorToRgbNumber(value: Color): number { /** * @hidden */ -export function colorToRgbaNumber(value: Color): number { +export function colorToRgbaNumber(value: IntColor): number { return ( value.getComponents('rgb').reduce((result, comp, index) => { const hex = Math.floor(index === 3 ? comp * 255 : comp) & 0xff; @@ -40,15 +29,18 @@ export function colorToRgbaNumber(value: Color): number { /** * @hidden */ -export function numberToRgbColor(num: number): Color { - return new Color([(num >> 16) & 0xff, (num >> 8) & 0xff, num & 0xff], 'rgb'); +export function numberToRgbColor(num: number): IntColor { + return new IntColor( + [(num >> 16) & 0xff, (num >> 8) & 0xff, num & 0xff], + 'rgb', + ); } /** * @hidden */ -export function numberToRgbaColor(num: number): Color { - return new Color( +export function numberToRgbaColor(num: number): IntColor { + return new IntColor( [ (num >> 24) & 0xff, (num >> 16) & 0xff, @@ -62,9 +54,9 @@ export function numberToRgbaColor(num: number): Color { /** * @hidden */ -export function colorFromRgbNumber(value: unknown): Color { +export function colorFromRgbNumber(value: unknown): IntColor { if (typeof value !== 'number') { - return Color.black(); + return IntColor.black(); } return numberToRgbColor(value); } @@ -72,9 +64,9 @@ export function colorFromRgbNumber(value: unknown): Color { /** * @hidden */ -export function colorFromRgbaNumber(value: unknown): Color { +export function colorFromRgbaNumber(value: unknown): IntColor { if (typeof value !== 'number') { - return Color.black(); + return IntColor.black(); } return numberToRgbaColor(value); } diff --git a/packages/core/src/input-binding/color/converter/color-object-test.ts b/packages/core/src/input-binding/color/converter/color-object-test.ts new file mode 100644 index 000000000..ffe93e45a --- /dev/null +++ b/packages/core/src/input-binding/color/converter/color-object-test.ts @@ -0,0 +1,89 @@ +import * as assert from 'assert'; +import {describe, it} from 'mocha'; + +import {Color} from '../model/color'; +import {colorFromObject} from './color-object'; + +describe(colorFromObject.name, () => { + [ + { + input: {r: 0x00, g: 0x78, b: 0xff}, + expected: { + r: 0x00, + g: 0x78, + b: 0xff, + a: 1, + }, + }, + { + input: {foo: 'bar'}, + expected: {r: 0, g: 0, b: 0, a: 1}, + }, + ].forEach((testCase) => { + describe(`when input = ${JSON.stringify(testCase.input)}`, () => { + it('should convert object to color', () => { + assert.deepStrictEqual( + colorFromObject(testCase.input, 'int').toRgbaObject(), + testCase.expected, + ); + }); + }); + }); + + [ + { + expected: {r: 10, g: 20, b: 30, a: 1}, + object: {r: 10, g: 20, b: 30}, + }, + { + expected: {r: 0, g: 255, b: 0, a: 1}, + object: {r: -1, g: 300, b: 0}, + }, + { + expected: {r: 0, g: 255, b: 0, a: 0.5}, + object: {r: -1, g: 300, b: 0, a: 0.5}, + }, + ].forEach(({expected, object}) => { + describe(`when ${JSON.stringify(object)}`, () => { + const c = colorFromObject(object, 'int'); + + it('should create int color from object', () => { + assert.deepStrictEqual(c.getComponents('rgb'), [ + expected.r, + expected.g, + expected.b, + expected.a, + ]); + }); + }); + }); + + [ + { + params: {r: 0, g: 0.5, b: 1}, + expected: [0, 0.5, 1, 1], + }, + { + params: {r: 0.1, g: 0.2, b: 0.3, a: 0.5}, + expected: [0.1, 0.2, 0.3, 0.5], + }, + ].forEach(({params, expected}) => { + describe(`when ${JSON.stringify(params)}`, () => { + it('should create float color from object', () => { + const c = colorFromObject(params, 'float'); + assert.deepStrictEqual(c.getComponents(), expected); + }); + }); + }); + + it('should create color for invalid color type', () => { + let c: Color | null = null; + + assert.doesNotThrow(() => { + c = colorFromObject({r: 0, g: 0, b: 0}, 'invalid' as any); + }); + + assert.ok(typeof (c as any).mode === 'string'); + assert.ok(typeof (c as any).type === 'string'); + }); +}); diff --git a/packages/core/src/input-binding/color/converter/color-object.ts b/packages/core/src/input-binding/color/converter/color-object.ts new file mode 100644 index 000000000..fc0806fdc --- /dev/null +++ b/packages/core/src/input-binding/color/converter/color-object.ts @@ -0,0 +1,30 @@ +import { + Color, + createColorComponentsFromRgbObject, + isColorObject, +} from '../model/color'; +import {ColorType} from '../model/color-model'; +import {mapColorType} from '../model/colors'; +import {FloatColor} from '../model/float-color'; +import {IntColor} from '../model/int-color'; + +/** + * @hidden + */ +export function colorFromObject(value: unknown, type: 'int'): IntColor; +export function colorFromObject(value: unknown, type: 'float'): FloatColor; +export function colorFromObject(value: unknown, type: ColorType): Color; +export function colorFromObject(value: unknown, type: ColorType): Color { + if (!isColorObject(value)) { + return mapColorType(IntColor.black(), type); + } + if (type === 'int') { + const comps = createColorComponentsFromRgbObject(value); + return new IntColor(comps, 'rgb'); + } + if (type === 'float') { + const comps = createColorComponentsFromRgbObject(value); + return new FloatColor(comps, 'rgb'); + } + return mapColorType(IntColor.black(), 'int'); +} diff --git a/packages/core/src/input-binding/color/converter/color-string-test.ts b/packages/core/src/input-binding/color/converter/color-string-test.ts index a5fa89cfe..5f844bc98 100644 --- a/packages/core/src/input-binding/color/converter/color-string-test.ts +++ b/packages/core/src/input-binding/color/converter/color-string-test.ts @@ -2,12 +2,12 @@ import * as assert from 'assert'; import {describe as context, describe, it} from 'mocha'; import {TestUtil} from '../../../misc/test-util'; -import {Color} from '../model/color'; import { ColorComponents3, ColorComponents4, ColorMode, } from '../model/color-model'; +import {IntColor} from '../model/int-color'; import { colorToFunctionalHslaString, colorToFunctionalHslString, @@ -16,8 +16,8 @@ import { colorToHexRgbaString, colorToHexRgbString, colorToObjectRgbString, - createColorStringBindingReader, createColorStringParser, + readIntColorString, } from './color-string'; const DELTA = 1e-5; @@ -179,32 +179,24 @@ describe(createColorStringParser.name, () => { context(`when input = ${JSON.stringify(testCase.input)}`, () => { it('should convert string to color', () => { assert.deepStrictEqual( - createColorStringBindingReader('int')(testCase.input).toRgbaObject(), + readIntColorString(testCase.input).toRgbaObject(), testCase.expected, ); }); }); }); - it('should format color with specified stringifier', () => { - const stringifier = (color: Color): string => { - return String(color); - }; - const c = new Color([0, 127, 255], 'rgb'); - assert.strictEqual(stringifier(c), stringifier(c)); - }); - [ { - input: new Color([0, 0, 0], 'rgb'), + input: new IntColor([0, 0, 0], 'rgb'), expected: '#000000', }, { - input: new Color([0, 127, 255], 'rgb'), + input: new IntColor([0, 127, 255], 'rgb'), expected: '#007fff', }, { - input: new Color([255, 255, 255], 'rgb'), + input: new IntColor([255, 255, 255], 'rgb'), expected: '#ffffff', }, ].forEach((testCase) => { @@ -220,17 +212,17 @@ describe(createColorStringParser.name, () => { [ { - input: new Color([0, 0, 0, 1], 'rgb'), + input: new IntColor([0, 0, 0, 1], 'rgb'), frgba: 'rgba(0, 0, 0, 1.00)', number: 0x000000ff, }, { - input: new Color([0, 127, 255, 0.5], 'rgb'), + input: new IntColor([0, 127, 255, 0.5], 'rgb'), frgba: 'rgba(0, 127, 255, 0.50)', number: 0x007fff7f, }, { - input: new Color([255, 255, 255, 0], 'rgb'), + input: new IntColor([255, 255, 255, 0], 'rgb'), frgba: 'rgba(255, 255, 255, 0.00)', number: 0xffffff00, }, @@ -247,55 +239,22 @@ describe(createColorStringParser.name, () => { [ { - input: new Color([0, 0, 0], 'rgb'), - expected: { - int: 'rgb(0, 0, 0)', - float: 'rgb(0.00, 0.00, 0.00)', - }, + input: new IntColor([0, 0, 0], 'rgb'), + expected: 'rgb(0, 0, 0)', }, { - input: new Color([0, 127, 255], 'rgb'), - expected: { - int: 'rgb(0, 127, 255)', - float: 'rgb(0.00, 0.50, 1.00)', - }, + input: new IntColor([0, 127, 255], 'rgb'), + expected: 'rgb(0, 127, 255)', }, { - input: new Color([255, 255, 255], 'rgb'), - expected: { - int: 'rgb(255, 255, 255)', - float: 'rgb(1.00, 1.00, 1.00)', - }, + input: new IntColor([255, 255, 255], 'rgb'), + expected: 'rgb(255, 255, 255)', }, ].forEach((testCase) => { context(`when input = ${JSON.stringify(testCase.input)}`, () => { it('should convert color to RGB string', () => { assert.strictEqual( colorToFunctionalRgbString(testCase.input), - testCase.expected.int, - ); - assert.strictEqual( - colorToFunctionalRgbString(testCase.input, 'float'), - testCase.expected.float, - ); - }); - }); - }); - - [ - { - input: new Color([0, 0, 0, 1], 'rgb', 'int'), - expected: 'rgba(0.00, 0.00, 0.00, 1.00)', - }, - { - input: new Color([0, 127, 255, 1], 'rgb', 'int'), - expected: 'rgba(0.00, 0.50, 1.00, 1.00)', - }, - ].forEach((testCase) => { - context(`when input = ${JSON.stringify(testCase.input)}`, () => { - it('should convert color to float RGBA string', () => { - assert.strictEqual( - colorToFunctionalRgbaString(testCase.input, 'float'), testCase.expected, ); }); @@ -304,14 +263,14 @@ describe(createColorStringParser.name, () => { [ { - input: new Color([0, 0, 0], 'rgb', 'int'), + input: new IntColor([0, 0, 0], 'rgb'), expected: { int: '{r: 0, g: 0, b: 0}', float: '{r: 0.00, g: 0.00, b: 0.00}', }, }, { - input: new Color([0, 127, 255], 'rgb', 'int'), + input: new IntColor([0, 127, 255], 'rgb'), expected: { int: '{r: 0, g: 127, b: 255}', float: '{r: 0.00, g: 0.50, b: 1.00}', @@ -334,21 +293,21 @@ describe(createColorStringParser.name, () => { [ { - input: new Color([0, 0, 0], 'hsl'), + input: new IntColor([0, 0, 0], 'hsl'), expected: { hsl: 'hsl(0, 0%, 0%)', hsla: 'hsla(0, 0%, 0%, 1.00)', }, }, { - input: new Color([0, 127, 255], 'hsl'), + input: new IntColor([0, 127, 255], 'hsl'), expected: { hsl: 'hsl(0, 100%, 100%)', hsla: 'hsla(0, 100%, 100%, 1.00)', }, }, { - input: new Color([255, 11, 22], 'hsl'), + input: new IntColor([255, 11, 22], 'hsl'), expected: { hsl: 'hsl(255, 11%, 22%)', hsla: 'hsla(255, 11%, 22%, 1.00)', @@ -414,7 +373,7 @@ describe(createColorStringParser.name, () => { ).forEach((testCase) => { context(`when ${JSON.stringify(testCase.components)}`, () => { it(`it should format to ${JSON.stringify(testCase.hex)}`, () => { - const c = new Color(testCase.components, 'rgb'); + const c = new IntColor(testCase.components, 'rgb'); const f = testCase.components.length === 3 ? colorToHexRgbString @@ -424,7 +383,7 @@ describe(createColorStringParser.name, () => { it(`it should format to ${JSON.stringify(testCase.rgb)}`, () => { const comps = testCase.components; - const c = new Color(comps, 'rgb'); + const c = new IntColor(comps, 'rgb'); if (comps.length === 3) { assert.strictEqual(colorToFunctionalRgbString(c), testCase.rgb); @@ -438,7 +397,7 @@ describe(createColorStringParser.name, () => { }); it('should format color with prefix', () => { - const c = new Color([0x12, 0x34, 0x56, 1], 'rgb'); + const c = new IntColor([0x12, 0x34, 0x56, 1], 'rgb'); assert.strictEqual(colorToHexRgbString(c, '0x'), '0x123456'); assert.strictEqual(colorToHexRgbaString(c, '0x'), '0x123456ff'); }); diff --git a/packages/core/src/input-binding/color/converter/color-string.ts b/packages/core/src/input-binding/color/converter/color-string.ts index 64306c1e4..d9e62a4e9 100644 --- a/packages/core/src/input-binding/color/converter/color-string.ts +++ b/packages/core/src/input-binding/color/converter/color-string.ts @@ -1,7 +1,6 @@ -import {BindingReader} from '../../../common/binding/binding'; import {Formatter} from '../../../common/converter/formatter'; import {createNumberFormatter} from '../../../common/converter/number'; -import {Parser} from '../../../common/converter/parser'; +import {composeParsers, Parser} from '../../../common/converter/parser'; import {formatPercentage} from '../../../common/converter/percentage'; import {constrainRange, mapRange} from '../../../common/number-util'; import {Color} from '../model/color'; @@ -12,6 +11,9 @@ import { ColorType, removeAlphaComponent, } from '../model/color-model'; +import {createColor, mapColorType} from '../model/colors'; +import {FloatColor} from '../model/float-color'; +import {IntColor} from '../model/int-color'; type StringColorNotation = 'func' | 'hex' | 'object'; @@ -83,11 +85,9 @@ function parseFunctionalRgbColorComponents( return comps; } -function createFunctionalRgbColorParser(type: ColorType): Parser { - return (text) => { - const comps = parseFunctionalRgbColorComponents(text); - return comps ? new Color(comps, 'rgb', type) : null; - }; +function parseFunctionalRgbColor(text: string): IntColor | null { + const comps = parseFunctionalRgbColorComponents(text); + return comps ? new IntColor(comps, 'rgb') : null; } function parseFunctionalRgbaColorComponents( @@ -117,14 +117,14 @@ function parseFunctionalRgbaColorComponents( return comps; } -function createFunctionalRgbaColorParser(type: ColorType): Parser { - return (text) => { - const comps = parseFunctionalRgbaColorComponents(text); - return comps ? new Color(comps, 'rgb', type) : null; - }; +function parseFunctionalRgbaColor(text: string): IntColor | null { + const comps = parseFunctionalRgbaColorComponents(text); + return comps ? new IntColor(comps, 'rgb') : null; } -function parseHslColorComponents(text: string): ColorComponents3 | null { +function parseFunctionalHslColorComponents( + text: string, +): ColorComponents3 | null { const m = text.match( /^hsl\(\s*([0-9A-Fa-f.]+(?:deg|grad|rad|turn)?)\s*,\s*([0-9A-Fa-f.]+%?)\s*,\s*([0-9A-Fa-f.]+%?)\s*\)$/, ); @@ -143,11 +143,9 @@ function parseHslColorComponents(text: string): ColorComponents3 | null { return comps; } -function createHslColorParser(type: ColorType): Parser { - return (text) => { - const comps = parseHslColorComponents(text); - return comps ? new Color(comps, 'hsl', type) : null; - }; +function parseFunctionalHslColor(text: string): IntColor | null { + const comps = parseFunctionalHslColorComponents(text); + return comps ? new IntColor(comps, 'hsl') : null; } function parseHslaColorComponents(text: string): ColorComponents4 | null { @@ -175,11 +173,9 @@ function parseHslaColorComponents(text: string): ColorComponents4 | null { return comps; } -function createHslaColorParser(type: ColorType): Parser { - return (text) => { - const comps = parseHslaColorComponents(text); - return comps ? new Color(comps, 'hsl', type) : null; - }; +function parseFunctionalHslaColor(text: string): IntColor | null { + const comps = parseHslaColorComponents(text); + return comps ? new IntColor(comps, 'hsl') : null; } function parseHexRgbColorComponents(text: string): ColorComponents3 | null { @@ -205,9 +201,9 @@ function parseHexRgbColorComponents(text: string): ColorComponents3 | null { return null; } -function parseHexRgbColor(text: string): Color | null { +function parseHexRgbColor(text: string): IntColor | null { const comps = parseHexRgbColorComponents(text); - return comps ? new Color(comps, 'rgb', 'int') : null; + return comps ? new IntColor(comps, 'rgb') : null; } function parseHexRgbaColorComponents(text: string): ColorComponents4 | null { @@ -238,9 +234,9 @@ function parseHexRgbaColorComponents(text: string): ColorComponents4 | null { return null; } -function parseHexRgbaColor(text: string): Color | null { +function parseHexRgbaColor(text: string): IntColor | null { const comps = parseHexRgbaColorComponents(text); - return comps ? new Color(comps, 'rgb', 'int') : null; + return comps ? new IntColor(comps, 'rgb') : null; } function parseObjectRgbColorComponents(text: string): ColorComponents3 | null { @@ -262,10 +258,12 @@ function parseObjectRgbColorComponents(text: string): ColorComponents3 | null { return comps; } +function createObjectRgbColorParser(type: 'int'): Parser; +function createObjectRgbColorParser(type: 'float'): Parser; function createObjectRgbColorParser(type: ColorType): Parser { return (text) => { const comps = parseObjectRgbColorComponents(text); - return comps ? new Color(comps, 'rgb', type) : null; + return comps ? createColor(comps, 'rgb', type) : null; }; } @@ -294,10 +292,12 @@ function parseObjectRgbaColorComponents(text: string): ColorComponents4 | null { return comps; } +function createObjectRgbaColorParser(type: 'int'): Parser; +function createObjectRgbaColorParser(type: 'float'): Parser; function createObjectRgbaColorParser(type: ColorType): Parser { return (text) => { const comps = parseObjectRgbaColorComponents(text); - return comps ? new Color(comps, 'rgb', type) : null; + return comps ? createColor(comps, 'rgb', type) : null; }; } @@ -340,7 +340,7 @@ const PARSER_AND_RESULT: { }, }, { - parser: parseHslColorComponents, + parser: parseFunctionalHslColorComponents, result: { alpha: false, mode: 'hsl', @@ -408,56 +408,45 @@ export function detectStringColorFormat( return null; } -const TYPE_TO_PARSERS: {[type in ColorType]: Parser[]} = { - int: [ +export function createColorStringParser(type: 'int'): Parser; +export function createColorStringParser(type: 'float'): Parser; +export function createColorStringParser(type: ColorType): Parser { + const parsers: Parser[] = [ parseHexRgbColor, parseHexRgbaColor, - createFunctionalRgbColorParser('int'), - createFunctionalRgbaColorParser('int'), - createHslColorParser('int'), - createHslaColorParser('int'), - createObjectRgbColorParser('int'), - createObjectRgbaColorParser('int'), - ], - float: [ - createFunctionalRgbColorParser('float'), - createFunctionalRgbaColorParser('float'), - createHslColorParser('float'), - createHslaColorParser('float'), - createObjectRgbColorParser('float'), - createObjectRgbaColorParser('float'), - ], -}; - -export function createColorStringBindingReader( - type: ColorType, -): BindingReader { - const parsers = TYPE_TO_PARSERS[type]; - return (value) => { - if (typeof value !== 'string') { - return Color.black(type); - } + parseFunctionalRgbColor, + parseFunctionalRgbaColor, + parseFunctionalHslColor, + parseFunctionalHslaColor, + ]; + if (type === 'int') { + parsers.push( + createObjectRgbColorParser('int'), + createObjectRgbaColorParser('int'), + ); + } + if (type === 'float') { + parsers.push( + createObjectRgbColorParser('float'), + createObjectRgbaColorParser('float'), + ); + } + const parser = composeParsers(parsers); - const result = parsers.reduce((prev: Color | null, parser) => { - if (prev) { - return prev; - } - return parser(value); - }, null); - return result ?? Color.black(type); + return (text) => { + const result = parser(text); + return result ? mapColorType(result, type) : null; }; } -export function createColorStringParser(type: ColorType): Parser { - const parsers = TYPE_TO_PARSERS[type]; - return (value) => { - return parsers.reduce((prev: Color | null, parser) => { - if (prev) { - return prev; - } - return parser(value); - }, null); - }; +export function readIntColorString(value: unknown): IntColor { + const parser = createColorStringParser('int'); + if (typeof value !== 'string') { + return IntColor.black(); + } + + const result = parser(value); + return result ?? IntColor.black(); } function zerofill(comp: number): string { @@ -468,7 +457,7 @@ function zerofill(comp: number): string { /** * @hidden */ -export function colorToHexRgbString(value: Color, prefix = '#'): string { +export function colorToHexRgbString(value: IntColor, prefix = '#'): string { const hexes = removeAlphaComponent(value.getComponents('rgb')) .map(zerofill) .join(''); @@ -478,7 +467,7 @@ export function colorToHexRgbString(value: Color, prefix = '#'): string { /** * @hidden */ -export function colorToHexRgbaString(value: Color, prefix = '#'): string { +export function colorToHexRgbaString(value: IntColor, prefix = '#'): string { const rgbaComps = value.getComponents('rgb'); const hexes = [rgbaComps[0], rgbaComps[1], rgbaComps[2], rgbaComps[3] * 255] .map(zerofill) @@ -486,50 +475,32 @@ export function colorToHexRgbaString(value: Color, prefix = '#'): string { return `${prefix}${hexes}`; } -// TODO: Make type required in the next major version /** * @hidden */ -export function colorToFunctionalRgbString( - value: Color, - opt_type?: ColorType, -): string { - const formatter = createNumberFormatter(opt_type === 'float' ? 2 : 0); - const comps = removeAlphaComponent(value.getComponents('rgb', opt_type)).map( - (comp) => formatter(comp), +export function colorToFunctionalRgbString(value: Color): string { + const formatter = createNumberFormatter(0); + const ci = mapColorType(value, 'int'); + const comps = removeAlphaComponent(ci.getComponents('rgb')).map((comp) => + formatter(comp), ); return `rgb(${comps.join(', ')})`; } -function createFunctionalRgbColorFormatter(type: ColorType): Formatter { - return (value) => { - return colorToFunctionalRgbString(value, type); - }; -} - -// TODO: Make type required in the next major version /** * @hidden */ -export function colorToFunctionalRgbaString( - value: Color, - opt_type?: ColorType, -): string { +export function colorToFunctionalRgbaString(value: Color): string { const aFormatter = createNumberFormatter(2); - const rgbFormatter = createNumberFormatter(opt_type === 'float' ? 2 : 0); - const comps = value.getComponents('rgb', opt_type).map((comp, index) => { + const rgbFormatter = createNumberFormatter(0); + const ci = mapColorType(value, 'int'); + const comps = ci.getComponents('rgb').map((comp, index) => { const formatter = index === 3 ? aFormatter : rgbFormatter; return formatter(comp); }); return `rgba(${comps.join(', ')})`; } -function createFunctionalRgbaColorFormatter(type: ColorType): Formatter { - return (value) => { - return colorToFunctionalRgbaString(value, type); - }; -} - /** * @hidden */ @@ -539,7 +510,8 @@ export function colorToFunctionalHslString(value: Color): string { formatPercentage, formatPercentage, ]; - const comps = removeAlphaComponent(value.getComponents('hsl')).map( + const ci = mapColorType(value, 'int'); + const comps = removeAlphaComponent(ci.getComponents('hsl')).map( (comp, index) => formatters[index](comp), ); return `hsl(${comps.join(', ')})`; @@ -555,7 +527,8 @@ export function colorToFunctionalHslaString(value: Color): string { formatPercentage, createNumberFormatter(2), ]; - const comps = value + const ci = mapColorType(value, 'int'); + const comps = ci .getComponents('hsl') .map((comp, index) => formatters[index](comp)); return `hsla(${comps.join(', ')})`; @@ -567,7 +540,8 @@ export function colorToFunctionalHslaString(value: Color): string { export function colorToObjectRgbString(value: Color, type: ColorType): string { const formatter = createNumberFormatter(type === 'float' ? 2 : 0); const names = ['r', 'g', 'b']; - const comps = removeAlphaComponent(value.getComponents('rgb', type)).map( + const cc = mapColorType(value, type); + const comps = removeAlphaComponent(cc.getComponents('rgb')).map( (comp, index) => `${names[index]}: ${formatter(comp)}`, ); return `{${comps.join(', ')}}`; @@ -584,7 +558,8 @@ export function colorToObjectRgbaString(value: Color, type: ColorType): string { const aFormatter = createNumberFormatter(2); const rgbFormatter = createNumberFormatter(type === 'float' ? 2 : 0); const names = ['r', 'g', 'b', 'a']; - const comps = value.getComponents('rgb', type).map((comp, index) => { + const cc = mapColorType(value, type); + const comps = cc.getComponents('rgb').map((comp, index) => { const formatter = index === 3 ? aFormatter : rgbFormatter; return `${names[index]}: ${formatter(comp)}`; }); @@ -608,7 +583,7 @@ const FORMAT_AND_STRINGIFIERS: FormatAndStringifier[] = [ notation: 'hex', type: 'int', }, - stringifier: colorToHexRgbString, + stringifier: colorToHexRgbString as Formatter, }, { format: { @@ -617,7 +592,25 @@ const FORMAT_AND_STRINGIFIERS: FormatAndStringifier[] = [ notation: 'hex', type: 'int', }, - stringifier: colorToHexRgbaString, + stringifier: colorToHexRgbaString as Formatter, + }, + { + format: { + alpha: false, + mode: 'rgb', + notation: 'func', + type: 'int', + }, + stringifier: colorToFunctionalRgbString, + }, + { + format: { + alpha: true, + mode: 'rgb', + notation: 'func', + type: 'int', + }, + stringifier: colorToFunctionalRgbaString, }, { format: { @@ -641,24 +634,6 @@ const FORMAT_AND_STRINGIFIERS: FormatAndStringifier[] = [ (prev: FormatAndStringifier[], type) => { return [ ...prev, - { - format: { - alpha: false, - mode: 'rgb', - notation: 'func', - type: type, - }, - stringifier: createFunctionalRgbColorFormatter(type), - }, - { - format: { - alpha: true, - mode: 'rgb', - notation: 'func', - type: type, - }, - stringifier: createFunctionalRgbaColorFormatter(type), - }, { format: { alpha: false, diff --git a/packages/core/src/input-binding/color/converter/writer-test.ts b/packages/core/src/input-binding/color/converter/writer-test.ts index d0171b14c..666ed726c 100644 --- a/packages/core/src/input-binding/color/converter/writer-test.ts +++ b/packages/core/src/input-binding/color/converter/writer-test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; import {BindingTarget} from '../../../common/binding/target'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; import {writeRgbaColorObject, writeRgbColorObject} from './writer'; describe('writer/color', () => { @@ -12,13 +12,13 @@ describe('writer/color', () => { }; const objFoo = obj.foo; const t = new BindingTarget(obj, 'foo'); - writeRgbaColorObject(t, new Color([128, 255, 0, 0.7], 'rgb')); + writeRgbaColorObject(t, new IntColor([128, 255, 0, 0.7], 'rgb'), 'int'); - assert.strictEqual(obj.foo, objFoo); - assert.strictEqual(obj.foo.r, 128); - assert.strictEqual(obj.foo.g, 255); - assert.strictEqual(obj.foo.b, 0); - assert.strictEqual(obj.foo.a, 0.7); + assert.strictEqual(obj.foo, objFoo, 'instance'); + assert.strictEqual(obj.foo.r, 128, 'r'); + assert.strictEqual(obj.foo.g, 255, 'g'); + assert.strictEqual(obj.foo.b, 0, 'b'); + assert.strictEqual(obj.foo.a, 0.7, 'a'); }); it('should write RGB color object value without destruction', () => { @@ -27,13 +27,13 @@ describe('writer/color', () => { }; const objFoo = obj.foo; const t = new BindingTarget(obj, 'foo'); - writeRgbColorObject(t, new Color([128, 255, 0, 0.7], 'rgb')); + writeRgbColorObject(t, new IntColor([128, 255, 0, 0.7], 'rgb'), 'int'); - assert.strictEqual(obj.foo, objFoo); - assert.strictEqual(obj.foo.r, 128); - assert.strictEqual(obj.foo.g, 255); - assert.strictEqual(obj.foo.b, 0); + assert.strictEqual(obj.foo, objFoo, 'instance'); + assert.strictEqual(obj.foo.r, 128, 'r'); + assert.strictEqual(obj.foo.g, 255, 'g'); + assert.strictEqual(obj.foo.b, 0, 'b'); // should not overwrite alpha component - assert.strictEqual(obj.foo.a, 0.5); + assert.strictEqual(obj.foo.a, 0.5, 'a'); }); }); diff --git a/packages/core/src/input-binding/color/converter/writer.ts b/packages/core/src/input-binding/color/converter/writer.ts index 3050c80b9..162c856f7 100644 --- a/packages/core/src/input-binding/color/converter/writer.ts +++ b/packages/core/src/input-binding/color/converter/writer.ts @@ -8,6 +8,8 @@ import { } from '../converter/color-string'; import {Color} from '../model/color'; import {ColorType} from '../model/color-model'; +import {mapColorType} from '../model/colors'; +import {IntColor} from '../model/int-color'; export function createColorStringWriter( format: StringColorFormat, @@ -22,48 +24,47 @@ export function createColorStringWriter( export function createColorNumberWriter( supportsAlpha: boolean, -): BindingWriter { +): BindingWriter { const colorToNumber = supportsAlpha ? colorToRgbaNumber : colorToRgbNumber; return (target, value) => { writePrimitive(target, colorToNumber(value)); }; } -// TODO: Make type required in the next version export function writeRgbaColorObject( target: BindingTarget, value: Color, - opt_type?: ColorType, + type: ColorType, ) { - const obj = value.toRgbaObject(opt_type); + const cc = mapColorType(value, type); + const obj = cc.toRgbaObject(); target.writeProperty('r', obj.r); target.writeProperty('g', obj.g); target.writeProperty('b', obj.b); target.writeProperty('a', obj.a); } -// TODO: Make type required in the next version export function writeRgbColorObject( target: BindingTarget, value: Color, - opt_type?: ColorType, + type: ColorType, ) { - const obj = value.toRgbaObject(opt_type); + const cc = mapColorType(value, type); + const obj = cc.toRgbaObject(); target.writeProperty('r', obj.r); target.writeProperty('g', obj.g); target.writeProperty('b', obj.b); } -// TODO: Make type required in the next version export function createColorObjectWriter( supportsAlpha: boolean, - opt_type?: ColorType, + type: ColorType, ): BindingWriter { return (target, inValue) => { if (supportsAlpha) { - writeRgbaColorObject(target, inValue, opt_type); + writeRgbaColorObject(target, inValue, type); } else { - writeRgbColorObject(target, inValue, opt_type); + writeRgbColorObject(target, inValue, type); } }; } diff --git a/packages/core/src/input-binding/color/model/color-test.ts b/packages/core/src/input-binding/color/model/color-test.ts index ea5c3f10f..ec5ef9b67 100644 --- a/packages/core/src/input-binding/color/model/color-test.ts +++ b/packages/core/src/input-binding/color/model/color-test.ts @@ -1,123 +1,11 @@ import * as assert from 'assert'; -import {describe as context, describe, it} from 'mocha'; +import {describe, it} from 'mocha'; -import {TestUtil} from '../../../misc/test-util'; -import {Color, RgbaColorObject} from './color'; -import { - ColorComponents3, - ColorComponents4, - ColorMode, - ColorType, -} from './color-model'; - -const DELTA = 1e-5; - -describe(Color.name, () => { - ( - [ - { - expected: {r: 10, g: 20, b: 30, a: 1}, - params: { - components: [10, 20, 30], - }, - }, - { - expected: {r: 0, g: 255, b: 0, a: 1}, - params: { - components: [-1, 300, 0], - }, - }, - ] as { - expected: RgbaColorObject; - params: { - components: ColorComponents3; - }; - }[] - ).forEach(({expected, params}) => { - context(`when ${JSON.stringify(params)}`, () => { - const c = new Color(params.components, 'rgb'); - it('should get components', () => { - assert.deepStrictEqual(c.getComponents('rgb'), [ - expected.r, - expected.g, - expected.b, - expected.a, - ]); - }); - it('should convert to object', () => { - assert.deepStrictEqual(c.toRgbaObject(), expected); - }); - }); - }); - - ( - [ - { - expected: [359, 0, 100, 1], - params: { - components: [359, 0, 100], - mode: 'hsv', - }, - }, - { - expected: [350, 0, 100, 1], - params: { - components: [-10, -10, 100], - mode: 'hsv', - }, - }, - { - expected: [10, 100, 100, 1], - params: { - components: [370, 110, 100], - mode: 'hsv', - }, - }, - { - expected: [359, 0, 100, 1], - params: { - components: [359, 0, 100], - mode: 'hsl', - }, - }, - { - expected: [350, 0, 100, 1], - params: { - components: [-10, -10, 100], - mode: 'hsl', - }, - }, - { - expected: [10, 100, 100, 1], - params: { - components: [370, 110, 100], - mode: 'hsl', - }, - }, - ] as { - expected: ColorComponents4; - params: { - components: ColorComponents3; - mode: ColorMode; - }; - }[] - ).forEach(({expected, params}) => { - context(`when ${JSON.stringify(params)}`, () => { - const c = new Color(params.components, params.mode); - it('should get components', () => { - assert.deepStrictEqual(c.getComponents(params.mode), expected); - }); - }); - }); - - [{r: 0, g: 127, b: 255}].forEach((input: unknown) => { - context(`when ${JSON.stringify(input)}`, () => { - it('should be regarded as rgb color object', () => { - assert.strictEqual(Color.isRgbColorObject(input), true); - }); - }); - }); +import {equalsColor, isRgbaColorObject, isRgbColorObject} from './color'; +import {FloatColor} from './float-color'; +import {IntColor} from './int-color'; +describe(isRgbColorObject.name, () => { [ null, undefined, @@ -131,91 +19,23 @@ describe(Color.name, () => { {r: 0, g: 127, b: null}, {r: 0, g: '127', b: 255}, ].forEach((input: unknown) => { - context(`when ${JSON.stringify(input)}`, () => { + describe(`when ${JSON.stringify(input)}`, () => { it('should not be regarded as rgb color object', () => { - assert.strictEqual(Color.isRgbColorObject(input), false); - }); - }); - }); - - [ - { - expected: {r: 10, g: 20, b: 30, a: 1}, - object: {r: 10, g: 20, b: 30}, - }, - { - expected: {r: 0, g: 255, b: 0, a: 1}, - object: {r: -1, g: 300, b: 0}, - }, - { - expected: {r: 0, g: 255, b: 0, a: 0.5}, - object: {r: -1, g: 300, b: 0, a: 0.5}, - }, - ].forEach(({expected, object}) => { - context(`when ${JSON.stringify(object)}`, () => { - const c = Color.fromObject(object); - it('should create instance from object', () => { - assert.deepStrictEqual(c.getComponents('rgb'), [ - expected.r, - expected.g, - expected.b, - expected.a, - ]); + assert.strictEqual(isRgbColorObject(input), false); }); }); }); - ([{mode: 'rgb'}, {mode: 'hsv'}] as {mode: ColorMode}[]).forEach(({mode}) => { - context(`when ${JSON.stringify({mode})}`, () => { - it('should get mode', () => { - const c = new Color([0, 0, 0], mode); - assert.strictEqual(c.mode, mode); - }); - }); - }); - - [ - { - expected: {components: [255, 0, 0, 0.5]}, - params: { - components: [255, 0, 0, 0.5], - fromMode: 'rgb', - toMode: 'rgb', - }, - }, - { - expected: {components: [0, 100, 100, 0]}, - params: { - components: [255, 0, 0, 0], - fromMode: 'rgb', - toMode: 'hsv', - }, - }, - { - expected: {components: [22, 24, 33, 1]}, - params: { - components: [229, 33, 13, 1], - fromMode: 'hsv', - toMode: 'rgb', - }, - }, - ].forEach(({expected, params}) => { - context(`when ${JSON.stringify(params)}`, () => { - it('should get components with specific mode', () => { - const c = new Color( - params.components as ColorComponents4, - params.fromMode as ColorMode, - ); - assert.deepStrictEqual( - c.getComponents(params.toMode as ColorMode).map((comp, index) => { - return index === 3 ? comp : Math.floor(comp); - }), - expected.components, - ); + [{r: 0, g: 127, b: 255}].forEach((input: unknown) => { + describe(`when ${JSON.stringify(input)}`, () => { + it('should be regarded as rgb color object', () => { + assert.strictEqual(isRgbColorObject(input), true); }); }); }); +}); +describe(isRgbaColorObject.name, () => { [ { expected: true, @@ -226,222 +46,48 @@ describe(Color.name, () => { params: {r: 10, g: 20, b: 30}, }, ].forEach(({expected, params}) => { - context(`when ${JSON.stringify(params)}`, () => { + describe(`when ${JSON.stringify(params)}`, () => { it('should detect RGBA color object', () => { - assert.strictEqual(Color.isRgbaColorObject(params), expected); + assert.strictEqual(isRgbaColorObject(params), expected); }); }); }); +}); +describe(equalsColor.name, () => { [ { expected: true, params: { - c1: new Color([10, 20, 30, 0.5], 'rgb', 'int'), - c2: new Color([10, 20, 30, 0.5], 'rgb', 'int'), + c1: new IntColor([10, 20, 30, 0.5], 'rgb'), + c2: new IntColor([10, 20, 30, 0.5], 'rgb'), }, }, { expected: false, params: { - c1: new Color([10, 20, 30], 'rgb', 'int'), - c2: new Color([10, 20, 30, 0.5], 'rgb', 'int'), + c1: new IntColor([10, 20, 30], 'rgb'), + c2: new IntColor([10, 20, 30, 0.5], 'rgb'), }, }, { expected: false, params: { - c1: new Color([10, 20, 30], 'rgb', 'int'), - c2: new Color([10, 20, 30], 'hsl', 'int'), - }, - }, - ].forEach(({expected, params}) => { - context(`when ${JSON.stringify(params)}`, () => { - it('should compare color', () => { - assert.strictEqual(Color.equals(params.c1, params.c2), expected); - }); - }); - }); - - [ - { - expected: {r: 10, g: 20, b: 30, a: 0.5}, - params: { - color: new Color([10, 20, 30, 0.5], 'rgb', 'int'), - type: 'int', + c1: new IntColor([10, 20, 30], 'rgb'), + c2: new IntColor([10, 20, 30], 'hsl'), }, }, { - expected: {r: 10 / 255, g: 20 / 255, b: 30 / 255, a: 0.5}, - params: { - color: new Color([10, 20, 30, 0.5], 'rgb', 'int'), - type: 'float', - }, - }, - ].forEach(({expected, params}) => { - context(`when ${JSON.stringify(params)}`, () => { - it('should export rgba color object', () => { - const o = Color.toRgbaObject(params.color, params.type as ColorType); - assert.ok(TestUtil.closeTo(o.r, expected.r, DELTA), 'r'); - assert.ok(TestUtil.closeTo(o.g, expected.g, DELTA), 'g'); - assert.ok(TestUtil.closeTo(o.b, expected.b, DELTA), 'b'); - assert.ok(TestUtil.closeTo(o.a, expected.a, DELTA), 'a'); - }); - }); - }); - - [ - { - expected: [0, 0, 0, 0], - params: { - components: [-10, -20, -30, -1], - mode: 'rgb', - type: 'int', - }, - }, - { - expected: [255, 255, 255, 1], - params: { - components: [300, 400, 500, 2], - mode: 'rgb', - type: 'int', - }, - }, - { - expected: [1, 1, 1, 1], - params: { - components: [2, 3, 4, 2], - mode: 'rgb', - type: 'float', - }, - }, - { - expected: [350, 0, 0, 0], - params: { - components: [-10, -20, -30, -1], - mode: 'hsl', - type: 'int', - }, - }, - { - expected: [40, 100, 100, 1], - params: { - components: [400, 500, 500, 2], - mode: 'hsl', - type: 'int', - }, - }, - { - expected: [0.5, 1, 1, 1], - params: { - components: [1.5, 3, 4, 2], - mode: 'hsl', - type: 'float', - }, - }, - { - expected: [350, 0, 0, 0], - params: { - components: [-10, -20, -30, -1], - mode: 'hsv', - type: 'int', - }, - }, - { - expected: [40, 100, 100, 1], - params: { - components: [400, 500, 500, 2], - mode: 'hsv', - type: 'int', - }, - }, - { - expected: [0.5, 1, 1, 1], + expected: false, params: { - components: [1.5, 3, 4, 2], - mode: 'hsl', - type: 'float', + c1: new IntColor([0, 1, 1], 'rgb'), + c2: new FloatColor([0, 1, 1], 'rgb'), }, }, ].forEach(({expected, params}) => { context(`when ${JSON.stringify(params)}`, () => { - it('should constrain components', () => { - const c = new Color( - params.components as ColorComponents4, - params.mode as ColorMode, - params.type as ColorType, - ); - const o = c.getComponents( - params.mode as ColorMode, - params.type as ColorType, - ); - expected.forEach((e, index) => { - assert.ok( - TestUtil.closeTo(o[index], e, DELTA), - `components[${index}]`, - ); - }); - }); - }); - }); - - ( - [ - { - params: { - hue: 360, - type: 'int', - }, - expected: 360, - }, - { - params: { - hue: 361, - type: 'int', - }, - expected: 1, - }, - { - params: { - hue: 1, - type: 'float', - }, - expected: 1, - }, - { - params: { - hue: 1.01, - type: 'float', - }, - expected: 0.01, - }, - ] as { - params: { - hue: number; - type: ColorType; - }; - expected: number; - }[] - ).forEach(({params, expected}) => { - context(`when ${JSON.stringify(params)}`, () => { - it(`it should constrain hue range`, () => { - const c1 = new Color([params.hue, 0, 0], 'hsl', params.type); - assert.ok( - TestUtil.closeTo( - c1.getComponents('hsl', params.type)[0], - expected, - DELTA, - ), - ); - - const c2 = new Color([params.hue, 0, 0], 'hsv', params.type); - assert.ok( - TestUtil.closeTo( - c2.getComponents('hsv', params.type)[0], - expected, - DELTA, - ), - ); + it('should compare color', () => { + assert.strictEqual(equalsColor(params.c1, params.c2), expected); }); }); }); diff --git a/packages/core/src/input-binding/color/model/color.ts b/packages/core/src/input-binding/color/model/color.ts index c3df43167..9df4ff9a5 100644 --- a/packages/core/src/input-binding/color/model/color.ts +++ b/packages/core/src/input-binding/color/model/color.ts @@ -1,13 +1,9 @@ import {isEmpty} from '../../../misc/type-util'; import { - appendAlphaComponent, ColorComponents3, ColorComponents4, ColorMode, ColorType, - constrainColorComponents, - convertColor, - removeAlphaComponent, } from './color-model'; export interface RgbColorObject { @@ -29,99 +25,55 @@ function isRgbColorComponent(obj: any, key: string): boolean { return key in obj && typeof obj[key] === 'number'; } -// TODO: Make type required in the next major version -/** - * @hidden - */ -export class Color { - public static black(type: ColorType = 'int'): Color { - return new Color([0, 0, 0], 'rgb', type); - } +export function isRgbColorObject(obj: unknown): obj is RgbColorObject { + return ( + isRgbColorComponent(obj, 'r') && + isRgbColorComponent(obj, 'g') && + isRgbColorComponent(obj, 'b') + ); +} - public static fromObject( - obj: RgbColorObject | RgbaColorObject, - type: ColorType = 'int', - ): Color { - const comps: ColorComponents4 | ColorComponents3 = - 'a' in obj ? [obj.r, obj.g, obj.b, obj.a] : [obj.r, obj.g, obj.b]; - return new Color(comps, 'rgb', type); - } +export function isRgbaColorObject(obj: unknown): obj is RgbaColorObject { + return isRgbColorObject(obj) && isRgbColorComponent(obj, 'a'); +} - public static toRgbaObject( - color: Color, - type: ColorType = 'int', - ): RgbaColorObject { - return color.toRgbaObject(type); - } +export function isColorObject( + obj: unknown, +): obj is RgbColorObject | RgbaColorObject { + return isRgbColorObject(obj); +} - public static isRgbColorObject(obj: unknown): obj is RgbColorObject { - return ( - isRgbColorComponent(obj, 'r') && - isRgbColorComponent(obj, 'g') && - isRgbColorComponent(obj, 'b') - ); - } +export interface Color { + readonly mode: ColorMode; + readonly type: ColorType; - public static isRgbaColorObject(obj: unknown): obj is RgbaColorObject { - return this.isRgbColorObject(obj) && isRgbColorComponent(obj, 'a'); - } + getComponents(opt_mode?: ColorMode): ColorComponents4; + toRgbaObject(): RgbaColorObject; +} - public static isColorObject( - obj: unknown, - ): obj is RgbColorObject | RgbaColorObject { - return this.isRgbColorObject(obj); +export function equalsColor(v1: Color, v2: Color): boolean { + if (v1.mode !== v2.mode) { + return false; + } + if (v1.type !== v2.type) { + return false; } - public static equals(v1: Color, v2: Color): boolean { - if (v1.mode !== v2.mode) { + const comps1 = v1.getComponents(); + const comps2 = v2.getComponents(); + for (let i = 0; i < comps1.length; i++) { + if (comps1[i] !== comps2[i]) { return false; } - - const comps1 = v1.comps_; - const comps2 = v2.comps_; - for (let i = 0; i < comps1.length; i++) { - if (comps1[i] !== comps2[i]) { - return false; - } - } - return true; - } - - private readonly comps_: ColorComponents4; - public readonly mode: ColorMode; - public readonly type: ColorType; - - constructor( - comps: ColorComponents3 | ColorComponents4, - mode: ColorMode, - type: ColorType = 'int', - ) { - this.mode = mode; - this.type = type; - this.comps_ = constrainColorComponents(comps, mode, type); - } - - public getComponents( - opt_mode?: ColorMode, - type: ColorType = 'int', - ): ColorComponents4 { - return appendAlphaComponent( - convertColor( - removeAlphaComponent(this.comps_), - {mode: this.mode, type: this.type}, - {mode: opt_mode ?? this.mode, type}, - ), - this.comps_[3], - ); } + return true; +} - public toRgbaObject(type: ColorType = 'int'): RgbaColorObject { - const rgbComps = this.getComponents('rgb', type); - return { - r: rgbComps[0], - g: rgbComps[1], - b: rgbComps[2], - a: rgbComps[3], - }; - } +/** + * @hidden + */ +export function createColorComponentsFromRgbObject( + obj: RgbColorObject | RgbaColorObject, +): ColorComponents3 | ColorComponents4 { + return 'a' in obj ? [obj.r, obj.g, obj.b, obj.a] : [obj.r, obj.g, obj.b]; } diff --git a/packages/core/src/input-binding/color/model/colors-test.ts b/packages/core/src/input-binding/color/model/colors-test.ts new file mode 100644 index 000000000..f3457efb8 --- /dev/null +++ b/packages/core/src/input-binding/color/model/colors-test.ts @@ -0,0 +1,166 @@ +import * as assert from 'assert'; +import {describe, it} from 'mocha'; + +import {Class} from '../../../index'; +import {TestUtil} from '../../../misc/test-util'; +import {Color} from './color'; +import { + ColorComponents3, + ColorComponents4, + ColorMode, + ColorType, +} from './color-model'; +import {createColor, mapColorType} from './colors'; +import {FloatColor} from './float-color'; +import {IntColor} from './int-color'; + +const DELTA = 1e-5; + +describe(createColor.name, () => { + ( + [ + { + params: { + components: [0, 127, 255, 0.5], + mode: 'rgb', + type: 'int', + }, + expected: IntColor, + }, + { + params: { + components: [0, 0.5, 1.0, 0.25], + mode: 'rgb', + type: 'float', + }, + expected: FloatColor, + }, + { + params: { + components: [300, 50, 100, 0.75], + mode: 'hsl', + type: 'int', + }, + expected: IntColor, + }, + ] as { + params: { + components: ColorComponents3 | ColorComponents4; + mode: ColorMode; + type: ColorType; + }; + expected: Class; + }[] + ).forEach(({params, expected}) => { + const c = createColor(params.components, params.mode, params.type); + assert.strictEqual(c.mode, params.mode, 'mode'); + assert.strictEqual(c.type, params.type, 'type'); + c.getComponents(c.mode).forEach((comp, i) => { + assert.strictEqual(comp, params.components[i], `component[${i}]`); + }); + assert.ok(c instanceof expected, 'class'); + }); +}); + +describe(mapColorType.name, () => { + ( + [ + // RGB + { + params: { + input: new IntColor([0, 127, 255, 0.5], 'rgb'), + toType: 'int', + }, + expected: { + components: [0, 127, 255, 0.5], + }, + }, + { + params: { + input: new IntColor([0, 0, 255, 0.5], 'rgb'), + toType: 'float', + }, + expected: { + components: [0, 0, 1, 0.5], + }, + }, + { + params: { + input: new FloatColor([0, 0, 1, 0.25], 'rgb'), + toType: 'float', + }, + expected: { + components: [0, 0, 1, 0.25], + }, + }, + { + params: { + input: new FloatColor([1, 1, 0, 0.75], 'rgb'), + toType: 'int', + }, + expected: { + components: [255, 255, 0, 0.75], + }, + }, + // HSL + { + params: { + input: new IntColor([180, 50, 100, 0.5], 'hsl'), + toType: 'int', + }, + expected: { + components: [180, 50, 100, 0.5], + }, + }, + { + params: { + input: new IntColor([180, 50, 100, 0.5], 'hsl'), + toType: 'float', + }, + expected: { + components: [0.5, 0.5, 1, 0.5], + }, + }, + { + params: { + input: new FloatColor([0.5, 1, 0.5, 0.25], 'hsl'), + toType: 'float', + }, + expected: { + components: [0.5, 1, 0.5, 0.25], + }, + }, + { + params: { + input: new FloatColor([0.5, 1, 0.5, 0.75], 'hsl'), + toType: 'int', + }, + expected: { + components: [180, 100, 50, 0.75], + }, + }, + ] as { + params: { + input: Color; + toType: ColorType; + }; + expected: { + components: ColorComponents4; + }; + }[] + ).forEach(({params, expected}) => { + describe(`when ${JSON.stringify(params)}`, () => { + it('should map color', () => { + const mc = mapColorType(params.input, params.toType); + + assert.strictEqual(mc.mode, params.input.mode, 'mode'); + mc.getComponents().forEach((comp, i) => { + assert.ok( + TestUtil.closeTo(comp, expected.components[i], DELTA), + `component[${i}]`, + ); + }); + }); + }); + }); +}); diff --git a/packages/core/src/input-binding/color/model/colors.ts b/packages/core/src/input-binding/color/model/colors.ts new file mode 100644 index 000000000..f43285135 --- /dev/null +++ b/packages/core/src/input-binding/color/model/colors.ts @@ -0,0 +1,98 @@ +import {mapRange, TpError} from '../../../index'; +import {Color} from './color'; +import { + ColorComponents3, + ColorComponents4, + ColorMode, + ColorType, + getColorMaxComponents, +} from './color-model'; +import {FloatColor} from './float-color'; +import {IntColor} from './int-color'; + +const TYPE_TO_CONSTRUCTOR_MAP: { + [type in ColorType]: ( + components: ColorComponents3 | ColorComponents4, + mode: ColorMode, + ) => Color; +} = { + int: (comps, mode) => new IntColor(comps, mode), + float: (comps, mode) => new FloatColor(comps, mode), +}; + +export function createColor( + comps: ColorComponents3 | ColorComponents4, + mode: ColorMode, + type: 'int', +): IntColor; +export function createColor( + comps: ColorComponents3 | ColorComponents4, + mode: ColorMode, + type: 'float', +): FloatColor; +export function createColor( + comps: ColorComponents3 | ColorComponents4, + mode: ColorMode, + type: ColorType, +): Color; +export function createColor( + comps: ColorComponents3 | ColorComponents4, + mode: ColorMode, + type: ColorType, +): Color { + return TYPE_TO_CONSTRUCTOR_MAP[type](comps, mode); +} + +function isFloatColor(c: Color): c is FloatColor { + return c.type === 'float'; +} + +function isIntColor(c: Color): c is IntColor { + return c.type === 'int'; +} + +function convertFloatToInt(cf: FloatColor): IntColor { + const comps = cf.getComponents(); + const ms = getColorMaxComponents(cf.mode, 'int'); + return new IntColor( + [ + Math.round(mapRange(comps[0], 0, 1, 0, ms[0])), + Math.round(mapRange(comps[1], 0, 1, 0, ms[1])), + Math.round(mapRange(comps[2], 0, 1, 0, ms[2])), + comps[3], + ], + cf.mode, + ); +} + +function convertIntToFloat(ci: IntColor): FloatColor { + const comps = ci.getComponents(); + const ms = getColorMaxComponents(ci.mode, 'int'); + return new FloatColor( + [ + mapRange(comps[0], 0, ms[0], 0, 1), + mapRange(comps[1], 0, ms[1], 0, 1), + mapRange(comps[2], 0, ms[2], 0, 1), + comps[3], + ], + ci.mode, + ); +} + +export function mapColorType(c: Color, type: 'int'): IntColor; +export function mapColorType(c: Color, type: 'float'): FloatColor; +export function mapColorType(c: Color, type: ColorType): Color; +export function mapColorType(c: Color, type: ColorType): Color { + if (c.type === type) { + return c; + } + if (isIntColor(c) && type === 'float') { + return convertIntToFloat(c); + } + /* istanbul ignore else */ + if (isFloatColor(c) && type === 'int') { + return convertFloatToInt(c); + } + /* istanbul ignore next */ + throw TpError.shouldNeverHappen(); +} diff --git a/packages/core/src/input-binding/color/model/float-color-test.ts b/packages/core/src/input-binding/color/model/float-color-test.ts new file mode 100644 index 000000000..fd1f8b538 --- /dev/null +++ b/packages/core/src/input-binding/color/model/float-color-test.ts @@ -0,0 +1,86 @@ +import * as assert from 'assert'; +import {describe, it} from 'mocha'; + +import {TestUtil} from '../../../misc/test-util'; +import {ColorComponents4, ColorMode} from './color-model'; +import {FloatColor} from './float-color'; + +const DELTA = 1e-5; + +describe(FloatColor.name, () => { + [ + { + expected: [1, 1, 1, 1], + params: { + components: [2, 3, 4, 2], + mode: 'rgb', + }, + }, + { + expected: [0.5, 1, 1, 1], + params: { + components: [1.5, 3, 4, 2], + mode: 'hsl', + }, + }, + { + expected: [0.5, 1, 1, 1], + params: { + components: [1.5, 3, 4, 2], + mode: 'hsl', + }, + }, + ].forEach(({expected, params}) => { + describe(`when ${JSON.stringify(params)}`, () => { + it('should constrain components', () => { + const c = new FloatColor( + params.components as ColorComponents4, + params.mode as ColorMode, + ); + const o = c.getComponents(params.mode as ColorMode); + expected.forEach((e, index) => { + assert.ok( + TestUtil.closeTo(o[index], e, DELTA), + `components[${index}]`, + ); + }); + }); + }); + }); + + ( + [ + { + params: { + hue: 1, + }, + expected: 1, + }, + { + params: { + hue: 1.01, + }, + expected: 0.01, + }, + ] as { + params: { + hue: number; + }; + expected: number; + }[] + ).forEach(({params, expected}) => { + describe(`when ${JSON.stringify(params)}`, () => { + it(`it should constrain hue range`, () => { + const c1 = new FloatColor([params.hue, 0, 0], 'hsl'); + assert.ok( + TestUtil.closeTo(c1.getComponents('hsl')[0], expected, DELTA), + ); + + const c2 = new FloatColor([params.hue, 0, 0], 'hsv'); + assert.ok( + TestUtil.closeTo(c2.getComponents('hsv')[0], expected, DELTA), + ); + }); + }); + }); +}); diff --git a/packages/core/src/input-binding/color/model/float-color.ts b/packages/core/src/input-binding/color/model/float-color.ts new file mode 100644 index 000000000..592f8683f --- /dev/null +++ b/packages/core/src/input-binding/color/model/float-color.ts @@ -0,0 +1,43 @@ +import {Color, RgbaColorObject} from './color'; +import { + appendAlphaComponent, + ColorComponents3, + ColorComponents4, + ColorMode, + ColorType, + constrainColorComponents, + convertColor, + removeAlphaComponent, +} from './color-model'; + +export class FloatColor implements Color { + private readonly comps_: ColorComponents4; + public readonly mode: ColorMode; + public readonly type: ColorType = 'float'; + + constructor(comps: ColorComponents3 | ColorComponents4, mode: ColorMode) { + this.mode = mode; + this.comps_ = constrainColorComponents(comps, mode, this.type); + } + + public getComponents(opt_mode?: ColorMode): ColorComponents4 { + return appendAlphaComponent( + convertColor( + removeAlphaComponent(this.comps_), + {mode: this.mode, type: this.type}, + {mode: opt_mode ?? this.mode, type: this.type}, + ), + this.comps_[3], + ); + } + + public toRgbaObject(): RgbaColorObject { + const rgbComps = this.getComponents('rgb'); + return { + r: rgbComps[0], + g: rgbComps[1], + b: rgbComps[2], + a: rgbComps[3], + }; + } +} diff --git a/packages/core/src/input-binding/color/model/int-color-test.ts b/packages/core/src/input-binding/color/model/int-color-test.ts new file mode 100644 index 000000000..8e8d9f94e --- /dev/null +++ b/packages/core/src/input-binding/color/model/int-color-test.ts @@ -0,0 +1,256 @@ +import * as assert from 'assert'; +import {describe as context, describe, it} from 'mocha'; + +import {TestUtil} from '../../../misc/test-util'; +import {RgbaColorObject} from './color'; +import {ColorComponents3, ColorComponents4, ColorMode} from './color-model'; +import {IntColor} from './int-color'; + +const DELTA = 1e-5; + +describe(IntColor.name, () => { + ( + [ + { + expected: {r: 10, g: 20, b: 30, a: 1}, + params: { + components: [10, 20, 30], + }, + }, + { + expected: {r: 0, g: 255, b: 0, a: 1}, + params: { + components: [-1, 300, 0], + }, + }, + ] as { + expected: RgbaColorObject; + params: { + components: ColorComponents3; + }; + }[] + ).forEach(({expected, params}) => { + context(`when ${JSON.stringify(params)}`, () => { + const c = new IntColor(params.components, 'rgb'); + it('should get components', () => { + assert.deepStrictEqual(c.getComponents('rgb'), [ + expected.r, + expected.g, + expected.b, + expected.a, + ]); + }); + it('should convert to object', () => { + assert.deepStrictEqual(c.toRgbaObject(), expected); + }); + }); + }); + + ( + [ + { + expected: [359, 0, 100, 1], + params: { + components: [359, 0, 100], + mode: 'hsv', + }, + }, + { + expected: [350, 0, 100, 1], + params: { + components: [-10, -10, 100], + mode: 'hsv', + }, + }, + { + expected: [10, 100, 100, 1], + params: { + components: [370, 110, 100], + mode: 'hsv', + }, + }, + { + expected: [359, 0, 100, 1], + params: { + components: [359, 0, 100], + mode: 'hsl', + }, + }, + { + expected: [350, 0, 100, 1], + params: { + components: [-10, -10, 100], + mode: 'hsl', + }, + }, + { + expected: [10, 100, 100, 1], + params: { + components: [370, 110, 100], + mode: 'hsl', + }, + }, + ] as { + expected: ColorComponents4; + params: { + components: ColorComponents3; + mode: ColorMode; + }; + }[] + ).forEach(({expected, params}) => { + context(`when ${JSON.stringify(params)}`, () => { + const c = new IntColor(params.components, params.mode); + it('should get components', () => { + assert.deepStrictEqual(c.getComponents(params.mode), expected); + }); + }); + }); + + ([{mode: 'rgb'}, {mode: 'hsv'}] as {mode: ColorMode}[]).forEach(({mode}) => { + context(`when ${JSON.stringify({mode})}`, () => { + it('should get mode', () => { + const c = new IntColor([0, 0, 0], mode); + assert.strictEqual(c.mode, mode); + }); + }); + }); + + [ + { + expected: {components: [255, 0, 0, 0.5]}, + params: { + components: [255, 0, 0, 0.5], + fromMode: 'rgb', + toMode: 'rgb', + }, + }, + { + expected: {components: [0, 100, 100, 0]}, + params: { + components: [255, 0, 0, 0], + fromMode: 'rgb', + toMode: 'hsv', + }, + }, + { + expected: {components: [22, 24, 33, 1]}, + params: { + components: [229, 33, 13, 1], + fromMode: 'hsv', + toMode: 'rgb', + }, + }, + ].forEach(({expected, params}) => { + context(`when ${JSON.stringify(params)}`, () => { + it('should get components with specific mode', () => { + const c = new IntColor( + params.components as ColorComponents4, + params.fromMode as ColorMode, + ); + assert.deepStrictEqual( + c.getComponents(params.toMode as ColorMode).map((comp, index) => { + return index === 3 ? comp : Math.floor(comp); + }), + expected.components, + ); + }); + }); + }); + + [ + { + expected: [0, 0, 0, 0], + params: { + components: [-10, -20, -30, -1], + mode: 'rgb', + }, + }, + { + expected: [255, 255, 255, 1], + params: { + components: [300, 400, 500, 2], + mode: 'rgb', + }, + }, + { + expected: [350, 0, 0, 0], + params: { + components: [-10, -20, -30, -1], + mode: 'hsl', + }, + }, + { + expected: [40, 100, 100, 1], + params: { + components: [400, 500, 500, 2], + mode: 'hsl', + }, + }, + { + expected: [350, 0, 0, 0], + params: { + components: [-10, -20, -30, -1], + mode: 'hsv', + }, + }, + { + expected: [40, 100, 100, 1], + params: { + components: [400, 500, 500, 2], + mode: 'hsv', + }, + }, + ].forEach(({expected, params}) => { + context(`when ${JSON.stringify(params)}`, () => { + it('should constrain components', () => { + const c = new IntColor( + params.components as ColorComponents4, + params.mode as ColorMode, + ); + const o = c.getComponents(params.mode as ColorMode); + expected.forEach((e, index) => { + assert.ok( + TestUtil.closeTo(o[index], e, DELTA), + `components[${index}]`, + ); + }); + }); + }); + }); + + ( + [ + { + params: { + hue: 360, + }, + expected: 360, + }, + { + params: { + hue: 361, + }, + expected: 1, + }, + ] as { + params: { + hue: number; + }; + expected: number; + }[] + ).forEach(({params, expected}) => { + context(`when ${JSON.stringify(params)}`, () => { + it(`it should constrain hue range`, () => { + const c1 = new IntColor([params.hue, 0, 0], 'hsl'); + assert.ok( + TestUtil.closeTo(c1.getComponents('hsl')[0], expected, DELTA), + ); + + const c2 = new IntColor([params.hue, 0, 0], 'hsv'); + assert.ok( + TestUtil.closeTo(c2.getComponents('hsv')[0], expected, DELTA), + ); + }); + }); + }); +}); diff --git a/packages/core/src/input-binding/color/model/int-color.ts b/packages/core/src/input-binding/color/model/int-color.ts new file mode 100644 index 000000000..a36272536 --- /dev/null +++ b/packages/core/src/input-binding/color/model/int-color.ts @@ -0,0 +1,47 @@ +import {Color, RgbaColorObject} from './color'; +import { + appendAlphaComponent, + ColorComponents3, + ColorComponents4, + ColorMode, + ColorType, + constrainColorComponents, + convertColor, + removeAlphaComponent, +} from './color-model'; + +export class IntColor implements Color { + public static black(): IntColor { + return new IntColor([0, 0, 0], 'rgb'); + } + + private readonly comps_: ColorComponents4; + public readonly mode: ColorMode; + public readonly type: ColorType = 'int'; + + constructor(comps: ColorComponents3 | ColorComponents4, mode: ColorMode) { + this.mode = mode; + this.comps_ = constrainColorComponents(comps, mode, this.type); + } + + public getComponents(opt_mode?: ColorMode): ColorComponents4 { + return appendAlphaComponent( + convertColor( + removeAlphaComponent(this.comps_), + {mode: this.mode, type: this.type}, + {mode: opt_mode ?? this.mode, type: this.type}, + ), + this.comps_[3], + ); + } + + public toRgbaObject(): RgbaColorObject { + const rgbComps = this.getComponents('rgb'); + return { + r: rgbComps[0], + g: rgbComps[1], + b: rgbComps[2], + a: rgbComps[3], + }; + } +} diff --git a/packages/core/src/input-binding/color/plugin-number-test.ts b/packages/core/src/input-binding/color/plugin-number-test.ts index 12c47c561..a1cfe0eee 100644 --- a/packages/core/src/input-binding/color/plugin-number-test.ts +++ b/packages/core/src/input-binding/color/plugin-number-test.ts @@ -35,13 +35,6 @@ describe('NumberColorInputPlugin', () => { }, expected: 1, }, - { - params: { - alpha: true, - view: 'color', - }, - expected: 0, - }, { params: { color: { @@ -67,7 +60,7 @@ describe('NumberColorInputPlugin', () => { it('should apply alpha', () => { const c = reader(input.color); - assert.strictEqual(c.getComponents('rgb', 'int')[3], expected); + assert.strictEqual(c.getComponents('rgb')[3], expected); }); }); }); diff --git a/packages/core/src/input-binding/color/plugin-number.ts b/packages/core/src/input-binding/color/plugin-number.ts index d715dbb11..c39babb84 100644 --- a/packages/core/src/input-binding/color/plugin-number.ts +++ b/packages/core/src/input-binding/color/plugin-number.ts @@ -11,7 +11,8 @@ import { createColorStringParser, } from './converter/color-string'; import {createColorNumberWriter} from './converter/writer'; -import {Color} from './model/color'; +import {equalsColor} from './model/color'; +import {IntColor} from './model/int-color'; import {ColorInputParams, parseColorInputParams} from './util'; function shouldSupportAlpha(inputParams: ColorInputParams): boolean { @@ -21,10 +22,10 @@ function shouldSupportAlpha(inputParams: ColorInputParams): boolean { return false; } -function createFormatter(supportsAlpha: boolean): Formatter { +function createFormatter(supportsAlpha: boolean): Formatter { return supportsAlpha - ? (v: Color) => colorToHexRgbaString(v, '0x') - : (v: Color) => colorToHexRgbString(v, '0x'); + ? (v: IntColor) => colorToHexRgbaString(v, '0x') + : (v: IntColor) => colorToHexRgbString(v, '0x'); } function isForColor(params: Record): boolean { @@ -41,7 +42,7 @@ function isForColor(params: Record): boolean { * @hidden */ export const NumberColorInputPlugin: InputBindingPlugin< - Color, + IntColor, number, ColorInputParams > = { @@ -69,7 +70,7 @@ export const NumberColorInputPlugin: InputBindingPlugin< ? colorFromRgbaNumber : colorFromRgbNumber; }, - equals: Color.equals, + equals: equalsColor, writer: (args) => { return createColorNumberWriter(shouldSupportAlpha(args.params)); }, diff --git a/packages/core/src/input-binding/color/plugin-object-test.ts b/packages/core/src/input-binding/color/plugin-object-test.ts index 59de5d32b..7c059c5e9 100644 --- a/packages/core/src/input-binding/color/plugin-object-test.ts +++ b/packages/core/src/input-binding/color/plugin-object-test.ts @@ -6,26 +6,28 @@ import {createTestWindow} from '../../misc/dom-test-util'; import {TestUtil} from '../../misc/test-util'; import {createInputBindingController} from '../plugin'; import {ColorController} from './controller/color'; -import {Color} from './model/color'; +import {IntColor} from './model/int-color'; import {ObjectColorInputPlugin} from './plugin-object'; +const DELTA = 1e-5; + describe(ObjectColorInputPlugin.id, () => { [ { params: {}, - expected: 'int', + expected: {r: 1, g: 0, b: 0, a: 0.5}, }, { params: { color: {type: 'int'}, }, - expected: 'int', + expected: {r: 1, g: 0, b: 0, a: 0.5}, }, { params: { color: {type: 'float'}, }, - expected: 'float', + expected: {r: 255, g: 0, b: 0, a: 0.5}, }, ].forEach(({params, expected}) => { context(`when params=${JSON.stringify(params)}`, () => { @@ -36,14 +38,21 @@ describe(ObjectColorInputPlugin.id, () => { if (!result) { throw new Error('unexpected result'); } + const target = new BindingTarget(input, 'color'); const reader = ObjectColorInputPlugin.binding.reader({ initialValue: input.color, params: result.params, - target: new BindingTarget(input, 'color'), + target: target, }); it('should apply color type to binding reader', () => { - assert.strictEqual(reader(input.color).type, expected); + const c = reader({r: 1, g: 0, b: 0, a: 0.5}); + const comps = c.getComponents('rgb'); + + assert.ok(TestUtil.closeTo(comps[0], expected.r, DELTA), 'r'); + assert.ok(TestUtil.closeTo(comps[1], expected.g, DELTA), 'g'); + assert.ok(TestUtil.closeTo(comps[2], expected.b, DELTA), 'b'); + assert.ok(TestUtil.closeTo(comps[3], expected.a, DELTA), 'a'); }); }); }); @@ -86,7 +95,7 @@ describe(ObjectColorInputPlugin.id, () => { params: result.params, target: target, }); - writer(target, new Color([255, 0, 0, 1], 'rgb', 'int')); + writer(target, new IntColor([255, 0, 0, 1], 'rgb')); it('should apply color type to binding writer', () => { assert.deepStrictEqual(target.read(), expected); @@ -143,9 +152,9 @@ describe(ObjectColorInputPlugin.id, () => { type: 'float', }, }, - inputText: 'rgba(0.1, 0.2, 0.3, 1)', + inputText: 'rgba(0, 127, 255, 1)', }, - expected: '{r: 0.10, g: 0.20, b: 0.30, a: 1.00}', + expected: '{r: 0.00, g: 0.50, b: 1.00, a: 1.00}', }, ].forEach(({params, expected}) => { context(`when params=${JSON.stringify(params)}`, () => { diff --git a/packages/core/src/input-binding/color/plugin-object.ts b/packages/core/src/input-binding/color/plugin-object.ts index ad39a8d93..d8cd6a39d 100644 --- a/packages/core/src/input-binding/color/plugin-object.ts +++ b/packages/core/src/input-binding/color/plugin-object.ts @@ -2,15 +2,24 @@ import {BindingReader} from '../../common/binding/binding'; import {Formatter} from '../../common/converter/formatter'; import {InputBindingPlugin} from '../plugin'; import {ColorController} from './controller/color'; -import {colorFromObject} from './converter/color-number'; +import {colorFromObject} from './converter/color-object'; import { colorToObjectRgbaString, colorToObjectRgbString, createColorStringParser, } from './converter/color-string'; import {createColorObjectWriter} from './converter/writer'; -import {Color, RgbaColorObject, RgbColorObject} from './model/color'; +import { + Color, + equalsColor, + isColorObject, + isRgbaColorObject, + RgbaColorObject, + RgbColorObject, +} from './model/color'; import {ColorType} from './model/color-model'; +import {mapColorType} from './model/colors'; +import {IntColor} from './model/int-color'; import { ColorInputParams, extractColorType, @@ -20,14 +29,15 @@ import { function shouldSupportAlpha( initialValue: RgbColorObject | RgbaColorObject, ): boolean { - return Color.isRgbaColorObject(initialValue); + return isRgbaColorObject(initialValue); } -function createColorObjectReader( - opt_type: ColorType | undefined, -): BindingReader { - return (value: unknown): Color => { - return colorFromObject(value, opt_type); +function createColorObjectBindingReader( + type: ColorType, +): BindingReader { + return (value) => { + const c = colorFromObject(value, type); + return mapColorType(c, 'int'); }; } @@ -47,14 +57,14 @@ function createColorObjectFormatter( * @hidden */ export const ObjectColorInputPlugin: InputBindingPlugin< - Color, + IntColor, RgbColorObject | RgbaColorObject, ColorInputParams > = { id: 'input-color-object', type: 'input', accept: (value, params) => { - if (!Color.isColorObject(value)) { + if (!isColorObject(value)) { return null; } const result = parseColorInputParams(params); @@ -66,16 +76,17 @@ export const ObjectColorInputPlugin: InputBindingPlugin< : null; }, binding: { - reader: (args) => createColorObjectReader(extractColorType(args.params)), - equals: Color.equals, + reader: (args) => + createColorObjectBindingReader(extractColorType(args.params) ?? 'int'), + equals: equalsColor, writer: (args) => createColorObjectWriter( shouldSupportAlpha(args.initialValue), - extractColorType(args.params), + extractColorType(args.params) ?? 'int', ), }, controller: (args) => { - const supportsAlpha = Color.isRgbaColorObject(args.initialValue); + const supportsAlpha = isRgbaColorObject(args.initialValue); const expanded = 'expanded' in args.params ? args.params.expanded : undefined; const picker = 'picker' in args.params ? args.params.picker : undefined; @@ -84,7 +95,7 @@ export const ObjectColorInputPlugin: InputBindingPlugin< colorType: type, expanded: expanded ?? false, formatter: createColorObjectFormatter(supportsAlpha, type), - parser: createColorStringParser(type), + parser: createColorStringParser('int'), pickerLayout: picker ?? 'popup', supportsAlpha: supportsAlpha, value: args.value, diff --git a/packages/core/src/input-binding/color/plugin-string.ts b/packages/core/src/input-binding/color/plugin-string.ts index b612dae8f..034c19046 100644 --- a/packages/core/src/input-binding/color/plugin-string.ts +++ b/packages/core/src/input-binding/color/plugin-string.ts @@ -2,13 +2,14 @@ import {TpError} from '../../common/tp-error'; import {InputBindingPlugin} from '../plugin'; import {ColorController} from './controller/color'; import { - createColorStringBindingReader, createColorStringParser, detectStringColorFormat, findColorStringifier, + readIntColorString, } from './converter/color-string'; import {createColorStringWriter} from './converter/writer'; -import {Color} from './model/color'; +import {equalsColor} from './model/color'; +import {IntColor} from './model/int-color'; import { ColorInputParams, extractColorType, @@ -19,7 +20,7 @@ import { * @hidden */ export const StringColorInputPlugin: InputBindingPlugin< - Color, + IntColor, string, ColorInputParams > = { @@ -50,9 +51,8 @@ export const StringColorInputPlugin: InputBindingPlugin< : null; }, binding: { - reader: (args) => - createColorStringBindingReader(extractColorType(args.params) ?? 'int'), - equals: Color.equals, + reader: () => readIntColorString, + equals: equalsColor, writer: (args) => { const format = detectStringColorFormat( args.initialValue, @@ -88,7 +88,7 @@ export const StringColorInputPlugin: InputBindingPlugin< colorType: format.type, expanded: expanded ?? false, formatter: stringifier, - parser: createColorStringParser(format.type), + parser: createColorStringParser('int'), pickerLayout: picker ?? 'popup', supportsAlpha: format.alpha, value: args.value, diff --git a/packages/core/src/input-binding/color/view/a-palette.ts b/packages/core/src/input-binding/color/view/a-palette.ts index 2e5370e50..758b0c013 100644 --- a/packages/core/src/input-binding/color/view/a-palette.ts +++ b/packages/core/src/input-binding/color/view/a-palette.ts @@ -4,12 +4,12 @@ import {mapRange} from '../../../common/number-util'; import {ClassName} from '../../../common/view/class-name'; import {View} from '../../../common/view/view'; import {colorToFunctionalRgbaString} from '../converter/color-string'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; const className = ClassName('apl'); interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -18,7 +18,7 @@ interface Config { */ export class APaletteView implements View { public readonly element: HTMLElement; - public readonly value: Value; + public readonly value: Value; private readonly colorElem_: HTMLDivElement; private readonly markerElem_: HTMLDivElement; private readonly previewElem_: HTMLDivElement; @@ -59,11 +59,11 @@ export class APaletteView implements View { private update_(): void { const c = this.value.rawValue; const rgbaComps = c.getComponents('rgb'); - const leftColor = new Color( + const leftColor = new IntColor( [rgbaComps[0], rgbaComps[1], rgbaComps[2], 0], 'rgb', ); - const rightColor = new Color( + const rightColor = new IntColor( [rgbaComps[0], rgbaComps[1], rgbaComps[2], 255], 'rgb', ); diff --git a/packages/core/src/input-binding/color/view/color-swatch.ts b/packages/core/src/input-binding/color/view/color-swatch.ts index 1651ca342..c58202d50 100644 --- a/packages/core/src/input-binding/color/view/color-swatch.ts +++ b/packages/core/src/input-binding/color/view/color-swatch.ts @@ -3,10 +3,10 @@ import {ViewProps} from '../../../common/model/view-props'; import {ClassName} from '../../../common/view/class-name'; import {View} from '../../../common/view/view'; import {colorToHexRgbaString} from '../converter/color-string'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -17,7 +17,7 @@ const className = ClassName('colsw'); */ export class ColorSwatchView implements View { public readonly element: HTMLElement; - public readonly value: Value; + public readonly value: Value; public readonly buttonElement: HTMLButtonElement; private readonly swatchElem_: HTMLDivElement; diff --git a/packages/core/src/input-binding/color/view/h-palette.ts b/packages/core/src/input-binding/color/view/h-palette.ts index 9d9afed76..a949ec93c 100644 --- a/packages/core/src/input-binding/color/view/h-palette.ts +++ b/packages/core/src/input-binding/color/view/h-palette.ts @@ -4,12 +4,12 @@ import {mapRange} from '../../../common/number-util'; import {ClassName} from '../../../common/view/class-name'; import {View} from '../../../common/view/view'; import {colorToFunctionalRgbString} from '../converter/color-string'; -import {Color} from '../model/color'; +import {IntColor} from '../model/int-color'; const className = ClassName('hpl'); interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -18,7 +18,7 @@ interface Config { */ export class HPaletteView implements View { public readonly element: HTMLElement; - public readonly value: Value; + public readonly value: Value; private readonly markerElem_: HTMLDivElement; constructor(doc: Document, config: Config) { @@ -48,7 +48,7 @@ export class HPaletteView implements View { const c = this.value.rawValue; const [h] = c.getComponents('hsv'); this.markerElem_.style.backgroundColor = colorToFunctionalRgbString( - new Color([h, 100, 100], 'hsv'), + new IntColor([h, 100, 100], 'hsv'), ); const left = mapRange(h, 0, 360, 0, 100); this.markerElem_.style.left = `${left}%`; diff --git a/packages/core/src/input-binding/color/view/sv-palette.ts b/packages/core/src/input-binding/color/view/sv-palette.ts index 66ed3c70b..2a4ac57d3 100644 --- a/packages/core/src/input-binding/color/view/sv-palette.ts +++ b/packages/core/src/input-binding/color/view/sv-palette.ts @@ -4,13 +4,13 @@ import {ViewProps} from '../../../common/model/view-props'; import {mapRange} from '../../../common/number-util'; import {ClassName} from '../../../common/view/class-name'; import {View} from '../../../common/view/view'; -import {Color} from '../model/color'; import {hsvToRgbInt} from '../model/color-model'; +import {IntColor} from '../model/int-color'; const className = ClassName('svp'); interface Config { - value: Value; + value: Value; viewProps: ViewProps; } @@ -21,7 +21,7 @@ const CANVAS_RESOL = 64; */ export class SvPaletteView implements View { public readonly element: HTMLElement; - public readonly value: Value; + public readonly value: Value; public readonly canvasElement: HTMLCanvasElement; private readonly markerElem_: HTMLDivElement; diff --git a/packages/tweakpane/src/doc/ts/panepaint.ts b/packages/tweakpane/src/doc/ts/panepaint.ts index 7e17cb9ae..90f528688 100644 --- a/packages/tweakpane/src/doc/ts/panepaint.ts +++ b/packages/tweakpane/src/doc/ts/panepaint.ts @@ -1,7 +1,7 @@ import { - Color, - colorFromString, colorToFunctionalRgbaString, + IntColor, + readIntColorString, } from '@tweakpane/core'; import {Pane} from 'tweakpane'; @@ -188,23 +188,23 @@ export function createPane(container: HTMLElement, theme: Theme): any { title: 'Autofill', }).on('click', () => { const value = theme[m[1] as ThemeProperty]; - const c = colorFromString(value); + const c = readIntColorString(value); const hslComps = c.getComponents('hsl'); const sign = hslComps[2] > 50 ? -1 : +1; theme[`${m[1]}-hover` as ThemeProperty] = colorToFunctionalRgbaString( - new Color( + new IntColor( [hslComps[0], hslComps[1], hslComps[2] + 5 * sign, hslComps[3]], 'hsl', ), ); theme[`${m[1]}-focus` as ThemeProperty] = colorToFunctionalRgbaString( - new Color( + new IntColor( [hslComps[0], hslComps[1], hslComps[2] + 10 * sign, hslComps[3]], 'hsl', ), ); theme[`${m[1]}-active` as ThemeProperty] = colorToFunctionalRgbaString( - new Color( + new IntColor( [hslComps[0], hslComps[1], hslComps[2] + 15 * sign, hslComps[3]], 'hsl', ), diff --git a/packages/tweakpane/src/doc/ts/route/index.ts b/packages/tweakpane/src/doc/ts/route/index.ts index e2b88e761..27f5cdd13 100644 --- a/packages/tweakpane/src/doc/ts/route/index.ts +++ b/packages/tweakpane/src/doc/ts/route/index.ts @@ -1,8 +1,8 @@ import { - Color, - colorFromString, colorToFunctionalRgbaString, + IntColor, mapRange, + readIntColorString, } from '@tweakpane/core'; import {Pane} from 'tweakpane'; @@ -81,8 +81,8 @@ export function initIndex() { return; } - const [h, s, l] = colorFromString(ENV.color).getComponents('hsl'); - const bg = new Color([h + 30, s, l < 50 ? l - 4 : l + 5], 'hsl'); + const [h, s, l] = readIntColorString(ENV.color).getComponents('hsl'); + const bg = new IntColor([h + 30, s, l < 50 ? l - 4 : l + 5], 'hsl'); headerElem.style.backgroundColor = colorToFunctionalRgbaString(bg); }; diff --git a/packages/tweakpane/src/main/ts/pane/input-test.ts b/packages/tweakpane/src/main/ts/pane/input-test.ts index 46f635512..b0405e934 100644 --- a/packages/tweakpane/src/main/ts/pane/input-test.ts +++ b/packages/tweakpane/src/main/ts/pane/input-test.ts @@ -1,8 +1,8 @@ import { CheckboxController, - Color, ColorController, forceCast, + IntColor, ListController, NumberTextController, Point2dController, @@ -96,14 +96,14 @@ describe(Pane.name, () => { expected: '#224488', params: { propertyValue: '#123', - newInternalValue: new Color([0x22, 0x44, 0x88], 'rgb'), + newInternalValue: new IntColor([0x22, 0x44, 0x88], 'rgb'), }, }, { expected: 'rgb(0, 127, 255)', params: { propertyValue: 'rgb(10, 20, 30)', - newInternalValue: new Color([0, 127, 255], 'rgb'), + newInternalValue: new IntColor([0, 127, 255], 'rgb'), }, }, ].forEach(({expected, params}) => {