Plasma-framework

transientplacementhint.cpp
1/*
2 SPDX-FileCopyrightText: 2023 David Edmundson <davidedmundson@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "transientplacementhint_p.h"
7#include <QSharedData>
8
9#include <QDebug>
10#include <QGuiApplication>
11#include <QScreen>
12#include <QWindow>
13
14// This class is proposed for Qt6.something, but it's not there yet.
15// keep as an implementation detail, and then drop eventually (famous last words)
16
17class TransientPlacementHintPrivate : public QSharedData
18{
19public:
20 QRect parentAnchorRect;
21 Qt::Edges parentAnchor = Qt::BottomEdge | Qt::RightEdge;
22 Qt::Edges popupAnchor = Qt::TopEdge | Qt::LeftEdge;
23 Qt::Orientations slideConstraintAdjustments = Qt::Horizontal | Qt::Vertical;
24 Qt::Orientations flipConstraintAdjustments;
25 int margin = 0;
26};
27/*!
28 * Constructs a new QTransientPlacementHint
29 */
30TransientPlacementHint::TransientPlacementHint()
31 : d(new TransientPlacementHintPrivate)
32{
33}
34
35TransientPlacementHint::~TransientPlacementHint()
36{
37}
38
39TransientPlacementHint::TransientPlacementHint(const TransientPlacementHint &other)
40{
41 d = other.d;
42}
43TransientPlacementHint &TransientPlacementHint::operator=(const TransientPlacementHint &other)
44{
45 d = other.d;
46 return *this;
47}
48
49bool TransientPlacementHint::isValid() const
50{
51 return d->parentAnchorRect.isValid();
52}
53
54void TransientPlacementHint::setParentAnchorArea(const QRect &parentAnchorRect)
55{
56 d->parentAnchorRect = parentAnchorRect;
57}
58
59QRect TransientPlacementHint::parentAnchorArea() const
60{
61 return d->parentAnchorRect;
62}
63
64void TransientPlacementHint::setParentAnchor(Qt::Edges parentAnchor)
65{
66 d->parentAnchor = parentAnchor;
67}
68
69Qt::Edges TransientPlacementHint::parentAnchor() const
70{
71 return d->parentAnchor;
72}
73
74void TransientPlacementHint::setPopupAnchor(Qt::Edges popupAnchor)
75{
76 d->popupAnchor = popupAnchor;
77}
78
79Qt::Edges TransientPlacementHint::popupAnchor() const
80{
81 return d->popupAnchor;
82}
83
84void TransientPlacementHint::setSlideConstraintAdjustments(Qt::Orientations slideConstraintAdjustments)
85{
86 d->slideConstraintAdjustments = slideConstraintAdjustments;
87}
88
89Qt::Orientations TransientPlacementHint::slideConstraintAdjustments() const
90{
91 return d->slideConstraintAdjustments;
92}
93
94void TransientPlacementHint::setFlipConstraintAdjustments(Qt::Orientations flipConstraintAdjustments)
95{
96 d->flipConstraintAdjustments = flipConstraintAdjustments;
97}
98
99Qt::Orientations TransientPlacementHint::flipConstraintAdjustments() const
100{
101 return d->flipConstraintAdjustments;
102}
103
104int TransientPlacementHint::margin() const
105{
106 return d->margin;
107}
108
109void TransientPlacementHint::setMargin(int margin)
110{
111 d->margin = margin;
112}
113
114static QPoint popupPosition(const QRect &anchorRect, const Qt::Edges parentAnchor, const Qt::Edges popupAnchor, const QSize &popupSize)
115{
116 QPoint anchorPoint;
117 switch (parentAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
118 case Qt::LeftEdge:
119 anchorPoint.setX(anchorRect.x());
120 break;
121 case Qt::RightEdge:
122 anchorPoint.setX(anchorRect.x() + anchorRect.width());
123 break;
124 default:
125 anchorPoint.setX(qRound(anchorRect.x() + anchorRect.width() / 2.0));
126 }
127 switch (parentAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
128 case Qt::TopEdge:
129 anchorPoint.setY(anchorRect.y());
130 break;
131 case Qt::BottomEdge:
132 anchorPoint.setY(anchorRect.y() + anchorRect.height());
133 break;
134 default:
135 anchorPoint.setY(qRound(anchorRect.y() + anchorRect.height() / 2.0));
136 }
137 // calculate where the top left point of the popup will end up with the applied popup anchor
138 QPoint popupPosAdjust;
139 switch (popupAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
140 case Qt::LeftEdge:
141 popupPosAdjust.setX(0);
142 break;
143 case Qt::RightEdge:
144 popupPosAdjust.setX(-popupSize.width());
145 break;
146 default:
147 popupPosAdjust.setX(qRound(-popupSize.width() / 2.0));
148 }
149 switch (popupAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
150 case Qt::TopEdge:
151 popupPosAdjust.setY(0);
152 break;
153 case Qt::BottomEdge:
154 popupPosAdjust.setY(-popupSize.height());
155 break;
156 default:
157 popupPosAdjust.setY(qRound(-popupSize.height() / 2.0));
158 }
159 return anchorPoint + popupPosAdjust;
160}
161
162QRect TransientPlacementHelper::popupRect(QWindow *w, const TransientPlacementHint &placement)
163{
164 // We are not checking the placement being valid, as visual parents with size 0 is an
165 // allowed thing, also, every PlasmoidItem will initially be 0x0 when created
166 QScreen *screen = nullptr;
167 QRect globalParentAnchorRect = placement.parentAnchorArea();
168 if (w->transientParent()) {
169 globalParentAnchorRect = globalParentAnchorRect.translated(w->transientParent()->position());
170 screen = w->transientParent()->screen();
171 }
172
173 const QMargins margin(placement.margin(), placement.margin(), placement.margin(), placement.margin());
174 QSize paddedWindowSize = w->size().grownBy(margin);
175 QRect popupRect = QRect(popupPosition(globalParentAnchorRect, placement.parentAnchor(), placement.popupAnchor(), paddedWindowSize), paddedWindowSize);
176
177 if (!screen)
178 screen = qApp->screenAt(globalParentAnchorRect.center());
179 if (!screen)
180 screen = qApp->primaryScreen();
181
182 const QRect screenArea = screen->geometry();
183
184 auto inScreenArea = [screenArea](const QRect &target, Qt::Edges edges = Qt::LeftEdge | Qt::RightEdge | Qt::TopEdge | Qt::BottomEdge) -> bool {
185 if (edges & Qt::LeftEdge && target.left() < screenArea.left()) {
186 return false;
187 }
188 if (edges & Qt::TopEdge && target.top() < screenArea.top()) {
189 return false;
190 }
191 if (edges & Qt::RightEdge && target.right() > screenArea.right()) {
192 return false;
193 }
194 if (edges & Qt::BottomEdge && target.bottom() > screenArea.bottom()) {
195 return false;
196 }
197 return true;
198 };
199
200 // if that fits, we don't need to do anything
201 if (inScreenArea(popupRect)) {
202 return popupRect.marginsRemoved(margin);
203 }
204 // Otherwise,
205 if (placement.flipConstraintAdjustments() & Qt::Horizontal) {
206 if (!inScreenArea(popupRect, Qt::LeftEdge | Qt::RightEdge)) {
207 // flip both edges (if either bit is set, XOR both)
208 auto flippedParentAnchor = placement.parentAnchor();
209 if (flippedParentAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
210 flippedParentAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
211 }
212 auto flippedPopupAnchor = placement.popupAnchor();
213 if (flippedPopupAnchor & (Qt::LeftEdge | Qt::RightEdge)) {
214 flippedPopupAnchor ^= (Qt::LeftEdge | Qt::RightEdge);
215 }
216 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
217 // if it still doesn't fit we should continue with the unflipped version
218 if (inScreenArea(flippedPopupRect, Qt::LeftEdge | Qt::RightEdge)) {
219 popupRect.moveLeft(flippedPopupRect.left());
220 }
221 }
222 }
223 if (placement.slideConstraintAdjustments() & Qt::Horizontal) {
224 if (!inScreenArea(popupRect, Qt::LeftEdge)) {
225 popupRect.moveLeft(screenArea.left());
226 }
227 if (!inScreenArea(popupRect, Qt::RightEdge)) {
228 popupRect.moveRight(screenArea.right());
229 }
230 }
231 if (placement.flipConstraintAdjustments() & Qt::Vertical) {
232 if (!inScreenArea(popupRect, Qt::TopEdge | Qt::BottomEdge)) {
233 // flip both edges (if either bit is set, XOR both)
234 auto flippedParentAnchor = placement.parentAnchor();
235 if (flippedParentAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
236 flippedParentAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
237 }
238 auto flippedPopupAnchor = placement.popupAnchor();
239 if (flippedPopupAnchor & (Qt::TopEdge | Qt::BottomEdge)) {
240 flippedPopupAnchor ^= (Qt::TopEdge | Qt::BottomEdge);
241 }
242 QRect flippedPopupRect = QRect(popupPosition(globalParentAnchorRect, flippedParentAnchor, flippedPopupAnchor, w->size()), w->size());
243 // if it still doesn't fit we should continue with the unflipped version
244 if (inScreenArea(flippedPopupRect, Qt::TopEdge | Qt::BottomEdge)) {
245 popupRect.moveTop(flippedPopupRect.top());
246 }
247 }
248 }
249 if (placement.slideConstraintAdjustments() & Qt::Vertical) {
250 if (!inScreenArea(popupRect, Qt::TopEdge)) {
251 popupRect.moveTop(screenArea.top());
252 }
253 if (!inScreenArea(popupRect, Qt::BottomEdge)) {
254 popupRect.moveBottom(screenArea.bottom());
255 }
256 }
257 return popupRect.marginsRemoved(margin);
258}
void setX(int x)
void setY(int y)
int bottom() const const
QPoint center() const const
int height() const const
int left() const const
QRect marginsRemoved(const QMargins &margins) const const
void moveBottom(int y)
void moveLeft(int x)
void moveRight(int x)
void moveTop(int y)
int right() const const
int top() const const
QRect translated(const QPoint &offset) const const
int width() const const
int x() const const
int y() const const
QSize grownBy(QMargins margins) const const
int height() const const
int width() const const
typedef Edges
typedef Orientations
virtual QSize size() const const override
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:54:11 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.