Browse Source

refactored Repr/Color/Size registry & built-ins
updated component UI

David Sehnal 5 years ago
parent
commit
a85adfdf1f
33 changed files with 409 additions and 297 deletions
  1. 2 2
      src/apps/basic-wrapper/helpers.ts
  2. 2 2
      src/apps/basic-wrapper/index.ts
  3. 5 5
      src/apps/viewer/extensions/cellpack/model.ts
  4. 3 3
      src/examples/proteopedia-wrapper/helpers.ts
  5. 3 3
      src/examples/proteopedia-wrapper/index.ts
  6. 2 2
      src/mol-plugin-state/actions/structure.ts
  7. 4 4
      src/mol-plugin-state/builder/structure.ts
  8. 3 3
      src/mol-plugin-state/builder/structure/preset.ts
  9. 1 1
      src/mol-plugin-state/formats/trajectory.ts
  10. 48 18
      src/mol-plugin-state/helpers/structure-representation-params.ts
  11. 42 6
      src/mol-plugin-state/manager/structure/component.ts
  12. 8 7
      src/mol-plugin-state/manager/structure/hierarchy-state.ts
  13. 33 33
      src/mol-plugin-state/transforms/representation.ts
  14. 76 55
      src/mol-plugin-ui/controls/action-menu.tsx
  15. 2 2
      src/mol-plugin-ui/controls/common.tsx
  16. 1 1
      src/mol-plugin-ui/controls/parameters.tsx
  17. 29 9
      src/mol-plugin-ui/structure/components.tsx
  18. 5 5
      src/mol-plugin-ui/structure/measurements.tsx
  19. 2 2
      src/mol-plugin-ui/structure/source.tsx
  20. 2 2
      src/mol-plugin/behavior/dynamic/custom-props/computed/accessible-surface-area.ts
  21. 4 4
      src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts
  22. 4 4
      src/mol-plugin/behavior/dynamic/custom-props/integrative/cross-link-restraint.ts
  23. 2 2
      src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts
  24. 3 3
      src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts
  25. 8 8
      src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts
  26. 2 2
      src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts
  27. 5 5
      src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts
  28. 11 9
      src/mol-plugin/context.ts
  29. 31 30
      src/mol-repr/structure/registry.ts
  30. 12 12
      src/mol-repr/structure/representation.ts
  31. 15 14
      src/mol-repr/volume/registry.ts
  32. 28 28
      src/mol-theme/color.ts
  33. 11 11
      src/mol-theme/size.ts

+ 2 - 2
src/apps/basic-wrapper/helpers.ts

@@ -11,7 +11,7 @@ import { StateTransforms } from '../../mol-plugin-state/transforms';
 import { MolScriptBuilder as MS } from '../../mol-script/language/builder';
 import { StateBuilder } from '../../mol-state';
 import Expression from '../../mol-script/language/expression';
-import { BuiltInColorThemeName } from '../../mol-theme/color';
+import { ColorTheme } from '../../mol-theme/color';
 import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
 type SupportedFormats = 'cif' | 'pdb'
 
@@ -86,7 +86,7 @@ export namespace StateHelper {
         return visualRoot;
     }
 
-    export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, color?: BuiltInColorThemeName) {
+    export function ballsAndSticks(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>, expression: Expression, color?: ColorTheme.BuiltIn) {
         visualRoot
             .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression })
             .apply(StateTransforms.Representation.StructureRepresentation3D,

+ 2 - 2
src/apps/basic-wrapper/index.ts

@@ -47,7 +47,7 @@ class BasicWrapper {
             }
         });
 
-        this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
+        this.plugin.representation.structure.themes.colorThemeRegistry.add(StripedResidues.colorThemeProvider!);
         this.plugin.managers.lociLabels.addProvider(StripedResidues.labelProvider!);
         this.plugin.customModelProperties.register(StripedResidues.propertyProvider, true);
     }
@@ -154,7 +154,7 @@ class BasicWrapper {
 
             const visuals = state.selectQ(q => q.ofTransformer(StateTransforms.Representation.StructureRepresentation3D));
             const tree = state.build();
-            const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
+            const colorTheme = { name: StripedResidues.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(StripedResidues.propertyProvider.descriptor.name).defaultValues };
 
             for (const v of visuals) {
                 tree.to(v).update(old => ({ ...old, colorTheme }));

+ 5 - 5
src/apps/viewer/extensions/cellpack/model.ts

@@ -430,15 +430,15 @@ function getReprParams(ctx: PluginContext, params: { representation: Representat
         case 'spacefill':
             return traceOnly
                 ? {
-                    type: ctx.structureRepresentation.registry.get('spacefill'),
+                    type: ctx.representation.structure.registry.get('spacefill'),
                     typeParams: { sizeFactor: 2, ignoreHydrogens: true }
                 } : {
-                    type: ctx.structureRepresentation.registry.get('spacefill'),
+                    type: ctx.representation.structure.registry.get('spacefill'),
                     typeParams: { ignoreHydrogens: true }
                 }
         case 'gaussian-surface':
             return {
-                type: ctx.structureRepresentation.registry.get('gaussian-surface'),
+                type: ctx.representation.structure.registry.get('gaussian-surface'),
                 typeParams: {
                     quality: 'custom', resolution: 10, radiusOffset: 2,
                     alpha: 1.0, flatShaded: false, doubleSided: false,
@@ -446,9 +446,9 @@ function getReprParams(ctx: PluginContext, params: { representation: Representat
                 }
             }
         case 'point':
-            return { type: ctx.structureRepresentation.registry.get('point') }
+            return { type: ctx.representation.structure.registry.get('point') }
         case 'ellipsoid':
-            return { type: ctx.structureRepresentation.registry.get('orientation') }
+            return { type: ctx.representation.structure.registry.get('orientation') }
     }
 }
 

+ 3 - 3
src/examples/proteopedia-wrapper/helpers.ts

@@ -5,8 +5,8 @@
  */
 
 import { ResidueIndex, Model } from '../../mol-model/structure';
-import { BuiltInStructureRepresentationsName } from '../../mol-repr/structure/registry';
-import { BuiltInColorThemeName } from '../../mol-theme/color';
+import { StructureRepresentationRegistry } from '../../mol-repr/structure/registry';
+import { ColorTheme } from '../../mol-theme/color';
 import { PolymerType } from '../../mol-model/structure/model/types';
 import { PluginContext } from '../../mol-plugin/context';
 import { ModelSymmetry } from '../../mol-model-formats/structure/property/symmetry';
@@ -98,7 +98,7 @@ export interface RepresentationStyle {
 }
 
 export namespace RepresentationStyle {
-    export type Entry = { hide?: boolean, kind?: BuiltInStructureRepresentationsName, coloring?: BuiltInColorThemeName }
+    export type Entry = { hide?: boolean, kind?: StructureRepresentationRegistry.BuiltIn, coloring?: ColorTheme.BuiltIn }
 }
 
 export enum StateElements {

+ 3 - 3
src/examples/proteopedia-wrapper/index.ts

@@ -67,8 +67,8 @@ class MolStarProteopediaWrapper {
 
         const customColoring = createProteopediaCustomTheme((options && options.customColorList) || []);
 
-        this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(customColoring);
-        this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.add(EvolutionaryConservation.colorThemeProvider!);
+        this.plugin.representation.structure.themes.colorThemeRegistry.add(customColoring);
+        this.plugin.representation.structure.themes.colorThemeRegistry.add(EvolutionaryConservation.colorThemeProvider!);
         this.plugin.managers.lociLabels.addProvider(EvolutionaryConservation.labelProvider!);
         this.plugin.customModelProperties.register(EvolutionaryConservation.propertyProvider, true);
     }
@@ -315,7 +315,7 @@ class MolStarProteopediaWrapper {
             // }
 
             const tree = state.build();
-            const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.structureRepresentation.themeCtx.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
+            const colorTheme = { name: EvolutionaryConservation.propertyProvider.descriptor.name, params: this.plugin.representation.structure.themes.colorThemeRegistry.get(EvolutionaryConservation.propertyProvider.descriptor.name).defaultValues };
 
             if (!params || !!params.sequence) {
                 tree.to(StateElements.SequenceVisual).update(StateTransforms.Representation.StructureRepresentation3D, old => ({ ...old, colorTheme }));

+ 2 - 2
src/mol-plugin-state/actions/structure.ts

@@ -11,7 +11,7 @@ import { Task } from '../../mol-task';
 import { FileInfo } from '../../mol-util/file-info';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { TrajectoryFormat } from '../builder/structure';
-import { BuildInTrajectoryFormat } from '../formats/trajectory';
+import { BuiltInTrajectoryFormat } from '../formats/trajectory';
 import { RootStructureDefinition } from '../helpers/root-structure';
 import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
@@ -187,7 +187,7 @@ const DownloadStructure = StateAction.build({
 
     const src = params.source;
     let downloadParams: StateTransformer.Params<Download>[];
-    let supportProps = false, asTrajectory = false, format: BuildInTrajectoryFormat = 'mmcif';
+    let supportProps = false, asTrajectory = false, format: BuiltInTrajectoryFormat = 'mmcif';
 
     switch (src.name) {
         case 'url':

+ 4 - 4
src/mol-plugin-state/builder/structure.ts

@@ -10,7 +10,7 @@ import { PluginStateObject as SO } from '../objects';
 import { StateTransforms } from '../transforms';
 import { RootStructureDefinition } from '../helpers/root-structure';
 import { StructureComponentParams, StaticStructureComponentType } from '../helpers/structure-component';
-import { BuildInTrajectoryFormat, TrajectoryFormatProvider } from '../formats/trajectory';
+import { BuiltInTrajectoryFormat, TrajectoryFormatProvider } from '../formats/trajectory';
 import { StructureRepresentationBuilder } from './structure/representation';
 import { StructureSelectionQuery } from '../helpers/structure-selection-query';
 import { Task } from '../../mol-task';
@@ -32,7 +32,7 @@ export class StructureBuilder {
         return this.plugin.state.dataState;
     }
 
-    private async parseTrajectoryData(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuildInTrajectoryFormat | TrajectoryFormatProvider) {
+    private async parseTrajectoryData(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider) {
         const provider = typeof format === 'string' ? this.plugin.dataFormat.trajectory.get(format) : format;
         if (!provider) throw new Error(`'${format}' is not a supported data format.`);
         const { trajectory } = await provider.parse(this.plugin, data, { trajectoryTags: StructureBuilderTags.Trajectory });
@@ -52,7 +52,7 @@ export class StructureBuilder {
 
     async parseStructure(params: {
         data?: StateObjectRef<SO.Data.Binary | SO.Data.String>,
-        dataFormat?: BuildInTrajectoryFormat | TrajectoryFormatProvider,
+        dataFormat?: BuiltInTrajectoryFormat | TrajectoryFormatProvider,
         blob?: StateObjectRef<SO.Data.Blob>
         blobParams?: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>,
         model?: StateTransformer.Params<StateTransforms['Model']['ModelFromTrajectory']>,
@@ -83,7 +83,7 @@ export class StructureBuilder {
         };
     }
 
-    async parseTrajectory(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuildInTrajectoryFormat | TrajectoryFormatProvider): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
+    async parseTrajectory(data: StateObjectRef<SO.Data.Binary | SO.Data.String>, format: BuiltInTrajectoryFormat | TrajectoryFormatProvider): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
     async parseTrajectory(blob: StateObjectRef<SO.Data.Blob>, params: StateTransformer.Params<StateTransforms['Data']['ParseBlob']>): Promise<StateObjectSelector<SO.Molecule.Trajectory>>
     async parseTrajectory(data: StateObjectRef, params: any) {
         const cell = StateObjectRef.resolveAndCheck(this.dataState, data as StateObjectRef);

+ 3 - 3
src/mol-plugin-state/builder/structure/preset.ts

@@ -9,17 +9,17 @@ import { VisualQuality, VisualQualityOptions } from '../../../mol-geo/geometry/b
 import { Structure } from '../../../mol-model/structure';
 import { PluginContext } from '../../../mol-plugin/context';
 import { StateObjectRef } from '../../../mol-state';
-import { BuiltInColorThemeName } from '../../../mol-theme/color';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { StaticStructureComponentType } from '../../helpers/structure-component';
 import { StructureSelectionQueries as Q } from '../../helpers/structure-selection-query';
 import { PluginStateObject } from '../../objects';
 import { RepresentationProviderTags, StructureRepresentationProvider } from './provider';
+import { ColorTheme } from '../../../mol-theme/color';
 
 export const CommonStructureRepresentationParams = {
     ignoreHydrogens: PD.Optional(PD.Boolean(false)),
     quality: PD.Optional(PD.Select<VisualQuality>('auto', VisualQualityOptions)),
-    globalThemeName: PD.Optional(PD.Text<BuiltInColorThemeName>(''))
+    globalThemeName: PD.Optional(PD.Text<ColorTheme.BuiltIn>(''))
 }
 export type CommonStructureRepresentationParams = PD.ValuesFor<typeof CommonStructureRepresentationParams>
 
@@ -54,7 +54,7 @@ function reprBuilder(plugin: PluginContext, params: CommonStructureRepresentatio
     };
     if (params.quality && params.quality !== 'auto') typeParams.quality = params.quality;
     if (params.ignoreHydrogens !== void 0) typeParams.ignoreHydrogens = !!params.ignoreHydrogens;
-    const color: BuiltInColorThemeName | undefined = params.globalThemeName ? params.globalThemeName : void 0;
+    const color: ColorTheme.BuiltIn | undefined = params.globalThemeName ? params.globalThemeName : void 0;
 
     return { update, builder, color, typeParams };
 }

+ 1 - 1
src/mol-plugin-state/formats/trajectory.ts

@@ -90,4 +90,4 @@ export const BuildInTrajectoryFormats = [
     ['3dg', Provider3dg] as const,
 ] as const
 
-export type BuildInTrajectoryFormat = (typeof BuildInTrajectoryFormats)[number][0]
+export type BuiltInTrajectoryFormat = (typeof BuildInTrajectoryFormats)[number][0]

+ 48 - 18
src/mol-plugin-state/helpers/structure-representation-params.ts

@@ -7,26 +7,26 @@
 import { Structure } from '../../mol-model/structure';
 import { PluginContext } from '../../mol-plugin/context';
 import { RepresentationProvider } from '../../mol-repr/representation';
-import { BuiltInStructureRepresentations, BuiltInStructureRepresentationsName } from '../../mol-repr/structure/registry'
+import { StructureRepresentationRegistry } from '../../mol-repr/structure/registry'
 import { StateTransformer } from '../../mol-state';
-import { BuiltInColorThemeName, BuiltInColorThemes, ColorTheme } from '../../mol-theme/color';
-import { BuiltInSizeThemeName, BuiltInSizeThemes, SizeTheme } from '../../mol-theme/size';
+import { ColorTheme } from '../../mol-theme/color';
+import { SizeTheme } from '../../mol-theme/size';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { StructureRepresentation3D } from '../transforms/representation';
 
 export interface StructureRepresentationBuiltInProps<
-    R extends BuiltInStructureRepresentationsName = BuiltInStructureRepresentationsName,
-    C extends BuiltInColorThemeName = BuiltInColorThemeName,
-    S extends BuiltInSizeThemeName = BuiltInSizeThemeName> {
+    R extends StructureRepresentationRegistry.BuiltIn = StructureRepresentationRegistry.BuiltIn,
+    C extends ColorTheme.BuiltIn = ColorTheme.BuiltIn,
+    S extends SizeTheme.BuiltIn = SizeTheme.BuiltIn> {
     /** Using any registered name will work, but code completion will break */
     type?: R,
-    typeParams?: Partial<RepresentationProvider.ParamValues<BuiltInStructureRepresentations[R]>>,
+    typeParams?: StructureRepresentationRegistry.BuiltInParams<R>,
     /** Using any registered name will work, but code completion will break */
     color?: C,
-    colorParams?: Partial<ColorTheme.ParamValues<BuiltInColorThemes[C]>>,
+    colorParams?: ColorTheme.BuiltInParams<C>,
     /** Using any registered name will work, but code completion will break */
     size?: S,
-    sizeParams?: Partial<SizeTheme.ParamValues<BuiltInSizeThemes[S]>>
+    sizeParams?: SizeTheme.BuiltInParams<S>
 }
 
 export interface StructureRepresentationProps<
@@ -42,7 +42,7 @@ export interface StructureRepresentationProps<
 }
 
 export function createStructureRepresentationParams
-    <R extends BuiltInStructureRepresentationsName, C extends BuiltInColorThemeName, S extends BuiltInSizeThemeName>
+    <R extends StructureRepresentationRegistry.BuiltIn, C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>
     (ctx: PluginContext, structure?: Structure, props?: StructureRepresentationBuiltInProps<R, C, S>): StateTransformer.Params<StructureRepresentation3D>
 export function createStructureRepresentationParams
     <R extends RepresentationProvider<Structure>, C extends ColorTheme.Provider, S extends SizeTheme.Provider>
@@ -53,13 +53,43 @@ export function createStructureRepresentationParams(ctx: PluginContext, structur
     return createParamsProvider(ctx, structure || Structure.Empty, props);
 }
 
+export function getStructureThemeTypes(ctx: PluginContext, structure?: Structure) {
+    const { themes: themeCtx } = ctx.representation.structure;
+    if (!structure) return themeCtx.colorThemeRegistry.types;
+    return themeCtx.colorThemeRegistry.getApplicableTypes({ structure });
+}
+
+export function createStructureColorThemeParams<T extends ColorTheme.BuiltIn>
+    (ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName: T, params?: ColorTheme.BuiltInParams<T>): StateTransformer.Params<StructureRepresentation3D>['colorTheme']
+export function createStructureColorThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['colorTheme']
+export function createStructureColorThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['colorTheme'] {
+    const { registry, themes } = ctx.representation.structure;
+    const repr = registry.get(typeName || registry.default.name);
+    const color = themes.colorThemeRegistry.get(themeName || repr.defaultColorTheme.name);
+    const colorDefaultParams = PD.getDefaultValues(color.getParams({ structure: structure || Structure.Empty }));
+    if (color.name === repr.defaultColorTheme.name) Object.assign(colorDefaultParams, repr.defaultColorTheme.props);
+    return { name: color.name, params: Object.assign(colorDefaultParams, params) };
+}
+
+export function createStructureSizeThemeParams<T extends SizeTheme.BuiltIn>
+    (ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName: T, params?: SizeTheme.BuiltInParams<T>): StateTransformer.Params<StructureRepresentation3D>['sizeTheme']
+export function createStructureSizeThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['sizeTheme']
+export function createStructureSizeThemeParams(ctx: PluginContext, structure: Structure | undefined, typeName: string | undefined, themeName?: string, params?: any): StateTransformer.Params<StructureRepresentation3D>['sizeTheme'] {
+    const { registry, themes } = ctx.representation.structure;
+    const repr = registry.get(typeName || registry.default.name);
+    const size = themes.sizeThemeRegistry.get(themeName || repr.defaultSizeTheme.name);
+    const sizeDefaultParams = PD.getDefaultValues(size.getParams({ structure: structure || Structure.Empty }));
+    if (size.name === repr.defaultSizeTheme.name) Object.assign(sizeDefaultParams, repr.defaultSizeTheme.props);
+    return { name: size.name, params: Object.assign(sizeDefaultParams, params) };
+}
+
 function createParamsByName(ctx: PluginContext, structure: Structure, props: StructureRepresentationBuiltInProps): StateTransformer.Params<StructureRepresentation3D> {
-    const typeProvider = (props.type && ctx.structureRepresentation.registry.get(props.type))
-        || ctx.structureRepresentation.registry.default.provider;
-    const colorProvider = (props.color && ctx.structureRepresentation.themeCtx.colorThemeRegistry.get(props.color)) 
-        || ctx.structureRepresentation.themeCtx.colorThemeRegistry.get(typeProvider.defaultColorTheme.name);
-    const sizeProvider = (props.size && ctx.structureRepresentation.themeCtx.sizeThemeRegistry.get(props.size))
-        || ctx.structureRepresentation.themeCtx.sizeThemeRegistry.get(typeProvider.defaultSizeTheme.name);
+    const typeProvider = (props.type && ctx.representation.structure.registry.get(props.type))
+        || ctx.representation.structure.registry.default.provider;
+    const colorProvider = (props.color && ctx.representation.structure.themes.colorThemeRegistry.get(props.color)) 
+        || ctx.representation.structure.themes.colorThemeRegistry.get(typeProvider.defaultColorTheme.name);
+    const sizeProvider = (props.size && ctx.representation.structure.themes.sizeThemeRegistry.get(props.size))
+        || ctx.representation.structure.themes.sizeThemeRegistry.get(typeProvider.defaultSizeTheme.name);
 
     return createParamsProvider(ctx, structure, {
         type: typeProvider,
@@ -72,10 +102,10 @@ function createParamsByName(ctx: PluginContext, structure: Structure, props: Str
 }
 
 function createParamsProvider(ctx: PluginContext, structure: Structure, props: StructureRepresentationProps = {}): StateTransformer.Params<StructureRepresentation3D> {
-    const { themeCtx } = ctx.structureRepresentation
+    const { themes: themeCtx } = ctx.representation.structure
     const themeDataCtx = { structure };
     
-    const repr = props.type || ctx.structureRepresentation.registry.default.provider;
+    const repr = props.type || ctx.representation.structure.registry.default.provider;
     const reprDefaultParams = PD.getDefaultValues(repr.getParams(themeCtx, structure));
     const reprParams = Object.assign(reprDefaultParams, props.typeParams);
     

+ 42 - 6
src/mol-plugin-state/manager/structure/component.ts

@@ -21,9 +21,11 @@ import { PluginComponent } from '../../component';
 import { StructureComponentParams } from '../../helpers/structure-component';
 import { clearStructureOverpaint, setStructureOverpaint } from '../../helpers/structure-overpaint';
 import { StructureSelectionQueries, StructureSelectionQuery, StructureSelectionQueryOptions } from '../../helpers/structure-selection-query';
-import { CustomStructureProperties } from '../../transforms/model';
 import { StructureRepresentation3D } from '../../transforms/representation';
 import { HierarchyRef, StructureComponentRef, StructureRef, StructureRepresentationRef } from './hierarchy-state';
+import { createStructureColorThemeParams, createStructureSizeThemeParams } from '../../helpers/structure-representation-params';
+import { ColorTheme } from '../../../mol-theme/color';
+import { SizeTheme } from '../../../mol-theme/size';
 
 export { StructureComponentManager };
 
@@ -90,7 +92,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
                 if (PD.areEqual(interactionParams, params, this.state.options.interactions)) continue;
 
                 const b = this.dataState.build();
-                b.to(s.properties.cell).update((old: StateTransformer.Params<CustomStructureProperties>) => {
+                b.to(s.properties.cell).update(old => {
                     arraySetAdd(old.autoAttach, InteractionsProvider.descriptor.name);
                     old.properties[InteractionsProvider.descriptor.name] = this.state.options.interactions;
                 });
@@ -197,6 +199,30 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
         return this.plugin.runTask(this.dataState.updateTree(update, { canUndo: true }));
     }
 
+    updateRepresentationsTheme<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn>(components: ReadonlyArray<StructureComponentRef>, params: StructureComponentManager.UpdateThemeParams<C, S>): Promise<any> {
+        if (components.length === 0) return Promise.resolve();
+        
+        const update = this.dataState.build();
+
+        for (const c of components) {
+            for (const repr of c.representations) {
+                const old = repr.cell.transform.params;
+                const colorTheme = params.color 
+                    ? createStructureColorThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.color, params.colorParams)
+                    : void 0;
+                const sizeTheme = params.color 
+                    ? createStructureSizeThemeParams(this.plugin, c.structure.cell.obj?.data, old?.type.name, params.size, params.sizeParams)
+                    : void 0;
+                update.to(repr.cell).update(prev => { 
+                    if (colorTheme) prev.colorTheme = colorTheme;
+                    if (sizeTheme) prev.sizeTheme = sizeTheme;
+                });
+            }
+        }
+
+        return this.plugin.runTask(this.dataState.updateTree(update, { canUndo: true }));
+    }
+
     addRepresentation(components: ReadonlyArray<StructureComponentRef>, type: string) {
         if (components.length === 0) return;
 
@@ -207,7 +233,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
         return this.plugin.dataTransaction(async () => {
             for (const component of components) {
                 await this.plugin.builders.structure.representation.addRepresentation(component.cell, {
-                    type: this.plugin.structureRepresentation.registry.get(type),
+                    type: this.plugin.representation.structure.registry.get(type),
                     typeParams
                 });
             }
@@ -229,7 +255,7 @@ class StructureComponentManager extends PluginComponent<StructureComponentManage
                 });
                 if (params.representation === 'none' || !component) continue;
                 await this.plugin.builders.structure.representation.addRepresentation(component, {
-                    type: this.plugin.structureRepresentation.registry.get(params.representation)
+                    type: this.plugin.representation.structure.registry.get(params.representation)
                 });
             }
         }, { canUndo: true });
@@ -335,8 +361,8 @@ namespace StructureComponentManager {
 
     export function getRepresentationTypes(plugin: PluginContext, pivot: StructureRef | StructureComponentRef | undefined) {
         return pivot?.cell.obj?.data
-            ? plugin.structureRepresentation.registry.getApplicableTypes(pivot.cell.obj?.data!)
-            : plugin.structureRepresentation.registry.types;
+            ? plugin.representation.structure.registry.getApplicableTypes(pivot.cell.obj?.data!)
+            : plugin.representation.structure.registry.types;
     }
 
     function getRepresentationTypesSelect(plugin: PluginContext, pivot: StructureRef | undefined, custom: [string, string][], label?: string) {
@@ -348,4 +374,14 @@ namespace StructureComponentManager {
     }
 
     export type ModifyAction = 'union' | 'subtract' | 'intersect'
+
+    export interface UpdateThemeParams<C extends ColorTheme.BuiltIn, S extends SizeTheme.BuiltIn> {
+        /** 
+         * this works for any theme name (use 'name as any'), but code completion will break
+         */
+        color?: C,
+        colorParams?: ColorTheme.BuiltInParams<C>,
+        size?: S,
+        sizeParams?: SizeTheme.BuiltInParams<S>
+    }
 }

+ 8 - 7
src/mol-plugin-state/manager/structure/hierarchy-state.ts

@@ -5,10 +5,11 @@
  */
 
 import { PluginStateObject as SO } from '../../objects';
-import { StateObject, StateTransform, State, StateObjectCell, StateTree } from '../../../mol-state';
+import { StateObject, StateTransform, State, StateObjectCell, StateTree, StateTransformer } from '../../../mol-state';
 import { StructureBuilderTags } from '../../builder/structure';
 import { RepresentationProviderTags } from '../../builder/structure/provider';
 import { StructureRepresentationInteractionTags } from '../../../mol-plugin/behavior/dynamic/selection/structure-representation-interaction';
+import { StateTransforms } from '../../transforms';
 
 export function buildStructureHierarchy(state: State, previous?: StructureHierarchy) {
     const build = BuildState(state, previous || StructureHierarchy());
@@ -29,9 +30,9 @@ export function StructureHierarchy(): StructureHierarchy {
     return { trajectories: [], models: [], structures: [], refs: new Map() }
 }
 
-interface RefBase<K extends string = string, T extends StateObject = StateObject> {
+interface RefBase<K extends string = string, O extends StateObject = StateObject, T extends StateTransformer = StateTransformer> {
     kind: K,
-    cell: StateObjectCell<T>,
+    cell: StateObjectCell<O, StateTransform<T>>,
     version: StateTransform['version']
 }
 
@@ -59,7 +60,7 @@ function ModelRef(cell: StateObjectCell<SO.Molecule.Model>, trajectory?: Traject
     return { kind: 'model', cell, version: cell.transform.version, trajectory, structures: [] };
 }
 
-export interface ModelPropertiesRef extends RefBase<'model-properties', SO.Molecule.Model> {
+export interface ModelPropertiesRef extends RefBase<'model-properties', SO.Molecule.Model, StateTransforms['Model']['CustomModelProperties']> {
     model: ModelRef
 }
 
@@ -83,7 +84,7 @@ function StructureRef(cell: StateObjectCell<SO.Molecule.Structure>, model?: Mode
     return { kind: 'structure', cell, version: cell.transform.version, model, components: [] };
 }
 
-export interface StructurePropertiesRef extends RefBase<'structure-properties', SO.Molecule.Structure> {
+export interface StructurePropertiesRef extends RefBase<'structure-properties', SO.Molecule.Structure, StateTransforms['Model']['CustomStructureProperties']> {
     structure: StructureRef
 }
 
@@ -91,7 +92,7 @@ function StructurePropertiesRef(cell: StateObjectCell<SO.Molecule.Structure>, st
     return { kind: 'structure-properties', cell, version: cell.transform.version, structure };
 }
 
-export interface StructureComponentRef extends RefBase<'structure-component', SO.Molecule.Structure> {
+export interface StructureComponentRef extends RefBase<'structure-component', SO.Molecule.Structure, StateTransforms['Model']['StructureComponent']> {
     structure: StructureRef,
     key?: string,
     representations: StructureRepresentationRef[],
@@ -107,7 +108,7 @@ function StructureComponentRef(cell: StateObjectCell<SO.Molecule.Structure>, str
     return { kind: 'structure-component', cell, version: cell.transform.version, structure, key: componentKey(cell), representations: [] };
 }
 
-export interface StructureRepresentationRef extends RefBase<'structure-representation', SO.Molecule.Structure.Representation3D> {
+export interface StructureRepresentationRef extends RefBase<'structure-representation', SO.Molecule.Structure.Representation3D, StateTransforms['Representation']['StructureRepresentation3D']> {
     component: StructureComponentRef
 }
 

+ 33 - 33
src/mol-plugin-state/transforms/representation.ts

@@ -8,12 +8,12 @@
 import { Structure, StructureElement } from '../../mol-model/structure';
 import { VolumeData, VolumeIsoValue } from '../../mol-model/volume';
 import { PluginContext } from '../../mol-plugin/context';
-import { BuiltInVolumeRepresentationsName } from '../../mol-repr/volume/registry';
+import { VolumeRepresentationRegistry } from '../../mol-repr/volume/registry';
 import { VolumeParams } from '../../mol-repr/volume/representation';
 import { StateTransformer, StateObject } from '../../mol-state';
 import { Task } from '../../mol-task';
-import { BuiltInColorThemeName, ColorTheme } from '../../mol-theme/color';
-import { BuiltInSizeThemeName, SizeTheme } from '../../mol-theme/size';
+import { ColorTheme } from '../../mol-theme/color';
+import { SizeTheme } from '../../mol-theme/size';
 import { Theme } from '../../mol-theme/theme';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { PluginStateObject as SO, PluginStateTransform } from '../objects';
@@ -51,7 +51,7 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
     from: SO.Molecule.Structure,
     to: SO.Molecule.Structure.Representation3D,
     params: (a, ctx: PluginContext) => {
-        const { registry, themeCtx } = ctx.structureRepresentation
+        const { registry, themes: themeCtx } = ctx.representation.structure
         const type = registry.get(registry.default.name);
 
         if (!a) {
@@ -121,12 +121,12 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Representation', async ctx => {
             const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
-            const provider = plugin.structureRepresentation.registry.get(params.type.name)
+            const provider = plugin.representation.structure.registry.get(params.type.name)
             if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data)
             const props = params.type.params || {}
-            const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, provider.getParams)
-            await Theme.ensureDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, params)
-            repr.setTheme(Theme.create(plugin.structureRepresentation.themeCtx, { structure: a.data }, params))
+            const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, provider.getParams)
+            await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, params)
+            repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, params))
             // TODO set initial state, repr.setState({})
             await repr.createOrUpdate(props, a.data).runInContext(ctx);
             return new SO.Molecule.Structure.Representation3D({ repr, source: a } , { label: provider.label });
@@ -134,17 +134,17 @@ const StructureRepresentation3D = PluginStateTransform.BuiltIn({
     },
     update({ a, b, oldParams, newParams }, plugin: PluginContext) {
         return Task.create('Structure Representation', async ctx => {
-            const oldProvider = plugin.structureRepresentation.registry.get(oldParams.type.name);
+            const oldProvider = plugin.representation.structure.registry.get(oldParams.type.name);
             const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
             if (oldProvider.ensureCustomProperties) oldProvider.ensureCustomProperties.detach(propertyCtx, a.data);
-            Theme.releaseDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, oldParams);
+            Theme.releaseDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, oldParams);
 
             if (newParams.type.name !== oldParams.type.name) return StateTransformer.UpdateResult.Recreate;
-            const provider = plugin.structureRepresentation.registry.get(newParams.type.name)
+            const provider = plugin.representation.structure.registry.get(newParams.type.name)
             if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data)
             const props = { ...b.data.repr.props, ...newParams.type.params }
-            await Theme.ensureDependencies(propertyCtx, plugin.structureRepresentation.themeCtx, { structure: a.data }, newParams)
-            b.data.repr.setTheme(Theme.create(plugin.structureRepresentation.themeCtx, { structure: a.data }, newParams));
+            await Theme.ensureDependencies(propertyCtx, plugin.representation.structure.themes, { structure: a.data }, newParams)
+            b.data.repr.setTheme(Theme.create(plugin.representation.structure.themes, { structure: a.data }, newParams));
             await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
             b.data.source = a
             return StateTransformer.UpdateResult.Updated;
@@ -417,13 +417,13 @@ const TransparencyStructureRepresentation3DFromBundle = PluginStateTransform.Bui
 //
 
 export namespace VolumeRepresentation3DHelpers {
-    export function getDefaultParams(ctx: PluginContext, name: BuiltInVolumeRepresentationsName, volume: VolumeData, volumeParams?: Partial<PD.Values<VolumeParams>>): StateTransformer.Params<VolumeRepresentation3D> {
-        const type = ctx.volumeRepresentation.registry.get(name);
+    export function getDefaultParams(ctx: PluginContext, name: VolumeRepresentationRegistry.BuiltIn, volume: VolumeData, volumeParams?: Partial<PD.Values<VolumeParams>>): StateTransformer.Params<VolumeRepresentation3D> {
+        const type = ctx.representation.volume.registry.get(name);
 
         const themeDataCtx = { volume };
-        const colorParams = ctx.volumeRepresentation.themeCtx.colorThemeRegistry.get(type.defaultColorTheme.name).getParams(themeDataCtx);
-        const sizeParams = ctx.volumeRepresentation.themeCtx.sizeThemeRegistry.get(type.defaultSizeTheme.name).getParams(themeDataCtx)
-        const volumeDefaultParams = PD.getDefaultValues(type.getParams(ctx.volumeRepresentation.themeCtx, volume))
+        const colorParams = ctx.representation.volume.themes.colorThemeRegistry.get(type.defaultColorTheme.name).getParams(themeDataCtx);
+        const sizeParams = ctx.representation.volume.themes.sizeThemeRegistry.get(type.defaultSizeTheme.name).getParams(themeDataCtx)
+        const volumeDefaultParams = PD.getDefaultValues(type.getParams(ctx.representation.volume.themes, volume))
         return ({
             type: { name, params: volumeParams ? { ...volumeDefaultParams, ...volumeParams } : volumeDefaultParams },
             colorTheme: { name: type.defaultColorTheme.name, params: PD.getDefaultValues(colorParams) },
@@ -431,10 +431,10 @@ export namespace VolumeRepresentation3DHelpers {
         })
     }
 
-    export function getDefaultParamsStatic(ctx: PluginContext, name: BuiltInVolumeRepresentationsName, volumeParams?: Partial<PD.Values<PD.Params>>, colorName?: BuiltInColorThemeName, colorParams?: Partial<ColorTheme.Props>, sizeName?: BuiltInSizeThemeName, sizeParams?: Partial<SizeTheme.Props>): StateTransformer.Params<VolumeRepresentation3D> {
-        const type = ctx.volumeRepresentation.registry.get(name);
-        const colorType = ctx.volumeRepresentation.themeCtx.colorThemeRegistry.get(colorName || type.defaultColorTheme.name);
-        const sizeType = ctx.volumeRepresentation.themeCtx.sizeThemeRegistry.get(sizeName || type.defaultSizeTheme.name);
+    export function getDefaultParamsStatic(ctx: PluginContext, name: VolumeRepresentationRegistry.BuiltIn, volumeParams?: Partial<PD.Values<PD.Params>>, colorName?: ColorTheme.BuiltIn, colorParams?: Partial<ColorTheme.Props>, sizeName?: SizeTheme.BuiltIn, sizeParams?: Partial<SizeTheme.Props>): StateTransformer.Params<VolumeRepresentation3D> {
+        const type = ctx.representation.volume.registry.get(name);
+        const colorType = ctx.representation.volume.themes.colorThemeRegistry.get(colorName || type.defaultColorTheme.name);
+        const sizeType = ctx.representation.volume.themes.sizeThemeRegistry.get(sizeName || type.defaultSizeTheme.name);
         return ({
             type: { name, params: volumeParams ? { ...type.defaultValues, ...volumeParams } : type.defaultValues },
             colorTheme: { name: type.defaultColorTheme.name, params: colorParams ? { ...colorType.defaultValues, ...colorParams } : colorType.defaultValues },
@@ -453,7 +453,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
     from: SO.Volume.Data,
     to: SO.Volume.Representation3D,
     params: (a, ctx: PluginContext) => {
-        const { registry, themeCtx } = ctx.volumeRepresentation
+        const { registry, themes: themeCtx } = ctx.representation.volume
         const type = registry.get(registry.default.name);
 
         if (!a) {
@@ -501,11 +501,11 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Volume Representation', async ctx => {
             const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
-            const provider = plugin.volumeRepresentation.registry.get(params.type.name)
+            const provider = plugin.representation.volume.registry.get(params.type.name)
             if (provider.ensureCustomProperties) await provider.ensureCustomProperties.attach(propertyCtx, a.data)
             const props = params.type.params || {}
-            const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
-            repr.setTheme(Theme.create(plugin.volumeRepresentation.themeCtx, { volume: a.data }, params))
+            const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams)
+            repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, params))
             // TODO set initial state, repr.setState({})
             await repr.createOrUpdate(props, a.data).runInContext(ctx);
             return new SO.Volume.Representation3D({ repr, source: a }, { label: provider.label, description: VolumeRepresentation3DHelpers.getDescription(props) });
@@ -514,7 +514,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
     update({ a, b, oldParams, newParams }, plugin: PluginContext) {
         return Task.create('Volume Representation', async ctx => {
             if (newParams.type.name !== oldParams.type.name) {
-                const oldProvider = plugin.volumeRepresentation.registry.get(oldParams.type.name);
+                const oldProvider = plugin.representation.volume.registry.get(oldParams.type.name);
                 if (oldProvider.ensureCustomProperties) {
                     const propertyCtx = { runtime: ctx, fetch: plugin.fetch }
                     oldProvider.ensureCustomProperties.detach(propertyCtx, a.data)
@@ -522,7 +522,7 @@ const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
                 return StateTransformer.UpdateResult.Recreate;
             }
             const props = { ...b.data.repr.props, ...newParams.type.params }
-            b.data.repr.setTheme(Theme.create(plugin.volumeRepresentation.themeCtx, { volume: a.data }, newParams))
+            b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: a.data }, newParams))
             await b.data.repr.createOrUpdate(props, a.data).runInContext(ctx);
             b.description = VolumeRepresentation3DHelpers.getDescription(props)
             return StateTransformer.UpdateResult.Updated;
@@ -612,7 +612,7 @@ const StructureSelectionsDistance3D = PluginStateTransform.BuiltIn({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Distance', async ctx => {
             const data = getDistanceDataFromStructureSelections(a.data)
-            const repr = DistanceRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, () => DistanceParams)
+            const repr = DistanceRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DistanceParams)
             await repr.createOrUpdate(params, data).runInContext(ctx);
             return new SO.Shape.Representation3D({ repr, source: a }, { label: `Distance` });
         });
@@ -645,7 +645,7 @@ const StructureSelectionsAngle3D = PluginStateTransform.BuiltIn({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Angle', async ctx => {
             const data = getAngleDataFromStructureSelections(a.data)
-            const repr = AngleRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, () => AngleParams)
+            const repr = AngleRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AngleParams)
             await repr.createOrUpdate(params, data).runInContext(ctx);
             return new SO.Shape.Representation3D({ repr, source: a }, { label: `Angle` });
         });
@@ -678,7 +678,7 @@ const StructureSelectionsDihedral3D = PluginStateTransform.BuiltIn({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Dihedral', async ctx => {
             const data = getDihedralDataFromStructureSelections(a.data)
-            const repr = DihedralRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, () => DihedralParams)
+            const repr = DihedralRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => DihedralParams)
             await repr.createOrUpdate(params, data).runInContext(ctx);
             return new SO.Shape.Representation3D({ repr, source: a }, { label: `Dihedral` });
         });
@@ -711,7 +711,7 @@ const StructureSelectionsLabel3D = PluginStateTransform.BuiltIn({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Label', async ctx => {
             const data = getLabelDataFromStructureSelections(a.data)
-            const repr = LabelRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, () => LabelParams)
+            const repr = LabelRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => LabelParams)
             await repr.createOrUpdate(params, data).runInContext(ctx);
             return new SO.Shape.Representation3D({ repr, source: a }, { label: `Label` });
         });
@@ -744,7 +744,7 @@ const StructureSelectionsOrientation3D = PluginStateTransform.BuiltIn({
     apply({ a, params }, plugin: PluginContext) {
         return Task.create('Structure Orientation', async ctx => {
             const data = getOrientationDataFromStructureSelections(a.data)
-            const repr = OrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, () => OrientationParams)
+            const repr = OrientationRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => OrientationParams)
             await repr.createOrUpdate(params, data).runInContext(ctx);
             return new SO.Shape.Representation3D({ repr, source: a }, { label: `Orientation` });
         });

+ 76 - 55
src/mol-plugin-ui/controls/action-menu.tsx

@@ -14,14 +14,7 @@ export class ActionMenu extends React.PureComponent<ActionMenu.Props> {
 
     render() {
         const cmd = this.props;
-
         return <div className='msp-action-menu-options' style={{ marginTop: cmd.header ? void 0 : '1px' }}>
-            {/* {cmd.header && <div className='msp-control-group-header' style={{ position: 'relative' }}>
-                <button className='msp-btn msp-btn-block' onClick={this.hide}>
-                    <Icon name='off' style={{ position: 'absolute', right: '2px', top: 0 }} />
-                    <b>{cmd.header}</b>
-                </button>
-            </div>} */}
             {cmd.header && <ControlGroup header={cmd.header} initialExpanded={true} hideExpander={true} hideOffset={false} onHeaderClick={this.hide} topRightIcon='off'></ControlGroup>}
             <Section items={cmd.items} onSelect={cmd.onSelect} current={cmd.current} multiselect={this.props.multiselect} />
         </div>
@@ -34,42 +27,58 @@ export namespace ActionMenu {
     export type OnSelect = (item: Item | undefined) => void
     export type OnSelectMany = (itemOrItems: Item[] | undefined) => void
 
-    export type Items =  string | Item | Items[]
-    export type Item = { label: string, icon?: IconName, disabled?: boolean, selected?: boolean, value: unknown }
+    export type Items =  Header | Item | Items[]
+    export type Header = { kind: 'header', label: string, isIndependent?: boolean, initiallyExpanded?: boolean }
+    export type Item = { kind: 'item', label: string, icon?: IconName, disabled?: boolean, selected?: boolean, value: unknown }
+
+    export function Header(label: string, options?: { isIndependent?: boolean, initiallyExpanded?: boolean }): Header {
+        return options ? { kind: 'header', label, ...options } : { kind: 'header', label };
+    }
 
     export function Item(label: string, value: unknown): Item
     export function Item(label: string, icon: string, value: unknown): Item
     export function Item(label: string, iconOrValue: any, value?: unknown): Item {
-        if (value) return { label, icon: iconOrValue, value };
-        return { label, value: iconOrValue };
+        if (value) return { kind: 'item', label, icon: iconOrValue, value };
+        return { kind: 'item', label, value: iconOrValue };
     }
 
-    export function createItems<T>(xs: ArrayLike<T>, options?: { filter?: (t: T) => boolean, label?: (t: T) => string, value?: (t: T) => any, category?: (t: T) => string | undefined }) {
-        const { label, value, category } = options || { };
-        let cats: Map<string, (ActionMenu.Item | string)[]> | undefined = void 0;
-        const items: (ActionMenu.Item | (ActionMenu.Item | string)[] | string)[] = [];
+    export interface CreateItemsParams<T> {
+        filter?: (t: T) => boolean,
+        label?: (t: T) => string,
+        value?: (t: T) => any,
+        category?: (t: T) => string | undefined,
+        icon?: (t: T) => IconName | undefined,
+        selected?: (t: T) => boolean | undefined
+    }
+
+    export function createItems<T>(xs: ArrayLike<T>, params?: CreateItemsParams<T>) {
+        const { label, value, category, selected, icon } = params || { };
+        let cats: Map<string, (ActionMenu.Item | ActionMenu.Header)[]> | undefined = void 0;
+        const items: (ActionMenu.Item | (ActionMenu.Item | ActionMenu.Header)[] | string)[] = [];
         for (let i = 0; i < xs.length; i++) {
             const x = xs[i];
 
-            if (options?.filter && !options.filter(x)) continue;
+            if (params?.filter && !params.filter(x)) continue;
 
             const catName = category?.(x);
             const l = label ? label(x) : '' + x;
             const v = value ? value(x) : x;
 
+            let cat: (ActionMenu.Item | ActionMenu.Header)[] | undefined;
             if (!!catName) {
-                if (!cats) cats = new Map<string, (ActionMenu.Item | string)[]>();
+                if (!cats) cats = new Map<string, (ActionMenu.Item | ActionMenu.Header)[]>();
 
-                let cat = cats.get(catName);
+                cat = cats.get(catName);
                 if (!cat) {
-                    cat = [catName];
+                    cat = [ActionMenu.Header(catName)];
                     cats.set(catName, cat);
                     items.push(cat);
                 }
-                cat.push(ActionMenu.Item(l, v));
             } else {
-                items.push(ActionMenu.Item(l, v));
+                cat = items as any;
             }
+            
+            cat!.push({ kind: 'item', label: l, value: v, icon: icon ? icon(x) : void 0, selected: selected ? selected(x) : void 0 });
         }
         return items as ActionMenu.Items;
     }
@@ -77,12 +86,12 @@ export namespace ActionMenu {
     type Opt = ParamDefinition.Select<any>['options'][0];
     const _selectOptions = { value: (o: Opt) => o[0], label: (o: Opt) => o[1], category: (o: Opt) => o[2] };
 
-    export function createItemsFromSelectParam(param: ParamDefinition.Select<any>) {
-        return createItems(param.options, _selectOptions);
+    export function createItemsFromSelectOptions(options: ParamDefinition.Select<any>['options'], params?: CreateItemsParams<ParamDefinition.Select<any>['options'][0]>) {
+        return createItems(options, params ? { ..._selectOptions, ...params } : _selectOptions);
     }
 
     export function hasSelectedItem(items: Items): boolean {
-        if (typeof items === 'string') return false;
+        if (isHeader(items)) return false;
         if (isItem(items)) return !!items.selected;
         for (const s of items) {
             const found = hasSelectedItem(s);
@@ -92,7 +101,7 @@ export namespace ActionMenu {
     }
 
     export function findItem(items: Items, value: any): Item | undefined {
-        if (typeof items === 'string') return;
+        if (isHeader(items)) return;
         if (isItem(items)) return items.value === value ? items : void 0;
         for (const s of items) {
             const found = findItem(s, value);
@@ -101,7 +110,7 @@ export namespace ActionMenu {
     }
 
     export function getFirstItem(items: Items): Item | undefined {
-        if (typeof items === 'string') return;
+        if (isHeader(items)) return;
         if (isItem(items)) return items;
         for (const s of items) {
             const found = getFirstItem(s);
@@ -110,22 +119,30 @@ export namespace ActionMenu {
     }
 }
 
-type SectionProps = { header?: string, items: ActionMenu.Items, onSelect: ActionMenu.OnSelect | ActionMenu.OnSelectMany, current: ActionMenu.Item | undefined, multiselect: boolean | undefined }
-type SectionState = { items: ActionMenu.Items, current: ActionMenu.Item | undefined, isExpanded: boolean }
+type SectionProps = { items: ActionMenu.Items, onSelect: ActionMenu.OnSelect | ActionMenu.OnSelectMany, current: ActionMenu.Item | undefined, multiselect: boolean | undefined }
+type SectionState = { items: ActionMenu.Items, current: ActionMenu.Item | undefined, isExpanded: boolean, hasCurrent: boolean, header?: ActionMenu.Header }
 
 class Section extends React.PureComponent<SectionProps, SectionState> {
-    state = {
-        items: this.props.items,
-        current: this.props.current,
-        isExpanded: this.hasCurrent
-    }
+    static createState(props: SectionProps): SectionState {
+        const header = isItems(props.items) && isHeader(props.items[0]) ? props.items[0] : void 0;
 
-    get hasCurrent() {
-        return this.props.multiselect
-            ? ActionMenu.hasSelectedItem(this.props.items)
-            : !!this.props.current && !!ActionMenu.findItem(this.props.items, this.props.current.value);
+        const hasCurrent = header?.isIndependent
+            ? false 
+            : props.multiselect
+                ? ActionMenu.hasSelectedItem(props.items)
+                : (!!props.current && !!ActionMenu.findItem(props.items, props.current.value)) || ActionMenu.hasSelectedItem(props.items);
+
+        return {
+            items: props.items,
+            current: props.current,
+            header,
+            hasCurrent,
+            isExpanded: hasCurrent || !!header?.initiallyExpanded
+        };
     }
 
+    state = Section.createState(this.props)
+
     toggleExpanded = (e: React.MouseEvent<HTMLButtonElement>) => {
         this.setState({ isExpanded: !this.state.isExpanded });
         e.currentTarget.blur();
@@ -133,12 +150,7 @@ class Section extends React.PureComponent<SectionProps, SectionState> {
 
     static getDerivedStateFromProps(props: SectionProps, state: SectionState) {
         if (props.items === state.items && props.current === state.current) return null;
-        return {
-            items: props.items,
-            current: props.current,
-            isExpanded: props.multiselect
-                ? ActionMenu.hasSelectedItem(props.items)
-                : props.current && !!ActionMenu.findItem(props.items, props.current.value) }
+        return Section.createState(props);
     }
 
     selectAll = () => {
@@ -152,13 +164,12 @@ class Section extends React.PureComponent<SectionProps, SectionState> {
     }
 
     get multiselectHeader() {
-        const { header } = this.props;
-        const hasCurrent = this.hasCurrent;
+        const { header, hasCurrent } = this.state;
 
         return <div className='msp-control-group-header msp-flex-row' style={{ marginTop: '1px' }}>
             <button className='msp-btn msp-form-control msp-flex-item' onClick={this.toggleExpanded}>
                 <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} />
-                {hasCurrent ? <b>{header}</b> : header}
+                {hasCurrent ? <b>{header?.label}</b> : header?.label}
             </button>
             <button className='msp-btn msp-form-control msp-flex-item' onClick={this.selectAll} style={{ flex: '0 0 50px', textAlign: 'right' }}>
                 <Icon name='check' />
@@ -172,30 +183,31 @@ class Section extends React.PureComponent<SectionProps, SectionState> {
     }
 
     get basicHeader() {
-        const { header } = this.props;
-        const hasCurrent = this.hasCurrent;
+        const { header, hasCurrent } = this.state;
 
         return <div className='msp-control-group-header' style={{ marginTop: '1px' }}>
             <button className='msp-btn msp-btn-block msp-form-control' onClick={this.toggleExpanded}>
                 <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} />
-                {hasCurrent ? <b>{header}</b> : header}
+                {hasCurrent ? <b>{header?.label}</b> : header?.label}
             </button>
         </div>;
     }
 
     render() {
-        const { header, items, onSelect, current } = this.props;
+        const { items, onSelect, current } = this.props;
 
-        if (typeof items === 'string') return null;
+        if (isHeader(items)) return null;
         if (isItem(items)) return <Action item={items} onSelect={onSelect} current={current} multiselect={this.props.multiselect} />
 
+        const { header } = this.state;
+
         return <div>
             {header && (this.props.multiselect && this.state.isExpanded ? this.multiselectHeader : this.basicHeader)}
             <div className='msp-control-offset'>
                 {(!header || this.state.isExpanded) && items.map((x, i) => {
-                    if (typeof x === 'string') return null;
+                    if (isHeader(x)) return null;
                     if (isItem(x)) return <Action key={i} item={x} onSelect={onSelect} current={current} multiselect={this.props.multiselect} />
-                    return <Section key={i} header={typeof x[0] === 'string' ? x[0] : void 0} items={x} onSelect={onSelect} current={current} multiselect={this.props.multiselect} />
+                    return <Section key={i} items={x} onSelect={onSelect} current={current} multiselect={this.props.multiselect} />
                 })}
             </div>
         </div>;
@@ -214,13 +226,22 @@ const Action: React.FC<{
     </button>;
 }
 
+function isItems(x: any): x is ActionMenu.Items[] {
+    return !!x && Array.isArray(x);
+}
+
 function isItem(x: any): x is ActionMenu.Item {
     const v = x as ActionMenu.Item;
-    return v && !!v.label && typeof v.value !== 'undefined';
+    return v && v.kind === 'item';
+}
+
+function isHeader(x: any): x is ActionMenu.Header {
+    const v = x as ActionMenu.Header;
+    return v && v.kind === 'header';
 }
 
 function collectItems(items: ActionMenu.Items, target: ActionMenu.Item[]) {
-    if (typeof items === 'string') return target;
+    if (isHeader(items)) return target;
     if (isItem(items)) {
         target.push(items);
         return target;

+ 2 - 2
src/mol-plugin-ui/controls/common.tsx

@@ -335,7 +335,7 @@ export class ToggleButton extends React.PureComponent<ToggleButtonProps> {
     }
 }
 
-export class ExpandGroup extends React.PureComponent<{ header: string, initiallyExpanded?: boolean, noOffset?: boolean, marginTop?: 0 | string }, { isExpanded: boolean }> {
+export class ExpandGroup extends React.PureComponent<{ header: string, headerStyle?: React.CSSProperties, initiallyExpanded?: boolean, noOffset?: boolean, marginTop?: 0 | string }, { isExpanded: boolean }> {
     state = { isExpanded: !!this.props.initiallyExpanded };
 
     toggleExpanded = () => this.setState({ isExpanded: !this.state.isExpanded });
@@ -343,7 +343,7 @@ export class ExpandGroup extends React.PureComponent<{ header: string, initially
     render() {
         return <>
             <div className='msp-control-group-header' style={{ marginTop: this.props.marginTop !== void 0 ? this.props.marginTop : '1px' }}>
-                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded}>
+                <button className='msp-btn msp-btn-block' onClick={this.toggleExpanded} style={this.props.headerStyle}>
                     <Icon name={this.state.isExpanded ? 'collapse' : 'expand'} />
                     {this.props.header}
                 </button>

+ 1 - 1
src/mol-plugin-ui/controls/parameters.tsx

@@ -447,7 +447,7 @@ export class SelectControl extends React.PureComponent<ParamProps<PD.Select<stri
         this.props.onChange({ param: this.props.param, name: this.props.name, value: options[next][0] });
     };
 
-    items = memoizeLatest((param: PD.Select<any>) => ActionMenu.createItemsFromSelectParam(param));
+    items = memoizeLatest((param: PD.Select<any>) => ActionMenu.createItemsFromSelectOptions(param.options));
 
     renderControl() {
         const items = this.items(this.props.param);

+ 29 - 9
src/mol-plugin-ui/structure/components.tsx

@@ -17,6 +17,7 @@ import { Icon } from '../controls/icons';
 import { ParameterControls } from '../controls/parameters';
 import { UpdateTransformControl } from '../state/update-transform';
 import { PluginContext } from '../../mol-plugin/context';
+import { getStructureThemeTypes } from '../../mol-plugin-state/helpers/structure-representation-params';
 
 interface StructureComponentControlState extends CollapsableState {
     isDisabled: boolean
@@ -237,19 +238,37 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
         this.plugin.managers.structure.component.toggleVisibility(this.props.group);
     }
 
+    get colorByActions() {
+        const mng = this.plugin.managers.structure.component;
+        const repr = this.pivot.representations[0];
+        const name = repr.cell.transform.params?.colorTheme.name;
+        const themes = getStructureThemeTypes(this.plugin, this.pivot.cell.obj?.data);
+        return ActionMenu.createItemsFromSelectOptions(themes, { 
+            value: o => () => mng.updateRepresentationsTheme(this.props.group, { color: o[0] }),
+            selected: o => o[0] === name 
+        }) as ActionMenu.Item[];
+    }
+
     get actions(): ActionMenu.Items {
         const mng = this.plugin.managers.structure.component;
         const ret: ActionMenu.Items = [
             [
-                'Add Representation',
+                ActionMenu.Header('Add Representation'),
                 ...StructureComponentManager.getRepresentationTypes(this.plugin, this.props.group[0])
                     .map(t => ActionMenu.Item(t[1], () => mng.addRepresentation(this.props.group, t[0])))
             ]
         ];
 
+        if (this.pivot.representations.length > 0) {
+            ret.push([
+                ActionMenu.Header('Set Coloring', { isIndependent: true }),
+                ...this.colorByActions
+            ]);
+        }
+
         if (mng.canBeModified(this.props.group[0])) {
             ret.push([
-                'Modify by Selection',
+                ActionMenu.Header('Modify by Selection'),
                 ActionMenu.Item('Include', 'plus', () => mng.modifyByCurrentSelection(this.props.group, 'union')),
                 ActionMenu.Item('Subtract', 'minus', () => mng.modifyByCurrentSelection(this.props.group, 'subtract')),
                 ActionMenu.Item('Intersect', 'shuffle', () => mng.modifyByCurrentSelection(this.props.group, 'intersect'))
@@ -322,10 +341,12 @@ class StructureComponentGroup extends PurePluginUIComponent<{ group: StructureCo
                 </div>
             </div>
             {this.state.action === 'remove' && <ActionMenu items={this.removeActions} onSelect={this.selectRemoveAction} />}
-            {this.state.action === 'action' && <ActionMenu items={this.actions} onSelect={this.selectAction} />}
-            <div className='msp-control-offset'>
-                {component.representations.map(r => <StructureRepresentationEntry group={this.props.group} key={r.cell.transform.ref} representation={r} />)}
-            </div>
+            {this.state.action === 'action' && <>
+                <ActionMenu items={this.actions} onSelect={this.selectAction} />
+                <div className='msp-control-offset'>
+                    {component.representations.map(r => <StructureRepresentationEntry group={this.props.group} key={r.cell.transform.ref} representation={r} />)}
+                </div>
+            </>}
         </div>;
     }
 }
@@ -337,12 +358,11 @@ class StructureRepresentationEntry extends PurePluginUIComponent<{ group: Struct
 
     render() {
         const repr = this.props.representation.cell;
-        // TODO: style in CSS
         return <div style={{ position: 'relative' }}>
-            <ExpandGroup header={`${repr.obj?.label || ''} Representation`} noOffset>
+            <ExpandGroup header={`${repr.obj?.label || ''} Representation`} noOffset headerStyle={{ fontWeight: 'bold' }}>
                 <UpdateTransformControl state={repr.parent} transform={repr.transform} customHeader='none' customUpdate={this.update} noMargin />
                 <IconButton onClick={this.remove} icon='remove' title='Remove' small style={{
-                    position: 'absolute', top: 0, right: 0, lineHeight: '20px', height: '20px', textAlign: 'right', width: '44px', paddingRight: '6px'
+                    position: 'absolute', top: 0, right: 0, lineHeight: '22px', height: '22px', textAlign: 'right', width: '44px', paddingRight: '6px'
                 }} />
             </ExpandGroup>
         </div>;

+ 5 - 5
src/mol-plugin-ui/structure/measurements.tsx

@@ -112,11 +112,11 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
     get actions(): ActionMenu.Items {
         const history = this.selection.additionsHistory;
         const ret: ActionMenu.Item[] = [
-            { label: `Label ${history.length === 0 ? ' (1 selection required)' : ' (1st selection)'}`, value: this.addLabel, disabled: history.length === 0 },
-            { label: `Orientation ${history.length === 0 ? ' (1 selection required)' : ' (1st selection)'}`, value: this.addOrientation, disabled: history.length === 0 },
-            { label: `Distance ${history.length < 2 ? ' (2 selections required)' : ' (top 2 selections)'}`, value: this.measureDistance, disabled: history.length < 2 },
-            { label: `Angle ${history.length < 3 ? ' (3 selections required)' : ' (top 3 selections)'}`, value: this.measureAngle, disabled: history.length < 3 },
-            { label: `Dihedral ${history.length < 4 ? ' (4 selections required)' : ' (top 4 selections)'}`, value: this.measureDihedral, disabled: history.length < 4 },
+            { kind: 'item', label: `Label ${history.length === 0 ? ' (1 selection required)' : ' (1st selection)'}`, value: this.addLabel, disabled: history.length === 0 },
+            { kind: 'item', label: `Orientation ${history.length === 0 ? ' (1 selection required)' : ' (1st selection)'}`, value: this.addOrientation, disabled: history.length === 0 },
+            { kind: 'item', label: `Distance ${history.length < 2 ? ' (2 selections required)' : ' (top 2 selections)'}`, value: this.measureDistance, disabled: history.length < 2 },
+            { kind: 'item', label: `Angle ${history.length < 3 ? ' (3 selections required)' : ' (top 3 selections)'}`, value: this.measureAngle, disabled: history.length < 3 },
+            { kind: 'item', label: `Dihedral ${history.length < 4 ? ' (4 selections required)' : ' (top 4 selections)'}`, value: this.measureDihedral, disabled: history.length < 4 },
         ];
         return ret;
     }

+ 2 - 2
src/mol-plugin-ui/structure/source.tsx

@@ -38,7 +38,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
 
     getTrajectoryItems = (t: TrajectoryRef): ActionMenu.Items => {
         if (t.models.length === 0) return this.item(t);
-        return [t.cell.obj?.label!, ...t.models.map(this.getModelItems)];
+        return [ActionMenu.Header(t.cell.obj?.label!), ...t.models.map(this.getModelItems)];
     }
 
     private getModelItems = (m: ModelRef): ActionMenu.Items => {
@@ -48,7 +48,7 @@ export class StructureSourceControls extends CollapsableControls<{}, StructureSo
             const ref = m.structures[0];
             return { label: `${m.cell.obj?.label} | ${ref.cell.obj?.label}`, selected: selected.has(ref.cell.transform.ref), value: [m, ref] } as ActionMenu.Item;
         }
-        return [m.cell.obj?.label!, ...m.structures.map(this.item)];
+        return [ActionMenu.Header(m.cell.obj?.label!), ...m.structures.map(this.item)];
     }
 
     get hierarchyItems() {

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/computed/accessible-surface-area.ts

@@ -40,7 +40,7 @@ export const AccessibleSurfaceArea = PluginBehavior.create<{ autoAttach: boolean
             DefaultQueryRuntimeTable.addCustomProp(this.provider.descriptor);
 
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(AccessibleSurfaceAreaColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(AccessibleSurfaceAreaColorThemeProvider)
             this.ctx.managers.lociLabels.addProvider(this.label);
         }
 
@@ -49,7 +49,7 @@ export const AccessibleSurfaceArea = PluginBehavior.create<{ autoAttach: boolean
             // DefaultQueryRuntimeTable.removeCustomProp(this.provider.descriptor);
 
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(AccessibleSurfaceAreaColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(AccessibleSurfaceAreaColorThemeProvider)
             this.ctx.managers.lociLabels.removeProvider(this.label);
         }
     },

+ 4 - 4
src/mol-plugin/behavior/dynamic/custom-props/computed/interactions.ts

@@ -99,16 +99,16 @@ export const Interactions = PluginBehavior.create<{ autoAttach: boolean, showToo
 
         register(): void {
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(InteractionTypeColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(InteractionTypeColorThemeProvider)
             this.ctx.managers.lociLabels.addProvider(this.label);
-            this.ctx.structureRepresentation.registry.add(InteractionsRepresentationProvider)
+            this.ctx.representation.structure.registry.add(InteractionsRepresentationProvider)
         }
 
         unregister() {
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(InteractionTypeColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(InteractionTypeColorThemeProvider)
             this.ctx.managers.lociLabels.removeProvider(this.label);
-            this.ctx.structureRepresentation.registry.remove(InteractionsRepresentationProvider)
+            this.ctx.representation.structure.registry.remove(InteractionsRepresentationProvider)
         }
     },
     params: () => ({

+ 4 - 4
src/mol-plugin/behavior/dynamic/custom-props/integrative/cross-link-restraint.ts

@@ -22,15 +22,15 @@ export const CrossLinkRestraint = PluginBehavior.create<{ }>({
         register(): void {
             this.provider.formatRegistry.add('mmCIF', crossLinkRestraintFromMmcif)
 
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(CrossLinkColorThemeProvider)
-            this.ctx.structureRepresentation.registry.add(CrossLinkRestraintRepresentationProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(CrossLinkColorThemeProvider)
+            this.ctx.representation.structure.registry.add(CrossLinkRestraintRepresentationProvider)
         }
 
         unregister() {
             this.provider.formatRegistry.remove('mmCIF')
 
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(CrossLinkColorThemeProvider)
-            this.ctx.structureRepresentation.registry.remove(CrossLinkRestraintRepresentationProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(CrossLinkColorThemeProvider)
+            this.ctx.representation.structure.registry.remove(CrossLinkRestraintRepresentationProvider)
         }
     }
 });

+ 2 - 2
src/mol-plugin/behavior/dynamic/custom-props/pdbe/structure-quality-report.ts

@@ -46,7 +46,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
             this.ctx.customModelProperties.register(this.provider, false);
             this.ctx.managers.lociLabels.addProvider(this.labelPDBeValidation);
 
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(StructureQualityReportColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(StructureQualityReportColorThemeProvider)
         }
 
         update(p: { autoAttach: boolean, showTooltip: boolean }) {
@@ -60,7 +60,7 @@ export const PDBeStructureQualityReport = PluginBehavior.create<{ autoAttach: bo
         unregister() {
             this.ctx.customModelProperties.unregister(StructureQualityReportProvider.descriptor.name);
             this.ctx.managers.lociLabels.removeProvider(this.labelPDBeValidation);
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(StructureQualityReportColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(StructureQualityReportColorThemeProvider)
         }
     },
     params: () => ({

+ 3 - 3
src/mol-plugin/behavior/dynamic/custom-props/rcsb/assembly-symmetry.ts

@@ -29,7 +29,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
         register(): void {
             this.ctx.state.dataState.actions.add(InitAssemblySymmetry3D)
             this.ctx.customStructureProperties.register(this.provider, this.params.autoAttach);
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(AssemblySymmetryClusterColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(AssemblySymmetryClusterColorThemeProvider)
         }
 
         update(p: { autoAttach: boolean }) {
@@ -42,7 +42,7 @@ export const RCSBAssemblySymmetry = PluginBehavior.create<{ autoAttach: boolean
         unregister() {
             this.ctx.state.dataState.actions.remove(InitAssemblySymmetry3D)
             this.ctx.customStructureProperties.unregister(this.provider.descriptor.name);
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(AssemblySymmetryClusterColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(AssemblySymmetryClusterColorThemeProvider)
         }
     },
     params: () => ({
@@ -97,7 +97,7 @@ const AssemblySymmetry3D = PluginStateTransform.BuiltIn({
             if (!assemblySymmetry || assemblySymmetry.length === 0) {
                 return StateObject.Null;
             }
-            const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.structureRepresentation.themeCtx }, () => AssemblySymmetryParams)
+            const repr = AssemblySymmetryRepresentation({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.structure.themes }, () => AssemblySymmetryParams)
             await repr.createOrUpdate(params, a.data).runInContext(ctx);
             const { type, kind, symbol } = assemblySymmetry![params.symmetryIndex]
             return new PluginStateObject.Shape.Representation3D({ repr, source: a }, { label: kind, description: `${type} (${symbol})` });

+ 8 - 8
src/mol-plugin/behavior/dynamic/custom-props/rcsb/validation-report.ts

@@ -42,11 +42,11 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
 
             this.ctx.managers.lociLabels.addProvider(this.label);
 
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(DensityFitColorThemeProvider)
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(GeometryQualityColorThemeProvider)
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.add(RandomCoilIndexColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(DensityFitColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(GeometryQualityColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(RandomCoilIndexColorThemeProvider)
 
-            this.ctx.structureRepresentation.registry.add(ClashesRepresentationProvider)
+            this.ctx.representation.structure.registry.add(ClashesRepresentationProvider)
         }
 
         update(p: { autoAttach: boolean, showTooltip: boolean }) {
@@ -65,11 +65,11 @@ export const RCSBValidationReport = PluginBehavior.create<{ autoAttach: boolean,
 
             this.ctx.managers.lociLabels.removeProvider(this.label);
 
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(DensityFitColorThemeProvider)
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(GeometryQualityColorThemeProvider)
-            this.ctx.structureRepresentation.themeCtx.colorThemeRegistry.remove(RandomCoilIndexColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(DensityFitColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(GeometryQualityColorThemeProvider)
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(RandomCoilIndexColorThemeProvider)
 
-            this.ctx.structureRepresentation.registry.remove(ClashesRepresentationProvider)
+            this.ctx.representation.structure.registry.remove(ClashesRepresentationProvider)
         }
     },
     params: () => ({

+ 2 - 2
src/mol-plugin/behavior/dynamic/selection/structure-representation-interaction.ts

@@ -15,7 +15,7 @@ import { StateTransforms } from '../../../../mol-plugin-state/transforms';
 import { PluginBehavior } from '../../../../mol-plugin/behavior';
 import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
 import { StateObjectCell, StateSelection, StateTransform } from '../../../../mol-state';
-import { BuiltInSizeThemes } from '../../../../mol-theme/size';
+import { SizeTheme } from '../../../../mol-theme/size';
 import { Binding } from '../../../../mol-util/binding';
 import { ButtonsType, ModifiersKeys } from '../../../../mol-util/input/input-observer';
 import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
@@ -52,7 +52,7 @@ const StructureRepresentationInteractionParams = (plugin: PluginContext) => {
             customDefault: createStructureRepresentationParams(plugin, void 0, {
                 type: InteractionsRepresentationProvider,
                 color: InteractionTypeColorThemeProvider,
-                size: BuiltInSizeThemes.uniform
+                size: SizeTheme.BuiltIn.uniform
             })
         })
     };

+ 5 - 5
src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts

@@ -17,7 +17,7 @@ import { StateAction, StateObject, StateTransformer } from '../../../../mol-stat
 import { getStreamingMethod, getIds, getContourLevel, getEmdbIds } from './util';
 import { VolumeStreaming } from './behavior';
 import { VolumeRepresentation3DHelpers } from '../../../../mol-plugin-state/transforms/representation';
-import { BuiltInVolumeRepresentations } from '../../../../mol-repr/volume/registry';
+import { VolumeRepresentationRegistry } from '../../../../mol-repr/volume/registry';
 import { Theme } from '../../../../mol-theme/theme';
 import { Box3D } from '../../../../mol-math/geometry';
 import { Vec3 } from '../../../../mol-math/linear-algebra';
@@ -246,10 +246,10 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
 
         const params = createVolumeProps(a.data, srcParams.channel);
 
-        const provider = BuiltInVolumeRepresentations.isosurface;
+        const provider = VolumeRepresentationRegistry.BuiltIn.isosurface;
         const props = params.type.params || {}
-        const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
-        repr.setTheme(Theme.create(plugin.volumeRepresentation.themeCtx, { volume: channel.data }, params))
+        const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams)
+        repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params))
         await repr.createOrUpdate(props, channel.data).runInContext(ctx);
         return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
     }),
@@ -262,7 +262,7 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
 
         const params = createVolumeProps(a.data, newParams.channel);
         const props = { ...b.data.repr.props, ...params.type.params };
-        b.data.repr.setTheme(Theme.create(plugin.volumeRepresentation.themeCtx, { volume: channel.data }, params))
+        b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params))
         await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
         return StateTransformer.UpdateResult.Updated;
     })

+ 11 - 9
src/mol-plugin/context.ts

@@ -103,18 +103,20 @@ export class PluginContext {
     readonly canvas3d: Canvas3D | undefined;
     readonly layout = new PluginLayout(this);
 
-    readonly structureRepresentation = {
-        registry: new StructureRepresentationRegistry(),
-        themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext,
-    } as const
-
-    readonly volumeRepresentation = {
-        registry: new VolumeRepresentationRegistry(),
-        themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
-    } as const
+    readonly representation = {
+        structure: {
+            registry: new StructureRepresentationRegistry(),
+            themes: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext,
+        },
+        volume: {
+            registry: new VolumeRepresentationRegistry(),
+            themes: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
+        }
+    } as const;
 
     readonly dataFormat = {
         trajectory: TrajectoryFormatRegistry(),
+        // TODO: separate registries for format catgories
         registry: new DataFormatRegistry()
     } as const
 

+ 31 - 30
src/mol-repr/structure/registry.ts

@@ -5,47 +5,48 @@
  */
 
 import { Structure } from '../../mol-model/structure';
-import { RepresentationRegistry } from '../representation';
-import { CartoonRepresentationProvider } from './representation/cartoon';
+import { objectForEach } from '../../mol-util/object';
+import { RepresentationRegistry, RepresentationProvider } from '../representation';
+import { StructureRepresentationState } from './representation';
 import { BallAndStickRepresentationProvider } from './representation/ball-and-stick';
-import { GaussianSurfaceRepresentationProvider } from './representation/gaussian-surface';
 import { CarbohydrateRepresentationProvider } from './representation/carbohydrate';
-import { SpacefillRepresentationProvider } from './representation/spacefill';
-import { PointRepresentationProvider } from './representation/point';
-import { StructureRepresentationState } from './representation';
-import { PuttyRepresentationProvider } from './representation/putty';
-import { MolecularSurfaceRepresentationProvider } from './representation/molecular-surface';
+import { CartoonRepresentationProvider } from './representation/cartoon';
 import { EllipsoidRepresentationProvider } from './representation/ellipsoid';
-import { OrientationRepresentationProvider } from './representation/orientation';
+import { GaussianSurfaceRepresentationProvider } from './representation/gaussian-surface';
 import { LabelRepresentationProvider } from './representation/label';
-import { objectForEach } from '../../mol-util/object';
+import { MolecularSurfaceRepresentationProvider } from './representation/molecular-surface';
+import { OrientationRepresentationProvider } from './representation/orientation';
+import { PointRepresentationProvider } from './representation/point';
+import { PuttyRepresentationProvider } from './representation/putty';
+import { SpacefillRepresentationProvider } from './representation/spacefill';
 
 export class StructureRepresentationRegistry extends RepresentationRegistry<Structure, StructureRepresentationState> {
     constructor() {
         super()
-        objectForEach(BuiltInStructureRepresentations, (p, k) => {
+        objectForEach(StructureRepresentationRegistry.BuiltIn, (p, k) => {
             if (p.name !== k) throw new Error(`Fix BuiltInStructureRepresentations to have matching names. ${p.name} ${k}`);
             this.add(p as any)
         })
     }
 }
 
-export const BuiltInStructureRepresentations = {
-    'cartoon': CartoonRepresentationProvider,
-    'ball-and-stick': BallAndStickRepresentationProvider,
-    'carbohydrate': CarbohydrateRepresentationProvider,
-    'ellipsoid': EllipsoidRepresentationProvider,
-    'gaussian-surface': GaussianSurfaceRepresentationProvider,
-    // 'gaussian-volume': GaussianVolumeRepresentationProvider, // TODO disabled for now, needs more work
-    'label': LabelRepresentationProvider,
-    'molecular-surface': MolecularSurfaceRepresentationProvider,
-    'orientation': OrientationRepresentationProvider,
-    'point': PointRepresentationProvider,
-    'putty': PuttyRepresentationProvider,
-    'spacefill': SpacefillRepresentationProvider,
-}
-
-export type BuiltInStructureRepresentations = typeof BuiltInStructureRepresentations
-export type BuiltInStructureRepresentationsName = keyof typeof BuiltInStructureRepresentations
-export const BuiltInStructureRepresentationsNames = Object.keys(BuiltInStructureRepresentations)
-export const BuiltInStructureRepresentationsOptions = BuiltInStructureRepresentationsNames.map(n => [n, n] as [BuiltInStructureRepresentationsName, string])
+export namespace StructureRepresentationRegistry {
+    export const BuiltIn = {
+        'cartoon': CartoonRepresentationProvider,
+        'ball-and-stick': BallAndStickRepresentationProvider,
+        'carbohydrate': CarbohydrateRepresentationProvider,
+        'ellipsoid': EllipsoidRepresentationProvider,
+        'gaussian-surface': GaussianSurfaceRepresentationProvider,
+        // 'gaussian-volume': GaussianVolumeRepresentationProvider, // TODO disabled for now, needs more work
+        'label': LabelRepresentationProvider,
+        'molecular-surface': MolecularSurfaceRepresentationProvider,
+        'orientation': OrientationRepresentationProvider,
+        'point': PointRepresentationProvider,
+        'putty': PuttyRepresentationProvider,
+        'spacefill': SpacefillRepresentationProvider,
+    }
+    
+    type _BuiltIn = typeof BuiltIn
+    export type BuiltIn = keyof _BuiltIn
+    export type BuiltInParams<T extends BuiltIn> = Partial<RepresentationProvider.ParamValues<_BuiltIn[T]>>
+}

+ 12 - 12
src/mol-repr/structure/representation.ts

@@ -5,17 +5,17 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { ParamDefinition as PD } from '../../mol-util/param-definition';
-import { Representation, RepresentationProps, RepresentationProvider } from '../representation';
-import { StructureUnitTransforms } from '../../mol-model/structure/structure/util/unit-transforms';
-import { Structure } from '../../mol-model/structure';
+import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
+import { Lines } from '../../mol-geo/geometry/lines/lines';
 import { Mesh } from '../../mol-geo/geometry/mesh/mesh';
-import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
 import { Points } from '../../mol-geo/geometry/points/points';
-import { Lines } from '../../mol-geo/geometry/lines/lines';
-import { DirectVolume } from '../../mol-geo/geometry/direct-volume/direct-volume';
-import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
+import { Spheres } from '../../mol-geo/geometry/spheres/spheres';
 import { Text } from '../../mol-geo/geometry/text/text';
+import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
+import { Structure } from '../../mol-model/structure';
+import { StructureUnitTransforms } from '../../mol-model/structure/structure/util/unit-transforms';
+import { ParamDefinition as PD } from '../../mol-util/param-definition';
+import { Representation, RepresentationProps, RepresentationProvider } from '../representation';
 import { UnitKind, UnitKindOptions } from './visual/util/common';
 
 export function getUnitKindsParam(defaultValue: UnitKind[]) {
@@ -72,7 +72,7 @@ export type StructureDirectVolumeParams = typeof StructureDirectVolumeParams
 export const StructureTextureMeshParams = { ...TextureMesh.Params }
 export type StructureTextureMeshParams = typeof StructureTextureMeshParams
 
-export { ComplexRepresentation } from './complex-representation'
-export { UnitsRepresentation } from './units-representation'
-export { ComplexVisual } from './complex-visual'
-export { UnitsVisual } from './units-visual'
+export { ComplexRepresentation } from './complex-representation';
+export { ComplexVisual } from './complex-visual';
+export { UnitsRepresentation } from './units-representation';
+export { UnitsVisual } from './units-visual';

+ 15 - 14
src/mol-repr/volume/registry.ts

@@ -4,27 +4,28 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { RepresentationRegistry, Representation } from '../representation';
+import { RepresentationRegistry, Representation, RepresentationProvider } from '../representation';
 import { VolumeData } from '../../mol-model/volume';
 import { IsosurfaceRepresentationProvider } from './isosurface';
 import { objectForEach } from '../../mol-util/object';
 
 export class VolumeRepresentationRegistry extends RepresentationRegistry<VolumeData, Representation.State> {
     constructor() {
-        super()
-        Object.keys(BuiltInVolumeRepresentations).forEach(name => {
-            objectForEach(BuiltInVolumeRepresentations, (p, k) => {
-                if (p.name !== k) throw new Error(`Fix BuiltInVolumeRepresentations to have matching names. ${p.name} ${k}`);
-                this.add(p as any)
-            })
+        super()        
+        objectForEach(VolumeRepresentationRegistry.BuiltIn, (p, k) => {
+            if (p.name !== k) throw new Error(`Fix BuiltInVolumeRepresentations to have matching names. ${p.name} ${k}`);
+            this.add(p as any)
         })
     }
 }
 
-export const BuiltInVolumeRepresentations = {
-    'isosurface': IsosurfaceRepresentationProvider,
-    // 'direct-volume': DirectVolumeRepresentationProvider, // TODO disabled for now, needs more work
-}
-export type BuiltInVolumeRepresentationsName = keyof typeof BuiltInVolumeRepresentations
-export const BuiltInVolumeRepresentationsNames = Object.keys(BuiltInVolumeRepresentations)
-export const BuiltInVolumeRepresentationsOptions = BuiltInVolumeRepresentationsNames.map(n => [n, n] as [BuiltInVolumeRepresentationsName, string])
+export namespace VolumeRepresentationRegistry {
+    export const BuiltIn = {
+        'isosurface': IsosurfaceRepresentationProvider,
+        // 'direct-volume': DirectVolumeRepresentationProvider, // TODO disabled for now, needs more work
+    }
+
+    type _BuiltIn = typeof BuiltIn
+    export type BuiltIn = keyof _BuiltIn
+    export type BuiltInParams<T extends BuiltIn> = Partial<RepresentationProvider.ParamValues<_BuiltIn[T]>>
+}

+ 28 - 28
src/mol-theme/color.ts

@@ -75,36 +75,36 @@ namespace ColorTheme {
 
     export type Registry = ThemeRegistry<ColorTheme<any>>
     export function createRegistry() {
-        return new ThemeRegistry(BuiltInColorThemes as { [k: string]: Provider<any> }, EmptyProvider)
+        return new ThemeRegistry(BuiltIn as { [k: string]: Provider<any> }, EmptyProvider)
     }
 
+    export const BuiltIn = {
+        'carbohydrate-symbol': CarbohydrateSymbolColorThemeProvider,
+        'chain-id': ChainIdColorThemeProvider,
+        'element-index': ElementIndexColorThemeProvider,
+        'element-symbol': ElementSymbolColorThemeProvider,
+        'entity-source': EntitySourceColorThemeProvider,
+        'hydrophobicity': HydrophobicityColorThemeProvider,
+        'illustrative': IllustrativeColorThemeProvider,
+        'model-index': ModelIndexColorThemeProvider,
+        'molecule-type': MoleculeTypeColorThemeProvider,
+        'occupancy': OccupancyColorThemeProvider,
+        'operator-hkl': OperatorHklColorThemeProvider,
+        'operator-name': OperatorNameColorThemeProvider,
+        'polymer-id': PolymerIdColorThemeProvider,
+        'polymer-index': PolymerIndexColorThemeProvider,
+        'residue-name': ResidueNameColorThemeProvider,
+        'secondary-structure': SecondaryStructureColorThemeProvider,
+        'sequence-id': SequenceIdColorThemeProvider,
+        'shape-group': ShapeGroupColorThemeProvider,
+        'uncertainty': UncertaintyColorThemeProvider,
+        'unit-index': UnitIndexColorThemeProvider,
+        'uniform': UniformColorThemeProvider,
+    }
+    type _BuiltIn = typeof BuiltIn
+    export type BuiltIn = keyof _BuiltIn
     export type ParamValues<C extends ColorTheme.Provider<any>> = C extends ColorTheme.Provider<infer P> ? PD.Values<P> : never
+    export type BuiltInParams<T extends BuiltIn> = Partial<ParamValues<_BuiltIn[T]>>
 }
 
-export function ColorThemeProvider<P extends PD.Params, Id extends string>(p: ColorTheme.Provider<P, Id>): ColorTheme.Provider<P, Id> { return p; }
-
-export const BuiltInColorThemes = {
-    'carbohydrate-symbol': CarbohydrateSymbolColorThemeProvider,
-    'chain-id': ChainIdColorThemeProvider,
-    'element-index': ElementIndexColorThemeProvider,
-    'element-symbol': ElementSymbolColorThemeProvider,
-    'entity-source': EntitySourceColorThemeProvider,
-    'hydrophobicity': HydrophobicityColorThemeProvider,
-    'illustrative': IllustrativeColorThemeProvider,
-    'model-index': ModelIndexColorThemeProvider,
-    'molecule-type': MoleculeTypeColorThemeProvider,
-    'occupancy': OccupancyColorThemeProvider,
-    'operator-hkl': OperatorHklColorThemeProvider,
-    'operator-name': OperatorNameColorThemeProvider,
-    'polymer-id': PolymerIdColorThemeProvider,
-    'polymer-index': PolymerIndexColorThemeProvider,
-    'residue-name': ResidueNameColorThemeProvider,
-    'secondary-structure': SecondaryStructureColorThemeProvider,
-    'sequence-id': SequenceIdColorThemeProvider,
-    'shape-group': ShapeGroupColorThemeProvider,
-    'uncertainty': UncertaintyColorThemeProvider,
-    'unit-index': UnitIndexColorThemeProvider,
-    'uniform': UniformColorThemeProvider,
-}
-export type BuiltInColorThemes = typeof BuiltInColorThemes
-export type BuiltInColorThemeName = keyof typeof BuiltInColorThemes
+export function ColorThemeProvider<P extends PD.Params, Id extends string>(p: ColorTheme.Provider<P, Id>): ColorTheme.Provider<P, Id> { return p; }

+ 11 - 11
src/mol-theme/size.ts

@@ -36,17 +36,17 @@ namespace SizeTheme {
 
     export type Registry = ThemeRegistry<SizeTheme<any>>
     export function createRegistry() {
-        return new ThemeRegistry(BuiltInSizeThemes as { [k: string]: Provider<any> }, EmptyProvider)
+        return new ThemeRegistry(BuiltIn as { [k: string]: Provider<any> }, EmptyProvider)
     }
 
+    export const BuiltIn = {
+        'physical': PhysicalSizeThemeProvider,
+        'shape-group': ShapeGroupSizeThemeProvider,
+        'uncertainty': UncertaintySizeThemeProvider,
+        'uniform': UniformSizeThemeProvider
+    }
+    type _BuiltIn = typeof BuiltIn
+    export type BuiltIn = keyof _BuiltIn
     export type ParamValues<C extends SizeTheme.Provider<any>> = C extends SizeTheme.Provider<infer P> ? PD.Values<P> : never
-}
-
-export const BuiltInSizeThemes = {
-    'physical': PhysicalSizeThemeProvider,
-    'shape-group': ShapeGroupSizeThemeProvider,
-    'uncertainty': UncertaintySizeThemeProvider,
-    'uniform': UniformSizeThemeProvider
-}
-export type BuiltInSizeThemes = typeof BuiltInSizeThemes
-export type BuiltInSizeThemeName = keyof typeof BuiltInSizeThemes
+    export type BuiltInParams<T extends BuiltIn> = Partial<ParamValues<_BuiltIn[T]>>
+}