Ver código fonte

ParamDefinition category and hideIf support

David Sehnal 5 anos atrás
pai
commit
4dcb68af33

+ 5 - 0
src/mol-geo/geometry/base.ts

@@ -40,6 +40,11 @@ export namespace BaseGeometry {
     }
     export type Params = typeof Params
 
+    export const CustomQualityParamInfo: PD.Info = { 
+        category: 'Custom Quality',
+        hideIf: (params: PD.Values<Params>) => typeof params.quality !== 'undefined' && params.quality !== 'custom'
+    };
+
     export type Counts = { drawCount: number, groupCount: number, instanceCount: number }
 
     export function createSimple(colorValue = ColorNames.grey, sizeValue = 1, transform?: TransformData) {

+ 7 - 4
src/mol-geo/geometry/mesh/mesh.ts

@@ -319,12 +319,15 @@ export namespace Mesh {
 
     //
 
+
+    const ShadingCategory: PD.Info = { category: 'Shading' };
+
     export const Params = {
         ...BaseGeometry.Params,
-        doubleSided: PD.Boolean(false),
-        flipSided: PD.Boolean(false),
-        flatShaded: PD.Boolean(false),
-        ignoreLight: PD.Boolean(false),
+        doubleSided: PD.Boolean(false, ShadingCategory),
+        flipSided: PD.Boolean(false, ShadingCategory),
+        flatShaded: PD.Boolean(false, ShadingCategory),
+        ignoreLight: PD.Boolean(false, ShadingCategory),
     }
     export type Params = typeof Params
 

+ 117 - 49
src/mol-plugin-ui/controls/parameters.tsx

@@ -34,63 +34,63 @@ export interface ParameterControlsProps<P extends PD.Params = PD.Params> {
     onEnter?: () => void
 }
 
-export class ParameterControls<P extends PD.Params> extends React.PureComponent<ParameterControlsProps<P>, { isExpanded: boolean }> {
-    state = { isExpanded: false };
-
+export class ParameterControls<P extends PD.Params> extends React.PureComponent<ParameterControlsProps<P>> {
     onChange: ParamOnChange = (params) => this.props.onChange(params, this.props.values);
 
-    renderControls(keys: string[], essentials: boolean) {
-        const params = this.props.params;
+    renderGroup(group: ParamInfo[]) {
+        if (group.length === 0) return null;
+
         const values = this.props.values;
+        let ctrls: JSX.Element[] | null = null;
+        let category: string | undefined = void 0;
 
-        return <>
-            {keys.map(key => {                
-                const param = params[key];
-                if (param.isHidden) return null;
-                if ((essentials && !param.isEssential) || (!essentials && param.isEssential)) return null;
-                const Control = controlFor(param);
-                if (!Control) return null;
-                return <Control param={param} key={key} onChange={this.onChange} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} name={key} value={values[key]} />
-            })}
-        </>;
-    }
+        for (const [key, p, Control] of group) {
+            if (p.hideIf?.(values)) continue;
 
-    toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
+            if (!ctrls) ctrls = [];
+            category = p.category;
+            ctrls.push(<Control param={p} key={key} onChange={this.onChange} onEnter={this.props.onEnter} isDisabled={this.props.isDisabled} name={key} value={values[key]} />)
+        }
 
-    renderCategories(keys: string[]) {
-        return <>
-            {this.renderControls(keys, true)}
-            <div className='msp-control-group-header' style={{ marginTop: '1px' }}>
-                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
-                    <span className={`msp-icon msp-icon-${this.state.isExpanded ? 'collapse' : 'expand'}`} />
-                    {'Advanced Parameters'}
-                </button>
-            </div>
-            {this.state.isExpanded && <div className='msp-control-offset'>
-                {this.renderControls(keys, false)}
-            </div>}
-        </>;
+        if (!ctrls) return null;
+
+        if (category) {
+            return [<ExpandGroup header={category}>{ctrls}</ExpandGroup>];
+        }
+        return ctrls;
     }
 
-    render() {
-        const params = this.props.params;
-        const values = this.props.values;
-        const keys = Object.keys(params);
-        if (keys.length === 0 || values === undefined) return null;
-
-        let essentialCount = 0, nonEssentialCount = 0;
-        for (const k of keys) {
-            const p = params[k];
-            if (p.isEssential) essentialCount += p.isHidden ? 0 : 1;
-            else nonEssentialCount += p.isHidden ? 0 : 1;
+    renderPart(groups: ParamInfo[][]) {
+        let parts: JSX.Element[] | null = null;
+        for (const g of groups) {
+            const ctrls = this.renderGroup(g);
+            if (!ctrls) continue;
+            if (!parts) parts = [];
+            for (const c of ctrls) parts.push(c);
         }
+        return parts;
+    }
 
-        if (essentialCount === 0 && nonEssentialCount === 0) return null;
+    paramGroups = memoize1((params: PD.Params) => classifyParams(params));
 
-        if (essentialCount === 0) return this.renderControls(keys, false);
-        if (nonEssentialCount === 0) return this.renderControls(keys, true);
+    render() {
+        const groups = this.paramGroups(this.props.params);
 
-        return this.renderCategories(keys);
+        const essentials = this.renderPart(groups.essentials);
+        const advanced = this.renderPart(groups.advanced);
+        
+        if (essentials && advanced) {
+            return <>
+                {essentials}
+                <ExpandGroup header='Advanced Options'>
+                    {advanced}
+                </ExpandGroup>
+            </>;
+        } else if (essentials) {
+            return essentials;
+        } else {
+            return advanced;
+        }
     }
 }
 
@@ -113,6 +113,74 @@ export class ParameterMappingControl<S, T> extends PluginUIComponent<{ mapping:
     }
 }
 
+class ExpandGroup extends React.PureComponent<{ header: string, initiallyExpanded?: boolean, noOffset?: boolean }, { isExpanded: boolean }> {
+    state = { isExpanded: !!this.props.initiallyExpanded };
+
+    toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
+
+    render() {
+        return <>
+            <div className='msp-control-group-header' style={{ marginTop: '1px' }}>
+                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
+                    <span className={`msp-icon msp-icon-${this.state.isExpanded ? 'collapse' : 'expand'}`} />
+                    {this.props.header}
+                </button>
+            </div>
+            {this.state.isExpanded &&
+                (this.props.noOffset
+                    ? this.props.children
+                    : <div className='msp-control-offset'>
+                        {this.props.children}
+                    </div>)}
+        </>;
+    }
+}
+
+type ParamInfo = [string, PD.Any, ParamControl];
+function classifyParams(params: PD.Params) {
+    function addParam(k: string, p: PD.Any, group: typeof essentials) {
+        const ctrl = controlFor(p);
+        if (!ctrl) return;
+
+        if (!p.category) group.params[0].push([k, p, ctrl]);
+        else {
+            if (!group.map) group.map = new Map();
+            let c = group.map.get(p.category);
+            if (!c) {
+                c = [];
+                group.map.set(p.category, c);
+                group.params.push(c);
+            }
+            c.push([k, p, ctrl]);
+        }
+    }
+
+    function sortGroups(x: ParamInfo[], y: ParamInfo[]) {
+        const a = x[0], b = y[0];
+        if (!a || !a[1].category) return -1;
+        if (!b || !b[1].category) return 1;
+        return a[1].category < b[1].category ? -1 : 1;
+    }
+
+    const keys = Object.keys(params);
+
+    const essentials: { params: ParamInfo[][], map: Map<string, ParamInfo[]> | undefined } = { params: [[]], map: void 0 };
+    const advanced: typeof essentials = { params: [[]], map: void 0 };
+
+    for (const k of keys) {
+        const p = params[k];
+        if (p.isHidden) continue;
+
+        if (p.isEssential) addParam(k, p, essentials)
+        else addParam(k, p, advanced);
+    }
+
+    essentials.params.sort(sortGroups);
+    advanced.params.sort(sortGroups);
+
+    return { essentials: essentials.params, advanced: advanced.params };
+}
+
 function controlFor(param: PD.Any): ParamControl | undefined {
     switch (param.type) {
         case 'value': return void 0;
@@ -350,7 +418,7 @@ export class TextControl extends SimpleParam<PD.Text> {
     }
 }
 
-export class PureSelectControl extends  React.PureComponent<ParamProps<PD.Select<string | number>> & { title?: string }> {
+export class PureSelectControl extends React.PureComponent<ParamProps<PD.Select<string | number>> & { title?: string }> {
     protected update(value: string | number) {
         this.props.onChange({ param: this.props.param, name: this.props.name, value });
     }
@@ -395,9 +463,9 @@ export class SelectControl extends React.PureComponent<ParamProps<PD.Select<stri
         const label = current
             ? current.label
             : typeof this.props.value === 'undefined'
-            ? `${ActionMenu.getFirstItem(items)?.label || ''} [Default]`
-            : `[Invalid] ${this.props.value}`;
-        
+                ? `${ActionMenu.getFirstItem(items)?.label || ''} [Default]`
+                : `[Invalid] ${this.props.value}`;
+
         return <ToggleButton disabled={this.props.isDisabled} style={{ textAlign: 'left', overflow: 'hidden', textOverflow: 'ellipsis' }}
             label={label} title={label as string} toggle={this.toggle} isSelected={this.state.showOptions} />;
     }

+ 2 - 1
src/mol-repr/structure/visual/nucleotide-block-mesh.ts

@@ -19,6 +19,7 @@ import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
 import { NucleotideLocationIterator, getNucleotideElementLoci, eachNucleotideElement } from './util/nucleotide';
 import { VisualUpdateState } from '../../util';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const p1 = Vec3.zero()
 const p2 = Vec3.zero()
@@ -36,7 +37,7 @@ const box = Box()
 
 export const NucleotideBlockMeshParams = {
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
 }
 export const DefaultNucleotideBlockMeshProps = PD.getDefaultValues(NucleotideBlockMeshParams)
 export type NucleotideBlockMeshProps = typeof DefaultNucleotideBlockMeshProps

+ 3 - 2
src/mol-repr/structure/visual/nucleotide-ring-mesh.ts

@@ -20,6 +20,7 @@ import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
 import { NucleotideLocationIterator, getNucleotideElementLoci, eachNucleotideElement } from './util/nucleotide';
 import { VisualUpdateState } from '../../util';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 const pTrace = Vec3.zero()
 const pN1 = Vec3.zero()
@@ -35,8 +36,8 @@ const normal = Vec3.zero()
 
 export const NucleotideRingMeshParams = {
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
-    detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
+    detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
 }
 export const DefaultNucleotideRingMeshProps = PD.getDefaultValues(NucleotideRingMeshParams)
 export type NucleotideRingProps = typeof DefaultNucleotideRingMeshProps

+ 2 - 1
src/mol-repr/structure/visual/polymer-backbone-cylinder.ts

@@ -18,10 +18,11 @@ import { addCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
 import { ElementIterator, getElementLoci, eachElement } from './util/element';
 import { VisualUpdateState } from '../../util';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 export const PolymerBackboneCylinderParams = {
     sizeFactor: PD.Numeric(0.3, { min: 0, max: 10, step: 0.01 }),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
 }
 export const DefaultPolymerBackboneCylinderProps = PD.getDefaultValues(PolymerBackboneCylinderParams)
 export type PolymerBackboneCylinderProps = typeof DefaultPolymerBackboneCylinderProps

+ 2 - 1
src/mol-repr/structure/visual/polymer-gap-cylinder.ts

@@ -16,13 +16,14 @@ import { PolymerGapIterator, PolymerGapLocationIterator, getPolymerGapElementLoc
 import { addFixedCountDashedCylinder } from '../../../mol-geo/geometry/mesh/builder/cylinder';
 import { UnitsMeshParams, UnitsVisual, UnitsMeshVisual } from '../units-visual';
 import { VisualUpdateState } from '../../util';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 // import { TriangularPyramid } from '../../../mol-geo/primitive/pyramid';
 
 const segmentCount = 10
 
 export const PolymerGapCylinderParams = {
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
 }
 export const DefaultPolymerGapCylinderProps = PD.getDefaultValues(PolymerGapCylinderParams)
 export type PolymerGapCylinderProps = typeof DefaultPolymerGapCylinderProps

+ 4 - 3
src/mol-repr/structure/visual/polymer-trace-mesh.ts

@@ -20,14 +20,15 @@ import { SecondaryStructureProvider } from '../../../mol-model-props/computed/se
 import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon';
 import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
 import { Vec3 } from '../../../mol-math/linear-algebra';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 export const PolymerTraceMeshParams = {
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
-    linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
     aspectRatio: PD.Numeric(5, { min: 0.1, max: 10, step: 0.1 }),
     arrowFactor: PD.Numeric(1.5, { min: 0, max: 3, step: 0.1 }),
+    detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
+    linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }, BaseGeometry.CustomQualityParamInfo),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo)
 }
 export const DefaultPolymerTraceMeshProps = PD.getDefaultValues(PolymerTraceMeshParams)
 export type PolymerTraceMeshProps = typeof DefaultPolymerTraceMeshProps

+ 4 - 3
src/mol-repr/structure/visual/polymer-tube-mesh.ts

@@ -19,12 +19,13 @@ import { addSheet } from '../../../mol-geo/geometry/mesh/builder/sheet';
 import { addRibbon } from '../../../mol-geo/geometry/mesh/builder/ribbon';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { addSphere } from '../../../mol-geo/geometry/mesh/builder/sphere';
+import { BaseGeometry } from '../../../mol-geo/geometry/base';
 
 export const PolymerTubeMeshParams = {
     sizeFactor: PD.Numeric(0.2, { min: 0, max: 10, step: 0.01 }),
-    detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }),
-    linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
+    detail: PD.Numeric(0, { min: 0, max: 3, step: 1 }, BaseGeometry.CustomQualityParamInfo),
+    linearSegments: PD.Numeric(8, { min: 1, max: 48, step: 1 }, BaseGeometry.CustomQualityParamInfo),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
 }
 export const DefaultPolymerTubeMeshProps = PD.getDefaultValues(PolymerTubeMeshParams)
 export type PolymerTubeMeshProps = typeof DefaultPolymerTubeMeshProps

+ 2 - 1
src/mol-repr/structure/visual/util/link.ts

@@ -11,12 +11,13 @@ import { MeshBuilder } from '../../../../mol-geo/geometry/mesh/mesh-builder';
 import { CylinderProps } from '../../../../mol-geo/primitive/cylinder';
 import { addFixedCountDashedCylinder, addCylinder, addDoubleCylinder } from '../../../../mol-geo/geometry/mesh/builder/cylinder';
 import { VisualContext } from '../../../visual';
+import { BaseGeometry } from '../../../../mol-geo/geometry/base';
 
 export const LinkCylinderParams = {
     linkScale: PD.Numeric(0.4, { min: 0, max: 1, step: 0.1 }),
     linkSpacing: PD.Numeric(1, { min: 0, max: 2, step: 0.01 }),
     linkCap: PD.Boolean(false),
-    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }),
+    radialSegments: PD.Numeric(16, { min: 2, max: 56, step: 2 }, BaseGeometry.CustomQualityParamInfo),
 }
 export const DefaultLinkCylinderProps = PD.getDefaultValues(LinkCylinderParams)
 export type LinkCylinderProps = typeof DefaultLinkCylinderProps

+ 5 - 1
src/mol-util/param-definition.ts

@@ -22,8 +22,10 @@ export namespace ParamDefinition {
         isHidden?: boolean,
         shortLabel?: boolean,
         twoColumns?: boolean,
-        isEssential?: boolean
+        isEssential?: boolean,
+        category?: string,
 
+        hideIf?: (currentGroup: any) => boolean,
         help?: (value: any) => { description?: string, legend?: Legend }
     }
 
@@ -39,7 +41,9 @@ export namespace ParamDefinition {
         if (info.shortLabel) param.shortLabel = info.shortLabel;
         if (info.twoColumns) param.twoColumns = info.twoColumns;
         if (info.isEssential) param.isEssential = info.isEssential;
+        if (info.category) param.category = info.category;
 
+        if (info.hideIf) param.hideIf = info.hideIf;
         if (info.help) param.help = info.help;
         return param;
     }