Browse Source

add preferAtoms param to Select/Highlight behaviors

Alexander Rose 3 years ago
parent
commit
73e9aed98c

+ 1 - 0
CHANGELOG.md

@@ -17,6 +17,7 @@ Note that since we don't clearly distinguish between a public and private interf
 - Fix pickScale not taken into account in line/point shader
 - Add pixel-scale & pick-scale GET params to Viewer app
 - Fix selecting bonds not adding their atoms in selection manager
+- Add ``preferAtoms`` option to SelectLoci/HighlightLoci behaviors
 
 ## [v2.3.0] - 2021-09-06
 

+ 7 - 2
src/mol-model/structure/structure/unit/bonds.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -8,7 +8,7 @@
 import { Unit, StructureElement } from '../../structure';
 import { Structure } from '../structure';
 import { BondType } from '../../model/types';
-import { SortedArray, Iterator } from '../../../../mol-data/int';
+import { SortedArray, Iterator, OrderedSet } from '../../../../mol-data/int';
 import { CentroidHelper } from '../../../../mol-math/geometry/centroid-helper';
 import { Sphere3D } from '../../../../mol-math/geometry';
 
@@ -132,6 +132,11 @@ namespace Bond {
         return StructureElement.Loci(loci.structure, elements);
     }
 
