diff --git a/src/mol-app/ui/controls/common.tsx b/src/mol-app/ui/controls/common.tsx index 695273e18cee4b2738f8e563f7aeac75d74ab2e4..90125044172357c28a8def3b58c8290deecf9024 100644 --- a/src/mol-app/ui/controls/common.tsx +++ b/src/mol-app/ui/controls/common.tsx @@ -88,7 +88,7 @@ export function isEnter(e: React.KeyboardEvent<HTMLInputElement>) { export function TextBoxGroup(props: { value: string, onChange: (v: string) => void, - placeholder?:string, + placeholder?: string, label: string, onEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void title?: string diff --git a/src/mol-app/ui/entity/tree.tsx b/src/mol-app/ui/entity/tree.tsx index 1d9afd1d9624bc0e2dacf49166b9ff24e24777a0..cc889f7ff027455d15a18cf6e455d8d1c7d920b0 100644 --- a/src/mol-app/ui/entity/tree.tsx +++ b/src/mol-app/ui/entity/tree.tsx @@ -13,7 +13,7 @@ import { View } from '../view'; import { EntityTreeController } from '../../controller/entity/tree'; import { Controller } from '../../controller/controller'; import { AnyEntity, RootEntity } from 'mol-view/state/entity'; -import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill } from 'mol-view/state/transform'; +import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill, StructureCenter } from 'mol-view/state/transform'; function getTransforms(entity: AnyEntity): AnyTransform[] { const transforms: AnyTransform[] = [] @@ -40,7 +40,7 @@ function getTransforms(entity: AnyEntity): AnyTransform[] { transforms.push(ModelToStructure) break; case 'structure': - transforms.push(StructureToSpacefill) + transforms.push(StructureToSpacefill, StructureCenter) break; case 'spacefill': transforms.push(SpacefillUpdate) diff --git a/src/mol-app/ui/transform/list.tsx b/src/mol-app/ui/transform/list.tsx index b72e8e6be91a76d34e05e310a8076377c5fcaf87..dd3840ac610c2642ddf46a729794c886269df04d 100644 --- a/src/mol-app/ui/transform/list.tsx +++ b/src/mol-app/ui/transform/list.tsx @@ -17,6 +17,7 @@ import { Spacefill } from './spacefill'; import { AnyEntity } from 'mol-view/state/entity'; import { FileLoader } from './file-loader'; import { ModelToStructure } from './model'; +import { StructureCenter } from './structure'; function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) { switch (transform.kind) { @@ -24,6 +25,8 @@ function getTransformComponent(controller: TransformListController, entity: AnyE return <FileLoader controller={controller} ctx={controller.context.stage.ctx}></FileLoader> case 'model-to-structure': return <ModelToStructure controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></ModelToStructure> + case 'structure-center': + return <StructureCenter controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></StructureCenter> case 'spacefill-update': return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill> } diff --git a/src/mol-app/ui/transform/spacefill.tsx b/src/mol-app/ui/transform/spacefill.tsx index 5ee8d295991ba9f2b494138531c63b48be78f76c..315f61d31244942541896551336b564ad0582675 100644 --- a/src/mol-app/ui/transform/spacefill.tsx +++ b/src/mol-app/ui/transform/spacefill.tsx @@ -11,11 +11,14 @@ import * as React from 'react' import { View } from '../view'; import { Controller } from '../../controller/controller'; +import { Toggle } from '../controls/common'; import { SpacefillEntity } from 'mol-view/state/entity'; import { SpacefillUpdate } from 'mol-view/state/transform' import { StateContext } from 'mol-view/state/context'; import { ColorTheme } from 'mol-geo/theme'; import { Color, ColorNames } from 'mol-util/color'; +import { Query, Queries as Q } from 'mol-model/structure'; +import { Slider } from '../controls/slider'; export const ColorThemeInfo = { 'atom-index': {}, @@ -31,6 +34,8 @@ interface SpacefillState { detail: number colorTheme: ColorTheme colorValue: Color + visible: boolean + alpha: number } export class Spacefill extends View<Controller<any>, SpacefillState, { transform: SpacefillUpdate, entity: SpacefillEntity, ctx: StateContext }> { @@ -38,7 +43,9 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform doubleSided: true, detail: 2, colorTheme: { name: 'element-symbol' } as ColorTheme, - colorValue: 0x000000 + colorValue: 0x000000, + visible: true, + alpha: 1 } update(state?: Partial<SpacefillState>) { @@ -135,6 +142,27 @@ export class Spacefill extends View<Controller<any>, SpacefillState, { transform </select> </div> </div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Toggle + value={this.state.visible} + label='Visibility' + onChange={value => this.update({ visible: value })} + /> + </div> + </div> + {/* <div className='molstar-control-row molstar-options-group'> + <div> + <Slider + value={this.state.alpha} + label='Opacity' + min={0} + max={1} + step={0.01} + onChange={value => this.update({ alpha: value })} + /> + </div> + </div> */} </div> </div> </div> diff --git a/src/mol-app/ui/transform/structure.tsx b/src/mol-app/ui/transform/structure.tsx new file mode 100644 index 0000000000000000000000000000000000000000..59871cd21908428181550e05f64764959ae70ffd --- /dev/null +++ b/src/mol-app/ui/transform/structure.tsx @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { Button } from '../controls/common'; +import { StructureEntity } from 'mol-view/state/entity'; +import { StructureCenter as StructureCenterTransform } from 'mol-view/state/transform' +import { StateContext } from 'mol-view/state/context'; + +export const ColorThemeInfo = { + 'atom-index': {}, + 'chain-id': {}, + 'element-symbol': {}, + 'instance-index': {}, + 'uniform': {} +} +export type ColorThemeInfo = keyof typeof ColorThemeInfo + +export class StructureCenter extends View<Controller<any>, {}, { transform: StructureCenterTransform, entity: StructureEntity, ctx: StateContext }> { + center() { + const { ctx, entity, transform } = this.props + transform.apply(ctx, entity) + } + + render() { + const { transform } = this.props + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={() => {}} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </div> + <div className='molstar-panel-body'> + <div> + <div className='molstar-control-row molstar-options-group'> + <div> + <Button onClick={value => this.center()}> + Center + </Button> + </div> + </div> + </div> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 363e1771ed7ff49ddb907967511e6123f4769410..0c59898054c003624fe018aebdc1c0930d503f44 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/src/mol-geo/representation/structure/index.ts @@ -9,8 +9,7 @@ import { Structure, StructureSymmetry, Unit } from 'mol-model/structure'; import { Task } from 'mol-task' import { RenderObject } from 'mol-gl/scene'; import { Representation, RepresentationProps } from '..'; -// import { Mat4, EPSILON } from 'mol-math/linear-algebra'; - +import { ColorTheme } from '../../theme'; export interface UnitsRepresentation<P> { renderObjects: ReadonlyArray<RenderObject> @@ -29,14 +28,29 @@ interface GroupRepresentation<T> { group: Unit.SymmetryGroup } -export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> { +export const DefaultStructureProps = { + colorTheme: { name: 'instance-index' } as ColorTheme, + alpha: 1, + visible: true, + doubleSided: false +} +export type StructureProps = Partial<typeof DefaultStructureProps> + +export function StructureRepresentation<P extends StructureProps>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> { const renderObjects: RenderObject[] = [] const groupReprs: GroupRepresentation<P>[] = [] + // let currentProps: typeof DefaultStructureProps return { renderObjects, create(structure: Structure, props: P = {} as P) { + // currentProps = Object.assign({}, DefaultStructureProps, props) + return Task.create('StructureRepresentation.create', async ctx => { + // const { query } = currentProps + // const qs = await query(structure).runAsChild(ctx) + // const subStructure = Selection.unionStructure(qs) + const groups = StructureSymmetry.getTransformGroups(structure); for (let i = 0; i < groups.length; i++) { const group = groups[i]; @@ -49,8 +63,8 @@ export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P }, update(props: P) { return Task.create('StructureRepresentation.update', async ctx => { - // TODO check model.id, conformation.id, unit.id, elementGroup(.hashCode/.areEqual) renderObjects.length = 0 // clear + for (let i = 0, il = groupReprs.length; i < il; ++i) { const groupRepr = groupReprs[i] const { repr, group } = groupRepr diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 7f2b7ab722ffe1911b2cb70cc8de332bb98fb8b1..0311cff73dbd20ff5b39c9f31edce1ae795bcea9 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/src/mol-geo/representation/structure/spacefill.ts @@ -11,20 +11,16 @@ import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/s // import { createColorTexture } from 'mol-gl/util'; import { Vec3, Mat4 } from 'mol-math/linear-algebra' import { Unit, Element, Queries } from 'mol-model/structure'; -import { UnitsRepresentation } from './index'; +import { UnitsRepresentation, DefaultStructureProps } from './index'; import { Task } from 'mol-task' import { MeshBuilder } from '../../shape/mesh-builder'; import { createTransforms, createColors } from './utils'; -import { ColorTheme } from '../../theme'; import VertexMap from '../../shape/vertex-map'; import { icosahedronVertexCount } from '../../primitive/icosahedron'; export const DefaultSpacefillProps = { + ...DefaultStructureProps, detail: 0, - colorTheme: { name: 'instance-index' } as ColorTheme, - alpha: 1, - visible: true, - doubleSided: false } export type SpacefillProps = Partial<typeof DefaultSpacefillProps> @@ -74,10 +70,13 @@ function createSpacefillMesh(unit: Unit, detail: number) { export default function Spacefill(): UnitsRepresentation<SpacefillProps> { const renderObjects: RenderObject[] = [] let spheres: MeshRenderObject + let currentProps: typeof DefaultSpacefillProps return { renderObjects, create(group: Unit.SymmetryGroup, props: SpacefillProps = {}) { + currentProps = Object.assign({}, DefaultSpacefillProps, props) + return Task.create('Spacefill.create', async ctx => { renderObjects.length = 0 // clear @@ -116,10 +115,20 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> { }) }, update(props: SpacefillProps) { + const newProps = Object.assign({}, currentProps, props) + return Task.create('Spacefill.update', async ctx => { if (!spheres) return false + if (props.detail !== currentProps.detail) return false + if (props.colorTheme !== currentProps.colorTheme) return false + if (props.detail !== currentProps.detail) return false + + spheres.props.alpha = newProps.alpha + spheres.props.visible = newProps.visible + spheres.props.doubleSided = newProps.doubleSided - return false + currentProps = newProps + return true }) } } diff --git a/src/mol-view/controls/trackball.ts b/src/mol-view/controls/trackball.ts index d55e69035d99cd67adcd079f40fd7397eb1a9c10..4f05c84465127e974a1c3e5ba2a2dffb23291423 100644 --- a/src/mol-view/controls/trackball.ts +++ b/src/mol-view/controls/trackball.ts @@ -15,6 +15,7 @@ import InputObserver, { DragInput, WheelInput, ButtonsFlag, PinchInput } from 'm export const DefaultTrackballControlsProps = { noScroll: true, + target: [0, 0, 0] as Vec3, rotateSpeed: 3.0, zoomSpeed: 4.0, @@ -36,6 +37,7 @@ interface Object { interface TrackballControls { viewport: Viewport + target: Vec3 dynamicDampingFactor: number rotateSpeed: number @@ -52,6 +54,7 @@ namespace TrackballControls { const p = { ...DefaultTrackballControlsProps, ...props } const viewport: Viewport = { x: 0, y: 0, width: 0, height: 0 } + const target: Vec3 = Vec3.clone(p.target) let { rotateSpeed, zoomSpeed, panSpeed } = p let { staticMoving, dynamicDampingFactor } = p @@ -64,7 +67,6 @@ namespace TrackballControls { const pinchSub = input.pinch.subscribe(onPinch) // For internal use - const target = Vec3.zero() const lastPosition = Vec3.zero() const _eye = Vec3.zero() @@ -297,6 +299,7 @@ namespace TrackballControls { return { viewport, + target, get dynamicDampingFactor() { return dynamicDampingFactor }, set dynamicDampingFactor(value: number ) { dynamicDampingFactor = value }, diff --git a/src/mol-view/state/entity.ts b/src/mol-view/state/entity.ts index b77e5b82215d3f1823fd4821b379ca0561feb945..2b28f71d3d8cda0dc347f3d0501d3f4311d41ee9 100644 --- a/src/mol-view/state/entity.ts +++ b/src/mol-view/state/entity.ts @@ -31,7 +31,9 @@ export namespace StateEntity { } export type AnyEntity = StateEntity<any, any> +export type NullEntity = StateEntity<null, 'null'> +export const NullEntity: NullEntity = { id: -1, kind: 'null', value: null } export const RootEntity: StateEntity<null, 'root'> = { id: 0, kind: 'root', value: null } export interface UrlProps { diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts index 0389fc0646d088a592c9292fb36e813397373aa6..1009263491692e195d9000bae5746faada62324f 100644 --- a/src/mol-view/state/transform.ts +++ b/src/mol-view/state/transform.ts @@ -5,7 +5,7 @@ */ import CIF from 'mol-io/reader/cif' -import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity } from './entity'; +import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity, NullEntity } from './entity'; import { Model, Structure } from 'mol-model/structure'; import { StateContext } from './context'; @@ -80,9 +80,17 @@ export const ModelToStructure: ModelToStructure = StateTransform.create('model', } else { structure = Structure.ofModel(model) } + console.log('center', structure.boundary.sphere.center) return StructureEntity.ofStructure(ctx, structure) }) +export type StructureCenter = StateTransform<StructureEntity, NullEntity, {}> + export const StructureCenter: StructureCenter = StateTransform.create('structure', 'null', 'structure-center', + async function (ctx: StateContext, structureEntity: StructureEntity) { + ctx.viewer.center(structureEntity.value.boundary.sphere.center) + return NullEntity + }) + export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEntity, SpacefillProps> export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill', async function (ctx: StateContext, structureEntity: StructureEntity, props: SpacefillProps = {}) { @@ -90,21 +98,29 @@ export const StructureToSpacefill: StructureToSpacefill = StateTransform.create( await spacefillRepr.create(structureEntity.value, props).run(ctx.log) ctx.viewer.add(spacefillRepr) ctx.viewer.requestDraw() - console.log(ctx.viewer.stats) + console.log(ctx.viewer.stats, props) + // ctx.viewer.input.drag.subscribe(async () => { + // console.log('drag') + // console.time('spacefill update') + // await spacefillRepr.update(props).run(ctx.log) + // console.timeEnd('spacefill update') + // ctx.viewer.add(spacefillRepr) + // ctx.viewer.update() + // ctx.viewer.requestDraw() + // }) return SpacefillEntity.ofRepr(ctx, spacefillRepr) }) -export type SpacefillUpdate = StateTransform<SpacefillEntity, SpacefillEntity, SpacefillProps> -export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'spacefill', 'spacefill-update', +export type SpacefillUpdate = StateTransform<SpacefillEntity, NullEntity, SpacefillProps> +export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'null', 'spacefill-update', async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: SpacefillProps = {}) { - console.log('fopbar') const spacefillRepr = spacefillEntity.value await spacefillRepr.update(props).run(ctx.log) ctx.viewer.add(spacefillRepr) ctx.viewer.update() ctx.viewer.requestDraw() - console.log(ctx.viewer.stats) - return spacefillEntity + console.log(ctx.viewer.stats, props) + return NullEntity }) // composed diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index c4384837d32afff409180b28d77f5267fe8091f4..c39a142a748b1b478023ffb7287cbe6941eabb88 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -20,6 +20,8 @@ import { createContext } from 'mol-gl/webgl/context'; import { Representation } from 'mol-geo/representation'; interface Viewer { + center: (p: Vec3) => void + hide: (repr: Representation<any>) => void show: (repr: Representation<any>) => void @@ -37,6 +39,7 @@ interface Viewer { resetCamera: () => void downloadScreenshot: () => void + input: InputObserver stats: RendererStats dispose: () => void } @@ -110,6 +113,10 @@ namespace Viewer { handleResize() return { + center: (p: Vec3) => { + Vec3.set(controls.target, p[0], p[1], p[2]) + }, + hide: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) if (renderObjectSet) renderObjectSet.forEach(o => o.props.visible = false) @@ -159,6 +166,9 @@ namespace Viewer { }, reprCount, + get input() { + return input + }, get stats() { return renderer.stats },