viewport.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /**
  2. * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author David Sehnal <david.sehnal@gmail.com>
  6. */
  7. import * as React from 'react';
  8. import { resizeCanvas } from '../mol-canvas3d/util';
  9. import { PluginCommands } from '../mol-plugin/command';
  10. import { ParamDefinition as PD } from '../mol-util/param-definition';
  11. import { PluginUIComponent } from './base';
  12. import { ControlGroup, IconButton } from './controls/common';
  13. import { SimpleSettingsControl } from './viewport/simple-settings';
  14. interface ViewportControlsState {
  15. isSettingsExpanded: boolean,
  16. isHelpExpanded: boolean
  17. }
  18. interface ViewportControlsProps {
  19. }
  20. export class ViewportControls extends PluginUIComponent<ViewportControlsProps, ViewportControlsState> {
  21. state = {
  22. isSettingsExpanded: false,
  23. isHelpExpanded: false
  24. };
  25. resetCamera = () => {
  26. PluginCommands.Camera.Reset.dispatch(this.plugin, {});
  27. }
  28. toggleSettingsExpanded = (e?: React.MouseEvent<HTMLButtonElement>) => {
  29. this.setState({ isSettingsExpanded: !this.state.isSettingsExpanded, isHelpExpanded: false });
  30. e?.currentTarget.blur();
  31. }
  32. toggleHelpExpanded = (e?: React.MouseEvent<HTMLButtonElement>) => {
  33. this.setState({ isSettingsExpanded: false, isHelpExpanded: !this.state.isHelpExpanded });
  34. e?.currentTarget.blur();
  35. }
  36. toggleControls = () => {
  37. PluginCommands.Layout.Update.dispatch(this.plugin, { state: { showControls: !this.plugin.layout.state.showControls } });
  38. }
  39. toggleExpanded = () => {
  40. PluginCommands.Layout.Update.dispatch(this.plugin, { state: { isExpanded: !this.plugin.layout.state.isExpanded } });
  41. }
  42. setSettings = (p: { param: PD.Base<any>, name: string, value: any }) => {
  43. PluginCommands.Canvas3D.SetSettings.dispatch(this.plugin, { settings: { [p.name]: p.value } });
  44. }
  45. setLayout = (p: { param: PD.Base<any>, name: string, value: any }) => {
  46. PluginCommands.Layout.Update.dispatch(this.plugin, { state: { [p.name]: p.value } });
  47. }
  48. setInteractivityProps = (p: { param: PD.Base<any>, name: string, value: any }) => {
  49. PluginCommands.Interactivity.SetProps.dispatch(this.plugin, { props: { [p.name]: p.value } });
  50. }
  51. screenshot = () => {
  52. this.plugin.helpers.viewportScreenshot?.download();
  53. }
  54. componentDidMount() {
  55. this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate());
  56. this.subscribe(this.plugin.layout.events.updated, () => this.forceUpdate());
  57. this.subscribe(this.plugin.events.interactivity.propsUpdated, () => this.forceUpdate());
  58. }
  59. icon(name: string, onClick: (e: React.MouseEvent<HTMLButtonElement>) => void, title: string, isOn = true) {
  60. return <IconButton icon={name} toggleState={isOn} onClick={onClick} title={title} />;
  61. }
  62. onMouseMove = (e: React.MouseEvent) => {
  63. // ignore mouse moves when no button is held
  64. if (e.buttons === 0) e.stopPropagation()
  65. }
  66. render() {
  67. return <div className={'msp-viewport-controls'} onMouseMove={this.onMouseMove}>
  68. <div className='msp-viewport-controls-buttons'>
  69. <div>
  70. <div className='msp-semi-transparent-background' />
  71. {this.icon('reset-scene', this.resetCamera, 'Reset Camera')}
  72. </div>
  73. <div>
  74. <div className='msp-semi-transparent-background' />
  75. {this.icon('screenshot', this.screenshot, 'Download Screenshot')}
  76. </div>
  77. <div>
  78. <div className='msp-semi-transparent-background' />
  79. {this.icon('tools', this.toggleControls, 'Toggle Controls', this.plugin.layout.state.showControls)}
  80. {this.icon('expand-layout', this.toggleExpanded, 'Toggle Expanded', this.plugin.layout.state.isExpanded)}
  81. {this.icon('settings', this.toggleSettingsExpanded, 'Settings', this.state.isSettingsExpanded)}
  82. </div>
  83. {/* <div>
  84. <div className='msp-semi-transparent-background' />
  85. {this.icon('help-circle', this.toggleHelpExpanded, 'Help', this.state.isHelpExpanded)}
  86. </div> */}
  87. </div>
  88. {/* {this.state.isHelpExpanded && <div className='msp-viewport-controls-panel'>
  89. <ControlGroup header='Help' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleHelpExpanded} topRightIcon='off'>
  90. <HelpContent />
  91. </ControlGroup>
  92. </div>} */}
  93. {this.state.isSettingsExpanded && <div className='msp-viewport-controls-panel'>
  94. <ControlGroup header='Basic Settings' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleSettingsExpanded} topRightIcon='off'>
  95. <SimpleSettingsControl />
  96. </ControlGroup>
  97. {/* <ControlGroup header='Layout' initialExpanded={true}>
  98. <ParameterControls params={PluginLayoutStateParams} values={this.plugin.layout.state} onChange={this.setLayout} />
  99. </ControlGroup>
  100. <ControlGroup header='Interactivity' initialExpanded={true}>
  101. <ParameterControls params={Interactivity.Params} values={this.plugin.interactivity.props} onChange={this.setInteractivityProps} />
  102. </ControlGroup>
  103. {this.plugin.canvas3d && <ControlGroup header='Viewport' initialExpanded={true}>
  104. <ParameterControls params={Canvas3DParams} values={this.plugin.canvas3d.props} onChange={this.setSettings} />
  105. </ControlGroup>} */}
  106. </div>}
  107. </div>
  108. }
  109. }
  110. export const Logo = () =>
  111. <div className='msp-logo'>
  112. <div>
  113. <div>
  114. <div />
  115. <div className='msp-logo-image' />
  116. </div>
  117. </div>
  118. </div>
  119. interface ViewportState {
  120. noWebGl: boolean
  121. showLogo: boolean
  122. }
  123. export class Viewport extends PluginUIComponent<{ }, ViewportState> {
  124. private container = React.createRef<HTMLDivElement>();
  125. private canvas = React.createRef<HTMLCanvasElement>();
  126. state: ViewportState = {
  127. noWebGl: false,
  128. showLogo: true
  129. };
  130. private handleLogo = () => {
  131. this.setState({ showLogo: !this.plugin.canvas3d?.reprCount.value })
  132. }
  133. private handleResize = () => {
  134. const container = this.container.current;
  135. const canvas = this.canvas.current;
  136. if (container && canvas) {
  137. resizeCanvas(canvas, container);
  138. this.plugin.canvas3d!.handleResize();
  139. }
  140. }
  141. componentDidMount() {
  142. if (!this.canvas.current || !this.container.current || !this.plugin.initViewer(this.canvas.current!, this.container.current!)) {
  143. this.setState({ noWebGl: true });
  144. return;
  145. }
  146. this.handleLogo();
  147. this.handleResize();
  148. const canvas3d = this.plugin.canvas3d!;
  149. this.subscribe(canvas3d.reprCount, this.handleLogo);
  150. this.subscribe(canvas3d.input.resize, this.handleResize);
  151. this.subscribe(canvas3d.interaction.click, e => this.plugin.behaviors.interaction.click.next(e));
  152. this.subscribe(canvas3d.interaction.hover, e => this.plugin.behaviors.interaction.hover.next(e));
  153. this.subscribe(this.plugin.layout.events.updated, () => {
  154. setTimeout(this.handleResize, 50);
  155. });
  156. }
  157. componentWillUnmount() {
  158. if (super.componentWillUnmount) super.componentWillUnmount();
  159. // TODO viewer cleanup
  160. }
  161. renderMissing() {
  162. return <div className='msp-no-webgl'>
  163. <div>
  164. <p><b>WebGL does not seem to be available.</b></p>
  165. <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
  166. <p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p>
  167. </div>
  168. </div>
  169. }
  170. render() {
  171. if (this.state.noWebGl) return this.renderMissing();
  172. return <div className='msp-viewport'>
  173. <div className='msp-viewport-host3d' ref={this.container}>
  174. <canvas ref={this.canvas} />
  175. </div>
  176. {this.state.showLogo && <Logo />}
  177. </div>;
  178. }
  179. }