Browse Source

mol-plugin: camera snapshot manager

David Sehnal 6 years ago
parent
commit
3f14f4ba3a

+ 30 - 0
src/mol-plugin/behavior/static/camera.ts

@@ -7,9 +7,12 @@
 import { PluginContext } from 'mol-plugin/context';
 import { PluginCommands } from 'mol-plugin/command';
 import { PluginStateObject as SO } from '../../state/objects';
+import { CameraSnapshotManager } from 'mol-plugin/state/camera';
 
 export function registerDefault(ctx: PluginContext) {
     Reset(ctx);
+    SetSnapshot(ctx);
+    Snapshots(ctx);
 }
 
 export function Reset(ctx: PluginContext) {
@@ -24,4 +27,31 @@ export function Reset(ctx: PluginContext) {
         // TODO
         // ctx.canvas3d.resetCamera();
     })
+}
+
+export function SetSnapshot(ctx: PluginContext) {
+    PluginCommands.Camera.SetSnapshot.subscribe(ctx, ({ snapshot }) => {
+        ctx.canvas3d.camera.setState(snapshot);
+        ctx.canvas3d.requestDraw();
+    })
+}
+
+export function Snapshots(ctx: PluginContext) {
+    PluginCommands.Camera.Snapshots.Clear.subscribe(ctx, () => {
+        ctx.state.cameraSnapshots.clear();
+    });
+
+    PluginCommands.Camera.Snapshots.Remove.subscribe(ctx, ({ id }) => {
+        ctx.state.cameraSnapshots.remove(id);
+    });
+
+    PluginCommands.Camera.Snapshots.Add.subscribe(ctx, ({ name, description }) => {
+        const entry = CameraSnapshotManager.Entry(name || new Date().toLocaleTimeString(), ctx.canvas3d.camera.getSnapshot(), description);
+        ctx.state.cameraSnapshots.add(entry);
+    });
+
+    PluginCommands.Camera.Snapshots.Apply.subscribe(ctx, ({ id }) => {
+        const e = ctx.state.cameraSnapshots.getEntry(id);
+        return PluginCommands.Camera.SetSnapshot.dispatch(ctx, { snapshot: e.snapshot });
+    });
 }

+ 10 - 1
src/mol-plugin/command/camera.ts

@@ -5,5 +5,14 @@
  */
 
 import { PluginCommand } from './command';
+import { Camera } from 'mol-canvas3d/camera';
 
-export const Reset = PluginCommand<{}>({ isImmediate: true });
+export const Reset = PluginCommand<{}>({ isImmediate: true });
+export const SetSnapshot = PluginCommand<{ snapshot: Camera.Snapshot }>({ isImmediate: true });
+
+export const Snapshots = {
+    Add: PluginCommand<{ name?: string, description?: string }>({ isImmediate: true }),
+    Remove: PluginCommand<{ id: string }>({ isImmediate: true }),
+    Apply: PluginCommand<{ id: string }>({ isImmediate: true }),
+    Clear: PluginCommand<{ }>({ isImmediate: true }),
+}

+ 2 - 1
src/mol-plugin/context.ts

