From d1d02a18f93f11bc64e2e24af16daf41af412844 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Thu, 8 Nov 2018 17:28:43 +0100
Subject: [PATCH] mol-plugin: wip

---
 src/mol-plugin/behavior.ts                    |  4 +--
 src/mol-plugin/behavior/behavior.ts           | 16 +++++----
 .../behavior/{ => built-in}/camera.ts         |  0
 .../behavior/{ => built-in}/data.ts           |  2 +-
 .../behavior/{ => built-in}/representation.ts | 16 ++++-----
 src/mol-plugin/context.ts                     | 23 ++++++------
 src/mol-plugin/state.ts                       | 13 ++-----
 src/mol-plugin/state/base.ts                  | 20 ++++++++++-
 src/mol-plugin/state/objects.ts               | 21 ++++++-----
 src/mol-plugin/state/transforms/data.ts       |  4 +--
 src/mol-plugin/state/transforms/model.ts      | 30 ++++++++--------
 src/mol-plugin/state/transforms/visuals.ts    |  8 ++---
 src/mol-plugin/ui/plugin.tsx                  |  7 ++--
 src/mol-plugin/ui/state-tree.tsx              | 35 ++++++++++---------
 14 files changed, 111 insertions(+), 88 deletions(-)
 rename src/mol-plugin/behavior/{ => built-in}/camera.ts (100%)
 rename src/mol-plugin/behavior/{ => built-in}/data.ts (96%)
 rename src/mol-plugin/behavior/{ => built-in}/representation.ts (73%)

diff --git a/src/mol-plugin/behavior.ts b/src/mol-plugin/behavior.ts
index db04070ec..9758f6629 100644
--- a/src/mol-plugin/behavior.ts
+++ b/src/mol-plugin/behavior.ts
@@ -5,8 +5,8 @@
  */
 
 export * from './behavior/behavior'
