From 517de96d01da7ece156dcc6ced6075d065b6f54d Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Wed, 28 Nov 2018 16:20:42 -0800
Subject: [PATCH] theme registry refactoring, added .isApplicable method

---
 src/mol-app/component/color-theme.tsx         |  2 +-
 src/mol-geo/geometry/color-data.ts            |  2 +-
 src/mol-geo/geometry/size-data.ts             |  2 +-
 .../rcsb/themes/assembly-symmetry-cluster.ts  |  3 +-
 .../pdbe/structure-quality-report.ts          |  6 +-
 .../custom-props/rcsb/assembly-symmetry.ts    |  3 -
 src/mol-plugin/context.ts                     |  2 +-
 .../state/transforms/representation.ts        | 18 +++---
 src/mol-theme/color.ts                        | 56 +++--------------
 src/mol-theme/color/carbohydrate-symbol.ts    |  3 +-
 src/mol-theme/color/chain-id.ts               |  3 +-
 src/mol-theme/color/cross-link.ts             |  3 +-
 src/mol-theme/color/element-index.ts          |  3 +-
 src/mol-theme/color/element-symbol.ts         |  3 +-
 src/mol-theme/color/molecule-type.ts          |  3 +-
 src/mol-theme/color/polymer-id.ts             |  3 +-
 src/mol-theme/color/polymer-index.ts          |  3 +-
 src/mol-theme/color/residue-name.ts           |  3 +-
 src/mol-theme/color/secondary-structure.ts    |  3 +-
 src/mol-theme/color/sequence-id.ts            |  3 +-
 src/mol-theme/color/shape-group.ts            |  3 +-
 src/mol-theme/color/uniform.ts                |  3 +-
 src/mol-theme/color/unit-index.ts             |  3 +-
 src/mol-theme/size.ts                         | 56 +++--------------
 src/mol-theme/size/physical.ts                |  3 +-
 src/mol-theme/size/uniform.ts                 |  3 +-
 src/mol-theme/theme.ts                        | 61 ++++++++++++++++++-
 27 files changed, 126 insertions(+), 133 deletions(-)

diff --git a/src/mol-app/component/color-theme.tsx b/src/mol-app/component/color-theme.tsx
index e546c25bd..bf3c58078 100644
--- a/src/mol-app/component/color-theme.tsx
+++ b/src/mol-app/component/color-theme.tsx
@@ -9,7 +9,7 @@ import { ColorTheme } from 'mol-theme/color';
 import { Color } from 'mol-util/color';
 
 export interface ColorThemeComponentProps {
-    colorTheme: ColorTheme
+    colorTheme: ColorTheme<any>
 }
 
 export interface ColorThemeComponentState {
diff --git a/src/mol-geo/geometry/color-data.ts b/src/mol-geo/geometry/color-data.ts
index 43c2bd310..1f4fb376a 100644
--- a/src/mol-geo/geometry/color-data.ts
+++ b/src/mol-geo/geometry/color-data.ts
@@ -22,7 +22,7 @@ export type ColorData = {
     dColorType: ValueCell<string>,
 }
 
-export function createColors(locationIt: LocationIterator, colorTheme: ColorTheme, colorData?: ColorData): ColorData {
+export function createColors(locationIt: LocationIterator, colorTheme: ColorTheme<any>, colorData?: ColorData): ColorData {
     switch (getGranularity(locationIt, colorTheme.granularity)) {
         case 'uniform': return createUniformColor(locationIt, colorTheme.color, colorData)
         case 'group': return createGroupColor(locationIt, colorTheme.color, colorData)
diff --git a/src/mol-geo/geometry/size-data.ts b/src/mol-geo/geometry/size-data.ts
index a1651cc4b..6cb0e8b75 100644
--- a/src/mol-geo/geometry/size-data.ts
+++ b/src/mol-geo/geometry/size-data.ts
@@ -21,7 +21,7 @@ export type SizeData = {
     dSizeType: ValueCell<string>,
 }
 
-export function createSizes(locationIt: LocationIterator, sizeTheme: SizeTheme, sizeData?: SizeData): SizeData {
+export function createSizes(locationIt: LocationIterator, sizeTheme: SizeTheme<any>, sizeData?: SizeData): SizeData {
     switch (getGranularity(locationIt, sizeTheme.granularity)) {
         case 'uniform': return createUniformSize(locationIt, sizeTheme.size, sizeData)
         case 'group': return createGroupSize(locationIt, sizeTheme.size, sizeData)
diff --git a/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts b/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts
index 7bfbff51a..d739f926c 100644
--- a/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts
+++ b/src/mol-model-props/rcsb/themes/assembly-symmetry-cluster.ts
@@ -113,5 +113,6 @@ export const AssemblySymmetryClusterColorThemeProvider: ColorTheme.Provider<Asse
     label: 'RCSB Assembly Symmetry Cluster',
     factory: AssemblySymmetryClusterColorTheme,
     getParams: getAssemblySymmetryClusterColorThemeParams,
-    defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams)
+    defaultValues: PD.getDefaultValues(AssemblySymmetryClusterColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models[0].customProperties.has(AssemblySymmetry.Descriptor)
 }
\ No newline at end of file
diff --git a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
index 948edad6c..61a09e969 100644
--- a/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
+++ b/src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
@@ -12,6 +12,7 @@ import { StructureElement } from 'mol-model/structure';
 import { CustomPropertyRegistry } from 'mol-plugin/util/custom-prop-registry';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { PluginBehavior } from '../../../behavior';
+import { ThemeDataContext } from 'mol-theme/theme';
 
 export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: boolean }>({
     name: 'pdbe-structure-quality-report-prop',
@@ -34,13 +35,12 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
             this.ctx.customModelProperties.register(this.provider);
             this.ctx.lociLabels.addProvider(labelPDBeValidation);
 
-            // TODO: support filtering of themes based on the input structure
-            // in this case, it would check structure.models[0].customProperties.has(StructureQualityReport.Descriptor)
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('pdbe-structure-quality-report', {
                 label: 'PDBe Structure Quality Report',
                 factory: StructureQualityReportColorTheme,
                 getParams: () => ({}),
-                defaultValues: {}
+                defaultValues: {},
+                isApplicable: (ctx: ThemeDataContext) => !!ctx.structure && ctx.structure.models[0].customProperties.has(StructureQualityReport.Descriptor)
             })
         }
 
diff --git a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts
index e7c7ce4f5..5fac25309 100644
--- a/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts
+++ b/src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts
@@ -31,9 +31,6 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
         register(): void {
             this.ctx.customModelProperties.register(this.provider);
             this.ctx.lociLabels.addProvider(labelAssemblySymmetryAxes);
-
-            // TODO: support filtering of themes and representations based on the input structure
-            // in this case, it would check structure.models[0].customProperties.has(AssemblySymmetry.Descriptor)
             this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add('rcsb-assembly-symmetry-cluster', AssemblySymmetryClusterColorThemeProvider)
             this.ctx.structureRepresentation.registry.add('rcsb-assembly-symmetry-axes', AssemblySymmetryAxesRepresentationProvider)
         }
diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts
index 588fc0dc6..069805cb0 100644
--- a/src/mol-plugin/context.ts
+++ b/src/mol-plugin/context.ts
@@ -73,7 +73,7 @@ export class PluginContext {
 
     readonly structureRepresentation = {
         registry: new StructureRepresentationRegistry(),
-        themeCtx: { colorThemeRegistry: new ColorTheme.Registry(), sizeThemeRegistry: new SizeTheme.Registry() } as ThemeRegistryContext
+        themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
     }
 
     readonly customModelProperties = new CustomPropertyRegistry();
diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts
index 443148667..942816e5e 100644
--- a/src/mol-plugin/state/transforms/representation.ts
+++ b/src/mol-plugin/state/transforms/representation.ts
@@ -50,21 +50,23 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
     from: SO.Molecule.Structure,
     to: SO.Molecule.Representation3D,
     params: (a, ctx: PluginContext) => {
-        const type = ctx.structureRepresentation.registry.get(ctx.structureRepresentation.registry.default.name);
+        const { registry, themeCtx } = ctx.structureRepresentation
+        const type = registry.get(registry.default.name);
+        const dataCtx = { structure: a.data }
         return ({
             type: PD.Mapped<any>(
-                ctx.structureRepresentation.registry.default.name,
-                ctx.structureRepresentation.registry.types,
-                name => PD.Group<any>(ctx.structureRepresentation.registry.get(name).getParams(ctx.structureRepresentation.themeCtx, a.data))),
+                registry.default.name,
+                registry.types,
+                name => PD.Group<any>(registry.get(name).getParams(themeCtx, a.data))),
             colorTheme: PD.Mapped<any>(
                 type.defaultColorTheme,
-                ctx.structureRepresentation.themeCtx.colorThemeRegistry.types,
-                name => PD.Group<any>(ctx.structureRepresentation.themeCtx.colorThemeRegistry.get(name).getParams({ structure: a.data }))
+                themeCtx.colorThemeRegistry.getApplicableTypes(dataCtx),
+                name => PD.Group<any>(themeCtx.colorThemeRegistry.get(name).getParams(dataCtx))
             ),
             sizeTheme: PD.Mapped<any>(
                 type.defaultSizeTheme,
-                ctx.structureRepresentation.themeCtx.sizeThemeRegistry.types,
-                name => PD.Group<any>(ctx.structureRepresentation.themeCtx.sizeThemeRegistry.get(name).getParams({ structure: a.data }))
+                themeCtx.sizeThemeRegistry.types,
+                name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx))
             )
         })
     }
diff --git a/src/mol-theme/color.ts b/src/mol-theme/color.ts
index 75bbc2eee..ba17219d3 100644
--- a/src/mol-theme/color.ts
+++ b/src/mol-theme/color.ts
@@ -11,7 +11,7 @@ import { CarbohydrateSymbolColorThemeProvider } from './color/carbohydrate-symbo
 import { UniformColorThemeProvider } from './color/uniform';
 import { deepEqual } from 'mol-util';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { ThemeDataContext } from './theme';
+import { ThemeDataContext, ThemeRegistry, ThemeProvider } from './theme';
 import { ChainIdColorThemeProvider } from './color/chain-id';
 import { CrossLinkColorThemeProvider } from './color/cross-link';
 import { ElementIndexColorThemeProvider } from './color/element-index';
@@ -32,7 +32,7 @@ export type LocationColor = (location: Location, isSecondary: boolean) => Color
 export type ColorThemeProps = { [k: string]: any }
 
 export { ColorTheme }
-interface ColorTheme<P extends PD.Params = {}> {
+interface ColorTheme<P extends PD.Params> {
     readonly factory: ColorTheme.Factory<P>
     readonly granularity: ColorType
     readonly color: LocationColor
@@ -47,56 +47,16 @@ namespace ColorTheme {
     const EmptyColor = Color(0xCCCCCC)
     export const Empty: ColorTheme<{}> = { factory: EmptyFactory, granularity: 'uniform', color: () => EmptyColor, props: {} }
 
-    export function areEqual(themeA: ColorTheme, themeB: ColorTheme) {
+    export function areEqual(themeA: ColorTheme<any>, themeB: ColorTheme<any>) {
         return themeA.factory === themeB.factory && deepEqual(themeA.props, themeB.props)
     }
 
-    export interface Provider<P extends PD.Params> {
-        readonly label: string
-        readonly factory: (ctx: ThemeDataContext, props: PD.Values<P>) => ColorTheme<P>
-        readonly getParams: (ctx: ThemeDataContext) => P
-        readonly defaultValues: PD.Values<P>
-    }
-    export const EmptyProvider: Provider<{}> = { label: '', factory: EmptyFactory, getParams: () => ({}), defaultValues: {} }
-
-    export class Registry {
-        private _list: { name: string, provider: Provider<any> }[] = []
-        private _map = new Map<string, Provider<any>>()
-
-        get default() { return this._list[0]; }
-        get types(): [string, string][] {
-            return this._list.map(e => [e.name, e.provider.label] as [string, string]);
-        }
-
-        constructor() {
-            Object.keys(BuiltInColorThemes).forEach(name => {
-                const p = (BuiltInColorThemes as { [k: string]: Provider<any> })[name]
-                this.add(name, p)
-            })
-        }
-
-        add<P extends PD.Params>(name: string, provider: Provider<P>) {
-            this._list.push({ name, provider })
-            this._map.set(name, provider)
-        }
-
-        remove(name: string) {
-            this._list.splice(this._list.findIndex(e => e.name === name))
-            this._map.delete(name)
-        }
-
-        get<P extends PD.Params>(name: string): Provider<P> {
-            return this._map.get(name) || EmptyProvider as unknown as Provider<P>
-        }
-
-        create(name: string, ctx: ThemeDataContext, props = {}) {
-            const provider = this.get(name)
-            return provider.factory(ctx, { ...PD.getDefaultValues(provider.getParams(ctx)), ...props })
-        }
+    export interface Provider<P extends PD.Params> extends ThemeProvider<ColorTheme<P>, P> { }
+    export const EmptyProvider: Provider<{}> = { label: '', factory: EmptyFactory, getParams: () => ({}), defaultValues: {}, isApplicable: () => true }
 
-        get list() {
-            return this._list
-        }
+    export type Registry = ThemeRegistry<ColorTheme<any>>
+    export function createRegistry() {
+        return new ThemeRegistry(BuiltInColorThemes as { [k: string]: Provider<any> }, EmptyProvider)
     }
 }
 
diff --git a/src/mol-theme/color/carbohydrate-symbol.ts b/src/mol-theme/color/carbohydrate-symbol.ts
index 550c8f35d..72c7382c1 100644
--- a/src/mol-theme/color/carbohydrate-symbol.ts
+++ b/src/mol-theme/color/carbohydrate-symbol.ts
@@ -78,5 +78,6 @@ export const CarbohydrateSymbolColorThemeProvider: ColorTheme.Provider<Carbohydr
     label: 'Carbohydrate Symbol',
     factory: CarbohydrateSymbolColorTheme,
     getParams: getCarbohydrateSymbolColorThemeParams,
-    defaultValues: PD.getDefaultValues(CarbohydrateSymbolColorThemeParams)
+    defaultValues: PD.getDefaultValues(CarbohydrateSymbolColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/chain-id.ts b/src/mol-theme/color/chain-id.ts
index eb37bf31c..c89d06002 100644
--- a/src/mol-theme/color/chain-id.ts
+++ b/src/mol-theme/color/chain-id.ts
@@ -95,5 +95,6 @@ export const ChainIdColorThemeProvider: ColorTheme.Provider<ChainIdColorThemePar
     label: 'Chain Id',
     factory: ChainIdColorTheme,
     getParams: getChainIdColorThemeParams,
-    defaultValues: PD.getDefaultValues(ChainIdColorThemeParams)
+    defaultValues: PD.getDefaultValues(ChainIdColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/cross-link.ts b/src/mol-theme/color/cross-link.ts
index bfe6cc0ac..d3cdf4b80 100644
--- a/src/mol-theme/color/cross-link.ts
+++ b/src/mol-theme/color/cross-link.ts
@@ -72,5 +72,6 @@ export const CrossLinkColorThemeProvider: ColorTheme.Provider<CrossLinkColorThem
     label: 'Cross Link',
     factory: CrossLinkColorTheme,
     getParams: getCrossLinkColorThemeParams,
-    defaultValues: PD.getDefaultValues(CrossLinkColorThemeParams)
+    defaultValues: PD.getDefaultValues(CrossLinkColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/element-index.ts b/src/mol-theme/color/element-index.ts
index c650f6c99..3636026ab 100644
--- a/src/mol-theme/color/element-index.ts
+++ b/src/mol-theme/color/element-index.ts
@@ -72,5 +72,6 @@ export const ElementIndexColorThemeProvider: ColorTheme.Provider<ElementIndexCol
     label: 'Element Index',
     factory: ElementIndexColorTheme,
     getParams: getElementIndexColorThemeParams,
-    defaultValues: PD.getDefaultValues(ElementIndexColorThemeParams)
+    defaultValues: PD.getDefaultValues(ElementIndexColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/element-symbol.ts b/src/mol-theme/color/element-symbol.ts
index bc15c9f23..28055bab2 100644
--- a/src/mol-theme/color/element-symbol.ts
+++ b/src/mol-theme/color/element-symbol.ts
@@ -64,5 +64,6 @@ export const ElementSymbolColorThemeProvider: ColorTheme.Provider<ElementSymbolC
     label: 'Element Symbol',
     factory: ElementSymbolColorTheme,
     getParams: getElementSymbolColorThemeParams,
-    defaultValues: PD.getDefaultValues(ElementSymbolColorThemeParams)
+    defaultValues: PD.getDefaultValues(ElementSymbolColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/molecule-type.ts b/src/mol-theme/color/molecule-type.ts
index b91c557af..ad643930c 100644
--- a/src/mol-theme/color/molecule-type.ts
+++ b/src/mol-theme/color/molecule-type.ts
@@ -73,5 +73,6 @@ export const MoleculeTypeColorThemeProvider: ColorTheme.Provider<MoleculeTypeCol
     label: 'Molecule Type',
     factory: MoleculeTypeColorTheme,
     getParams: getMoleculeTypeColorThemeParams,
-    defaultValues: PD.getDefaultValues(MoleculeTypeColorThemeParams)
+    defaultValues: PD.getDefaultValues(MoleculeTypeColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/polymer-id.ts b/src/mol-theme/color/polymer-id.ts
index 41b92030a..87c1c75ae 100644
--- a/src/mol-theme/color/polymer-id.ts
+++ b/src/mol-theme/color/polymer-id.ts
@@ -102,5 +102,6 @@ export const PolymerIdColorThemeProvider: ColorTheme.Provider<PolymerIdColorThem
     label: 'Polymer Id',
     factory: PolymerIdColorTheme,
     getParams: getPolymerIdColorThemeParams,
-    defaultValues: PD.getDefaultValues(PolymerIdColorThemeParams)
+    defaultValues: PD.getDefaultValues(PolymerIdColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/polymer-index.ts b/src/mol-theme/color/polymer-index.ts
index a51257fc8..d13fe8d17 100644
--- a/src/mol-theme/color/polymer-index.ts
+++ b/src/mol-theme/color/polymer-index.ts
@@ -70,5 +70,6 @@ export const PolymerIndexColorThemeProvider: ColorTheme.Provider<PolymerIndexCol
     label: 'Polymer Index',
     factory: PolymerIndexColorTheme,
     getParams: getPolymerIndexColorThemeParams,
-    defaultValues: PD.getDefaultValues(PolymerIndexColorThemeParams)
+    defaultValues: PD.getDefaultValues(PolymerIndexColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/residue-name.ts b/src/mol-theme/color/residue-name.ts
index 682ae8a4b..999bf2a9b 100644
--- a/src/mol-theme/color/residue-name.ts
+++ b/src/mol-theme/color/residue-name.ts
@@ -129,5 +129,6 @@ export const ResidueNameColorThemeProvider: ColorTheme.Provider<ResidueNameColor
     label: 'Residue Name',
     factory: ResidueNameColorTheme,
     getParams: getResidueNameColorThemeParams,
-    defaultValues: PD.getDefaultValues(ResidueNameColorThemeParams)
+    defaultValues: PD.getDefaultValues(ResidueNameColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/secondary-structure.ts b/src/mol-theme/color/secondary-structure.ts
index 6f2d094fe..384c5fb0b 100644
--- a/src/mol-theme/color/secondary-structure.ts
+++ b/src/mol-theme/color/secondary-structure.ts
@@ -96,5 +96,6 @@ export const SecondaryStructureColorThemeProvider: ColorTheme.Provider<Secondary
     label: 'Secondary Structure',
     factory: SecondaryStructureColorTheme,
     getParams: getSecondaryStructureColorThemeParams,
-    defaultValues: PD.getDefaultValues(SecondaryStructureColorThemeParams)
+    defaultValues: PD.getDefaultValues(SecondaryStructureColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/sequence-id.ts b/src/mol-theme/color/sequence-id.ts
index cb8e49ffa..b0dd084b6 100644
--- a/src/mol-theme/color/sequence-id.ts
+++ b/src/mol-theme/color/sequence-id.ts
@@ -103,5 +103,6 @@ export const SequenceIdColorThemeProvider: ColorTheme.Provider<SequenceIdColorTh
     label: 'Sequence Id',
     factory: SequenceIdColorTheme,
     getParams: getSequenceIdColorThemeParams,
-    defaultValues: PD.getDefaultValues(SequenceIdColorThemeParams)
+    defaultValues: PD.getDefaultValues(SequenceIdColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/shape-group.ts b/src/mol-theme/color/shape-group.ts
index b6cbba700..66c51e853 100644
--- a/src/mol-theme/color/shape-group.ts
+++ b/src/mol-theme/color/shape-group.ts
@@ -39,5 +39,6 @@ export const ShapeGroupColorThemeProvider: ColorTheme.Provider<ShapeGroupColorTh
     label: 'Shape Group',
     factory: ShapeGroupColorTheme,
     getParams: getShapeGroupColorThemeParams,
-    defaultValues: PD.getDefaultValues(ShapeGroupColorThemeParams)
+    defaultValues: PD.getDefaultValues(ShapeGroupColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.shape
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/uniform.ts b/src/mol-theme/color/uniform.ts
index 8c702a998..c18b62c9a 100644
--- a/src/mol-theme/color/uniform.ts
+++ b/src/mol-theme/color/uniform.ts
@@ -38,5 +38,6 @@ export const UniformColorThemeProvider: ColorTheme.Provider<UniformColorThemePar
     label: 'Uniform',
     factory: UniformColorTheme,
     getParams: getUniformColorThemeParams,
-    defaultValues: PD.getDefaultValues(UniformColorThemeParams)
+    defaultValues: PD.getDefaultValues(UniformColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => true
 }
\ No newline at end of file
diff --git a/src/mol-theme/color/unit-index.ts b/src/mol-theme/color/unit-index.ts
index 4f9d14883..ac25d5113 100644
--- a/src/mol-theme/color/unit-index.ts
+++ b/src/mol-theme/color/unit-index.ts
@@ -61,5 +61,6 @@ export const UnitIndexColorThemeProvider: ColorTheme.Provider<UnitIndexColorThem
     label: 'Unit Index',
     factory: UnitIndexColorTheme,
     getParams: getUnitIndexColorThemeParams,
-    defaultValues: PD.getDefaultValues(UnitIndexColorThemeParams)
+    defaultValues: PD.getDefaultValues(UnitIndexColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/size.ts b/src/mol-theme/size.ts
index 16179eb0d..11dd21588 100644
--- a/src/mol-theme/size.ts
+++ b/src/mol-theme/size.ts
@@ -7,12 +7,12 @@
 import { SizeType, LocationSize } from 'mol-geo/geometry/size-data';
 import { UniformSizeThemeProvider } from './size/uniform';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
-import { ThemeDataContext } from 'mol-theme/theme';
+import { ThemeDataContext, ThemeRegistry, ThemeProvider } from 'mol-theme/theme';
 import { PhysicalSizeThemeProvider } from './size/physical';
 import { deepEqual } from 'mol-util';
 
 export { SizeTheme }
-interface SizeTheme<P extends PD.Params = {}> {
+interface SizeTheme<P extends PD.Params> {
     readonly factory: SizeTheme.Factory<P>
     readonly granularity: SizeType
     readonly size: LocationSize
@@ -25,56 +25,16 @@ namespace SizeTheme {
     export const EmptyFactory = () => Empty
     export const Empty: SizeTheme<{}> = { factory: EmptyFactory, granularity: 'uniform', size: () => 1, props: {} }
 
-    export function areEqual(themeA: SizeTheme, themeB: SizeTheme) {
+    export function areEqual(themeA: SizeTheme<any>, themeB: SizeTheme<any>) {
         return themeA.factory === themeB.factory && deepEqual(themeA.props, themeB.props)
     }
 
-    export interface Provider<P extends PD.Params> {
-        readonly label: string
-        readonly factory: Factory<P>
-        readonly getParams: (ctx: ThemeDataContext) => P
-        readonly defaultValues: PD.Values<P>
-    }
-    export const EmptyProvider: Provider<{}> = { label: '', factory: EmptyFactory, getParams: () => ({}), defaultValues: {} }
-
-    export class Registry {
-        private _list: { name: string, provider: Provider<any> }[] = []
-        private _map = new Map<string, Provider<any>>()
-
-        get default() { return this._list[0]; }
-        get types(): [string, string][] {
-            return this._list.map(e => [e.name, e.provider.label] as [string, string]);
-        }
-
-        constructor() {
-            Object.keys(BuiltInSizeThemes).forEach(name => {
-                const p = (BuiltInSizeThemes as { [k: string]: Provider<any> })[name]
-                this.add(name, p)
-            })
-        }
-
-        add<P extends PD.Params>(name: string, provider: Provider<P>) {
-            this._list.push({ name, provider })
-            this._map.set(name, provider)
-        }
-
-        remove(name: string) {
-            this._list.splice(this._list.findIndex(e => e.name === name))
-            this._map.delete(name)
-        }
-
-        get<P extends PD.Params>(name: string): Provider<P> {
-            return this._map.get(name) || EmptyProvider as unknown as Provider<P>
-        }
-
-        create(name: string, ctx: ThemeDataContext, props = {}) {
-            const provider = this.get(name)
-            return provider.factory(ctx, { ...PD.getDefaultValues(provider.getParams(ctx)), ...props })
-        }
+    export interface Provider<P extends PD.Params> extends ThemeProvider<SizeTheme<P>, P> { }
+    export const EmptyProvider: Provider<{}> = { label: '', factory: EmptyFactory, getParams: () => ({}), defaultValues: {}, isApplicable: () => true }
 
-        get list() {
-            return this._list
-        }
+    export type Registry = ThemeRegistry<SizeTheme<any>>
+    export function createRegistry() {
+        return new ThemeRegistry(BuiltInSizeThemes as { [k: string]: Provider<any> }, EmptyProvider)
     }
 }
 
diff --git a/src/mol-theme/size/physical.ts b/src/mol-theme/size/physical.ts
index b856912c4..3d141c045 100644
--- a/src/mol-theme/size/physical.ts
+++ b/src/mol-theme/size/physical.ts
@@ -60,5 +60,6 @@ export const PhysicalSizeThemeProvider: SizeTheme.Provider<PhysicalSizeThemePara
     label: 'Physical',
     factory: PhysicalSizeTheme,
     getParams: getPhysicalSizeThemeParams,
-    defaultValues: PD.getDefaultValues(PhysicalSizeThemeParams)
+    defaultValues: PD.getDefaultValues(PhysicalSizeThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure
 }
\ No newline at end of file
diff --git a/src/mol-theme/size/uniform.ts b/src/mol-theme/size/uniform.ts
index 3d91d0c35..69c7fd832 100644
--- a/src/mol-theme/size/uniform.ts
+++ b/src/mol-theme/size/uniform.ts
@@ -34,5 +34,6 @@ export const UniformSizeThemeProvider: SizeTheme.Provider<UniformSizeThemeParams
     label: 'Uniform',
     factory: UniformSizeTheme,
     getParams: getUniformSizeThemeParams,
-    defaultValues: PD.getDefaultValues(UniformSizeThemeParams)
+    defaultValues: PD.getDefaultValues(UniformSizeThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => true
 }
\ No newline at end of file
diff --git a/src/mol-theme/theme.ts b/src/mol-theme/theme.ts
index ad2222c38..7808942b8 100644
--- a/src/mol-theme/theme.ts
+++ b/src/mol-theme/theme.ts
@@ -9,6 +9,7 @@ import { SizeTheme } from './size';
 import { Structure } from 'mol-model/structure';
 import { VolumeData } from 'mol-model/volume';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
+import { Shape } from 'mol-model/shape';
 
 export interface ThemeRegistryContext {
     colorThemeRegistry: ColorTheme.Registry
@@ -19,11 +20,12 @@ export interface ThemeDataContext {
     [k: string]: any
     structure?: Structure
     volume?: VolumeData
+    shape?: Shape
 }
 
 export interface Theme {
-    color: ColorTheme
-    size: SizeTheme
+    color: ColorTheme<any>
+    size: SizeTheme<any>
     // label: LabelTheme // TODO
 }
 
@@ -43,4 +45,59 @@ export function createTheme(ctx: ThemeRegistryContext, data: ThemeDataContext, p
 
 export function createEmptyTheme(): Theme {
     return { color: ColorTheme.Empty, size: SizeTheme.Empty }
+}
+
+//
+
+export interface ThemeProvider<T extends ColorTheme<P> | SizeTheme<P>, P extends PD.Params> {
+    readonly label: string
+    readonly factory: (ctx: ThemeDataContext, props: PD.Values<P>) => T
+    readonly getParams: (ctx: ThemeDataContext) => P
+    readonly defaultValues: PD.Values<P>
+    readonly isApplicable: (ctx: ThemeDataContext) => boolean
+}
+
+function getTypes(list: { name: string, provider: ThemeProvider<any, any> }[]) {
+    return list.map(e => [e.name, e.provider.label] as [string, string]);
+}
+
+export class ThemeRegistry<T extends ColorTheme<any> | SizeTheme<any>> {
+    private _list: { name: string, provider: ThemeProvider<T, any> }[] = []
+    private _map = new Map<string, ThemeProvider<T, any>>()
+
+    get default() { return this._list[0]; }
+    get list() { return this._list }
+    get types(): [string, string][] { return getTypes(this._list) }
+
+    constructor(builtInThemes: { [k: string]: ThemeProvider<T, any> }, private emptyProvider: ThemeProvider<T, any>) {
+        Object.keys(builtInThemes).forEach(name => this.add(name, builtInThemes[name]))
+    }
+
+    add<P extends PD.Params>(name: string, provider: ThemeProvider<T, P>) {
+        this._list.push({ name, provider })
+        this._map.set(name, provider)
+    }
+
+    remove(name: string) {
+        this._list.splice(this._list.findIndex(e => e.name === name))
+        this._map.delete(name)
+        console.log('removed', name, this._list, this._map)
+    }
+
+    get<P extends PD.Params>(name: string): ThemeProvider<T, P> {
+        return this._map.get(name) || this.emptyProvider
+    }
+
+    create(name: string, ctx: ThemeDataContext, props = {}) {
+        const provider = this.get(name)
+        return provider.factory(ctx, { ...PD.getDefaultValues(provider.getParams(ctx)), ...props })
+    }
+
+    getApplicableList(ctx: ThemeDataContext) {
+        return this._list.filter(e => e.provider.isApplicable(ctx));
+    }
+
+    getApplicableTypes(ctx: ThemeDataContext) {
+        return getTypes(this.getApplicableList(ctx))
+    }
 }
\ No newline at end of file
-- 
GitLab