@@ -28,7 +28,8 @@ export class PluginContext {
     readonly events = {
         state: {
             data: this.state.data.events,
-            behavior: this.state.behavior.events
+            behavior: this.state.behavior.events,
+            cameraSnapshots: this.state.cameraSnapshots.events
         }
     };
 

+ 7 - 0
src/mol-plugin/state.ts

@@ -8,17 +8,20 @@ import { State } from 'mol-state';
 import { PluginStateObject as SO } from './state/objects';
 import { Camera } from 'mol-canvas3d/camera';
 import { PluginBehavior } from './behavior';
+import { CameraSnapshotManager } from './state/camera';
 
 export { PluginState }
 
 class PluginState {
     readonly data: State;
     readonly behavior: State;
+    readonly cameraSnapshots: CameraSnapshotManager = new CameraSnapshotManager();
 
     getSnapshot(): PluginState.Snapshot {
         return {
             data: this.data.getSnapshot(),
             behaviour: this.behavior.getSnapshot(),
+            cameraSnapshots: this.cameraSnapshots.getStateSnapshot(),
             canvas3d: {
                 camera: this.plugin.canvas3d.camera.getSnapshot()
             }
@@ -28,12 +31,15 @@ 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));
+        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.cameraSnapshots.dispose();
     }
 
     constructor(private plugin: import('./context').PluginContext) {
@@ -46,6 +52,7 @@ namespace PluginState {
     export interface Snapshot {
         data: State.Snapshot,
         behaviour: State.Snapshot,
+        cameraSnapshots: CameraSnapshotManager.StateSnapshot,
         canvas3d: {
             camera: Camera.Snapshot
         }

+ 79 - 0
src/mol-plugin/state/camera.ts

@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Camera } from 'mol-canvas3d/camera';
+import { OrderedMap } from 'immutable';
+import { UUID } from 'mol-util';
+import { RxEventHelper } from 'mol-util/rx-event-helper';
+
+export { CameraSnapshotManager }
+
+class CameraSnapshotManager {
+    private ev = RxEventHelper.create();
+    private _entries = OrderedMap<string, CameraSnapshotManager.Entry>().asMutable();
+
+    readonly events = {
+        changed: this.ev()
+    };
+
+    get entries() { return this._entries; }
+
+    getEntry(id: string) {
+        return this._entries.get(id);
+    }
+
+    remove(id: string) {
+        if (!this._entries.has(id)) return;
+        this._entries.delete(id);
+        this.events.changed.next();
+    }
+
+    add(e: CameraSnapshotManager.Entry) {
+        this._entries.set(e.id, e);
+        this.events.changed.next();
+    }
+
+    clear() {
+        if (this._entries.size === 0) return;
+        this._entries = OrderedMap<string, CameraSnapshotManager.Entry>().asMutable();
+        this.events.changed.next();
+    }
+
+    getStateSnapshot(): CameraSnapshotManager.StateSnapshot {
+        const entries: CameraSnapshotManager.Entry[] = [];
+        this._entries.forEach(e => entries.push(e!));
+        return { entries };
+    }
+
+    setStateSnapshot(state: CameraSnapshotManager.StateSnapshot ) {
+        this._entries = OrderedMap<string, CameraSnapshotManager.Entry>().asMutable();
+        for (const e of state.entries) {
+            this._entries.set(e.id, e);
+        }
+        this.events.changed.next();
+    }
+
+    dispose() {
+        this.ev.dispose();
+    }
+}
+
+namespace CameraSnapshotManager {
+    export interface Entry {
+        id: UUID,
+        name: string,
+        description?: string,
+        snapshot: Camera.Snapshot
+    }
+
+    export function Entry(name: string, snapshot: Camera.Snapshot, description?: string): Entry {
+        return { id: UUID.create22(), name, snapshot, description };
+    }
+
+    export interface StateSnapshot {
+        entries: Entry[]
+    }
+}

+ 62 - 0
src/mol-plugin/ui/controls.tsx

@@ -163,4 +163,66 @@ export class _test_UpdateTransform extends PluginComponent<{ state: State, nodeR
             <button onClick={() => this.update()} style={{ width: '100%' }}>Update</button>
         </div>
     }
+}
+
+export class CameraSnapshots extends PluginComponent<{ }, { }> {
+    render() {
+        return <div>
+            <h3>Camera Snapshots</h3>
+            <CameraSnapshotControls />
+            <CameraSnapshotList />
+        </div>;
+    }
+}
+
+class CameraSnapshotControls extends PluginComponent<{ }, { name: string, description: string }> {
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.cameraSnapshots.changed, () => this.forceUpdate());
+    }
+
+    state = { name: '', description: '' };
+
+    add = () => {
+        PluginCommands.Camera.Snapshots.Add.dispatch(this.plugin, this.state);
+        this.setState({ name: '', description: '' })
+    }
+
+    clear = () => {
+        PluginCommands.Camera.Snapshots.Clear.dispatch(this.plugin, {});
+    }
+
+    render() {
+        return <div>
+            <input type='text' value={this.state.name} placeholder='Name...' style={{ width: '33%', display: 'block', float: 'left' }} onChange={e => this.setState({ name: e.target.value })} />
+            <input type='text' value={this.state.description} placeholder='Description...' style={{ width: '67%', display: 'block' }} onChange={e => this.setState({ description: e.target.value })} />
+            <button style={{ float: 'right' }} onClick={this.clear}>Clear</button>
+            <button onClick={this.add}>Add</button>
+        </div>;
+    }
+}
+
+class CameraSnapshotList extends PluginComponent<{ }, { }> {
+    componentDidMount() {
+        this.subscribe(this.plugin.events.state.cameraSnapshots.changed, () => this.forceUpdate());
+    }
+
+    apply(id: string) {
+        return () => PluginCommands.Camera.Snapshots.Apply.dispatch(this.plugin, { id });
+    }
+
+    remove(id: string) {
+        return () => {
+            PluginCommands.Camera.Snapshots.Remove.dispatch(this.plugin, { id });
+        }
+    }
+
+    render() {
+        return <ul style={{ listStyle: 'none' }}>
+            {this.plugin.state.cameraSnapshots.entries.valueSeq().map(e =><li key={e!.id}>
+                <button onClick={this.apply(e!.id)}>Set</button>
+                &nbsp;{e!.name} <small>{e!.description}</small>
+                <button onClick={this.remove(e!.id)} style={{ float: 'right' }}>X</button>
+            </li>)}
+        </ul>;
+    }
 }

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

@@ -8,7 +8,7 @@ import * as React from 'react';
 import { PluginContext } from '../context';
 import { StateTree } from './state-tree';
 import { Viewport, ViewportControls } from './viewport';
-import { Controls, _test_UpdateTransform, _test_ApplyAction, _test_TrajectoryControls } from './controls';
+import { Controls, _test_UpdateTransform, _test_ApplyAction, _test_TrajectoryControls, CameraSnapshots } from './controls';
 import { PluginComponent, PluginReactContext } from './base';
 import { merge } from 'rxjs';
 import { State } from 'mol-state';
@@ -33,6 +33,8 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> {
                     <_test_CurrentObject />
                     <hr />
                     <Controls />
+                    <hr />
+                    <CameraSnapshots />
                 </div>
             </div>
         </PluginReactContext.Provider>;