5
5
import * as SVG from 'svg.js' ;
6
6
import consts from './consts' ;
7
7
import Crosshair from './crosshair' ;
8
- import { translateToSVG } from './shared' ;
8
+ import {
9
+ translateToSVG , PropType , stringifyPoints , translateToCanvas ,
10
+ } from './shared' ;
9
11
import { InteractionData , InteractionResult , Geometry } from './canvasModel' ;
10
12
11
13
export interface InteractionHandler {
@@ -26,6 +28,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
26
28
private crosshair : Crosshair ;
27
29
private threshold : SVG . Rect | null ;
28
30
private thresholdRectSize : number ;
31
+ private intermediateShape : PropType < InteractionData , 'intermediateShape' > ;
32
+ private drawnIntermediateShape : SVG . Shape ;
29
33
30
34
private prepareResult ( ) : InteractionResult [ ] {
31
35
return this . interactionShapes . map (
@@ -65,8 +69,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
65
69
return enabled && ! ctrlKey && ! ! interactionShapes . length ;
66
70
}
67
71
68
- const minPosVerticesAchieved = typeof minPosVertices === 'undefined' || minPosVertices <= positiveShapes . length ;
69
- const minNegVerticesAchieved = typeof minNegVertices === 'undefined' || minPosVertices <= negativeShapes . length ;
72
+ const minPosVerticesDefined = Number . isInteger ( minPosVertices ) ;
73
+ const minNegVerticesDefined = Number . isInteger ( minNegVertices ) && minNegVertices >= 0 ;
74
+ const minPosVerticesAchieved = ! minPosVerticesDefined || minPosVertices <= positiveShapes . length ;
75
+ const minNegVerticesAchieved = ! minNegVerticesDefined || minNegVertices <= negativeShapes . length ;
70
76
const minimumVerticesAchieved = minPosVerticesAchieved && minNegVerticesAchieved ;
71
77
return enabled && ! ctrlKey && minimumVerticesAchieved && shapesWereUpdated ;
72
78
}
@@ -91,7 +97,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
91
97
92
98
private interactPoints ( ) : void {
93
99
const eventListener = ( e : MouseEvent ) : void => {
94
- if ( ( e . button === 0 || ( e . button === 2 && this . interactionData . enableNegVertices ) ) && ! e . altKey ) {
100
+ if ( ( e . button === 0 || ( e . button === 2 && this . interactionData . minNegVertices >= 0 ) ) && ! e . altKey ) {
95
101
e . preventDefault ( ) ;
96
102
const [ cx , cy ] = translateToSVG ( ( this . canvas . node as any ) as SVGSVGElement , [ e . clientX , e . clientY ] ) ;
97
103
if ( ! this . isWithinFrame ( cx , cy ) ) return ;
@@ -121,8 +127,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
121
127
}
122
128
}
123
129
130
+ self . addClass ( 'cvat_canvas_removable_interaction_point' ) ;
124
131
self . attr ( {
125
132
'stroke-width' : consts . POINTS_SELECTED_STROKE_WIDTH / this . geometry . scale ,
133
+ r : ( consts . BASE_POINT_SIZE * 1.5 ) / this . geometry . scale ,
126
134
} ) ;
127
135
128
136
self . on ( 'mousedown' , ( _e : MouseEvent ) : void => {
@@ -132,6 +140,9 @@ export class InteractionHandlerImpl implements InteractionHandler {
132
140
this . interactionShapes = this . interactionShapes . filter (
133
141
( shape : SVG . Shape ) : boolean => shape !== self ,
134
142
) ;
143
+ if ( this . interactionData . startWithBox && this . interactionShapes . length === 1 ) {
144
+ this . interactionShapes [ 0 ] . style ( { visibility : '' } ) ;
145
+ }
135
146
this . shapesWereUpdated = true ;
136
147
if ( this . shouldRaiseEvent ( _e . ctrlKey ) ) {
137
148
this . onInteraction ( this . prepareResult ( ) , true , false ) ;
@@ -140,8 +151,10 @@ export class InteractionHandlerImpl implements InteractionHandler {
140
151
} ) ;
141
152
142
153
self . on ( 'mouseleave' , ( ) : void => {
154
+ self . removeClass ( 'cvat_canvas_removable_interaction_point' ) ;
143
155
self . attr ( {
144
156
'stroke-width' : consts . POINTS_STROKE_WIDTH / this . geometry . scale ,
157
+ r : consts . BASE_POINT_SIZE / this . geometry . scale ,
145
158
} ) ;
146
159
147
160
self . off ( 'mousedown' ) ;
@@ -153,7 +166,7 @@ export class InteractionHandlerImpl implements InteractionHandler {
153
166
this . canvas . on ( 'mousedown.interaction' , eventListener ) ;
154
167
}
155
168
156
- private interactRectangle ( ) : void {
169
+ private interactRectangle ( shouldFinish : boolean , onContinue ?: ( ) => void ) : void {
157
170
let initialized = false ;
158
171
const eventListener = ( e : MouseEvent ) : void => {
159
172
if ( e . button === 0 && ! e . altKey ) {
@@ -170,11 +183,15 @@ export class InteractionHandlerImpl implements InteractionHandler {
170
183
this . canvas . on ( 'mousedown.interaction' , eventListener ) ;
171
184
this . currentInteractionShape
172
185
. on ( 'drawstop' , ( ) : void => {
186
+ this . canvas . off ( 'mousedown.interaction' , eventListener ) ;
173
187
this . interactionShapes . push ( this . currentInteractionShape ) ;
174
188
this . shapesWereUpdated = true ;
175
189
176
- this . canvas . off ( 'mousedown.interaction' , eventListener ) ;
177
- this . interact ( { enabled : false } ) ;
190
+ if ( shouldFinish ) {
191
+ this . interact ( { enabled : false } ) ;
192
+ } else if ( onContinue ) {
193
+ onContinue ( ) ;
194
+ }
178
195
} )
179
196
. addClass ( 'cvat_canvas_shape_drawing' )
180
197
. attr ( {
@@ -194,15 +211,24 @@ export class InteractionHandlerImpl implements InteractionHandler {
194
211
195
212
private startInteraction ( ) : void {
196
213
if ( this . interactionData . shapeType === 'rectangle' ) {
197
- this . interactRectangle ( ) ;
214
+ this . interactRectangle ( true ) ;
198
215
} else if ( this . interactionData . shapeType === 'points' ) {
199
- this . interactPoints ( ) ;
216
+ if ( this . interactionData . startWithBox ) {
217
+ this . interactRectangle ( false , ( ) : void => this . interactPoints ( ) ) ;
218
+ } else {
219
+ this . interactPoints ( ) ;
220
+ }
200
221
} else {
201
222
throw new Error ( 'Interactor implementation supports only rectangle and points' ) ;
202
223
}
203
224
}
204
225
205
226
private release ( ) : void {
227
+ if ( this . drawnIntermediateShape ) {
228
+ this . drawnIntermediateShape . remove ( ) ;
229
+ this . drawnIntermediateShape = null ;
230
+ }
231
+
206
232
if ( this . crosshair ) {
207
233
this . removeCrosshair ( ) ;
208
234
}
@@ -241,6 +267,31 @@ export class InteractionHandlerImpl implements InteractionHandler {
241
267
return imageX >= 0 && imageX < width && imageY >= 0 && imageY < height ;
242
268
}
243
269
270
+ private updateIntermediateShape ( ) : void {
271
+ const { intermediateShape, geometry } = this ;
272
+ if ( this . drawnIntermediateShape ) {
273
+ this . drawnIntermediateShape . remove ( ) ;
274
+ }
275
+
276
+ if ( ! intermediateShape ) return ;
277
+ const { shapeType, points } = intermediateShape ;
278
+ if ( shapeType === 'polygon' ) {
279
+ this . drawnIntermediateShape = this . canvas
280
+ . polygon ( stringifyPoints ( translateToCanvas ( geometry . offset , points ) ) )
281
+ . attr ( {
282
+ 'color-rendering' : 'optimizeQuality' ,
283
+ 'shape-rendering' : 'geometricprecision' ,
284
+ 'stroke-width' : consts . BASE_STROKE_WIDTH / this . geometry . scale ,
285
+ fill : 'none' ,
286
+ } )
287
+ . addClass ( 'cvat_canvas_interact_intermediate_shape' ) ;
288
+ } else {
289
+ throw new Error (
290
+ `Shape type "${ shapeType } " was not implemented at interactionHandler::updateIntermediateShape` ,
291
+ ) ;
292
+ }
293
+ }
294
+
244
295
public constructor (
245
296
onInteraction : (
246
297
shapes : InteractionResult [ ] | null ,
@@ -264,6 +315,8 @@ export class InteractionHandlerImpl implements InteractionHandler {
264
315
this . crosshair = new Crosshair ( ) ;
265
316
this . threshold = null ;
266
317
this . thresholdRectSize = 300 ;
318
+ this . intermediateShape = null ;
319
+ this . drawnIntermediateShape = null ;
267
320
this . cursorPosition = {
268
321
x : 0 ,
269
322
y : 0 ,
@@ -334,16 +387,27 @@ export class InteractionHandlerImpl implements InteractionHandler {
334
387
: [ ...this . interactionShapes ] ;
335
388
for ( const shape of shapesToBeScaled ) {
336
389
if ( shape . type === 'circle' ) {
337
- ( shape as SVG . Circle ) . radius ( consts . BASE_POINT_SIZE / this . geometry . scale ) ;
338
- shape . attr ( 'stroke-width' , consts . POINTS_STROKE_WIDTH / this . geometry . scale ) ;
390
+ if ( shape . hasClass ( 'cvat_canvas_removable_interaction_point' ) ) {
391
+ ( shape as SVG . Circle ) . radius ( ( consts . BASE_POINT_SIZE * 1.5 ) / this . geometry . scale ) ;
392
+ shape . attr ( 'stroke-width' , consts . POINTS_SELECTED_STROKE_WIDTH / this . geometry . scale ) ;
393
+ } else {
394
+ ( shape as SVG . Circle ) . radius ( consts . BASE_POINT_SIZE / this . geometry . scale ) ;
395
+ shape . attr ( 'stroke-width' , consts . POINTS_STROKE_WIDTH / this . geometry . scale ) ;
396
+ }
339
397
} else {
340
398
shape . attr ( 'stroke-width' , consts . BASE_STROKE_WIDTH / this . geometry . scale ) ;
341
399
}
342
400
}
343
401
}
344
402
345
403
public interact ( interactionData : InteractionData ) : void {
346
- if ( interactionData . enabled ) {
404
+ if ( interactionData . intermediateShape ) {
405
+ this . intermediateShape = interactionData . intermediateShape ;
406
+ this . updateIntermediateShape ( ) ;
407
+ if ( this . interactionData . startWithBox ) {
408
+ this . interactionShapes [ 0 ] . style ( { visibility : 'hidden' } ) ;
409
+ }
410
+ } else if ( interactionData . enabled ) {
347
411
this . interactionData = interactionData ;
348
412
this . initInteraction ( ) ;
349
413
this . startInteraction ( ) ;
0 commit comments