diff --git a/src/mol-plugin-ui/controls/common.tsx b/src/mol-plugin-ui/controls/common.tsx index 3fe23915c2d97be7ce50a5d3dbde0e101e25e4c7..04fbab7abc2541c385123b3ddb131c183dab192c 100644 --- a/src/mol-plugin-ui/controls/common.tsx +++ b/src/mol-plugin-ui/controls/common.tsx @@ -64,7 +64,8 @@ export interface TextInputProps<T> { blurOnEnter?: boolean, blurOnEscape?: boolean, isDisabled?: boolean, - placeholder?: string + placeholder?: string, + numeric?: boolean } interface TextInputState { @@ -116,15 +117,21 @@ export class TextInput<T = string> extends React.PureComponent<TextInputProps<T> onChange = (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; - if (this.props.isValid && !this.props.isValid(value)) { + let isInvalid = (this.props.isValid && !this.props.isValid(value)) || (this.props.numeric && Number.isNaN(+value)); + if (isInvalid) { this.clearTimeout(); this.setState({ value }); return; } - const converted = (this.props.toValue || _id)(value); - const formatted = (this.props.fromValue || _id)(converted); - this.setState({ value: formatted }, () => this.triggerChanged(formatted, converted)); + if (this.props.numeric) { + this.setState({ value }, () => this.triggerChanged(value, +value as any)); + } else { + const converted = (this.props.toValue || _id)(value); + const formatted = (this.props.fromValue || _id)(converted); + this.setState({ value: formatted }, () => this.triggerChanged(formatted, converted)); + } + } onKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => { @@ -171,62 +178,6 @@ export class TextInput<T = string> extends React.PureComponent<TextInputProps<T> } } -// TODO: replace this with parametrized TextInput -export class NumericInput extends React.PureComponent<{ - value: number, - onChange: (v: number) => void, - onEnter?: () => void, - onBlur?: () => void, - blurOnEnter?: boolean, - isDisabled?: boolean, - placeholder?: string -}, { value: string }> { - state = { value: '0' }; - input = React.createRef<HTMLInputElement>(); - - onChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const value = +e.target.value; - this.setState({ value: e.target.value }, () => { - if (!Number.isNaN(value) && value !== this.props.value) { - this.props.onChange(value); - } - }); - } - - onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { - if ((e.keyCode === 13 || e.charCode === 13)) { - if (this.props.blurOnEnter && this.input.current) { - this.input.current.blur(); - } - if (this.props.onEnter) this.props.onEnter(); - } - e.stopPropagation(); - } - - onBlur = () => { - this.setState({ value: '' + this.props.value }); - if (this.props.onBlur) this.props.onBlur(); - } - - static getDerivedStateFromProps(props: { value: number }, state: { value: string }) { - const value = +state.value; - if (Number.isNaN(value) || value === props.value) return null; - return { value: '' + props.value }; - } - - render() { - return <input type='text' - ref={this.input} - onBlur={this.onBlur} - value={this.state.value} - placeholder={this.props.placeholder} - onChange={this.onChange} - onKeyPress={this.props.onEnter || this.props.blurOnEnter ? this.onKeyPress : void 0} - disabled={!!this.props.isDisabled} - />; - } -} - export class ExpandableControlRow extends React.Component<{ label: string, colorStripe?: Color, diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index fc1874b90567a237d3b0a9f20bc7829c5fd74f40..f6a3c4a294abd16ba162f05d7c678158a1a3509f 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -19,7 +19,7 @@ import { camelCaseToWords } from '../../mol-util/string'; import { PluginUIComponent, _Props, _State } from '../base'; import { ActionMenu } from './action-menu'; import { ColorOptions, ColorValueOption, CombinedColorControl } from './color'; -import { ControlGroup, ExpandGroup, IconButton, NumericInput, ToggleButton, Button, ControlRow } from './common'; +import { ControlGroup, ExpandGroup, IconButton, TextInput, ToggleButton, Button, ControlRow } from './common'; import { Icon } from './icons'; import { legendFor } from './legend'; import LineGraphComponent from './line-graph/line-graph-component'; @@ -353,7 +353,7 @@ export class NumberInputControl extends React.PureComponent<ParamProps<PD.Numeri return <ControlRow title={this.props.param.description} label={label} - control={<NumericInput + control={<TextInput numeric value={parseFloat(this.props.value.toFixed(p))} onEnter={this.props.onEnter} placeholder={placeholder} isDisabled={this.props.isDisabled} onChange={this.update} />} />; } @@ -724,22 +724,6 @@ export class Mat4Control extends React.PureComponent<ParamProps<PD.Mat4>, { isEx state = { isExpanded: false } components = { - 0: PD.Numeric(0, undefined, { label: 'Col 0, Row 0' }), - 1: PD.Numeric(0, undefined, { label: 'Col 0, Row 1' }), - 2: PD.Numeric(0, undefined, { label: 'Col 0, Row 2' }), - 3: PD.Numeric(0, undefined, { label: 'Col 0, Row 3' }), - 4: PD.Numeric(0, undefined, { label: 'Col 1, Row 0' }), - 5: PD.Numeric(0, undefined, { label: 'Col 1, Row 1' }), - 6: PD.Numeric(0, undefined, { label: 'Col 1, Row 2' }), - 7: PD.Numeric(0, undefined, { label: 'Col 1, Row 3' }), - 8: PD.Numeric(0, undefined, { label: 'Col 2, Row 0' }), - 9: PD.Numeric(0, undefined, { label: 'Col 2, Row 1' }), - 10: PD.Numeric(0, undefined, { label: 'Col 2, Row 2' }), - 11: PD.Numeric(0, undefined, { label: 'Col 2, Row 3' }), - 12: PD.Numeric(0, undefined, { label: 'Col 3, Row 0' }), - 13: PD.Numeric(0, undefined, { label: 'Col 3, Row 1' }), - 14: PD.Numeric(0, undefined, { label: 'Col 3, Row 2' }), - 15: PD.Numeric(0, undefined, { label: 'Col 3, Row 3' }), json: PD.Text(JSON.stringify(Mat4()), { description: 'JSON array with 4x4 matrix in a column major (j * 4 + i indexing) format' }) } @@ -763,15 +747,36 @@ export class Mat4Control extends React.PureComponent<ParamProps<PD.Mat4>, { isEx e.currentTarget.blur(); } + changeValue(idx: number) { + return (v: number) => { + const m = Mat4.copy(Mat4(), this.props.value); + m[idx] = v; + this.change(m); + }; + } + + get grid() { + const v = this.props.value; + const rows: React.ReactNode[] = []; + for (let i = 0; i < 4; i++) { + const row: React.ReactNode[] = []; + for (let j = 0; j < 4; j++) { + row.push(<TextInput key={j} numeric delayMs={50} value={Mat4.getValue(v, i, j)} onChange={this.changeValue(4 * j + i)} className='msp-form-control' blurOnEnter={true} isDisabled={this.props.isDisabled} />); + } + rows.push(<div className='msp-flex-row' key={i}>{row}</div>); + } + return <div className='msp-parameter-matrix'>{rows}</div>; + } + render() { const v = { - ...this.props.value, json: JSON.stringify(this.props.value) }; const label = this.props.param.label || camelCaseToWords(this.props.name); return <> <ControlRow label={label} control={<button onClick={this.toggleExpanded} disabled={this.props.isDisabled}>{'4\u00D74 Matrix'}</button>} /> {this.state.isExpanded && <div className='msp-control-offset'> + {this.grid} <ParameterControls params={this.components} values={v} onChange={this.componentChange} onEnter={this.props.onEnter} /> </div>} </>; diff --git a/src/mol-plugin-ui/controls/slider.tsx b/src/mol-plugin-ui/controls/slider.tsx index 3795d5a65d965ea5d65e5a6f2c24566e3909dc57..34b41c97c37d26ced9e7f3b98f917c2308819167 100644 --- a/src/mol-plugin-ui/controls/slider.tsx +++ b/src/mol-plugin-ui/controls/slider.tsx @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { NumericInput } from './common'; +import { TextInput } from './common'; import { noop } from '../../mol-util'; export class Slider extends React.Component<{ @@ -65,7 +65,7 @@ export class Slider extends React.Component<{ onChange={this.updateCurrent as any} onAfterChange={this.end as any} /> </div> <div> - <NumericInput + <TextInput numeric delayMs={50} value={this.state.current} blurOnEnter={true} onBlur={this.onManualBlur} isDisabled={this.props.disabled} onChange={this.updateManually} /> </div> @@ -126,7 +126,7 @@ export class Slider2 extends React.Component<{ if (step === void 0) step = 1; return <div className='msp-slider2'> <div> - <NumericInput + <TextInput numeric delayMs={50} value={this.state.current[0]} onEnter={this.props.onEnter} blurOnEnter={true} isDisabled={this.props.disabled} onChange={this.updateMin} /> </div> @@ -135,7 +135,7 @@ export class Slider2 extends React.Component<{ onBeforeChange={this.begin} onChange={this.updateCurrent as any} onAfterChange={this.end as any} range={true} pushable={true} /> </div> <div> - <NumericInput + <TextInput numeric delayMs={50} value={this.state.current[1]} onEnter={this.props.onEnter} blurOnEnter={true} isDisabled={this.props.disabled} onChange={this.updateMax} /> </div> diff --git a/src/mol-plugin-ui/skin/base/components/misc.scss b/src/mol-plugin-ui/skin/base/components/misc.scss index 18bbf17a171a68b1933ca88115927f2456735fcc..ef6cf78e6d6d34ab4de02267345f5fac9dcf648c 100644 --- a/src/mol-plugin-ui/skin/base/components/misc.scss +++ b/src/mol-plugin-ui/skin/base/components/misc.scss @@ -531,6 +531,12 @@ } } +.msp-parameter-matrix { + input { + flex: 1 1 auto; + } +} + @mixin type-class-border($name, $color) { .msp-type-class-#{$name} { border-left-color: $color;