effects/overview: implement new layout algorithm
Replace old "closest" and "natural" layout algorithms with new layout algorithm. The new layout algorithm tries to - use screen space efficiently, given diverse geometries of windows - be aesthetically pleasing - and minimize movement of windows from initial positions. More concretely, find a layered layout, where each layer, or strip, is a row or column. Ensure that different strips have similar widths, and use binary search to find a packing with similar aspect ratio to the layout area. Within each strip, minimize horizontal movement (for rows) or vertical movement (for columns) of windows. Run time is O(n) (up to log factors), where n is the number of windows. CCBUG: 453749 BUG: 450263 BUG: 477833 BUG: 478097 BUG: 477830
This commit is contained in:
parent
7732f0e56b
commit
c3cda8b62a
13 changed files with 677 additions and 554 deletions
|
@ -17,69 +17,48 @@
|
|||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_LayoutMode">
|
||||
<property name="text">
|
||||
<string>Layout mode:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="kcfg_LayoutMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Closest</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Natural</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Ignore minimized windows:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_IgnoreMinimized">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Organize windows in the Grid View:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_OrganizedGrid">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_FilterWindows">
|
||||
<property name="text">
|
||||
<string>Search results include filtered windows:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_FilterWindows">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="KShortcutsEditor" name="shortcutsEditor">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
|
|
|
@ -10,9 +10,6 @@
|
|||
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
|
||||
<kcfgfile arg="true"/>
|
||||
<group name="Effect-overview">
|
||||
<entry name="LayoutMode" type="Int">
|
||||
<default>1</default>
|
||||
</entry>
|
||||
<entry name="IgnoreMinimized" type="bool">
|
||||
<default>false</default>
|
||||
</entry>
|
||||
|
|
|
@ -172,7 +172,6 @@ OverviewEffect::~OverviewEffect()
|
|||
void OverviewEffect::reconfigure(ReconfigureFlags)
|
||||
{
|
||||
OverviewConfig::self()->read();
|
||||
setLayout(OverviewConfig::layoutMode());
|
||||
setAnimationDuration(animationTime(300));
|
||||
setFilterWindows(OverviewConfig::filterWindows());
|
||||
|
||||
|
@ -252,11 +251,6 @@ QPointF OverviewEffect::desktopOffset() const
|
|||
return m_desktopOffset;
|
||||
}
|
||||
|
||||
int OverviewEffect::layout() const
|
||||
{
|
||||
return m_layout;
|
||||
}
|
||||
|
||||
bool OverviewEffect::ignoreMinimized() const
|
||||
{
|
||||
return OverviewConfig::ignoreMinimized();
|
||||
|
@ -267,14 +261,6 @@ bool OverviewEffect::organizedGrid() const
|
|||
return OverviewConfig::organizedGrid();
|
||||
}
|
||||
|
||||
void OverviewEffect::setLayout(int layout)
|
||||
{
|
||||
if (m_layout != layout) {
|
||||
m_layout = layout;
|
||||
Q_EMIT layoutChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int OverviewEffect::requestedEffectChainPosition() const
|
||||
{
|
||||
return 70;
|
||||
|
|
|
@ -18,7 +18,6 @@ class OverviewEffect : public QuickSceneEffect
|
|||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int animationDuration READ animationDuration NOTIFY animationDurationChanged)
|
||||
Q_PROPERTY(int layout READ layout NOTIFY layoutChanged)
|
||||
Q_PROPERTY(bool ignoreMinimized READ ignoreMinimized NOTIFY ignoreMinimizedChanged)
|
||||
Q_PROPERTY(bool filterWindows READ filterWindows NOTIFY filterWindowsChanged)
|
||||
Q_PROPERTY(bool organizedGrid READ organizedGrid NOTIFY organizedGridChanged)
|
||||
|
@ -36,9 +35,6 @@ public:
|
|||
OverviewEffect();
|
||||
~OverviewEffect() override;
|
||||
|
||||
int layout() const;
|
||||
void setLayout(int layout);
|
||||
|
||||
bool ignoreMinimized() const;
|
||||
bool organizedGrid() const;
|
||||
|
||||
|
@ -65,7 +61,6 @@ public:
|
|||
|
||||
Q_SIGNALS:
|
||||
void animationDurationChanged();
|
||||
void layoutChanged();
|
||||
void overviewPartialActivationFactorChanged();
|
||||
void overviewGestureInProgressChanged();
|
||||
void transitionPartialActivationFactorChanged();
|
||||
|
@ -102,7 +97,6 @@ private:
|
|||
QPointF m_desktopOffset;
|
||||
bool m_filterWindows = true;
|
||||
int m_animationDuration = 400;
|
||||
int m_layout = 1;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
||||
|
|
|
@ -602,7 +602,6 @@ FocusScope {
|
|||
Drag.hotSpot: Qt.point(width * 0.5, height * 0.5)
|
||||
Drag.keys: ["kwin-desktop"]
|
||||
|
||||
layout.mode: effect.layout
|
||||
focus: current
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
animationDuration: effect.animationDuration
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
|
||||
// The layouting code is taken from the present windows effect.
|
||||
SPDX-FileCopyrightText: 2007 Rivo Laks <rivolaks@hot.ee>
|
||||
SPDX-FileCopyrightText: 2008 Lucas Murray <lmurray@undefinedfire.com>
|
||||
SPDX-FileCopyrightText: 2024 Yifan Zhu <fanzhuyifan@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
@ -11,6 +8,8 @@
|
|||
#include "expolayout.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <tuple>
|
||||
|
||||
ExpoCell::ExpoCell(QObject *parent)
|
||||
: QObject(parent)
|
||||
|
@ -222,45 +221,17 @@ ExpoLayout::ExpoLayout(QQuickItem *parent)
|
|||
{
|
||||
}
|
||||
|
||||
ExpoLayout::LayoutMode ExpoLayout::mode() const
|
||||
ExpoLayout::PlacementMode ExpoLayout::placementMode() const
|
||||
{
|
||||
return m_mode;
|
||||
return m_placementMode;
|
||||
}
|
||||
|
||||
void ExpoLayout::setMode(LayoutMode mode)
|
||||
void ExpoLayout::setPlacementMode(PlacementMode mode)
|
||||
{
|
||||
if (m_mode != mode) {
|
||||
m_mode = mode;
|
||||
if (m_placementMode != mode) {
|
||||
m_placementMode = mode;
|
||||
polish();
|
||||
Q_EMIT modeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool ExpoLayout::fillGaps() const
|
||||
{
|
||||
return m_fillGaps;
|
||||
}
|
||||
|
||||
void ExpoLayout::setFillGaps(bool fill)
|
||||
{
|
||||
if (m_fillGaps != fill) {
|
||||
m_fillGaps = fill;
|
||||
polish();
|
||||
Q_EMIT fillGapsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int ExpoLayout::spacing() const
|
||||
{
|
||||
return m_spacing;
|
||||
}
|
||||
|
||||
void ExpoLayout::setSpacing(int spacing)
|
||||
{
|
||||
if (m_spacing != spacing) {
|
||||
m_spacing = spacing;
|
||||
polish();
|
||||
Q_EMIT spacingChanged();
|
||||
Q_EMIT placementModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,25 +253,6 @@ void ExpoLayout::forceLayout()
|
|||
updatePolish();
|
||||
}
|
||||
|
||||
void ExpoLayout::updatePolish()
|
||||
{
|
||||
if (!m_cells.isEmpty()) {
|
||||
switch (m_mode) {
|
||||
case LayoutClosest:
|
||||
calculateWindowTransformationsClosest();
|
||||
break;
|
||||
case LayoutNatural:
|
||||
calculateWindowTransformationsNatural();
|
||||
break;
|
||||
case LayoutNone:
|
||||
resetTransformations();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setReady();
|
||||
}
|
||||
|
||||
void ExpoLayout::addCell(ExpoCell *cell)
|
||||
{
|
||||
Q_ASSERT(!m_cells.contains(cell));
|
||||
|
@ -322,397 +274,482 @@ void ExpoLayout::geometryChange(const QRectF &newGeometry, const QRectF &oldGeom
|
|||
QQuickItem::geometryChange(newGeometry, oldGeometry);
|
||||
}
|
||||
|
||||
static int distance(const QPoint &a, const QPoint &b)
|
||||
// Move and scale rect to fit inside area
|
||||
static void moveToFit(QRectF &rect, const QRectF &area)
|
||||
{
|
||||
const int xdiff = a.x() - b.x();
|
||||
const int ydiff = a.y() - b.y();
|
||||
return int(std::sqrt(qreal(xdiff * xdiff + ydiff * ydiff)));
|
||||
qreal scale = std::min(area.width() / rect.width(), area.height() / rect.height());
|
||||
rect.setWidth(rect.width() * scale);
|
||||
rect.setHeight(rect.height() * scale);
|
||||
rect.moveCenter(area.center());
|
||||
}
|
||||
|
||||
static QRect centered(ExpoCell *cell, const QRect &bounds)
|
||||
void ExpoLayout::updatePolish()
|
||||
{
|
||||
const QSize scaled = QSize(cell->naturalWidth(), cell->naturalHeight())
|
||||
.scaled(bounds.size(), Qt::KeepAspectRatio);
|
||||
|
||||
return QRect(bounds.center().x() - scaled.width() / 2,
|
||||
bounds.center().y() - scaled.height() / 2,
|
||||
scaled.width(),
|
||||
scaled.height());
|
||||
}
|
||||
|
||||
void ExpoLayout::calculateWindowTransformationsClosest()
|
||||
{
|
||||
QRect area = QRect(0, 0, width(), height());
|
||||
const int columns = int(std::ceil(std::sqrt(qreal(m_cells.count()))));
|
||||
const int rows = int(std::ceil(m_cells.count() / qreal(columns)));
|
||||
|
||||
// Assign slots
|
||||
const int slotWidth = area.width() / columns;
|
||||
const int slotHeight = area.height() / rows;
|
||||
QList<ExpoCell *> takenSlots;
|
||||
takenSlots.resize(rows * columns);
|
||||
takenSlots.fill(nullptr);
|
||||
|
||||
// precalculate all slot centers
|
||||
QList<QPoint> slotCenters;
|
||||
slotCenters.resize(rows * columns);
|
||||
for (int x = 0; x < columns; ++x) {
|
||||
for (int y = 0; y < rows; ++y) {
|
||||
slotCenters[x + y * columns] = QPoint(area.x() + slotWidth * x + slotWidth / 2,
|
||||
area.y() + slotHeight * y + slotHeight / 2);
|
||||
}
|
||||
if (m_cells.isEmpty()) {
|
||||
setReady();
|
||||
return;
|
||||
}
|
||||
|
||||
// Assign each window to the closest available slot
|
||||
QList<ExpoCell *> tmpList = m_cells; // use a QLinkedList copy instead?
|
||||
while (!tmpList.isEmpty()) {
|
||||
ExpoCell *cell = tmpList.first();
|
||||
int slotCandidate = -1, slotCandidateDistance = INT_MAX;
|
||||
const QPoint pos = cell->naturalRect().center();
|
||||
QRectF area = QRectF(0, 0, width(), height());
|
||||
|
||||
for (int i = 0; i < columns * rows; ++i) { // all slots
|
||||
const int dist = distance(pos, slotCenters[i]);
|
||||
if (dist < slotCandidateDistance) { // window is interested in this slot
|
||||
ExpoCell *occupier = takenSlots[i];
|
||||
Q_ASSERT(occupier != cell);
|
||||
if (!occupier || dist < distance(occupier->naturalRect().center(), slotCenters[i])) {
|
||||
// either nobody lives here, or we're better - takeover the slot if it's our best
|
||||
slotCandidate = i;
|
||||
slotCandidateDistance = dist;
|
||||
}
|
||||
}
|
||||
}
|
||||
Q_ASSERT(slotCandidate != -1);
|
||||
if (takenSlots[slotCandidate]) {
|
||||
tmpList << takenSlots[slotCandidate]; // occupier needs a new home now :p
|
||||
}
|
||||
tmpList.removeAll(cell);
|
||||
takenSlots[slotCandidate] = cell; // ...and we rumble in =)
|
||||
std::sort(m_cells.begin(), m_cells.end(),
|
||||
[](const ExpoCell *a, const ExpoCell *b) {
|
||||
return a->persistentKey() < b->persistentKey();
|
||||
});
|
||||
|
||||
// Estimate the scale factor we need to apply by simple heuristics
|
||||
qreal totalArea = 0;
|
||||
qreal availableArea = area.width() * area.height();
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
totalArea += cell->naturalWidth() * cell->naturalHeight();
|
||||
}
|
||||
qreal scale = std::sqrt(availableArea / totalArea) * 0.7; // conservative estimate
|
||||
scale = std::clamp(scale, 0.1, 10.0); // don't go crazy
|
||||
|
||||
for (int slot = 0; slot < columns * rows; ++slot) {
|
||||
ExpoCell *cell = takenSlots[slot];
|
||||
if (!cell) { // some slots might be empty
|
||||
continue;
|
||||
}
|
||||
QList<QRectF> windowSizes;
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
const QMargins &margins = cell->margins();
|
||||
const QMarginsF scaledMargins(margins.left() / scale, margins.top() / scale, margins.right() / scale, margins.bottom() / scale);
|
||||
windowSizes.emplace_back(cell->naturalRect().toRectF().marginsAdded(scaledMargins));
|
||||
}
|
||||
auto windowLayouts = ExpoLayout::layout(area, windowSizes);
|
||||
for (int i = 0; i < windowLayouts.size(); ++i) {
|
||||
ExpoCell *cell = m_cells[i];
|
||||
QRectF target = windowLayouts[i];
|
||||
|
||||
// Work out where the slot is
|
||||
QRect target(area.x() + (slot % columns) * slotWidth,
|
||||
area.y() + (slot / columns) * slotHeight,
|
||||
slotWidth, slotHeight);
|
||||
QRect adjustedTarget = target.adjusted(m_spacing, m_spacing, -m_spacing, -m_spacing);
|
||||
QRectF adjustedTarget = target.marginsRemoved(cell->margins());
|
||||
if (adjustedTarget.isValid()) {
|
||||
target = adjustedTarget; // Borders
|
||||
}
|
||||
target = target.marginsRemoved(cell->margins());
|
||||
|
||||
qreal scale;
|
||||
if (target.width() / qreal(cell->naturalWidth()) < target.height() / qreal(cell->naturalHeight())) {
|
||||
// Center vertically
|
||||
scale = target.width() / qreal(cell->naturalWidth());
|
||||
target.moveTop(target.top() + (target.height() - int(cell->naturalHeight() * scale)) / 2);
|
||||
target.setHeight(int(cell->naturalHeight() * scale));
|
||||
} else {
|
||||
// Center horizontally
|
||||
scale = target.height() / qreal(cell->naturalHeight());
|
||||
target.moveLeft(target.left() + (target.width() - int(cell->naturalWidth() * scale)) / 2);
|
||||
target.setWidth(int(cell->naturalWidth() * scale));
|
||||
}
|
||||
// Don't scale the windows too much
|
||||
if (scale > 2.0 || (scale > 1.0 && (cell->naturalWidth() > 300 || cell->naturalHeight() > 300))) {
|
||||
scale = (cell->naturalWidth() > 300 || cell->naturalHeight() > 300) ? 1.0 : 2.0;
|
||||
target = QRect(
|
||||
target.center().x() - int(cell->naturalWidth() * scale) / 2,
|
||||
target.center().y() - int(cell->naturalHeight() * scale) / 2,
|
||||
scale * cell->naturalWidth(), scale * cell->naturalHeight());
|
||||
}
|
||||
|
||||
cell->setX(target.x());
|
||||
cell->setY(target.y());
|
||||
cell->setWidth(target.width());
|
||||
cell->setHeight(target.height());
|
||||
}
|
||||
}
|
||||
|
||||
static inline int heightForWidth(ExpoCell *cell, int width)
|
||||
{
|
||||
return int((width / qreal(cell->naturalWidth())) * cell->naturalHeight());
|
||||
}
|
||||
|
||||
static bool isOverlappingAny(ExpoCell *w, const QHash<ExpoCell *, QRect> &targets, const QRegion &border, int spacing)
|
||||
{
|
||||
QHash<ExpoCell *, QRect>::const_iterator winTarget = targets.find(w);
|
||||
if (winTarget == targets.constEnd()) {
|
||||
return false;
|
||||
}
|
||||
if (border.intersects(*winTarget)) {
|
||||
return true;
|
||||
}
|
||||
const QMargins halfSpacing(spacing / 2, spacing / 2, spacing / 2, spacing / 2);
|
||||
|
||||
// Is there a better way to do this?
|
||||
QHash<ExpoCell *, QRect>::const_iterator target;
|
||||
for (target = targets.constBegin(); target != targets.constEnd(); ++target) {
|
||||
if (target == winTarget) {
|
||||
continue;
|
||||
}
|
||||
if (winTarget->marginsAdded(halfSpacing).intersects(target->marginsAdded(halfSpacing))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExpoLayout::calculateWindowTransformationsNatural()
|
||||
{
|
||||
const QRect area = QRect(0, 0, width(), height());
|
||||
|
||||
// As we are using pseudo-random movement (See "slot") we need to make sure the list
|
||||
// is always sorted the same way no matter which window is currently active.
|
||||
std::sort(m_cells.begin(), m_cells.end(), [](const ExpoCell *a, const ExpoCell *b) {
|
||||
return a->persistentKey() < b->persistentKey();
|
||||
});
|
||||
|
||||
QRect bounds;
|
||||
int direction = 0;
|
||||
QHash<ExpoCell *, QRect> targets;
|
||||
QHash<ExpoCell *, int> directions;
|
||||
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
const QRect cellRect(cell->naturalX(), cell->naturalY(), cell->naturalWidth(), cell->naturalHeight());
|
||||
targets[cell] = cellRect;
|
||||
// Reuse the unused "slot" as a preferred direction attribute. This is used when the window
|
||||
// is on the edge of the screen to try to use as much screen real estate as possible.
|
||||
directions[cell] = direction;
|
||||
bounds = bounds.united(cellRect);
|
||||
direction++;
|
||||
if (direction == 4) {
|
||||
direction = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over all windows, if two overlap push them apart _slightly_ as we try to
|
||||
// brute-force the most optimal positions over many iterations.
|
||||
const int halfSpacing = m_spacing / 2;
|
||||
bool overlap;
|
||||
do {
|
||||
overlap = false;
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
QRect *target_w = &targets[cell];
|
||||
for (ExpoCell *e : std::as_const(m_cells)) {
|
||||
if (cell == e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QRect *target_e = &targets[e];
|
||||
if (target_w->adjusted(-halfSpacing, -halfSpacing, halfSpacing, halfSpacing)
|
||||
.intersects(target_e->adjusted(-halfSpacing, -halfSpacing, halfSpacing, halfSpacing))) {
|
||||
overlap = true;
|
||||
|
||||
// Determine pushing direction
|
||||
QPoint diff(target_e->center() - target_w->center());
|
||||
// Prevent dividing by zero and non-movement
|
||||
if (diff.x() == 0 && diff.y() == 0) {
|
||||
diff.setX(1);
|
||||
}
|
||||
// Try to keep screen aspect ratio
|
||||
// if (bounds.height() / bounds.width() > area.height() / area.width())
|
||||
// diff.setY(diff.y() / 2);
|
||||
// else
|
||||
// diff.setX(diff.x() / 2);
|
||||
// Approximate a vector of between 10px and 20px in magnitude in the same direction
|
||||
diff *= m_accuracy / qreal(diff.manhattanLength());
|
||||
// Move both windows apart
|
||||
target_w->translate(-diff);
|
||||
target_e->translate(diff);
|
||||
|
||||
// Try to keep the bounding rect the same aspect as the screen so that more
|
||||
// screen real estate is utilised. We do this by splitting the screen into nine
|
||||
// equal sections, if the window center is in any of the corner sections pull the
|
||||
// window towards the outer corner. If it is in any of the other edge sections
|
||||
// alternate between each corner on that edge. We don't want to determine it
|
||||
// randomly as it will not produce consistant locations when using the filter.
|
||||
// Only move one window so we don't cause large amounts of unnecessary zooming
|
||||
// in some situations. We need to do this even when expanding later just in case
|
||||
// all windows are the same size.
|
||||
// (We are using an old bounding rect for this, hopefully it doesn't matter)
|
||||
int xSection = (target_w->x() - bounds.x()) / (bounds.width() / 3);
|
||||
int ySection = (target_w->y() - bounds.y()) / (bounds.height() / 3);
|
||||
diff = QPoint(0, 0);
|
||||
if (xSection != 1 || ySection != 1) { // Remove this if you want the center to pull as well
|
||||
if (xSection == 1) {
|
||||
xSection = (directions[cell] / 2 ? 2 : 0);
|
||||
}
|
||||
if (ySection == 1) {
|
||||
ySection = (directions[cell] % 2 ? 2 : 0);
|
||||
}
|
||||
}
|
||||
if (xSection == 0 && ySection == 0) {
|
||||
diff = QPoint(bounds.topLeft() - target_w->center());
|
||||
}
|
||||
if (xSection == 2 && ySection == 0) {
|
||||
diff = QPoint(bounds.topRight() - target_w->center());
|
||||
}
|
||||
if (xSection == 2 && ySection == 2) {
|
||||
diff = QPoint(bounds.bottomRight() - target_w->center());
|
||||
}
|
||||
if (xSection == 0 && ySection == 2) {
|
||||
diff = QPoint(bounds.bottomLeft() - target_w->center());
|
||||
}
|
||||
if (diff.x() != 0 || diff.y() != 0) {
|
||||
diff *= m_accuracy / qreal(diff.manhattanLength());
|
||||
target_w->translate(diff);
|
||||
}
|
||||
|
||||
// Update bounding rect
|
||||
bounds = bounds.united(*target_w);
|
||||
bounds = bounds.united(*target_e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (overlap);
|
||||
|
||||
// Compute the scale factor so the bounding rect fits the target area.
|
||||
qreal scale;
|
||||
if (bounds.width() <= area.width() && bounds.height() <= area.height()) {
|
||||
scale = 1.0;
|
||||
} else if (area.width() / qreal(bounds.width()) < area.height() / qreal(bounds.height())) {
|
||||
scale = area.width() / qreal(bounds.width());
|
||||
} else {
|
||||
scale = area.height() / qreal(bounds.height());
|
||||
}
|
||||
// Make bounding rect fill the screen size for later steps
|
||||
bounds = QRect(bounds.x() - (area.width() / scale - bounds.width()) / 2,
|
||||
bounds.y() - (area.height() / scale - bounds.height()) / 2,
|
||||
area.width() / scale,
|
||||
area.height() / scale);
|
||||
|
||||
// Move all windows back onto the screen and set their scale
|
||||
QHash<ExpoCell *, QRect>::iterator target = targets.begin();
|
||||
while (target != targets.end()) {
|
||||
target->setRect((target->x() - bounds.x()) * scale + area.x(),
|
||||
(target->y() - bounds.y()) * scale + area.y(),
|
||||
target->width() * scale,
|
||||
target->height() * scale);
|
||||
++target;
|
||||
}
|
||||
|
||||
// Try to fill the gaps by enlarging windows if they have the space
|
||||
if (m_fillGaps) {
|
||||
// Don't expand onto or over the border
|
||||
QRegion borderRegion(area.adjusted(-200, -200, 200, 200));
|
||||
borderRegion ^= area;
|
||||
|
||||
bool moved;
|
||||
do {
|
||||
moved = false;
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
QRect oldRect;
|
||||
QRect *target = &targets[cell];
|
||||
// This may cause some slight distortion if the windows are enlarged a large amount
|
||||
int widthDiff = m_accuracy;
|
||||
int heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
|
||||
int xDiff = widthDiff / 2; // Also move a bit in the direction of the enlarge, allows the
|
||||
int yDiff = heightDiff / 2; // center windows to be enlarged if there is gaps on the side.
|
||||
|
||||
// heightDiff (and yDiff) will be re-computed after each successful enlargement attempt
|
||||
// so that the error introduced in the window's aspect ratio is minimized
|
||||
|
||||
// Attempt enlarging to the top-right
|
||||
oldRect = *target;
|
||||
target->setRect(target->x() + xDiff,
|
||||
target->y() - yDiff - heightDiff,
|
||||
target->width() + widthDiff,
|
||||
target->height() + heightDiff);
|
||||
if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
|
||||
*target = oldRect;
|
||||
} else {
|
||||
moved = true;
|
||||
heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
|
||||
yDiff = heightDiff / 2;
|
||||
}
|
||||
|
||||
// Attempt enlarging to the bottom-right
|
||||
oldRect = *target;
|
||||
target->setRect(target->x() + xDiff,
|
||||
target->y() + yDiff,
|
||||
target->width() + widthDiff,
|
||||
target->height() + heightDiff);
|
||||
if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
|
||||
*target = oldRect;
|
||||
} else {
|
||||
moved = true;
|
||||
heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
|
||||
yDiff = heightDiff / 2;
|
||||
}
|
||||
|
||||
// Attempt enlarging to the bottom-left
|
||||
oldRect = *target;
|
||||
target->setRect(target->x() - xDiff - widthDiff,
|
||||
target->y() + yDiff,
|
||||
target->width() + widthDiff,
|
||||
target->height() + heightDiff);
|
||||
if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
|
||||
*target = oldRect;
|
||||
} else {
|
||||
moved = true;
|
||||
heightDiff = heightForWidth(cell, target->width() + widthDiff) - target->height();
|
||||
yDiff = heightDiff / 2;
|
||||
}
|
||||
|
||||
// Attempt enlarging to the top-left
|
||||
oldRect = *target;
|
||||
target->setRect(target->x() - xDiff - widthDiff,
|
||||
target->y() - yDiff - heightDiff,
|
||||
target->width() + widthDiff,
|
||||
target->height() + heightDiff);
|
||||
if (isOverlappingAny(cell, targets, borderRegion, m_spacing)) {
|
||||
*target = oldRect;
|
||||
} else {
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
} while (moved);
|
||||
|
||||
// The expanding code above can actually enlarge windows over 1.0/2.0 scale, we don't like this
|
||||
// We can't add this to the loop above as it would cause a never-ending loop so we have to make
|
||||
// do with the less-than-optimal space usage with using this method.
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
QRect *target = &targets[cell];
|
||||
qreal scale = target->width() / qreal(cell->naturalWidth());
|
||||
if (scale > 2.0 || (scale > 1.0 && (cell->naturalWidth() > 300 || cell->naturalHeight() > 300))) {
|
||||
scale = (cell->naturalWidth() > 300 || cell->naturalHeight() > 300) ? 1.0 : 2.0;
|
||||
target->setRect(target->center().x() - int(cell->naturalWidth() * scale) / 2,
|
||||
target->center().y() - int(cell->naturalHeight() * scale) / 2,
|
||||
cell->naturalWidth() * scale,
|
||||
cell->naturalHeight() * scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
const QRect &cellRect = targets.value(cell);
|
||||
QRect cellRectWithoutMargins = cellRect.marginsRemoved(cell->margins());
|
||||
if (!cellRectWithoutMargins.isValid()) {
|
||||
cellRectWithoutMargins = cellRect;
|
||||
}
|
||||
const QRect rect = centered(cell, cellRectWithoutMargins);
|
||||
|
||||
QRectF rect = cell->naturalRect();
|
||||
moveToFit(rect, target);
|
||||
cell->setX(rect.x());
|
||||
cell->setY(rect.y());
|
||||
cell->setWidth(rect.width());
|
||||
cell->setHeight(rect.height());
|
||||
}
|
||||
setReady();
|
||||
}
|
||||
|
||||
void ExpoLayout::resetTransformations()
|
||||
Layer::Layer(qreal maxWidth, const QList<QRectF> &windowSizes, const QList<size_t> &windowIds, size_t startPos, size_t endPos)
|
||||
: maxWidth(maxWidth)
|
||||
, maxHeight(windowSizes[windowIds[endPos - 1]].height())
|
||||
, ids(windowIds.begin() + startPos, windowIds.begin() + endPos)
|
||||
{
|
||||
for (ExpoCell *cell : std::as_const(m_cells)) {
|
||||
cell->setX(cell->naturalX());
|
||||
cell->setY(cell->naturalY());
|
||||
cell->setWidth(cell->naturalWidth());
|
||||
cell->setHeight(cell->naturalHeight());
|
||||
remainingWidth = maxWidth;
|
||||
for (auto id = ids.begin(); id != ids.end(); ++id) {
|
||||
remainingWidth -= windowSizes[*id].width();
|
||||
}
|
||||
}
|
||||
|
||||
qreal Layer::width() const
|
||||
{
|
||||
return maxWidth - remainingWidth;
|
||||
}
|
||||
|
||||
LayeredPacking::LayeredPacking(qreal maxWidth, const QList<QRectF> &windowSizes, const QList<size_t> &ids, const QList<size_t> &layerStartPos)
|
||||
: maxWidth(maxWidth)
|
||||
, width(0)
|
||||
, height(0)
|
||||
{
|
||||
for (int i = 1; i < layerStartPos.size(); ++i) {
|
||||
layers.emplace_back(maxWidth, windowSizes, ids, layerStartPos[i - 1], layerStartPos[i]);
|
||||
width = std::max(width, layers.back().width());
|
||||
height += layers.back().maxHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if @param candidate can be ignored in the future because either @param alternativeSmall or @param alternativeBig is at least as good as @param candidate for layerStart.
|
||||
*
|
||||
* More formally, returns false if and only if there exists a k with @param alternativeBig < k <= @param length
|
||||
* such that leastWeightCandidate( @param candidate, k ) < leastWeightCandidate( @param alternativeSmall, k ) and leastWeightCandidate( @param candidate, k ) < leastWeightCandidate(
|
||||
* @param alternativeBig, k ).
|
||||
*
|
||||
* The input must satisfy @param alternativeSmall < @param candidate < @param alternativeBig
|
||||
*
|
||||
* The run time of the algorithm is O(log length).
|
||||
*
|
||||
* The Bridge algorithm from Hirschberg, Daniel S., and Lawrence L.
|
||||
* Larmore. "The least weight subsequence problem." SIAM Journal on
|
||||
* Computing 16.4 (1987): 628-638
|
||||
*
|
||||
* @param length The length of the sequence.
|
||||
* @param leastWeightCandidate leastWeightCandidate(i, j) is the weight of arranging the first j windows,
|
||||
* if we use the optimal arrangement of the first i windows, and the last layest consists of windows [i, j)
|
||||
*/
|
||||
static bool isDominated(size_t candidate, size_t alternativeSmall, size_t alternativeBig, size_t length, std::function<qreal(size_t, size_t)> leastWeightCandidate)
|
||||
{
|
||||
Q_ASSERT(alternativeSmall < candidate && candidate < alternativeBig);
|
||||
if (alternativeBig == length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We assumed that the weigth function is concave, i.e., for all i <= j < k <= l,
|
||||
// weight(i,k) + weight(j,l) <= weight(i,l) + weight(j,k)
|
||||
// This implies the following about leastWeightCandidate:
|
||||
// For all i <= j < k <= l
|
||||
// - If leastWeightCandidate(i, l) <= leastWeightCandidate(j, l), then leastWeightCandidate(i, k) <= leastWeightCandidate(j, k)
|
||||
// - If leastWeightCandidate(j, k) <= leastWeightCandidate(i, k), then leastWeightCandidate(j, l) <= leastWeightCandidate(i, l)
|
||||
//
|
||||
// In particular, this implies that the set of ks such that
|
||||
// leastWeightCandidate(candidate, k) < leastWeightCandidate(alternativeSmall, k)
|
||||
// is a (possibly empty) interval [k1, length] for some k1.
|
||||
// This is because if for some k,
|
||||
// leastWeightCandidate(alternativeSmall, k) <= leastWeightCandidate(candidate, k),
|
||||
// then for all candidate < k' <= k,
|
||||
// leastWeightCandidate(alternativeSmall, k') <= leastWeightCandidate(candidate, k')
|
||||
//
|
||||
// Similarly, the set of ks such that
|
||||
// leastWeightCandidate(candidate, k) < leastWeightCandidate(alternativeBig, k)
|
||||
// is a (possibly empty) interval [alternativeBig + 1, k2] for some k2.
|
||||
// This is because if for some k,
|
||||
// leastWeightCandidate(alternativeBig, k) <= leastWeightCandidate(candidate, k),
|
||||
// then for all k' >= k
|
||||
// leastWeightCandidate(alternativeBig, k') <= leastWeightCandidate(candidate, k')
|
||||
//
|
||||
// Hence, to check if a k exists in both intervals, we can use binary search to find the smallest k1 such that
|
||||
// leastWeightCandidate(candidate, k1) < leastWeightCandidate(alternativeSmall, k1).
|
||||
// If such a k1 exists, it suffices to check if
|
||||
// leastWeightCandidate(alternativeBig, k1) <= leastWeightCandidate(candidate, k1).
|
||||
if (leastWeightCandidate(alternativeSmall, length) <= leastWeightCandidate(candidate, length)) {
|
||||
return true;
|
||||
}
|
||||
// Now we know that leastWeightCandidate(candidate, length) < leastWeightCandidate(alternativeSmall, length)
|
||||
// i.e, the first interval is non-empty
|
||||
// Our candidate k1 is in the interval (low, high] (inclusive on high)
|
||||
size_t low = alternativeBig;
|
||||
size_t high = length;
|
||||
while (high - low >= 2) {
|
||||
size_t mid = (low + high) / 2;
|
||||
if (leastWeightCandidate(alternativeSmall, mid) <= leastWeightCandidate(candidate, mid)) {
|
||||
low = mid;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
return (leastWeightCandidate(alternativeBig, high) <= leastWeightCandidate(candidate, high));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the layerStartPos for a good packing of the windows using the Basic algorithm
|
||||
* from Hirschberg, Daniel S., and Lawrence L. Larmore. "The least weight subsequence problem."
|
||||
* SIAM Journal on Computing 16.4 (1987): 628-638.
|
||||
*
|
||||
* The Basic algorithm solves the Least Weight Subsequence Problem (LWS) for
|
||||
* concave weight functions.
|
||||
*
|
||||
* The LWS problem on the interval [a,b] is defined as follows:
|
||||
* Given a weight function weight(i,j) for all i,j in [a,b], find a subsequence
|
||||
* of [a,b], i.e. a sequence of strictly monotonically increasing indices
|
||||
* i_0 < i_2 < ... < i_t, such that the total weight,
|
||||
* sum_{k=1}^t weight(i_{k-1}, i_k), is minimized.
|
||||
*
|
||||
* A weight function is concave if for all i <= j < k <= l, the following holds:
|
||||
* weight(i,k) + weight(j,l) <= weight(i,l) + weight(j,k)
|
||||
*
|
||||
* The run time of the algorithm is O(n log n).
|
||||
*
|
||||
* Modified from the version in the paper to fix some bugs.
|
||||
*
|
||||
* @param idealWidth The target width of each layer. All widths of windows *MUST* be smaller than idealWidth.
|
||||
* @param length The length of the sequence. Solves the LWS problem on the interval [0, length]. (n in paper)
|
||||
* @param cumWidths cumWidths[i] is the sum of widths of windows 0, 1, ..., i - 1
|
||||
*
|
||||
* @return QList<size_t> The subsequence (starting at 0 and ending at length)
|
||||
* that minimizes the total weight. The ith element is the index of the first window in layer i.
|
||||
* Always starts with 0 and ends with ids.size().
|
||||
*/
|
||||
static QList<size_t> getLayerStartPos(qreal maxWidth, qreal idealWidth, const size_t length, const QList<qreal> &cumWidths)
|
||||
{
|
||||
// weight(start, end) is the penalty of placing all windows in the range [start, end) in the same layer.
|
||||
// The following form only works when the maximum width of a window is less than or equal to idealWidth.
|
||||
//
|
||||
// The weight function is designed such that
|
||||
// 1. The weight function is concave (see definition in Basic algorithm)
|
||||
// 2. It scales like (width - idealWidth) ^ 2 for width < idealWidth
|
||||
// 3. Exceeding maxWidth is guaranteed to be worse than any other solution
|
||||
//
|
||||
// 1. holds as long as weight(i, j) = f(cumWidths[j] - cumWidths[i]) for some convex function f
|
||||
// 3. is guaranteed by making the penalty of exceeding maxWidth at least
|
||||
// cumWidths.size(), which strictly upper bounds the total weight of placing
|
||||
// each window in its own layer
|
||||
//
|
||||
auto weight = [maxWidth, idealWidth, &cumWidths](size_t start, size_t end) {
|
||||
qreal width = cumWidths[end] - cumWidths[start];
|
||||
if (width < idealWidth) {
|
||||
return (width - idealWidth) * (width - idealWidth) / idealWidth / idealWidth;
|
||||
} else {
|
||||
qreal penaltyFactor = cumWidths.size();
|
||||
return penaltyFactor * (width - idealWidth) * (width - idealWidth) / (maxWidth - idealWidth) / (maxWidth - idealWidth);
|
||||
}
|
||||
};
|
||||
|
||||
// layerStart[j] is where the last layer should start, if there were only the first j windows.
|
||||
// I.e., layerStart[5]=3 means that the last layer should start at window 3 if there were only the first 10 windows
|
||||
// (bestLeft in paper)
|
||||
QList<size_t> layerStart(length + 1);
|
||||
// leastWeight[i] is the least weight of any subsequence starting at 0 and ending at i (f in paper)
|
||||
QList<qreal> leastWeight(length + 1);
|
||||
// layerStartcandidates contains all current candidates for layerStart[currentIndex] (d in paper)
|
||||
std::deque<size_t> layerStartCandidates;
|
||||
|
||||
leastWeight[0] = 0;
|
||||
|
||||
// leastWeightCandidate(lastRowStartPos, num) is a candidate value for leastWeight[num].
|
||||
// It is the weight for arranging the first num windows, assuming optimal arrangement of
|
||||
// the first lastRowStartPos windows, and a last layer consisting of windows [lastRowStartPos, num)
|
||||
// (g in paper)
|
||||
auto leastWeightCandidate = [&leastWeight, &weight](size_t lastRowStartPos, size_t num) {
|
||||
return leastWeight[lastRowStartPos] + weight(lastRowStartPos, num);
|
||||
};
|
||||
layerStartCandidates.push_back(0);
|
||||
for (size_t currentIndex = 1; currentIndex < length; ++currentIndex) { // currentIndex is m in paper
|
||||
leastWeight[currentIndex] = leastWeightCandidate(layerStartCandidates.front(), currentIndex);
|
||||
layerStart[currentIndex] = layerStartCandidates.front();
|
||||
|
||||
// Modification of algorithm in paper;
|
||||
// needed so that layerStartCandidates.front can be correctly removed when layerStartCandidates.size() == 1
|
||||
layerStartCandidates.push_back(currentIndex);
|
||||
|
||||
// Remove candidates from the front if they are dominated by the second candidate
|
||||
// Dominate means that the second candidate is at least as good as the first candidate for layerStart
|
||||
while (layerStartCandidates.size() >= 2 && leastWeightCandidate(layerStartCandidates[1], currentIndex + 1) <= leastWeightCandidate(layerStartCandidates[0], currentIndex + 1)) {
|
||||
layerStartCandidates.pop_front();
|
||||
}
|
||||
layerStartCandidates.pop_back(); // Modification of algorithm in paper
|
||||
|
||||
// Remove candidates from the back if they are dominated by either the second to last candidate, or currentIndex
|
||||
while (layerStartCandidates.size() >= 2 && isDominated(layerStartCandidates.back(), layerStartCandidates[layerStartCandidates.size() - 2], currentIndex, length, leastWeightCandidate)) {
|
||||
layerStartCandidates.pop_back();
|
||||
}
|
||||
|
||||
// Modification of algorithm in paper; we need at least one candidate in layerStartCandidates
|
||||
if (layerStartCandidates.empty()) {
|
||||
layerStartCandidates.push_back(currentIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add currentIndex to layerStartCandidates if it is not dominated by the last candidate
|
||||
if (leastWeightCandidate(currentIndex, length) < leastWeightCandidate(layerStartCandidates.back(), length)) {
|
||||
layerStartCandidates.push_back(currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
// recover the solution using layerStart
|
||||
leastWeight[length] = leastWeightCandidate(layerStartCandidates.front(), length);
|
||||
layerStart[length] = layerStartCandidates.front();
|
||||
|
||||
QList<size_t> layerStartPosReversed;
|
||||
layerStartPosReversed.push_back(length);
|
||||
size_t currentIndex = length;
|
||||
|
||||
while (currentIndex > 0) {
|
||||
currentIndex = layerStart[currentIndex];
|
||||
layerStartPosReversed.push_back(currentIndex);
|
||||
}
|
||||
|
||||
return QList<size_t>(layerStartPosReversed.rbegin(), layerStartPosReversed.rend());
|
||||
}
|
||||
|
||||
// Reflection about the line y = x
|
||||
static QMarginsF reflect(const QMarginsF &margins)
|
||||
{
|
||||
return QMarginsF(margins.top(), margins.right(), margins.bottom(), margins.left());
|
||||
}
|
||||
static QRectF reflect(const QRectF &rect)
|
||||
{
|
||||
return QRectF(rect.y(), rect.x(), rect.height(), rect.width());
|
||||
}
|
||||
static QPointF reflect(const QPointF &point)
|
||||
{
|
||||
return point.transposed();
|
||||
}
|
||||
template<typename T>
|
||||
static QList<T> reflect(const QList<T> &v)
|
||||
{
|
||||
QList<T> result;
|
||||
result.reserve(v.size());
|
||||
for (const auto &x : v) {
|
||||
result.emplace_back(reflect(x));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<QRectF> ExpoLayout::layout(const QRectF &area, const QList<QRectF> &windowSizes)
|
||||
{
|
||||
const qreal shortSide = std::min(area.width(), area.height());
|
||||
const QMarginsF margins(shortSide * m_relativeMarginLeft,
|
||||
shortSide * m_relativeMarginTop,
|
||||
shortSide * m_relativeMarginRight,
|
||||
shortSide * m_relativeMarginBottom);
|
||||
const qreal minLength = m_relativeMinLength * shortSide;
|
||||
const QRectF minSize = QRectF(0, 0, minLength, minLength);
|
||||
|
||||
QList<QPointF> centers;
|
||||
for (const QRectF &windowSize : windowSizes) {
|
||||
centers.push_back(windowSize.center());
|
||||
}
|
||||
|
||||
// windows bigger than 4x the area are considered ill-behaved and their sizes are clipped
|
||||
const auto adjustedSizes = adjustSizes(minSize, QRectF(0, 0, 4 * area.width(), 4 * area.height()), margins, windowSizes);
|
||||
|
||||
if (placementMode() == PlacementMode::Rows) {
|
||||
LayeredPacking bestPacking = findGoodPacking(area, adjustedSizes, centers, m_idealWidthRatio, m_searchTolerance);
|
||||
return refineAndApplyPacking(area, margins, bestPacking, adjustedSizes, centers);
|
||||
} else {
|
||||
QList<QRectF> adjustedSizesReflected(reflect(adjustedSizes));
|
||||
QList<QPointF> centersReflected(reflect(centers));
|
||||
|
||||
LayeredPacking bestPacking = findGoodPacking(area.transposed(), adjustedSizesReflected, centersReflected, m_idealWidthRatio, m_searchTolerance);
|
||||
return reflect(refineAndApplyPacking(area.transposed(), reflect(margins), bestPacking, adjustedSizesReflected, centersReflected));
|
||||
}
|
||||
}
|
||||
|
||||
QList<QRectF> ExpoLayout::adjustSizes(const QRectF &minSize, const QRectF &maxSize, const QMarginsF &margins, const QList<QRectF> &windowSizes)
|
||||
{
|
||||
QList<QRectF> adjustedSizes;
|
||||
for (QRectF windowSize : windowSizes) {
|
||||
windowSize.setWidth(std::clamp(windowSize.width(), minSize.width(), maxSize.width()));
|
||||
windowSize.setHeight(std::clamp(windowSize.height(), minSize.height(), maxSize.height()));
|
||||
windowSize += margins;
|
||||
adjustedSizes.emplace_back(windowSize);
|
||||
}
|
||||
return adjustedSizes;
|
||||
}
|
||||
|
||||
LayeredPacking
|
||||
ExpoLayout::findGoodPacking(const QRectF &area, const QList<QRectF> &windowSizes, const QList<QPointF> ¢ers, qreal idealWidthRatio, qreal tol)
|
||||
{
|
||||
QList<std::tuple<size_t, QRectF, QPointF>> windowSizesWithIds;
|
||||
|
||||
for (int i = 0; i < windowSizes.size(); ++i) {
|
||||
windowSizesWithIds.emplace_back(i, windowSizes[i], centers[i]);
|
||||
}
|
||||
|
||||
// Sorting by height ensures that windows in same layer (row) have similar heights
|
||||
std::stable_sort(windowSizesWithIds.begin(), windowSizesWithIds.end(), [](const auto &a, const auto &b) {
|
||||
// in case of same height, sort by y to minimize vertical movement
|
||||
return std::tuple(std::get<1>(a).height(), std::get<2>(a).y())
|
||||
< std::tuple(std::get<1>(b).height(), std::get<2>(b).y());
|
||||
});
|
||||
|
||||
QList<size_t> ids; // ids of windows in sorted order
|
||||
QList<qreal> cumWidths; // cumWidths[i] is the sum of widths of windows 0, 1, ..., i - 1
|
||||
|
||||
// Minimum and maximum strip widths to use in the binary search.
|
||||
// Strips should be at least as wide as the widest window, and at most as
|
||||
// wide as the sum of all window widths.
|
||||
qreal stripWidthMin = 0;
|
||||
qreal stripWidthMax = 0;
|
||||
|
||||
cumWidths.push_back(0);
|
||||
for (const auto &windowSizeWithId : windowSizesWithIds) {
|
||||
ids.push_back(std::get<0>(windowSizeWithId));
|
||||
qreal width = std::get<1>(windowSizeWithId).width();
|
||||
cumWidths.push_back(cumWidths.back() + width);
|
||||
|
||||
stripWidthMin = std::max(stripWidthMin, width);
|
||||
stripWidthMax += width;
|
||||
}
|
||||
stripWidthMin /= idealWidthRatio;
|
||||
stripWidthMax /= idealWidthRatio;
|
||||
|
||||
qreal targetRatio = area.height() / area.width();
|
||||
|
||||
auto findPacking = [&windowSizes, &ids, &cumWidths, idealWidthRatio](qreal stripWidth) {
|
||||
QList<size_t> layerStartPos = getLayerStartPos(stripWidth, stripWidth * idealWidthRatio, ids.size(), cumWidths);
|
||||
LayeredPacking result(stripWidth, windowSizes, ids, layerStartPos);
|
||||
Q_ASSERT(result.width <= stripWidth);
|
||||
return result;
|
||||
};
|
||||
|
||||
// the placement with the minimum strip width corresponds with a big aspect
|
||||
// ratio (ratioHigh), and the placement with the maximum strip width
|
||||
// corresponds with a small aspect ratio (ratioLow)
|
||||
|
||||
LayeredPacking placementWidthMin = findPacking(stripWidthMin);
|
||||
qreal ratioHigh = placementWidthMin.height / placementWidthMin.width;
|
||||
|
||||
if (ratioHigh <= targetRatio) {
|
||||
return placementWidthMin;
|
||||
}
|
||||
|
||||
LayeredPacking placementWidthMax = findPacking(stripWidthMax);
|
||||
qreal ratioLow = placementWidthMax.height / placementWidthMax.width;
|
||||
|
||||
if (ratioLow >= targetRatio) {
|
||||
return placementWidthMax;
|
||||
}
|
||||
|
||||
while (stripWidthMax / stripWidthMin > 1 + tol) {
|
||||
qreal stripWidthMid = std::sqrt(stripWidthMin * stripWidthMax);
|
||||
LayeredPacking placementMid = findPacking(stripWidthMid);
|
||||
qreal ratioMid = placementMid.height / placementMid.width;
|
||||
|
||||
if (ratioMid > targetRatio) {
|
||||
stripWidthMin = stripWidthMid;
|
||||
placementWidthMin = placementMid;
|
||||
ratioHigh = ratioMid;
|
||||
} else {
|
||||
// small optimization: use the actual strip width
|
||||
stripWidthMax = placementMid.width;
|
||||
placementWidthMax = placementMid;
|
||||
ratioLow = ratioMid;
|
||||
}
|
||||
}
|
||||
|
||||
// how much we need to scale the placement to fit
|
||||
qreal scaleWidthMin = std::min(area.width() / placementWidthMin.width, area.height() / placementWidthMin.height);
|
||||
qreal scaleWidthMax = std::min(area.width() / placementWidthMax.width, area.height() / placementWidthMax.height);
|
||||
|
||||
if (scaleWidthMin > scaleWidthMax) {
|
||||
return placementWidthMin;
|
||||
} else {
|
||||
return placementWidthMax;
|
||||
}
|
||||
}
|
||||
|
||||
QList<QRectF> ExpoLayout::refineAndApplyPacking(const QRectF &area, const QMarginsF &margins, const LayeredPacking &packing, const QList<QRectF> &windowSizes, const QList<QPointF> ¢ers)
|
||||
{
|
||||
// Scale packing to fit area
|
||||
qreal scale = std::min(area.width() / packing.width, area.height() / packing.height);
|
||||
scale = std::min(scale, m_maxScale);
|
||||
|
||||
const QMarginsF scaledMargins = QMarginsF(margins.left() * scale, margins.top() * scale,
|
||||
margins.right() * scale, margins.bottom() * scale);
|
||||
|
||||
// The maximum gap in additional to margins to leave between windows
|
||||
qreal maxGapY = m_maxGapRatio * (scaledMargins.top() + scaledMargins.bottom());
|
||||
qreal maxGapX = m_maxGapRatio * (scaledMargins.left() + scaledMargins.right());
|
||||
|
||||
// center align y
|
||||
qreal extraY = area.height() - packing.height * scale;
|
||||
qreal gapY = std::min(maxGapY, extraY / (packing.layers.size() + 1));
|
||||
qreal y = area.y() + (extraY - gapY * (packing.layers.size() - 1)) / 2;
|
||||
|
||||
QList<QRectF> finalWindowLayouts(windowSizes);
|
||||
// smaller windows "float" to the top
|
||||
for (const auto &layer : packing.layers) {
|
||||
qreal extraX = area.width() - layer.width() * scale;
|
||||
qreal gapX = std::min(maxGapX, extraX / (layer.ids.size() + 1));
|
||||
qreal x = area.x() + (extraX - gapX * (layer.ids.size() - 1)) / 2;
|
||||
|
||||
QList<size_t> ids(layer.ids);
|
||||
std::stable_sort(ids.begin(), ids.end(), [¢ers](size_t a, size_t b) {
|
||||
return centers[a].x() < centers[b].x(); // minimize horizontal movement
|
||||
});
|
||||
for (auto id : std::as_const(ids)) {
|
||||
QRectF &windowLayout = finalWindowLayouts[id];
|
||||
qreal newY = y + (layer.maxHeight - windowLayout.height()) * scale / 2; // center align y
|
||||
windowLayout = QRectF(x, newY, windowLayout.width() * scale, windowLayout.height() * scale);
|
||||
x += windowLayout.width() + gapX;
|
||||
windowLayout -= scaledMargins;
|
||||
}
|
||||
y += layer.maxHeight * scale + gapY;
|
||||
}
|
||||
return finalWindowLayouts;
|
||||
}
|
||||
|
||||
#include "moc_expolayout.cpp"
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
||||
SPDX-FileCopyrightText: 2024 Yifan Zhu <fanzhuyifan@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QQuickItem>
|
||||
#include <QRect>
|
||||
|
@ -13,33 +15,88 @@
|
|||
#include <optional>
|
||||
|
||||
class ExpoCell;
|
||||
struct Layer;
|
||||
struct LayeredPacking;
|
||||
|
||||
/**
|
||||
* @brief Adapts the algorithm from [0] to layout the windows intelligently.
|
||||
*
|
||||
* Design goals:
|
||||
* - use screen space efficiently, given diverse geometries of windows
|
||||
* - be aesthetically pleasing
|
||||
* - and minimize movement of windows from initial positions
|
||||
*
|
||||
* More concretely, the algorithm produces a layered layout, where each layer,
|
||||
* or strip, is a row or column. The algorithm tries to ensure that different
|
||||
* strips have similar widths, and uses binary search to find a packing with
|
||||
* similar aspect ratio to the layout area. Within each strip, the algorithm
|
||||
* tries to minimize horizontal movement (for rows) or vertical movement (for
|
||||
* columns) of the windows.
|
||||
*
|
||||
* [0] Hirschberg, Daniel S., and Lawrence L. Larmore. "The least weight
|
||||
* subsequence problem." SIAM Journal on Computing 16.4 (1987): 628-638.
|
||||
*/
|
||||
class ExpoLayout : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(LayoutMode mode READ mode WRITE setMode NOTIFY modeChanged)
|
||||
Q_PROPERTY(bool fillGaps READ fillGaps WRITE setFillGaps NOTIFY fillGapsChanged)
|
||||
Q_PROPERTY(int spacing READ spacing WRITE setSpacing NOTIFY spacingChanged)
|
||||
// Place windows in rows or columns.
|
||||
Q_PROPERTY(PlacementMode placementMode READ placementMode WRITE setPlacementMode NOTIFY placementModeChanged)
|
||||
Q_PROPERTY(bool ready READ isReady NOTIFY readyChanged)
|
||||
/**
|
||||
* Stop binary search when the two candidate strip widths are within tol (as a fraction of the larger strip width).
|
||||
* Default is 0.2.
|
||||
*/
|
||||
Q_PROPERTY(qreal searchTolerance MEMBER m_searchTolerance NOTIFY searchToleranceChanged)
|
||||
/**
|
||||
* The ideal sum of window widths in a strip (including added margins), as a fraction of the strip width. *MUST* be strictly less than 1.
|
||||
* Default is 0.8.
|
||||
*/
|
||||
Q_PROPERTY(qreal idealWidthRatio MEMBER m_idealWidthRatio NOTIFY idealWidthRatioChanged)
|
||||
/**
|
||||
* Left margin size, as a ratio of the short side of layout area. Default is 0.07.
|
||||
* The margins are added to each window before layout.
|
||||
*/
|
||||
Q_PROPERTY(qreal relativeMarginLeft MEMBER m_relativeMarginLeft NOTIFY relativeMarginLeftChanged)
|
||||
/**
|
||||
* Right margin size, as a ratio of the short side of layout area. Default is 0.07.
|
||||
* The margins are added to each window before layout.
|
||||
*/
|
||||
Q_PROPERTY(qreal relativeMarginRight MEMBER m_relativeMarginRight NOTIFY relativeMarginRightChanged)
|
||||
/**
|
||||
* Top margin size, as a ratio of the short side of layout area. Default is 0.07.
|
||||
* The margins are added to each window before layout.
|
||||
*/
|
||||
Q_PROPERTY(qreal relativeMarginTop MEMBER m_relativeMarginTop NOTIFY relativeMarginTopChanged)
|
||||
/**
|
||||
* Bottom margin size, as a ratio of the short side of layout area. Default is 0.07.
|
||||
* The margins are added to each window before layout.
|
||||
*/
|
||||
Q_PROPERTY(qreal relativeMarginBottom MEMBER m_relativeMarginBottom NOTIFY relativeMarginBottomChanged)
|
||||
/**
|
||||
* Minimal length of windows, as a ratio of the short side of layout area.
|
||||
* Smaller windows will be resized to this. Default is 0.15.
|
||||
*/
|
||||
Q_PROPERTY(qreal relativeMinLength MEMBER m_relativeMinLength NOTIFY relativeMinLengthChanged)
|
||||
/**
|
||||
* Maximum additional gap between windows, as a ratio of normal spacing (2*margin). Default is 1.5.
|
||||
*/
|
||||
Q_PROPERTY(qreal maxGapRatio MEMBER m_maxGapRatio NOTIFY maxGapRatioChanged)
|
||||
/**
|
||||
* Maximum scale applied to windows, *after* the minimum length is enforced. Default is 1.0.
|
||||
*/
|
||||
Q_PROPERTY(qreal maxScale MEMBER m_maxScale NOTIFY maxScaleChanged)
|
||||
|
||||
public:
|
||||
enum LayoutMode : uint {
|
||||
LayoutClosest = 0,
|
||||
LayoutNatural = 1,
|
||||
LayoutNone = 2
|
||||
enum PlacementMode : uint {
|
||||
Rows,
|
||||
Columns,
|
||||
};
|
||||
Q_ENUM(LayoutMode)
|
||||
Q_ENUM(PlacementMode)
|
||||
|
||||
explicit ExpoLayout(QQuickItem *parent = nullptr);
|
||||
|
||||
LayoutMode mode() const;
|
||||
void setMode(LayoutMode mode);
|
||||
|
||||
bool fillGaps() const;
|
||||
void setFillGaps(bool fill);
|
||||
|
||||
int spacing() const;
|
||||
void setSpacing(int spacing);
|
||||
PlacementMode placementMode() const;
|
||||
void setPlacementMode(PlacementMode mode);
|
||||
|
||||
void addCell(ExpoCell *cell);
|
||||
void removeCell(ExpoCell *cell);
|
||||
|
@ -53,23 +110,79 @@ protected:
|
|||
void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override;
|
||||
void updatePolish() override;
|
||||
|
||||
/**
|
||||
* @brief Layout the windows with @param windowSizes into @param area.
|
||||
*
|
||||
* This is the main entry point for the layout algorithm.
|
||||
*/
|
||||
QList<QRectF> layout(const QRectF &area, const QList<QRectF> &windowSizes);
|
||||
|
||||
/**
|
||||
* @brief First clip @param windowSizes to be between @param minSize and
|
||||
* @param maxSize. Then add @param margins to each window size, and @return
|
||||
* the adjusted window sizes.
|
||||
*/
|
||||
QList<QRectF> adjustSizes(const QRectF &minSize, const QRectF &maxSize, const QMarginsF &margins, const QList<QRectF> &windowSizes);
|
||||
|
||||
/**
|
||||
* @brief Use binary search to find a good packing of the @param windowSizes
|
||||
* into @param area such that the resulting packing has similar aspect ratio
|
||||
* (height/width) to @param area.
|
||||
*
|
||||
* The binary search is performed on the logarithm of the width of the
|
||||
* possible packings, and the search is terminated when the width of the
|
||||
* packing is within @param tol of the ideal width.
|
||||
*
|
||||
* We try to find a packing such that the total widths of windows in each
|
||||
* layer are close to @param idealWidthRatio times the maximum width of the
|
||||
* packing.
|
||||
*
|
||||
* In the case of identical window heights, we also try to minimize vertical
|
||||
* movement based on the @param centers of the windows.
|
||||
*
|
||||
* Run time is O(n log n log log (totalWidth / maxWidth))
|
||||
* Since we clip the window size, this is just O(n log n log log n)
|
||||
*/
|
||||
LayeredPacking
|
||||
findGoodPacking(const QRectF &area, const QList<QRectF> &windowSizes, const QList<QPointF> ¢ers, qreal idealWidthRatio, qreal tol);
|
||||
|
||||
/**
|
||||
* @brief Output the final window layouts from the packing.
|
||||
*
|
||||
* Geven @param windowSizes, scale @param packing to fit @param area,
|
||||
* remove previously added @param margins, add padding and align,
|
||||
* and @return the final layout.
|
||||
* In each layer, sort the windows by x coordinates of the @param centers.
|
||||
*/
|
||||
QList<QRectF> refineAndApplyPacking(const QRectF &area, const QMarginsF &margins, const LayeredPacking &packing, const QList<QRectF> &windowSizes, const QList<QPointF> ¢ers);
|
||||
|
||||
Q_SIGNALS:
|
||||
void modeChanged();
|
||||
void fillGapsChanged();
|
||||
void spacingChanged();
|
||||
void placementModeChanged();
|
||||
void readyChanged();
|
||||
void searchToleranceChanged();
|
||||
void idealWidthRatioChanged();
|
||||
void relativeMarginLeftChanged();
|
||||
void relativeMarginRightChanged();
|
||||
void relativeMarginTopChanged();
|
||||
void relativeMarginBottomChanged();
|
||||
void relativeMinLengthChanged();
|
||||
void maxGapRatioChanged();
|
||||
void maxScaleChanged();
|
||||
|
||||
private:
|
||||
void calculateWindowTransformationsClosest();
|
||||
void calculateWindowTransformationsNatural();
|
||||
void resetTransformations();
|
||||
|
||||
QList<ExpoCell *> m_cells;
|
||||
LayoutMode m_mode = LayoutNatural;
|
||||
int m_accuracy = 20;
|
||||
int m_spacing = 10;
|
||||
PlacementMode m_placementMode = Rows;
|
||||
bool m_ready = false;
|
||||
bool m_fillGaps = false;
|
||||
|
||||
qreal m_searchTolerance = 0.2;
|
||||
qreal m_idealWidthRatio = 0.8;
|
||||
qreal m_relativeMarginLeft = 0.07;
|
||||
qreal m_relativeMarginRight = 0.07;
|
||||
qreal m_relativeMarginTop = 0.07;
|
||||
qreal m_relativeMarginBottom = 0.07;
|
||||
qreal m_relativeMinLength = 0.15;
|
||||
qreal m_maxGapRatio = 1.5;
|
||||
qreal m_maxScale = 1.0;
|
||||
};
|
||||
|
||||
class ExpoCell : public QObject
|
||||
|
@ -162,3 +275,66 @@ private:
|
|||
std::optional<int> m_height;
|
||||
QPointer<ExpoLayout> m_layout;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Each Layer is a horizontal strip of windows with a maximum width and
|
||||
* height.
|
||||
*/
|
||||
struct Layer
|
||||
{
|
||||
qreal maxWidth;
|
||||
qreal maxHeight;
|
||||
/**
|
||||
* @brief The remaining width available to new windows in this layer.
|
||||
* width() + remainingWidth() == maxWidth
|
||||
*/
|
||||
qreal remainingWidth;
|
||||
|
||||
/**
|
||||
* @brief The indices of windows in this layer.
|
||||
*/
|
||||
QList<size_t> ids;
|
||||
|
||||
/**
|
||||
* @brief Initializes a new layer with the given maximum width and populates
|
||||
* it with the given windows.
|
||||
*
|
||||
* @param maxWidth The maximum width of the layer.
|
||||
* @param windowSizes The sizes of all the windows. Must be sorted in
|
||||
* ascending order by height.
|
||||
* @param windowIds Ids of the windows.
|
||||
* @param startPos windowIds[startPos] is the first window in this layer.
|
||||
* @param endPos windowIds[endPos-1] is the last window in this layer.
|
||||
*/
|
||||
Layer(qreal maxWidth, const QList<QRectF> &windowSizes, const QList<size_t> &windowIds, size_t startPos, size_t endPos);
|
||||
|
||||
/**
|
||||
* @brief The total width of all the windows in this layer.
|
||||
*
|
||||
*/
|
||||
qreal width() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A LayeredPacking is a packing of windows into layers, which are
|
||||
* horizontal strips of windows.
|
||||
*/
|
||||
struct LayeredPacking
|
||||
{
|
||||
qreal maxWidth;
|
||||
qreal width;
|
||||
qreal height;
|
||||
QList<Layer> layers;
|
||||
|
||||
/**
|
||||
* @brief Construct a new LayeredPacking object from a list of windows
|
||||
* sorted by height in descending order.
|
||||
*
|
||||
* @param maxWidth The maximum width of the packing.
|
||||
* @param windowSizes must be sorted by height in ascending order
|
||||
* @param ids Ids of the windows
|
||||
* @param layerStartPos Array of indices into ids that indicate the start
|
||||
* of a new layer. Must start with 0 and end with ids.size().
|
||||
*/
|
||||
LayeredPacking(qreal maxWidth, const QList<QRectF> &windowSizes, const QList<size_t> &ids, const QList<size_t> &layerStartPos);
|
||||
};
|
||||
|
|
|
@ -126,8 +126,8 @@ FocusScope {
|
|||
|
||||
anchors.fill: parent
|
||||
anchors.margins: heap.padding
|
||||
fillGaps: true
|
||||
spacing: Kirigami.Units.smallSpacing * 5
|
||||
|
||||
placementMode: width >= height ? ExpoLayout.Rows : ExpoLayout.Columns
|
||||
|
||||
Instantiator {
|
||||
id: windowsInstantiator
|
||||
|
|
|
@ -16,28 +16,14 @@
|
|||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_LayoutMode">
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_IgnoreMinimized">
|
||||
<property name="text">
|
||||
<string>Layout mode:</string>
|
||||
<string>Ignore &minimized windows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="kcfg_LayoutMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Closest</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Natural</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="KShortcutsEditor" name="shortcutsEditor">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
|
@ -50,13 +36,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="kcfg_IgnoreMinimized">
|
||||
<property name="text">
|
||||
<string>Ignore &minimized windows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
|
|
@ -178,7 +178,6 @@ Item {
|
|||
return container.effect.selectedIds;
|
||||
}
|
||||
}
|
||||
layout.mode: effect.layout
|
||||
model: KWinComponents.WindowFilterModel {
|
||||
activity: KWinComponents.Workspace.currentActivity
|
||||
desktop: {
|
||||
|
|
|
@ -10,9 +10,6 @@
|
|||
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
|
||||
<kcfgfile arg="true"/>
|
||||
<group name="Effect-windowview">
|
||||
<entry name="LayoutMode" type="Int">
|
||||
<default>1</default>
|
||||
</entry>
|
||||
<entry name="IgnoreMinimized" type="bool">
|
||||
<default>false</default>
|
||||
</entry>
|
||||
|
|
|
@ -133,19 +133,6 @@ void WindowViewEffect::setAnimationDuration(int duration)
|
|||
}
|
||||
}
|
||||
|
||||
int WindowViewEffect::layout() const
|
||||
{
|
||||
return m_layout;
|
||||
}
|
||||
|
||||
void WindowViewEffect::setLayout(int layout)
|
||||
{
|
||||
if (m_layout != layout) {
|
||||
m_layout = layout;
|
||||
Q_EMIT layoutChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowViewEffect::ignoreMinimized() const
|
||||
{
|
||||
return WindowViewConfig::ignoreMinimized();
|
||||
|
@ -160,7 +147,6 @@ void WindowViewEffect::reconfigure(ReconfigureFlags)
|
|||
{
|
||||
WindowViewConfig::self()->read();
|
||||
setAnimationDuration(animationTime(300));
|
||||
setLayout(WindowViewConfig::layoutMode());
|
||||
|
||||
for (ElectricBorder border : std::as_const(m_borderActivate)) {
|
||||
effects->unreserveElectricBorder(border, this);
|
||||
|
|
|
@ -19,7 +19,6 @@ class WindowViewEffect : public QuickSceneEffect
|
|||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int animationDuration READ animationDuration NOTIFY animationDurationChanged)
|
||||
Q_PROPERTY(int layout READ layout NOTIFY layoutChanged)
|
||||
Q_PROPERTY(bool ignoreMinimized READ ignoreMinimized NOTIFY ignoreMinimizedChanged)
|
||||
Q_PROPERTY(PresentWindowsMode mode READ mode NOTIFY modeChanged)
|
||||
Q_PROPERTY(qreal partialActivationFactor READ partialActivationFactor NOTIFY partialActivationFactorChanged)
|
||||
|
@ -50,9 +49,6 @@ public:
|
|||
int animationDuration() const;
|
||||
void setAnimationDuration(int duration);
|
||||
|
||||
int layout() const;
|
||||
void setLayout(int layout);
|
||||
|
||||
bool ignoreMinimized() const;
|
||||
|
||||
void reconfigure(ReconfigureFlags) override;
|
||||
|
@ -85,7 +81,6 @@ Q_SIGNALS:
|
|||
void partialActivationFactorChanged();
|
||||
void gestureInProgressChanged();
|
||||
void modeChanged();
|
||||
void layoutChanged();
|
||||
void ignoreMinimizedChanged();
|
||||
void searchTextChanged();
|
||||
void selectedIdsChanged();
|
||||
|
@ -121,7 +116,6 @@ private:
|
|||
qreal m_partialActivationFactor = 0;
|
||||
PresentWindowsMode m_mode;
|
||||
int m_animationDuration = 400;
|
||||
int m_layout = 1;
|
||||
bool m_gestureInProgress = false;
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue