File indexing completed on 2024-05-19 05:32:28

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()->geometryF();
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()->geometryF();
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()->geometryF();
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()->geometryF();
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()->geometryF();
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     m_children.insert(std::clamp<qsizetype>(position, 0, m_children.length()), item);
0315 
0316     if (wasEmpty) {
0317         Q_EMIT isLayoutChanged(true);
0318         for (auto *w : std::as_const(m_windows)) {
0319             Tile *tile = m_tiling->bestTileForPosition(w->moveResizeGeometry().center());
0320             w->setTile(tile);
0321         }
0322     }
0323 
0324     Q_EMIT childTilesChanged();
0325 }
0326 
0327 void Tile::destroyChild(Tile *tile)
0328 {
0329     removeChild(tile);
0330     delete tile;
0331 }
0332 
0333 void Tile::removeChild(Tile *child)
0334 {
0335     const bool wasEmpty = m_children.isEmpty();
0336     const int idx = m_children.indexOf(child);
0337     m_children.removeAll(child);
0338     if (m_children.isEmpty() && !wasEmpty) {
0339         Q_EMIT isLayoutChanged(false);
0340     }
0341     if (idx > -1) {
0342         for (int i = idx; i < m_children.count(); ++i) {
0343             Q_EMIT m_children[i]->rowChanged(i);
0344         }
0345     }
0346     Q_EMIT childTilesChanged();
0347 }
0348 
0349 QList<Tile *> Tile::childTiles() const
0350 {
0351     return m_children;
0352 }
0353 
0354 Tile *Tile::childTile(int row)
0355 {
0356     if (row < 0 || row >= m_children.size()) {
0357         return nullptr;
0358     }
0359     return m_children.value(row);
0360 }
0361 
0362 int Tile::childCount() const
0363 {
0364     return m_children.count();
0365 }
0366 
0367 QList<Tile *> Tile::descendants() const
0368 {
0369     QList<Tile *> tiles;
0370     for (auto *t : std::as_const(m_children)) {
0371         tiles << t << t->descendants();
0372     }
0373     return tiles;
0374 }
0375 
0376 Tile *Tile::parentTile() const
0377 {
0378     return m_parentTile;
0379 }
0380 
0381 void Tile::visitDescendants(std::function<void(const Tile *child)> callback) const
0382 {
0383     callback(this);
0384     for (const Tile *child : m_children) {
0385         child->visitDescendants(callback);
0386     }
0387 }
0388 
0389 TileManager *Tile::manager() const
0390 {
0391     return m_tiling;
0392 }
0393 
0394 int Tile::row() const
0395 {
0396     if (m_parentTile) {
0397         return m_parentTile->m_children.indexOf(this);
0398     }
0399 
0400     return -1;
0401 }
0402 
0403 Tile *Tile::nextSibling() const
0404 {
0405     const int r = row();
0406     if (!m_parentTile || row() >= m_parentTile->childCount() - 1) {
0407         return nullptr;
0408     } else {
0409         return m_parentTile->childTiles()[r + 1];
0410     }
0411 }
0412 
0413 Tile *Tile::previousSibling() const
0414 {
0415     const int r = row();
0416     if (r <= 0 || !m_parentTile) {
0417         return nullptr;
0418     } else {
0419         return m_parentTile->childTiles().at(r - 1);
0420     }
0421 }
0422 
0423 } // namespace KWin
0424 
0425 #include "moc_tile.cpp"