|
@@ -25,6 +25,7 @@ import { legendFor } from './legend';
|
|
|
import LineGraphComponent from './line-graph/line-graph-component';
|
|
|
import { Slider, Slider2 } from './slider';
|
|
|
import { Asset } from '../../mol-util/assets';
|
|
|
+import { ColorListEntry } from '../../mol-util/color/color';
|
|
|
|
|
|
export type ParameterControlsCategoryFilter = string | null | (string | null)[]
|
|
|
|
|
@@ -177,7 +178,7 @@ function controlFor(param: PD.Any): ParamControl | undefined {
|
|
|
case 'conditioned': return ConditionedControl;
|
|
|
case 'multi-select': return MultiSelectControl;
|
|
|
case 'color': return CombinedColorControl;
|
|
|
- case 'color-list': return ColorListControl;
|
|
|
+ case 'color-list': return param.offsets ? OffsetColorListControl : ColorListControl;
|
|
|
case 'vec3': return Vec3Control;
|
|
|
case 'mat4': return Mat4Control;
|
|
|
case 'url': return UrlControl;
|
|
@@ -557,21 +558,30 @@ export class ColorControl extends SimpleParam<PD.Color> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const colorGradientInterpolated = memoize1((colors: Color[]) => {
|
|
|
- const styles = colors.map(c => Color.toStyle(c));
|
|
|
+function colorEntryToStyle(e: ColorListEntry, includeOffset = false) {
|
|
|
+ if (Array.isArray(e)) {
|
|
|
+ if (includeOffset) return `${Color.toStyle(e[0])} ${(100 * e[1]).toFixed(2)}%`;
|
|
|
+ return Color.toStyle(e[0]);
|
|
|
+ }
|
|
|
+ return Color.toStyle(e);
|
|
|
+}
|
|
|
+
|
|
|
+const colorGradientInterpolated = memoize1((colors: ColorListEntry[]) => {
|
|
|
+ const styles = colors.map(c => colorEntryToStyle(c, true));
|
|
|
return `linear-gradient(to right, ${styles.join(', ')})`;
|
|
|
});
|
|
|
|
|
|
-const colorGradientBanded = memoize1((colors: Color[]) => {
|
|
|
+const colorGradientBanded = memoize1((colors: ColorListEntry[]) => {
|
|
|
const n = colors.length;
|
|
|
- const styles: string[] = [`${Color.toStyle(colors[0])} ${100 * (1 / n)}%`];
|
|
|
+ const styles: string[] = [`${colorEntryToStyle(colors[0])} ${100 * (1 / n)}%`];
|
|
|
+ // TODO: does this need to support offsets?
|
|
|
for (let i = 1, il = n - 1; i < il; ++i) {
|
|
|
styles.push(
|
|
|
- `${Color.toStyle(colors[i])} ${100 * (i / n)}%`,
|
|
|
- `${Color.toStyle(colors[i])} ${100 * ((i + 1) / n)}%`
|
|
|
+ `${colorEntryToStyle(colors[i])} ${100 * (i / n)}%`,
|
|
|
+ `${colorEntryToStyle(colors[i])} ${100 * ((i + 1) / n)}%`
|
|
|
);
|
|
|
}
|
|
|
- styles.push(`${Color.toStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`);
|
|
|
+ styles.push(`${colorEntryToStyle(colors[n - 1])} ${100 * ((n - 1) / n)}%`);
|
|
|
return `linear-gradient(to right, ${styles.join(', ')})`;
|
|
|
});
|
|
|
|
|
@@ -586,7 +596,7 @@ function colorStripStyle(list: PD.ColorList['defaultValue'], right = '0'): React
|
|
|
};
|
|
|
}
|
|
|
|
|
|
-function colorGradient(colors: Color[], banded: boolean) {
|
|
|
+function colorGradient(colors: ColorListEntry[], banded: boolean) {
|
|
|
return banded ? colorGradientBanded(colors) : colorGradientInterpolated(colors);
|
|
|
}
|
|
|
|
|
@@ -604,6 +614,9 @@ function createColorListHelpers() {
|
|
|
set: ActionMenu.createItemsFromSelectOptions(ColorListOptionsSet, { addOn })
|
|
|
},
|
|
|
ColorsParam: PD.ObjectList({ color: PD.Color(0x0 as Color) }, ({ color }) => Color.toHexString(color).toUpperCase()),
|
|
|
+ OffsetColorsParam: PD.ObjectList(
|
|
|
+ { color: PD.Color(0x0 as Color), offset: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }) },
|
|
|
+ ({ color, offset }) => `${Color.toHexString(color).toUpperCase()} [${offset.toFixed(2)}]`),
|
|
|
IsInterpolatedParam: PD.Boolean(false, { label: 'Interpolated' })
|
|
|
};
|
|
|
}
|
|
@@ -684,6 +697,79 @@ export class ColorListControl extends React.PureComponent<ParamProps<PD.ColorLis
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+export class OffsetColorListControl extends React.PureComponent<ParamProps<PD.ColorList>, { showHelp: boolean, show?: 'edit' | 'presets' }> {
|
|
|
+ state = { showHelp: false, show: void 0 as 'edit' | 'presets' | undefined };
|
|
|
+
|
|
|
+ protected update(value: PD.ColorList['defaultValue']) {
|
|
|
+ this.props.onChange({ param: this.props.param, name: this.props.name, value });
|
|
|
+ }
|
|
|
+
|
|
|
+ toggleEdit = () => this.setState({ show: this.state.show === 'edit' ? void 0 : 'edit' });
|
|
|
+ togglePresets = () => this.setState({ show: this.state.show === 'presets' ? void 0 : 'presets' });
|
|
|
+
|
|
|
+ renderControl() {
|
|
|
+ const { value } = this.props;
|
|
|
+ // TODO: fix the button right offset
|
|
|
+ return <>
|
|
|
+ <button onClick={this.toggleEdit} style={{ position: 'relative', paddingRight: '33px' }}>
|
|
|
+ {value.colors.length === 1 ? '1 color' : `${value.colors.length} colors`}
|
|
|
+ <div style={colorStripStyle(value, '33px')} />
|
|
|
+ </button>
|
|
|
+ <IconButton svg={BookmarksOutlinedSvg} onClick={this.togglePresets} toggleState={this.state.show === 'presets'} title='Color Presets'
|
|
|
+ style={{ padding: 0, position: 'absolute', right: 0, top: 0, width: '32px' }} />
|
|
|
+ </>;
|
|
|
+ }
|
|
|
+
|
|
|
+ selectPreset: ActionMenu.OnSelect = item => {
|
|
|
+ if (!item) return;
|
|
|
+ this.setState({ show: void 0 });
|
|
|
+
|
|
|
+ const preset = getColorListFromName(item.value as ColorListName);
|
|
|
+ this.update({ kind: preset.type !== 'qualitative' ? 'interpolate' : 'set', colors: preset.list });
|
|
|
+ }
|
|
|
+
|
|
|
+ colorsChanged: ParamOnChange = ({ value }) => {
|
|
|
+ const colors = (value as (typeof _colorListHelpers)['OffsetColorsParam']['defaultValue']).map(c => [c.color, c.offset] as [Color, number]);
|
|
|
+ colors.sort((a, b) => a[1] - b[1]);
|
|
|
+ this.update({ kind: this.props.value.kind, colors });
|
|
|
+ }
|
|
|
+
|
|
|
+ isInterpolatedChanged: ParamOnChange = ({ value }) => {
|
|
|
+ this.update({ kind: value ? 'interpolate' : 'set', colors: this.props.value.colors });
|
|
|
+ }
|
|
|
+
|
|
|
+ renderColors() {
|
|
|
+ if (!this.state.show) return null;
|
|
|
+ const { ColorPresets, OffsetColorsParam, IsInterpolatedParam } = ColorListHelpers();
|
|
|
+
|
|
|
+ const preset = ColorPresets[this.props.param.presetKind];
|
|
|
+ if (this.state.show === 'presets') return <ActionMenu items={preset} onSelect={this.selectPreset} />;
|
|
|
+
|
|
|
+ const colors = this.props.value.colors;
|
|
|
+ const values = colors.map((color, i) => {
|
|
|
+ if (Array.isArray(color)) return { color: color[0], offset: color[1] };
|
|
|
+ return { color, offset: i / colors.length };
|
|
|
+ });
|
|
|
+ values.sort((a, b) => a.offset - b.offset);
|
|
|
+ return <div className='msp-control-offset'>
|
|
|
+ <ObjectListControl name='colors' param={OffsetColorsParam} value={values} onChange={this.colorsChanged} isDisabled={this.props.isDisabled} onEnter={this.props.onEnter} />
|
|
|
+ <BoolControl name='isInterpolated' param={IsInterpolatedParam} value={this.props.value.kind === 'interpolate'} onChange={this.isInterpolatedChanged} isDisabled={this.props.isDisabled} onEnter={this.props.onEnter} />
|
|
|
+ </div>;
|
|
|
+ }
|
|
|
+
|
|
|
+ toggleHelp = () => this.setState({ showHelp: !this.state.showHelp });
|
|
|
+
|
|
|
+ render() {
|
|
|
+ return renderSimple({
|
|
|
+ props: this.props,
|
|
|
+ state: this.state,
|
|
|
+ control: this.renderControl(),
|
|
|
+ toggleHelp: this.toggleHelp,
|
|
|
+ addOn: this.renderColors()
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export class Vec3Control extends React.PureComponent<ParamProps<PD.Vec3>, { isExpanded: boolean }> {
|
|
|
state = { isExpanded: false }
|
|
|
|