1 /*
2     SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "resizehandle.h"
8 
9 #include <QCursor>
10 #include <cmath>
11 
ResizeHandle(QQuickItem * parent)12 ResizeHandle::ResizeHandle(QQuickItem *parent)
13     : QQuickItem(parent)
14 {
15     setAcceptedMouseButtons(Qt::LeftButton);
16 
17     QQuickItem *candidate = parent;
18     while (candidate) {
19         ConfigOverlay *overlay = qobject_cast<ConfigOverlay *>(candidate);
20         if (overlay) {
21             setConfigOverlay(overlay);
22             break;
23         }
24 
25         candidate = candidate->parentItem();
26     }
27 
28     connect(this, &QQuickItem::parentChanged, this, [this]() {
29         QQuickItem *candidate = parentItem();
30         while (candidate) {
31             ConfigOverlay *overlay = qobject_cast<ConfigOverlay *>(candidate);
32             if (overlay) {
33                 setConfigOverlay(overlay);
34                 break;
35             }
36 
37             candidate = candidate->parentItem();
38         }
39     });
40 
41     auto syncCursor = [this]() {
42         switch (m_resizeCorner) {
43         case Left:
44         case Right:
45             setCursor(QCursor(Qt::SizeHorCursor));
46             break;
47         case Top:
48         case Bottom:
49             setCursor(QCursor(Qt::SizeVerCursor));
50             break;
51         case TopLeft:
52         case BottomRight:
53             setCursor(QCursor(Qt::SizeFDiagCursor));
54             break;
55         case TopRight:
56         case BottomLeft:
57         default:
58             setCursor(Qt::SizeBDiagCursor);
59         }
60     };
61 
62     syncCursor();
63     connect(this, &ResizeHandle::resizeCornerChanged, this, syncCursor);
64 }
65 
~ResizeHandle()66 ResizeHandle::~ResizeHandle()
67 {
68 }
69 
resizeBlocked() const70 bool ResizeHandle::resizeBlocked() const
71 {
72     return m_resizeWidthBlocked || m_resizeHeightBlocked;
73 }
74 
setPressed(bool pressed)75 void ResizeHandle::setPressed(bool pressed)
76 {
77     if (pressed == m_pressed) {
78         return;
79     }
80 
81     m_pressed = pressed;
82     emit pressedChanged();
83 }
84 
isPressed() const85 bool ResizeHandle::isPressed() const
86 {
87     return m_pressed;
88 }
89 
resizeLeft() const90 bool ResizeHandle::resizeLeft() const
91 {
92     return m_resizeCorner == Left || m_resizeCorner == TopLeft || m_resizeCorner == BottomLeft;
93 }
94 
resizeTop() const95 bool ResizeHandle::resizeTop() const
96 {
97     return m_resizeCorner == Top || m_resizeCorner == TopLeft || m_resizeCorner == TopRight;
98 }
99 
resizeRight() const100 bool ResizeHandle::resizeRight() const
101 {
102     return m_resizeCorner == Right || m_resizeCorner == TopRight || m_resizeCorner == BottomRight;
103 }
104 
resizeBottom() const105 bool ResizeHandle::resizeBottom() const
106 {
107     return m_resizeCorner == Bottom || m_resizeCorner == BottomLeft || m_resizeCorner == BottomRight;
108 }
109 
setResizeBlocked(bool width,bool height)110 void ResizeHandle::setResizeBlocked(bool width, bool height)
111 {
112     if (m_resizeWidthBlocked == width && m_resizeHeightBlocked == height) {
113         return;
114     }
115 
116     m_resizeWidthBlocked = width;
117     m_resizeHeightBlocked = height;
118 
119     emit resizeBlockedChanged();
120 }
121 
mousePressEvent(QMouseEvent * event)122 void ResizeHandle::mousePressEvent(QMouseEvent *event)
123 {
124     ItemContainer *itemContainer = m_configOverlay->itemContainer();
125     if (!itemContainer) {
126         return;
127     }
128     m_mouseDownPosition = event->windowPos();
129     m_mouseDownGeometry = QRectF(itemContainer->x(), itemContainer->y(), itemContainer->width(), itemContainer->height());
130     setResizeBlocked(false, false);
131     setPressed(true);
132     event->accept();
133 }
134 
mouseMoveEvent(QMouseEvent * event)135 void ResizeHandle::mouseMoveEvent(QMouseEvent *event)
136 {
137     if (!m_configOverlay || !m_configOverlay->itemContainer()) {
138         return;
139     }
140 
141     ItemContainer *itemContainer = m_configOverlay->itemContainer();
142     AppletsLayout *layout = itemContainer->layout();
143 
144     if (!layout) {
145         return;
146     }
147 
148     layout->releaseSpace(itemContainer);
149     const QPointF difference = m_mouseDownPosition - event->windowPos();
150 
151     QSizeF minimumSize = QSize(layout->minimumItemWidth(), layout->minimumItemHeight());
152     if (itemContainer->layoutAttached()) {
153         minimumSize.setWidth(qMax(minimumSize.width(), itemContainer->layoutAttached()->property("minimumWidth").toReal()));
154         minimumSize.setHeight(qMax(minimumSize.height(), itemContainer->layoutAttached()->property("minimumHeight").toReal()));
155     }
156 
157     // Now make minimumSize an integer number of cells
158     minimumSize.setWidth(ceil(minimumSize.width() / layout->cellWidth()) * layout->cellWidth());
159     minimumSize.setHeight(ceil(minimumSize.height() / layout->cellWidth()) * layout->cellHeight());
160 
161     // Horizontal resize
162     if (resizeLeft()) {
163         const qreal width = qMax(minimumSize.width(), m_mouseDownGeometry.width() + difference.x());
164         const qreal x = m_mouseDownGeometry.x() + (m_mouseDownGeometry.width() - width);
165 
166         // -1 to have a bit of margins around
167         if (layout->isRectAvailable(x - 1, m_mouseDownGeometry.y(), width, m_mouseDownGeometry.height())) {
168             itemContainer->setX(x);
169             itemContainer->setWidth(width);
170             setResizeBlocked(m_mouseDownGeometry.width() + difference.x() < minimumSize.width(), m_resizeHeightBlocked);
171         } else {
172             setResizeBlocked(true, m_resizeHeightBlocked);
173         }
174     } else if (resizeRight()) {
175         const qreal width = qMax(minimumSize.width(), m_mouseDownGeometry.width() - difference.x());
176 
177         if (layout->isRectAvailable(m_mouseDownGeometry.x(), m_mouseDownGeometry.y(), width, m_mouseDownGeometry.height())) {
178             itemContainer->setWidth(width);
179             setResizeBlocked(m_mouseDownGeometry.width() - difference.x() < minimumSize.width(), m_resizeHeightBlocked);
180         } else {
181             setResizeBlocked(true, m_resizeHeightBlocked);
182         }
183     }
184 
185     // Vertical Resize
186     if (resizeTop()) {
187         const qreal height = qMax(minimumSize.height(), m_mouseDownGeometry.height() + difference.y());
188         const qreal y = m_mouseDownGeometry.y() + (m_mouseDownGeometry.height() - height);
189 
190         // -1 to have a bit of margins around
191         if (layout->isRectAvailable(m_mouseDownGeometry.x(), y - 1, m_mouseDownGeometry.width(), m_mouseDownGeometry.height())) {
192             itemContainer->setY(y);
193             itemContainer->setHeight(height);
194             setResizeBlocked(m_resizeWidthBlocked, m_mouseDownGeometry.height() + difference.y() < minimumSize.height());
195         } else {
196             setResizeBlocked(m_resizeWidthBlocked, true);
197         }
198     } else if (resizeBottom()) {
199         const qreal height = qMax(minimumSize.height(), m_mouseDownGeometry.height() - difference.y());
200 
201         if (layout->isRectAvailable(m_mouseDownGeometry.x(), m_mouseDownGeometry.y(), m_mouseDownGeometry.width(), height)) {
202             itemContainer->setHeight(qMax(height, minimumSize.height()));
203             setResizeBlocked(m_resizeWidthBlocked, m_mouseDownGeometry.height() - difference.y() < minimumSize.height());
204         } else {
205             setResizeBlocked(m_resizeWidthBlocked, true);
206         }
207     }
208 
209     event->accept();
210 }
211 
mouseReleaseEvent(QMouseEvent * event)212 void ResizeHandle::mouseReleaseEvent(QMouseEvent *event)
213 {
214     setPressed(false);
215     if (!m_configOverlay || !m_configOverlay->itemContainer()) {
216         return;
217     }
218 
219     ItemContainer *itemContainer = m_configOverlay->itemContainer();
220     AppletsLayout *layout = itemContainer->layout();
221 
222     if (!layout) {
223         return;
224     }
225 
226     layout->positionItem(itemContainer);
227 
228     event->accept();
229 
230     setResizeBlocked(false, false);
231     emit resizeBlockedChanged();
232 }
233 
mouseUngrabEvent()234 void ResizeHandle::mouseUngrabEvent()
235 {
236     setPressed(false);
237 }
238 
setConfigOverlay(ConfigOverlay * handle)239 void ResizeHandle::setConfigOverlay(ConfigOverlay *handle)
240 {
241     if (handle == m_configOverlay) {
242         return;
243     }
244 
245     m_configOverlay = handle;
246 }
247 
248 #include "moc_resizehandle.cpp"
249