diff --git a/src/mol-plugin/skin/base/components/misc.scss b/src/mol-plugin/skin/base/components/misc.scss index 97cb047b5a80f095da9c3f814c8414959d4b4854..7276cb46aaeb6b0cc54823a732731a6e751093c9 100644 --- a/src/mol-plugin/skin/base/components/misc.scss +++ b/src/mol-plugin/skin/base/components/misc.scss @@ -70,4 +70,33 @@ .msp-controls-section { margin-bottom: $control-spacing; +} + +.msp-combined-color-button { + border: 4px solid $msp-form-control-background !important; + margin: 0; + text-align: center; + padding-right: $control-spacing; + padding-left: $control-spacing; + + &:hover { + border-color: color-increase-contrast($msp-form-control-background, 5%) !important; + border: none; + outline-offset: -1px !important; + outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important; + } +} + +.msp-combined-color-swatch { + width: 100%; + display: grid; + grid-gap: 1px; + grid-template-columns: repeat(6, auto); + + .msp-btn { + &:hover { + outline-offset: -1px !important; + outline: 1px solid color-increase-contrast($msp-form-control-background, 20%) !important; + } + } } \ No newline at end of file diff --git a/src/mol-plugin/ui/controls/color.tsx b/src/mol-plugin/ui/controls/color.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9122634d391229236bf251dcb3016dd4520be06b --- /dev/null +++ b/src/mol-plugin/ui/controls/color.tsx @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Color } from '../../../mol-util/color'; +import { ColorNames, ColorNamesValueMap } from '../../../mol-util/color/names'; +import { ParamDefinition as PD } from '../../../mol-util/param-definition'; +import { camelCaseToWords } from '../../../mol-util/string'; +import * as React from 'react'; +import { _Props, _State } from '../base'; +import { ParamProps } from './parameters'; + +export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Color>, { isExpanded: boolean }> { + state = { isExpanded: false } + + protected update(value: Color) { + this.props.onChange({ param: this.props.param, name: this.props.name, value }); + } + + toggleExpanded = (e: React.MouseEvent<HTMLButtonElement>) => { + this.setState({ isExpanded: !this.state.isExpanded }); + e.currentTarget.blur(); + } + + onChangeSelect = (e: React.ChangeEvent<HTMLSelectElement>) => { + const value = Color(parseInt(e.target.value)); + if (value !== this.props.value) { + this.update(value); + } + } + + onClickSwatch = (e: React.MouseEvent<HTMLButtonElement>) => { + const value = Color(+(e.currentTarget.getAttribute('data-color') || '0')); + if (value !== this.props.value) { + this.update(value); + } + } + + swatch() { + // const def = this.props.param.defaultValue; + return <div className='msp-combined-color-swatch'> + {/* <button title='Default Color' key={def} className='msp-form-control msp-btn' data-color={def} onClick={this.onClickSwatch} style={{ background: Color.toStyle(def) }}></button> */} + {SwatchColors.map(c => <button key={c} className='msp-form-control msp-btn' data-color={c} onClick={this.onClickSwatch} style={{ background: Color.toStyle(c) }}></button>)} + </div>; + } + + // TODO: include text options as well? + // onChangeText = () => {}; + // text() { + // const [r, g, b] = Color.toRgb(this.props.value); + // return <input type='text' + // value={`${r} ${g} ${b}`} + // placeholder={'Red Green Blue'} + // onChange={this.onChangeText} + // onKeyPress={this.props.onEnter ? this.onKeyPress : void 0} + // disabled={this.props.isDisabled} + // />; + // } + // onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { + // if (!this.props.onEnter) return; + // if ((e.keyCode === 13 || e.charCode === 13)) { + // this.props.onEnter(); + // } + // } + + stripStyle(): React.CSSProperties { + return { + background: Color.toStyle(this.props.value), + position: 'absolute', + bottom: '0', + height: '4px', + right: '0', + left: '0' + }; + } + + + render() { + const label = this.props.param.label || camelCaseToWords(this.props.name); + return <> + <div className='msp-control-row'> + <span>{label}</span> + <div> + <button onClick={this.toggleExpanded} className='msp-combined-color-button' style={{ background: Color.toStyle(this.props.value) }}></button> + </div> + </div> + {this.state.isExpanded && <> + {this.swatch()} + <div className='msp-control-row'> + <div style={{ position: 'relative' }}> + <select value={this.props.value} onChange={this.onChangeSelect}> + {ColorValueOption(this.props.value)} + {ColorOptions()} + </select> + <div style={this.stripStyle()} /> + </div> + </div> + </>} + </>; + } +} + +// the 1st color is the default value. +const SwatchColors = [ + 0x000000, 0x808080, 0xFFFFFF, 0xD33115, 0xE27300, 0xFCC400, + 0x68BC00, 0x16A5A5, 0x009CE0, 0x7B64FF, 0xFA28FF, 0x7D2187 +].map(Color); + +let _colors: React.ReactFragment | undefined = void 0; +function ColorOptions() { + if (_colors) return _colors; + _colors = <>{Object.keys(ColorNames).map(name => + <option key={name} value={(ColorNames as { [k: string]: Color })[name]} style={{ background: `${Color.toStyle((ColorNames as { [k: string]: Color })[name])}` }} > + {name} + </option> + )}</>; + return _colors; +} + +function ColorValueOption(color: Color) { + return !ColorNamesValueMap.has(color) ? <option key={Color.toHexString(color)} value={color} style={{ background: `${Color.toStyle(color)}` }} > + {Color.toRgbString(color)} + </option> : null +} \ No newline at end of file diff --git a/src/mol-plugin/ui/controls/parameters.tsx b/src/mol-plugin/ui/controls/parameters.tsx index 3ef023cf8e0c5399c23524e644cfffc81406f1ee..486ce2978704350da532adcb6483fa54e065546a 100644 --- a/src/mol-plugin/ui/controls/parameters.tsx +++ b/src/mol-plugin/ui/controls/parameters.tsx @@ -19,6 +19,7 @@ import { NumericInput, IconButton, ControlGroup } from './common'; import { _Props, _State } from '../base'; import { legendFor } from './legend'; import { Legend as LegendData } from '../../../mol-util/legend'; +import { CombinedColorControl } from './color'; export interface ParameterControlsProps<P extends PD.Params = PD.Params> { params: P, @@ -55,7 +56,7 @@ function controlFor(param: PD.Any): ParamControl | undefined { case 'converted': return ConvertedControl; case 'conditioned': return ConditionedControl; case 'multi-select': return MultiSelectControl; - case 'color': return ColorControl; + case 'color': return CombinedColorControl; case 'color-list': return ColorListControl; case 'vec3': return Vec3Control; case 'file': return FileControl;