diff --git a/Sources/MainView.js b/Sources/MainView.js index bee75251..e63f276e 100644 --- a/Sources/MainView.js +++ b/Sources/MainView.js @@ -6,6 +6,7 @@ import PropTypes from 'prop-types'; import { Layout, Menu, Progress } from 'antd'; import Layouts from './layouts'; +import LayoutConfig from './config/glanceLayoutConfig'; import style from './pv-explorer.mcss'; import icons from './icons'; @@ -13,7 +14,9 @@ import Controls from './controls'; const { Header, Sider, Content } = Layout; -const layouts = ['Layout2D', 'Layout3D', 'LayoutSplit', 'LayoutQuad']; +const { LayoutGrid } = Layouts; + +const layouts = ['2D', '3D', 'Split', 'Quad']; const WIDTH = 300; @@ -21,7 +24,7 @@ export default class MainView extends React.Component { constructor(props) { super(props); this.state = { - layout: 'Layout3D', + layout: '3D', overlayOpacity: 100, collapsed: false, tab: 'files', @@ -36,10 +39,7 @@ export default class MainView extends React.Component { } onLayoutChange({ item, key, selectedKeys }) { - this.setState({ layout: key }, () => { - this.forceUpdate(); - this.props.proxyManager.createRepresentationInAllViews(); - }); + this.setState({ layout: key }); } onToggleControl() { @@ -50,7 +50,6 @@ export default class MainView extends React.Component { } render() { - const Renderer = Layouts[this.state.layout]; let progress = null; if (this.state.showProgress) { @@ -80,7 +79,7 @@ export default class MainView extends React.Component { {name} ))} @@ -105,9 +104,11 @@ export default class MainView extends React.Component { - diff --git a/Sources/config/glanceLayoutConfig.js b/Sources/config/glanceLayoutConfig.js new file mode 100644 index 00000000..35b726bf --- /dev/null +++ b/Sources/config/glanceLayoutConfig.js @@ -0,0 +1,98 @@ +import Layout2D from '../layouts/Layout2D'; +import Layout3D from '../layouts/Layout3D'; + +export default { + layouts: { + '2D': { + grid: [1, 1], + views: { + // stacked + sliceX: { cell: [1, 1] }, + sliceY: { cell: [1, 1], defaultVisible: true, defaultActive: true }, + sliceZ: { cell: [1, 1] }, + }, + }, + '3D': { + grid: [1, 1], + views: { + volume: { cell: [1, 1], defaultActive: true }, + }, + }, + Split: { + grid: [1, 2], + views: { + // stacked + sliceX: { cell: [1, 1] }, + sliceY: { cell: [1, 1], defaultVisible: true }, + sliceZ: { cell: [1, 1] }, + + volume: { cell: [1, 2], defaultActive: true }, + }, + }, + Quad: { + grid: [2, 2], + views: { + sliceX: { + cell: [2, 1], + propOverrides: { + viewType: 'View2D_X', + title: '-Z+Y', + orientations: [], + }, + }, + sliceY: { + cell: [1, 1], + propOverrides: { + viewType: 'View2D_Y', + title: '-Z+X', + orientations: [], + }, + }, + sliceZ: { + cell: [2, 2], + propOverrides: { + viewType: 'View2D_Z', + title: '+X+Y', + orientations: [], + }, + }, + volume: { cell: [1, 2], defaultActive: true }, + }, + }, + }, + views: { + sliceX: { + component: Layout2D, + props: { + axis: 0, + orientation: 1, + viewUp: [0, 0, 1], + onAxisChange: (axis) => ['sliceX', 'sliceY', 'sliceZ'][axis], + }, + stackChangeFunc: 'onAxisChange', + }, + sliceY: { + component: Layout2D, + props: { + axis: 1, + orientation: 1, + viewUp: [0, -1, 0], + onAxisChange: (axis) => ['sliceX', 'sliceY', 'sliceZ'][axis], + }, + stackChangeFunc: 'onAxisChange', + }, + sliceZ: { + component: Layout2D, + props: { + axis: 2, + orientation: 1, + viewUp: [0, -1, 0], + onAxisChange: (axis) => ['sliceX', 'sliceY', 'sliceZ'][axis], + }, + stackChangeFunc: 'onAxisChange', + }, + volume: { + component: Layout3D, + }, + }, +}; diff --git a/Sources/layouts/Layout2D.js b/Sources/layouts/Layout2D.js index 991806cc..2e730c4d 100644 --- a/Sources/layouts/Layout2D.js +++ b/Sources/layouts/Layout2D.js @@ -37,7 +37,6 @@ export default class Layout2D extends React.Component { this.onActiveSourceChange = this.onActiveSourceChange.bind(this); this.rotate = this.rotate.bind(this); this.toggleOrientationMarker = this.toggleOrientationMarker.bind(this); - this.updateOrientation = this.updateOrientation.bind(this); this.activateView = this.activateView.bind(this); this.flush = () => this.forceUpdate(); @@ -158,26 +157,9 @@ export default class Layout2D extends React.Component { } } - updateOrientation(e) { - const state = this.props.orientations[Number(e.target.dataset.index)]; - this.view.updateOrientation(state.axis, state.orientation, state.viewUp); - const reps = this.view.getRepresentations(); - for (let i = 0; i < reps.length; i++) { - if (reps[i].setSlicingMode) { - reps[i].setSlicingMode('XYZ'[state.axis]); - } - } - this.props.proxyManager.modified(); - this.view.resetCamera(); - this.view.renderLater(); - this.onActiveSourceChange(); - this.updateSlider(); - - // Update control panel - this.props.proxyManager.modified(); - - // Update slider - this.forceUpdate(); + resize() { + this.view.resize(); + this.slider.resize(); } rotate() { @@ -217,7 +199,7 @@ export default class Layout2D extends React.Component { key={o.label} className={style.button} data-index={i} - onClick={this.updateOrientation} + onClick={() => this.props.onAxisChange(o.axis)} > {o.label} @@ -269,6 +251,7 @@ Layout2D.propTypes = { orientations: PropTypes.array, activateOnMount: PropTypes.bool, viewType: PropTypes.string, + onAxisChange: PropTypes.func, }; Layout2D.defaultProps = { @@ -285,4 +268,5 @@ Layout2D.defaultProps = { { label: 'Z', axis: 2, orientation: 1, viewUp: [0, -1, 0] }, ], activateOnMount: false, + onAxisChange: () => {}, }; diff --git a/Sources/layouts/Layout3D.js b/Sources/layouts/Layout3D.js index 41bb817d..be1836ad 100644 --- a/Sources/layouts/Layout3D.js +++ b/Sources/layouts/Layout3D.js @@ -59,6 +59,10 @@ export default class Layout3D extends React.Component { this.props.proxyManager.setActiveView(this.view); } + resize() { + this.view.resize(); + } + render() { return (
{ + const layout = this.config.layouts[layoutName]; + + // set initial active view, if any + const viewName = Object.keys(layout.views).find( + (name) => !!layout.views[name].defaultActive + ); + if (viewName) { + this.layoutInitActiveView[layoutName] = viewName; + } + + // process stacks + const gridY = layout.grid[1]; + const tmpStacks = {}; + Object.keys(layout.views).forEach((name) => { + const view = layout.views[name]; + const [x, y] = view.cell; + const stackId = x * gridY + y; + tmpStacks[stackId] = tmpStacks[stackId] || []; + tmpStacks[stackId].push(name); + }); + + this.layoutStacks[layoutName] = {}; + Object.keys(tmpStacks).forEach((stackId) => { + const stack = tmpStacks[stackId]; + if (stack.length > 1) { + const id = `StackActive_${layoutName}_${stackId}`; + stack.forEach((view) => { + // set stack id for this view in this layout + this.layoutStacks[layoutName][view] = id; + // set view as visible in stack if possible + if (layout.views[view].defaultVisible || !this.state[id]) { + this.state[id] = view; + } + }); + } + }); + }); + } + + componentDidUpdate() { + // resize all so views fill their containing space + Object.values(this.viewRefs) + .filter((v) => !!v) + .forEach((view) => view.resize()); + + // activate initial view, if specified + if (this.props.layout in this.layoutInitActiveView) { + let viewName = this.layoutInitActiveView[this.props.layout]; + + // if initial view is shadowed by another view in same stack, + // then activate the other view. + const stackId = this.layoutStacks[this.props.layout][viewName]; + if (stackId) { + // if active view in stack is current view, then this is a no-op. + viewName = this.state[stackId]; + } + + this.viewRefs[viewName].activateView(); + } + } + + setStackView(newView) { + const stackId = this.layoutStacks[this.props.layout][newView]; + if (stackId in this.state) { + this.setState({ [stackId]: newView }, () => { + this.viewRefs[newView].activateView(); + }); + } + } + + setViewRef(name, ref) { + this.viewRefs[name] = ref; + } + + render() { + const layout = this.config.layouts[this.props.layout]; + + const [xRows, yRows] = layout.grid; + const rootStyles = { + gridTemplate: `repeat(${xRows}, 1fr) / repeat(${yRows}, 1fr)`, + }; + + const views = Object.keys(this.config.views).map((viewName) => { + const viewSpec = this.config.views[viewName]; + const props = Object.assign({}, viewSpec.props); + const cellStyle = {}; + + let visible = viewName in layout.views; + if (visible) { + const layoutSpec = layout.views[viewName]; + + if (viewName in this.layoutStacks[this.props.layout]) { + const stackId = this.layoutStacks[this.props.layout][viewName]; + visible = this.state[stackId] === viewName; + } + + const [xCell, yCell] = layoutSpec.cell; + Object.assign(cellStyle, { + gridArea: `${xCell} / ${yCell} / ${xCell + 1} / ${yCell + 1}`, + }); + + Object.assign(props, layoutSpec.propOverrides); + + // set our custom stack changing function + if (viewSpec.stackChangeFunc && props[viewSpec.stackChangeFunc]) { + const func = props[viewSpec.stackChangeFunc]; + props[viewSpec.stackChangeFunc] = (...args) => { + this.setStackView(func(...args)); + }; + } + } + + return ( +
+ this.setViewRef(viewName, r)} + proxyManager={this.props.proxyManager} + {...props} + /> +
+ ); + }); + + return ( +
+ {views} +
+ ); + } +} + +LayoutGrid.propTypes = { + initialConfig: PropTypes.object.isRequired, + layout: PropTypes.string.isRequired, + proxyManager: PropTypes.object, + className: PropTypes.string, +}; + +LayoutGrid.defaultProps = { + proxyManager: null, + className: '', +}; diff --git a/Sources/layouts/LayoutQuad.js b/Sources/layouts/LayoutQuad.js deleted file mode 100644 index 4c4efb63..00000000 --- a/Sources/layouts/LayoutQuad.js +++ /dev/null @@ -1,59 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import Layout2D from './Layout2D'; -import Layout3D from './Layout3D'; -import style from './vtk-layout.mcss'; - -export default function LayoutQuad(props) { - return ( -
-
-
- -
-
- -
-
-
- - -
-
- ); -} - -LayoutQuad.propTypes = { - proxyManager: PropTypes.object, - className: PropTypes.string, -}; - -LayoutQuad.defaultProps = { - proxyManager: null, - className: '', -}; diff --git a/Sources/layouts/LayoutSplit.js b/Sources/layouts/LayoutSplit.js deleted file mode 100644 index 4d71b077..00000000 --- a/Sources/layouts/LayoutSplit.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import Layout2D from './Layout2D'; -import Layout3D from './Layout3D'; -import style from './vtk-layout.mcss'; - -export default function LayoutSplit(props) { - return ( -
-
-
- -
-
- -
-
-
- ); -} - -LayoutSplit.propTypes = { - proxyManager: PropTypes.object, - className: PropTypes.string, -}; - -LayoutSplit.defaultProps = { - proxyManager: null, - className: '', -}; diff --git a/Sources/layouts/index.js b/Sources/layouts/index.js index 48060428..a6a80d65 100644 --- a/Sources/layouts/index.js +++ b/Sources/layouts/index.js @@ -1,11 +1,9 @@ import Layout2D from './Layout2D'; import Layout3D from './Layout3D'; -import LayoutSplit from './LayoutSplit'; -import LayoutQuad from './LayoutQuad'; +import LayoutGrid from './LayoutGrid'; export default { Layout2D, Layout3D, - LayoutSplit, - LayoutQuad, + LayoutGrid, }; diff --git a/Sources/layouts/vtk-layout.mcss b/Sources/layouts/vtk-layout.mcss index 928fccda..32244fbc 100644 --- a/Sources/layouts/vtk-layout.mcss +++ b/Sources/layouts/vtk-layout.mcss @@ -1,12 +1,11 @@ -.quadRoot { +.layoutRoot { position: relative; - display: flex; - flex-direction: column; - align-items: stretch; height: 100%; + display: grid; } .splitRow { + min-height: 0; position: relative; flex: 1; display: flex; @@ -14,6 +13,16 @@ align-items: stretch; } +.viewContainer { + min-width: 0; + min-height: 0; + display: flex; +} + +.hiddenViewContainer { + display: none; +} + .view { flex: 1; min-width: 10px; @@ -22,7 +31,6 @@ .renderWindowContainer { flex: 1; - height: 100%; display: flex; flex-direction: column; align-items: stretch; diff --git a/Sources/pv-explorer.mcss b/Sources/pv-explorer.mcss index 08475733..4427b23d 100644 --- a/Sources/pv-explorer.mcss +++ b/Sources/pv-explorer.mcss @@ -89,6 +89,7 @@ .workspace { overflow: hidden; height: calc(100vh - 64px); + max-height: calc(100vh - 64px); } .progressContainer {