@@ -21,7 +21,6 @@ import * as common from './common.js';
2121import type { BlockMove } from './events/events_block_move.js' ;
2222import * as eventUtils from './events/utils.js' ;
2323import type { Icon } from './icons/icon.js' ;
24- import { InsertionMarkerManager } from './insertion_marker_manager.js' ;
2524import type { IBlockDragger } from './interfaces/i_block_dragger.js' ;
2625import type { IDragTarget } from './interfaces/i_drag_target.js' ;
2726import * as registry from './registry.js' ;
@@ -31,6 +30,26 @@ import type {WorkspaceSvg} from './workspace_svg.js';
3130import { hasBubble } from './interfaces/i_has_bubble.js' ;
3231import * as deprecation from './utils/deprecation.js' ;
3332import * as layers from './layers.js' ;
33+ import { ConnectionType , IConnectionPreviewer } from './blockly.js' ;
34+ import { RenderedConnection } from './rendered_connection.js' ;
35+ import { config } from './config.js' ;
36+ import { ComponentManager } from './component_manager.js' ;
37+ import { IDeleteArea } from './interfaces/i_delete_area.js' ;
38+ import { Connection } from './connection.js' ;
39+ import { Block } from './block.js' ;
40+ import { finishQueuedRenders } from './render_management.js' ;
41+
42+ /** Represents a nearby valid connection. */
43+ interface ConnectionCandidate {
44+ /** A connection on the dragging stack that is compatible with neighbour. */
45+ local : RenderedConnection ;
46+
47+ /** A nearby connection that is compatible with local. */
48+ neighbour : RenderedConnection ;
49+
50+ /** The distance between the local connection and the neighbour connection. */
51+ distance : number ;
52+ }
3453
3554/**
3655 * Class for a block dragger. It moves blocks around the workspace when they
@@ -39,14 +58,17 @@ import * as layers from './layers.js';
3958export class BlockDragger implements IBlockDragger {
4059 /** The top block in the stack that is being dragged. */
4160 protected draggingBlock_ : BlockSvg ;
42- protected draggedConnectionManager_ : InsertionMarkerManager ;
61+
62+ protected connectionPreviewer : IConnectionPreviewer ;
4363
4464 /** The workspace on which the block is being dragged. */
4565 protected workspace_ : WorkspaceSvg ;
4666
4767 /** Which drag area the mouse pointer is over, if any. */
4868 private dragTarget_ : IDragTarget | null = null ;
4969
70+ private connectionCandidate : ConnectionCandidate | null = null ;
71+
5072 /** Whether the block would be deleted if dropped immediately. */
5173 protected wouldDeleteBlock_ = false ;
5274 protected startXY_ : Coordinate ;
@@ -63,13 +85,13 @@ export class BlockDragger implements IBlockDragger {
6385 */
6486 constructor ( block : BlockSvg , workspace : WorkspaceSvg ) {
6587 this . draggingBlock_ = block ;
88+ this . workspace_ = workspace ;
6689
67- /** Object that keeps track of connections on dragged blocks. */
68- this . draggedConnectionManager_ = new InsertionMarkerManager (
69- this . draggingBlock_ ,
90+ const previewerConstructor = registry . getClassFromOptions (
91+ registry . Type . CONNECTION_PREVIEWER ,
92+ this . workspace_ . options ,
7093 ) ;
71-
72- this . workspace_ = workspace ;
94+ this . connectionPreviewer = new previewerConstructor ! ( block ) ;
7395
7496 /**
7597 * The location of the top left corner of the dragging block at the
@@ -87,9 +109,7 @@ export class BlockDragger implements IBlockDragger {
87109 */
88110 dispose ( ) {
89111 this . dragIconData_ . length = 0 ;
90- if ( this . draggedConnectionManager_ ) {
91- this . draggedConnectionManager_ . dispose ( ) ;
92- }
112+ this . connectionPreviewer . dispose ( ) ;
93113 }
94114
95115 /**
@@ -155,7 +175,6 @@ export class BlockDragger implements IBlockDragger {
155175
156176 this . draggingBlock_ . translate ( newLoc . x , newLoc . y ) ;
157177 blockAnimation . disconnectUiEffect ( this . draggingBlock_ ) ;
158- this . draggedConnectionManager_ . updateAvailableConnections ( ) ;
159178 }
160179
161180 /** Fire a UI event at the start of a block drag. */
@@ -173,32 +192,178 @@ export class BlockDragger implements IBlockDragger {
173192 * display accordingly.
174193 *
175194 * @param e The most recent move event.
176- * @param currentDragDeltaXY How far the pointer has moved from the position
195+ * @param delta How far the pointer has moved from the position
177196 * at the start of the drag, in pixel units.
178197 */
179- drag ( e : PointerEvent , currentDragDeltaXY : Coordinate ) {
180- const delta = this . pixelsToWorkspaceUnits_ ( currentDragDeltaXY ) ;
198+ drag ( e : PointerEvent , delta : Coordinate ) {
199+ const block = this . draggingBlock_ ;
200+ this . moveBlock ( block , delta ) ;
201+ this . updateDragTargets ( e , block ) ;
202+ this . wouldDeleteBlock_ = this . wouldDeleteBlock ( e , block , delta ) ;
203+ this . updateCursorDuringBlockDrag_ ( ) ;
204+ this . updateConnectionPreview ( block , delta ) ;
205+ }
206+
207+ private moveBlock ( draggingBlock : BlockSvg , dragDelta : Coordinate ) {
208+ const delta = this . pixelsToWorkspaceUnits_ ( dragDelta ) ;
181209 const newLoc = Coordinate . sum ( this . startXY_ , delta ) ;
182- this . draggingBlock_ . moveDuringDrag ( newLoc ) ;
210+ draggingBlock . moveDuringDrag ( newLoc ) ;
211+ }
212+
213+ private updateDragTargets ( e : PointerEvent , draggingBlock : BlockSvg ) {
214+ const newDragTarget = this . workspace_ . getDragTarget ( e ) ;
215+ if ( this . dragTarget_ !== newDragTarget ) {
216+ this . dragTarget_ ?. onDragExit ( draggingBlock ) ;
217+ newDragTarget ?. onDragEnter ( draggingBlock ) ;
218+ }
219+ newDragTarget ?. onDragOver ( draggingBlock ) ;
220+ this . dragTarget_ = newDragTarget ;
221+ }
183222
184- const oldDragTarget = this . dragTarget_ ;
185- this . dragTarget_ = this . workspace_ . getDragTarget ( e ) ;
223+ /**
224+ * Returns true if we would delete the block if it was dropped at this time,
225+ * false otherwise.
226+ */
227+ private wouldDeleteBlock (
228+ e : PointerEvent ,
229+ draggingBlock : BlockSvg ,
230+ delta : Coordinate ,
231+ ) : boolean {
232+ const dragTarget = this . workspace_ . getDragTarget ( e ) ;
233+ if ( ! dragTarget ) return false ;
234+
235+ const componentManager = this . workspace_ . getComponentManager ( ) ;
236+ const isDeleteArea = componentManager . hasCapability (
237+ dragTarget . id ,
238+ ComponentManager . Capability . DELETE_AREA ,
239+ ) ;
240+ if ( ! isDeleteArea ) return false ;
241+
242+ return ( dragTarget as IDeleteArea ) . wouldDelete (
243+ draggingBlock ,
244+ ! ! this . getConnectionCandidate ( draggingBlock , delta ) ,
245+ ) ;
246+ }
186247
187- this . draggedConnectionManager_ . update ( delta , this . dragTarget_ ) ;
188- const oldWouldDeleteBlock = this . wouldDeleteBlock_ ;
189- this . wouldDeleteBlock_ = this . draggedConnectionManager_ . wouldDeleteBlock ;
190- if ( oldWouldDeleteBlock !== this . wouldDeleteBlock_ ) {
191- // Prevent unnecessary add/remove class calls.
192- this . updateCursorDuringBlockDrag_ ( ) ;
248+ private updateConnectionPreview ( draggingBlock : BlockSvg , delta : Coordinate ) {
249+ const currCandidate = this . connectionCandidate ;
250+ const newCandidate = this . getConnectionCandidate ( draggingBlock , delta ) ;
251+ if ( ! newCandidate ) {
252+ this . connectionPreviewer . hidePreview ( ) ;
253+ this . connectionCandidate = null ;
254+ return ;
255+ }
256+ const candidate =
257+ currCandidate &&
258+ this . currCandidateIsBetter ( currCandidate , delta , newCandidate )
259+ ? currCandidate
260+ : newCandidate ;
261+ this . connectionCandidate = candidate ;
262+ const { local, neighbour} = candidate ;
263+ if (
264+ ( local . type === ConnectionType . OUTPUT_VALUE ||
265+ local . type === ConnectionType . PREVIOUS_STATEMENT ) &&
266+ neighbour . isConnected ( ) &&
267+ ! neighbour . targetBlock ( ) ! . isInsertionMarker ( ) &&
268+ ! this . orphanCanConnectAtEnd (
269+ draggingBlock ,
270+ neighbour . targetBlock ( ) ! ,
271+ local . type ,
272+ )
273+ ) {
274+ this . connectionPreviewer . previewReplacement (
275+ local ,
276+ neighbour ,
277+ neighbour . targetBlock ( ) ! ,
278+ ) ;
279+ return ;
193280 }
281+ this . connectionPreviewer . previewConnection ( local , neighbour ) ;
282+ }
194283
195- // Call drag enter/exit/over after wouldDeleteBlock is called in
196- // InsertionMarkerManager.update.
197- if ( this . dragTarget_ !== oldDragTarget ) {
198- oldDragTarget && oldDragTarget . onDragExit ( this . draggingBlock_ ) ;
199- this . dragTarget_ && this . dragTarget_ . onDragEnter ( this . draggingBlock_ ) ;
284+ /**
285+ * Returns true if the given orphan block can connect at the end of the
286+ * top block's stack or row, false otherwise.
287+ */
288+ private orphanCanConnectAtEnd (
289+ topBlock : BlockSvg ,
290+ orphanBlock : BlockSvg ,
291+ localType : number ,
292+ ) : boolean {
293+ const orphanConnection =
294+ localType === ConnectionType . OUTPUT_VALUE
295+ ? orphanBlock . outputConnection
296+ : orphanBlock . previousConnection ;
297+ return ! ! Connection . getConnectionForOrphanedConnection (
298+ topBlock as Block ,
299+ orphanConnection as Connection ,
300+ ) ;
301+ }
302+
303+ /**
304+ * Returns true if the current candidate is better than the new candidate.
305+ *
306+ * We slightly prefer the current candidate even if it is farther away.
307+ */
308+ private currCandidateIsBetter (
309+ currCandiate : ConnectionCandidate ,
310+ delta : Coordinate ,
311+ newCandidate : ConnectionCandidate ,
312+ ) : boolean {
313+ const { local : currLocal , neighbour : currNeighbour } = currCandiate ;
314+ const localPos = new Coordinate ( currLocal . x , currLocal . y ) ;
315+ const neighbourPos = new Coordinate ( currNeighbour . x , currNeighbour . y ) ;
316+ const distance = Coordinate . distance (
317+ Coordinate . sum ( localPos , delta ) ,
318+ neighbourPos ,
319+ ) ;
320+ return (
321+ newCandidate . distance > distance - config . currentConnectionPreference
322+ ) ;
323+ }
324+
325+ /**
326+ * Returns the closest valid candidate connection, if one can be found.
327+ *
328+ * Valid neighbour connections are within the configured start radius, with a
329+ * compatible type (input, output, etc) and connection check.
330+ */
331+ private getConnectionCandidate (
332+ draggingBlock : BlockSvg ,
333+ delta : Coordinate ,
334+ ) : ConnectionCandidate | null {
335+ const localConns = this . getLocalConnections ( draggingBlock ) ;
336+ let radius = config . snapRadius ;
337+ let candidate = null ;
338+
339+ for ( const conn of localConns ) {
340+ const { connection : neighbour , radius : rad } = conn . closest ( radius , delta ) ;
341+ if ( neighbour ) {
342+ candidate = {
343+ local : conn ,
344+ neighbour : neighbour ,
345+ distance : rad ,
346+ } ;
347+ radius = rad ;
348+ }
200349 }
201- this . dragTarget_ && this . dragTarget_ . onDragOver ( this . draggingBlock_ ) ;
350+
351+ return candidate ;
352+ }
353+
354+ /**
355+ * Returns all of the connections we might connect to blocks on the workspace.
356+ *
357+ * Includes any connections on the dragging block, and any last next
358+ * connection on the stack (if one exists).
359+ */
360+ private getLocalConnections ( draggingBlock : BlockSvg ) : RenderedConnection [ ] {
361+ const available = draggingBlock . getConnections_ ( false ) ;
362+ const lastOnStack = draggingBlock . lastConnectionInStack ( true ) ;
363+ if ( lastOnStack && lastOnStack !== draggingBlock . nextConnection ) {
364+ available . push ( lastOnStack ) ;
365+ }
366+ return available ;
202367 }
203368
204369 /**
@@ -216,6 +381,8 @@ export class BlockDragger implements IBlockDragger {
216381 dom . stopTextWidthCache ( ) ;
217382
218383 blockAnimation . disconnectUiStop ( ) ;
384+ this . connectionPreviewer . hidePreview ( ) ;
385+ this . connectionPreviewer . dispose ( ) ;
219386
220387 const preventMove =
221388 ! ! this . dragTarget_ &&
@@ -298,15 +465,33 @@ export class BlockDragger implements IBlockDragger {
298465 */
299466 protected updateBlockAfterMove_ ( ) {
300467 this . fireMoveEvent_ ( ) ;
301- if ( this . draggedConnectionManager_ . wouldConnectBlock ( ) ) {
468+ if ( this . connectionCandidate ) {
302469 // Applying connections also rerenders the relevant blocks.
303- this . draggedConnectionManager_ . applyConnections ( ) ;
470+ this . applyConnections ( this . connectionCandidate ) ;
304471 } else {
305472 this . draggingBlock_ . queueRender ( ) ;
306473 }
307474 this . draggingBlock_ . scheduleSnapAndBump ( ) ;
308475 }
309476
477+ private applyConnections ( candidate : ConnectionCandidate ) {
478+ const { local, neighbour} = candidate ;
479+ local . connect ( neighbour ) ;
480+ // TODO: We can remove this `rendered` check when we reconcile with v11.
481+ if ( this . draggingBlock_ . rendered ) {
482+ const inferiorConnection = local . isSuperior ( ) ? neighbour : local ;
483+ const rootBlock = this . draggingBlock_ . getRootBlock ( ) ;
484+
485+ finishQueuedRenders ( ) . then ( ( ) => {
486+ blockAnimation . connectionUiEffect ( inferiorConnection . getSourceBlock ( ) ) ;
487+ // bringToFront is incredibly expensive. Delay until the next frame.
488+ setTimeout ( ( ) => {
489+ rootBlock . bringToFront ( ) ;
490+ } , 0 ) ;
491+ } ) ;
492+ }
493+ }
494+
310495 /** Fire a UI event at the end of a block drag. */
311496 protected fireDragEndEvent_ ( ) {
312497 const event = new ( eventUtils . get ( eventUtils . BLOCK_DRAG ) ) (
@@ -415,14 +600,9 @@ export class BlockDragger implements IBlockDragger {
415600 * @returns A possibly empty list of insertion marker blocks.
416601 */
417602 getInsertionMarkers ( ) : BlockSvg [ ] {
418- // No insertion markers with the old style of dragged connection managers.
419- if (
420- this . draggedConnectionManager_ &&
421- this . draggedConnectionManager_ . getInsertionMarkers
422- ) {
423- return this . draggedConnectionManager_ . getInsertionMarkers ( ) ;
424- }
425- return [ ] ;
603+ return this . workspace_
604+ . getAllBlocks ( )
605+ . filter ( ( block ) => block . isInsertionMarker ( ) ) ;
426606 }
427607}
428608
0 commit comments