viewport.tsx 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * Adapted from LiteMol
  5. * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info.
  6. *
  7. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  8. */
  9. import * as React from 'react'
  10. import { ViewportController } from '../../controller/visualization/viewport'
  11. import { View } from '../view';
  12. import { HelpBox, Toggle, Button } from '../controls/common'
  13. import { Slider } from '../controls/slider'
  14. import { ImageCanvas } from './image-canvas';
  15. export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> {
  16. state = { showSceneOptions: false, showHelp: false };
  17. private help() {
  18. return <div className='molstar-viewport-controls-scene-options molstar-control'>
  19. <HelpBox title='Rotate' content={<div><div>Left button</div><div>One finger touch</div></div>} />
  20. <HelpBox title='Zoom' content={<div><div>Right button</div><div>Pinch</div></div>} />
  21. <HelpBox title='Move' content={<div><div>Middle button</div><div>Two finger touch</div></div>} />
  22. <HelpBox title='Slab' content={<div><div>Mouse wheel</div><div>Three finger touch</div></div>} />
  23. </div>
  24. }
  25. render() {
  26. let state = this.controller.latestState;
  27. let options: any;
  28. let layoutController = this.controller.context.layout;
  29. let layoutState = layoutController.latestState;
  30. if (this.state.showSceneOptions) {
  31. options = <div className='molstar-viewport-controls-scene-options molstar-control'>
  32. <Toggle onChange={v => this.controller.setState({ enableFog: v })} value={state.enableFog!} label='Fog' />
  33. <Slider label='FOV' min={30} max={90} onChange={v => this.controller.setState({ cameraFOV: v }) } value={state.cameraFOV!} />
  34. <Slider label='Camera Speed' min={1} max={10} step={0.01} onChange={v => this.controller.setState({ cameraSpeed: v }) } value={state.cameraSpeed!} />
  35. </div>;
  36. } else if (this.state.showHelp) {
  37. options = this.help();
  38. }
  39. let controlsShown = !layoutState.hideControls;
  40. return <div className='molstar-viewport-controls' onMouseLeave={() => this.setState({ showSceneOptions: false, showHelp: false })}>
  41. <div className='molstar-viewport-controls-buttons'>
  42. <Button
  43. style='link'
  44. active={this.state.showHelp}
  45. customClass={'molstar-btn-link-toggle-' + (this.state.showHelp ? 'on' : 'off')}
  46. icon='help-circle'
  47. onClick={(e) => this.setState({ showHelp: !this.state.showHelp, showSceneOptions: false }) } title='Controls Help' />
  48. <Button
  49. style='link'
  50. active={this.state.showSceneOptions}
  51. customClass={'molstar-btn-link-toggle-' + (this.state.showSceneOptions ? 'on' : 'off')}
  52. icon='settings'
  53. onClick={(e) => this.setState({ showSceneOptions: !this.state.showSceneOptions, showHelp: false }) } title='Scene Options' />
  54. <Button
  55. style='link'
  56. icon='screenshot'
  57. onClick={(e) => this.controller.context.stage.viewer.downloadScreenshot()}
  58. title='Screenshot' />
  59. <Button onClick={() => { layoutController.update({ hideControls: controlsShown }); this.forceUpdate(); } }
  60. icon='tools' title={controlsShown ? 'Hide Controls' : 'Show Controls'} active={controlsShown }
  61. customClass={'molstar-btn-link-toggle-' + (controlsShown ? 'on' : 'off')}
  62. style='link' />
  63. <Button onClick={() => layoutController.update({ isExpanded: !layoutState.isExpanded }) }
  64. icon='expand-layout' title={layoutState.isExpanded ? 'Collapse' : 'Expand'} active={layoutState.isExpanded }
  65. customClass={'molstar-btn-link-toggle-' + (layoutState.isExpanded ? 'on' : 'off')}
  66. style='link' />
  67. <Button
  68. style='link'
  69. icon='reset-scene'
  70. onClick={(e) => this.controller.context.stage.viewer.resetCamera()}
  71. title='Reset camera' />
  72. </div>
  73. {options}
  74. </div>;
  75. }
  76. }
  77. export const Logo = () =>
  78. <div className='molstar-logo'>
  79. <div>
  80. <div>
  81. <div />
  82. <div className='molstar-logo-image' />
  83. </div>
  84. </div>
  85. </div>
  86. type ViewportState = {
  87. noWebGl: boolean,
  88. showLogo: boolean,
  89. aspectRatio: number,
  90. width: number
  91. height: number
  92. images: { [k: string]: ImageData }
  93. info: string
  94. }
  95. export class Viewport extends View<ViewportController, ViewportState, { noWebGl?: boolean, showLogo?: boolean, aspectRatio: number, info: string }> {
  96. private container: HTMLDivElement | null = null;
  97. private canvas: HTMLCanvasElement | null = null;
  98. private defaultBg = { r: 1, g: 1, b: 1 }
  99. state: ViewportState = {
  100. noWebGl: false,
  101. showLogo: true,
  102. images: {},
  103. aspectRatio: 1,
  104. width: 0,
  105. height: 0,
  106. info: ''
  107. };
  108. handleResize() {
  109. if (this.container) {
  110. this.setState({
  111. aspectRatio: this.container.clientWidth / this.container.clientHeight,
  112. width: this.container.clientWidth,
  113. height: this.container.clientHeight
  114. })
  115. }
  116. }
  117. componentDidMount() {
  118. if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) {
  119. this.setState({ noWebGl: true });
  120. }
  121. const viewer = this.controller.context.stage.viewer
  122. viewer.reprCount.subscribe(count => {
  123. this.setState({
  124. showLogo: false
  125. // showLogo: count === 0
  126. })
  127. })
  128. viewer.identified.subscribe(info => {
  129. this.setState({ info })
  130. })
  131. viewer.didDraw.subscribe(() => {
  132. // this.setState({ imageData: viewer.getImageData() })
  133. this.setState({
  134. images: {
  135. 'object': viewer.getImageData('pickObject'),
  136. 'instance': viewer.getImageData('pickInstance'),
  137. 'element': viewer.getImageData('pickElement')
  138. }
  139. })
  140. })
  141. viewer.input.resize.subscribe(() => this.handleResize())
  142. this.handleResize()
  143. }
  144. componentWillUnmount() {
  145. super.componentWillUnmount();
  146. this.controller.context.destroy();
  147. }
  148. renderMissing() {
  149. return <div className='molstar-no-webgl'>
  150. <div>
  151. <p><b>WebGL does not seem to be available.</b></p>
  152. <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p>
  153. <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>
  154. </div>
  155. </div>
  156. }
  157. render() {
  158. if (this.state.noWebGl) return this.renderMissing();
  159. const color = this.controller.latestState.clearColor! || this.defaultBg;
  160. return <div className='molstar-viewport' style={{ backgroundColor: `rgb(${255 * color.r}, ${255 * color.g}, ${255 * color.b})` }}>
  161. <div ref={elm => this.container = elm} className='molstar-viewport-container'>
  162. <canvas ref={elm => this.canvas = elm} className='molstar-viewport-canvas'></canvas>
  163. </div>
  164. {this.state.showLogo ? <Logo /> : void 0}
  165. <ViewportControls controller={this.controller} />
  166. <div
  167. style={{
  168. position: 'absolute',
  169. top: 10,
  170. left: 10,
  171. padding: 10,
  172. color: 'lightgrey',
  173. background: 'rgba(0, 0, 0, 0.2)'
  174. }}
  175. >
  176. {this.state.info}
  177. </div>
  178. <div
  179. style={{
  180. position: 'absolute',
  181. bottom: 10,
  182. left: 10,
  183. }}
  184. >
  185. {Object.keys(this.state.images).map(k => {
  186. const imageData = this.state.images[k]
  187. return <ImageCanvas
  188. key={k}
  189. imageData={imageData}
  190. aspectRatio={this.state.aspectRatio}
  191. maxWidth={this.state.width / 4}
  192. maxHeight={this.state.height / 4}
  193. />
  194. })}
  195. </div>
  196. </div>;
  197. }
  198. }