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 { numDecimals, stof } from './util';
import { fuzzyCmp, numDecimals, reduceDecimals, stof } from './util';
const Zero = '0'.charCodeAt(0);
const Nine = '9'.charCodeAt(0);
const Minus = '-'.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++) {
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;
}
if (maxNumDecimals !== undefined)
return numDecimals(s) <= maxNumDecimals;
return true;
}
......@@ -109,9 +120,7 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
super(props);
this.state = {
displayedValue: this.props.formatter
? this.props.formatter(this.props.value.toString())
: this.props.value.toString(),
displayedValue: this.formatValue(this.props.value),
};
}
......@@ -129,7 +138,21 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
return;
const nv = n - this.props.step;
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() {
......@@ -137,11 +160,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
if (n === undefined)
return;
const nv = n + this.props.step;
if (nv >= this.props.min)
this.props.onChange(n);
if (nv <= this.props.max)
this.notifyChange(nv);
}
private handleChange(value: string) {
private maybeNotifyUpdate(value: string) {
if (this.props.maxNumDecimals !== undefined && numDecimals(value) > this.props.maxNumDecimals)
return;
......@@ -149,23 +172,33 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
if (
n !== undefined &&
n !== this.props.value &&
(this.props.min <= n && n <= this.props.max) &&
value[value.length - 1] !== '.'
!fuzzyCmp(n, this.props.value) &&
(this.props.min <= n && n <= this.props.max)
) {
this.props.onChange(n);
} else {
if (maybeNumeric(value))
this.setState({ ...this.state, displayedValue: value });
this.notifyChange(n);
}
}
componentDidUpdate(prevProps: SpinBox.Props) {
if (this.props !== prevProps) {
const displayedValue = this.props.formatter
? this.props.formatter(this.props.value.toString())
: this.props.value.toString();
this.setState({ ...this.state, displayedValue });
private notifyChange(n: number) {
if (this.props.maxNumDecimals !== undefined) {
const Factor = Math.pow(10, this.props.maxNumDecimals);
this.props.onChange(Math.round(n * Factor) / Factor);
} 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 });
} 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> {
type='text'
className={this.props.disabled ? this.clsDisabled() : this.clsEnabled()}
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 => {
evt.stopPropagation();
const n = stof(this.state.displayedValue);
......@@ -185,11 +222,11 @@ export class SpinBox extends React.Component<SpinBox.Props, SpinBoxState> {
if (evt.deltaY < 0) {
const nv = n + this.props.step;
if (nv <= this.props.max)
this.props.onChange(nv);
this.notifyChange(nv);
} else if (evt.deltaY > 0) {
const nv = n - this.props.step;
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> {
}
export namespace SpinBox {
export interface Formatter {
(v: string): string;
(v: number): string;
}
export interface OnChange {
......@@ -226,7 +263,6 @@ export namespace SpinBox {
disabled?: boolean;
className?: string;
classNameDisabled?: string;
formatter?: Formatter;
maxNumDecimals?: number;
}
}
import React from 'react';
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> {
render() {
......@@ -65,28 +65,22 @@ export class DensityMapControls extends React.Component<DensityMapControls.Props
<div className='rmsp-control-item'>
<RangeSlider
min={0}
max={1}
step={0.1}
value={(1.0 - this.props.alpha)}
onChange={(v) => this.props.changeAlpha(1.0 - v!)}
max={100}
step={1}
value={(1.0 - this.props.alpha) * 100}
onChange={(n) => this.props.changeAlpha(1.0 - (n! / 100))}
/>
</div>
<div className='rmsp-control-item'>
<div style={{ display: 'grid', gridTemplateColumns: '4em 1fr' }}>
<SpinBox
min={0}
max={1}
step={0.1}
maxNumDecimals={1}
value={(1.0 - this.props.alpha)}
onChange={(n) => this.props.changeAlpha(1.0 - n)}
max={100}
step={1}
maxNumDecimals={0}
value={(1.0 - this.props.alpha) * 100}
onChange={(n) => this.props.changeAlpha(1.0 - (n / 100))}
pathPrefix=''
formatter={v => {
const n = stof(v);
if (n !== undefined && numDecimals(v) > 1)
return n.toFixed(1);
return v;
}}
/>
</div>
</div>
......
import { Color } from '../../mol-util/color';
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 } {
let diff = max - min;
if (diff <= 0.0)
......@@ -41,6 +56,23 @@ export function prettyIso(iso: number, step: number) {
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) {
if (s.length === 0)
return void 0;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment