Skip to content
Snippets Groups Projects
Commit 9e81a47c authored by Michal Malý's avatar Michal Malý
Browse files

Attempt for saner behavior of SpinBox

parent cbcb7266
No related branches found
No related tags found
No related merge requests found
import React from 'react'; import React from 'react';
import { numDecimals, stof } from './util'; import { fuzzyCmp, numDecimals, reduceDecimals, stof } from './util';
const Zero = '0'.charCodeAt(0); const Zero = '0'.charCodeAt(0);
const Nine = '9'.charCodeAt(0); const Nine = '9'.charCodeAt(0);
const Minus = '-'.charCodeAt(0); const Minus = '-'.charCodeAt(0);
const Period = '.'.charCodeAt(0); const Period = '.'.charCodeAt(0);
function maybeNumeric(s: string) { function isAllowedNumericInput(s: string, maxNumDecimals?: number) {
let havePeriod = false;
for (let idx = 0; idx < s.length; idx++) { for (let idx = 0; idx < s.length; idx++) {
const cc = s.charCodeAt(idx); const cc = s.charCodeAt(idx);
if (!((cc >= Zero && cc <= Nine) || cc === Minus || cc === Period)) if (cc === Period) {
if (havePeriod)
return false;
else
havePeriod = true;
} else if (cc === Minus) {
if (idx > 0)
return false;
} else if (!(cc >= Zero && cc <= Nine))
return false; return false;
} }
if (maxNumDecimals !== undefined)
return numDecimals(s) <= maxNumDecimals;
return true; return true;
} }
...@@ -109,9 +120,7 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> { ...@@ -109,9 +120,7 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
super(props); super(props);
this.state = { this.state = {
displayedValue: this.props.formatter displayedValue: this.formatValue(this.props.value),
? this.props.formatter(this.props.value.toString())
: this.props.value.toString(),
}; };
} }
...@@ -129,7 +138,21 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> { ...@@ -129,7 +138,21 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
return; return;
const nv = n - this.props.step; const nv = n - this.props.step;
if (nv >= this.props.min) if (nv >= this.props.min)
this.props.onChange(n); this.notifyChange(nv);
}
private formatValue(n: number, padToDecimals?: number) {
if (this.props.maxNumDecimals !== undefined) {
if (padToDecimals !== undefined) {
const padded = n.toFixed(padToDecimals);
if (fuzzyCmp(n, stof(padded)!))
return padded;
}
const fn = n.toFixed(this.props.maxNumDecimals);
return reduceDecimals(fn);
}
return n.toString();
} }
private increase() { private increase() {
...@@ -137,11 +160,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> { ...@@ -137,11 +160,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
if (n === undefined) if (n === undefined)
return; return;
const nv = n + this.props.step; const nv = n + this.props.step;
if (nv >= this.props.min) if (nv <= this.props.max)
this.props.onChange(n); this.notifyChange(nv);
} }
private handleChange(value: string) { private maybeNotifyUpdate(value: string) {
if (this.props.maxNumDecimals !== undefined && numDecimals(value) > this.props.maxNumDecimals) if (this.props.maxNumDecimals !== undefined && numDecimals(value) > this.props.maxNumDecimals)
return; return;
...@@ -149,23 +172,33 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> { ...@@ -149,23 +172,33 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
if ( if (
n !== undefined && n !== undefined &&
n !== this.props.value && !fuzzyCmp(n, this.props.value) &&
(this.props.min <= n && n <= this.props.max) && (this.props.min <= n && n <= this.props.max)
value[value.length - 1] !== '.'
) { ) {
this.props.onChange(n); this.notifyChange(n);
} else {
if (maybeNumeric(value))
this.setState({ ...this.state, displayedValue: value });
} }
} }
componentDidUpdate(prevProps: SpinBox.Props) { private notifyChange(n: number) {
if (this.props !== prevProps) { if (this.props.maxNumDecimals !== undefined) {
const displayedValue = this.props.formatter const Factor = Math.pow(10, this.props.maxNumDecimals);
? this.props.formatter(this.props.value.toString()) this.props.onChange(Math.round(n * Factor) / Factor);
: this.props.value.toString(); } else
this.props.onChange(n);
}
componentDidUpdate(prevProps: SpinBox.Props, prevState: SpinBoxState) {
if (this.props !== prevProps && this.props.value !== prevProps.value) {
const padToDecimals = stof(this.state.displayedValue) !== undefined ? numDecimals(this.state.displayedValue) : void 0;
const displayedValue = this.formatValue(this.props.value, padToDecimals);
if (
displayedValue !== this.state.displayedValue &&
displayedValue !== this.state.displayedValue.substring(0, this.state.displayedValue.length - 1)
)
this.setState({ ...this.state, displayedValue }); this.setState({ ...this.state, displayedValue });
} else {
if (this.state.displayedValue !== prevState.displayedValue)
this.maybeNotifyUpdate(this.state.displayedValue);
} }
} }
...@@ -176,7 +209,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> { ...@@ -176,7 +209,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
type='text' type='text'
className={this.props.disabled ? this.clsDisabled() : this.clsEnabled()} className={this.props.disabled ? this.clsDisabled() : this.clsEnabled()}
value={this.state.displayedValue} value={this.state.displayedValue}
onChange={evt => this.handleChange(evt.currentTarget.value)} onChange={evt => {
const v = evt.currentTarget.value;
if (isAllowedNumericInput(v, this.props.maxNumDecimals))
this.setState({ ...this.state, displayedValue: v });
}}
onWheel={evt => { onWheel={evt => {
evt.stopPropagation(); evt.stopPropagation();
const n = stof(this.state.displayedValue); const n = stof(this.state.displayedValue);
...@@ -185,11 +222,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> { ...@@ -185,11 +222,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
if (evt.deltaY < 0) { if (evt.deltaY < 0) {
const nv = n + this.props.step; const nv = n + this.props.step;
if (nv <= this.props.max) if (nv <= this.props.max)
this.props.onChange(nv); this.notifyChange(nv);
} else if (evt.deltaY > 0) { } else if (evt.deltaY > 0) {
const nv = n - this.props.step; const nv = n - this.props.step;
if (nv >= this.props.min) if (nv >= this.props.min)
this.props.onChange(nv); this.notifyChange(nv);
} }
}} }}
/> />
...@@ -209,7 +246,7 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> { ...@@ -209,7 +246,7 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
} }
export namespace SpinBox { export namespace SpinBox {
export interface Formatter { export interface Formatter {
(v: string): string; (v: number): string;
} }
export interface OnChange { export interface OnChange {
...@@ -226,7 +263,6 @@ export namespace SpinBox { ...@@ -226,7 +263,6 @@ export namespace SpinBox {
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
classNameDisabled?: string; classNameDisabled?: string;
formatter?: Formatter;
maxNumDecimals?: number; maxNumDecimals?: number;
} }
} }
import React from 'react'; import React from 'react';
import { CollapsibleVertical, RangeSlider, SpinBox, ToggleButton } from './controls'; import { CollapsibleVertical, RangeSlider, SpinBox, ToggleButton } from './controls';
import { isoToFixed, numDecimals, stof } from './util'; import { isoToFixed } from './util';
export class DensityMapControls extends React.Component<DensityMapControls.Props> { export class DensityMapControls extends React.Component<DensityMapControls.Props> {
render() { render() {
...@@ -65,28 +65,22 @@ export class DensityMapControls extends React.Component<DensityMapControls.Props ...@@ -65,28 +65,22 @@ export class DensityMapControls extends React.Component<DensityMapControls.Props
<div className='rmsp-control-item'> <div className='rmsp-control-item'>
<RangeSlider <RangeSlider
min={0} min={0}
max={1} max={100}
step={0.1} step={1}
value={(1.0 - this.props.alpha)} value={(1.0 - this.props.alpha) * 100}
onChange={(v) => this.props.changeAlpha(1.0 - v!)} onChange={(n) => this.props.changeAlpha(1.0 - (n! / 100))}
/> />
</div> </div>
<div className='rmsp-control-item'> <div className='rmsp-control-item'>
<div style={{ display: 'grid', gridTemplateColumns: '4em 1fr' }}> <div style={{ display: 'grid', gridTemplateColumns: '4em 1fr' }}>
<SpinBox <SpinBox
min={0} min={0}
max={1} max={100}
step={0.1} step={1}
maxNumDecimals={1} maxNumDecimals={0}
value={(1.0 - this.props.alpha)} value={(1.0 - this.props.alpha) * 100}
onChange={(n) => this.props.changeAlpha(1.0 - n)} onChange={(n) => this.props.changeAlpha(1.0 - (n / 100))}
pathPrefix='' pathPrefix=''
formatter={v => {
const n = stof(v);
if (n !== undefined && numDecimals(v) > 1)
return n.toFixed(1);
return v;
}}
/> />
</div> </div>
</div> </div>
......
import { Color } from '../../mol-util/color'; import { Color } from '../../mol-util/color';
import { parseInt as parseIntMS, parseFloat as parseFloatMS } from '../../mol-io/reader/common/text/number-parser'; import { parseInt as parseIntMS, parseFloat as parseFloatMS } from '../../mol-io/reader/common/text/number-parser';
const Zero = '0'.charCodeAt(0);
const Period = '.'.charCodeAt(0);
export function clampDecimals(s: string, maxNumDecimals: number) {
const idx = s.lastIndexOf('.');
if (idx < 0)
return s;
return maxNumDecimals === 0 ? s.substring(0, idx) : s.substring(0, idx + maxNumDecimals + 1);
}
export function fuzzyCmp(a: number, b: number, relativeTolerance = 0.00001) {
const TOL = a * relativeTolerance;
return Math.abs(a - b) <= TOL;
}
export function isoBounds(min: number, max: number): { min: number, max: number, step: number } { export function isoBounds(min: number, max: number): { min: number, max: number, step: number } {
let diff = max - min; let diff = max - min;
if (diff <= 0.0) if (diff <= 0.0)
...@@ -41,6 +56,23 @@ export function prettyIso(iso: number, step: number) { ...@@ -41,6 +56,23 @@ export function prettyIso(iso: number, step: number) {
return Math.floor((iso - step) / step) * step + step; return Math.floor((iso - step) / step) * step + step;
} }
export function reduceDecimals(s: string) {
const delimIdx = s.lastIndexOf('.');
if (delimIdx < 0)
return s;
else if (delimIdx === s.length - 1)
return s.substring(0, s.length - 1);
let idx = s.length - 1;
for (; idx > delimIdx; idx--) {
if (s.charCodeAt(idx) !== Zero)
break;
}
const noDot = s.charCodeAt(idx) === Period ? 0 : 1
return s.substring(0, idx + noDot);
}
export function stof(s: string) { export function stof(s: string) {
if (s.length === 0) if (s.length === 0)
return void 0; return void 0;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment