@@ -15,6 +15,10 @@ Window {
1515
1616 property real startX: 0
1717 property real startY: 0
18+ property real dragSelectionX: 0
19+ property real dragSelectionY: 0
20+ property real dragSelectionWidth: 0
21+ property real dragSelectionHeight: 0
1822 property real selectionX: 0
1923 property real selectionY: 0
2024 property real selectionWidth: 0
@@ -29,11 +33,16 @@ Window {
2933 property bool textEditing: false
3034 property string activeTool: " "
3135 property string previousDrawTool: " pen"
36+ property string selectionDragMode: " "
3237 property string selectedColor: " #FF5967"
3338 property string hoveredPixelColor: " #000000"
3439 property var annotations: []
3540 property var draftAnnotation: null
3641 readonly property color shadeColor: " #52000000"
42+ readonly property real minimumSelectionSize: 8
43+ readonly property real newSelectionDragThreshold: 3
44+ readonly property real resizeHitMargin: 7
45+ readonly property real selectionHandleSize: 8
3746 readonly property bool selectionValid: selectionWidth >= 8 && selectionHeight >= 8
3847 readonly property real imageLeft: (width - baseImage .paintedWidth ) / 2
3948 readonly property real imageTop: (height - baseImage .paintedHeight ) / 2
@@ -70,6 +79,7 @@ Window {
7079 textEditing = false
7180 activeTool = " "
7281 previousDrawTool = " pen"
82+ selectionDragMode = " "
7383 annotations = []
7484 draftAnnotation = null
7585 showFullScreen ()
@@ -84,6 +94,171 @@ Window {
8494 return Math .max (selectableTop, Math .min (selectableBottom, value))
8595 }
8696
97+ function resizeModeAt (x , y ) {
98+ if (! editing || ! selectionValid)
99+ return " "
100+
101+ const left = selectionX
102+ const top = selectionY
103+ const right = selectionX + selectionWidth
104+ const bottom = selectionY + selectionHeight
105+ const nearLeft = Math .abs (x - left) <= resizeHitMargin
106+ const nearRight = Math .abs (x - right) <= resizeHitMargin
107+ const nearTop = Math .abs (y - top) <= resizeHitMargin
108+ const nearBottom = Math .abs (y - bottom) <= resizeHitMargin
109+ const withinHorizontal = x >= left - resizeHitMargin
110+ && x <= right + resizeHitMargin
111+ const withinVertical = y >= top - resizeHitMargin
112+ && y <= bottom + resizeHitMargin
113+
114+ if (nearLeft && nearTop) return " nw"
115+ if (nearRight && nearTop) return " ne"
116+ if (nearLeft && nearBottom) return " sw"
117+ if (nearRight && nearBottom) return " se"
118+ if (nearTop && withinHorizontal) return " n"
119+ if (nearBottom && withinHorizontal) return " s"
120+ if (nearLeft && withinVertical) return " w"
121+ if (nearRight && withinVertical) return " e"
122+ return " "
123+ }
124+
125+ function resizeCursor (mode ) {
126+ if (mode === " n" || mode === " s" ) return Qt .SizeVerCursor
127+ if (mode === " e" || mode === " w" ) return Qt .SizeHorCursor
128+ if (mode === " nw" || mode === " se" ) return Qt .SizeFDiagCursor
129+ if (mode === " ne" || mode === " sw" ) return Qt .SizeBDiagCursor
130+ return Qt .ArrowCursor
131+ }
132+
133+ function selectionCursorShape () {
134+ if (selectionDragMode === " new" || selectionDragMode === " pendingNew" )
135+ return Qt .CrossCursor
136+ const mode = selectionDragMode !== " "
137+ ? selectionDragMode : resizeModeAt (pointerX, pointerY)
138+ if (mode !== " " )
139+ return resizeCursor (mode)
140+ if (! editing || ! pointInsideSelection (pointerX, pointerY))
141+ return Qt .CrossCursor
142+ if (activeTool === " text" )
143+ return Qt .IBeamCursor
144+ return activeTool !== " " ? Qt .CrossCursor : Qt .ArrowCursor
145+ }
146+
147+ function handleCenterX (mode ) {
148+ if (mode .indexOf (" w" ) !== - 1 ) return selectionX
149+ if (mode .indexOf (" e" ) !== - 1 ) return selectionX + selectionWidth
150+ return selectionX + selectionWidth / 2
151+ }
152+
153+ function handleCenterY (mode ) {
154+ if (mode .indexOf (" n" ) !== - 1 ) return selectionY
155+ if (mode .indexOf (" s" ) !== - 1 ) return selectionY + selectionHeight
156+ return selectionY + selectionHeight / 2
157+ }
158+
159+ function clearSelectionContent () {
160+ editing = false
161+ activeTool = " "
162+ colorPaletteVisible = false
163+ textEditing = false
164+ textInput .text = " "
165+ annotations = []
166+ draftAnnotation = null
167+ annotationLayer .requestPaint ()
168+ }
169+
170+ function beginNewSelection (x , y ) {
171+ clearSelectionContent ()
172+ const imageX = clampImageX (x)
173+ const imageY = clampImageY (y)
174+ startX = imageX
175+ startY = imageY
176+ selectionX = imageX
177+ selectionY = imageY
178+ selectionWidth = 0
179+ selectionHeight = 0
180+ selectionDragMode = " new"
181+ }
182+
183+ function prepareNewSelection (x , y ) {
184+ startX = clampImageX (x)
185+ startY = clampImageY (y)
186+ colorPaletteVisible = false
187+ selectionDragMode = " pendingNew"
188+ }
189+
190+ function beginSelectionResize (mode ) {
191+ selectionDragMode = mode
192+ dragSelectionX = selectionX
193+ dragSelectionY = selectionY
194+ dragSelectionWidth = selectionWidth
195+ dragSelectionHeight = selectionHeight
196+ colorPaletteVisible = false
197+ draftAnnotation = null
198+ }
199+
200+ function translateAnnotations (deltaX , deltaY ) {
201+ if ((deltaX === 0 && deltaY === 0 ) || annotations .length === 0 )
202+ return
203+
204+ const translated = []
205+ for (let i = 0 ; i < annotations .length ; ++ i) {
206+ const item = annotations[i]
207+ const copy = {}
208+ for (let key in item)
209+ copy[key] = item[key]
210+ if (typeof item .x1 === " number" ) copy .x1 = item .x1 + deltaX
211+ if (typeof item .y1 === " number" ) copy .y1 = item .y1 + deltaY
212+ if (typeof item .x2 === " number" ) copy .x2 = item .x2 + deltaX
213+ if (typeof item .y2 === " number" ) copy .y2 = item .y2 + deltaY
214+ if (item .points ) {
215+ copy .points = []
216+ for (let j = 0 ; j < item .points .length ; ++ j) {
217+ copy .points .push ({
218+ x: item .points [j].x + deltaX,
219+ y: item .points [j].y + deltaY
220+ })
221+ }
222+ }
223+ translated .push (copy)
224+ }
225+ annotations = translated
226+ }
227+
228+ function updateSelectionResize (x , y ) {
229+ const mode = selectionDragMode
230+ const originalRight = dragSelectionX + dragSelectionWidth
231+ const originalBottom = dragSelectionY + dragSelectionHeight
232+ const imageX = clampImageX (x)
233+ const imageY = clampImageY (y)
234+ let left = dragSelectionX
235+ let top = dragSelectionY
236+ let right = originalRight
237+ let bottom = originalBottom
238+
239+ if (mode .indexOf (" w" ) !== - 1 )
240+ left = Math .max (selectableLeft,
241+ Math .min (originalRight - minimumSelectionSize, imageX))
242+ if (mode .indexOf (" e" ) !== - 1 )
243+ right = Math .min (selectableRight,
244+ Math .max (dragSelectionX + minimumSelectionSize, imageX))
245+ if (mode .indexOf (" n" ) !== - 1 )
246+ top = Math .max (selectableTop,
247+ Math .min (originalBottom - minimumSelectionSize, imageY))
248+ if (mode .indexOf (" s" ) !== - 1 )
249+ bottom = Math .min (selectableBottom,
250+ Math .max (dragSelectionY + minimumSelectionSize, imageY))
251+
252+ const deltaX = selectionX - left
253+ const deltaY = selectionY - top
254+ selectionX = left
255+ selectionY = top
256+ selectionWidth = right - left
257+ selectionHeight = bottom - top
258+ translateAnnotations (deltaX, deltaY)
259+ annotationLayer .requestPaint ()
260+ }
261+
87262 function cancel () {
88263 if (finishing)
89264 return
@@ -103,16 +278,10 @@ Window {
103278 }
104279
105280 function resetSelection () {
106- editing = false
107- activeTool = " "
108- colorPaletteVisible = false
109- textEditing = false
110- textInput .text = " "
111- annotations = []
112- draftAnnotation = null
281+ selectionDragMode = " "
282+ clearSelectionContent ()
113283 selectionWidth = 0
114284 selectionHeight = 0
115- annotationLayer .requestPaint ()
116285 }
117286
118287 function undo () {
@@ -259,13 +428,35 @@ Window {
259428 z: 3
260429 }
261430
431+ Item {
432+ anchors .fill : parent
433+ visible: overlay .editing && overlay .selectionValid
434+ z: 6
435+
436+ Repeater {
437+ model: [" nw" , " n" , " ne" , " e" , " se" , " s" , " sw" , " w" ]
438+
439+ delegate: Rectangle {
440+ id: selectionHandle
441+ required property string modelData
442+ x: overlay .handleCenterX (modelData) - width / 2
443+ y: overlay .handleCenterY (modelData) - height / 2
444+ width: overlay .selectionHandleSize
445+ height: overlay .selectionHandleSize
446+ radius: 1
447+ color: " white"
448+ border .width : 1
449+ border .color : " #4358E8"
450+ }
451+ }
452+ }
453+
262454 MouseArea {
263455 id: selectMouse
264456 anchors .fill : parent
265457 enabled: ! overlay .textEditing
266- cursorShape: ! overlay .editing ? Qt .CrossCursor
267- : overlay .activeTool === " text" ? Qt .IBeamCursor
268- : overlay .activeTool !== " " ? Qt .CrossCursor : Qt .ArrowCursor
458+ hoverEnabled: true
459+ cursorShape: overlay .selectionCursorShape ()
269460 z: 5
270461
271462 onPressed : mouse => {
@@ -274,17 +465,20 @@ Window {
274465 overlay .pointerX = mouse .x
275466 overlay .pointerY = mouse .y
276467 if (! overlay .editing ) {
277- const imageX = overlay .clampImageX (mouse .x )
278- const imageY = overlay .clampImageY (mouse .y )
279- overlay .startX = imageX
280- overlay .startY = imageY
281- overlay .selectionX = imageX
282- overlay .selectionY = imageY
283- overlay .selectionWidth = 0
284- overlay .selectionHeight = 0
468+ overlay .beginNewSelection (mouse .x , mouse .y )
285469 return
286470 }
287- if (overlay .activeTool === " " || ! overlay .pointInsideSelection (mouse .x , mouse .y ))
471+
472+ const resizeMode = overlay .resizeModeAt (mouse .x , mouse .y )
473+ if (resizeMode !== " " ) {
474+ overlay .beginSelectionResize (resizeMode)
475+ return
476+ }
477+ if (! overlay .pointInsideSelection (mouse .x , mouse .y )) {
478+ overlay .prepareNewSelection (mouse .x , mouse .y )
479+ return
480+ }
481+ if (overlay .activeTool === " " )
288482 return
289483
290484 if (overlay .activeTool === " picker" ) {
@@ -323,7 +517,17 @@ Window {
323517 }
324518 if (! pressed || overlay .finishing )
325519 return
326- if (! overlay .editing ) {
520+ if (overlay .selectionDragMode === " pendingNew" ) {
521+ const pendingX = overlay .clampImageX (mouse .x )
522+ const pendingY = overlay .clampImageY (mouse .y )
523+ if (Math .abs (pendingX - overlay .startX ) < overlay .newSelectionDragThreshold
524+ && Math .abs (pendingY - overlay .startY )
525+ < overlay .newSelectionDragThreshold ) {
526+ return
527+ }
528+ overlay .beginNewSelection (overlay .startX , overlay .startY )
529+ }
530+ if (overlay .selectionDragMode === " new" ) {
327531 const imageX = overlay .clampImageX (mouse .x )
328532 const imageY = overlay .clampImageY (mouse .y )
329533 overlay .selectionX = Math .min (overlay .startX , imageX)
@@ -332,6 +536,10 @@ Window {
332536 overlay .selectionHeight = Math .abs (imageY - overlay .startY )
333537 return
334538 }
539+ if (overlay .selectionDragMode !== " " ) {
540+ overlay .updateSelectionResize (mouse .x , mouse .y )
541+ return
542+ }
335543 if (! overlay .draftAnnotation )
336544 return
337545 const p = overlay .localPoint (mouse .x , mouse .y )
@@ -345,12 +553,21 @@ Window {
345553 onReleased: {
346554 if (overlay .finishing )
347555 return
348- if (! overlay .editing ) {
556+ if (overlay .selectionDragMode === " pendingNew" ) {
557+ overlay .selectionDragMode = " "
558+ return
559+ }
560+ if (overlay .selectionDragMode === " new" ) {
561+ overlay .selectionDragMode = " "
349562 if (overlay .selectionValid ) {
350563 overlay .editing = true
351564 }
352565 return
353566 }
567+ if (overlay .selectionDragMode !== " " ) {
568+ overlay .selectionDragMode = " "
569+ return
570+ }
354571 if (! overlay .draftAnnotation )
355572 return
356573 const dx = Math .abs (overlay .draftAnnotation .x2 - overlay .draftAnnotation .x1 )
@@ -359,6 +576,12 @@ Window {
359576 overlay .annotations = overlay .annotations .concat ([overlay .draftAnnotation ])
360577 overlay .draftAnnotation = null
361578 }
579+
580+ onCanceled: {
581+ overlay .selectionDragMode = " "
582+ overlay .draftAnnotation = null
583+ overlay .editing = overlay .selectionValid
584+ }
362585 }
363586
364587 Rectangle {
0 commit comments