KQuickImageEditor

SelectionTool.qml
1/* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
2 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
3 */
4
5import QtQuick
6import QtQml
7
8Item {
9 id: root
10 // make this readonly so it can be accessed without risking external modification
11 readonly property SelectionHandle pressedHandle: _private.pressedHandle
12 readonly property alias selectionArea: selectionArea
13 property alias selectionX: _private.pendingRect.x
14 property alias selectionY: _private.pendingRect.y
15 property alias selectionWidth: _private.pendingRect.width
16 property alias selectionHeight: _private.pendingRect.height
17 property int aspectRatio: SelectionTool.AspectRatio.Free
18
19 enum AspectRatio {
20 Free,
21 Square
22 }
23
24 QtObject {
25 id: _private
26
27 readonly property rect currentRect: Qt.rect(selectionArea.x, selectionArea.y, selectionArea.width, selectionArea.height)
28 property SelectionHandle pressedHandle: null
29 property bool applyingPendingRect: false
30 property bool updatingPendingRect: false
31 property rect pendingRect: currentRect
32
33 onPressedHandleChanged: {
34 if (!pressedHandle && !_private.applyingPendingRect) {
35 Qt.callLater(_private.updateHandles);
36 }
37 }
38
39 function isSquareRatio(): bool {
40 return root.aspectRatio === SelectionTool.AspectRatio.Square;
41 }
42
43 function updateHandles(): void {
44 if (_private.applyingPendingRect) {
45 return;
46 }
47
48 _private.applyingPendingRect = true;
49 selectionArea.x = _private.pendingRect.x;
50 selectionArea.y = _private.pendingRect.y;
51 selectionArea.width = _private.pendingRect.width;
52 selectionArea.height = _private.pendingRect.height;
53 _private.applyingPendingRect = false;
54 }
55
56 function updatePendingRect(): void {
57 if (_private.applyingPendingRect || _private.updatingPendingRect) {
58 return;
59 }
60
61 if ((selectionArea.pressed || _private.pressedHandle)) {
62 _private.updatingPendingRect = true;
63
64 if (_private.isSquareRatio()) {
65 const flagX = 0x1;
66 const flagY = 0x2;
67 const flagWidth = 0x4;
68 const flagHeight = 0x8;
69 const oldRect = _private.currentRect;
70 let wide = Math.max(oldRect.width, oldRect.height);
71 let newRect = oldRect;
72
73 function offset() {
74 if (newRect.x < 0 || newRect.y < 0) {
75 return Math.abs(Math.min(newRect.x, newRect.y));
76 } else if (newRect.x + newRect.width > root.width) {
77 return (newRect.x + newRect.width) - root.width;
78 } else if (newRect.y + newRect.height > root.height) {
79 return (newRect.y + newRect.height) - root.height;
80 }
81
82 return 0;
83 }
84
85 function patchValues(flags, offset) {
86 if (flags & flagX) newRect.x += offset;
87 if (flags & flagY) newRect.y += offset;
88 if (flags & flagWidth) newRect.width -= offset;
89 if (flags & flagHeight) newRect.height -= offset;
90 }
91
92 switch (_private.pressedHandle) {
93 case handleTopLeft:
94 newRect = Qt.rect(oldRect.right - wide, oldRect.bottom - wide, wide, wide);
95 patchValues(flagX | flagY | flagWidth | flagHeight, offset());
96 break;
97 case handleTopRight:
98 newRect = Qt.rect(oldRect.left, oldRect.bottom - wide, wide, wide);
99 patchValues(flagY | flagWidth | flagHeight, offset());
100 break;
101 case handleBottomRight:
102 newRect = Qt.rect(oldRect.left, oldRect.top, wide, wide);
103 patchValues(flagWidth | flagHeight, offset());
104 break;
105 case handleBottomLeft:
106 newRect = Qt.rect(oldRect.right - wide, oldRect.top, wide, wide);
107 patchValues(flagX | flagWidth | flagHeight, offset());
108 break;
109 }
110
111 if (_private.pendingRect !== newRect) {
112 _private.pendingRect = newRect;
113 }
114 } else {
115 _private.pendingRect = _private.currentRect;
116 }
117
118 _private.updatingPendingRect = false;
119 }
120 }
121 }
122
123 MouseArea {
124 id: selectionArea
125 x: 0
126 y: 0
127 z: 1
128 width: parent.width
129 height: parent.height
130 LayoutMirroring.enabled: false
131 anchors.left: if (_private.pressedHandle) {
132 if (_private.pressedHandle.backwardDiagonal) {
133 handleTopLeft.horizontalCenter
134 } else if (_private.pressedHandle.forwardDiagonal) {
135 handleBottomLeft.horizontalCenter
136 } else if (_private.pressedHandle.horizontalOnly) {
137 handleLeft.horizontalCenter
138 }
139 }
140 anchors.right: if (_private.pressedHandle) {
141 if (_private.pressedHandle.backwardDiagonal) {
142 handleBottomRight.horizontalCenter
143 } else if (_private.pressedHandle.forwardDiagonal) {
144 handleTopRight.horizontalCenter
145 } else if (_private.pressedHandle.horizontalOnly) {
146 handleRight.horizontalCenter
147 }
148 }
149 anchors.top: if (_private.pressedHandle) {
150 if (_private.pressedHandle.backwardDiagonal) {
151 handleTopLeft.verticalCenter
152 } else if (_private.pressedHandle.forwardDiagonal) {
153 handleTopRight.verticalCenter
154 } else if (_private.pressedHandle.verticalOnly) {
155 handleTop.verticalCenter
156 }
157 }
158 anchors.bottom: if (_private.pressedHandle) {
159 if (_private.pressedHandle.backwardDiagonal) {
160 handleBottomRight.verticalCenter
161 } else if (_private.pressedHandle.forwardDiagonal) {
162 handleBottomLeft.verticalCenter
163 } else if (_private.pressedHandle.verticalOnly) {
164 handleBottom.verticalCenter
165 }
166 }
167 enabled: drag.target
168 cursorShape: if (_private.pressedHandle || (pressed && enabled)) {
169 Qt.ClosedHandCursor
170 } else if (enabled) {
171 Qt.OpenHandCursor
172 } else {
173 Qt.ArrowCursor
174 }
175 drag {
176 axis: Drag.XAndYAxis
177 target: (selectionArea.width === root.width && selectionArea.height === root.height) || _private.pressedHandle ? null : selectionArea
178 minimumX: 0
179 maximumX: root.width - selectionArea.width
180 minimumY: 0
181 maximumY: root.height - selectionArea.height
182 threshold: 0
183 }
184
185 onMouseXChanged: _private.updatePendingRect()
186 onMouseYChanged: _private.updatePendingRect()
187 onXChanged: _private.updatePendingRect()
188 onYChanged: _private.updatePendingRect()
189 onWidthChanged: _private.updatePendingRect()
190 onHeightChanged: _private.updatePendingRect()
191 }
192
193 SelectionHandle {
194 id: handleTopLeft
195 target: selectionArea
196 position: SelectionHandle.TopLeft
197 lockX: _private.pressedHandle && _private.pressedHandle.backwardDiagonal
198 lockY: lockX
199 drag.maximumX: handleBottomRight.x - implicitWidth / 2
200 drag.maximumY: handleBottomRight.y - implicitHeight / 2
201 Binding {
202 target: _private; property: "pressedHandle"
203 value: handleTopLeft; when: !_private.applyingPendingRect && handleTopLeft.pressed
204 restoreMode: Binding.RestoreBindingOrValue
205 }
206 }
207 SelectionHandle {
208 id: handleTop
209 visible: !_private.isSquareRatio() && selectionArea.width >= implicitWidth
210 target: selectionArea
211 position: SelectionHandle.Top
212 lockY: _private.pressedHandle && _private.pressedHandle.verticalOnly
213 drag.maximumY: handleBottom.y - implicitHeight / 2
214 Binding {
215 target: _private; property: "pressedHandle"
216 value: handleTop; when: handleTop.pressed
217 restoreMode: Binding.RestoreBindingOrValue
218 }
219 }
220 SelectionHandle {
221 id: handleTopRight
222 target: selectionArea
223 position: SelectionHandle.TopRight
224 lockX: _private.pressedHandle && _private.pressedHandle.forwardDiagonal
225 lockY: lockX
226 drag.minimumX: handleBottomLeft.x + implicitWidth / 2
227 drag.maximumY: handleBottomLeft.y - implicitHeight / 2
228 Binding {
229 target: _private; property: "pressedHandle"
230 value: handleTopRight; when: !_private.applyingPendingRect && handleTopRight.pressed
231 restoreMode: Binding.RestoreBindingOrValue
232 }
233 }
234 SelectionHandle {
235 id: handleLeft
236 visible: !_private.isSquareRatio() && selectionArea.height >= implicitHeight
237 target: selectionArea
238 position: SelectionHandle.Left
239 lockX: _private.pressedHandle && _private.pressedHandle.horizontalOnly
240 drag.maximumX: handleRight.x - implicitWidth / 2
241 Binding {
242 target: _private; property: "pressedHandle"
243 value: handleLeft; when: handleLeft.pressed
244 restoreMode: Binding.RestoreBindingOrValue
245 }
246 }
247 SelectionHandle {
248 id: handleRight
249 visible: !_private.isSquareRatio() && selectionArea.height >= implicitHeight
250 target: selectionArea
251 position: SelectionHandle.Right
252 lockX: _private.pressedHandle && _private.pressedHandle.horizontalOnly
253 drag.minimumX: handleLeft.x + implicitWidth / 2
254 Binding {
255 target: _private; property: "pressedHandle"
256 value: handleRight; when: handleRight.pressed
257 restoreMode: Binding.RestoreBindingOrValue
258 }
259 }
260 SelectionHandle {
261 id: handleBottomLeft
262 target: selectionArea
263 position: SelectionHandle.BottomLeft
264 lockX: _private.pressedHandle && _private.pressedHandle.forwardDiagonal
265 lockY: lockX
266 drag.maximumX: handleTopRight.x - implicitWidth / 2
267 drag.minimumY: handleTopRight.y + implicitHeight / 2
268 Binding {
269 target: _private; property: "pressedHandle"
270 value: handleBottomLeft; when: !_private.applyingPendingRect && handleBottomLeft.pressed
271 restoreMode: Binding.RestoreBindingOrValue
272 }
273 }
274 SelectionHandle {
275 id: handleBottom
276 visible: !_private.isSquareRatio() && selectionArea.width >= implicitWidth
277 target: selectionArea
278 position: SelectionHandle.Bottom
279 lockY: _private.pressedHandle && _private.pressedHandle.verticalOnly
280 drag.minimumY: handleTop.y + implicitHeight / 2
281 Binding {
282 target: _private; property: "pressedHandle"
283 value: handleBottom; when: handleBottom.pressed
284 restoreMode: Binding.RestoreBindingOrValue
285 }
286 }
287 SelectionHandle {
288 id: handleBottomRight
289 target: selectionArea
290 position: SelectionHandle.BottomRight
291 lockX: _private.pressedHandle && _private.pressedHandle.backwardDiagonal
292 lockY: lockX
293 drag.minimumX: handleTopLeft.x + implicitWidth / 2
294 drag.minimumY: handleTopLeft.y + implicitHeight / 2
295 Binding {
296 target: _private; property: "pressedHandle"
297 value: handleBottomRight; when: !_private.applyingPendingRect && handleBottomRight.pressed
298 restoreMode: Binding.RestoreBindingOrValue
299 }
300 }
301 // TODO: maybe scale proportions instead of just limiting size
302 onWidthChanged: if (selectionArea.x + selectionArea.width > root.width) {
303 selectionArea.width = Math.max(root.width - selectionArea.x, handleTopLeft.implicitWidth/2)
304 if (selectionArea.x > root.width) {
305 selectionArea.x = Math.max(root.width - selectionArea.width, 0)
306 }
307 }
308 onHeightChanged: if (selectionArea.y + selectionArea.height > root.height) {
309 selectionArea.height = Math.max(root.height - selectionArea.y, handleTopLeft.implicitHeight/2)
310 if (selectionArea.y > root.height) {
311 selectionArea.y = Math.max(root.height - selectionArea.height, 0)
312 }
313 }
314}
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 11:52:59 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.