-import * as Data from './behavior/data'
-import * as Representation from './behavior/representation'
+import * as Data from './behavior/built-in/data'
+import * as Representation from './behavior/built-in/representation'
 
 export const PluginBehaviors = {
     Data,
diff --git a/src/mol-plugin/behavior/behavior.ts b/src/mol-plugin/behavior/behavior.ts
index ac667eee7..1064ac2ad 100644
--- a/src/mol-plugin/behavior/behavior.ts
+++ b/src/mol-plugin/behavior/behavior.ts
@@ -4,8 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { PluginStateTransform } from '../state/base';
-import { PluginStateObjects as SO } from '../state/objects';
+import { PluginStateTransform, PluginStateObject } from '../state/base';
 import { Transformer } from 'mol-state';
 import { Task } from 'mol-task';
 import { PluginContext } from 'mol-plugin/context';
@@ -23,6 +22,9 @@ interface PluginBehavior<P = unknown> {
 }
 
 namespace PluginBehavior {
+    export class Root extends PluginStateObject.Create({ name: 'Root', shortName: 'R', typeClass: 'Root', description: 'Where everything begins.' }) { }
+    export class Behavior extends PluginStateObject.CreateBehavior<PluginBehavior>({ name: 'Behavior', shortName: 'B', description: 'Modifies plugin functionality.' }) { }
+
     export interface Ctor<P = undefined> { new(ctx: PluginContext, params?: P): PluginBehavior<P> }
 
     export interface CreateParams<P> {
@@ -30,19 +32,19 @@ namespace PluginBehavior {
         ctor: Ctor<P>,
         label?: (params: P) => { label: string, description?: string },
         display: { name: string, description?: string },
-        params?: Transformer.Definition<SO.BehaviorRoot, SO.Behavior, P>['params']
+        params?: Transformer.Definition<Root, Behavior, P>['params']
     }
 
     export function create<P>(params: CreateParams<P>) {
-        return PluginStateTransform.Create<SO.BehaviorRoot, SO.Behavior, P>({
+        return PluginStateTransform.Create<Root, Behavior, P>({
             name: params.name,
             display: params.display,
-            from: [SO.BehaviorRoot],
-            to: [SO.Behavior],
+            from: [Root],
+            to: [Behavior],
             params: params.params,
             apply({ params: p }, ctx: PluginContext) {
                 const label = params.label ? params.label(p) : { label: params.display.name, description: params.display.description };
-                return new SO.Behavior(label, new params.ctor(ctx, p));
+                return new Behavior(label, new params.ctor(ctx, p));
             },
             update({ b, newParams }) {
                 return Task.create('Update Behavior', async () => {
diff --git a/src/mol-plugin/behavior/camera.ts b/src/mol-plugin/behavior/built-in/camera.ts
similarity index 100%
rename from src/mol-plugin/behavior/camera.ts
rename to src/mol-plugin/behavior/built-in/camera.ts
diff --git a/src/mol-plugin/behavior/data.ts b/src/mol-plugin/behavior/built-in/data.ts
similarity index 96%
rename from src/mol-plugin/behavior/data.ts
rename to src/mol-plugin/behavior/built-in/data.ts
index 730e3c217..d9e6a6b74 100644
--- a/src/mol-plugin/behavior/data.ts
+++ b/src/mol-plugin/behavior/built-in/data.ts
@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { PluginBehavior } from './behavior';
+import { PluginBehavior } from '../behavior';
 import { PluginCommands } from 'mol-plugin/command';
 import { StateTree } from 'mol-state';
 
diff --git a/src/mol-plugin/behavior/representation.ts b/src/mol-plugin/behavior/built-in/representation.ts
similarity index 73%
rename from src/mol-plugin/behavior/representation.ts
rename to src/mol-plugin/behavior/built-in/representation.ts
index f2eac5aa9..5246082ca 100644
--- a/src/mol-plugin/behavior/representation.ts
+++ b/src/mol-plugin/behavior/built-in/representation.ts
@@ -4,38 +4,36 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { PluginBehavior } from './behavior';
-import { PluginStateObjects as SO } from '../state/objects';
+import { PluginBehavior } from '../behavior';
+import { PluginStateObject as SO } from '../../state/base';
 
 class _AddRepresentationToCanvas extends PluginBehavior.Handler {
     register(): void {
         this.subscribeObservable(this.ctx.events.state.data.object.created, o => {
-            if (!SO.StructureRepresentation3D.is(o.obj)) return;
+            if (!SO.isRepresentation3D(o.obj)) return;
             this.ctx.canvas3d.add(o.obj.data);
             this.ctx.canvas3d.requestDraw(true);
         });
         this.subscribeObservable(this.ctx.events.state.data.object.updated, o => {
             const oo = o.obj;
-            if (!SO.StructureRepresentation3D.is(oo)) return;
+            if (!SO.isRepresentation3D(oo)) return;
             this.ctx.canvas3d.add(oo.data);
             this.ctx.canvas3d.requestDraw(true);
         });
         this.subscribeObservable(this.ctx.events.state.data.object.removed, o => {
             const oo = o.obj;
-            console.log('removed', o.ref, oo && oo.type);
-            if (!SO.StructureRepresentation3D.is(oo)) return;
+            if (!SO.isRepresentation3D(oo)) return;
             this.ctx.canvas3d.remove(oo.data);
-            console.log('removed from canvas', o.ref);
             this.ctx.canvas3d.requestDraw(true);
             oo.data.destroy();
         });
         this.subscribeObservable(this.ctx.events.state.data.object.replaced, o => {
-            if (o.oldObj && SO.StructureRepresentation3D.is(o.oldObj)) {
+            if (o.oldObj && SO.isRepresentation3D(o.oldObj)) {
                 this.ctx.canvas3d.remove(o.oldObj.data);
                 this.ctx.canvas3d.requestDraw(true);
                 o.oldObj.data.destroy();
             }
-            if (o.newObj && SO.StructureRepresentation3D.is(o.newObj)) {
+            if (o.newObj && SO.isRepresentation3D(o.newObj)) {
                 this.ctx.canvas3d.add(o.newObj.data);
                 this.ctx.canvas3d.requestDraw(true);
             }
diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index b519cc8d7..d32cb9bf9 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -7,6 +7,7 @@
 import { StateTree, StateSelection, Transformer, Transform } from 'mol-state';
 import { Canvas3D } from 'mol-canvas3d/canvas3d';
 import { StateTransforms } from './state/transforms';
+import { PluginStateObject as PSO } from './state/base';
 import { PluginStateObjects as SO } from './state/objects';
 import { RxEventHelper } from 'mol-util/rx-event-helper';
 import { PluginState } from './state';
@@ -77,10 +78,10 @@ export class PluginContext {
 
     async _test_initBehaviours() {
         const tree = StateTree.build(this.state.behavior.tree)
-            .toRoot().apply(PluginBehaviors.Data.SetCurrentObject)
-            .and().toRoot().apply(PluginBehaviors.Data.Update)
-            .and().toRoot().apply(PluginBehaviors.Data.RemoveObject)
-            .and().toRoot().apply(PluginBehaviors.Representation.AddRepresentationToCanvas)
+            .toRoot().apply(PluginBehaviors.Data.SetCurrentObject, { ref: PluginBehaviors.Data.SetCurrentObject.id })
+            .and().toRoot().apply(PluginBehaviors.Data.Update, { ref: PluginBehaviors.Data.Update.id })
+            .and().toRoot().apply(PluginBehaviors.Data.RemoveObject, { ref: PluginBehaviors.Data.RemoveObject.id })
+            .and().toRoot().apply(PluginBehaviors.Representation.AddRepresentationToCanvas, { ref: PluginBehaviors.Representation.AddRepresentationToCanvas.id })
             .getTree();
 
         await this.state.updateBehaviour(tree);
@@ -121,32 +122,32 @@ export class PluginContext {
     private initEvents() {
         merge(this.events.state.data.object.created, this.events.state.behavior.object.created).subscribe(o => {
             console.log('creating', o.obj.type);
-            if (!SO.Behavior.is(o.obj)) return;
+            if (!PSO.isBehavior(o.obj)) return;
             o.obj.data.register();
         });
 
         merge(this.events.state.data.object.removed, this.events.state.behavior.object.removed).subscribe(o => {
-            if (!SO.Behavior.is(o.obj)) return;
+            if (!PSO.isBehavior(o.obj)) return;
             o.obj.data.unregister();
         });
 
         merge(this.events.state.data.object.replaced, this.events.state.behavior.object.replaced).subscribe(o => {
-            if (o.oldObj && SO.Behavior.is(o.oldObj)) o.oldObj.data.unregister();
-            if (o.newObj && SO.Behavior.is(o.newObj)) o.newObj.data.register();
+            if (o.oldObj && PSO.isBehavior(o.oldObj)) o.oldObj.data.unregister();
+            if (o.newObj && PSO.isBehavior(o.newObj)) o.newObj.data.register();
         });
     }
 
     _test_centerView() {
-        const sel = StateSelection.select(StateSelection.root().subtree().ofType(SO.Structure.type), this.state.data);
+        const sel = StateSelection.select(StateSelection.root().subtree().ofType(SO.Molecule.Structure.type), this.state.data);
         if (!sel.length) return;
 
-        const center = (sel[0].obj! as SO.Structure).data.boundary.sphere.center;
+        const center = (sel[0].obj! as SO.Molecule.Structure).data.boundary.sphere.center;
         this.canvas3d.camera.setState({ target: center });
         this.canvas3d.requestDraw(true);
     }
 
     _test_nextModel() {
-        const models = StateSelection.select('models', this.state.data)[0].obj as SO.Models;
+        const models = StateSelection.select('models', this.state.data)[0].obj as SO.Molecule.Models;
         const idx = (this.state.data.tree.getValue('structure')!.params as Transformer.Params<typeof StateTransforms.Model.CreateStructureFromModel>).modelIndex;
         const newTree = StateTree.updateParams(this.state.data.tree, 'structure', { modelIndex: (idx + 1) % models.data.length });
         return this.state.updateData(newTree);
diff --git a/src/mol-plugin/state.ts b/src/mol-plugin/state.ts
index 47981d071..80cc88b03 100644
--- a/src/mol-plugin/state.ts
+++ b/src/mol-plugin/state.ts
@@ -7,6 +7,7 @@
 import { State, StateTree } from 'mol-state';
 import { PluginStateObjects as SO } from './state/objects';
 import { Camera } from 'mol-canvas3d/camera';
+import { PluginBehavior } from './behavior';
 
 export { PluginState }
 
@@ -28,15 +29,7 @@ class PluginState {
         await this.behavior.setSnapshot(snapshot.behaviour);
         await this.data.setSnapshot(snapshot.data);
         this.plugin.canvas3d.camera.setState(snapshot.canvas3d.camera);
-
-        // TODO: handle camera
-        // console.log({ old: { ...this.plugin.canvas3d.camera  }, new: snapshot.canvas3d.camera });
-        // CombinedCamera.copy(snapshot.canvas3d.camera, this.plugin.canvas3d.camera);
-        // CombinedCamera.update(this.plugin.canvas3d.camera);
-        // this.plugin.canvas3d.center
-        // console.log({ copied: { ...this.plugin.canvas3d.camera  } });
         this.plugin.canvas3d.requestDraw(true);
-        // console.log('updated camera');
     }
 
     updateData(tree: StateTree) {
@@ -52,8 +45,8 @@ class PluginState {
     }
 
     constructor(private plugin: import('./context').PluginContext) {
-        this.data = State.create(new SO.DataRoot({ label: 'Root' }, { }), { globalContext: plugin });
-        this.behavior = State.create(new SO.BehaviorRoot({ label: 'Root' }, { }), { globalContext: plugin });
+        this.data = State.create(new SO.Root({ label: 'Root' }, { }), { globalContext: plugin });
+        this.behavior = State.create(new PluginBehavior.Root({ label: 'Root' }, { }), { globalContext: plugin });
     }
 }
 
diff --git a/src/mol-plugin/state/base.ts b/src/mol-plugin/state/base.ts
index 5b7db3fc0..05e2f6278 100644
--- a/src/mol-plugin/state/base.ts
+++ b/src/mol-plugin/state/base.ts
@@ -5,15 +5,33 @@
  */
 
 import { StateObject, Transformer } from 'mol-state';
+import { Representation } from 'mol-repr';
+import { PluginBehavior } from 'mol-plugin/behavior/behavior';
 
 export type TypeClass = 'root' | 'data' | 'prop'
 
 export namespace PluginStateObject {
-    export type TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Representation' | 'Behavior'
+    export type TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Representation3D' | 'Behavior'
     export interface TypeInfo { name: string, shortName: string, description: string, typeClass: TypeClass }
     export interface Props { label: string, description?: string }
 
     export const Create = StateObject.factory<TypeInfo, Props>();
+
+    export function isRepresentation3D(o?: StateObject): o is StateObject<Props, Representation.Any> {
+        return !!o && (o.type.info as TypeInfo).typeClass === 'Representation3D';
+    }
+
+    export function isBehavior(o?: StateObject): o is StateObject<Props, PluginBehavior> {
+        return !!o && (o.type.info as TypeInfo).typeClass === 'Behavior';
+    }
+
+    export function CreateRepresentation3D<T extends Representation.Any>(type: { name: string, shortName: string, description: string }) {
+        return Create<T>({ ...type, typeClass: 'Representation3D' })
+    }
+
+    export function CreateBehavior<T extends PluginBehavior>(type: { name: string, shortName: string, description: string }) {
+        return Create<T>({ ...type, typeClass: 'Behavior' })
+    }
 }
 
 export namespace PluginStateTransform {
diff --git a/src/mol-plugin/state/objects.ts b/src/mol-plugin/state/objects.ts
index cb2cf337c..17784d5a7 100644
--- a/src/mol-plugin/state/objects.ts
+++ b/src/mol-plugin/state/objects.ts
@@ -8,17 +8,16 @@ import { PluginStateObject } from './base';
 import { CifFile } from 'mol-io/reader/cif';
 import { Model as _Model, Structure as _Structure } from 'mol-model/structure'
 import { StructureRepresentation } from 'mol-repr/structure/index';
+import { VolumeRepresentation } from 'mol-repr/volume';
+import { VolumeData } from 'mol-model/volume';
 
-const _create = PluginStateObject.Create
+const _create = PluginStateObject.Create, _createRepr3D = PluginStateObject.CreateRepresentation3D
 
 namespace PluginStateObjects {
-    export class DataRoot extends _create({ name: 'Root', shortName: 'R', typeClass: 'Root', description: 'Where everything begins.' }) { }
-    export class BehaviorRoot extends _create({ name: 'Root', shortName: 'R', typeClass: 'Root', description: 'Where everything begins.' }) { }
+    export class Root extends _create({ name: 'Root', shortName: 'R', typeClass: 'Root', description: 'Where everything begins.' }) { }
 
     export class Group extends _create({ name: 'Group', shortName: 'G', typeClass: 'Group', description: 'A group on entities.' }) { }
 
-    export class Behavior extends _create<import('../behavior').PluginBehavior>({ name: 'Behavior', shortName: 'B', typeClass: 'Behavior', description: 'Modifies plugin functionality.' }) { }
-
     export namespace Data {
         export class String extends _create<string>({ name: 'String Data', typeClass: 'Data', shortName: 'S_D', description: 'A string.' }) { }
         export class Binary extends _create<Uint8Array>({ name: 'Binary Data', typeClass: 'Data', shortName: 'B_D', description: 'A binary blob.' }) { }
@@ -31,11 +30,17 @@ namespace PluginStateObjects {
         // }>({ name: 'Data', typeClass: 'Data', shortName: 'MD', description: 'Multiple Keyed Data.' }) { }
     }
 
-    export class Models extends _create<ReadonlyArray<_Model>>({ name: 'Molecule Model', typeClass: 'Object', shortName: 'M_M', description: 'A model of a molecule.' }) { }
-    export class Structure extends _create<_Structure>({ name: 'Molecule Structure', typeClass: 'Object', shortName: 'M_S', description: 'A structure of a molecule.' }) { }
+    export namespace Molecule {
+        export class Models extends _create<ReadonlyArray<_Model>>({ name: 'Molecule Model', typeClass: 'Object', shortName: 'M_M', description: 'A model of a molecule.' }) { }
+        export class Structure extends _create<_Structure>({ name: 'Molecule Structure', typeClass: 'Object', shortName: 'M_S', description: 'A structure of a molecule.' }) { }
+        export class Representation3D extends _createRepr3D<StructureRepresentation<any>>({ name: 'Molecule Structure 3D Representation', shortName: 'S_R', description: 'A 3D representation of a molecular structure.' }) { }
+    }
 
+    export namespace Volume {
+        export class Data extends _create<VolumeData>({ name: 'Volume Data', typeClass: 'Object', shortName: 'V_D', description: 'Volume Data.' }) { }
+        export class Representation3D extends _createRepr3D<VolumeRepresentation<any>>({ name: 'Volume 3D Representation', shortName: 'V_R', description: 'A 3D representation of volumetric data.' }) { }
+    }
 
-    export class StructureRepresentation3D extends _create<StructureRepresentation<any>>({ name: 'Molecule Structure Representation', typeClass: 'Representation', shortName: 'S_R', description: 'A representation of a molecular structure.' }) { }
 }
 
 export { PluginStateObjects }
\ No newline at end of file
diff --git a/src/mol-plugin/state/transforms/data.ts b/src/mol-plugin/state/transforms/data.ts
index 67899a9c6..52344c2d9 100644
--- a/src/mol-plugin/state/transforms/data.ts
+++ b/src/mol-plugin/state/transforms/data.ts
@@ -13,13 +13,13 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 
 export { Download }
 namespace Download { export interface Params { url: string, isBinary?: boolean, label?: string } }
-const Download = PluginStateTransform.Create<SO.DataRoot, SO.Data.String | SO.Data.Binary, Download.Params>({
+const Download = PluginStateTransform.Create<SO.Root, SO.Data.String | SO.Data.Binary, Download.Params>({
     name: 'download',
     display: {
         name: 'Download',
         description: 'Download string or binary data from the specified URL'
     },
-    from: [SO.DataRoot],
+    from: [SO.Root],
     to: [SO.Data.String, SO.Data.Binary],
     params: {
         default: () => ({
diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts
index 1ebe478e2..47cd012e0 100644
--- a/src/mol-plugin/state/transforms/model.ts
+++ b/src/mol-plugin/state/transforms/model.ts
@@ -15,14 +15,14 @@ import { Mat4 } from 'mol-math/linear-algebra';
 
 export { ParseModelsFromMmCif }
 namespace ParseModelsFromMmCif { export interface Params { blockHeader?: string } }
-const ParseModelsFromMmCif = PluginStateTransform.Create<SO.Data.Cif, SO.Models, ParseModelsFromMmCif.Params>({
+const ParseModelsFromMmCif = PluginStateTransform.Create<SO.Data.Cif, SO.Molecule.Models, ParseModelsFromMmCif.Params>({
     name: 'parse-models-from-mmcif',
     display: {
         name: 'Models from mmCIF',
         description: 'Identify and create all separate models in the specified CIF data block'
     },
     from: [SO.Data.Cif],
-    to: [SO.Models],
+    to: [SO.Molecule.Models],
     params: {
         default: a => ({ blockHeader: a.data.blocks[0].header }),
         controls(a) {
@@ -41,21 +41,21 @@ const ParseModelsFromMmCif = PluginStateTransform.Create<SO.Data.Cif, SO.Models,
             const models = await Model.create(Format.mmCIF(block)).runInContext(ctx);
             if (models.length === 0) throw new Error('No models found.');
             const label = models.length === 1 ? `${models[0].label}` : `${models[0].label} (${models.length} models)`;
-            return new SO.Models({ label }, models);
+            return new SO.Molecule.Models({ label }, models);
         });
     }
 });
 
 export { CreateStructureFromModel }
 namespace CreateStructureFromModel { export interface Params { modelIndex: number, transform3d?: Mat4 } }
-const CreateStructureFromModel = PluginStateTransform.Create<SO.Models, SO.Structure, CreateStructureFromModel.Params>({
+const CreateStructureFromModel = PluginStateTransform.Create<SO.Molecule.Models, SO.Molecule.Structure, CreateStructureFromModel.Params>({
     name: 'create-structure-from-model',
     display: {
         name: 'Structure from Model',
         description: 'Create a molecular structure from the specified model.'
     },
-    from: [SO.Models],
-    to: [SO.Structure],
+    from: [SO.Molecule.Models],
+    to: [SO.Molecule.Structure],
     params: {
         default: () => ({ modelIndex: 0 }),
         controls: a => ({ modelIndex: PD.Range('Model Index', 'Model Index', 0, 0, Math.max(0, a.data.length - 1), 1) })
@@ -65,21 +65,21 @@ const CreateStructureFromModel = PluginStateTransform.Create<SO.Models, SO.Struc
         if (params.modelIndex < 0 || params.modelIndex >= a.data.length) throw new Error(`Invalid modelIndex ${params.modelIndex}`);
         let s = Structure.ofModel(a.data[params.modelIndex]);
         if (params.transform3d) s = Structure.transform(s, params.transform3d);
-        return new SO.Structure({ label: `Model ${s.models[0].modelNum}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
+        return new SO.Molecule.Structure({ label: `Model ${s.models[0].modelNum}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
     }
 });
 
 
 export { CreateStructureAssembly }
 namespace CreateStructureAssembly { export interface Params { /** if not specified, use the 1st */ id?: string } }
-const CreateStructureAssembly = PluginStateTransform.Create<SO.Structure, SO.Structure, CreateStructureAssembly.Params>({
+const CreateStructureAssembly = PluginStateTransform.Create<SO.Molecule.Structure, SO.Molecule.Structure, CreateStructureAssembly.Params>({
     name: 'create-structure-assembly',
     display: {
         name: 'Structure Assembly',
         description: 'Create a molecular structure assembly.'
     },
-    from: [SO.Structure],
-    to: [SO.Structure],
+    from: [SO.Molecule.Structure],
+    to: [SO.Molecule.Structure],
     params: {
         default: () => ({ id: void 0 }),
         controls(a) {
@@ -98,26 +98,26 @@ const CreateStructureAssembly = PluginStateTransform.Create<SO.Structure, SO.Str
             if (!asm) throw new Error(`Assembly '${id}' not found`);
 
             const s = await StructureSymmetry.buildAssembly(a.data, id!).runInContext(ctx);
-            return new SO.Structure({ label: `Assembly ${id}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
+            return new SO.Molecule.Structure({ label: `Assembly ${id}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
         })
     }
 });
 
 export { CreateStructureSelection }
 namespace CreateStructureSelection { export interface Params { query: Expression, label?: string } }
-const CreateStructureSelection = PluginStateTransform.Create<SO.Structure, SO.Structure, CreateStructureSelection.Params>({
+const CreateStructureSelection = PluginStateTransform.Create<SO.Molecule.Structure, SO.Molecule.Structure, CreateStructureSelection.Params>({
     name: 'create-structure-selection',
     display: {
         name: 'Structure Selection',
         description: 'Create a molecular structure from the specified model.'
     },
-    from: [SO.Structure],
-    to: [SO.Structure],
+    from: [SO.Molecule.Structure],
+    to: [SO.Molecule.Structure],
     apply({ a, params }) {
         // TODO: use cache, add "update"
         const compiled = compile<StructureSelection>(params.query);
         const result = compiled(new QueryContext(a.data));
         const s = StructureSelection.unionStructure(result);
-        return new SO.Structure({ label: `${params.label || 'Selection'}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
+        return new SO.Molecule.Structure({ label: `${params.label || 'Selection'}`, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` }, s);
     }
 });
diff --git a/src/mol-plugin/state/transforms/visuals.ts b/src/mol-plugin/state/transforms/visuals.ts
index 3dbb0a621..d85aa7faa 100644
--- a/src/mol-plugin/state/transforms/visuals.ts
+++ b/src/mol-plugin/state/transforms/visuals.ts
@@ -14,16 +14,16 @@ import { PluginContext } from 'mol-plugin/context';
 
 export { CreateStructureRepresentation }
 namespace CreateStructureRepresentation { export interface Params { } }
-const CreateStructureRepresentation = PluginStateTransform.Create<SO.Structure, SO.StructureRepresentation3D, CreateStructureRepresentation.Params>({
+const CreateStructureRepresentation = PluginStateTransform.Create<SO.Molecule.Structure, SO.Molecule.Representation3D, CreateStructureRepresentation.Params>({
     name: 'create-structure-representation',
     display: { name: 'Create 3D Representation' },
-    from: [SO.Structure],
-    to: [SO.StructureRepresentation3D],
+    from: [SO.Molecule.Structure],
+    to: [SO.Molecule.Representation3D],
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Representation', async ctx => {
             const repr = BallAndStickRepresentation(); // CartoonRepresentation();
             await repr.createOrUpdate({ webgl: plugin.canvas3d.webgl }, DefaultBallAndStickProps, a.data).runInContext(ctx);
-            return new SO.StructureRepresentation3D({ label: 'Visual Repr.' }, repr);
+            return new SO.Molecule.Representation3D({ label: 'Visual Repr.' }, repr);
         });
     },
     update({ a, b }, plugin: PluginContext) {
diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx
index 9e1f84a23..111e510b9 100644
--- a/src/mol-plugin/ui/plugin.tsx
+++ b/src/mol-plugin/ui/plugin.tsx
@@ -11,15 +11,18 @@ import { Viewport } from './viewport';
 import { Controls, _test_CreateTransform } from './controls';
 import { Transformer } from 'mol-state';
 
-// TODO: base object with subscribe helpers
+// TODO: base object with subscribe helpers, separate behavior list etc
 
 export class Plugin extends React.Component<{ plugin: PluginContext }, { }> {
     render() {
         return <div style={{ position: 'absolute', width: '100%', height: '100%', fontFamily: 'monospace' }}>
             <div style={{ position: 'absolute', width: '350px', height: '100%', overflowY: 'scroll' }}>
-                <StateTree plugin={this.props.plugin} />
+                <h3>Data</h3>
+                <StateTree plugin={this.props.plugin} state={this.props.plugin.state.data} />
                 <hr />
                 <_test_CurrentObject plugin={this.props.plugin} />
+                <h3>Behavior</h3>
+                <StateTree plugin={this.props.plugin} state={this.props.plugin.state.behavior} />
             </div>
             <div style={{ position: 'absolute', left: '350px', right: '250px', height: '100%' }}>
                 <Viewport plugin={this.props.plugin} />
diff --git a/src/mol-plugin/ui/state-tree.tsx b/src/mol-plugin/ui/state-tree.tsx
index f7dd468e2..01c950915 100644
--- a/src/mol-plugin/ui/state-tree.tsx
+++ b/src/mol-plugin/ui/state-tree.tsx
@@ -7,46 +7,49 @@
 import * as React from 'react';
 import { PluginContext } from '../context';
 import { PluginStateObject } from 'mol-plugin/state/base';
-import { StateObject } from 'mol-state'
+import { StateObject, State } from 'mol-state'
 import { PluginCommands } from 'mol-plugin/command';
 
-export class StateTree extends React.Component<{ plugin: PluginContext }, { }> {
+export class StateTree extends React.Component<{ plugin: PluginContext, state: State }, { }> {
     componentDidMount() {
         // TODO: move to constructor?
-        this.props.plugin.events.state.data.updated.subscribe(() => this.forceUpdate());
+        this.props.state.context.events.updated.subscribe(() => this.forceUpdate());
     }
     render() {
         // const n = this.props.plugin.state.data.tree.nodes.get(this.props.plugin.state.data.tree.rootRef)!;
-        const n = this.props.plugin.state.data.tree.rootRef;
+        const n = this.props.state.tree.rootRef;
         return <div>
-            <StateTreeNode plugin={this.props.plugin} nodeRef={n} key={n} />
+            <StateTreeNode plugin={this.props.plugin} state={this.props.state} nodeRef={n} key={n} />
             { /* n.children.map(c => <StateTreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />) */}
         </div>;
     }
 }
 
-export class StateTreeNode extends React.Component<{ plugin: PluginContext, nodeRef: string }, { }> {
+export class StateTreeNode extends React.Component<{ plugin: PluginContext, nodeRef: string, state: State }, { }> {
     render() {
-        const n = this.props.plugin.state.data.tree.nodes.get(this.props.nodeRef)!;
-        const obj = this.props.plugin.state.data.objects.get(this.props.nodeRef)!;
+        const n = this.props.state.tree.nodes.get(this.props.nodeRef)!;
+        const obj = this.props.state.objects.get(this.props.nodeRef)!;
+
+        const remove = <>[<a href='#' onClick={e => {
+            e.preventDefault();
+            PluginCommands.Data.RemoveObject.dispatch(this.props.plugin, { ref: this.props.nodeRef });
+        }}>X</a>]</>
+
         if (!obj.obj) {
-            return <div style={{ borderLeft: '1px solid black', paddingLeft: '5px' }}>
-                {StateObject.StateType[obj.state]} {obj.errorText}
+            return <div style={{ borderLeft: '1px solid black', paddingLeft: '7px' }}>
+                {remove} {StateObject.StateType[obj.state]} {obj.errorText}
             </div>;
         }
         const props = obj.obj!.props as PluginStateObject.Props;
         const type = obj.obj!.type.info as PluginStateObject.TypeInfo;
-        return <div style={{ borderLeft: '1px solid #999', paddingLeft: '7px' }}>
-            [<a href='#' onClick={e => {
-                e.preventDefault();
-                PluginCommands.Data.RemoveObject.dispatch(this.props.plugin, { ref: this.props.nodeRef });
-            }}>X</a>][<span title={type.description}>{ type.shortName }</span>] <a href='#' onClick={e => {
+        return <div style={{ borderLeft: '0px solid #999', paddingLeft: '0px' }}>
+            {remove}[<span title={type.description}>{ type.shortName }</span>] <a href='#' onClick={e => {
                 e.preventDefault();
                 PluginCommands.Data.SetCurrentObject.dispatch(this.props.plugin, { ref: this.props.nodeRef });
             }}>{props.label}</a> {props.description ? <small>{props.description}</small> : void 0}
             {n.children.size === 0
                 ? void 0
-                : <div style={{ marginLeft: '3px' }}>{n.children.map(c => <StateTreeNode plugin={this.props.plugin} nodeRef={c!} key={c} />)}</div>
+                : <div style={{ marginLeft: '7px', paddingLeft: '3px', borderLeft: '1px solid #999' }}>{n.children.map(c => <StateTreeNode plugin={this.props.plugin} state={this.props.state} nodeRef={c!} key={c} />)}</div>
             }
         </div>;
     }
-- 
GitLab