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 "customtile.h"
0011 #include "core/output.h"
0012 #include "tilemanager.h"
0013 
0014 namespace KWin
0015 {
0016 
0017 QDebug operator<<(QDebug debug, const CustomTile *tile)
0018 {
0019     QDebugStateSaver saver(debug);
0020     debug.nospace();
0021     if (tile) {
0022         debug << tile->metaObject()->className() << '(' << static_cast<const void *>(tile);
0023         debug << tile->relativeGeometry() << tile->layoutDirection();
0024         debug << ')';
0025     } else {
0026         debug << "Tile(0x0)";
0027     }
0028     return debug;
0029 }
0030 
0031 CustomTile::CustomTile(TileManager *tiling, CustomTile *parentItem)
0032     : Tile(tiling, parentItem)
0033 {
0034     setQuickTileMode(QuickTileFlag::Custom);
0035     m_geometryLock = true;
0036 }
0037 
0038 CustomTile *CustomTile::createChildAt(const QRectF &relativeGeometry, LayoutDirection layoutDirection, int position)
0039 {
0040     CustomTile *tile = new CustomTile(manager(), this);
0041     connect(tile, &CustomTile::layoutModified, this, &CustomTile::layoutModified);
0042     tile->setRelativeGeometry(relativeGeometry);
0043     tile->setLayoutDirection(layoutDirection);
0044     manager()->model()->beginInsertTile(tile, position);
0045     insertChild(position, tile);
0046     manager()->model()->endInsertTile();
0047     return tile;
0048 }
0049 
0050 void CustomTile::setRelativeGeometry(const QRectF &geom)
0051 {
0052     if (relativeGeometry() == geom) {
0053         return;
0054     }
0055 
0056     QRectF finalGeom = geom.intersected(QRectF(0, 0, 1, 1));
0057     finalGeom.setWidth(std::max(finalGeom.width(), minimumSize().width()));
0058     finalGeom.setHeight(std::max(finalGeom.height(), minimumSize().height()));
0059     // We couldn't set the minimum size and still remain in boundaries, do nothing
0060     if (finalGeom.right() > 1 || finalGeom.bottom() > 1) {
0061         return;
0062     }
0063 
0064     auto *parentT = static_cast<CustomTile *>(parentTile());
0065 
0066     if (!m_geometryLock && parentT && parentT->layoutDirection() != LayoutDirection::Floating) {
0067         m_geometryLock = true;
0068         if (finalGeom.left() != relativeGeometry().left()) {
0069             Tile *tile = nextTileAt(Qt::LeftEdge);
0070             if (tile) {
0071                 QRectF tileGeom = tile->relativeGeometry();
0072                 tileGeom.setRight(finalGeom.left());
0073                 tile->setRelativeGeometry(tileGeom);
0074                 // The other tile gometry may be not what we set due to size constraints
0075                 finalGeom.setLeft(tile->relativeGeometry().right());
0076             } else {
0077                 // We are at the left border of the screen, we are always at 0
0078                 finalGeom.setLeft(0);
0079             }
0080         }
0081         if (finalGeom.top() != relativeGeometry().top()) {
0082             auto *tile = nextTileAt(Qt::TopEdge);
0083             if (tile) {
0084                 auto tileGeom = tile->relativeGeometry();
0085                 tileGeom.setBottom(finalGeom.top());
0086                 tile->setRelativeGeometry(tileGeom);
0087                 finalGeom.setTop(tile->relativeGeometry().bottom());
0088             } else {
0089                 // We are at the top border of the screen, we are always at 0
0090                 finalGeom.setTop(0);
0091             }
0092         }
0093         if (finalGeom.right() != relativeGeometry().right()) {
0094             auto *tile = nextTileAt(Qt::RightEdge);
0095             if (tile) {
0096                 auto tileGeom = tile->relativeGeometry();
0097                 tileGeom.setLeft(finalGeom.right());
0098                 tile->setRelativeGeometry(tileGeom);
0099                 finalGeom.setRight(tile->relativeGeometry().left());
0100             } else {
0101                 // We are at the right border of the screen, we are always at 1
0102                 finalGeom.setRight(1);
0103             }
0104         }
0105         if (finalGeom.bottom() != relativeGeometry().bottom()) {
0106             auto *tile = nextTileAt(Qt::BottomEdge);
0107             if (tile) {
0108                 auto tileGeom = tile->relativeGeometry();
0109                 tileGeom.setTop(finalGeom.bottom());
0110                 tile->setRelativeGeometry(tileGeom);
0111                 finalGeom.setBottom(tile->relativeGeometry().top());
0112             } else {
0113                 // We are at the bottom border of the screen, we are always at 1
0114                 finalGeom.setBottom(1);
0115             }
0116         }
0117         m_geometryLock = false;
0118     } else if (parentT && parentT->layoutDirection() == LayoutDirection::Floating) {
0119         finalGeom = geom.intersected(parentT->relativeGeometry());
0120     }
0121 
0122     const auto childrenT = childTiles();
0123 
0124     // Resize all the children to fit in new size
0125     for (auto t : childrenT) {
0126         auto childGeom = t->relativeGeometry();
0127         childGeom = childGeom.intersected(finalGeom);
0128         if (layoutDirection() == LayoutDirection::Horizontal) {
0129             childGeom.setTop(finalGeom.top());
0130             childGeom.setBottom(finalGeom.bottom());
0131         } else if (layoutDirection() == LayoutDirection::Vertical) {
0132             childGeom.setLeft(finalGeom.left());
0133             childGeom.setRight(finalGeom.right());
0134         }
0135 
0136         t->setRelativeGeometry(childGeom);
0137     }
0138 
0139     if (!childrenT.isEmpty()) {
0140         auto childGeom = childrenT.first()->relativeGeometry();
0141         if (layoutDirection() == LayoutDirection::Horizontal) {
0142             childGeom.setLeft(finalGeom.left());
0143         } else if (layoutDirection() == LayoutDirection::Vertical) {
0144             childGeom.setTop(finalGeom.top());
0145         }
0146         childrenT.first()->setRelativeGeometry(childGeom);
0147     }
0148 
0149     // Make sure the last child item fills the area
0150     // TODO: ensure all children don't go below minimum size/resize all children
0151     if (!m_geometryLock && !childrenT.isEmpty() && layoutDirection() != LayoutDirection::Floating) {
0152         auto childGeom = childrenT.last()->relativeGeometry();
0153         childGeom.setRight(finalGeom.right());
0154         childGeom.setBottom(finalGeom.bottom());
0155         childrenT.last()->setRelativeGeometry(childGeom);
0156     }
0157 
0158     Tile::setRelativeGeometry(finalGeom);
0159     if (parentT) {
0160         Q_EMIT parentT->layoutModified();
0161     }
0162     m_geometryLock = false;
0163 }
0164 
0165 bool CustomTile::supportsResizeGravity(KWin::Gravity gravity)
0166 {
0167     if (layoutDirection() == LayoutDirection::Floating) {
0168         return gravity != Gravity::None;
0169     }
0170 
0171     return Tile::supportsResizeGravity(gravity);
0172 }
0173 
0174 void CustomTile::split(KWin::Tile::LayoutDirection newDirection)
0175 {
0176     auto *parentT = static_cast<CustomTile *>(parentTile());
0177 
0178     // If we are m_rootLayoutTile always create childrens, not siblings
0179     if (parentT && (parentT->childCount() < 2 || parentT->layoutDirection() == newDirection)) {
0180         // Add a new cell to the current layout
0181         setLayoutDirection(newDirection);
0182         QRectF newGeo;
0183         if (newDirection == LayoutDirection::Floating) {
0184             newGeo = QRectF(relativeGeometry().left() + 0.1, relativeGeometry().top() + 0.1, 0.3, 0.2);
0185             newGeo.setLeft(std::max(newGeo.left(), parentT->relativeGeometry().left()));
0186             newGeo.setTop(std::max(newGeo.top(), parentT->relativeGeometry().top()));
0187             newGeo.setRight(std::min(newGeo.right(), parentT->relativeGeometry().right()));
0188             newGeo.setBottom(std::min(newGeo.bottom(), parentT->relativeGeometry().bottom()));
0189         } else if (newDirection == LayoutDirection::Horizontal) {
0190             newGeo = relativeGeometry();
0191             newGeo.setWidth(relativeGeometry().width() / 2);
0192             Tile::setRelativeGeometry(newGeo);
0193             newGeo.moveLeft(newGeo.x() + newGeo.width());
0194         } else if (newDirection == LayoutDirection::Vertical) {
0195             newGeo = relativeGeometry();
0196             newGeo.setHeight(relativeGeometry().height() / 2);
0197             Tile::setRelativeGeometry(newGeo);
0198             newGeo.moveTop(newGeo.y() + newGeo.height());
0199         }
0200 
0201         parentT->createChildAt(newGeo, layoutDirection(), row() + 1);
0202     } else {
0203         // Do a new layout and put tiles inside
0204         setLayoutDirection(newDirection);
0205         auto newGeo = relativeGeometry();
0206         if (newDirection == LayoutDirection::Floating) {
0207             // Do a new layout with one floating tile inside
0208             auto startGeom = relativeGeometry();
0209             if (!childTiles().isEmpty()) {
0210                 startGeom = childTiles().last()->relativeGeometry();
0211             }
0212             newGeo = QRectF(startGeom.left() + 0.05, startGeom.top() + 0.05, 0.3, 0.25);
0213             newGeo.setLeft(std::max(newGeo.left(), relativeGeometry().left()));
0214             newGeo.setTop(std::max(newGeo.top(), relativeGeometry().top()));
0215             newGeo.setRight(std::min(newGeo.right(), relativeGeometry().right()));
0216             newGeo.setBottom(std::min(newGeo.bottom(), relativeGeometry().bottom()));
0217             createChildAt(newGeo, newDirection, childCount());
0218         } else if (newDirection == LayoutDirection::Horizontal) {
0219             // Do a new layout with 2 cells inside this one
0220             newGeo.setWidth(relativeGeometry().width() / 2);
0221             createChildAt(newGeo, newDirection, childCount());
0222             newGeo.moveLeft(newGeo.x() + newGeo.width());
0223             createChildAt(newGeo, newDirection, childCount());
0224         } else if (newDirection == LayoutDirection::Vertical) {
0225             // Do a new layout with 2 cells inside this one
0226             newGeo.setHeight(relativeGeometry().height() / 2);
0227             createChildAt(newGeo, newDirection, childCount());
0228             newGeo.moveTop(newGeo.y() + newGeo.height());
0229             createChildAt(newGeo, newDirection, childCount());
0230         }
0231     }
0232 }
0233 
0234 void CustomTile::moveByPixels(const QPointF &delta)
0235 {
0236     if (static_cast<CustomTile *>(parentTile())->layoutDirection() != LayoutDirection::Floating) {
0237         return;
0238     }
0239 
0240     const auto outGeom = manager()->output()->geometry();
0241     const auto relativeMove = QPointF(delta.x() / outGeom.width(), delta.y() / outGeom.height());
0242     auto geom = relativeGeometry();
0243     geom.moveTo(geom.topLeft() + relativeMove);
0244 
0245     setRelativeGeometry(geom);
0246 }
0247 
0248 void CustomTile::remove()
0249 {
0250     auto *parentT = static_cast<CustomTile *>(parentTile());
0251     if (!parentT) {
0252         return;
0253     }
0254 
0255     auto *prev = previousSibling();
0256     auto *next = nextSibling();
0257 
0258     manager()->model()->beginRemoveTile(this);
0259     parentT->removeChild(this);
0260     manager()->model()->endRemoveTile();
0261     manager()->tileRemoved(this);
0262 
0263     if (parentT->layoutDirection() == LayoutDirection::Horizontal) {
0264         if (prev && next) {
0265             auto geom = prev->relativeGeometry();
0266             geom.setRight(relativeGeometry().center().x());
0267             prev->setRelativeGeometry(geom);
0268             geom = next->relativeGeometry();
0269             geom.setLeft(relativeGeometry().center().x());
0270             next->setRelativeGeometry(geom);
0271         } else if (prev) {
0272             auto geom = prev->relativeGeometry();
0273             geom.setRight(relativeGeometry().right());
0274             prev->setRelativeGeometry(geom);
0275         } else if (next) {
0276             auto geom = next->relativeGeometry();
0277             geom.setLeft(relativeGeometry().left());
0278             next->setRelativeGeometry(geom);
0279         }
0280     } else if (parentT->layoutDirection() == LayoutDirection::Vertical) {
0281         if (prev && next) {
0282             auto geom = prev->relativeGeometry();
0283             geom.setBottom(relativeGeometry().center().y());
0284             prev->setRelativeGeometry(geom);
0285             geom = next->relativeGeometry();
0286             geom.setTop(relativeGeometry().center().y());
0287             next->setRelativeGeometry(geom);
0288         } else if (prev) {
0289             auto geom = prev->relativeGeometry();
0290             geom.setBottom(relativeGeometry().bottom());
0291             prev->setRelativeGeometry(geom);
0292         } else if (next) {
0293             auto geom = next->relativeGeometry();
0294             geom.setTop(relativeGeometry().top());
0295             next->setRelativeGeometry(geom);
0296         }
0297     }
0298 
0299     // On linear layouts remove the last one and promote the layout as leaf
0300     if (parentT->layoutDirection() != Tile::LayoutDirection::Floating && parentT->childCount() == 1) {
0301         auto *lastTile = static_cast<CustomTile *>(parentT->childTile(0));
0302         if (lastTile->childCount() == 0) {
0303             lastTile->remove();
0304         }
0305     }
0306 
0307     delete this;
0308 }
0309 
0310 CustomTile *CustomTile::nextTileAt(Qt::Edge edge) const
0311 {
0312     auto *parentT = static_cast<CustomTile *>(parentTile());
0313 
0314     // TODO: implement geometry base searching for floating?
0315     if (!parentT || parentT->layoutDirection() == LayoutDirection::Floating) {
0316         return nullptr;
0317     }
0318 
0319     Tile *sibling = nullptr;
0320 
0321     const int index = row();
0322     int layoutRows;
0323     int layoutColumns;
0324     switch (parentT->layoutDirection()) {
0325     case LayoutDirection::Vertical:
0326         layoutRows = std::max(1, parentT->childCount());
0327         layoutColumns = 1;
0328         break;
0329     case LayoutDirection::Horizontal:
0330     default:
0331         layoutColumns = std::max(1, parentT->childCount());
0332         layoutRows = 1;
0333         break;
0334     }
0335     int row = index / layoutColumns;
0336     int column = index % layoutColumns;
0337 
0338     switch (edge) {
0339     case Qt::LeftEdge:
0340         if (column > 0) {
0341             sibling = previousSibling();
0342         }
0343         break;
0344     case Qt::TopEdge:
0345         if (row > 0) {
0346             sibling = parentT->childTiles()[layoutColumns * (row - 1) + column];
0347         }
0348         break;
0349     case Qt::RightEdge:
0350         if (column < layoutColumns - 1) {
0351             sibling = nextSibling();
0352         }
0353         break;
0354     case Qt::BottomEdge:
0355         if (row < layoutRows - 1) {
0356             const int newIndex = layoutColumns * (row + 1) + column;
0357             if (newIndex < parentT->childCount()) {
0358                 sibling = parentT->childTiles()[newIndex];
0359             }
0360         }
0361         break;
0362     }
0363 
0364     if (sibling) {
0365         return static_cast<CustomTile *>(sibling);
0366     } else {
0367         return parentT->nextTileAt(edge);
0368     }
0369 }
0370 
0371 void CustomTile::setLayoutDirection(Tile::LayoutDirection dir)
0372 {
0373     if (m_layoutDirection == dir) {
0374         return;
0375     }
0376 
0377     m_layoutDirection = dir;
0378     Q_EMIT layoutDirectionChanged(dir);
0379 }
0380 
0381 Tile::LayoutDirection CustomTile::layoutDirection() const
0382 {
0383     return m_layoutDirection;
0384 }
0385 
0386 RootTile::RootTile(TileManager *tiling)
0387     : CustomTile(tiling, nullptr)
0388 {
0389     setParent(tiling);
0390     setRelativeGeometry({0, 0, 1, 1});
0391 }
0392 
0393 } // namespace KWin
0394 
0395 #include "moc_customtile.cpp"