diff --git a/packages/ketcher-core/src/application/editor/tools/Bond.ts b/packages/ketcher-core/src/application/editor/tools/Bond.ts index c7f6dd1062..0c173bc014 100644 --- a/packages/ketcher-core/src/application/editor/tools/Bond.ts +++ b/packages/ketcher-core/src/application/editor/tools/Bond.ts @@ -26,6 +26,7 @@ import { RNABase } from 'domain/entities/RNABase'; import { Phosphate } from 'domain/entities/Phosphate'; import { Coordinates } from '../shared/coordinates'; import { AttachmentPointName } from 'domain/types'; +import { AttachmentPoint } from 'domain/AttachmentPoint'; class PolymerBond implements BaseTool { private bondRenderer?: PolymerBondRenderer; @@ -40,7 +41,7 @@ class PolymerBond implements BaseTool { public mouseDownAttachmentPoint(event) { const selectedRenderer = event.target.__data__; if ( - selectedRenderer instanceof BaseMonomerRenderer && + selectedRenderer instanceof AttachmentPoint && !selectedRenderer.monomer.isAttachmentPointUsed(event.attachmentPointName) ) { selectedRenderer.monomer.setChosenFirstAttachmentPoint( @@ -62,7 +63,10 @@ class PolymerBond implements BaseTool { public mousedown(event) { const selectedRenderer = event.target.__data__; - if (selectedRenderer instanceof BaseMonomerRenderer) { + if ( + selectedRenderer instanceof BaseMonomerRenderer || + selectedRenderer instanceof AttachmentPoint + ) { const startAttachmentPoint = selectedRenderer.monomer.startBondAttachmentPoint; @@ -150,9 +154,13 @@ class PolymerBond implements BaseTool { } public mouseOverAttachmentPoint(event) { - const renderer: BaseMonomerRenderer = event.target.__data__; + const renderer: AttachmentPoint = event.target.__data__; let modelChanges; + if (renderer.monomer.isAttachmentPointUsed(event.attachmentPointName)) { + return; + } + if (this.bondRenderer) { // Don't need to do anything if we hover over the first monomer of the bond if (this.bondRenderer?.polymerBond.firstMonomer === renderer.monomer) { @@ -183,7 +191,19 @@ class PolymerBond implements BaseTool { } public mouseLeaveMonomer(event) { + const eventToElementData = event.toElement?.__data__; + const eventFromElementData = event.fromElement?.__data__; + if ( + eventToElementData instanceof AttachmentPoint && + eventToElementData.monomer === eventFromElementData.monomer + ) { + eventToElementData.monomer.removePotentialBonds(); + + return; + } + const renderer: BaseMonomerRenderer = event.target.__data__; + if ( renderer !== this.bondRenderer?.polymerBond?.firstMonomer?.renderer && !this.isBondConnectionModalOpen @@ -193,6 +213,7 @@ class PolymerBond implements BaseTool { renderer.monomer, this.bondRenderer?.polymerBond, ); + this.editor.renderersContainer.markForRecalculateBegin(); this.editor.renderersContainer.update(modelChanges); } @@ -202,11 +223,14 @@ class PolymerBond implements BaseTool { if (this.isBondConnectionModalOpen) { return; } - const renderer: BaseMonomerRenderer = event.target.__data__; - if (renderer !== this.bondRenderer?.polymerBond?.firstMonomer?.renderer) { + const attachmentPointRenderer: AttachmentPoint = event.target.__data__; + if ( + attachmentPointRenderer.monomer.renderer !== + this.bondRenderer?.polymerBond?.firstMonomer?.renderer + ) { const modelChanges = this.editor.drawingEntitiesManager.cancelIntentionToFinishBondCreation( - renderer.monomer, + attachmentPointRenderer.monomer, this.bondRenderer?.polymerBond, ); this.editor.renderersContainer.markForRecalculateBegin(); @@ -215,9 +239,10 @@ class PolymerBond implements BaseTool { } public mouseUpAttachmentPoint(event) { - const renderer = event.target.__data__; + const renderer = event.target.__data__ as AttachmentPoint; const isFirstMonomerHovered = - renderer === this.bondRenderer?.polymerBond?.firstMonomer?.renderer; + renderer.monomer.renderer === + this.bondRenderer?.polymerBond?.firstMonomer?.renderer; if (this.bondRenderer && !isFirstMonomerHovered) { const firstMonomer = this.bondRenderer?.polymerBond?.firstMonomer; diff --git a/packages/ketcher-core/src/domain/AttachmentPoint.ts b/packages/ketcher-core/src/domain/AttachmentPoint.ts index 5b34c5cd0b..51063e8c45 100644 --- a/packages/ketcher-core/src/domain/AttachmentPoint.ts +++ b/packages/ketcher-core/src/domain/AttachmentPoint.ts @@ -33,22 +33,20 @@ export class AttachmentPoint { }; private rootElement: D3SvgElementSelection; - private attachmentPoint: D3SvgElementSelection | null; - private monomer: BaseMonomer; + private attachmentPoint: D3SvgElementSelection | null; + public monomer: BaseMonomer; private bodyWidth: number; private bodyHeight: number; - private attachmentPointName: string; + private attachmentPointName: AttachmentPointName; private canvasOffset: Coordinates; private centerOfMonomer: Coordinates; - private element: Selection | undefined; + private element: Selection | undefined; private hoverableArea: - | Selection + | Selection | undefined; private initialAngle = 0; private isUsed: boolean; - private fill: string; - private stroke: string; private isSnake; private editorEvents: typeof editorEvents; @@ -69,25 +67,35 @@ export class AttachmentPoint { this.editorEvents = editorEvents; this.attachmentPoint = null; - if (constructorParams.isPotentiallyUsed) { - this.fill = AttachmentPoint.colors.fillPotentially; - this.stroke = AttachmentPoint.colors.strokePotentially; - } else if (constructorParams.isUsed) { - this.fill = AttachmentPoint.colors.fillUsed; - this.stroke = AttachmentPoint.colors.strokeUsed; + this.appendAttachmentPoint(); + } + + private get fill() { + if ( + this.monomer.isAttachmentPointPotentiallyUsed(this.attachmentPointName) + ) { + return AttachmentPoint.colors.fillPotentially; + } else if (this.monomer.isAttachmentPointUsed(this.attachmentPointName)) { + return AttachmentPoint.colors.fillUsed; } else { - this.fill = AttachmentPoint.colors.fill; - this.stroke = AttachmentPoint.colors.stroke; + return AttachmentPoint.colors.fill; } + } - this.appendAttachmentPoint(); + private get stroke() { + if ( + this.monomer.isAttachmentPointPotentiallyUsed(this.attachmentPointName) + ) { + return AttachmentPoint.colors.strokePotentially; + } else if (this.monomer.isAttachmentPointUsed(this.attachmentPointName)) { + return AttachmentPoint.colors.strokeUsed; + } else { + return AttachmentPoint.colors.stroke; + } } public removeAttachmentPoint() { - const remove = () => { - this.element?.remove(); - }; - setTimeout(remove, 1); + this.element?.remove(); } private renderAttachmentPointByCoordinates( @@ -98,7 +106,10 @@ export class AttachmentPoint { const fill = this.fill; const stroke = this.stroke; - this.attachmentPoint = this.rootElement.insert('g', ':first-child'); + this.attachmentPoint = this.rootElement + .insert('g', ':first-child') + .data([this]) + .style('pointer-events', 'none'); const attachmentPointElement = this.attachmentPoint.append('g'); @@ -144,7 +155,7 @@ export class AttachmentPoint { } const rotation = angleDegrees + 90; - const halfWidth = 20; + const halfWidth = 8; const areaHeight = Math.sqrt( (monomerCenter.x - attachmentPointCenter.x) ** 2 + @@ -152,8 +163,8 @@ export class AttachmentPoint { ); const points: Coordinates[] = [ - { x: -AttachmentPoint.radius, y: AttachmentPoint.radius }, - { x: AttachmentPoint.radius, y: AttachmentPoint.radius }, + { x: -AttachmentPoint.radius, y: AttachmentPoint.radius + 2 }, + { x: AttachmentPoint.radius, y: AttachmentPoint.radius + 2 }, { x: halfWidth, y: -areaHeight + 10, @@ -162,7 +173,7 @@ export class AttachmentPoint { x: -halfWidth, y: -areaHeight + 10, }, - { x: -AttachmentPoint.radius, y: AttachmentPoint.radius }, + { x: -AttachmentPoint.radius, y: AttachmentPoint.radius + 2 }, ]; const lineFunction = line() @@ -178,6 +189,7 @@ export class AttachmentPoint { .attr('stroke-width', '1px') .attr('fill', '#0097A8') .style('opacity', '0') + .style('pointer-events', 'auto') .attr( 'transform', `translate(${attachmentPointCenter.x},${attachmentPointCenter.y})rotate(${rotation})`, @@ -206,26 +218,20 @@ export class AttachmentPoint { public appendAttachmentPoint() { let angleDegrees; let angleRadians: number; - const flip = - this.monomer.id === - this.monomer.attachmentPointsToBonds[this.attachmentPointName] - ?.firstMonomer?.id; + const polymerBond = + this.monomer.attachmentPointsToBonds[this.attachmentPointName]; + const flip = this.monomer.id === polymerBond?.firstMonomer?.id; const isAttachmentpointR1 = this.attachmentPointName === 'R1'; - if (!this.isUsed) { + if (!polymerBond) { angleDegrees = this.initialAngle; } else if ( this.isSnake && - !this.monomer.attachmentPointsToBonds[ - this.attachmentPointName - ]?.renderer.isMonomersOnSameHorizontalLine() + !polymerBond?.renderer?.isMonomersOnSameHorizontalLine() ) { angleRadians = isAttachmentpointR1 ? 0 : Math.PI; angleDegrees = Vec2.radiansToDegrees(angleRadians); } else { - angleRadians = this.rotateToAngle( - this.monomer.attachmentPointsToBonds[this.attachmentPointName], - flip, - ); + angleRadians = this.rotateToAngle(polymerBond, flip); angleDegrees = Vec2.radiansToDegrees(angleRadians); } @@ -263,18 +269,11 @@ export class AttachmentPoint { } public updateAttachmentPointStyleForHover() { - const isAttachmentPointUsed = this.monomer.isAttachmentPointUsed( - this.attachmentPointName as AttachmentPointName, - ); - if (isAttachmentPointUsed) { - this.attachmentPoint - ?.select('line') - .style('stroke', AttachmentPoint.colors.fillUsed); - this.attachmentPoint - ?.select('circle') - .style('fill', AttachmentPoint.colors.fillUsed) - .attr('stroke', 'white'); - } + this.attachmentPoint?.select('line').style('stroke', this.stroke); + this.attachmentPoint + ?.select('circle') + .style('fill', this.fill) + .attr('stroke', this.fill === 'white' ? '#0097A8' : 'white'); } public rotateToAngle(polymerBond: PolymerBond, flip = false) { @@ -327,15 +326,14 @@ export class AttachmentPoint { } public updateCoords() { - const flip = - this.monomer.id === - this.monomer.attachmentPointsToBonds[this.attachmentPointName] - ?.firstMonomer?.id; - - const angleRadians = this.rotateToAngle( - this.monomer.attachmentPointsToBonds[this.attachmentPointName], - flip, - ); + const polymerBond = + this.monomer.attachmentPointsToBonds[this.attachmentPointName]; + + assert(polymerBond); + + const flip = this.monomer.id === polymerBond?.firstMonomer?.id; + + const angleRadians = this.rotateToAngle(polymerBond, flip); const angleDegrees = Vec2.radiansToDegrees(angleRadians); const [ diff --git a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts index 6f4612e322..73edfbbde1 100644 --- a/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts +++ b/packages/ketcher-core/src/domain/entities/DrawingEntitiesManager.ts @@ -750,14 +750,11 @@ export class DrawingEntitiesManager { monomer.turnOnHover(); monomer.turnOnAttachmentPointsVisibility(); - if ( - monomer.isAttachmentPointUsed(attachmentPointName as AttachmentPointName) - ) { + if (monomer.isAttachmentPointUsed(attachmentPointName)) { const operation = new MonomerHoverOperation(monomer, true); command.addOperation(operation); return command; } - if (attachmentPointName) { monomer.setPotentialSecondAttachmentPoint(attachmentPointName); monomer.setPotentialBond(attachmentPointName, bond);