/** * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose */ import { ParamDefinition as PD } from '../mol-util/param-definition'; import { WebGLContext } from '../mol-gl/webgl/context'; import { ColorTheme } from '../mol-theme/color'; import { SizeTheme } from '../mol-theme/size'; import { ThemeRegistryContext, Theme } from '../mol-theme/theme'; import { Subject } from 'rxjs'; import { GraphicsRenderObject } from '../mol-gl/render-object'; import { Task } from '../mol-task'; import { PickingId } from '../mol-geo/geometry/picking'; import { MarkerAction, MarkerActions } from '../mol-util/marker-action'; import { Loci as ModelLoci, EmptyLoci, isEmptyLoci } from '../mol-model/loci'; import { Overpaint } from '../mol-theme/overpaint'; import { Transparency } from '../mol-theme/transparency'; import { Mat4 } from '../mol-math/linear-algebra'; import { getQualityProps } from './util'; import { BaseGeometry } from '../mol-geo/geometry/base'; import { Visual } from './visual'; import { CustomProperty } from '../mol-model-props/common/custom-property'; // export interface RepresentationProps { // visuals?: string[] // } export type RepresentationProps = { [k: string]: any } export interface RepresentationContext { readonly webgl?: WebGLContext readonly colorThemeRegistry: ColorTheme.Registry readonly sizeThemeRegistry: SizeTheme.Registry } export type RepresentationParamsGetter = (ctx: ThemeRegistryContext, data: D) => P export type RepresentationFactory = (ctx: RepresentationContext, getParams: RepresentationParamsGetter) => Representation // export interface RepresentationProvider { readonly name: Id, readonly label: string readonly description: string readonly factory: RepresentationFactory readonly getParams: RepresentationParamsGetter readonly defaultValues: PD.Values

readonly defaultColorTheme: { name: string, props?: {} } readonly defaultSizeTheme: { name: string, props?: {} } readonly isApplicable: (data: D) => boolean readonly ensureCustomProperties?: { attach: (ctx: CustomProperty.Context, data: D) => Promise, detach: (ctx: CustomProperty.Context, data: D) => void } } export namespace RepresentationProvider { export type ParamValues> = R extends RepresentationProvider ? PD.Values

: never; export function getDetaultParams, D>(r: R, ctx: ThemeRegistryContext, data: D) { return PD.getDefaultValues(r.getParams(ctx, data)); } } export type AnyRepresentationProvider = RepresentationProvider export const EmptyRepresentationProvider = { label: '', description: '', factory: () => Representation.Empty, getParams: () => ({}), defaultValues: {} } function getTypes(list: { name: string, provider: RepresentationProvider }[]) { return list.map(e => [e.name, e.provider.label] as [string, string]); } export class RepresentationRegistry { private _list: { name: string, provider: RepresentationProvider }[] = [] private _map = new Map>() private _name = new Map, string>() get default() { return this._list[0]; } get types(): [string, string][] { return getTypes(this._list) } constructor() {}; add

(provider: RepresentationProvider) { if (this._map.has(provider.name)) { throw new Error(`${provider.name} already registered.`); } this._list.push({ name: provider.name, provider }) this._map.set(provider.name, provider) this._name.set(provider, provider.name) } getName(provider: RepresentationProvider): string { if (!this._name.has(provider)) throw new Error(`'${provider.label}' is not a registered represenatation provider.`); return this._name.get(provider)!; } remove(provider: RepresentationProvider) { const name = provider.name; this._list.splice(this._list.findIndex(e => e.name === name), 1) const p = this._map.get(name); if (p) { this._map.delete(name); this._name.delete(p); } } get

(name: string): RepresentationProvider { return this._map.get(name) || EmptyRepresentationProvider as unknown as RepresentationProvider } get list() { return this._list } getApplicableList(data: D) { return this._list.filter(e => e.provider.isApplicable(data)); } getApplicableTypes(data: D) { return getTypes(this.getApplicableList(data)) } } // export { Representation } interface Representation { readonly label: string readonly updated: Subject /** Number of addressable groups in all visuals of the representation */ readonly groupCount: number readonly renderObjects: ReadonlyArray readonly props: Readonly> readonly params: Readonly

readonly state: Readonly readonly theme: Readonly createOrUpdate: (props?: Partial>, data?: D) => Task setState: (state: Partial) => void setTheme: (theme: Theme) => void /** If no pickingId is given, returns a Loci for the whole representation */ getLoci: (pickingId?: PickingId) => ModelLoci mark: (loci: ModelLoci, action: MarkerAction) => boolean destroy: () => void } namespace Representation { export interface Loci { loci: T, repr?: Representation.Any } export namespace Loci { export function areEqual(a: Loci, b: Loci) { return a.repr === b.repr && ModelLoci.areEqual(a.loci, b.loci); } export const Empty: Loci = { loci: EmptyLoci }; } export interface State { /** Controls if the representation's renderobjects are rendered or not */ visible: boolean /** A factor applied to alpha value of the representation's renderobjects */ alphaFactor: number /** Controls if the representation's renderobjects are pickable or not */ pickable: boolean /** Overpaint applied to the representation's renderobjects */ overpaint: Overpaint /** Per group transparency applied to the representation's renderobjects */ transparency: Transparency /** Controls if the representation's renderobjects are synced automatically with GPU or not */ syncManually: boolean /** A transformation applied to the representation's renderobjects */ transform: Mat4 /** Bit mask of allowed marker actions */ markerActions: MarkerActions } export function createState(): State { return { visible: true, alphaFactor: 1, pickable: true, syncManually: false, transform: Mat4.identity(), overpaint: Overpaint.Empty, transparency: Transparency.Empty, markerActions: MarkerActions.All } } export function updateState(state: State, update: Partial) { if (update.visible !== undefined) state.visible = update.visible if (update.alphaFactor !== undefined) state.alphaFactor = update.alphaFactor if (update.pickable !== undefined) state.pickable = update.pickable if (update.overpaint !== undefined) state.overpaint = update.overpaint if (update.transparency !== undefined) state.transparency = update.transparency if (update.syncManually !== undefined) state.syncManually = update.syncManually if (update.transform !== undefined) Mat4.copy(state.transform, update.transform) if (update.markerActions !== undefined) state.markerActions = update.markerActions } export interface StateBuilder { create(): S update(state: S, update: Partial): void } export const StateBuilder: StateBuilder = { create: createState, update: updateState } export type Any = Representation export const Empty: Any = { label: '', groupCount: 0, renderObjects: [], props: {}, params: {}, updated: new Subject(), state: createState(), theme: Theme.createEmpty(), createOrUpdate: () => Task.constant('', undefined), setState: () => {}, setTheme: () => {}, getLoci: () => EmptyLoci, mark: () => false, destroy: () => {} } export type Def = { [k: string]: RepresentationFactory } export function createMulti(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter, stateBuilder: StateBuilder, reprDefs: Def): Representation { let version = 0 const updated = new Subject() const currentState = stateBuilder.create() let currentTheme = Theme.createEmpty() let currentParams: P let currentProps: PD.Values

let currentData: D const reprMap: { [k: number]: string } = {} const reprList: Representation[] = Object.keys(reprDefs).map((name, i) => { reprMap[i] = name const repr = reprDefs[name](ctx, getParams) repr.setState(currentState) return repr }) return { label, updated, get groupCount() { let groupCount = 0 if (currentProps) { const { visuals } = currentProps for (let i = 0, il = reprList.length; i < il; ++i) { if (!visuals || visuals.includes(reprMap[i])) { groupCount += reprList[i].groupCount } } } return groupCount }, get renderObjects() { const renderObjects: GraphicsRenderObject[] = [] if (currentProps) { const { visuals } = currentProps for (let i = 0, il = reprList.length; i < il; ++i) { if (!visuals || visuals.includes(reprMap[i])) { renderObjects.push(...reprList[i].renderObjects) } } } return renderObjects }, get props() { return currentProps }, get params() { return currentParams }, createOrUpdate: (props: Partial

= {}, data?: D) => { if (data && data !== currentData) { currentParams = getParams(ctx, data) currentData = data if (!currentProps) currentProps = PD.getDefaultValues(currentParams) as P } const qualityProps = getQualityProps(Object.assign({}, currentProps, props), currentData) Object.assign(currentProps, props, qualityProps) const { visuals } = currentProps return Task.create(`Creating or updating '${label}' representation`, async runtime => { for (let i = 0, il = reprList.length; i < il; ++i) { if (!visuals || visuals.includes(reprMap[i])) { await reprList[i].createOrUpdate(currentProps, currentData).runInContext(runtime) } } updated.next(version++) }) }, get state() { return currentState }, get theme() { return currentTheme }, getLoci: (pickingId?: PickingId) => { if (pickingId === undefined) { return reprList.length > 0 ? reprList[0].getLoci() : EmptyLoci } for (let i = 0, il = reprList.length; i < il; ++i) { const loci = reprList[i].getLoci(pickingId) if (!isEmptyLoci(loci)) return loci } return EmptyLoci }, mark: (loci: ModelLoci, action: MarkerAction) => { let marked = false for (let i = 0, il = reprList.length; i < il; ++i) { marked = reprList[i].mark(loci, action) || marked } return marked }, setState: (state: Partial) => { stateBuilder.update(currentState, state) for (let i = 0, il = reprList.length; i < il; ++i) { reprList[i].setState(currentState) } }, setTheme: (theme: Theme) => { for (let i = 0, il = reprList.length; i < il; ++i) { reprList[i].setTheme(theme) } }, destroy() { for (let i = 0, il = reprList.length; i < il; ++i) { reprList[i].destroy() } } } } export function fromRenderObject(label: string, renderObject: GraphicsRenderObject): Representation { let version = 0 const updated = new Subject() const currentState = Representation.createState() const currentTheme = Theme.createEmpty() const currentParams = PD.clone(BaseGeometry.Params) const currentProps = PD.getDefaultValues(BaseGeometry.Params) return { label, updated, get groupCount() { return renderObject.values.uGroupCount.ref.value }, get renderObjects() { return [renderObject] }, get props() { return currentProps }, get params() { return currentParams }, createOrUpdate: (props: Partial> = {}) => { const qualityProps = getQualityProps(Object.assign({}, currentProps, props)) Object.assign(currentProps, props, qualityProps) return Task.create(`Updating '${label}' representation`, async runtime => { // TODO updated.next(version++) }) }, get state() { return currentState }, get theme() { return currentTheme }, getLoci: () => { // TODO return EmptyLoci }, mark: (loci: ModelLoci, action: MarkerAction) => { // TODO return false }, setState: (state: Partial) => { if (state.visible !== undefined) Visual.setVisibility(renderObject, state.visible) if (state.alphaFactor !== undefined) Visual.setAlphaFactor(renderObject, state.alphaFactor) if (state.pickable !== undefined) Visual.setPickable(renderObject, state.pickable) if (state.overpaint !== undefined) { // TODO } if (state.transparency !== undefined) { // TODO } if (state.transform !== undefined) Visual.setTransform(renderObject, state.transform) Representation.updateState(currentState, state) }, setTheme: () => { }, destroy() { } } } }