Skip to content
Snippets Groups Projects
Unverified Commit ab3ff842 authored by David Sehnal's avatar David Sehnal Committed by GitHub
Browse files

Merge pull request #590 from molstar/reusable-canvas

Built-in support for mouting/unmounting PluginContext
parents a6709acf 82f0f92c
No related branches found
No related tags found
No related merge requests found
......@@ -6,6 +6,10 @@ Note that since we don't clearly distinguish between a public and private interf
## [Unreleased]
- 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
- Replace `VolumeIsosurfaceParams.pickingGranularity` param with `Volume.PickingGranuality`
......
......@@ -58,7 +58,8 @@ class Viewer {
}
static async create(elementOrId: string | HTMLElement, colors = [Color(0x992211), Color(0xDDDDDD)], showButtons = true) {
const o = { ...DefaultViewerOptions, ...{
const o = {
...DefaultViewerOptions, ...{
layoutIsExpanded: false,
layoutShowControls: false,
layoutShowRemoteState: false,
......@@ -71,7 +72,8 @@ class Viewer {
viewportShowSettings: false,
viewportShowSelectionMode: false,
viewportShowAnimation: false,
} };
}
};
const defaultSpec = DefaultPluginUISpec();
const spec: PluginUISpec = {
......@@ -135,9 +137,8 @@ class Viewer {
}
};
plugin.behaviors.canvas3d.initialized.subscribe(v => {
if (v) {
PluginCommands.Canvas3D.SetSettings(plugin, { settings: {
PluginCommands.Canvas3D.SetSettings(plugin, {
settings: {
renderer: {
...plugin.canvas3d!.props.renderer,
backgroundColor: ColorNames.white,
......@@ -146,7 +147,6 @@ class Viewer {
...plugin.canvas3d!.props.camera,
helper: { axes: { name: 'off', params: {} } }
}
} });
}
});
......
......@@ -4,16 +4,16 @@
* @author David Sehnal <david.sehnal@gmail.com>
*/
import * as ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { AlphaOrbitalsExample } from '.';
import { ParameterControls } from '../../mol-plugin-ui/controls/parameters';
import { useBehavior } from '../../mol-plugin-ui/hooks/use-behavior';
import { PluginContextContainer } from '../../mol-plugin-ui/plugin';
export function mountControls(orbitals: AlphaOrbitalsExample, parent: Element) {
ReactDOM.render(<PluginContextContainer plugin={orbitals.plugin}>
createRoot(parent).render(<PluginContextContainer plugin={orbitals.plugin}>
<Controls orbitals={orbitals} />
</PluginContextContainer>, parent);
</PluginContextContainer>);
}
function Controls({ orbitals }: { orbitals: AlphaOrbitalsExample }) {
......
......@@ -82,9 +82,6 @@ export class AlphaOrbitalsExample {
this.plugin.managers.interactivity.setProps({ granularity: 'element' });
this.plugin.behaviors.canvas3d.initialized.subscribe(init => {
if (!init) return;
if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) {
PluginCommands.Toast.Show(this.plugin, {
title: 'Error',
......@@ -99,7 +96,6 @@ export class AlphaOrbitalsExample {
});
mountControls(this, document.getElementById('controls')!);
});
}
readonly params = new BehaviorSubject<ParamDefinition.For<Params>>({} as any);
......
......@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
await options.onBeforeUIRender(ctx);
}
ReactDOM.render(React.createElement(Plugin, { plugin: ctx }), target);
try {
await ctx.canvas3dInitialized;
} catch {
// Error reported in UI/console elsewhere.
}
return ctx;
}
\ No newline at end of file
......@@ -24,14 +24,6 @@ import { BehaviorSubject } from 'rxjs';
import { useBehavior } from './hooks/use-behavior';
export class Plugin extends React.Component<{ plugin: PluginUIContext, children?: any }, {}> {
region(kind: 'left' | 'right' | 'bottom' | 'main', element: JSX.Element) {
return <div className={`msp-layout-region msp-layout-${kind}`}>
<div className='msp-layout-static'>
{element}
</div>
</div>;
}
render() {
return <PluginReactContext.Provider value={this.props.plugin}>
<Layout />
......
......@@ -18,5 +18,10 @@ export async function createPluginUI(target: HTMLElement, spec?: PluginUISpec, o
await options.onBeforeUIRender(ctx);
}
createRoot(target).render(createElement(Plugin, { plugin: ctx }));
try {
await ctx.canvas3dInitialized;
} catch {
// Error reported in UI/console elsewhere.
}
return ctx;
}
\ No newline at end of file
/**
* Copyright (c) 2020-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
* @author Alexander Rose <alexander.rose@weirdbyte.de>
......@@ -19,13 +19,14 @@ export interface ViewportCanvasParams {
parentClassName?: string,
parentStyle?: React.CSSProperties,
// NOTE: hostClassName/hostStyle no longer in use
// TODO: remove in 4.0
hostClassName?: string,
hostStyle?: React.CSSProperties,
}
export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, ViewportCanvasState> {
private container = React.createRef<HTMLDivElement>();
private canvas = React.createRef<HTMLCanvasElement>();
state: ViewportCanvasState = {
noWebGl: false,
......@@ -37,7 +38,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
};
componentDidMount() {
if (!this.canvas.current || !this.container.current || !this.plugin.initViewer(this.canvas.current!, this.container.current!)) {
if (!this.container.current || !this.plugin.mount(this.container.current!)) {
this.setState({ noWebGl: true });
return;
}
......@@ -47,7 +48,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
componentWillUnmount() {
super.componentWillUnmount();
// TODO viewer cleanup
this.plugin.unmount();
}
renderMissing() {
......@@ -70,10 +71,7 @@ export class ViewportCanvas extends PluginUIComponent<ViewportCanvasParams, View
const Logo = this.props.logo;
return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle}>
<div className={this.props.hostClassName || 'msp-viewport-host3d'} style={this.props.hostStyle} ref={this.container}>
<canvas ref={this.canvas} />
</div>
return <div className={this.props.parentClassName || 'msp-viewport'} style={this.props.parentStyle} ref={this.container}>
{(this.state.showLogo && Logo) && <Logo />}
</div>;
}
......
......@@ -35,7 +35,7 @@ import { Representation } from '../mol-repr/representation';
import { StructureRepresentationRegistry } from '../mol-repr/structure/registry';
import { VolumeRepresentationRegistry } from '../mol-repr/volume/registry';
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 { SizeTheme } from '../mol-theme/size';
import { ThemeRegistryContext } from '../mol-theme/theme';
......@@ -71,8 +71,10 @@ export class PluginContext {
};
protected subs: Subscription[] = [];
private initCanvas3dPromiseCallbacks: [res: () => void, rej: (err: any) => void] = [() => {}, () => {}];
private disposed = false;
private canvasContainer: HTMLDivElement | undefined = void 0;
private ev = RxEventHelper.create();
readonly config = new PluginConfigManager(this.spec.config); // needed to init state
......@@ -102,10 +104,15 @@ export class PluginContext {
leftPanelTabName: this.ev.behavior<LeftPanelTabName>('root')
},
canvas3d: {
// TODO: remove in 4.0?
initialized: this.canvas3dInit.pipe(filter(v => !!v), take(1))
}
} as const;
readonly canvas3dInitialized = new Promise<void>((res, rej) => {
this.initCanvas3dPromiseCallbacks = [res, rej];
});
readonly canvas3dContext: Canvas3DContext | undefined;
readonly canvas3d: Canvas3D | undefined;
readonly layout = new PluginLayout(this);
......@@ -186,6 +193,57 @@ export class PluginContext {
*/
readonly customState: unknown = Object.create(null);
initContainer(canvas3dContext?: Canvas3DContext) {
if (this.canvasContainer) return true;
const container = document.createElement('div');
Object.assign(container.style, {
position: 'absolute',
left: 0,
top: 0,
right: 0,
bottom: 0,
'-webkit-user-select': 'none',
'user-select': 'none',
'-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
'-webkit-touch-callout': 'none',
'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'
});
container.appendChild(canvas);
}
if (!this.initViewer(canvas, container, canvas3dContext)) {
return false;
}
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) {
this.canvasContainer!.parentElement?.removeChild(this.canvasContainer!);
}
target.appendChild(this.canvasContainer!);
this.handleResize();
return true;
}
unmount() {
this.canvasContainer?.parentElement?.removeChild(this.canvasContainer);
}
initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement, canvas3dContext?: Canvas3DContext) {
try {
this.layout.setRoot(container);
......@@ -232,10 +290,12 @@ export class PluginContext {
this.handleResize();
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[0]());
return true;
} catch (e) {
this.log.error('' + e);
console.error(e);
Scheduler.setImmediate(() => this.initCanvas3dPromiseCallbacks[1](e));
return false;
}
}
......@@ -306,6 +366,9 @@ export class PluginContext {
objectForEach(this.managers, m => (m as any)?.dispose?.());
objectForEach(this.managers.structure, m => (m as any)?.dispose?.());
this.unmount();
this.canvasContainer = undefined;
this.disposed = true;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment