Browse Source

Selection and focus improvements (#742)

* Updated measurements so that the toggle selection button is not shown if selection mode is turned off

* Updated selection controls so that they cannot be turned off if selection mode is not shown

* Extended camera focus bindings to allow override of behaviour to reset camera focus on click

* Exported default bindings for selection and focus so that they can be more easily selectively overridden

* Added description to changelog and headers to modified files

* Fixed spacing in text for toggling selection mode

* Updated camera bindings to not be a breaking change by setting the new bindings to be optional and using default values when undefined

* resolved linting issues with camera bindings

* updated superposition UI to hide selection toggle button when selection mode is disabled

* updated the default value for click to reset camera bindings
jpattle 2 years ago
parent
commit
a73633d0c3

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@ Note that since we don't clearly distinguish between a public and private interf
 
 
 ## [Unreleased]
+- Selection toggle buttons hidden if selection mode is off
+- Camera focus loci bindings allow reset on click-away to be overridden
 
 - Input/controls improvements
     - Move or fly around the scene using keys

+ 6 - 1
src/mol-plugin-ui/structure/measurements.tsx

@@ -3,6 +3,7 @@
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Jason Pattle <jpattle.exscientia.co.uk>
  */
 
 import * as React from 'react';
@@ -11,6 +12,7 @@ import { StructureElement } from '../../mol-model/structure';
 import { StructureMeasurementCell, StructureMeasurementOptions, StructureMeasurementParams } from '../../mol-plugin-state/manager/structure/measurement';
 import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/structure/selection';
 import { PluginCommands } from '../../mol-plugin/commands';
+import { PluginConfig } from '../../mol-plugin/config';
 import { AngleData } from '../../mol-repr/shape/loci/angle';
 import { DihedralData } from '../../mol-repr/shape/loci/dihedral';
 import { DistanceData } from '../../mol-repr/shape/loci/distance';
@@ -208,13 +210,16 @@ export class MeasurementControls extends PurePluginUIComponent<{}, { isBusy: boo
             entries.push(this.historyEntry(history[i], i + 1));
         }
 
+        const shouldShowToggleHint = this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode);
+        const toggleHint = shouldShowToggleHint ? (<>{' '}(toggle <ToggleSelectionModeButton inline /> mode)</>) : null;
+
         return <>
             <ActionMenu items={this.actions} onSelect={this.selectAction} />
             {entries.length > 0 && <div className='msp-control-offset'>
                 {entries}
             </div>}
             {entries.length === 0 && <div className='msp-control-offset msp-help-text'>
-                <div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add one or more selections (toggle <ToggleSelectionModeButton inline /> mode)</div>
+                <div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add one or more selections{toggleHint}</div>
             </div>}
         </>;
     }

+ 3 - 1
src/mol-plugin-ui/structure/selection.tsx

@@ -3,6 +3,7 @@
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Jason Pattle <jpattle.exscientia.co.uk>
  */
 
 import * as React from 'react';
@@ -12,6 +13,7 @@ import { InteractivityManager } from '../../mol-plugin-state/manager/interactivi
 import { StructureComponentManager } from '../../mol-plugin-state/manager/structure/component';
 import { StructureComponentRef, StructureRef } from '../../mol-plugin-state/manager/structure/hierarchy-state';
 import { StructureSelectionModifier } from '../../mol-plugin-state/manager/structure/selection';
+import { PluginConfig } from '../../mol-plugin/config';
 import { PluginContext } from '../../mol-plugin/context';
 import { compileIdListSelection } from '../../mol-script/util/id-list';
 import { memoizeLatest } from '../../mol-util/memoize';
@@ -272,7 +274,7 @@ export class StructureSelectionActionsControls extends PluginUIComponent<{}, Str
                 <IconButton svg={RestoreSvg} onClick={this.undo} disabled={!this.state.canUndo || this.isDisabled} title={undoTitle} />
 
                 <ToggleButton icon={HelpOutlineSvg} title='Show/hide help' toggle={this.toggleHelp} style={{ marginLeft: '10px' }} isSelected={this.state.action === 'help'} />
-                <IconButton svg={CancelOutlinedSvg} title='Turn selection mode off' onClick={this.turnOff} />
+                {this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode) && (<IconButton svg={CancelOutlinedSvg} title='Turn selection mode off' onClick={this.turnOff} />)}
             </div>
             {children}
         </>;

+ 8 - 2
src/mol-plugin-ui/structure/superposition.tsx

@@ -16,6 +16,7 @@ import { StructureSelectionHistoryEntry } from '../../mol-plugin-state/manager/s
 import { PluginStateObject } from '../../mol-plugin-state/objects';
 import { StateTransforms } from '../../mol-plugin-state/transforms';
 import { PluginCommands } from '../../mol-plugin/commands';
+import { PluginConfig } from '../../mol-plugin/config';
 import { StateObjectCell, StateObjectRef } from '../../mol-state';
 import { elementLabel, structureElementStatsLabel } from '../../mol-theme/label';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
@@ -324,6 +325,11 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
         return entries;
     }
 
