/** * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose * @author David Sehnal */ import { produce } from 'immer'; import { throttleTime } from 'rxjs'; import { Canvas3DParams, Canvas3DProps } from '../../mol-canvas3d/canvas3d'; import { PluginCommands } from '../../mol-plugin/commands'; import { PluginConfig } from '../../mol-plugin/config'; import { StateTransform } from '../../mol-state'; import { Color } from '../../mol-util/color'; import { deepClone } from '../../mol-util/object'; import { ParamDefinition as PD } from '../../mol-util/param-definition'; import { ParamMapping } from '../../mol-util/param-mapping'; import { Mutable } from '../../mol-util/type-helpers'; import { PluginUIComponent } from '../base'; import { PluginUIContext } from '../context'; import { ParameterMappingControl } from '../controls/parameters'; import { ViewportHelpContent } from './help'; export class SimpleSettingsControl extends PluginUIComponent { componentDidMount() { if (!this.plugin.canvas3d) return; this.subscribe(this.plugin.events.canvas3d.settingsUpdated, () => this.forceUpdate()); this.subscribe(this.plugin.canvas3d!.camera.stateChanged.pipe(throttleTime(500, undefined, { leading: true, trailing: true })), state => { if (state.radiusMax !== undefined || state.radius !== undefined) { this.forceUpdate(); } }); } render() { if (!this.plugin.canvas3d) return null; return <> ; } } const LayoutOptions = { 'sequence': 'Sequence', 'log': 'Log', 'left': 'Left Panel', 'right': 'Right Panel', }; type LayoutOptions = keyof typeof LayoutOptions const SimpleSettingsParams = { animate: Canvas3DParams.trackball.params.animate, camera: Canvas3DParams.camera, background: PD.Group({ color: PD.Color(Color(0xFCFBF9), { label: 'Background', description: 'Custom background color' }), transparent: PD.Boolean(false), style: Canvas3DParams.postprocessing.params.background, }, { pivot: 'color' }), lighting: PD.Group({ occlusion: Canvas3DParams.postprocessing.params.occlusion, shadow: Canvas3DParams.postprocessing.params.shadow, outline: Canvas3DParams.postprocessing.params.outline, fog: Canvas3DParams.cameraFog, }, { isFlat: true }), clipping: PD.Group({ ...Canvas3DParams.cameraClipping.params, }, { pivot: 'radius' }), layout: PD.MultiSelect([] as LayoutOptions[], PD.objectToOptions(LayoutOptions)), }; type SimpleSettingsParams = typeof SimpleSettingsParams const SimpleSettingsMapping = ParamMapping({ params: (ctx: PluginUIContext) => { const params = PD.clone(SimpleSettingsParams); const controls = ctx.spec.components?.controls; if (controls) { const options: [LayoutOptions, string][] = []; if (controls.top !== 'none') options.push(['sequence', LayoutOptions.sequence]); if (controls.bottom !== 'none') options.push(['log', LayoutOptions.log]); if (controls.left !== 'none') options.push(['left', LayoutOptions.left]); if (controls.right !== 'none') options.push(['right', LayoutOptions.right]); params.layout.options = options; } const bgStyles = ctx.config.get(PluginConfig.Background.Styles) || []; if (bgStyles.length > 0) { Object.assign(params.background.params.style, { presets: deepClone(bgStyles), isFlat: false, // so the presets menu is shown }); } return params; }, target(ctx: PluginUIContext) { const c = ctx.spec.components?.controls; const r = ctx.layout.state.regionState; const layout: SimpleSettingsParams['layout']['defaultValue'] = []; if (r.top !== 'hidden' && (!c || c.top !== 'none')) layout.push('sequence'); if (r.bottom !== 'hidden' && (!c || c.bottom !== 'none')) layout.push('log'); if (r.left !== 'hidden' && (!c || c.left !== 'none')) layout.push('left'); if (r.right !== 'hidden' && (!c || c.right !== 'none')) layout.push('right'); return { canvas: ctx.canvas3d?.props!, layout }; } })({ values(props, ctx) { const { canvas } = props; const renderer = canvas.renderer; return { layout: props.layout, animate: canvas.trackball.animate, camera: canvas.camera, background: { color: renderer.backgroundColor, transparent: canvas.transparentBackground, style: canvas.postprocessing.background, }, lighting: { occlusion: canvas.postprocessing.occlusion, shadow: canvas.postprocessing.shadow, outline: canvas.postprocessing.outline, fog: canvas.cameraFog, }, clipping: { ...canvas.cameraClipping, } }; }, update(s, props) { const canvas = props.canvas as Mutable; canvas.trackball.animate = s.animate; canvas.camera = s.camera; canvas.transparentBackground = s.background.transparent; canvas.renderer.backgroundColor = s.background.color; canvas.postprocessing.occlusion = s.lighting.occlusion; canvas.postprocessing.shadow = s.lighting.shadow; canvas.postprocessing.outline = s.lighting.outline; canvas.postprocessing.background = s.background.style; canvas.cameraFog = s.lighting.fog; canvas.cameraClipping = { radius: s.clipping.radius, far: s.clipping.far, minNear: s.clipping.minNear, }; props.layout = s.layout; }, async apply(props, ctx) { await PluginCommands.Canvas3D.SetSettings(ctx, { settings: props.canvas }); const hideLeft = props.layout.indexOf('left') < 0; const state = produce(ctx.layout.state, s => { s.regionState.top = props.layout.indexOf('sequence') >= 0 ? 'full' : 'hidden'; s.regionState.bottom = props.layout.indexOf('log') >= 0 ? 'full' : 'hidden'; s.regionState.left = hideLeft ? 'hidden' : ctx.behaviors.layout.leftPanelTabName.value === 'none' ? 'collapsed' : 'full'; s.regionState.right = props.layout.indexOf('right') >= 0 ? 'full' : 'hidden'; }); await PluginCommands.Layout.Update(ctx, { state }); if (hideLeft) { PluginCommands.State.SetCurrentObject(ctx, { state: ctx.state.data, ref: StateTransform.RootRef }); } } });