+    export function toFirstStructureElementLoci(loci: Loci): StructureElement.Loci {
+        const { aUnit, aIndex } = loci.bonds[0];
+        return StructureElement.Loci(loci.structure, [{ unit: aUnit, indices: OrderedSet.ofSingleton(aIndex) }]);
+    }
+
     export function getType(structure: Structure, location: Location<Unit.Atomic>): BondType {
         if (location.aUnit === location.bUnit) {
             const bonds = location.aUnit.bonds;

+ 26 - 10
src/mol-plugin/behavior/dynamic/representation.ts

@@ -16,7 +16,7 @@ import { ButtonsType, ModifiersKeys } from '../../../mol-util/input/input-observ
 import { Binding } from '../../../mol-util/binding';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { EmptyLoci, Loci } from '../../../mol-model/loci';
-import { Structure, StructureElement, StructureProperties } from '../../../mol-model/structure';
+import { Bond, Structure, StructureElement, StructureProperties } from '../../../mol-model/structure';
 import { arrayMax } from '../../../mol-util/array';
 import { Representation } from '../../../mol-repr/representation';
 import { LociLabel } from '../../../mol-plugin-state/manager/loci-label';
@@ -34,6 +34,7 @@ const DefaultHighlightLociBindings = {
 const HighlightLociParams = {
     bindings: PD.Value(DefaultHighlightLociBindings, { isHidden: true }),
     ignore: PD.Value<Loci['kind'][]>([], { isHidden: true }),
+    preferAtoms: PD.Boolean(false, { description: 'Always prefer atoms over bonds' }),
     mark: PD.Boolean(true)
 };
 type HighlightLociProps = PD.Values<typeof HighlightLociParams>
@@ -46,10 +47,17 @@ export const HighlightLoci = PluginBehavior.create({
             if (!this.ctx.canvas3d || !this.params.mark) return;
             this.ctx.canvas3d.mark(interactionLoci, action, noRender);
         }
+        private getLoci(loci: Loci) {
+            return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2
+                ? Bond.toFirstStructureElementLoci(loci)
+                : loci;
+        }
         register() {
             this.subscribeObservable(this.ctx.behaviors.interaction.hover, ({ current, buttons, modifiers }) => {
                 if (!this.ctx.canvas3d || this.ctx.isBusy) return;
-                if (this.params.ignore?.indexOf(current.loci.kind) >= 0) {
+
+                const loci = this.getLoci(current.loci);
+                if (this.params.ignore?.indexOf(loci.kind) >= 0) {
                     this.ctx.managers.interactivity.lociHighlights.highlightOnly({ repr: current.repr, loci: EmptyLoci });
                     return;
                 }
@@ -58,13 +66,13 @@ export const HighlightLoci = PluginBehavior.create({
 
                 if (Binding.match(this.params.bindings.hoverHighlightOnly, buttons, modifiers)) {
                     // remove repr to highlight loci everywhere on hover
-                    this.ctx.managers.interactivity.lociHighlights.highlightOnly({ loci: current.loci });
+                    this.ctx.managers.interactivity.lociHighlights.highlightOnly({ loci });
                     matched = true;
                 }
 
                 if (Binding.match(this.params.bindings.hoverHighlightOnlyExtend, buttons, modifiers)) {
                     // remove repr to highlight loci everywhere on hover
-                    this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend({ loci: current.loci });
+                    this.ctx.managers.interactivity.lociHighlights.highlightOnlyExtend({ loci });
                     matched = true;
                 }
 
@@ -95,6 +103,7 @@ const DefaultSelectLociBindings = {
 const SelectLociParams = {
     bindings: PD.Value(DefaultSelectLociBindings, { isHidden: true }),
     ignore: PD.Value<Loci['kind'][]>([], { isHidden: true }),
+    preferAtoms: PD.Boolean(false, { description: 'Always prefer atoms over bonds' }),
     mark: PD.Boolean(true)
 };
 type SelectLociProps = PD.Values<typeof SelectLociParams>
@@ -108,6 +117,11 @@ export const SelectLoci = PluginBehavior.create({
             if (!this.ctx.canvas3d || !this.params.mark) return;
             this.ctx.canvas3d.mark({ loci: reprLoci.loci }, action, noRender);
         }
+        private getLoci(loci: Loci) {
+            return this.params.preferAtoms && Bond.isLoci(loci) && loci.bonds.length === 2
+                ? Bond.toFirstStructureElementLoci(loci)
+                : loci;
+        }
         private applySelectMark(ref: string, clear?: boolean) {
             const cell = this.ctx.state.data.cells.get(ref);
             if (cell && SO.isRepresentation3D(cell.obj)) {
@@ -123,10 +137,10 @@ export const SelectLoci = PluginBehavior.create({
             }
         }
         register() {
-            const lociIsEmpty = (current: Representation.Loci) => Loci.isEmpty(current.loci);
-            const lociIsNotEmpty = (current: Representation.Loci) => !Loci.isEmpty(current.loci);
+            const lociIsEmpty = (loci: Loci) => Loci.isEmpty(loci);
+            const lociIsNotEmpty = (loci: Loci) => !Loci.isEmpty(loci);
 
-            const actions: [keyof typeof DefaultSelectLociBindings, (current: Representation.Loci) => void, ((current: Representation.Loci) => boolean) | undefined][] = [
+            const actions: [keyof typeof DefaultSelectLociBindings, (current: Representation.Loci) => void, ((current: Loci) => boolean) | undefined][] = [
                 ['clickSelect', current => this.ctx.managers.interactivity.lociSelects.select(current), lociIsNotEmpty],
                 ['clickToggle', current => this.ctx.managers.interactivity.lociSelects.toggle(current), lociIsNotEmpty],
                 ['clickToggleExtend', current => this.ctx.managers.interactivity.lociSelects.toggleExtend(current), lociIsNotEmpty],
@@ -145,12 +159,14 @@ export const SelectLoci = PluginBehavior.create({
 
             this.subscribeObservable(this.ctx.behaviors.interaction.click, ({ current, button, modifiers }) => {
                 if (!this.ctx.canvas3d || this.ctx.isBusy || !this.ctx.selectionMode) return;
-                if (this.params.ignore?.indexOf(current.loci.kind) >= 0) return;
+
+                const loci = this.getLoci(current.loci);
+                if (this.params.ignore?.indexOf(loci.kind) >= 0) return;
 
                 // only trigger the 1st action that matches
                 for (const [binding, action, condition] of actions) {
-                    if (Binding.match(this.params.bindings[binding], button, modifiers) && (!condition || condition(current))) {
-                        action(current);
+                    if (Binding.match(this.params.bindings[binding], button, modifiers) && (!condition || condition(loci))) {
+                        action({ repr: current.repr, loci });
                         break;
                     }
                 }