diff --git a/app/view/positioner.cpp b/app/view/positioner.cpp index bd50a583..61136086 100644 --- a/app/view/positioner.cpp +++ b/app/view/positioner.cpp @@ -1,922 +1,922 @@ /* * Copyright 2018 Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "positioner.h" // local #include #include "effects.h" #include "view.h" #include "visibilitymanager.h" #include "../lattecorona.h" #include "../screenpool.h" #include "../settings/universalsettings.h" // Qt #include // KDE #include #include #include namespace Latte { namespace ViewPart { Positioner::Positioner(Latte::View *parent) : QObject(parent), m_view(parent) { m_screenSyncTimer.setSingleShot(true); m_screenSyncTimer.setInterval(2000); connect(&m_screenSyncTimer, &QTimer::timeout, this, &Positioner::reconsiderScreen); //! under X11 it was identified that windows many times especially under screen changes //! don't end up at the correct position and size. This timer will enforce repositionings //! and resizes every 500ms if the window hasn't end up to correct values and until this //! is achieved m_validateGeometryTimer.setSingleShot(true); m_validateGeometryTimer.setInterval(500); connect(&m_validateGeometryTimer, &QTimer::timeout, this, &Positioner::syncGeometry); m_corona = qobject_cast(m_view->corona()); if (m_corona) { if (KWindowSystem::isPlatformX11()) { m_trackedWindowId = m_view->winId(); m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); connect(m_view, &Latte::View::forcedShown, this, [&]() { m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId); m_trackedWindowId = m_view->winId(); m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); }); } else { connect(m_view, &QWindow::windowTitleChanged, this, &Positioner::updateWaylandId); connect(m_corona->wm(), &WindowSystem::AbstractWindowInterface::latteWindowAdded, this, &Positioner::updateWaylandId); } ///// m_screenSyncTimer.setInterval(qMax(m_corona->universalSettings()->screenTrackerInterval() - 500, 1000)); connect(m_corona->universalSettings(), &UniversalSettings::screenTrackerIntervalChanged, this, [&]() { m_screenSyncTimer.setInterval(qMax(m_corona->universalSettings()->screenTrackerInterval() - 500, 1000)); }); connect(m_corona, &Latte::Corona::viewLocationChanged, this, [&]() { //! check if an edge has been freed for a primary dock //! from another screen if (m_view->onPrimary()) { m_screenSyncTimer.start(); } }); } init(); } Positioner::~Positioner() { m_inDelete = true; m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId); m_screenSyncTimer.stop(); m_validateGeometryTimer.stop(); } void Positioner::init() { //! connections connect(this, &Positioner::screenGeometryChanged, this, &Positioner::syncGeometry); connect(this, &Positioner::hideDockDuringLocationChangeStarted, this, &Positioner::updateInLocationAnimation); connect(this, &Positioner::hideDockDuringScreenChangeStarted, this, &Positioner::updateInLocationAnimation); connect(this, &Positioner::hideDockDuringMovingToLayoutStarted, this, &Positioner::updateInLocationAnimation); connect(this, &Positioner::showDockAfterLocationChangeFinished, this, &Positioner::updateInLocationAnimation); connect(this, &Positioner::showDockAfterScreenChangeFinished, this, &Positioner::updateInLocationAnimation); connect(this, &Positioner::showDockAfterMovingToLayoutFinished, this, &Positioner::updateInLocationAnimation); connect(this, &Positioner::showDockAfterLocationChangeFinished, this, &Positioner::syncLatteViews); connect(this, &Positioner::showDockAfterScreenChangeFinished, this, &Positioner::syncLatteViews); connect(this, &Positioner::showDockAfterMovingToLayoutFinished, this, &Positioner::syncLatteViews); connect(m_view, &Latte::View::onPrimaryChanged, this, &Positioner::syncLatteViews); connect(this, &Positioner::isStickedOnTopEdgeChanged, this, [&]() { if (m_view->formFactor() == Plasma::Types::Vertical) { syncGeometry(); } }); connect(this, &Positioner::isStickedOnBottomEdgeChanged, this, [&]() { if (m_view->formFactor() == Plasma::Types::Vertical) { syncGeometry(); } }); connect(this, &Positioner::slideOffsetChanged, this, [&]() { if (m_slideOffset == 0 && m_view->inEditMode()) { syncGeometry(); } }); connect(m_view, &QQuickWindow::xChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::yChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::widthChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::heightChanged, this, &Positioner::validateDockGeometry); connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::currentScreenChanged); connect(m_view, &QQuickWindow::screenChanged, this, &Positioner::screenChanged); connect(m_view, &Latte::View::behaveAsPlasmaPanelChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::maxThicknessChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::maxLengthChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::offsetChanged, this, &Positioner::syncGeometry); connect(m_view, &Latte::View::locationChanged, this, [&]() { updateFormFactor(); syncGeometry(); }); connect(m_view, &Latte::View::normalThicknessChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view, &Latte::View::screenEdgeMarginEnabledChanged, this, [&]() { if (m_view->inEditMode() ) { syncGeometry(); } }); connect(m_view, &Latte::View::screenEdgeMarginChanged, this, [&]() { if (m_view->screenEdgeMarginEnabled() && m_view->inEditMode()) { syncGeometry(); } }); connect(m_view->effects(), &Latte::ViewPart::Effects::drawShadowsChanged, this, [&]() { if (!m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(m_view->effects(), &Latte::ViewPart::Effects::innerShadowChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel()) { syncGeometry(); } }); connect(qGuiApp, &QGuiApplication::screenAdded, this, &Positioner::screenChanged); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, &Positioner::screenChanged); connect(m_view, &Latte::View::visibilityChanged, this, &Positioner::initDelayedSignals); initSignalingForLocationChangeSliding(); } void Positioner::initDelayedSignals() { connect(m_view->visibility(), &ViewPart::VisibilityManager::isHiddenChanged, this, [&]() { if (m_view->behaveAsPlasmaPanel() && !m_view->visibility()->isHidden() && qAbs(m_slideOffset)>0) { //! ignore any checks to make sure the panel geometry is up-to-date immediateSyncGeometry(); } }); } void Positioner::updateWaylandId() { QString validTitle = m_view->validTitle(); if (validTitle.isEmpty()) { return; } Latte::WindowSystem::WindowId newId = m_corona->wm()->winIdFor("latte-dock", validTitle); if (m_trackedWindowId != newId) { if (!m_trackedWindowId.isNull()) { m_corona->wm()->unregisterIgnoredWindow(m_trackedWindowId); } m_trackedWindowId = newId; m_corona->wm()->registerIgnoredWindow(m_trackedWindowId); } } int Positioner::currentScreenId() const { auto *latteCorona = qobject_cast(m_view->corona()); if (latteCorona) { return latteCorona->screenPool()->id(m_screenToFollowId); } return -1; } Latte::WindowSystem::WindowId Positioner::trackedWindowId() { return m_trackedWindowId; } QString Positioner::currentScreenName() const { return m_screenToFollowId; } void Positioner::syncLatteViews() { if (m_view->layout()) { //! This is needed in case the edge there are views that must be deleted //! after screen edges changes m_view->layout()->syncLatteViewsToScreens(); } } void Positioner::updateContainmentScreen() { if (m_view->containment()) { m_view->containment()->reactToScreenChange(); } } bool Positioner::setCurrentScreen(const QString id) { QScreen *nextScreen{qGuiApp->primaryScreen()}; if (id != "primary") { for (const auto scr : qGuiApp->screens()) { if (scr && scr->name() == id) { nextScreen = scr; break; } } } if (m_screenToFollow == nextScreen) { updateContainmentScreen(); return true; } if (nextScreen) { if (m_view->layout()) { m_goToScreen = nextScreen; //! asynchronous call in order to not crash from configwindow //! deletion from sliding out animation QTimer::singleShot(100, [this]() { emit hideDockDuringScreenChangeStarted(); }); } } return true; } //! this function updates the dock's associated screen. //! updateScreenId = true, update also the m_screenToFollowId //! updateScreenId = false, do not update the m_screenToFollowId //! that way an explicit dock can be shown in another screen when //! there isnt a tasks dock running in the system and for that //! dock its first origin screen is stored and that way when //! that screen is reconnected the dock will return to its original //! place void Positioner::setScreenToFollow(QScreen *scr, bool updateScreenId) { if (!scr || (scr && (m_screenToFollow == scr) && (m_view->screen() == scr))) { return; } qDebug() << "setScreenToFollow() called for screen:" << scr->name() << " update:" << updateScreenId; m_screenToFollow = scr; if (updateScreenId) { m_screenToFollowId = scr->name(); } qDebug() << "adapting to screen..."; m_view->setScreen(scr); updateContainmentScreen(); connect(scr, &QScreen::geometryChanged, this, &Positioner::screenGeometryChanged); syncGeometry(); m_view->updateAbsoluteGeometry(true); qDebug() << "setScreenToFollow() ended..."; emit screenGeometryChanged(); emit currentScreenChanged(); } //! the main function which decides if this dock is at the //! correct screen void Positioner::reconsiderScreen() { if (m_inDelete) { return; } qDebug() << "reconsiderScreen() called..."; qDebug() << " Delayer "; for (const auto scr : qGuiApp->screens()) { qDebug() << " D, found screen: " << scr->name(); } bool screenExists{false}; //!check if the associated screen is running for (const auto scr : qGuiApp->screens()) { if (m_screenToFollowId == scr->name() || (m_view->onPrimary() && scr == qGuiApp->primaryScreen())) { screenExists = true; } } qDebug() << "dock screen exists ::: " << screenExists; //! 1.a primary dock must be always on the primary screen if (m_view->onPrimary() && (m_screenToFollowId != qGuiApp->primaryScreen()->name() || m_screenToFollow != qGuiApp->primaryScreen() || m_view->screen() != qGuiApp->primaryScreen())) { //! case 1 qDebug() << "reached case 1: of updating dock primary screen..."; setScreenToFollow(qGuiApp->primaryScreen()); } else if (!m_view->onPrimary()) { //! 2.an explicit dock must be always on the correct associated screen //! there are cases that window manager misplaces the dock, this function //! ensures that this dock will return at its correct screen for (const auto scr : qGuiApp->screens()) { if (scr && scr->name() == m_screenToFollowId) { qDebug() << "reached case 2: updating the explicit screen for dock..."; setScreenToFollow(scr); break; } } } syncGeometry(); qDebug() << "reconsiderScreen() ended..."; } void Positioner::screenChanged(QScreen *scr) { m_screenSyncTimer.start(); //! this is needed in order to update the struts on screen change //! and even though the geometry has been set correctly the offsets //! of the screen must be updated to the new ones if (m_view->visibility() && m_view->visibility()->mode() == Latte::Types::AlwaysVisible) { m_view->updateAbsoluteGeometry(true); } } void Positioner::syncGeometry() { if (!(m_view->screen() && m_view->containment()) || m_inDelete || m_slideOffset!=0 || inSlideAnimation()) { return; } qDebug() << "syncGeometry() called..."; immediateSyncGeometry(); } void Positioner::immediateSyncGeometry() { bool found{false}; qDebug() << "immediateSyncGeometry() called..."; //! before updating the positioning and geometry of the dock //! we make sure that the dock is at the correct screen if (m_view->screen() != m_screenToFollow) { qDebug() << "Sync Geometry screens inconsistent!!!! "; if (m_screenToFollow) { qDebug() << "Sync Geometry screens inconsistent for m_screenToFollow:" << m_screenToFollow->name() << " dock screen:" << m_view->screen()->name(); } if (!m_screenSyncTimer.isActive()) { m_screenSyncTimer.start(); } } else { found = true; } //! if the dock isnt at the correct screen the calculations //! are not executed if (found) { //! compute the free screen rectangle for vertical panels only once //! this way the costly QRegion computations are calculated only once //! instead of two times (both inside the resizeWindow and the updatePosition) QRegion freeRegion;; QRect maximumRect; QRect availableScreenRect{m_view->screen()->geometry()}; if (m_view->formFactor() == Plasma::Types::Vertical) { QString layoutName = m_view->layout() ? m_view->layout()->name() : QString(); auto latteCorona = qobject_cast(m_view->corona()); int fixedScreen = m_view->onPrimary() ? latteCorona->screenPool()->primaryScreenId() : m_view->containment()->screen(); QList ignoreModes({Latte::Types::AutoHide, Latte::Types::SideBar}); QList ignoreEdges({Plasma::Types::LeftEdge, Plasma::Types::RightEdge}); if (m_isStickedOnTopEdge && m_isStickedOnBottomEdge) { //! dont send an empty edges array because that means include all screen edges in calculations ignoreEdges << Plasma::Types::TopEdge; ignoreEdges << Plasma::Types::BottomEdge; } else { if (m_isStickedOnTopEdge) { ignoreEdges << Plasma::Types::TopEdge; } if (m_isStickedOnBottomEdge) { ignoreEdges << Plasma::Types::BottomEdge; } } freeRegion = latteCorona->availableScreenRegionWithCriteria(fixedScreen, layoutName, ignoreModes, ignoreEdges); maximumRect = maximumNormalGeometry(); QRegion availableRegion = freeRegion.intersected(maximumRect); availableScreenRect = freeRegion.intersected(maximumRect).boundingRect(); float area = 0; //! it is used to choose which or the availableRegion rectangles will //! be the one representing dock geometry for (QRegion::const_iterator p_rect=availableRegion.begin(); p_rect!=availableRegion.end(); ++p_rect) { //! the area of each rectangle in calculated in squares of 50x50 //! this is a way to avoid enourmous numbers for area value float tempArea = (float)((*p_rect).width() * (*p_rect).height()) / 2500; if (tempArea > area) { availableScreenRect = (*p_rect); area = tempArea; } } validateTopBottomBorders(availableScreenRect, freeRegion); } else { m_view->effects()->setForceTopBorder(false); m_view->effects()->setForceBottomBorder(false); } m_view->effects()->updateEnabledBorders(); resizeWindow(availableScreenRect); updatePosition(availableScreenRect); qDebug() << "syncGeometry() calculations for screen: " << m_view->screen()->name() << " _ " << m_view->screen()->geometry(); qDebug() << "syncGeometry() calculations for edge: " << m_view->location(); } qDebug() << "syncGeometry() ended..."; // qDebug() << "dock geometry:" << qRectToStr(geometry()); } void Positioner::validateDockGeometry() { if (m_slideOffset==0 && m_view->geometry() != m_validGeometry) { m_validateGeometryTimer.start(); } } //! this is used mainly from vertical panels in order to //! to get the maximum geometry that can be used from the dock //! based on their alignment type and the location dock QRect Positioner::maximumNormalGeometry() { int xPos = 0; int yPos = m_view->screen()->geometry().y();; int maxHeight = m_view->screen()->geometry().height(); int maxWidth = m_view->normalThickness(); QRect maxGeometry; maxGeometry.setRect(0, 0, maxWidth, maxHeight); switch (m_view->location()) { case Plasma::Types::LeftEdge: xPos = m_view->screen()->geometry().x(); maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight); break; case Plasma::Types::RightEdge: xPos = m_view->screen()->geometry().right() - maxWidth + 1; maxGeometry.setRect(xPos, yPos, maxWidth, maxHeight); break; default: //! bypass clang warnings break; } return maxGeometry; } void Positioner::validateTopBottomBorders(QRect availableScreenRect, QRegion availableScreenRegion) { //! Check if the the top/bottom borders must be drawn also int edgeMargin = qMax(1, m_view->screenEdgeMargin()); if (availableScreenRect.top() != m_view->screenGeometry().top()) { //! check top border QRegion fitInRegion = QRect(m_view->screenGeometry().x(), availableScreenRect.y()-1, edgeMargin, 1); QRegion subtracted = fitInRegion.subtracted(availableScreenRegion); if (subtracted.isNull()) { //!FitIn rectangle fits TOTALLY in the free screen region and as such //!the top border should be drawn m_view->effects()->setForceTopBorder(true); } else { m_view->effects()->setForceTopBorder(false); } } else { m_view->effects()->setForceTopBorder(false); } if (availableScreenRect.bottom() != m_view->screenGeometry().bottom()) { //! check top border QRegion fitInRegion = QRect(m_view->screenGeometry().x(), availableScreenRect.bottom()+1, edgeMargin, 1); QRegion subtracted = fitInRegion.subtracted(availableScreenRegion); if (subtracted.isNull()) { //!FitIn rectangle fits TOTALLY in the free screen region and as such //!the BOTTOM border should be drawn m_view->effects()->setForceBottomBorder(true); } else { m_view->effects()->setForceBottomBorder(false); } } else { m_view->effects()->setForceBottomBorder(false); } } void Positioner::updatePosition(QRect availableScreenRect) { QRect screenGeometry{availableScreenRect}; QPoint position; position = {0, 0}; const auto gap = [&](int scr_length) -> int { return static_cast(scr_length * m_view->offset()); }; const auto gapCentered = [&](int scr_length) -> int { return static_cast(scr_length * ((1 - m_view->maxLength()) / 2) + scr_length * m_view->offset()); }; const auto gapReversed = [&](int scr_length) -> int { return static_cast(scr_length - (scr_length * m_view->maxLength()) - gap(scr_length)); }; int cleanThickness = m_view->normalThickness() - m_view->effects()->innerShadow(); int screenEdgeMargin = m_view->behaveAsPlasmaPanel() ? m_view->screenEdgeMargin() - qAbs(m_slideOffset) : 0; switch (m_view->location()) { case Plasma::Types::TopEdge: if (m_view->behaveAsPlasmaPanel()) { int y = screenGeometry.y() + screenEdgeMargin; if (m_view->alignment() == Latte::Types::Left) { position = {screenGeometry.x() + gap(screenGeometry.width()), y}; } else if (m_view->alignment() == Latte::Types::Right) { - position = {screenGeometry.x() + gapReversed(screenGeometry.width()), y}; + position = {screenGeometry.x() + gapReversed(screenGeometry.width()) + 1, y}; } else { position = {screenGeometry.x() + gapCentered(screenGeometry.width()), y}; } } else { position = {screenGeometry.x(), screenGeometry.y()}; } break; case Plasma::Types::BottomEdge: if (m_view->behaveAsPlasmaPanel()) { int y = screenGeometry.y() + screenGeometry.height() - cleanThickness - screenEdgeMargin; if (m_view->alignment() == Latte::Types::Left) { position = {screenGeometry.x() + gap(screenGeometry.width()), y}; } else if (m_view->alignment() == Latte::Types::Right) { - position = {screenGeometry.x() + gapReversed(screenGeometry.width()), y}; + position = {screenGeometry.x() + gapReversed(screenGeometry.width()) + 1, y}; } else { position = {screenGeometry.x() + gapCentered(screenGeometry.width()), y}; } } else { position = {screenGeometry.x(), screenGeometry.y() + screenGeometry.height() - m_view->height()}; } break; case Plasma::Types::RightEdge: if (m_view->behaveAsPlasmaPanel()) { int x = availableScreenRect.right() - cleanThickness + 1 - screenEdgeMargin; if (m_view->alignment() == Latte::Types::Top) { position = {x, availableScreenRect.y() + gap(availableScreenRect.height())}; } else if (m_view->alignment() == Latte::Types::Bottom) { - position = {x, availableScreenRect.y() + gapReversed(availableScreenRect.height())}; + position = {x, availableScreenRect.y() + gapReversed(availableScreenRect.height()) + 1}; } else { position = {x, availableScreenRect.y() + gapCentered(availableScreenRect.height())}; } } else { position = {availableScreenRect.right() - m_view->width() + 1, availableScreenRect.y()}; } break; case Plasma::Types::LeftEdge: if (m_view->behaveAsPlasmaPanel()) { int x = availableScreenRect.x() + screenEdgeMargin; if (m_view->alignment() == Latte::Types::Top) { position = {x, availableScreenRect.y() + gap(availableScreenRect.height())}; } else if (m_view->alignment() == Latte::Types::Bottom) { - position = {x, availableScreenRect.y() + gapReversed(availableScreenRect.height())}; + position = {x, availableScreenRect.y() + gapReversed(availableScreenRect.height()) + 1}; } else { position = {x, availableScreenRect.y() + gapCentered(availableScreenRect.height())}; } } else { position = {availableScreenRect.x(), availableScreenRect.y()}; } break; default: qWarning() << "wrong location, couldn't update the panel position" << m_view->location(); } if (m_slideOffset == 0) { //! update valid geometry in normal positioning m_validGeometry.setTopLeft(position); } else { //! when sliding in/out update only the relevant axis for the screen_edge in //! to not mess the calculations and the automatic geometry checkers that //! View::Positioner is using. if (m_view->formFactor() == Plasma::Types::Horizontal) { m_validGeometry.moveLeft(position.x()); } else { m_validGeometry.moveTop(position.y()); } } m_view->setPosition(position); if (m_view->surface()) { m_view->surface()->setPosition(position); } } int Positioner::slideOffset() const { return m_slideOffset; } void Positioner::setSlideOffset(int offset) { if (m_slideOffset == offset) { return; } m_slideOffset = offset; QPoint slidedTopLeft; if (m_view->location() == Plasma::Types::TopEdge) { int boundedY = qMax(m_view->screenGeometry().top() - (m_validGeometry.height() + 1), m_validGeometry.y() - qAbs(m_slideOffset)); slidedTopLeft = {m_validGeometry.x(), boundedY}; } else if (m_view->location() == Plasma::Types::BottomEdge) { int boundedY = qMin(m_view->screenGeometry().bottom() - 1, m_validGeometry.y() + qAbs(m_slideOffset)); slidedTopLeft = {m_validGeometry.x(), boundedY}; } else if (m_view->location() == Plasma::Types::RightEdge) { int boundedX = qMin(m_view->screenGeometry().right() - 1, m_validGeometry.x() + qAbs(m_slideOffset)); slidedTopLeft = {boundedX, m_validGeometry.y()}; } else if (m_view->location() == Plasma::Types::LeftEdge) { int boundedX = qMax(m_view->screenGeometry().left() - (m_validGeometry.width() + 1), m_validGeometry.x() - qAbs(m_slideOffset)); slidedTopLeft = {boundedX, m_validGeometry.y()}; } m_view->setPosition(slidedTopLeft); if (m_view->surface()) { m_view->surface()->setPosition(slidedTopLeft); } emit slideOffsetChanged(); } void Positioner::resizeWindow(QRect availableScreenRect) { QSize screenSize = m_view->screen()->size(); QSize size = (m_view->formFactor() == Plasma::Types::Vertical) ? QSize(m_view->maxThickness(), availableScreenRect.height()) : QSize(screenSize.width(), m_view->maxThickness()); if (m_view->formFactor() == Plasma::Types::Vertical) { //qDebug() << "MAXIMUM RECT :: " << maximumRect << " - AVAILABLE RECT :: " << availableRect; if (m_view->behaveAsPlasmaPanel()) { size.setWidth(m_view->normalThickness()); size.setHeight(static_cast(m_view->maxLength() * availableScreenRect.height())); } } else { if (m_view->behaveAsPlasmaPanel()) { size.setWidth(static_cast(m_view->maxLength() * screenSize.width())); size.setHeight(m_view->normalThickness()); } } m_validGeometry.setSize(size); m_view->setMinimumSize(size); m_view->setMaximumSize(size); m_view->resize(size); if (m_view->formFactor() == Plasma::Types::Horizontal) { emit windowSizeChanged(); } } void Positioner::updateFormFactor() { if (!m_view->containment()) return; switch (m_view->location()) { case Plasma::Types::TopEdge: case Plasma::Types::BottomEdge: m_view->containment()->setFormFactor(Plasma::Types::Horizontal); break; case Plasma::Types::LeftEdge: case Plasma::Types::RightEdge: m_view->containment()->setFormFactor(Plasma::Types::Vertical); break; default: qWarning() << "wrong location, couldn't update the panel position" << m_view->location(); } } void Positioner::initSignalingForLocationChangeSliding() { //! signals to handle the sliding-in/out during location changes connect(this, &Positioner::hideDockDuringLocationChangeStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(m_view, &View::locationChanged, this, [&]() { if (m_goToLocation != Plasma::Types::Floating) { m_goToLocation = Plasma::Types::Floating; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterLocationChangeFinished(); m_view->showSettingsWindow(); emit edgeChanged(); }); } }); //! signals to handle the sliding-in/out during screen changes connect(this, &Positioner::hideDockDuringScreenChangeStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(this, &Positioner::currentScreenChanged, this, [&]() { if (m_goToScreen) { m_goToScreen = nullptr; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterScreenChangeFinished(); m_view->showSettingsWindow(); emit edgeChanged(); }); } }); //! signals to handle the sliding-in/out during moving to another layout connect(this, &Positioner::hideDockDuringMovingToLayoutStarted, this, &Positioner::onHideWindowsForSlidingOut); connect(m_view, &View::layoutChanged, this, [&]() { if (!m_moveToLayout.isEmpty() && m_view->layout()) { m_moveToLayout = ""; QTimer::singleShot(100, [this]() { m_view->effects()->setAnimationsBlocked(false); emit showDockAfterMovingToLayoutFinished(); m_view->showSettingsWindow(); emit edgeChanged(); }); } }); //! ---- both cases ---- !// //! this is used for both location and screen change cases, this signal //! is send when the sliding-out animation has finished connect(this, &Positioner::hideDockDuringLocationChangeFinished, this, [&]() { m_view->effects()->setAnimationsBlocked(true); if (m_goToLocation != Plasma::Types::Floating) { m_view->setLocation(m_goToLocation); } else if (m_goToScreen) { setScreenToFollow(m_goToScreen); } else if (!m_moveToLayout.isEmpty()) { m_view->moveToLayout(m_moveToLayout); } }); } bool Positioner::inLocationAnimation() { return ((m_goToLocation != Plasma::Types::Floating) || (m_moveToLayout != "") || m_goToScreen); } bool Positioner::inSlideAnimation() const { return m_inSlideAnimation; } void Positioner::setInSlideAnimation(bool active) { if (m_inSlideAnimation == active) { return; } m_inSlideAnimation = active; emit inSlideAnimationChanged(); } bool Positioner::isStickedOnTopEdge() const { return m_isStickedOnTopEdge; } void Positioner::setIsStickedOnTopEdge(bool sticked) { if (m_isStickedOnTopEdge == sticked) { return; } m_isStickedOnTopEdge = sticked; emit isStickedOnTopEdgeChanged(); } bool Positioner::isStickedOnBottomEdge() const { return m_isStickedOnBottomEdge; } void Positioner::setIsStickedOnBottomEdge(bool sticked) { if (m_isStickedOnBottomEdge == sticked) { return; } m_isStickedOnBottomEdge = sticked; emit isStickedOnBottomEdgeChanged(); } void Positioner::updateInLocationAnimation() { bool inLocationAnimation = ((m_goToLocation != Plasma::Types::Floating) || (m_moveToLayout != "") || m_goToScreen); if (m_inLocationAnimation == inLocationAnimation) { return; } m_inLocationAnimation = inLocationAnimation; emit inLocationAnimationChanged(); } void Positioner::hideDockDuringLocationChange(int goToLocation) { m_goToLocation = static_cast(goToLocation); emit hideDockDuringLocationChangeStarted(); } void Positioner::hideDockDuringMovingToLayout(QString layoutName) { m_moveToLayout = layoutName; emit hideDockDuringMovingToLayoutStarted(); } } } diff --git a/containment/package/contents/ui/applet/AppletItem.qml b/containment/package/contents/ui/applet/AppletItem.qml index 073e7592..aeb92ae0 100644 --- a/containment/package/contents/ui/applet/AppletItem.qml +++ b/containment/package/contents/ui/applet/AppletItem.qml @@ -1,1203 +1,1201 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.7 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.kquickcontrolsaddons 2.0 import org.kde.latte.core 0.2 as LatteCore import org.kde.latte.components 1.0 as LatteComponents import "colorizer" as Colorizer import "communicator" as Communicator import "indicator" as Indicator import "../debug" as Debug Item { id: appletItem visible: false width: isInternalViewSplitter && !root.inConfigureAppletsMode ? 0 : computeWidth height: isInternalViewSplitter && !root.inConfigureAppletsMode ? 0 : computeHeight //any applets that exceed their limits should not take events from their surrounding applets clip: !isSeparator signal mousePressed(int x, int y, int button); signal mouseReleased(int x, int y, int button); property bool animationsEnabled: true property bool parabolicEffectIsSupported: true property bool canShowAppletNumberBadge: !isSeparator && !isHidden && !isLattePlasmoid && !isSpacer && !isInternalViewSplitter //! Fill Applet(s) property bool inFillCalculations: false //temp record, is used in calculations for fillWidth,fillHeight applets property bool isAutoFillApplet: { if (!applet || !applet.Layout) return false; if (((root.isHorizontal && applet.Layout.fillWidth===true) || (root.isVertical && applet.Layout.fillHeight===true)) && (applet.status !== PlasmaCore.Types.HiddenStatus)) return true; else return false; } property int maxAutoFillLength: -1 //it is used in calculations for fillWidth,fillHeight applets property int minAutoFillLength: -1 //it is used in calculations for fillWidth,fillHeight applets property bool userBlocksColorizing: false property bool appletBlocksColorizing: !communicator.requires.latteSideColoringEnabled property bool appletBlocksParabolicEffect: communicator.requires.parabolicEffectLocked property bool lockZoom: false property bool isActive: (isExpanded && applet.pluginName !== root.plasmoidName && applet.pluginName !== "org.kde.activeWindowControl" && applet.pluginName !== "org.kde.plasma.appmenu") property bool isExpanded: false property bool isHidden: (applet && applet.status === PlasmaCore.Types.HiddenStatus) || (isInternalViewSplitter && !root.inConfigureAppletsMode) property bool isInternalViewSplitter: (internalSplitterId > 0) property bool isLattePlasmoid: latteApplet !== null property bool isZoomed: false property bool isSeparator: applet && (applet.pluginName === "audoban.applet.separator" || applet.pluginName === "org.kde.latte.separator") property bool isSpacer: applet && (applet.pluginName === "org.kde.latte.spacer") property bool isSystray: applet && (applet.pluginName === "org.kde.plasma.systemtray" || applet.pluginName === "org.nomad.systemtray" ) property bool firstChildOfStartLayout: index === appletItem.layouter.startLayout.firstVisibleIndex property bool firstChildOfMainLayout: index === appletItem.layouter.mainLayout.firstVisibleIndex property bool lastChildOfMainLayout: index === appletItem.layouter.mainLayout.lastVisibleIndex property bool lastChildOfEndLayout: index === appletItem.layouter.endLayout.lastVisibleIndex readonly property bool atScreenEdge: { - if (root.panelAlignment !== LatteCore.Types.Justify || root.inConfigureAppletsMode || plasmoid.configuration.offset!==0) { + if (root.panelAlignment === LatteCore.Types.Center) { return false; } if (root.panelAlignment === LatteCore.Types.Justify) { //! Justify case - if (root.maxLengthPerCentage!==100) { + if (root.maxLengthPerCentage!==100 || plasmoid.configuration.offset!==0) { return false; } if (root.isHorizontal) { if (firstChildOfStartLayout) { return latteView && latteView.x === latteView.screenGeometry.x; } else if (lastChildOfEndLayout) { return latteView && ((latteView.x + latteView.width) === (latteView.screenGeometry.x + latteView.screenGeometry.width)); } } else { if (firstChildOfStartLayout) { return latteView && latteView.y === latteView.screenGeometry.y; } else if (lastChildOfEndLayout) { return latteView && ((latteView.y + latteView.height) === (latteView.screenGeometry.y + latteView.screenGeometry.height)); } } return false; } - //! [disabled] because it is probably not needed at all. If in the future there is a report - //! describing a case that this would be useful this disablement choice can be rethought - /*if (root.panelAlignment === LatteCore.Types.Left) { + if (root.panelAlignment === LatteCore.Types.Left && plasmoid.configuration.offset===0) { //! Left case return firstChildOfMainLayout; - } else if (root.panelAlignment === LatteCore.Types.Right) { + } else if (root.panelAlignment === LatteCore.Types.Right && plasmoid.configuration.offset===0) { //! Right case - return lastChildOfMainLayout + return lastChildOfMainLayout; } - if (root.panelAlignment === LatteCore.Types.Top) { + if (root.panelAlignment === LatteCore.Types.Top && plasmoid.configuration.offset===0) { return firstChildOfMainLayout && latteView && latteView.y === latteView.screenGeometry.y; - } else if (root.panelAlignment === LatteCore.Types.Bottom) { + } else if (root.panelAlignment === LatteCore.Types.Bottom && plasmoid.configuration.offset===0) { return lastChildOfMainLayout && latteView && ((latteView.y + latteView.height) === (latteView.screenGeometry.y + latteView.screenGeometry.height)); - }*/ + } return false; } //applet is in starting edge property bool firstAppletInContainer: (index >=0) && ((index === layouter.startLayout.firstVisibleIndex) || (index === layouter.mainLayout.firstVisibleIndex) || (index === layouter.endLayout.firstVisibleIndex)) //applet is in ending edge property bool lastAppletInContainer: (index >=0) && ((index === layouter.startLayout.lastVisibleIndex) || (index === layouter.mainLayout.lastVisibleIndex) || (index === layouter.endLayout.lastVisibleIndex)) readonly property bool acceptMouseEvents: applet && !isLattePlasmoid && !originalAppletBehavior && !appletItem.isSeparator && !communicator.requires.parabolicEffectLocked readonly property bool originalAppletBehavior: (appletItem.parabolic.factor.zoom === 1 && !lockZoom /*hacky flag to keep Latte behavior*/) || (appletItem.parabolic.factor.zoom>1 && !parabolicEffectIsSupported) || (appletItem.parabolic.factor.zoom>1 && parabolicEffectIsSupported && lockZoom) readonly property bool isSquare: communicator.overlayLatteIconIsActive readonly property bool screenEdgeMarginSupported: communicator.requires.screenEdgeMarginSupported property int animationTime: appletItem.animations.speedFactor.normal * (1.2*appletItem.animations.duration.small) property int index: -1 property int maxWidth: root.isHorizontal ? root.height : root.width property int maxHeight: root.isHorizontal ? root.height : root.width property int internalSplitterId: 0 property int previousIndex: -1 property int spacersMaxSize: Math.max(0,Math.ceil(0.55 * metrics.iconSize) - metrics.totals.lengthEdges) property int status: applet ? applet.status : -1 //! separators tracking readonly property bool tailAppletIsSeparator: { if (isSeparator || index<0) { return false; } var tail = index - 1; while(tail>=0 && indexer.hidden.indexOf(tail)>=0) { //! when a tail applet contains sub-indexing and does not influence //! tracking is considered hidden tail = tail - 1; } if (tail >= 0 && indexer.clients.indexOf(tail)>=0) { //! tail applet contains items sub-indexing var tailBridge = indexer.getClientBridge(tail); if (tailBridge && tailBridge.client) { return tailBridge.client.lastHeadItemIsSeparator; } } // tail applet is normal return (indexer.separators.indexOf(tail)>=0); } readonly property bool headAppletIsSeparator: { if (isSeparator || index<0) { return false; } var head = index + 1; while(head>=0 && indexer.hidden.indexOf(head)>=0) { //! when a head applet contains sub-indexing and does not influence //! tracking is considered hidden head = head + 1; } if (head >= 0 && indexer.clients.indexOf(head)>=0) { //! head applet contains items sub-indexing var headBridge = indexer.getClientBridge(head); if (headBridge && headBridge.client) { return headBridge.client.firstTailItemIsSeparator; } } // head applet is normal return (indexer.separators.indexOf(head)>=0); } //! local margins readonly property bool parabolicEffectMarginsEnabled: appletItem.parabolic.factor.zoom>1 && !originalAppletBehavior && !communicator.parabolicEffectIsSupported property int lengthAppletPadding: metrics.fraction.lengthAppletPadding === -1 || parabolicEffectMarginsEnabled ? metrics.padding.length : metrics.padding.lengthApplet property int lengthAppletFullMargin: 0 property int lengthAppletFullMargins: 2 * lengthAppletFullMargin property int internalWidthMargins: { if (root.isVertical) { return metrics.totals.thicknessEdges; } /*TODO, Fitt's case: is temporary until the atScreenEdge applets are aligned properly to the corner and the wrapper is taking all the space needed in order to fill right. For atScreenEdge appplets that should be: applet size + lengthAppletPadding + lengthAppletExtMargin. The indicator should follow also the applet alignment in this in order to feel right */ return 2 * lengthAppletPadding; } property int internalHeightMargins: { if (root.isHorizontal) { return root.metrics.totals.thicknessEdges; } /*TODO,Fitt's case: is temporary until the atScreenEdge applets are aligned properly to the corner and the wrapper is taking all the space needed in order to fill right. For atScreenEdge appplets that should be: applet size + lengthAppletPadding + lengthAppletExtMargin. The indicator should follow also the applet alignment in this in order to feel right */ return 2 * lengthAppletPadding; } //! are set by the indicator property int iconOffsetX: 0 property int iconOffsetY: 0 property real computeWidth: root.isVertical ? wrapper.width : hiddenSpacerLeft.width+wrapper.width+hiddenSpacerRight.width property real computeHeight: root.isVertical ? hiddenSpacerLeft.height + wrapper.height + hiddenSpacerRight.height : wrapper.height property string title: isInternalViewSplitter ? "Now Dock Splitter" : "" property Item applet: null property Item latteApplet: applet && (applet.pluginName === root.plasmoidName) ? (applet.children[0] ? applet.children[0] : null) : null property Item latteStyleApplet: applet && ((applet.pluginName === "org.kde.latte.spacer") || (applet.pluginName === "org.kde.latte.separator")) ? (applet.children[0] ? applet.children[0] : null) : null property Item appletWrapper: applet && (applet.pluginName === root.plasmoidName )? wrapper : wrapper.wrapperContainer property Item tooltipVisualParent: titleTooltipParent readonly property alias communicator: _communicator readonly property alias wrapper: _wrapper property Item animations: null property Item indexer: null property Item layouter: null property Item metrics: null property Item parabolic: null property Item shortcuts: null property bool containsMouse: appletMouseArea.containsMouse || (isLattePlasmoid && latteApplet.containsMouse) property bool pressed: viewSignalsConnector.pressed || clickedAnimation.running //// BEGIN :: Animate Applet when a new applet is dragged in the view //when the applet moves caused by its resize, don't animate. //this is completely heuristic, but looks way less "jumpy" property bool movingForResize: false property int oldX: x property int oldY: y onXChanged: { if (root.isVertical) { return; } if (movingForResize) { movingForResize = false; return; } var draggingAppletInConfigure = root.dragOverlay && root.dragOverlay.currentApplet; var isCurrentAppletInDragging = draggingAppletInConfigure && (root.dragOverlay.currentApplet === appletItem); var dropApplet = root.dragInfo.entered && foreDropArea.visible if (isCurrentAppletInDragging || !draggingAppletInConfigure && !dropApplet) { return; } if (!root.isVertical) { translation.x = oldX - x; translation.y = 0; } else { translation.y = oldY - y; translation.x = 0; } translAnim.running = true if (!root.isVertical) { oldX = x; oldY = 0; } else { oldY = y; oldX = 0; } } onYChanged: { if (root.isHorizontal) { return; } if (movingForResize) { movingForResize = false; return; } var draggingAppletInConfigure = root.dragOverlay && root.dragOverlay.currentApplet; var isCurrentAppletInDragging = draggingAppletInConfigure && (root.dragOverlay.currentApplet === appletItem); var dropApplet = root.dragInfo.entered && foreDropArea.visible if (isCurrentAppletInDragging || !draggingAppletInConfigure && !dropApplet) { return; } if (!root.isVertical) { translation.x = oldX - x; translation.y = 0; } else { translation.y = oldY - y; translation.x = 0; } translAnim.running = true; if (!root.isVertical) { oldX = x; oldY = 0; } else { oldY = y; oldX = 0; } } transform: Translate { id: translation } NumberAnimation { id: translAnim duration: appletItem.animations.duration.large easing.type: Easing.InOutQuad target: translation properties: "x,y" to: 0 } Behavior on lengthAppletPadding { NumberAnimation { duration: 0.8 * appletItem.animations.duration.proposed easing.type: Easing.OutCubic } } //// END :: Animate Applet when a new applet is dragged in the view /// BEGIN functions function activateAppletForNeutralAreas(mouse){ //if the event is at the active indicator or spacers area then try to expand the applet, //unfortunately for other applets there is no other way to activate them yet //for example the icon-only applets var choords = mapToItem(appletItem.appletWrapper, mouse.x, mouse.y); var wrapperContainsMouse = choords.x>=0 && choords.y>=0 && choords.x=0 && mouse.y>=0 && mouse.x0); var appletNeutralAreaEnabled = !(inThicknessNeutralArea && root.dragActiveWindowEnabled); if (appletItemContainsMouse && !wrapperContainsMouse && appletNeutralAreaEnabled) { //console.log("PASSED"); latteView.extendedInterface.toggleAppletExpanded(applet.id); } else { //console.log("REJECTED"); } } function checkIndex(){ index = -1; for(var i=0; i maxSize || applet.Layout.minimumWidth > maxForMinimumSize)) || (applet && root.isVertical && (applet.height > maxSize || applet.Layout.minimumHeight > maxForMinimumSize))) && !appletItem.isSpacer && !communicator.canShowOverlaiedLatteIcon) ) { appletItem.parabolicEffectIsSupported = false; } else { appletItem.parabolicEffectIsSupported = true; } } } function slotDestroyInternalViewSplitters() { if (isInternalViewSplitter) { destroy(); } } //! pos in global root positioning function containsPos(pos) { var relPos = root.mapToItem(appletItem,pos.x, pos.y); if (relPos.x>=0 && relPos.x<=width && relPos.y>=0 && relPos.y<=height) return true; return false; } ///END functions //BEGIN connections onAppletChanged: { if (!applet) { destroy(); } } onIndexChanged: { if (appletItem.latteApplet) { root.latteAppletPos = index; } if (index>-1) { previousIndex = index; } } onIsExpandedChanged: { if (isExpanded) { root.hideTooltipLabel(); } } onIsSystrayChanged: { updateParabolicEffectIsSupported(); } onLatteAppletChanged: { if(appletItem.latteApplet){ root.latteApplet = appletItem.latteApplet; root.latteAppletContainer = appletItem; root.latteAppletPos = index; appletItem.latteApplet.latteView = root; appletItem.latteApplet.forceHidePanel = true; appletItem.latteApplet.signalPreviewsShown.connect(slotPreviewsShown); } } onIsAutoFillAppletChanged: updateParabolicEffectIsSupported(); Component.onCompleted: { checkIndex(); root.updateIndexes.connect(checkIndex); root.destroyInternalViewSplitters.connect(slotDestroyInternalViewSplitters); parabolic.sglClearZoom.connect(sltClearZoom); } Component.onDestruction: { appletItem.animations.needBothAxis.removeEvent(appletItem); if(root.latteAppletPos>=0 && root.latteAppletPos === index){ root.latteApplet = null; root.latteAppletContainer = null; root.latteAppletPos = -1; } root.updateIndexes.disconnect(checkIndex); root.destroyInternalViewSplitters.disconnect(slotDestroyInternalViewSplitters); parabolic.sglClearZoom.disconnect(sltClearZoom); if (appletItem.latteApplet) { appletItem.latteApplet.signalPreviewsShown.disconnect(slotPreviewsShown); } } //! Bindings Binding { //! is used to aboid loop binding warnings on startup target: appletItem property: "lengthAppletFullMargin" when: !communicator.inStartup value: lengthAppletPadding + metrics.margin.length; } //! Connections Connections{ target: appletItem.shortcuts onSglActivateEntryAtIndex: { if (!appletItem.shortcuts.unifiedGlobalShortcuts) { return; } var visibleIndex = appletItem.indexer.visibleIndex(appletItem.index); if (visibleIndex === entryIndex && !communicator.positionShortcutsAreSupported) { latteView.extendedInterface.toggleAppletExpanded(applet.id); } } onSglNewInstanceForEntryAtIndex: { if (!appletItem.shortcuts.unifiedGlobalShortcuts) { return; } var visibleIndex = appletItem.indexer.visibleIndex(appletItem.index); if (visibleIndex === entryIndex && !communicator.positionShortcutsAreSupported) { latteView.extendedInterface.toggleAppletExpanded(applet.id); } } } Connections { id: viewSignalsConnector target: root.latteView ? root.latteView : null enabled: !appletItem.isLattePlasmoid && !appletItem.isSeparator && !appletItem.isSpacer && !appletItem.isHidden property bool pressed: false property bool blockWheel: false onMousePressed: { if (appletItem.containsPos(pos)) { viewSignalsConnector.pressed = true; var local = appletItem.mapFromItem(root, pos.x, pos.y); appletItem.mousePressed(local.x, local.y, button); if (button === Qt.LeftButton) { appletItem.activateAppletForNeutralAreas(local); } } } onMouseReleased: { if (appletItem.containsPos(pos)) { viewSignalsConnector.pressed = false; var local = appletItem.mapFromItem(root, pos.x, pos.y); appletItem.mouseReleased(local.x, local.y, button); } } onWheelScrolled: { if (!appletItem.applet || !root.mouseWheelActions || viewSignalsConnector.blockWheel || (root.latteViewIsHidden || root.inSlidingIn || root.inSlidingOut)) { return; } blockWheel = true; scrollDelayer.start(); if (appletItem.containsPos(pos) && root.latteView.extendedInterface.appletIsExpandable(applet.id)) { var angle = angleDelta.y / 8; var expanded = root.latteView.extendedInterface.appletIsExpanded(applet.id); if ((angle > 12 && !expanded) /*positive direction*/ || (angle < -12 && expanded) /*negative direction*/) { latteView.extendedInterface.toggleAppletExpanded(applet.id); } } } } Connections { target: root.latteView ? root.latteView.extendedInterface : null enabled: !appletItem.isLattePlasmoid && !appletItem.isSeparator && !appletItem.isSpacer && !appletItem.isHidden onExpandedAppletStateChanged: { if (latteView.extendedInterface.hasExpandedApplet && appletItem.applet) { appletItem.isExpanded = latteView.extendedInterface.appletIsExpandable(appletItem.applet.id) && latteView.extendedInterface.appletIsExpanded(appletItem.applet.id); } else { appletItem.isExpanded = false; } } } ///END connections //! It is used for any communication needed with the underlying applet Communicator.Engine{ id: _communicator //set up the overlayed appletItems and properties for when a overlaiedIconItem must be presented to the user //because the plasma widgets specific implementation breaks the Latte experience onOverlayLatteIconIsActiveChanged:{ if (!overlayLatteIconIsActive && applet.opacity===0) { applet.opacity = 1; } else if (overlayLatteIconIsActive && applet.opacity>0) { applet.opacity = 0; if (applet.pluginName === "org.kde.plasma.folder") { applet.parent = wrapper.containerForOverlayIcon; applet.anchors.fill = wrapper.containerForOverlayIcon; } } } } /* Rectangle{ anchors.fill: parent color: "transparent" border.color: "green" border.width: 1 }*/ /* DEPRECATED in favor of VIEW::MouseSignalsTracking MouseArea{ id: appletMouseAreaBottom anchors.fill: parent propagateComposedEvents: true visible: (!appletMouseArea.visible || !appletMouseArea.enabled) && !root.editMode && !originalAppletBehavior onPressed: { appletItem.activateAppletForNeutralAreas(mouse); mouse.accepted = false; } onReleased: { mouse.accepted = false; } }*/ //! Main Applet Shown Area Flow{ id: appletFlow width: appletItem.computeWidth height: appletItem.computeHeight // a hidden spacer for the first element to add stability // IMPORTANT: hidden spacers must be tested on vertical !!! HiddenSpacer{id: hiddenSpacerLeft} Item { width: wrapper.width height: wrapper.height Indicator.Bridge{ id: indicatorBridge } //! Indicator Back Layer Indicator.Loader{ id: indicatorBackLayer level: Indicator.LevelOptions { id: backLevelOptions isBackground: true bridge: indicatorBridge Binding { target: appletItem property: "iconOffsetX" value: backLevelOptions.requested.iconOffsetX } Binding { target: appletItem property: "iconOffsetY" value: backLevelOptions.requested.iconOffsetY } } } ItemWrapper{ id: _wrapper TitleTooltipParent{ id: titleTooltipParent metrics: appletItem.metrics parabolic: appletItem.parabolic } } //! The Applet Colorizer Colorizer.Applet { id: appletColorizer anchors.fill: parent opacity: mustBeShown ? 1 : 0 readonly property bool mustBeShown: colorizerManager.mustBeShown && !appletItem.userBlocksColorizing && !appletItem.appletBlocksColorizing && !appletItem.isInternalViewSplitter Behavior on opacity { NumberAnimation { duration: 1.2 * appletItem.animations.duration.proposed easing.type: Easing.OutCubic } } } //! Indicator Front Layer Indicator.Loader{ id: indicatorFrontLayer level: Indicator.LevelOptions { isForeground: true bridge: indicatorBridge } } //! Applet Shortcut Visual Badge Item { id: shortcutBadgeContainer width: { if (root.isHorizontal) { return appletItem.metrics.iconSize * wrapper.zoomScale } else { return badgeThickness; } } height: { if (root.isHorizontal) { return badgeThickness; } else { return appletItem.metrics.iconSize * wrapper.zoomScale } } readonly property int badgeThickness: { if (plasmoid.location === PlasmaCore.Types.BottomEdge || plasmoid.location === PlasmaCore.Types.RightEdge) { return ((appletItem.metrics.iconSize + appletItem.metrics.margin.thickness) * wrapper.zoomScale) + appletItem.metrics.margin.screenEdge; } return ((appletItem.metrics.iconSize + appletItem.metrics.margin.thickness) * wrapper.zoomScale); } ShortcutBadge{ anchors.fill: parent } states:[ State{ name: "horizontal" when: plasmoid.formFactor === PlasmaCore.Types.Horizontal AnchorChanges{ target: shortcutBadgeContainer; anchors.horizontalCenter: parent.horizontalCenter; anchors.verticalCenter: undefined; anchors.right: undefined; anchors.left: undefined; anchors.top: undefined; anchors.bottom: parent.bottom; } }, State{ name: "vertical" when: plasmoid.formFactor === PlasmaCore.Types.Vertical AnchorChanges{ target: shortcutBadgeContainer; anchors.horizontalCenter: undefined; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.left: undefined; anchors.top: undefined; anchors.bottom: undefined; } } ] } } // a hidden spacer on the right for the last item to add stability HiddenSpacer{id: hiddenSpacerRight; rightSpacer: true} }// Flow with hidden spacers inside //Busy Indicator PlasmaComponents.BusyIndicator { z: 1000 visible: applet && applet.busy running: visible anchors.centerIn: parent width: Math.min(parent.width, parent.height) height: width } Loader { id: addingAreaLoader width: root.isHorizontal ? parent.width : parent.width - appletItem.metrics.margin.screenEdge height: root.isHorizontal ? parent.height - appletItem.metrics.margin.screenEdge : parent.height active: isLattePlasmoid sourceComponent: LatteComponents.AddingArea{ id: addingAreaItem anchors.fill: parent // width: root.isHorizontal ? parent.width : parent.width - appletItem.metrics.margin.screenEdge // height: root.isHorizontal ? parent.height - appletItem.metrics.margin.screenEdge : parent.height radius: appletItem.metrics.iconSize/10 opacity: root.addLaunchersMessage ? 1 : 0 backgroundOpacity: 0.75 duration: appletItem.animations.speedFactor.current title: i18n("Tasks Area") } //! AddingAreaItem States states:[ State{ name: "bottom" when: plasmoid.location === PlasmaCore.Types.BottomEdge AnchorChanges{ target: addingAreaLoader anchors.horizontalCenter: parent.horizontalCenter; anchors.verticalCenter: undefined; anchors.right: undefined; anchors.left: undefined; anchors.top: undefined; anchors.bottom: parent.bottom; } PropertyChanges{ target: addingAreaLoader anchors.leftMargin: 0; anchors.rightMargin: 0; anchors.topMargin:0; anchors.bottomMargin: appletItem.metrics.margin.screenEdge; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } }, State{ name: "top" when: plasmoid.location === PlasmaCore.Types.TopEdge AnchorChanges{ target: addingAreaLoader anchors.horizontalCenter: parent.horizontalCenter; anchors.verticalCenter: undefined; anchors.right: undefined; anchors.left: undefined; anchors.top: parent.top; anchors.bottom: undefined; } PropertyChanges{ target: addingAreaLoader anchors.leftMargin: 0; anchors.rightMargin: 0; anchors.topMargin: appletItem.metrics.margin.screenEdge; anchors.bottomMargin: 0; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } }, State{ name: "left" when: plasmoid.location === PlasmaCore.Types.LeftEdge AnchorChanges{ target: addingAreaLoader anchors.horizontalCenter: undefined; anchors.verticalCenter: parent.verticalCenter; anchors.right: undefined; anchors.left: parent.left; anchors.top: undefined; anchors.bottom: undefined; } PropertyChanges{ target: addingAreaLoader anchors.leftMargin: appletItem.metrics.margin.screenEdge; anchors.rightMargin: 0; anchors.topMargin:0; anchors.bottomMargin: 0; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } }, State{ name: "right" when: plasmoid.location === PlasmaCore.Types.RightEdge AnchorChanges{ target: addingAreaLoader anchors.horizontalCenter: undefined; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.left: undefined; anchors.top: undefined; anchors.bottom: undefined; } PropertyChanges{ target: addingAreaLoader anchors.leftMargin: 0; anchors.rightMargin: appletItem.metrics.margin.screenEdge; anchors.topMargin:0; anchors.bottomMargin: 0; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } } ] } MouseArea{ id: appletMouseArea anchors.fill: parent enabled: visible hoverEnabled: latteApplet ? false : true propagateComposedEvents: true //! a way must be found in order for this be enabled //! only to support springloading for plasma 5.10 //! also on this is based the tooltips behavior by enabling it //! plasma tooltips are disabled visible: acceptMouseEvents property bool blockWheel: false onEntered: { appletItem.parabolic.stopRestoreZoomTimer(); if (restoreAnimation.running) { restoreAnimation.stop(); } if (!(isSeparator || isSpacer)) { root.showTooltipLabel(appletItem, applet.title); } if (originalAppletBehavior || communicator.requires.parabolicEffectLocked || !parabolicEffectIsSupported) { return; } if (root.isHalfShown || (root.latteApplet && (root.latteApplet.noTasksInAnimation>0 || root.latteApplet.contextMenu))) { return; } if (root.isHorizontal){ layoutsContainer.currentSpot = mouseX; wrapper.calculateParabolicScales(mouseX); } else{ layoutsContainer.currentSpot = mouseY; wrapper.calculateParabolicScales(mouseY); } } onExited:{ if (communicator.appletIconItemIsShown()) { communicator.setAppletIconItemActive(false); } root.hideTooltipLabel(); if (appletItem.parabolic.factor.zoom>1){ appletItem.parabolic.startRestoreZoomTimer(); } } onPositionChanged: { if (originalAppletBehavior || !parabolicEffectIsSupported) { mouse.accepted = false; return; } if (root.isHalfShown || (root.latteApplet && (root.latteApplet.noTasksInAnimation>0 || root.latteApplet.contextMenu))) { return; } var rapidMovement = appletItem.parabolic.lastIndex>=0 && Math.abs(appletItem.parabolic.lastIndex-index)>1; if (rapidMovement) { parabolic.setDirectRenderingEnabled(true); } if( ((wrapper.zoomScale == 1 || wrapper.zoomScale === appletItem.parabolic.factor.zoom) && !parabolic.directRenderingEnabled) || parabolic.directRenderingEnabled) { if (root.isHorizontal){ var step = Math.abs(layoutsContainer.currentSpot-mouse.x); if (step >= appletItem.animations.hoverPixelSensitivity){ layoutsContainer.currentSpot = mouse.x; wrapper.calculateParabolicScales(mouse.x); } } else{ var step = Math.abs(layoutsContainer.currentSpot-mouse.y); if (step >= appletItem.animations.hoverPixelSensitivity){ layoutsContainer.currentSpot = mouse.y; wrapper.calculateParabolicScales(mouse.y); } } } mouse.accepted = false; } //! these are needed in order for these events to be really forwarded underneath //! otherwise there were applets that did not receive them e.g. lock/logout applet //! when parabolic effect was used onPressed: mouse.accepted = false; onReleased: mouse.accepted = false; } //! Debug Elements Loader{ anchors.bottom: parent.bottom anchors.left: parent.left active: root.debugModeLayouter sourceComponent: Debug.Tag{ label.text: (root.isHorizontal ? appletItem.width : appletItem.height) + labeltext label.color: appletItem.isAutoFillApplet ? "green" : "white" readonly property string labeltext: { if (appletItem.isAutoFillApplet) { return " / max_fill:"+appletItem.maxAutoFillLength + " / min_fill:"+appletItem.minAutoFillLength; } return ""; } } } //! A timer is needed in order to handle also touchpads that probably //! send too many signals very fast. This way the signals per sec are limited. //! The user needs to have a steady normal scroll in order to not //! notice a annoying delay Timer{ id: scrollDelayer interval: 500 onTriggered: viewSignalsConnector.blockWheel = false; } //BEGIN states states: [ State { name: "left" when: (plasmoid.location === PlasmaCore.Types.LeftEdge) AnchorChanges { target: appletFlow anchors{ top:undefined; bottom:undefined; left:parent.left; right:undefined;} } }, State { name: "right" when: (plasmoid.location === PlasmaCore.Types.RightEdge) AnchorChanges { target: appletFlow anchors{ top:undefined; bottom:undefined; left:undefined; right:parent.right;} } }, State { name: "bottom" when: (plasmoid.location === PlasmaCore.Types.BottomEdge) AnchorChanges { target: appletFlow anchors{ top:undefined; bottom:parent.bottom; left:undefined; right:undefined;} } }, State { name: "top" when: (plasmoid.location === PlasmaCore.Types.TopEdge) AnchorChanges { target: appletFlow anchors{ top:parent.top; bottom:undefined; left:undefined; right:undefined;} } } ] //END states //BEGIN animations ///////Restore Zoom Animation///// ParallelAnimation{ id: restoreAnimation PropertyAnimation { target: wrapper property: "zoomScale" to: 1 duration: 3 * appletItem.animationTime easing.type: Easing.InCubic } } /////Clicked Animation///// SequentialAnimation{ id: clickedAnimation alwaysRunToEnd: true running: appletItem.isSquare && !originalAppletBehavior && appletItem.pressed && (appletItem.animations.speedFactor.current > 0) && !indicators.info.providesClickedAnimation ParallelAnimation{ PropertyAnimation { target: wrapper.clickedEffect property: "brightness" to: -0.35 duration: appletItem.animations.duration.large easing.type: Easing.OutQuad } } ParallelAnimation{ PropertyAnimation { target: wrapper.clickedEffect property: "brightness" to: 0 duration: appletItem.animations.duration.large easing.type: Easing.OutQuad } } } //END animations } diff --git a/containment/package/contents/ui/applet/HiddenSpacer.qml b/containment/package/contents/ui/applet/HiddenSpacer.qml index 68c8da93..efcc1c91 100644 --- a/containment/package/contents/ui/applet/HiddenSpacer.qml +++ b/containment/package/contents/ui/applet/HiddenSpacer.qml @@ -1,96 +1,105 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.1 import org.kde.latte.core 0.2 as LatteCore Item{ id: hiddenSpacer //we add one missing pixel from calculations width: root.isHorizontal ? nHiddenSize : wrapper.width height: root.isHorizontal ? wrapper.height : nHiddenSize ///check also if this is the first/last plasmoid in anylayout visible: (rightSpacer ? appletItem.lastAppletInContainer : appletItem.firstAppletInContainer) || separatorSpace>0 property bool neighbourSeparator: rightSpacer ? appletItem.headAppletIsSeparator : appletItem.tailAppletIsSeparator property int separatorSpace: neighbourSeparator && !appletItem.isSeparator && root.parabolicEffectEnabled && !appletItem.latteApplet ? ((LatteCore.Environment.separatorLength/2)+appletItem.metrics.margin.length) : subtrackedMargins property real nHiddenSize: { if (isSeparator || !communicator.requires.lengthMarginsEnabled) { return 0; } return (nScale > 0) ? (appletItem.spacersMaxSize * nScale) + separatorSpace : separatorSpace } property bool rightSpacer: false readonly property bool atEdgeForcingFittsLaw: !isSeparator && !parabolicEffectMarginsEnabled && atScreenEdge readonly property int subtrackedMargins: { - if (atEdgeForcingFittsLaw && !appletItem.isAutoFillApplet && ((firstChildOfStartLayout && rightSpacer) || (lastChildOfEndLayout && !rightSpacer ))) { - return (wrapper.edgeLengthMarginsDisabled ? appletItem.metrics.margin.length + appletItem.lengthAppletPadding : appletItem.metrics.margin.length); + if (atEdgeForcingFittsLaw && !appletItem.isAutoFillApplet) { + var inJustifyStart = (root.inFullJustify && firstChildOfStartLayout && rightSpacer); + var inJustifyEnd = (root.inFullJustify && lastChildOfEndLayout && !rightSpacer); + + var singleApplet = firstChildOfMainLayout && lastChildOfMainLayout; + var inSideStart = ((root.panelAlignment === LatteCore.Types.Left || root.panelAlignment === LatteCore.Types.Top) && firstChildOfMainLayout && rightSpacer); + var inSideEnd = ((root.panelAlignment === LatteCore.Types.Right || root.panelAlignment === LatteCore.Types.Bottom) && lastChildOfMainLayout && !rightSpacer); + + if (inJustifyStart || inJustifyEnd || inSideStart || inSideEnd) { + return (wrapper.edgeLengthMarginsDisabled ? appletItem.metrics.margin.length + appletItem.lengthAppletPadding : appletItem.metrics.margin.length); + } } return 0; } property real nScale: 0 Behavior on nHiddenSize { id: animatedBehavior enabled: !appletItem.parabolic.directRenderingEnabled || restoreAnimation.running NumberAnimation { duration: 3 * appletItem.animationTime } } Behavior on nHiddenSize { id: directBehavior enabled: !animatedBehavior.enabled NumberAnimation { duration: 0 } } Connections{ target: appletItem onContainsMouseChanged: { if (!appletItem.containsMouse) { hiddenSpacer.nScale = 0; } } } Loader{ active: root.debugModeSpacers sourceComponent: Rectangle{ width: !root.isVertical ? hiddenSpacer.width : 1 height: !root.isVertical ? 1 : hiddenSpacer.height x: root.isVertical ? hiddenSpacer.width/2 : 0 y: !root.isVertical ? hiddenSpacer.height/2 : 0 border.width: 1 border.color: "red" color: "transparent" } } } diff --git a/containment/package/contents/ui/applet/ItemWrapper.qml b/containment/package/contents/ui/applet/ItemWrapper.qml index 02f6ea99..b23d7736 100644 --- a/containment/package/contents/ui/applet/ItemWrapper.qml +++ b/containment/package/contents/ui/applet/ItemWrapper.qml @@ -1,721 +1,721 @@ /* * Copyright 2016 Smith AR * Michail Vourlakos * * This file is part of Latte-Dock * * Latte-Dock is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * Latte-Dock is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.1 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.latte.core 0.2 as LatteCore import "../../code/MathTools.js" as MathTools Item{ id: wrapper width: root.isHorizontal ? length : thickness height: root.isHorizontal ? thickness : length readonly property int length: { if (appletItem.isInternalViewSplitter && !root.inConfigureAppletsMode) { return 0; } if (isSeparator && root.parabolicEffectEnabled) { return -1; } if (appletItem.isAutoFillApplet) { if (appletItem.layouter.maxMetricsInHigherPriority) { return appletItem.maxAutoFillLength; } return Math.max(appletItem.minAutoFillLength,Math.min(appletPreferredLength,appletItem.maxAutoFillLength)); } return root.inConfigureAppletsMode ? Math.max(Math.min(appletItem.metrics.iconSize, root.minAppletLengthInConfigure), scaledLength) : scaledLength; } readonly property int thickness: { if (appletItem.isInternalViewSplitter && !root.inConfigureAppletsMode) { return 0; } return screenEdgeMarginSupported ? layoutThickness : scaledThickness + appletItem.metrics.margin.screenEdge } opacity: appletColorizer.mustBeShown && graphicsSystem.isAccelerated ? 0 : 1 property bool disableLengthScale: false property bool disableThicknessScale: false property bool editMode: root.inConfigureAppletsMode property bool edgeLengthMarginsDisabled: isSeparator || !communicator.requires.lengthMarginsEnabled || !parabolicEffectIsSupported property int appletWidth: applet ? applet.width : -1 property int appletHeight: applet ? applet.height : -1 property int appletMinimumWidth: applet && applet.Layout ? applet.Layout.minimumWidth : -1 property int appletMinimumHeight: applet && applet.Layout ? applet.Layout.minimumHeight : -1 property int appletPreferredWidth: applet && applet.Layout ? applet.Layout.preferredWidth : -1 property int appletPreferredHeight: applet && applet.Layout ? applet.Layout.preferredHeight : -1 property int appletMaximumWidth: applet && applet.Layout ? applet.Layout.maximumWidth : -1 property int appletMaximumHeight: applet && applet.Layout ? applet.Layout.maximumHeight : -1 readonly property int appletLength: root.isHorizontal ? appletWidth : appletHeight readonly property int appletThickness: root.isHorizontal ? appletHeight : appletWidth readonly property int appletMinimumLength : root.isHorizontal ? appletMinimumWidth : appletMinimumHeight readonly property int appletMinimumThickness: root.isHorizontal ? appletMinimumHeight : appletMinimumWidth readonly property int appletPreferredLength: root.isHorizontal ? appletPreferredWidth : appletPreferredHeight readonly property int appletPreferredThickness: root.isHorizontal ? appletPreferredHeight : appletPreferredWidth readonly property int appletMaximumLength: root.isHorizontal ? appletMaximumWidth : appletMaximumHeight readonly property int appletMaximumThickness: root.isHorizontal ? appletMaximumHeight : appletMaximumWidth property int iconSize: appletItem.metrics.iconSize property int marginsThickness: appletItem.metrics.totals.thicknessEdges property int marginsLength: 0 //Fitt's Law, through Binding to avoid Binding loops property int localLengthMargins: isSeparator || !communicator.requires.lengthMarginsEnabled || isInternalViewSplitter ? 0 : appletItem.lengthAppletFullMargins property int edgeLengthMargins: edgeLengthMarginsDisabled ? 0 : appletItem.lengthAppletPadding * 2 property real scaledLength: zoomScaleLength * (layoutLength + marginsLength) property real scaledThickness: zoomScaleThickness * (layoutThickness + marginsThickness) property real zoomScaleLength: disableLengthScale ? 1 : zoomScale property real zoomScaleThickness: disableThicknessScale ? 1 : zoomScale property int layoutLength: 0 property int layoutThickness: 0 property real center:root.isHorizontal ? (width + hiddenSpacerLeft.separatorSpace + hiddenSpacerRight.separatorSpace) / 2 : (height + hiddenSpacerLeft.separatorSpace + hiddenSpacerRight.separatorSpace) / 2 property real zoomScale: 1 property int index: appletItem.index property Item wrapperContainer: _wrapperContainer property Item clickedEffect: _clickedEffect property Item containerForOverlayIcon: _containerForOverlayIcon property Item overlayIconLoader: _overlayIconLoader Behavior on opacity { NumberAnimation { duration: 0.8 * appletItem.animations.duration.proposed easing.type: Easing.OutCubic } } // property int pHeight: applet ? applet.Layout.preferredHeight : -10 /*function debugLayouts(){ if(applet){ console.log("---------- "+ applet.pluginName +" ----------"); console.log("MinW "+applet.Layout.minimumWidth); console.log("PW "+applet.Layout.preferredWidth); console.log("MaxW "+applet.Layout.maximumWidth); console.log("FillW "+applet.Layout.fillWidth); console.log("-----"); console.log("MinH "+applet.Layout.minimumHeight); console.log("PH "+applet.Layout.preferredHeight); console.log("MaxH "+applet.Layout.maximumHeight); console.log("FillH "+applet.Layout.fillHeight); console.log("-----"); console.log("Real Applet Width: "+applet.width); console.log("Real Applet Height: "+applet.height); console.log("-----"); console.log("Real Wrapper Width: "+wrapper.width); console.log("Real Wrapper Height: "+wrapper.height); console.log("-----"); console.log("Can be hovered: " + parabolicEffectIsSupported); console.log("Icon size: " + appletItem.metrics.iconSize); console.log("Thick Margins: " + appletItem.metrics.totals.thicknessEdges); console.log("Intern. Margins: " + (appletItem.metrics.padding.length * 2)); console.log("Intern. Margins: " + (appletItem.metrics.margin.length * 2)); console.log("Max hovered criteria: " + (appletItem.metrics.iconSize + metrics.totals.thicknessEdges)); console.log("-----"); console.log("LayoutW: " + layoutWidth); console.log("LayoutH: " + layoutHeight); } } onLayoutWidthChanged: { debugLayouts(); } onLayoutHeightChanged: { debugLayouts(); }*/ onAppletLengthChanged: { if(zoomScale === 1) { appletItem.updateParabolicEffectIsSupported(); } } onAppletThicknessChanged: { if(zoomScale === 1) { appletItem.updateParabolicEffectIsSupported(); } } onAppletMinimumLengthChanged: { if(zoomScale === 1) { appletItem.updateParabolicEffectIsSupported(); } updateAutoFillLength(); } onAppletMinimumThicknessChanged: { if(zoomScale === 1) { appletItem.updateParabolicEffectIsSupported(); } } onAppletPreferredLengthChanged: updateAutoFillLength(); onAppletMaximumLengthChanged: updateAutoFillLength(); onZoomScaleChanged: { if ((zoomScale === appletItem.parabolic.factor.zoom) && !appletItem.parabolic.directRenderingEnabled) { appletItem.parabolic.setDirectRenderingEnabled(true); } if ((zoomScale > 1) && !appletItem.isZoomed) { appletItem.isZoomed = true; appletItem.animations.needBothAxis.addEvent(appletItem); } else if (zoomScale == 1) { appletItem.isZoomed = false; appletItem.animations.needBothAxis.removeEvent(appletItem); } } Binding { target: wrapper property: "layoutThickness" when: latteView && (wrapper.zoomScale === 1 || communicator.parabolicEffectIsSupported) value: { if (appletItem.isInternalViewSplitter){ return !root.inConfigureAppletsMode ? 0 : appletItem.metrics.iconSize; } if (communicator.parabolicEffectIsSupported && !communicator.inStartup/*avoid binding loops on startup*/) { return appletPreferredThickness; } return appletItem.metrics.iconSize; } } Binding { target: wrapper property: "layoutLength" when: latteView && !appletItem.isAutoFillApplet && (wrapper.zoomScale === 1) value: { if (appletItem.isInternalViewSplitter){ return !root.inConfigureAppletsMode ? 0 : Math.min(appletItem.metrics.iconSize, root.maxJustifySplitterSize); } else if (applet && ( appletMaximumLength < appletItem.metrics.iconSize || appletPreferredLength > appletItem.metrics.iconSize || appletItem.originalAppletBehavior) && !communicator.overlayLatteIconIsActive) { //this way improves performance, probably because during animation the preferred sizes update a lot if (appletMaximumLength>0 && appletMaximumLength < appletItem.metrics.iconSize){ return appletMaximumLength; } else if (appletMinimumLength > appletItem.metrics.iconSize){ return appletMinimumLength; } else if ((appletPreferredLength > appletItem.metrics.iconSize) || (appletPreferredLength === 0)/*applet wants to hide itself*/ || (appletItem.originalAppletBehavior && appletPreferredLength > 0)){ return appletPreferredLength; } } return appletItem.metrics.iconSize; } } Binding { target: wrapper property: "disableLengthScale" when: latteView && !(appletItem.isAutoFillApplet || appletItem.isLattePlasmoid) value: { var blockParabolicEffectInLength = false; if (communicator.parabolicEffectIsSupported) { return true; } if (appletItem.isInternalViewSplitter){ return false; } else { if(applet && (appletMinimumLength > appletItem.metrics.iconSize) && !appletItem.parabolicEffectIsSupported && !communicator.overlayLatteIconIsActive){ return (wrapper.zoomScale === 1); } //it is used for plasmoids that need to scale only one axis... e.g. the Weather Plasmoid else if(applet && ( appletMaximumLength < appletItem.metrics.iconSize || appletPreferredLength > appletItem.metrics.iconSize || appletItem.originalAppletBehavior) && !communicator.overlayLatteIconIsActive) { //this way improves performance, probably because during animation the preferred sizes update a lot if (appletMaximumLength>0 && appletMaximumLength < appletItem.metrics.iconSize){ return false; } else if (appletMinimumLength > appletItem.metrics.iconSize){ return (wrapper.zoomScale === 1); } else if ((appletPreferredLength > appletItem.metrics.iconSize) || (appletItem.originalAppletBehavior && appletPreferredLength > 0 )){ return (wrapper.zoomScale === 1); } } } return false; } } Binding { target: wrapper property: "marginsLength" when: latteView && (!root.inStartup || visibilityManager.inTempHiding) - value: root.inFullJustify && atScreenEdge && !parabolicEffectMarginsEnabled ? edgeLengthMargins : localLengthMargins + value: atScreenEdge && !parabolicEffectMarginsEnabled ? edgeLengthMargins : localLengthMargins } function updateAutoFillLength() { if (appletItem.isAutoFillApplet) { appletItem.layouter.updateSizeForAppletsInFill(); } } Item{ id:_wrapperContainer width: root.isHorizontal ? _length : _thickness height: root.isHorizontal ? _thickness : _length opacity: appletShadow.active ? 0 : 1 property int _length:0 // through Binding to avoid binding loops property int _thickness:0 // through Binding to avoid binding loops readonly property int appliedEdgeMargin: { if (appletItem.isInternalViewSplitter) { return appletItem.metrics.margin.screenEdge + appletItem.metrics.margin.thickness; } return appletItem.screenEdgeMarginSupported ? 0 : appletItem.metrics.margin.screenEdge; } Binding { target: _wrapperContainer property: "_thickness" when: !visibilityManager.inTempHiding value: { if (appletItem.isInternalViewSplitter) { return wrapper.layoutThickness; } var wrapperContainerThickness = wrapper.zoomScaleThickness * (appletItem.metrics.totals.thickness); return appletItem.screenEdgeMarginSupported ? wrapperContainerThickness + appletItem.metrics.margin.screenEdge : wrapperContainerThickness; } } Binding { target: _wrapperContainer property: "_length" when: !visibilityManager.inTempHiding value: { if (appletItem.isAutoFillApplet && (appletItem.maxAutoFillLength>-1)){ return wrapper.length; } if (appletItem.isInternalViewSplitter) { return wrapper.layoutLength; } return wrapper.zoomScaleLength * wrapper.layoutLength; } } ///Secret MouseArea to be used by the folder widget Loader{ anchors.fill: parent active: communicator.overlayLatteIconIsActive && applet.pluginName === "org.kde.plasma.folder" && !appletItem.acceptMouseEvents sourceComponent: MouseArea{ onClicked: latteView.extendedInterface.toggleAppletExpanded(applet.id); } } Item{ id: _containerForOverlayIcon anchors.fill: parent } Loader{ id: _overlayIconLoader anchors.fill: parent active: communicator.overlayLatteIconIsActive property color backgroundColor: "black" property color glowColor: "white" sourceComponent: LatteCore.IconItem{ id: overlayIconItem anchors.fill: parent source: { if (communicator.appletIconItemIsShown()) return communicator.appletIconItem.source; else if (communicator.appletImageItemIsShown()) return communicator.appletImageItem.source; return ""; } providesColors: indicators.info.needsIconColors && source != "" usesPlasmaTheme: communicator.appletIconItemIsShown() ? communicator.appletIconItem.usesPlasmaTheme : false Binding{ target: _overlayIconLoader property: "backgroundColor" when: overlayIconItem.providesColors value: overlayIconItem.backgroundColor } Binding{ target: _overlayIconLoader property: "glowColor" when: overlayIconItem.providesColors value: overlayIconItem.glowColor } Loader{ anchors.centerIn: parent active: root.debugModeOverloadedIcons sourceComponent: Rectangle{ width: 30 height: 30 color: "green" opacity: 0.65 } } } } //! WrapperContainer States states:[ State{ name: "bottom" when: plasmoid.location === PlasmaCore.Types.BottomEdge AnchorChanges{ target: _wrapperContainer; anchors.horizontalCenter: parent.horizontalCenter; anchors.verticalCenter: undefined; anchors.right: undefined; anchors.left: undefined; anchors.top: undefined; anchors.bottom: parent.bottom; } PropertyChanges{ target: _wrapperContainer; anchors.leftMargin: 0; anchors.rightMargin: 0; anchors.topMargin:0; anchors.bottomMargin: _wrapperContainer.appliedEdgeMargin; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } }, State{ name: "top" when: plasmoid.location === PlasmaCore.Types.TopEdge AnchorChanges{ target:_wrapperContainer; anchors.horizontalCenter: parent.horizontalCenter; anchors.verticalCenter: undefined; anchors.right: undefined; anchors.left: undefined; anchors.top: parent.top; anchors.bottom: undefined; } PropertyChanges{ target: _wrapperContainer; anchors.leftMargin: 0; anchors.rightMargin: 0; anchors.topMargin: _wrapperContainer.appliedEdgeMargin; anchors.bottomMargin: 0; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } }, State{ name: "left" when: plasmoid.location === PlasmaCore.Types.LeftEdge AnchorChanges{ target: _wrapperContainer; anchors.horizontalCenter: undefined; anchors.verticalCenter: parent.verticalCenter; anchors.right: undefined; anchors.left: parent.left; anchors.top: undefined; anchors.bottom: undefined; } PropertyChanges{ target: _wrapperContainer; anchors.leftMargin: _wrapperContainer.appliedEdgeMargin; anchors.rightMargin: 0; anchors.topMargin:0; anchors.bottomMargin: 0; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } }, State{ name: "right" when: plasmoid.location === PlasmaCore.Types.RightEdge AnchorChanges{ target: _wrapperContainer; anchors.horizontalCenter: undefined; anchors.verticalCenter: parent.verticalCenter; anchors.right: parent.right; anchors.left: undefined; anchors.top: undefined; anchors.bottom: undefined; } PropertyChanges{ target: _wrapperContainer; anchors.leftMargin: 0; anchors.rightMargin: _wrapperContainer.appliedEdgeMargin; anchors.topMargin:0; anchors.bottomMargin: 0; anchors.horizontalCenterOffset: 0; anchors.verticalCenterOffset: 0; } } ] } Loader{ anchors.fill: _wrapperContainer active: appletItem.isInternalViewSplitter && root.inConfigureAppletsMode sourceComponent: Item { anchors.fill: parent PlasmaCore.SvgItem{ id:splitterImage anchors.centerIn: parent width: Math.min(root.maxJustifySplitterSize, appletItem.metrics.iconSize) height: width rotation: root.isVertical ? 90 : 0 svg: PlasmaCore.Svg{ imagePath: root.universalSettings.splitterIconPath() } layer.enabled: graphicsSystem.isAccelerated layer.effect: DropShadow { radius: root.appShadowSize fast: true samples: 2 * radius color: root.appShadowColor verticalOffset: 2 } } } } ///Shadow in applets Loader{ id: appletShadow anchors.fill: appletItem.appletWrapper active: appletItem.applet && graphicsSystem.isAccelerated && !appletColorizer.mustBeShown && (root.enableShadows && applet.pluginName !== root.plasmoidName) onActiveChanged: { if (active && !isSeparator && graphicsSystem.isAccelerated) { wrapperContainer.opacity = 0; } else { wrapperContainer.opacity = 1; } } opacity: isSeparator ? 0.4 : 1 sourceComponent: DropShadow{ anchors.fill: parent color: root.appShadowColor //"#ff080808" fast: true samples: 2 * radius source: communicator.overlayLatteIconIsActive ? _wrapperContainer : appletItem.applet radius: shadowSize verticalOffset: root.forceTransparentPanel || root.forcePanelForBusyBackground ? 0 : 2 property int shadowSize : root.appShadowSize } } BrightnessContrast{ id:hoveredImage anchors.fill: _wrapperContainer source: _wrapperContainer enabled: appletItem.isSquare && !originalAppletBehavior && !indicators.info.providesHoveredAnimation && opacity != 0 ? true : false opacity: appletMouseArea.containsMouse ? 1 : 0 brightness: 0.25 contrast: 0.15 visible: !indicators.info.providesHoveredAnimation Behavior on opacity { NumberAnimation { duration: appletItem.animations.speedFactor.current*appletItem.animations.duration.large } } } BrightnessContrast { id: _clickedEffect anchors.fill: _wrapperContainer source: _wrapperContainer visible: clickedAnimation.running && !indicators.info.providesClickedAnimation } /* onHeightChanged: { if ((index == 1)|| (index==3)){ console.log("H: "+index+" ("+zoomScale+"). "+currentLayout.children[1].height+" - "+currentLayout.children[3].height+" - "+(currentLayout.children[1].height+currentLayout.children[3].height)); } } onZoomScaleChanged:{ if ((index == 1)|| (index==3)){ console.log(index+" ("+zoomScale+"). "+currentLayout.children[1].height+" - "+currentLayout.children[3].height+" - "+(currentLayout.children[1].height+currentLayout.children[3].height)); } }*/ Loader{ anchors.fill: parent active: root.debugMode sourceComponent: Rectangle{ anchors.fill: parent color: "transparent" //! red visualizer, in debug mode for the applets that use fillWidth or fillHeight //! green, for the rest border.color: (appletItem.isAutoFillApplet && (appletItem.maxAutoFillLength>-1) && root.isHorizontal) ? "red" : "green" border.width: 1 } } Behavior on zoomScale { id: animatedScaleBehavior enabled: !appletItem.parabolic.directRenderingEnabled || restoreAnimation.running NumberAnimation { duration: 3 * appletItem.animationTime easing.type: Easing.OutCubic } } Behavior on zoomScale { enabled: !animatedScaleBehavior.enabled NumberAnimation { duration: 0 } } function calculateParabolicScales( currentMousePosition ){ if (parabolic.factor.zoom===1 || parabolic.restoreZoomIsBlocked) { return; } //use the new parabolic effect manager in order to handle all parabolic effect messages var scales = parabolic.applyParabolicEffect(index, currentMousePosition, center); //Left hiddenSpacer if(appletItem.firstAppletInContainer){ hiddenSpacerLeft.nScale = scales.leftScale - 1; } //Right hiddenSpacer ///there is one more item in the currentLayout ???? if(appletItem.lastAppletInContainer){ hiddenSpacerRight.nScale = scales.rightScale - 1; } zoomScale = parabolic.factor.zoom; } //scale function updateScale(nIndex, nScale, step){ if(appletItem && !appletItem.containsMouse && (appletItem.index === nIndex)){ if ( ((parabolicEffectIsSupported && !appletItem.originalAppletBehavior) || appletItem.latteApplet) && (applet && applet.status !== PlasmaCore.Types.HiddenStatus) ){ if(!appletItem.latteApplet){ if(nScale >= 0) zoomScale = nScale + step; else zoomScale = zoomScale + step; } } } } function sltUpdateLowerItemScale(delegateIndex, newScale, step) { if (delegateIndex === appletItem.index) { if (communicator.parabolicEffectIsSupported) { communicator.bridge.parabolic.client.hostRequestUpdateLowerItemScale(newScale, step); return; } if (!appletItem.isSeparator && !appletItem.isHidden) { //! when accepted updateScale(delegateIndex, newScale, step); if (newScale > 1) { // clear lower items parabolic.sglUpdateLowerItemScale(delegateIndex-1, 1, 0); } } else { parabolic.sglUpdateLowerItemScale(delegateIndex-1, newScale, step); } } else if ((newScale === 1) && (appletItem.index < delegateIndex)) { //! apply zoom clearing if (communicator.parabolicEffectIsSupported) { communicator.bridge.parabolic.client.hostRequestUpdateLowerItemScale(1, step); } else { updateScale(appletItem.index, 1, 0); } } } function sltUpdateHigherItemScale(delegateIndex, newScale, step) { if (delegateIndex === appletItem.index) { if (communicator.parabolicEffectIsSupported) { communicator.bridge.parabolic.client.hostRequestUpdateHigherItemScale(newScale, step); return; } if (!appletItem.isSeparator && !appletItem.isHidden) { //! when accepted updateScale(delegateIndex, newScale, step); if (newScale > 1) { // clear higher items parabolic.sglUpdateHigherItemScale(delegateIndex+1, 1, 0); } } else { parabolic.sglUpdateHigherItemScale(delegateIndex+1, newScale, step); } } else if ((newScale === 1) && (appletItem.index > delegateIndex)) { //! apply zoom clearing if (communicator.parabolicEffectIsSupported) { communicator.bridge.parabolic.client.hostRequestUpdateHigherItemScale(1, step); } else { updateScale(appletItem.index, 1, 0); } } } Component.onCompleted: { parabolic.sglUpdateLowerItemScale.connect(sltUpdateLowerItemScale); parabolic.sglUpdateHigherItemScale.connect(sltUpdateHigherItemScale); } Component.onDestruction: { parabolic.sglUpdateLowerItemScale.disconnect(sltUpdateLowerItemScale); parabolic.sglUpdateHigherItemScale.disconnect(sltUpdateHigherItemScale); } }// Main task area // id:wrapper