+    toggleHint() {
+        const shouldShowToggleHint = this.plugin.config.get(PluginConfig.Viewport.ShowSelectionMode);
+        return shouldShowToggleHint ? (<>{' '}(toggle <ToggleSelectionModeButton inline /> mode)</>) : null;
+    }
+
     addByChains() {
         const entries = this.chainEntries;
         return <>
@@ -331,7 +337,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
                 {entries.map((e, i) => this.lociEntry(e, i))}
             </div>}
             {entries.length < 2 && <div className='msp-control-offset msp-help-text'>
-                <div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add 2 or more selections (toggle <ToggleSelectionModeButton inline /> mode) from separate structures. Selections must be limited to single polymer chains or residues therein.</div>
+                <div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add 2 or more selections{this.toggleHint()} from separate structures. Selections must be limited to single polymer chains or residues therein.</div>
             </div>}
             {entries.length > 1 && <Button title='Superpose structures by selected chains.' className='msp-btn-commit msp-btn-commit-on' onClick={this.superposeChains} style={{ marginTop: '1px' }}>
                 Superpose
@@ -346,7 +352,7 @@ export class SuperpositionControls extends PurePluginUIComponent<{ }, Superposit
                 {entries.map((e, i) => this.atomsLociEntry(e, i))}
             </div>}
             {entries.length < 2 && <div className='msp-control-offset msp-help-text'>
-                <div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add 1 or more selections (toggle <ToggleSelectionModeButton inline /> mode) from
+                <div className='msp-help-description'><Icon svg={HelpOutlineSvg} inline />Add 1 or more selections{this.toggleHint()} from
                 separate structures. Selections must be limited to single atoms.</div>
             </div>}
             {entries.length > 1 && <Button title='Superpose structures by selected atoms.' className='msp-btn-commit msp-btn-commit-on' onClick={this.superposeAtoms} style={{ marginTop: '1px' }}>

+ 29 - 6
src/mol-plugin/behavior/dynamic/camera.ts

@@ -3,6 +3,7 @@
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Jason Pattle <jpattle.exscientia.co.uk>
  */
 
 import { Loci } from '../../../mol-model/loci';
@@ -19,7 +20,23 @@ const M = ModifiersKeys;
 const Trigger = Binding.Trigger;
 const Key = Binding.TriggerKey;
 
-const DefaultFocusLociBindings = {
+export const DefaultClickResetCameraOnEmpty = Binding([
+    Trigger(B.Flag.Primary, M.create()),
+    Trigger(B.Flag.Secondary, M.create()),
+    Trigger(B.Flag.Primary, M.create({ control: true }))
+], 'Reset camera focus', 'Click on nothing using ${triggers}');
+export const DefaultClickResetCameraOnEmptySelectMode = Binding([
+    Trigger(B.Flag.Secondary, M.create()),
+    Trigger(B.Flag.Primary, M.create({ control: true }))
+], 'Reset camera focus', 'Click on nothing using ${triggers}');
+
+type FocusLociBindings = {
+    clickCenterFocus: Binding
+    clickCenterFocusSelectMode: Binding
+    clickResetCameraOnEmpty?: Binding
+    clickResetCameraOnEmptySelectMode?: Binding
+}
+export const DefaultFocusLociBindings: FocusLociBindings = {
     clickCenterFocus: Binding([
         Trigger(B.Flag.Primary, M.create()),
         Trigger(B.Flag.Secondary, M.create()),
@@ -29,6 +46,8 @@ const DefaultFocusLociBindings = {
         Trigger(B.Flag.Secondary, M.create()),
         Trigger(B.Flag.Primary, M.create({ control: true }))
     ], 'Camera center and focus', 'Click element using ${triggers}'),
+    clickResetCameraOnEmpty: DefaultClickResetCameraOnEmpty,
+    clickResetCameraOnEmptySelectMode: DefaultClickResetCameraOnEmptySelectMode,
 };
 const FocusLociParams = {
     minRadius: PD.Numeric(8, { min: 1, max: 50, step: 1 }),
@@ -51,12 +70,16 @@ export const FocusLoci = PluginBehavior.create<FocusLociProps>({
                     ? this.params.bindings.clickCenterFocusSelectMode
                     : this.params.bindings.clickCenterFocus;
 
-                if (Binding.match(binding, button, modifiers)) {
-                    if (Loci.isEmpty(current.loci)) {
-                        PluginCommands.Camera.Reset(this.ctx, { });
-                        return;
-                    }
+                const resetBinding = this.ctx.selectionMode
+                    ? (this.params.bindings.clickResetCameraOnEmptySelectMode ?? DefaultClickResetCameraOnEmptySelectMode)
+                    : (this.params.bindings.clickResetCameraOnEmpty ?? DefaultClickResetCameraOnEmpty);
+
+                if (Loci.isEmpty(current.loci) && Binding.match(resetBinding, button, modifiers)) {
+                    PluginCommands.Camera.Reset(this.ctx, { });
+                    return;
+                }
 
+                if (Binding.match(binding, button, modifiers)) {
                     const loci = Loci.normalize(current.loci, this.ctx.managers.interactivity.props.granularity);
                     this.ctx.managers.camera.focusLoci(loci, this.params);
                 }

+ 3 - 2
src/mol-plugin/behavior/dynamic/representation.ts

@@ -3,6 +3,7 @@
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Jason Pattle <jpattle.exscientia.co.uk>
  */
 
 import { MarkerAction } from '../../../mol-util/marker-action';
@@ -92,7 +93,7 @@ export const HighlightLoci = PluginBehavior.create({
 
 //
 
-const DefaultSelectLociBindings = {
+export const DefaultSelectLociBindings = {
     clickSelect: Binding.Empty,
     clickToggleExtend: Binding([Trigger(B.Flag.Primary, M.create({ shift: true }))], 'Toggle extended selection', 'Click on element using ${triggers} to extend selection along polymer'),
     clickSelectOnly: Binding.Empty,
@@ -236,7 +237,7 @@ export const DefaultLociLabelProvider = PluginBehavior.create({
 
 //
 
-const DefaultFocusLociBindings = {
+export const DefaultFocusLociBindings = {
     clickFocus: Binding([
         Trigger(B.Flag.Primary, M.create()),
     ], 'Representation Focus', 'Click element using ${triggers}'),