diff --git a/CHANGELOG.md b/CHANGELOG.md index fdaf339b1f73fa568cb8c5aa8a290247a240c500..e2778a2013e89d6ffe54dff326bacf1010bf1611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,8 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] -- Fixed a bug in mesh visualization (show backfaces when opacity < 1) +- `meshes` extension: Fixed a bug in mesh visualization (show backfaces when opacity < 1) +- Add color quick select control to Volume controls ## [v3.28.0] - 2022-12-20 @@ -14,6 +15,7 @@ Note that since we don't clearly distinguish between a public and private interf - Add `solidInterior` parameter to sphere/cylinder impostors - [Breaking] Tweak `ignoreHydrogens` non-polar handling (introduced in 3.27.0) - Add `meshes` and `volumes-and-segmentations` extensions + - See https://molstarvolseg.ncbr.muni.cz/ for more info - Fix missing support for info in `ParamDefinition.Converted` - Add support for multi-visual volume representations - Improve volume isosurface bounding-sphere diff --git a/src/mol-plugin-ui/controls/color.tsx b/src/mol-plugin-ui/controls/color.tsx index 5e5cc779216bde9d534de02bc405a48dfc5bc6a0..d537c631be74c701a97bb79aeb11e8f34315141a 100644 --- a/src/mol-plugin-ui/controls/color.tsx +++ b/src/mol-plugin-ui/controls/color.tsx @@ -14,9 +14,9 @@ import { ParamProps } from './parameters'; import { TextInput, Button, ControlRow } from './common'; import { DefaultColorSwatch } from '../../mol-util/color/swatches'; -export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Color>, { isExpanded: boolean, lightness: number }> { +export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Color> & { hideNameRow?: boolean }, { isExpanded: boolean, lightness: number }> { state = { - isExpanded: !!this.props.param.isExpanded, + isExpanded: !!this.props.param.isExpanded || !!this.props.hideNameRow, lightness: 0 }; @@ -72,21 +72,30 @@ export class CombinedColorControl extends React.PureComponent<ParamProps<PD.Colo render() { const label = this.props.param.label || camelCaseToWords(this.props.name); const [r, g, b] = Color.toRgb(this.props.value); + + const inner = <> + {this.swatch()} + <ControlRow label='RGB' className='msp-control-label-short' control={<div style={{ display: 'flex', textAlignLast: 'center', left: '80px' }}> + <TextInput onChange={this.onR} numeric value={r} delayMs={250} style={{ order: 1, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} /> + <TextInput onChange={this.onG} numeric value={g} delayMs={250} style={{ order: 2, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} /> + <TextInput onChange={this.onB} numeric value={b} delayMs={250} style={{ order: 3, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} /> + </div>} /> + <div style={{ display: 'flex', textAlignLast: 'center' }}> + <Button onClick={this.onLighten} style={{ order: 1, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control'>Lighten</Button> + <Button onClick={this.onDarken} style={{ order: 1, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control'>Darken</Button> + </div> + </>; + + if (this.props.hideNameRow) { + return inner; + } + return <> <ControlRow title={this.props.param.description} label={label} control={<Button onClick={this.toggleExpanded} inline className='msp-combined-color-button' style={{ background: Color.toStyle(this.props.value) }} />} /> {this.state.isExpanded && <div className='msp-control-offset'> - {this.swatch()} - <ControlRow label='RGB' className='msp-control-label-short' control={<div style={{ display: 'flex', textAlignLast: 'center', left: '80px' }}> - <TextInput onChange={this.onR} numeric value={r} delayMs={250} style={{ order: 1, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} /> - <TextInput onChange={this.onG} numeric value={g} delayMs={250} style={{ order: 2, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} /> - <TextInput onChange={this.onB} numeric value={b} delayMs={250} style={{ order: 3, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control' onEnter={this.props.onEnter} blurOnEnter={true} blurOnEscape={true} /> - </div>}/> - <div style={{ display: 'flex', textAlignLast: 'center' }}> - <Button onClick={this.onLighten} style={{ order: 1, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control'>Lighten</Button> - <Button onClick={this.onDarken} style={{ order: 1, flex: '1 1 auto', minWidth: 0 }} className='msp-form-control'>Darken</Button> - </div> + {inner} </div>} </>; } diff --git a/src/mol-plugin-ui/structure/volume.tsx b/src/mol-plugin-ui/structure/volume.tsx index 9c5666147f195ea764b3bd3e72671e201a7a4b91..8c8aaea6d99c9eaf89583a32b65616be7b692109 100644 --- a/src/mol-plugin-ui/structure/volume.tsx +++ b/src/mol-plugin-ui/structure/volume.tsx @@ -15,15 +15,19 @@ import { InitVolumeStreaming } from '../../mol-plugin/behavior/dynamic/volume-st import { State, StateObjectCell, StateObjectSelector, StateSelection, StateTransform } from '../../mol-state'; import { CollapsableControls, CollapsableState, PurePluginUIComponent } from '../base'; import { ActionMenu } from '../controls/action-menu'; -import { Button, ExpandGroup, IconButton } from '../controls/common'; +import { Button, ControlGroup, ExpandGroup, IconButton } from '../controls/common'; import { ApplyActionControl } from '../state/apply-action'; import { UpdateTransformControl } from '../state/update-transform'; import { BindingsHelp } from '../viewport/help'; import { PluginCommands } from '../../mol-plugin/commands'; -import { BlurOnSvg, ErrorSvg, CheckSvg, AddSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, DeleteOutlinedSvg, MoreHorizSvg } from '../controls/icons'; +import { BlurOnSvg, ErrorSvg, CheckSvg, AddSvg, VisibilityOffOutlinedSvg, VisibilityOutlinedSvg, DeleteOutlinedSvg, MoreHorizSvg, CloseSvg } from '../controls/icons'; import { PluginStateObject } from '../../mol-plugin-state/objects'; import { StateTransforms } from '../../mol-plugin-state/transforms'; import { createVolumeRepresentationParams } from '../../mol-plugin-state/helpers/volume-representation-params'; +import { Color } from '../../mol-util/color'; +import { ParamDefinition } from '../../mol-util/param-definition'; +import { CombinedColorControl } from '../controls/color'; +import { ParamOnChange } from '../controls/parameters'; interface VolumeStreamingControlState extends CollapsableState { isBusy: boolean @@ -260,7 +264,7 @@ export class VolumeSourceControls extends CollapsableControls<{}, VolumeSourceCo } } -type VolumeRepresentationEntryActions = 'update' +type VolumeRepresentationEntryActions = 'update' | 'select-color' class VolumeRepresentationControls extends PurePluginUIComponent<{ representation: VolumeRepresentationRef }, { action?: VolumeRepresentationEntryActions }> { state = { action: void 0 as VolumeRepresentationEntryActions | undefined }; @@ -279,6 +283,10 @@ class VolumeRepresentationControls extends PurePluginUIComponent<{ representatio this.plugin.managers.volume.hierarchy.toggleVisibility([this.props.representation]); }; + toggleColor = () => { + this.setState({ action: this.state.action === 'select-color' ? undefined : 'select-color' }); + }; + toggleUpdate = () => this.setState({ action: this.state.action === 'update' ? void 0 : 'update' }); highlight = (e: React.MouseEvent<HTMLElement>) => { @@ -299,10 +307,30 @@ class VolumeRepresentationControls extends PurePluginUIComponent<{ representatio if (lociList) this.plugin.managers.camera.focusLoci(lociList, { extraRadius: 1 }); }; + private get color() { + const repr = this.props.representation.cell; + const isUniform = repr.transform.params?.colorTheme.name === 'uniform'; + if (!isUniform) return void 0; + return repr.transform.params?.colorTheme.params.value; + } + + updateColor: ParamOnChange = ({ value }) => { + const t = this.props.representation.cell.transform; + return this.plugin.build().to(t.ref).update({ + ...t.params, + colorTheme: { + name: 'uniform', + params: { value } + }, + }).commit(); + }; + render() { const repr = this.props.representation.cell; + const color = this.color; return <> <div className='msp-flex-row'> + {color !== void 0 && <Button style={{ backgroundColor: Color.toStyle(color), minWidth: 32, width: 32 }} onClick={this.toggleColor} />} <Button noOverflow className='msp-control-button-label' title={`${repr.obj?.label}. Click to focus.`} onClick={this.focus} onMouseEnter={this.highlight} onMouseLeave={this.clearHighlight} style={{ textAlign: 'left' }}> {repr.obj?.label} <small className='msp-25-lower-contrast-text' style={{ float: 'right' }}>{repr.obj?.description}</small> @@ -314,6 +342,14 @@ class VolumeRepresentationControls extends PurePluginUIComponent<{ representatio {this.state.action === 'update' && !!repr.parent && <div style={{ marginBottom: '6px' }} className='msp-accent-offset'> <UpdateTransformControl state={repr.parent} transform={repr.transform} customHeader='none' noMargin /> </div>} + {this.state.action === 'select-color' && color !== void 0 && <div style={{ marginBottom: '6px', marginTop: 1 }} className='msp-accent-offset'> + <ControlGroup header='Select Color' initialExpanded={true} hideExpander={true} hideOffset={true} onHeaderClick={this.toggleColor} + topRightIcon={CloseSvg} noTopMargin childrenClassName='msp-viewport-controls-panel-controls'> + <CombinedColorControl param={VolumeColorParam} value={this.color} onChange={this.updateColor} name='color' hideNameRow /> + </ControlGroup> + </div>} </>; } -} \ No newline at end of file +} + +const VolumeColorParam = ParamDefinition.Color(Color(0x121212));