Skip to content
Snippets Groups Projects
Commit 545d9434 authored by dsehnal's avatar dsehnal
Browse files

Add PluginContext.initContainer/canvas3dInitialized and their usage

parent bbc43d51
No related branches found
No related tags found
No related merge requests found
...@@ -6,7 +6,9 @@ Note that since we don't clearly distinguish between a public and private interf ...@@ -6,7 +6,9 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased] ## [Unreleased]
- Add `PluginContext.mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI - Add `PluginContext.initContainer/mount/unmount` methods; these should make it easier to reuse a plugin context with both custom and built-in UI
- Add `PluginContext.canvas3dInitialized`
- `createPluginUI` now resolves after the 3d canvas has been initialized.
## [v3.22.0] - 2022-10-17 ## [v3.22.0] - 2022-10-17
......
...@@ -58,20 +58,22 @@ class Viewer { ...@@ -58,20 +58,22 @@ class Viewer {
} }
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) { static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
const o = { ...DefaultViewerOptions, ...{ const o = {
layoutIsExpanded: false, ...DefaultViewerOptions, ...{
layoutShowControls: false, layoutIsExpanded: false,
layoutShowRemoteState: false, layoutShowControls: false,
layoutShowSequence: true, layoutShowRemoteState: false,
layoutShowLog: false, layoutShowSequence: true,
layoutShowLeftPanel: true, layoutShowLog: false,
layoutShowLeftPanel: true,
viewportShowExpand: true,
viewportShowControls: false, viewportShowExpand: true,
viewportShowSettings: false, viewportShowControls: false,
viewportShowSelectionMode: false, viewportShowSettings: false,
viewportShowAnimation: false, viewportShowSelectionMode: false,
} }; viewportShowAnimation: false,
}
};
const defaultSpec = DefaultPluginUISpec(); const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = { const spec: PluginUISpec = {
...@@ -135,18 +137,16 @@ class Viewer { ...@@ -135,18 +137,16 @@ class Viewer {
} }
}; };
plugin.behaviors.canvas3d.initialized.subscribe(v => { PluginCommands.Canvas3D.SetSettings(plugin, {
if (v) { settings: {
PluginCommands.Canvas3D.SetSettings(plugin, { settings: { renderer: {
renderer: { ...plugin.canvas3d!.props.renderer,
...plugin.canvas3d!.props.renderer, backgroundColor: ColorNames.white,
backgroundColor: ColorNames.white, },
}, camera: {
camera: { ...plugin.canvas3d!.props.camera,
...plugin.canvas3d!.props.camera, helper: { axes: { name: 'off', params: {} } }
helper: { axes: { name: 'off', params: {} } } }
}
} });
} }
}); });
......
...@@ -4,16 +4,16 @@ ...@@ -4,16 +4,16 @@
* @author David Sehnal <david.sehnal@gmail.com> * @author David Sehnal <david.sehnal@gmail.com>
*/ */
import * as ReactDOM from 'react-dom'; import { createRoot } from 'react-dom/client';
import { AlphaOrbitalsExample } from '.'; import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters'; import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior'; import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { PluginContextContainer } from '../../mol-plugin-ui/plugin'; import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) { export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}> createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}>
<Controls orbitals={orbitals} /> <Controls orbitals={orbitals} />
</PluginContextContainer>, parent); </PluginContextContainer>);
} }
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) { function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
......
...@@ -82,24 +82,20 @@ export class AlphaOrbitalsExample { ...@@ -82,24 +82,20 @@ export class AlphaOrbitalsExample {
this.plugin.managers.interactivity.setProps({ granularity: 'element' }); this.plugin.managers.interactivity.setProps({ granularity: 'element' });
this.plugin.behaviors.canvas3d.initialized.subscribe(init => { if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
if (!init) return; PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) { message: `Browser/device does not support required WebGL extension (OES_texture_float).`
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
message: `Browser/device does not support required WebGL extension (OES_texture_float).`
});
return;
}
this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
}); });
return;
}
mountControls(this, document.getElementById('controls')!); this.load({
moleculeSdf: DemoMoleculeSDF,
...DemoOrbitals
}); });
mountControls(this, document.getElementById('controls')!);
} }
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any); readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
......
...@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o ...@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
await options.onBeforeUIRender(ctx); await options.onBeforeUIRender(ctx);
} }
ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target); ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
try {
await ctx.canvas3dInitialized;
} catch {
// Error reported in UI/console elsewhere.
}
return ctx; return ctx;
} }
\ No newline at end of file
...@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o ...@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
await options.onBeforeUIRender(ctx); await options.onBeforeUIRender(ctx);
} }
createRoot(target).render(createElement(Plugin, { plugin: ctx })); createRoot(target).render(createElement(Plugin, { plugin: ctx }));
try {
await ctx.canvas3dInitialized;
} catch {
// Error reported in UI/console elsewhere.
}
return ctx; return ctx;
} }
\ No newline at end of file
...@@ -35,7 +35,7 @@ import { Representation } from '../mol-repr/representation'; ...@@ -35,7 +35,7 @@ import { Representation } from '../mol-repr/representation';
import { StructureRepresentationRegistry } from '../mol-repr/structure/registry'; import { StructureRepresentationRegistry } from '../mol-repr/structure/registry';
import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry'; import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry';
import { StateTransform } from '../mol-state'; import { StateTransform } from '../mol-state';
import { RuntimeContext, Task } from '../mol-task'; import { RuntimeContext, Scheduler, Task } from '../mol-task';
import { ColorTheme } from '../mol-theme/color'; import { ColorTheme } from '../mol-theme/color';
import { SizeTheme } from '../mol-theme/size'; import { SizeTheme } from '../mol-theme/size';
import { ThemeRegistryContext } from '../mol-theme/theme'; import { ThemeRegistryContext } from '../mol-theme/theme';
...@@ -71,6 +71,7 @@ export class PluginContext { ...@@ -71,6 +71,7 @@ export class PluginContext {
}; };
protected subs: Subscription[] = []; protected subs: Subscription[] = [];
private initCanvas3dPromiseCallbacks: [res: () => void, rej: (err: any) => void] = [() => {}, () => {}];
private disposed = false; private disposed = false;
private canvasContainer: HTMLDivElement | undefined = void 0; private canvasContainer: HTMLDivElement | undefined = void 0;
...@@ -103,10 +104,15 @@ export class PluginContext { ...@@ -103,10 +104,15 @@ export class PluginContext {
leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root') leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root')
}, },
canvas3d: { canvas3d: {
// TODO: remove in 4.0?
initialized: this.canvas3dInit.pipe(filter(v => !!v), take(1)) initialized: this.canvas3dInit.pipe(filter(v => !!v), take(1))
} }
} as const; } as const;
readonly canvas3dInitialized = new Promise<void>((res, rej) => {
this.initCanvas3dPromiseCallbacks = [res, rej];
});
readonly canvas3dContext: Canvas3DContext | undefined; readonly canvas3dContext: Canvas3DContext | undefined;
readonly canvas3d: Canvas3D | undefined; readonly canvas3d: Canvas3D | undefined;
readonly layout = new PluginLayout(this); readonly layout = new PluginLayout(this);
...@@ -187,44 +193,49 @@ export class PluginContext { ...@@ -187,44 +193,49 @@ export class PluginContext {
*/ */
readonly customState: unknown = Object.create(null); readonly customState: unknown = Object.create(null);
mount(target: HTMLElement, canvas3dContext?: Canvas3DContext) { initContainer(canvas3dContext?: Canvas3DContext) {
if (this.disposed) throw new Error('Cannot mount a disposed context'); if (this.canvasContainer) return true;
if (!this.canvasContainer) { const container = document.createElement('div');
const container = document.createElement('div'); Object.assign(container.style, {
Object.assign(container.style, { position: 'absolute',
position: 'absolute', left: 0,
left: 0, top: 0,
top: 0, right: 0,
right: 0, bottom: 0,
bottom: 0, '-webkit-user-select': 'none',
'-webkit-user-select': 'none', 'user-select': 'none',
'user-select': 'none', '-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
'-webkit-tap-highlight-color': 'rgba(0,0,0,0)', '-webkit-touch-callout': 'none',
'-webkit-touch-callout': 'none', 'touch-action': 'manipulation',
'touch-action': 'manipulation', });
let canvas = canvas3dContext?.canvas;
if (!canvas) {
canvas = document.createElement('canvas');
Object.assign(canvas.style, {
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
'background-size': '60px 60px',
'background-position': '0 0, 30px 30px'
}); });
let canvas = canvas3dContext?.canvas; container.appendChild(canvas);
if (!canvas) { }
canvas = document.createElement('canvas'); if (!this.initViewer(canvas, container, canvas3dContext)) {
Object.assign(canvas.style, { return false;
'background-image': 'linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%, lightgrey)',
'background-size': '60px 60px',
'background-position': '0 0, 30px 30px'
});
container.appendChild(canvas);
}
if (!this.initViewer(canvas, container, canvas3dContext)) {
return false;
}
this.canvasContainer = container;
} }
this.canvasContainer = container;
return true;
}
mount(target: HTMLElement) {
if (this.disposed) throw new Error('Cannot mount a disposed context');
if (!this.initContainer()) return false;
if (this.canvasContainer.parentElement !== target) { if (this.canvasContainer!.parentElement !== target) {
this.canvasContainer.parentElement?.removeChild(this.canvasContainer); this.canvasContainer!.parentElement?.removeChild(this.canvasContainer!);
} }
target.appendChild(this.canvasContainer); target.appendChild(this.canvasContainer!);
this.handleResize(); this.handleResize();
return true; return true;
} }
...@@ -279,10 +290,12 @@ export class PluginContext { ...@@ -279,10 +290,12 @@ export class PluginContext {
this.handleResize(); this.handleResize();
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[0]());
return true; return true;
} catch (e) { } catch (e) {
this.log.error('' + e); this.log.error('' + e);
console.error(e); console.error(e);
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[1](e));
return false; return false;
} }
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment