ソースを参照

mol-plugin: simple settings

David Sehnal 5 年 前
コミット
8d3c091d2e

+ 3 - 3
src/mol-plugin/skin/base/components/viewport.scss

@@ -66,7 +66,7 @@
 
 .msp-semi-transparent-background {    
     background: $default-background;
-    opacity: 0.2;
+    opacity: 0.5;
     position: absolute;
     top: 0;
     left: 0;
@@ -78,10 +78,10 @@
     overflow-y: auto;
     max-height: 400px;
     width: 290px;
-    right: 0px;
+    top: 0;
+    right: $row-height + $control-spacing;
     position: absolute;
     background: $control-background;
-    margin-top: $control-spacing;
 
     .msp-control-group-wrapper:first-child {
         padding-top: 0;

+ 4 - 4
src/mol-plugin/ui/base.tsx

@@ -69,12 +69,12 @@ export type _State<C extends React.Component> = C extends React.Component<any, i
 export type CollapsableProps = { initiallyCollapsed?: boolean, header?: string }
 export type CollapsableState = { isCollapsed: boolean, header: string }
 
-export abstract class CollapsableControls<P extends CollapsableProps = CollapsableProps, S extends CollapsableState = CollapsableState, SS = {}> extends PluginUIComponent<P, S, SS> {
+export abstract class CollapsableControls<P = {}, S = {}, SS = {}> extends PluginUIComponent<P & CollapsableProps, S & CollapsableState, SS> {
     toggleCollapsed = () => {
-        this.setState({ isCollapsed: !this.state.isCollapsed })
+        this.setState({ isCollapsed: !this.state.isCollapsed } as (S & CollapsableState))
     }
 
-    protected abstract defaultState(): S
+    protected abstract defaultState(): (S & CollapsableState)
     protected abstract renderControls(): JSX.Element | null
 
     render() {
@@ -93,7 +93,7 @@ export abstract class CollapsableControls<P extends CollapsableProps = Collapsab
         </div>
     }
 
-    constructor(props: P, context?: any) {
+    constructor(props: P & CollapsableProps, context?: any) {
         super(props, context)
 
         const state = this.defaultState()

+ 4 - 3
src/mol-plugin/ui/controls/common.tsx

@@ -8,20 +8,21 @@ import * as React from 'react';
 import { Color } from '../../../mol-util/color';
 import { PurePluginUIComponent } from '../base';
 
-export class ControlGroup extends React.Component<{ header: string, initialExpanded?: boolean }, { isExpanded: boolean }> {
+export class ControlGroup extends React.Component<{ header: string, initialExpanded?: boolean, hideExpander?: boolean, hideOffset?: boolean }, { isExpanded: boolean }> {
     state = { isExpanded: !!this.props.initialExpanded }
 
     toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
 
     render() {
+        // TODO: customize header style (bg color, togle button etc)
         return <div className='msp-control-group-wrapper'>
             <div className='msp-control-group-header'>
                 <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
-                    <span className={`msp-icon msp-icon-${this.state.isExpanded ? 'collapse' : 'expand'}`} />
+                    {!this.props.hideExpander && <span className={`msp-icon msp-icon-${this.state.isExpanded ? 'collapse' : 'expand'}`} />}
                     {this.props.header}
                 </button>
             </div>
-            {this.state.isExpanded && <div className='msp-control-offset' style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
+            {this.state.isExpanded && <div className={this.props.hideOffset ? '' : 'msp-control-offset'} style={{ display: this.state.isExpanded ? 'block' : 'none' }}>
                 {this.props.children}
             </div>
             }

+ 8 - 8
src/mol-plugin/ui/viewport.tsx

@@ -6,15 +6,12 @@
  */
 
 import * as React from 'react';
-import { PluginUIComponent } from './base';
+import { resizeCanvas } from '../../mol-canvas3d/util';
 import { PluginCommands } from '../../mol-plugin/command';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { ParameterControls } from './controls/parameters';
-import { Canvas3DParams } from '../../mol-canvas3d/canvas3d';
-import { PluginLayoutStateParams } from '../../mol-plugin/layout';
+import { PluginUIComponent } from './base';
 import { ControlGroup, IconButton } from './controls/common';
-import { resizeCanvas } from '../../mol-canvas3d/util';
-import { Interactivity } from '../util/interactivity';
+import { SimpleSettingsControl } from './viewport/simple-settings';
 
 interface ViewportControlsState {
     isSettingsExpanded: boolean
@@ -88,7 +85,10 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
                 {!this.props.hideSettingsIcon && this.icon('settings', this.toggleSettingsExpanded, 'Settings', this.state.isSettingsExpanded)}
             </div>
             {this.state.isSettingsExpanded && <div className='msp-viewport-controls-scene-options'>
-                <ControlGroup header='Layout' initialExpanded={true}>
+                <ControlGroup header='Basic Settings' initialExpanded={true} hideExpander={true} hideOffset={true}>
+                    <SimpleSettingsControl />
+                </ControlGroup>
+                {/* <ControlGroup header='Layout' initialExpanded={true}>
                     <ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.state} onChange={this.setLayout} />
                 </ControlGroup>
                 <ControlGroup header='Interactivity' initialExpanded={true}>
@@ -96,7 +96,7 @@ export class ViewportControls extends PluginUIComponent<ViewportControlsProps, V
                 </ControlGroup>
                 {this.plugin.canvas3d && <ControlGroup header='Viewport' initialExpanded={true}>
                     <ParameterControls params={Canvas3DParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
-                </ControlGroup>}
+                </ControlGroup>} */}
             </div>}
         </div>
     }

+ 141 - 0
src/mol-plugin/ui/viewport/simple-settings.tsx

@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import * as React from 'react';
+import { Canvas3DParams } from '../../../mol-canvas3d/canvas3d';
+import { PluginCommands } from '../../command';
+import { ColorNames } from '../../../mol-util/color/names';
+import { ParameterControls } from '../controls/parameters';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { PluginUIComponent } from '../base';
+import { Color } from '../../../mol-util/color';
+
+const SimpleSettingsParams = {
+    spin: Canvas3DParams.trackball.params.spin,
+    camera: Canvas3DParams.cameraMode,
+    background: PD.MappedStatic('white', {
+        'white': PD.EmptyGroup(),
+        'black': PD.EmptyGroup(),
+        'transparent': PD.EmptyGroup(),
+        'custom': PD.Group({ color: PD.Color(Color(0xFCFBF9), { description: 'Custom background color' }) }, { isFlat: true })
+    }, { description: 'Background of the 3D canvas' }),
+    renderStyle: PD.Select('glossy', [['toon', 'Toon'], ['matte', 'Matte'], ['glossy', 'Glossy'], ['metallic', 'Metallic']], { description: 'Style in which the 3D scene is rendered' }),
+    occlusion: PD.Boolean(false, { description: 'Darken occluded crevices with the ambient occlusion effect' }),
+    outline: PD.Boolean(false, { description: 'Draw outline around 3D objects' }),
+    fog: PD.Boolean(false, { description: 'Show fog in the distance' }),
+};
+
+export class SimpleSettingsControl extends PluginUIComponent {
+    setSettings = (p: { param: PD.Base<any>, name: keyof typeof SimpleSettingsParams | string, value: any }) => {
+        if (p.name === 'spin') {
+            if (!this.plugin.canvas3d) return;
+            const trackball = this.plugin.canvas3d.props.trackball;
+            PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { trackball: { ...trackball, spin: p.value } } });
+        } else if (p.name === 'camera') {
+            PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { cameraMode: p.value }});
+        } else if (p.name === 'background') {
+            if (!this.plugin.canvas3d) return;
+            const renderer = this.plugin.canvas3d.props.renderer;
+            const color: typeof SimpleSettingsParams['background']['defaultValue'] = p.value;
+            if (color.name === 'white') {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.white, transparentBackground: false } } });
+            } else if (color.name === 'black') {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.black, transparentBackground: false } } });
+            } else if (color.name === 'transparent') {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: ColorNames.white, transparentBackground: true } } });
+            } else {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { renderer: { ...renderer, backgroundColor: color.params.color, transparentBackground: false } } });
+            }
+        } else if (p.name === 'renderStyle') {
+            if (!this.plugin.canvas3d) return;
+
+            const renderer = this.plugin.canvas3d.props.renderer;
+            if (p.value === 'toon') {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
+                    renderer: { ...renderer, lightIntensity: 0, ambientIntensity: 1, roughness: 0.4, metalness: 0 }
+                } });
+            } else if (p.value === 'matte') {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
+                    renderer: { ...renderer, lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 1, metalness: 0 }
+                } });
+            } else if (p.value === 'glossy') {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
+                    renderer: { ...renderer, lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 0.4, metalness: 0 }
+                } });
+            } else if (p.value === 'metallic') {
+                PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
+                    renderer: { ...renderer, lightIntensity: 0.6, ambientIntensity: 0.4, roughness: 0.6, metalness: 0.4 }
+                } });
+            }
+        } else if (p.name === 'occlusion') {
+            if (!this.plugin.canvas3d) return;
+            const postprocessing = this.plugin.canvas3d.props.postprocessing;
+            PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
+                postprocessing: { ...postprocessing, occlusionEnable: p.value, occlusionBias: 0.5, occlusionRadius: 64 },
+            } });
+        } else if (p.name === 'outline') {
+            if (!this.plugin.canvas3d) return;
+            const postprocessing = this.plugin.canvas3d.props.postprocessing;
+            PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
+                postprocessing: { ...postprocessing, outlineEnable: p.value },
+            } });
+        } else if (p.name === 'fog') {;
+            PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: {
+                cameraFog: p.value ? 50 : 1,
+            } });
+        }
+    }
+
+    get values () {
+        const renderer = this.plugin.canvas3d?.props.renderer;
+
+        let renderStyle = 'custom'
+        let background: typeof SimpleSettingsParams['background']['defaultValue'] = { name: 'white', params: { } }
+
+        if (renderer) {
+            if (renderer.lightIntensity === 0 && renderer.ambientIntensity === 1 && renderer.roughness === 0.4 && renderer.metalness === 0) {
+                renderStyle = 'toon'
+            } else if (renderer.lightIntensity === 0.6 && renderer.ambientIntensity === 0.4) {
+                if (renderer.roughness === 1 && renderer.metalness === 0) {
+                    renderStyle = 'matte'
+                } else if (renderer.roughness === 0.4 && renderer.metalness === 0) {
+                    renderStyle = 'glossy'
+                } else if (renderer.roughness === 0.6 && renderer.metalness === 0.4) {
+                    renderStyle = 'metallic'
+                }
+            }
+
+            if (renderer.backgroundColor === ColorNames.white && !renderer.transparentBackground) {
+                background = { name: 'white', params: { } }
+            } else if (renderer.backgroundColor === ColorNames.black && !renderer.transparentBackground) {
+                background = { name: 'black', params: { } }
+            } else if (renderer.backgroundColor === ColorNames.white && renderer.transparentBackground) {
+                background = { name: 'transparent', params: { } }
+            } else {
+                background = { name: 'custom', params: { color: renderer.backgroundColor } }
+            }
+        }
+
+        return {
+            spin: !!this.plugin.canvas3d?.props.trackball.spin,
+            camera: this.plugin.canvas3d?.props.cameraMode,
+            background,
+            renderStyle,
+            occlusion: this.plugin.canvas3d?.props.postprocessing.occlusionEnable,
+            outline: this.plugin.canvas3d?.props.postprocessing.outlineEnable,
+            fog: this.plugin.canvas3d ? this.plugin.canvas3d.props.cameraFog > 1 : false
+        }
+    }
+
+    componentDidMount() {
+        this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
+    }
+
+    render() {
+        return <ParameterControls params={SimpleSettingsParams} values={this.values} onChange={this.setSettings} />
+    }
+}

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

@@ -11,6 +11,7 @@ import { Vec2 as Vec2Data, Vec3 as Vec3Data } from '../mol-math/linear-algebra';
 import { deepClone } from './object';
 import { Script as ScriptData } from '../mol-script/script';
 import { Legend } from './legend';
+import { camelCaseToWords } from './string';
 
 export namespace ParamDefinition {
     export interface Info {
@@ -202,7 +203,7 @@ export namespace ParamDefinition {
     export function MappedStatic<C extends Params>(defaultKey: keyof C, map: C, info?: Info & { options?: [keyof C, string][] }): Mapped<NamedParamUnion<C>> {
         const options: [string, string][] = info && info.options
             ? info.options as [string, string][]
-            : Object.keys(map).map(k => [k, k]) as [string, string][];
+            : Object.keys(map).map(k => [k, camelCaseToWords(k)]) as [string, string][];
         const name = checkDefaultKey(defaultKey, options);
         return setInfo<Mapped<NamedParamUnion<C>>>({
             type: 'mapped',