Browse Source

mol-plugin: switch between data and behavior trees

David Sehnal 6 years ago
parent
commit
72a156edaa

+ 1 - 1
src/mol-plugin/behavior/static/camera.ts

@@ -17,7 +17,7 @@ export function registerDefault(ctx: PluginContext) {
 
 export function Reset(ctx: PluginContext) {
     PluginCommands.Camera.Reset.subscribe(ctx, () => {
-        const sel = ctx.state.data.select(q => q.root.subtree().ofType(SO.Molecule.Structure));
+        const sel = ctx.state.dataState.select(q => q.root.subtree().ofType(SO.Molecule.Structure));
         if (!sel.length) return;
 
         const center = (sel[0].obj! as SO.Molecule.Structure).data.boundary.sphere.center;

+ 5 - 4
src/mol-plugin/behavior/static/representation.ts

@@ -12,14 +12,15 @@ export function registerDefault(ctx: PluginContext) {
 }
 
 export function SyncRepresentationToCanvas(ctx: PluginContext) {
-    ctx.events.state.data.object.created.subscribe(e => {
+    const events = ctx.state.dataState.events;
+    events.object.created.subscribe(e => {
         if (!SO.isRepresentation3D(e.obj)) return;
         ctx.canvas3d.add(e.obj.data);
         ctx.canvas3d.requestDraw(true);
 
         // TODO: update visiblity
     });
-    ctx.events.state.data.object.updated.subscribe(e => {
+    events.object.updated.subscribe(e => {
         if (e.oldObj && SO.isRepresentation3D(e.oldObj)) {
             ctx.canvas3d.remove(e.oldObj.data);
             ctx.canvas3d.requestDraw(true);
@@ -32,7 +33,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
         ctx.canvas3d.add(e.obj.data);
         ctx.canvas3d.requestDraw(true);
     });
-    ctx.events.state.data.object.removed.subscribe(e => {
+    events.object.removed.subscribe(e => {
         const oo = e.obj;
         if (!SO.isRepresentation3D(oo)) return;
         ctx.canvas3d.remove(oo.data);
@@ -42,7 +43,7 @@ export function SyncRepresentationToCanvas(ctx: PluginContext) {
 }
 
 export function UpdateRepresentationVisibility(ctx: PluginContext) {
-    ctx.events.state.data.object.cellState.subscribe(e => {
+    ctx.state.dataState.events.object.cellState.subscribe(e => {
         const cell = e.state.cells.get(e.ref)!;
         if (!SO.isRepresentation3D(cell.obj)) return;
 

+ 21 - 14
src/mol-plugin/context.ts

@@ -30,8 +30,16 @@ export class PluginContext {
 
     readonly events = {
         state: {
-            data: this.state.data.events,
-            behavior: this.state.behavior.events,
+            object: {
+                cellState: merge(this.state.dataState.events.object.cellState, this.state.behaviorState.events.object.cellState),
+                cellCreated: merge(this.state.dataState.events.object.cellCreated, this.state.behaviorState.events.object.cellCreated),
+
+                created: merge(this.state.dataState.events.object.created, this.state.behaviorState.events.object.created),
+                removed: merge(this.state.dataState.events.object.removed, this.state.behaviorState.events.object.removed),
+                updated: merge(this.state.dataState.events.object.updated, this.state.behaviorState.events.object.updated)
+            },
+            // data: this.state.dataState.events,
+            // behavior: this.state.behaviorState.events,
             cameraSnapshots: this.state.cameraSnapshots.events,
             snapshots: this.state.snapshots.events,
         },
@@ -40,10 +48,10 @@ export class PluginContext {
     };
 
     readonly behaviors = {
-        state: {
-            data: this.state.data.behaviors,
-            behavior: this.state.behavior.behaviors
-        },
+        // state: {
+        //     data: this.state.dataState.behaviors,
+        //     behavior: this.state.behaviorState.behaviors
+        // },
         canvas: {
             highlightLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
             selectLoci: this.ev.behavior<{ loci: Loci, repr?: Representation.Any }>({ loci: EmptyLoci }),
@@ -98,20 +106,20 @@ export class PluginContext {
         BuiltInPluginBehaviors.Representation.registerDefault(this);
         BuiltInPluginBehaviors.Camera.registerDefault(this);
 
-        merge(this.state.data.events.log, this.state.behavior.events.log).subscribe(e => this.events.log.next(e));
+        merge(this.state.dataState.events.log, this.state.behaviorState.events.log).subscribe(e => this.events.log.next(e));
     }
 
     async _test_initBehaviors() {
-        const tree = this.state.behavior.tree.build()
+        const tree = this.state.behaviorState.tree.build()
             .toRoot().apply(PluginBehaviors.Representation.HighlightLoci, { ref: PluginBehaviors.Representation.HighlightLoci.id })
             .toRoot().apply(PluginBehaviors.Representation.SelectLoci, { ref: PluginBehaviors.Representation.SelectLoci.id })
             .getTree();
 
-        await this.runTask(this.state.behavior.update(tree));
+        await this.runTask(this.state.behaviorState.update(tree));
     }
 
     _test_initDataActions() {
-        this.state.data.actions
+        this.state.dataState.actions
             .add(CreateStructureFromPDBe)
             .add(StateTransforms.Data.Download)
             .add(StateTransforms.Data.ParseCif)
@@ -132,18 +140,17 @@ export class PluginContext {
     }
 
     private initEvents() {
-        merge(this.events.state.data.object.created, this.events.state.behavior.object.created).subscribe(o => {
+        this.events.state.object.created.subscribe(o => {
             if (!SO.isBehavior(o.obj)) return;
-            console.log('registering behavior', o.obj.label);
             o.obj.data.register();
         });
 
-        merge(this.events.state.data.object.removed, this.events.state.behavior.object.removed).subscribe(o => {
+        this.events.state.object.removed.subscribe(o => {
             if (!SO.isBehavior(o.obj)) return;
             o.obj.data.unregister();
         });
 
-        merge(this.events.state.data.object.updated, this.events.state.behavior.object.updated).subscribe(o => {
+        this.events.state.object.updated.subscribe(o => {
             if (o.action === 'recreate') {
                 if (o.oldObj && SO.isBehavior(o.oldObj)) o.oldObj.data.unregister();
                 if (o.obj && SO.isBehavior(o.obj)) o.obj.data.register();

+ 41 - 12
src/mol-plugin/state.ts

@@ -10,20 +10,37 @@ import { Camera } from 'mol-canvas3d/camera';
 import { PluginBehavior } from './behavior';
 import { CameraSnapshotManager } from './state/camera';
 import { PluginStateSnapshotManager } from './state/snapshots';
-
+import { RxEventHelper } from 'mol-util/rx-event-helper';
 export { PluginState }
 
 class PluginState {
-    readonly data: State;
-    readonly behavior: State;
+    private ev = RxEventHelper.create();
+
+    readonly dataState: State;
+    readonly behaviorState: State;
     readonly cameraSnapshots = new CameraSnapshotManager();
 
     readonly snapshots = new PluginStateSnapshotManager();
 
+    readonly behavior = {
+        kind: this.ev.behavior<PluginState.Kind>('data'),
+        currentObject: this.ev.behavior<State.ObjectEvent>({} as any)
+    }
+
+    setKind(kind: PluginState.Kind) {
+        const current = this.behavior.kind.value;
+        if (kind !== current) {
+            this.behavior.kind.next(kind);
+            this.behavior.currentObject.next(kind === 'data'
+                ? this.dataState.behaviors.currentObject.value
+                : this.behaviorState.behaviors.currentObject.value)
+        }
+    }
+
     getSnapshot(): PluginState.Snapshot {
         return {
-            data: this.data.getSnapshot(),
-            behaviour: this.behavior.getSnapshot(),
+            data: this.dataState.getSnapshot(),
+            behaviours: this.behaviorState.getSnapshot(),
             cameraSnapshots: this.cameraSnapshots.getStateSnapshot(),
             canvas3d: {
                 camera: this.plugin.canvas3d.camera.getSnapshot()
@@ -32,29 +49,41 @@ class PluginState {
     }
 
     async setSnapshot(snapshot: PluginState.Snapshot) {
-        await this.plugin.runTask(this.behavior.setSnapshot(snapshot.behaviour));
-        await this.plugin.runTask(this.data.setSnapshot(snapshot.data));
+        await this.plugin.runTask(this.behaviorState.setSnapshot(snapshot.behaviours));
+        await this.plugin.runTask(this.dataState.setSnapshot(snapshot.data));
         this.cameraSnapshots.setStateSnapshot(snapshot.cameraSnapshots);
         this.plugin.canvas3d.camera.setState(snapshot.canvas3d.camera);
         this.plugin.canvas3d.requestDraw(true);
     }
 
     dispose() {
-        this.data.dispose();
-        this.behavior.dispose();
+        this.ev.dispose();
+        this.dataState.dispose();
+        this.behaviorState.dispose();
         this.cameraSnapshots.dispose();
     }
 
     constructor(private plugin: import('./context').PluginContext) {
-        this.data = State.create(new SO.Root({ }), { globalContext: plugin });
-        this.behavior = State.create(new PluginBehavior.Root({ }), { globalContext: plugin });
+        this.dataState = State.create(new SO.Root({ }), { globalContext: plugin });
+        this.behaviorState = State.create(new PluginBehavior.Root({ }), { globalContext: plugin });
+
+        this.dataState.behaviors.currentObject.subscribe(o => {
+            if (this.behavior.kind.value === 'data') this.behavior.currentObject.next(o);
+        });
+        this.behaviorState.behaviors.currentObject.subscribe(o => {
+            if (this.behavior.kind.value === 'behavior') this.behavior.currentObject.next(o);
+        });
+
+        this.behavior.currentObject.next(this.dataState.behaviors.currentObject.value);
     }
 }
 
 namespace PluginState {
+    export type Kind = 'data' | 'behavior'
+
     export interface Snapshot {
         data: State.Snapshot,
-        behaviour: State.Snapshot,
+        behaviours: State.Snapshot,
         cameraSnapshots: CameraSnapshotManager.StateSnapshot,
         canvas3d: {
             camera: Camera.Snapshot

+ 9 - 3
src/mol-plugin/ui/action.tsx

@@ -118,17 +118,23 @@ class ActionContol extends PluginComponent<ActionContol.Props, { params: any, in
 
     state = this.defaultState()
 
+    nothingToUpdate() {
+        return <div>Nothing to update</div>;
+    }
+
     render() {
-        console.log('render', this.props.nodeRef, this.action.id);
         const cell = this.cell;
-        if (cell.status !== 'ok' || (this.isUpdate && cell.transform.ref === Transform.RootRef)) return null;
+        if (cell.status !== 'ok' || (this.isUpdate && cell.transform.ref === Transform.RootRef)) return this.nothingToUpdate();
+
+        const paramDefs = this.getParamDefinitions();
+        if (this.isUpdate && Object.keys(paramDefs).length === 0) return this.nothingToUpdate();
 
         const action = this.action;
 
         return <div>
             <div style={{ borderBottom: '1px solid #999', marginBottom: '5px' }}><h3>{(action.definition.display && action.definition.display.name) || action.id}</h3></div>
 
-            <ParameterControls params={this.getParamDefinitions()} values={this.state.params} changes={this.changes} onEnter={this.onEnter} isEnabled={!this.state.busy} />
+            <ParameterControls params={paramDefs} values={this.state.params} changes={this.changes} onEnter={this.onEnter} isEnabled={!this.state.busy} />
 
             <div style={{ textAlign: 'right' }}>
                 <span style={{ color: 'red' }}>{this.state.error}</span>

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

@@ -22,15 +22,15 @@ export class TrajectoryControls extends PluginComponent {
         return <div>
             <b>Trajectory: </b>
             <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
-                state: this.plugin.state.data,
+                state: this.plugin.state.dataState,
                 action: UpdateTrajectory.create({ action: 'advance', by: -1 })
             })}>&lt;&lt;</button>
             <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
-                state: this.plugin.state.data,
+                state: this.plugin.state.dataState,
                 action: UpdateTrajectory.create({ action: 'reset' })
             })}>Reset</button>
             <button onClick={() => PluginCommands.State.ApplyAction.dispatch(this.plugin, {
-                state: this.plugin.state.data,
+                state: this.plugin.state.dataState,
                 action: UpdateTrajectory.create({ action: 'advance', by: +1 })
             })}>&gt;&gt;</button><br />
         </div>

+ 34 - 16
src/mol-plugin/ui/plugin.tsx

@@ -10,7 +10,6 @@ import { StateTree } from './state-tree';
 import { Viewport, ViewportControls } from './viewport';
 import { Controls, TrajectoryControls } from './controls';
 import { PluginComponent, PluginReactContext } from './base';
-import { merge } from 'rxjs';
 import { CameraSnapshots } from './camera';
 import { StateSnapshots } from './state';
 import { List } from 'immutable';
@@ -18,15 +17,14 @@ import { LogEntry } from 'mol-util/log-entry';
 import { formatTime } from 'mol-util';
 import { BackgroundTaskProgress } from './task';
 import { ActionContol } from './action';
+import { PluginState } from 'mol-plugin/state';
 
 export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
     render() {
         return <PluginReactContext.Provider value={this.props.plugin}>
             <div style={{ position: 'absolute', width: '100%', height: '100%', fontFamily: 'monospace' }}>
                 <div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll', padding: '10px' }}>
-                    <StateTree state={this.props.plugin.state.data} />
-                    <h3>Behaviors</h3>
-                    <StateTree state={this.props.plugin.state.behavior} />
+                    <State />
                 </div>
                 <div style={{ position: 'absolute', left: '350px', right: '300px', top: '0', bottom: '100px' }}>
                     <Viewport />
@@ -55,6 +53,26 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
     }
 }
 
+export class State extends PluginComponent {
+    componentDidMount() {
+        this.subscribe(this.plugin.state.behavior.kind, () => this.forceUpdate());
+    }
+
+    set(kind: PluginState.Kind) {
+        // TODO: do command for this?
+        this.plugin.state.setKind(kind);
+    }
+
+    render() {
+        const kind = this.plugin.state.behavior.kind.value;
+        return <>
+            <button onClick={() => this.set('data')} style={{ fontWeight: kind === 'data' ? 'bold' : 'normal'}}>Data</button>
+            <button onClick={() => this.set('behavior')} style={{ fontWeight: kind === 'behavior' ? 'bold' : 'normal'}}>Behavior</button>
+            <StateTree state={kind === 'data' ? this.plugin.state.dataState : this.plugin.state.behaviorState} />
+        </>
+    }
+}
+
 export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
     private wrapper = React.createRef<HTMLDivElement>();
 
@@ -85,33 +103,33 @@ export class Log extends PluginComponent<{}, { entries: List<LogEntry> }> {
 }
 
 export class CurrentObject extends PluginComponent {
+    get current() {
+        return this.plugin.state.behavior.currentObject.value;
+    }
+
     componentDidMount() {
-        // let current: State.ObjectEvent | undefined = void 0;
-        this.subscribe(merge(this.plugin.behaviors.state.data.currentObject, this.plugin.behaviors.state.behavior.currentObject), o => {
-            // current = o;
-            this.forceUpdate()
+        this.subscribe(this.plugin.state.behavior.currentObject, o => {
+            this.forceUpdate();
         });
 
-        this.subscribe(this.plugin.events.state.data.object.updated, ({ ref, state }) => {
-            console.log('curr event', +new Date);
-            const current = this.plugin.behaviors.state.data.currentObject.value;
+        this.subscribe(this.plugin.events.state.object.updated, ({ ref, state }) => {
+            const current = this.current;
             if (current.ref !== ref || current.state !== state) return;
-            console.log('curr event pass', +new Date);
             this.forceUpdate();
         });
     }
 
     render() {
-        console.log('curr', +new Date);
-
-        const current = this.plugin.behaviors.state.data.currentObject.value;
+        const current = this.current;
 
         const ref = current.ref;
         // const n = this.props.plugin.state.data.tree.nodes.get(ref)!;
-        const obj = this.plugin.state.data.cells.get(ref)!;
+        const obj = current.state.cells.get(ref)!;
 
         const type = obj && obj.obj ? obj.obj.type : void 0;
 
+        console.log(obj);
+
         const actions = type
             ? current.state.actions.fromType(type)
             : []

+ 1 - 2
src/mol-plugin/ui/state-tree.tsx

@@ -9,7 +9,6 @@ import { PluginStateObject } from 'mol-plugin/state/objects';
 import { State } from 'mol-state'
 import { PluginCommands } from 'mol-plugin/command';
 import { PluginComponent } from './base';
-import { merge } from 'rxjs';
 
 export class StateTree extends PluginComponent<{ state: State }, { }> {
     componentDidMount() {
@@ -28,7 +27,7 @@ export class StateTree extends PluginComponent<{ state: State }, { }> {
 
 export class StateTreeNode extends PluginComponent<{ nodeRef: string, state: State }, { }> {
     componentDidMount() {
-        this.subscribe(merge(this.plugin.events.state.data.object.cellState, this.plugin.events.state.behavior.object.cellState), o => {
+        this.subscribe(this.plugin.events.state.object.cellState, o => {
             if (o.ref === this.props.nodeRef && o.state === this.props.state) this.forceUpdate();
         });
     }