File indexing completed on 2024-05-19 16:34:56

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2022 Marco Martin <mart@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "tile.h"
0011 #include "core/output.h"
0012 #include "tilemanager.h"
0013 #include "virtualdesktops.h"
0014 #include "window.h"
0015 #include "workspace.h"
0016 
0017 #include <cmath>
0018 
0019 namespace KWin
0020 {
0021 
0022 QSizeF Tile::s_minimumSize = QSizeF(0.15, 0.15);
0023 
0024 Tile::Tile(TileManager *tiling, Tile *parent)
0025     : QObject(parent)
0026     , m_parentTile(parent)
0027     , m_tiling(tiling)
0028 {
0029     if (m_parentTile) {
0030         m_padding = m_parentTile->padding();
0031     }
0032     connect(Workspace::self(), &Workspace::configChanged, this, &Tile::windowGeometryChanged);
0033 }
0034 
0035 Tile::~Tile()
0036 {
0037     for (auto *t : std::as_const(m_children)) {
0038         // Prevents access upon child tiles destruction
0039         t->m_parentTile = nullptr;
0040     }
0041     if (m_parentTile) {
0042         m_parentTile->removeChild(this);
0043     }
0044     for (auto *w : std::as_const(m_windows)) {
0045         Tile *tile = m_tiling->bestTileForPosition(w->moveResizeGeometry().center());
0046         w->setTile(tile);
0047     }
0048 }
0049 
0050 bool Tile::supportsResizeGravity(Gravity gravity)
0051 {
0052     if (!m_parentTile) {
0053         return false;
0054     }
0055 
0056     switch (gravity) {
0057     case Gravity::Left:
0058         return m_relativeGeometry.left() > 0.0;
0059     case Gravity::Top:
0060         return m_relativeGeometry.top() > 0.0;
0061     case Gravity::Right:
0062         return m_relativeGeometry.right() < 1.0;
0063     case Gravity::Bottom:
0064         return m_relativeGeometry.bottom() < 1.0;
0065     case Gravity::TopLeft:
0066         return m_relativeGeometry.top() > 0.0 && m_relativeGeometry.left() > 0.0;
0067     case Gravity::TopRight:
0068         return m_relativeGeometry.top() > 0.0 && m_relativeGeometry.right() < 1.0;
0069     case Gravity::BottomLeft:
0070         return m_relativeGeometry.bottom() < 1.0 && m_relativeGeometry.left() > 0.0;
0071     case Gravity::BottomRight:
0072         return m_relativeGeometry.bottom() < 1.0 && m_relativeGeometry.right() < 1.0;
0073     case Gravity::None:
0074     default:
0075         return false;
0076     }
0077 }
0078 
0079 void Tile::setGeometryFromWindow(const QRectF &geom)
0080 {
0081     setGeometryFromAbsolute(geom + QMarginsF(m_padding, m_padding, m_padding, m_padding));
0082 }
0083 
0084 void Tile::setGeometryFromAbsolute(const QRectF &geom)
0085 {
0086     const QRectF outGeom = m_tiling->output()->fractionalGeometry();
0087     const QRectF relGeom((geom.x() - outGeom.x()) / outGeom.width(),
0088                          (geom.y() - outGeom.y()) / outGeom.height(),
0089                          geom.width() / outGeom.width(),
0090                          geom.height() / outGeom.height());
0091 
0092     setRelativeGeometry(relGeom);
0093 }
0094 
0095 void Tile::setRelativeGeometry(const QRectF &geom)
0096 {
0097     QRectF constrainedGeom = geom;
0098     constrainedGeom.setWidth(std::max(constrainedGeom.width(), s_minimumSize.width()));
0099     constrainedGeom.setHeight(std::max(constrainedGeom.height(), s_minimumSize.height()));
0100 
0101     if (m_relativeGeometry == constrainedGeom) {
0102         return;
0103     }
0104 
0105     m_relativeGeometry = constrainedGeom;
0106 
0107     Q_EMIT relativeGeometryChanged();
0108     Q_EMIT absoluteGeometryChanged();
0109     Q_EMIT windowGeometryChanged();
0110 
0111     for (auto *w : std::as_const(m_windows)) {
0112         w->moveResize(windowGeometry());
0113     }
0114 }
0115 
0116 QRectF Tile::relativeGeometry() const
0117 {
0118     return m_relativeGeometry;
0119 }
0120 
0121 QRectF Tile::absoluteGeometry() const
0122 {
0123     const QRectF geom = m_tiling->output()->fractionalGeometry();
0124     return QRectF(std::round(geom.x() + m_relativeGeometry.x() * geom.width()),
0125                   std::round(geom.y() + m_relativeGeometry.y() * geom.height()),
0126                   std::round(m_relativeGeometry.width() * geom.width()),
0127                   std::round(m_relativeGeometry.height() * geom.height()));
0128 }
0129 
0130 QRectF Tile::absoluteGeometryInScreen() const
0131 {
0132     const QRectF geom = m_tiling->output()->fractionalGeometry();
0133     return QRectF(std::round(m_relativeGeometry.x() * geom.width()),
0134                   std::round(m_relativeGeometry.y() * geom.height()),
0135                   std::round(m_relativeGeometry.width() * geom.width()),
0136                   std::round(m_relativeGeometry.height() * geom.height()));
0137 }
0138 
0139 QRectF Tile::windowGeometry() const
0140 {
0141     // Apply half padding between tiles and full against the screen edges
0142     QMarginsF effectiveMargins;
0143     effectiveMargins.setLeft(m_relativeGeometry.left() > 0.0 ? m_padding / 2.0 : m_padding);
0144     effectiveMargins.setTop(m_relativeGeometry.top() > 0.0 ? m_padding / 2.0 : m_padding);
0145     effectiveMargins.setRight(m_relativeGeometry.right() < 1.0 ? m_padding / 2.0 : m_padding);
0146     effectiveMargins.setBottom(m_relativeGeometry.bottom() < 1.0 ? m_padding / 2.0 : m_padding);
0147 
0148     const auto geom = absoluteGeometry();
0149     return geom.intersected(workspace()->clientArea(MaximizeArea, m_tiling->output(), VirtualDesktopManager::self()->currentDesktop())) - effectiveMargins;
0150 }
0151 
0152 QRectF Tile::maximizedWindowGeometry() const
0153 {
0154     const auto geom = absoluteGeometry();
0155     return geom.intersected(workspace()->clientArea(MaximizeArea, m_tiling->output(), VirtualDesktopManager::self()->currentDesktop()));
0156 }
0157 
0158 bool Tile::isLayout() const
0159 {
0160     // Items with a single child are not allowed, unless the root which is *always* layout
0161     return m_children.count() > 0 || !m_parentTile;
0162 }
0163 
0164 bool Tile::canBeRemoved() const
0165 {
0166     // The root tile can *never* be removed
0167     return m_parentTile;
0168 }
0169 
0170 qreal Tile::padding() const
0171 {
0172     // Assume padding is all the same
0173     return m_padding;
0174 }
0175 
0176 void Tile::setPadding(qreal padding)
0177 {
0178     if (m_padding == padding) {
0179         return;
0180     }
0181 
0182     m_padding = padding;
0183 
0184     for (auto *t : std::as_const(m_children)) {
0185         t->setPadding(padding);
0186     }
0187     for (auto *w : std::as_const(m_windows)) {
0188         w->moveResize(windowGeometry());
0189     }
0190 
0191     Q_EMIT paddingChanged(padding);
0192     Q_EMIT windowGeometryChanged();
0193 }
0194 
0195 QuickTileMode Tile::quickTileMode() const
0196 {
0197     return m_quickTileMode;
0198 }
0199 
0200 void Tile::setQuickTileMode(QuickTileMode mode)
0201 {
0202     m_quickTileMode = mode;
0203 }
0204 
0205 void Tile::resizeFromGravity(Gravity gravity, int x_root, int y_root)
0206 {
0207     if (!m_parentTile) {
0208         return;
0209     }
0210 
0211     const QRectF outGeom = m_tiling->output()->fractionalGeometry();
0212     const QPointF relativePos = QPointF((x_root - outGeom.x()) / outGeom.width(), (y_root - outGeom.y()) / outGeom.height());
0213     QRectF newGeom = m_relativeGeometry;
0214 
0215     switch (gravity) {
0216     case Gravity::TopLeft:
0217         newGeom.setTopLeft(relativePos - QPointF(m_padding / outGeom.width(), m_padding / outGeom.height()));
0218         break;
0219     case Gravity::BottomRight:
0220         newGeom.setBottomRight(relativePos + QPointF(m_padding / outGeom.width(), m_padding / outGeom.height()));
0221         break;
0222     case Gravity::BottomLeft:
0223         newGeom.setBottomLeft(relativePos + QPointF(-m_padding / outGeom.width(), m_padding / outGeom.height()));
0224         break;
0225     case Gravity::TopRight:
0226         newGeom.setTopRight(relativePos + QPointF(m_padding / outGeom.width(), -m_padding / outGeom.height()));
0227         break;
0228     case Gravity::Top:
0229         newGeom.setTop(relativePos.y() - m_padding / outGeom.height());
0230         break;
0231     case Gravity::Bottom:
0232         newGeom.setBottom(relativePos.y() + m_padding / outGeom.height());
0233         break;
0234     case Gravity::Left:
0235         newGeom.setLeft(relativePos.x() - m_padding / outGeom.width());
0236         break;
0237     case Gravity::Right:
0238         newGeom.setRight(relativePos.x() + m_padding / outGeom.width());
0239         break;
0240     case Gravity::None:
0241         Q_UNREACHABLE();
0242         break;
0243     }
0244 
0245     setRelativeGeometry(newGeom);
0246 }
0247 
0248 void Tile::resizeByPixels(qreal delta, Qt::Edge edge)
0249 {
0250     if (!m_parentTile) {
0251         return;
0252     }
0253 
0254     const auto outGeom = m_tiling->output()->fractionalGeometry();
0255     auto newGeom = m_relativeGeometry;
0256 
0257     switch (edge) {
0258     case Qt::LeftEdge: {
0259         qreal relativeDelta = delta / outGeom.width();
0260         newGeom.setLeft(newGeom.left() + relativeDelta);
0261         break;
0262     }
0263     case Qt::TopEdge: {
0264         qreal relativeDelta = delta / outGeom.height();
0265         newGeom.setTop(newGeom.top() + relativeDelta);
0266         break;
0267     }
0268     case Qt::RightEdge: {
0269         qreal relativeDelta = delta / outGeom.width();
0270         newGeom.setRight(newGeom.right() + relativeDelta);
0271         break;
0272     }
0273     case Qt::BottomEdge: {
0274         qreal relativeDelta = delta / outGeom.height();
0275         newGeom.setBottom(newGeom.bottom() + relativeDelta);
0276         break;
0277     }
0278     }
0279     setRelativeGeometry(newGeom);
0280 }
0281 
0282 void Tile::addWindow(Window *window)
0283 {
0284     if (!m_windows.contains(window)) {
0285         window->moveResize(windowGeometry());
0286         m_windows.append(window);
0287         window->setTile(this);
0288         Q_EMIT windowAdded(window);
0289         Q_EMIT windowsChanged();
0290     }
0291 }
0292 
0293 void Tile::removeWindow(Window *window)
0294 {
0295     // We already ensure there is a single copy of window in m_windows
0296     if (m_windows.removeOne(window)) {
0297         window->setTile(nullptr);
0298         Q_EMIT windowRemoved(window);
0299         Q_EMIT windowsChanged();
0300     }
0301 }
0302 
0303 QList<KWin::Window *> Tile::windows() const
0304 {
0305     return m_windows;
0306 }
0307 
0308 void Tile::insertChild(int position, Tile *item)
0309 {
0310     Q_ASSERT(position >= 0);
0311     const bool wasEmpty = m_children.isEmpty();
0312     item->setParent(this);
0313 
0314 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0315     m_children.insert(std::clamp(position, 0, m_children.length()), item);
0316 #else
0317     m_children.insert(std::clamp<qsizetype>(position, 0, m_children.length()), item);
0318 #endif
0319 
0320     if (wasEmpty) {
0321         Q_EMIT isLayoutChanged(true);
0322         for (auto *w : std::as_const(m_windows)) {
0323             Tile *tile = m_tiling->bestTileForPosition(w->moveResizeGeometry().center());
0324             w->setTile(tile);
0325         }
0326     }
0327 
0328     Q_EMIT childTilesChanged();
0329 }
0330 
0331 void Tile::destroyChild(Tile *tile)
0332 {
0333     removeChild(tile);
0334     delete tile;
0335 }
0336 
0337 void Tile::removeChild(Tile *child)
0338 {
0339     const bool wasEmpty = m_children.isEmpty();
0340     const int idx = m_children.indexOf(child);
0341     m_children.removeAll(child);
0342     if (m_children.isEmpty() && !wasEmpty) {
0343         Q_EMIT isLayoutChanged(false);
0344     }
0345     if (idx > -1) {
0346         for (int i = idx; i < m_children.count(); ++i) {
0347             Q_EMIT m_children[i]->rowChanged(i);
0348         }
0349     }
0350     Q_EMIT childTilesChanged();
0351 }
0352 
0353 QList<Tile *> Tile::childTiles() const
0354 {
0355     return m_children;
0356 }
0357 
0358 Tile *Tile::childTile(int row)
0359 {
0360     if (row < 0 || row >= m_children.size()) {
0361         return nullptr;
0362     }
0363     return m_children.value(row);
0364 }
0365 
0366 int Tile::childCount() const
0367 {
0368     return m_children.count();
0369 }
0370 
0371 QList<Tile *> Tile::descendants() const
0372 {
0373     QList<Tile *> tiles;
0374     for (auto *t : std::as_const(m_children)) {
0375         tiles << t << t->descendants();
0376     }
0377     return tiles;
0378 }
0379 
0380 Tile *Tile::parentTile() const
0381 {
0382     return m_parentTile;
0383 }
0384 
0385 void Tile::visitDescendants(std::function<void(const Tile *child)> callback) const
0386 {
0387     callback(this);
0388     for (const Tile *child : m_children) {
0389         child->visitDescendants(callback);
0390     }
0391 }
0392 
0393 TileManager *Tile::manager() const
0394 {
0395     return m_tiling;
0396 }
0397 
0398 int Tile::row() const
0399 {
0400     if (m_parentTile) {
0401         return m_parentTile->m_children.indexOf(const_cast<Tile *>(this));
0402     }
0403 
0404     return -1;
0405 }
0406 
0407 Tile *Tile::nextSibling() const
0408 {
0409     const int r = row();
0410     if (!m_parentTile || row() >= m_parentTile->childCount() - 1) {
0411         return nullptr;
0412     } else {
0413         return m_parentTile->childTiles()[r + 1];
0414     }
0415 }
0416 
0417 Tile *Tile::previousSibling() const
0418 {
0419     const int r = row();
0420     if (r <= 0 || !m_parentTile) {
0421         return nullptr;
0422     } else {
0423         return m_parentTile->childTiles().at(r - 1);
0424     }
0425 }
0426 
0427 } // namespace KWin
0428 
0429 #include "moc_tile.cpp"