/*
* SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
+ * SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "dolphin_generalsettings.h"
#include "dolphinviewcontainer.h"
+#include "global.h"
+#include <QVariantAnimation>
#include <QSplitter>
#include <QGridLayout>
#include <QWidgetAction>
+#include <QStyle>
DolphinTabPage::DolphinTabPage(const QUrl &primaryUrl, const QUrl &secondaryUrl, QWidget* parent) :
QWidget(parent),
+ m_expandingContainer{nullptr},
m_primaryViewActive(true),
m_splitViewEnabled(false),
m_active(true)
return m_splitViewEnabled;
}
-void DolphinTabPage::setSplitViewEnabled(bool enabled, const QUrl &secondaryUrl)
+void DolphinTabPage::setSplitViewEnabled(bool enabled, Animated animated, const QUrl &secondaryUrl)
{
if (m_splitViewEnabled != enabled) {
m_splitViewEnabled = enabled;
+ if (animated == WithAnimation && (
+ style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) < 1 ||
+ GlobalConfig::animationDurationFactor() <= 0.0)) {
+ animated = WithoutAnimation;
+ }
+ if (m_expandViewAnimation) {
+ m_expandViewAnimation->stop(); // deletes because of QAbstractAnimation::DeleteWhenStopped.
+ if (animated == WithoutAnimation) {
+ slotAnimationFinished();
+ }
+ }
if (enabled) {
+ QList<int> splitterSizes = m_splitter->sizes();
const QUrl& url = (secondaryUrl.isEmpty()) ? m_primaryViewContainer->url() : secondaryUrl;
m_secondaryViewContainer = createViewContainer(url);
m_splitter->addWidget(m_secondaryViewContainer);
m_secondaryViewContainer->installEventFilter(this);
- m_secondaryViewContainer->show();
m_secondaryViewContainer->setActive(true);
+
+ if (animated == WithAnimation) {
+ m_secondaryViewContainer->setMinimumWidth(1);
+ splitterSizes.append(1);
+ m_splitter->setSizes(splitterSizes);
+ startExpandViewAnimation(m_secondaryViewContainer);
+ }
+ m_secondaryViewContainer->show();
} else {
m_navigatorsWidget->setSecondaryNavigatorVisible(false);
m_secondaryViewContainer->disconnectUrlNavigator();
}
}
m_primaryViewContainer->setActive(true);
- view->close();
- view->deleteLater();
+
+ if (animated == WithoutAnimation) {
+ view->close();
+ view->deleteLater();
+ } else {
+ // Kill it but keep it as a zombie for the closing animation.
+ m_secondaryViewContainer = nullptr;
+ view->blockSignals(true);
+ view->view()->blockSignals(true);
+ view->setDisabled(true);
+ startExpandViewAnimation(m_primaryViewContainer);
+ }
}
}
}
void DolphinTabPage::resizeNavigators() const
{
- if (!m_splitViewEnabled) {
+ if (!m_secondaryViewContainer) {
m_navigatorsWidget->followViewContainerGeometry(
m_primaryViewContainer->mapToGlobal(QPoint(0,0)).x(),
m_primaryViewContainer->width());
bool isSplitViewEnabled = false;
stream >> isSplitViewEnabled;
- setSplitViewEnabled(isSplitViewEnabled);
+ setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
QUrl primaryUrl;
stream >> primaryUrl;
bool isSplitViewEnabled = false;
stream >> isSplitViewEnabled;
- setSplitViewEnabled(isSplitViewEnabled);
+ setSplitViewEnabled(isSplitViewEnabled, WithoutAnimation);
QUrl primaryUrl;
stream >> primaryUrl;
activeViewContainer()->setActive(active);
}
+void DolphinTabPage::slotAnimationFinished()
+{
+ for (int i = 0; i < m_splitter->count(); ++i) {
+ QWidget *viewContainer = m_splitter->widget(i);
+ if (viewContainer != m_primaryViewContainer &&
+ viewContainer != m_secondaryViewContainer) {
+ viewContainer->close();
+ viewContainer->deleteLater();
+ }
+ }
+ for (int i = 0; i < m_splitter->count(); ++i) {
+ QWidget *viewContainer = m_splitter->widget(i);
+ viewContainer->setMinimumWidth(viewContainer->minimumSizeHint().width());
+ }
+ m_expandingContainer = nullptr;
+}
+
+void DolphinTabPage::slotAnimationValueChanged(const QVariant& value)
+{
+ Q_CHECK_PTR(m_expandingContainer);
+ const int indexOfExpandingContainer = m_splitter->indexOf(m_expandingContainer);
+ int indexOfNonExpandingContainer = -1;
+ if (m_expandingContainer == m_primaryViewContainer) {
+ indexOfNonExpandingContainer = m_splitter->indexOf(m_secondaryViewContainer);
+ } else {
+ indexOfNonExpandingContainer = m_splitter->indexOf(m_primaryViewContainer);
+ }
+ std::vector<QWidget *> widgetsToRemove;
+ const QList<int> oldSplitterSizes = m_splitter->sizes();
+ QList<int> newSplitterSizes{oldSplitterSizes};
+ int expansionWidthNeeded = value.toInt() - oldSplitterSizes.at(indexOfExpandingContainer);
+
+ // Reduce the size of the other widgets to make space for the expandingContainer.
+ for (int i = m_splitter->count() - 1; i >= 0; --i) {
+ if (m_splitter->widget(i) == m_primaryViewContainer ||
+ m_splitter->widget(i) == m_secondaryViewContainer) {
+ continue;
+ }
+ newSplitterSizes[i] = oldSplitterSizes.at(i) - expansionWidthNeeded;
+ expansionWidthNeeded = 0;
+ if (indexOfNonExpandingContainer != -1) {
+ // Make sure every zombie container is at least slightly reduced in size
+ // so it doesn't seem like they are here to stay.
+ newSplitterSizes[i]--;
+ newSplitterSizes[indexOfNonExpandingContainer]++;
+ }
+ if (newSplitterSizes.at(i) <= 0) {
+ expansionWidthNeeded -= newSplitterSizes.at(i);
+ newSplitterSizes[i] = 0;
+ widgetsToRemove.emplace_back(m_splitter->widget(i));
+ }
+ }
+ if (expansionWidthNeeded > 1 && indexOfNonExpandingContainer != -1) {
+ Q_ASSERT(m_splitViewEnabled);
+ newSplitterSizes[indexOfNonExpandingContainer] -= expansionWidthNeeded;
+ }
+ newSplitterSizes[indexOfExpandingContainer] = value.toInt();
+ m_splitter->setSizes(newSplitterSizes);
+ while (!widgetsToRemove.empty()) {
+ widgetsToRemove.back()->close();
+ widgetsToRemove.back()->deleteLater();
+ widgetsToRemove.pop_back();
+ }
+}
+
+
void DolphinTabPage::slotViewActivated()
{
const DolphinView* oldActiveView = activeViewContainer()->view();
return container;
}
+
+void DolphinTabPage::startExpandViewAnimation(DolphinViewContainer *expandingContainer)
+{
+ Q_CHECK_PTR(expandingContainer);
+ Q_ASSERT(expandingContainer == m_primaryViewContainer ||
+ expandingContainer == m_secondaryViewContainer);
+ m_expandingContainer = expandingContainer;
+
+ m_expandViewAnimation = new QVariantAnimation(m_splitter);
+ m_expandViewAnimation->setDuration(2 *
+ style()->styleHint(QStyle::SH_Widget_Animation_Duration, nullptr, this) *
+ GlobalConfig::animationDurationFactor());
+ for (int i = 0; i < m_splitter->count(); ++i) {
+ m_splitter->widget(i)->setMinimumWidth(1);
+ }
+ connect(m_expandViewAnimation, &QAbstractAnimation::finished,
+ this, &DolphinTabPage::slotAnimationFinished);
+ connect(m_expandViewAnimation, &QVariantAnimation::valueChanged,
+ this, &DolphinTabPage::slotAnimationValueChanged);
+
+ m_expandViewAnimation->setStartValue(expandingContainer->width());
+ if (m_splitViewEnabled) { // A new viewContainer is being opened.
+ m_expandViewAnimation->setEndValue(m_splitter->width() / 2);
+ m_expandViewAnimation->setEasingCurve(QEasingCurve::OutCubic);
+ } else { // A viewContainer is being closed.
+ m_expandViewAnimation->setEndValue(m_splitter->width());
+ m_expandViewAnimation->setEasingCurve(QEasingCurve::InCubic);
+ }
+ m_expandViewAnimation->start(QAbstractAnimation::DeleteWhenStopped);
+}
/*
* SPDX-FileCopyrightText: 2014 Emmanuel Pescosta <emmanuelpescosta099@gmail.com>
+ * SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
class DolphinNavigatorsWidgetAction;
class DolphinViewContainer;
class QSplitter;
+class QVariantAnimation;
class KFileItemList;
+enum Animated {
+ WithAnimation,
+ WithoutAnimation
+};
+
class DolphinTabPage : public QWidget
{
Q_OBJECT
/**
* Enables or disables the split view mode.
*
- * If \a enabled is true, it creates a secondary view with the url of the primary view.
+ * @param enabled If true, creates a secondary viewContainer in this tab.
+ * Otherwise deletes it.
+ * @param animated Decides wether the effects of this method call should
+ * happen instantly or be transitioned to smoothly.
+ * @param secondaryUrl If \p enabled is true, the new viewContainer will be opened at this
+ * parameter. The default value will set the Url of the new viewContainer
+ * to be the same as the existing one.
*/
- void setSplitViewEnabled(bool enabled, const QUrl &secondaryUrl = QUrl());
+ void setSplitViewEnabled(bool enabled, Animated animated, const QUrl &secondaryUrl = QUrl());
/**
* @return The primary view container.
void splitterMoved(int pos, int index);
private slots:
+ /**
+ * Deletes all zombie viewContainers that were used for the animation
+ * and resets the minimum size of the others to a sane value.
+ */
+ void slotAnimationFinished();
+
+ /**
+ * This method is called for every frame of the m_expandViewAnimation.
+ */
+ void slotAnimationValueChanged(const QVariant &value);
+
/**
* Handles the view activated event.
*
*/
DolphinViewContainer* createViewContainer(const QUrl& url) const;
+ /**
+ * Starts an animation that transitions between split view mode states.
+ *
+ * One of the viewContainers is always being expanded when toggling so
+ * this method can animate both opening and closing of viewContainers.
+ * @param expandingContainer The container that will increase in size
+ * over the course of the animation.
+ */
+ void startExpandViewAnimation(DolphinViewContainer *expandingContainer);
+
private:
QSplitter* m_splitter;
QPointer<DolphinViewContainer> m_primaryViewContainer;
QPointer<DolphinViewContainer> m_secondaryViewContainer;
+ DolphinViewContainer *m_expandingContainer;
+ QPointer<QVariantAnimation> m_expandViewAnimation;
+
bool m_primaryViewActive;
bool m_splitViewEnabled;
bool m_active;