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) && _private.isSquareRatio()) {
62 _private.updatingPendingRect = true;
63
64 const flagX = 0x1;
65 const flagY = 0x2;
66 const flagWidth = 0x4;
67 const flagHeight = 0x8;
68 const oldRect = _private.currentRect;
69 let wide = Math.max(oldRect.width, oldRect.height);
70 let newRect = oldRect;
71
72 function offset() {
73 if (newRect.x < 0 || newRect.y < 0) {
74 return Math.abs(Math.min(newRect.x, newRect.y));
75 } else if (newRect.x + newRect.width > root.width) {
76 return (newRect.x + newRect.width) - root.width;
77 } else if (newRect.y + newRect.height > root.height) {
78 return (newRect.y + newRect.height) - root.height;
79 }
80
81 return 0;
82 }
83
84 function patchValues(flags, offset) {
85 if (flags & flagX) newRect.x += offset;
86 if (flags & flagY) newRect.y += offset;
87 if (flags & flagWidth) newRect.width -= offset;
88 if (flags & flagHeight) newRect.height -= offset;
89 }
90
91 switch (_private.pressedHandle) {
92 case handleTopLeft:
93 newRect = Qt.rect(oldRect.right - wide, oldRect.bottom - wide, wide, wide);
94 patchValues(flagX | flagY | flagWidth | flagHeight, offset());
95 break;
96 case handleTopRight:
97 newRect = Qt.rect(oldRect.left, oldRect.bottom - wide, wide, wide);
98 patchValues(flagY | flagWidth | flagHeight, offset());
99 break;
100 case handleBottomRight:
101 newRect = Qt.rect(oldRect.left, oldRect.top, wide, wide);
102 patchValues(flagWidth | flagHeight, offset());
103 break;
104 case handleBottomLeft:
105 newRect = Qt.rect(oldRect.right - wide, oldRect.top, wide, wide);
106 patchValues(flagX | flagWidth | flagHeight, offset());
107 break;
108 }
109
110 if (_private.pendingRect !== newRect) {
111 _private.pendingRect = newRect;
112 }
113
114 _private.updatingPendingRect = false;
115 }
116 }
117 }
118
119 MouseArea {
120 id: selectionArea
121 x: 0
122 y: 0
123 z: 1
124 width: parent.width
125 height: parent.height
126 LayoutMirroring.enabled: false
127 anchors.left: if (_private.pressedHandle) {
128 if (_private.pressedHandle.backwardDiagonal) {
129 handleTopLeft.horizontalCenter
130 } else if (_private.pressedHandle.forwardDiagonal) {
131 handleBottomLeft.horizontalCenter
132 } else if (_private.pressedHandle.horizontalOnly) {
133 handleLeft.horizontalCenter
134 }
135 }
136 anchors.right: if (_private.pressedHandle) {
137 if (_private.pressedHandle.backwardDiagonal) {
138 handleBottomRight.horizontalCenter
139 } else if (_private.pressedHandle.forwardDiagonal) {
140 handleTopRight.horizontalCenter
141 } else if (_private.pressedHandle.horizontalOnly) {
142 handleRight.horizontalCenter
143 }
144 }
145 anchors.top: if (_private.pressedHandle) {
146 if (_private.pressedHandle.backwardDiagonal) {
147 handleTopLeft.verticalCenter
148 } else if (_private.pressedHandle.forwardDiagonal) {
149 handleTopRight.verticalCenter
150 } else if (_private.pressedHandle.verticalOnly) {
151 handleTop.verticalCenter
152 }
153 }
154 anchors.bottom: if (_private.pressedHandle) {
155 if (_private.pressedHandle.backwardDiagonal) {
156 handleBottomRight.verticalCenter
157 } else if (_private.pressedHandle.forwardDiagonal) {
158 handleBottomLeft.verticalCenter
159 } else if (_private.pressedHandle.verticalOnly) {
160 handleBottom.verticalCenter
161 }
162 }
163 enabled: drag.target
164 cursorShape: if (_private.pressedHandle || (pressed && enabled)) {
165 Qt.ClosedHandCursor
166 } else if (enabled) {
167 Qt.OpenHandCursor
168 } else {
169 Qt.ArrowCursor
170 }
171 drag {
172 axis: Drag.XAndYAxis
173 target: (selectionArea.width === root.width && selectionArea.height === root.height) || _private.pressedHandle ? null : selectionArea
174 minimumX: 0
175 maximumX: root.width - selectionArea.width
176 minimumY: 0
177 maximumY: root.height - selectionArea.height
178 threshold: 0
179 }
180
181 onMouseXChanged: _private.updatePendingRect()
182 onMouseYChanged: _private.updatePendingRect()
183 onXChanged: _private.updatePendingRect()
184 onYChanged: _private.updatePendingRect()
185 onWidthChanged: _private.updatePendingRect()
186 onHeightChanged: _private.updatePendingRect()
187 }
188
189 SelectionHandle {
190 id: handleTopLeft
191 target: selectionArea
192 position: SelectionHandle.TopLeft
193 lockX: _private.pressedHandle && _private.pressedHandle.backwardDiagonal
194 lockY: lockX
195 drag.maximumX: handleBottomRight.x - implicitWidth / 2
196 drag.maximumY: handleBottomRight.y - implicitHeight / 2
197 Binding {
198 target: _private; property: "pressedHandle"
199 value: handleTopLeft; when: !_private.applyingPendingRect && handleTopLeft.pressed
200 restoreMode: Binding.RestoreBindingOrValue
201 }
202 }
203 SelectionHandle {
204 id: handleTop
205 visible: !_private.isSquareRatio() && selectionArea.width >= implicitWidth
206 target: selectionArea
207 position: SelectionHandle.Top
208 lockY: _private.pressedHandle && _private.pressedHandle.verticalOnly
209 drag.maximumY: handleBottom.y - implicitHeight / 2
210 Binding {
211 target: _private; property: "pressedHandle"
212 value: handleTop; when: handleTop.pressed
213 restoreMode: Binding.RestoreBindingOrValue
214 }
215 }
216 SelectionHandle {
217 id: handleTopRight
218 target: selectionArea
219 position: SelectionHandle.TopRight
220 lockX: _private.pressedHandle && _private.pressedHandle.forwardDiagonal
221 lockY: lockX
222 drag.minimumX: handleBottomLeft.x + implicitWidth / 2
223 drag.maximumY: handleBottomLeft.y - implicitHeight / 2
224 Binding {
225 target: _private; property: "pressedHandle"
226 value: handleTopRight; when: !_private.applyingPendingRect && handleTopRight.pressed
227 restoreMode: Binding.RestoreBindingOrValue
228 }
229 }
230 SelectionHandle {
231 id: handleLeft
232 visible: !_private.isSquareRatio() && selectionArea.height >= implicitHeight
233 target: selectionArea
234 position: SelectionHandle.Left
235 lockX: _private.pressedHandle && _private.pressedHandle.horizontalOnly
236 drag.maximumX: handleRight.x - implicitWidth / 2
237 Binding {
238 target: _private; property: "pressedHandle"
239 value: handleLeft; when: handleLeft.pressed
240 restoreMode: Binding.RestoreBindingOrValue
241 }
242 }
243 SelectionHandle {
244 id: handleRight
245 visible: !_private.isSquareRatio() && selectionArea.height >= implicitHeight
246 target: selectionArea
247 position: SelectionHandle.Right
248 lockX: _private.pressedHandle && _private.pressedHandle.horizontalOnly
249 drag.minimumX: handleLeft.x + implicitWidth / 2
250 Binding {
251 target: _private; property: "pressedHandle"
252 value: handleRight; when: handleRight.pressed
253 restoreMode: Binding.RestoreBindingOrValue
254 }
255 }
256 SelectionHandle {
257 id: handleBottomLeft
258 target: selectionArea
259 position: SelectionHandle.BottomLeft
260 lockX: _private.pressedHandle && _private.pressedHandle.forwardDiagonal
261 lockY: lockX
262 drag.maximumX: handleTopRight.x - implicitWidth / 2
263 drag.minimumY: handleTopRight.y + implicitHeight / 2
264 Binding {
265 target: _private; property: "pressedHandle"
266 value: handleBottomLeft; when: !_private.applyingPendingRect && handleBottomLeft.pressed
267 restoreMode: Binding.RestoreBindingOrValue
268 }
269 }
270 SelectionHandle {
271 id: handleBottom
272 visible: !_private.isSquareRatio() && selectionArea.width >= implicitWidth
273 target: selectionArea
274 position: SelectionHandle.Bottom
275 lockY: _private.pressedHandle && _private.pressedHandle.verticalOnly
276 drag.minimumY: handleTop.y + implicitHeight / 2
277 Binding {
278 target: _private; property: "pressedHandle"
279 value: handleBottom; when: handleBottom.pressed
280 restoreMode: Binding.RestoreBindingOrValue
281 }
282 }
283 SelectionHandle {
284 id: handleBottomRight
285 target: selectionArea
286 position: SelectionHandle.BottomRight
287 lockX: _private.pressedHandle && _private.pressedHandle.backwardDiagonal
288 lockY: lockX
289 drag.minimumX: handleTopLeft.x + implicitWidth / 2
290 drag.minimumY: handleTopLeft.y + implicitHeight / 2
291 Binding {
292 target: _private; property: "pressedHandle"
293 value: handleBottomRight; when:!_private.applyingPendingRect && handleBottomRight.pressed
294 restoreMode: Binding.RestoreBindingOrValue
295 }
296 }
297 // TODO: maybe scale proportions instead of just limiting size
298 onWidthChanged: if (selectionArea.x + selectionArea.width > root.width) {
299 selectionArea.width = Math.max(root.width - selectionArea.x, handleTopLeft.implicitWidth/2)
300 if (selectionArea.x > root.width) {
301 selectionArea.x = Math.max(root.width - selectionArea.width, 0)
302 }
303 }
304 onHeightChanged: if (selectionArea.y + selectionArea.height > root.height) {
305 selectionArea.height = Math.max(root.height - selectionArea.y, handleTopLeft.implicitHeight/2)
306 if (selectionArea.y > root.height) {
307 selectionArea.y = Math.max(root.height - selectionArea.height, 0)
308 }
309 }
310}
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 27 2024 11:49:24 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.