mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 15:03:09 +00:00
This fixes an issue where if the device is rotated on the 2nd or further page, the page would not snap immediately back to the right position, but would instead animate.
946 lines
33 KiB
C++
946 lines
33 KiB
C++
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "dragstate.h"
|
|
#include "favouritesmodel.h"
|
|
#include "pagelistmodel.h"
|
|
|
|
#include <KLocalizedString>
|
|
#include <algorithm>
|
|
|
|
// TODO don't hardcode, use page widths
|
|
const int PAGE_CHANGE_THRESHOLD = 30;
|
|
|
|
const QString DEFAULT_FOLDER_NAME = i18n("Folder");
|
|
|
|
DelegateDragPosition::DelegateDragPosition(QObject *parent)
|
|
: QObject{parent}
|
|
{
|
|
}
|
|
|
|
DelegateDragPosition::~DelegateDragPosition() = default;
|
|
|
|
void DelegateDragPosition::copyFrom(DelegateDragPosition *position)
|
|
{
|
|
setPage(position->page());
|
|
setPageRow(position->pageRow());
|
|
setPageColumn(position->pageColumn());
|
|
setFavouritesPosition(position->favouritesPosition());
|
|
setFolderPosition(position->folderPosition());
|
|
setFolder(position->folder());
|
|
setLocation(position->location());
|
|
}
|
|
|
|
DelegateDragPosition::Location DelegateDragPosition::location() const
|
|
{
|
|
return m_location;
|
|
}
|
|
|
|
void DelegateDragPosition::setLocation(Location location)
|
|
{
|
|
if (m_location != location) {
|
|
m_location = location;
|
|
Q_EMIT locationChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::page() const
|
|
{
|
|
return m_page;
|
|
}
|
|
|
|
void DelegateDragPosition::setPage(int page)
|
|
{
|
|
if (m_page != page) {
|
|
m_page = page;
|
|
Q_EMIT pageChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::pageRow() const
|
|
{
|
|
return m_pageRow;
|
|
}
|
|
|
|
void DelegateDragPosition::setPageRow(int pageRow)
|
|
{
|
|
if (m_pageRow != pageRow) {
|
|
m_pageRow = pageRow;
|
|
Q_EMIT pageRowChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::pageColumn() const
|
|
{
|
|
return m_pageColumn;
|
|
}
|
|
|
|
void DelegateDragPosition::setPageColumn(int pageColumn)
|
|
{
|
|
if (m_pageColumn != pageColumn) {
|
|
m_pageColumn = pageColumn;
|
|
Q_EMIT pageColumnChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::favouritesPosition() const
|
|
{
|
|
return m_favouritesPosition;
|
|
}
|
|
|
|
void DelegateDragPosition::setFavouritesPosition(int favouritesPosition)
|
|
{
|
|
if (m_favouritesPosition != favouritesPosition) {
|
|
m_favouritesPosition = favouritesPosition;
|
|
Q_EMIT favouritesPositionChanged();
|
|
}
|
|
}
|
|
|
|
int DelegateDragPosition::folderPosition() const
|
|
{
|
|
return m_folderPosition;
|
|
}
|
|
|
|
void DelegateDragPosition::setFolderPosition(int folderPosition)
|
|
{
|
|
if (m_folderPosition != folderPosition) {
|
|
m_folderPosition = folderPosition;
|
|
Q_EMIT folderPositionChanged();
|
|
}
|
|
}
|
|
|
|
FolioApplicationFolder *DelegateDragPosition::folder() const
|
|
{
|
|
return m_folder;
|
|
}
|
|
|
|
void DelegateDragPosition::setFolder(FolioApplicationFolder *folder)
|
|
{
|
|
if (m_folder != folder) {
|
|
m_folder = folder;
|
|
Q_EMIT folderChanged();
|
|
}
|
|
}
|
|
|
|
DragState::DragState(HomeScreenState *state, HomeScreen *parent)
|
|
: QObject{parent}
|
|
, m_homeScreen{parent}
|
|
, m_state{state}
|
|
, m_changePageTimer{new QTimer{this}}
|
|
, m_openFolderTimer{new QTimer{this}}
|
|
, m_leaveFolderTimer{new QTimer{this}}
|
|
, m_changeFolderPageTimer{new QTimer{this}}
|
|
, m_folderInsertBetweenTimer{new QTimer{this}}
|
|
, m_favouritesInsertBetweenTimer{new QTimer{this}}
|
|
, m_candidateDropPosition{new DelegateDragPosition{this}}
|
|
, m_startPosition{new DelegateDragPosition{this}}
|
|
{
|
|
if (!state) {
|
|
return;
|
|
}
|
|
|
|
// 500 ms hold before page timer changes
|
|
m_changePageTimer->setInterval(500);
|
|
m_changePageTimer->setSingleShot(true);
|
|
|
|
m_openFolderTimer->setInterval(1000);
|
|
m_openFolderTimer->setSingleShot(true);
|
|
|
|
m_leaveFolderTimer->setInterval(500);
|
|
m_leaveFolderTimer->setSingleShot(true);
|
|
|
|
m_changeFolderPageTimer->setInterval(500);
|
|
m_changeFolderPageTimer->setSingleShot(true);
|
|
|
|
m_folderInsertBetweenTimer->setInterval(250);
|
|
m_folderInsertBetweenTimer->setSingleShot(true);
|
|
|
|
m_favouritesInsertBetweenTimer->setInterval(250);
|
|
m_favouritesInsertBetweenTimer->setSingleShot(true);
|
|
|
|
connect(m_changePageTimer, &QTimer::timeout, this, &DragState::onChangePageTimerFinished);
|
|
connect(m_openFolderTimer, &QTimer::timeout, this, &DragState::onOpenFolderTimerFinished);
|
|
connect(m_leaveFolderTimer, &QTimer::timeout, this, &DragState::onLeaveFolderTimerFinished);
|
|
connect(m_changeFolderPageTimer, &QTimer::timeout, this, &DragState::onChangeFolderPageTimerFinished);
|
|
connect(m_folderInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFolderInsertBetweenTimerFinished);
|
|
connect(m_favouritesInsertBetweenTimer, &QTimer::timeout, this, &DragState::onFavouritesInsertBetweenTimerFinished);
|
|
|
|
connect(m_state, &HomeScreenState::delegateDragFromPageStarted, this, &DragState::onDelegateDragFromPageStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromAppDrawerStarted, this, &DragState::onDelegateDragFromAppDrawerStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromFavouritesStarted, this, &DragState::onDelegateDragFromFavouritesStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromFolderStarted, this, &DragState::onDelegateDragFromFolderStarted);
|
|
connect(m_state, &HomeScreenState::delegateDragFromWidgetListStarted, this, &DragState::onDelegateDragFromWidgetListStarted);
|
|
connect(m_state, &HomeScreenState::swipeStateChanged, this, [this]() {
|
|
if (m_state->swipeState() == HomeScreenState::DraggingDelegate) {
|
|
onDelegateDraggingStarted();
|
|
}
|
|
});
|
|
connect(m_state, &HomeScreenState::delegateDragEnded, this, &DragState::onDelegateDropped);
|
|
|
|
connect(m_state, &HomeScreenState::pageNumChanged, this, [this]() {
|
|
m_candidateDropPosition->setPageRow(m_state->currentPage());
|
|
});
|
|
|
|
connect(m_state, &HomeScreenState::delegateDragXChanged, this, &DragState::onDelegateDragPositionChanged);
|
|
connect(m_state, &HomeScreenState::delegateDragYChanged, this, &DragState::onDelegateDragPositionChanged);
|
|
|
|
connect(m_state, &HomeScreenState::leftCurrentFolder, this, &DragState::onLeaveCurrentFolder);
|
|
}
|
|
|
|
DelegateDragPosition *DragState::candidateDropPosition() const
|
|
{
|
|
return m_candidateDropPosition;
|
|
}
|
|
|
|
DelegateDragPosition *DragState::startPosition() const
|
|
{
|
|
return m_startPosition;
|
|
}
|
|
|
|
FolioDelegate *DragState::dropDelegate() const
|
|
{
|
|
return m_dropDelegate;
|
|
}
|
|
|
|
void DragState::setDropDelegate(FolioDelegate *dropDelegate)
|
|
{
|
|
m_dropDelegate = dropDelegate;
|
|
Q_EMIT dropDelegateChanged();
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionChanged()
|
|
{
|
|
if (!m_state) {
|
|
return;
|
|
}
|
|
|
|
// we want to update the candidate drop position variable in this function!
|
|
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
|
|
bool inFolder = m_state->viewState() == HomeScreenState::FolderView;
|
|
bool inFavouritesArea = !inFolder;
|
|
|
|
// the favourites bar can be in different locations, so account for each
|
|
switch (m_state->favouritesBarLocation()) {
|
|
case HomeScreenState::Bottom:
|
|
inFavouritesArea = inFavouritesArea && y > m_state->pageHeight();
|
|
break;
|
|
case HomeScreenState::Left:
|
|
inFavouritesArea = inFavouritesArea && x < m_state->viewWidth() - m_state->pageHeight();
|
|
break;
|
|
case HomeScreenState::Right:
|
|
inFavouritesArea = inFavouritesArea && x > m_state->pageWidth();
|
|
break;
|
|
}
|
|
|
|
// stop the favourites insertion timer if the delegate has moved out
|
|
if (!inFavouritesArea) {
|
|
m_favouritesInsertBetweenTimer->stop();
|
|
}
|
|
|
|
if (inFavouritesArea || inFolder) {
|
|
m_openFolderTimer->stop();
|
|
}
|
|
|
|
if (m_state->viewState() == HomeScreenState::FolderView) {
|
|
// if we are in a folder
|
|
onDelegateDragPositionOverFolderViewChanged();
|
|
|
|
} else if (inFavouritesArea) {
|
|
// we are in the favourites bar area
|
|
onDelegateDragPositionOverFavouritesChanged();
|
|
} else {
|
|
// we are in the homescreen pages area
|
|
onDelegateDragPositionOverPageViewChanged();
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionOverFolderViewChanged()
|
|
{
|
|
// if the drag position changes while in the folder view
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
|
|
auto *folder = m_state->currentFolder();
|
|
if (!folder) {
|
|
return;
|
|
}
|
|
|
|
// if the drop position is not in the folder, but outside (going to page view)
|
|
if (folder->isDropPositionOutside(x, y)) {
|
|
if (!m_leaveFolderTimer->isActive()) {
|
|
m_leaveFolderTimer->start();
|
|
}
|
|
return;
|
|
} else if (m_leaveFolderTimer->isActive()) {
|
|
// cancel timer if we are back in the folder
|
|
m_leaveFolderTimer->stop();
|
|
}
|
|
|
|
// the potential folder index that can be dropped at
|
|
int dropIndex = folder->dropInsertPosition(m_state->currentFolderPage(), x, y);
|
|
|
|
// if the delegate has moved to another position, cancel the insert timer
|
|
if (dropIndex != m_folderInsertBetweenIndex) {
|
|
m_folderInsertBetweenTimer->stop();
|
|
}
|
|
|
|
// start the insertion timer (so that the user has time to move the delegate away)
|
|
if (!m_folderInsertBetweenTimer->isActive()) {
|
|
m_folderInsertBetweenTimer->start();
|
|
m_folderInsertBetweenIndex = dropIndex;
|
|
}
|
|
|
|
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
|
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
|
|
|
// determine if the delegate is near the edge of a page (to switch pages).
|
|
// -> start the change page timer if we at the page edge.
|
|
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD || x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
|
|
if (!m_changeFolderPageTimer->isActive()) {
|
|
m_changeFolderPageTimer->start();
|
|
}
|
|
} else {
|
|
if (m_changeFolderPageTimer->isActive()) {
|
|
m_changeFolderPageTimer->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionOverFavouritesChanged()
|
|
{
|
|
// the drag position changed while over the favourites strip
|
|
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
FavouritesModel *favouritesModel = m_homeScreen->favouritesModel();
|
|
int dropIndex = favouritesModel->dropInsertPosition(x, y);
|
|
|
|
// if the drop position changed, cancel the open folder timer
|
|
if (m_candidateDropPosition->location() != DelegateDragPosition::Favourites || m_candidateDropPosition->favouritesPosition() != dropIndex) {
|
|
if (m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->stop();
|
|
}
|
|
}
|
|
|
|
// if the delegate has moved to another position, cancel the insert timer
|
|
if (dropIndex != m_favouritesInsertBetweenIndex) {
|
|
m_favouritesInsertBetweenTimer->stop();
|
|
}
|
|
|
|
// ignore this event if the favourites area is full already
|
|
if (favouritesModel->isFull()) {
|
|
return;
|
|
}
|
|
|
|
// ignore widget drop delegates (since they can't be placed in the favourites)
|
|
if (m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Widget) {
|
|
return;
|
|
}
|
|
|
|
if (favouritesModel->dropPositionIsEdge(x, y)) {
|
|
// if we need to make space for the delegate
|
|
|
|
// start the insertion timer (so that the user has time to move the delegate away)
|
|
if (!m_favouritesInsertBetweenTimer->isActive()) {
|
|
m_favouritesInsertBetweenTimer->start();
|
|
m_favouritesInsertBetweenIndex = dropIndex;
|
|
}
|
|
} else {
|
|
// if we are hovering over the center of a folder or app
|
|
|
|
// delete ghost entry if there is one
|
|
int ghostEntryPosition = favouritesModel->getGhostEntryPosition();
|
|
if (ghostEntryPosition != -1 && ghostEntryPosition != dropIndex) {
|
|
if (dropIndex > ghostEntryPosition) {
|
|
// correct index if deleting the ghost will change the index
|
|
dropIndex--;
|
|
}
|
|
favouritesModel->deleteGhostEntry();
|
|
}
|
|
|
|
// update the current drop position
|
|
m_candidateDropPosition->setFavouritesPosition(dropIndex);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
|
|
|
|
// start folder open timer if hovering over a folder
|
|
// get delegate being hovered over
|
|
FolioDelegate *delegate = favouritesModel->getEntryAt(dropIndex);
|
|
|
|
// check delegate is a folder and the drop delegate is an app
|
|
if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
|
|
if (!m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->start();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDragPositionOverPageViewChanged()
|
|
{
|
|
// the drag position changed while over the homescreen pages strip
|
|
|
|
qreal delegateX = getDraggedDelegateX();
|
|
qreal delegateY = getDraggedDelegateY();
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
int page = m_state->currentPage();
|
|
|
|
// calculate the row and column the delegate is over
|
|
qreal pageHorizontalMargin = (m_state->pageWidth() - m_state->pageContentWidth()) / 2;
|
|
qreal pageVerticalMargin = (m_state->pageHeight() - m_state->pageContentHeight()) / 2;
|
|
|
|
int row = 0;
|
|
int column = 0;
|
|
|
|
if (m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Widget) {
|
|
// for widgets, we use their top left position to determine where they are placed (since they are larger than one cell)
|
|
row = (delegateY - pageVerticalMargin) / m_state->pageCellHeight();
|
|
column = (delegateX - pageHorizontalMargin) / m_state->pageCellWidth();
|
|
} else {
|
|
// otherwise, we base it on the pointer position
|
|
row = (y - pageVerticalMargin) / m_state->pageCellHeight();
|
|
column = (x - pageHorizontalMargin) / m_state->pageCellWidth();
|
|
}
|
|
|
|
// ensure it's in bounds
|
|
row = std::max(0, std::min(m_state->pageRows() - 1, row));
|
|
column = std::max(0, std::min(m_state->pageColumns() - 1, column));
|
|
|
|
// if the drop position changed, cancel the open folder timer
|
|
if (m_candidateDropPosition->location() != DelegateDragPosition::Pages || m_candidateDropPosition->pageRow() != row
|
|
|| m_candidateDropPosition->pageColumn() != column) {
|
|
if (m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->stop();
|
|
}
|
|
}
|
|
|
|
// update the current drop position
|
|
m_candidateDropPosition->setPage(page);
|
|
m_candidateDropPosition->setPageRow(row);
|
|
m_candidateDropPosition->setPageColumn(column);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Pages);
|
|
|
|
// start folder open timer if hovering over a folder
|
|
PageModel *pageModel = m_homeScreen->pageListModel()->getPage(page);
|
|
if (pageModel) {
|
|
// get delegate being hovered over
|
|
FolioDelegate *delegate = pageModel->getDelegate(row, column);
|
|
|
|
// check delegate is a folder and the drop delegate is an app
|
|
if (delegate && delegate->type() == FolioDelegate::Folder && m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Application) {
|
|
if (!m_openFolderTimer->isActive()) {
|
|
m_openFolderTimer->start();
|
|
}
|
|
}
|
|
}
|
|
|
|
const int leftPagePosition = 0;
|
|
const int rightPagePosition = m_state->pageWidth();
|
|
|
|
// determine if the delegate is near the edge of a page (to switch pages).
|
|
// -> start the change page timer if we at the page edge.
|
|
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD || qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
|
if (!m_changePageTimer->isActive()) {
|
|
m_changePageTimer->start();
|
|
}
|
|
} else {
|
|
if (m_changePageTimer->isActive()) {
|
|
m_changePageTimer->stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onDelegateDraggingStarted()
|
|
{
|
|
// remove the delegate from the model
|
|
// NOTE: we only delete here (and not from the event trigger, ex. onDelegateDragFromPageStarted)
|
|
// because the actual dragging only started when this is called
|
|
deleteStartPositionDelegate();
|
|
}
|
|
|
|
void DragState::onDelegateDragFromPageStarted(int page, int row, int column)
|
|
{
|
|
// fetch delegate at start position
|
|
PageModel *pageModel = m_homeScreen->pageListModel()->getPage(page);
|
|
if (pageModel) {
|
|
setDropDelegate(pageModel->getDelegate(row, column));
|
|
} else {
|
|
setDropDelegate(nullptr);
|
|
}
|
|
|
|
// set start location
|
|
m_startPosition->setPage(page);
|
|
m_startPosition->setPageRow(row);
|
|
m_startPosition->setPageColumn(column);
|
|
m_startPosition->setLocation(DelegateDragPosition::Pages);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromFavouritesStarted(int position)
|
|
{
|
|
// fetch delegate at start position
|
|
setDropDelegate(m_homeScreen->favouritesModel()->getEntryAt(position));
|
|
|
|
// set start location
|
|
m_startPosition->setFavouritesPosition(position);
|
|
m_startPosition->setLocation(DelegateDragPosition::Favourites);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromAppDrawerStarted(QString storageId)
|
|
{
|
|
// fetch delegate at start position
|
|
if (KService::Ptr service = KService::serviceByStorageId(storageId)) {
|
|
FolioApplication *app = new FolioApplication{m_homeScreen, service};
|
|
setDropDelegate(new FolioDelegate{app, m_homeScreen});
|
|
} else {
|
|
setDropDelegate(nullptr);
|
|
}
|
|
|
|
// set start location
|
|
m_startPosition->setLocation(DelegateDragPosition::AppDrawer);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position)
|
|
{
|
|
// fetch delegate at start position
|
|
setDropDelegate(folder->applications()->getDelegate(position));
|
|
|
|
// set start location
|
|
m_startPosition->setFolder(folder);
|
|
m_startPosition->setFolderPosition(position);
|
|
m_startPosition->setLocation(DelegateDragPosition::Folder);
|
|
}
|
|
|
|
void DragState::onDelegateDragFromWidgetListStarted(QString appletPluginId)
|
|
{
|
|
// default widget has dimensions of 1x1, and id of -1
|
|
m_createdAppletPluginId = appletPluginId;
|
|
FolioWidget *widget = new FolioWidget{m_homeScreen, -1, 1, 1};
|
|
setDropDelegate(new FolioDelegate{widget, m_homeScreen});
|
|
|
|
// set start location
|
|
m_startPosition->setLocation(DelegateDragPosition::WidgetList);
|
|
}
|
|
|
|
void DragState::onDelegateDropped()
|
|
{
|
|
if (!m_dropDelegate) {
|
|
return;
|
|
}
|
|
|
|
// add dropped delegate
|
|
bool success = createDropPositionDelegate();
|
|
|
|
// delete empty pages at the end if they exist
|
|
// (it can be created if user drags app to new page, but doesn't place it there)
|
|
m_homeScreen->pageListModel()->deleteEmptyPagesAtEnd();
|
|
|
|
// clear ghost position if there is one
|
|
m_homeScreen->favouritesModel()->deleteGhostEntry();
|
|
|
|
// reset timers
|
|
m_folderInsertBetweenTimer->stop();
|
|
m_changeFolderPageTimer->stop();
|
|
m_leaveFolderTimer->stop();
|
|
m_changePageTimer->stop();
|
|
m_favouritesInsertBetweenTimer->stop();
|
|
|
|
// emit corresponding signal
|
|
// -> if we couldn't drop a new delegate at a spot, emit newDelegateDropAbandoned()
|
|
// -> otherwise, emit delegateDroppedAndPlaced()
|
|
if (!success && (m_startPosition->location() == DelegateDragPosition::WidgetList || m_startPosition->location() == DelegateDragPosition::AppDrawer)) {
|
|
Q_EMIT newDelegateDropAbandoned();
|
|
} else {
|
|
Q_EMIT delegateDroppedAndPlaced();
|
|
}
|
|
}
|
|
|
|
void DragState::onLeaveCurrentFolder()
|
|
{
|
|
if (!m_state) {
|
|
return;
|
|
}
|
|
|
|
// reset timers
|
|
m_folderInsertBetweenTimer->stop();
|
|
m_changeFolderPageTimer->stop();
|
|
m_leaveFolderTimer->stop();
|
|
|
|
if (m_candidateDropPosition->location() == DelegateDragPosition::Folder && m_candidateDropPosition->folder()) {
|
|
// clear ghost entry
|
|
m_candidateDropPosition->folder()->applications()->deleteGhostEntry();
|
|
}
|
|
}
|
|
|
|
void DragState::onChangePageTimerFinished()
|
|
{
|
|
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate)) {
|
|
return;
|
|
}
|
|
|
|
const int leftPagePosition = 0;
|
|
const int rightPagePosition = m_state->pageWidth();
|
|
|
|
PageListModel *pageListModel = m_homeScreen->pageListModel();
|
|
|
|
qreal x = getPointerX();
|
|
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the left edge, go left
|
|
int page = m_state->currentPage() - 1;
|
|
if (page >= 0) {
|
|
m_state->goToPage(page, false);
|
|
}
|
|
|
|
} else if (qAbs(rightPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the right edge, go right
|
|
int page = m_state->currentPage() + 1;
|
|
|
|
// if we are at the right-most page, try to create a new one if the current page isn't empty
|
|
if (page == pageListModel->rowCount() && !pageListModel->isLastPageEmpty()) {
|
|
pageListModel->addPageAtEnd();
|
|
}
|
|
|
|
// go to page if it exists
|
|
if (page < pageListModel->rowCount()) {
|
|
m_state->goToPage(page, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onOpenFolderTimerFinished()
|
|
{
|
|
if (!m_state || m_state->swipeState() != HomeScreenState::DraggingDelegate || m_state->viewState() != HomeScreenState::PageView
|
|
|| (m_candidateDropPosition->location() != DelegateDragPosition::Pages && m_candidateDropPosition->location() != DelegateDragPosition::Favourites)) {
|
|
return;
|
|
}
|
|
|
|
FolioApplicationFolder *folder = nullptr;
|
|
QPointF screenPosition;
|
|
|
|
switch (m_candidateDropPosition->location()) {
|
|
case DelegateDragPosition::Pages: {
|
|
// get current page
|
|
PageModel *page = m_homeScreen->pageListModel()->getPage(m_candidateDropPosition->page());
|
|
if (!page) {
|
|
return;
|
|
}
|
|
|
|
// get delegate being hovered over
|
|
FolioDelegate *delegate = page->getDelegate(m_candidateDropPosition->pageRow(), m_candidateDropPosition->pageColumn());
|
|
if (!delegate || delegate->type() != FolioDelegate::Folder) {
|
|
return;
|
|
}
|
|
|
|
folder = delegate->folder();
|
|
screenPosition =
|
|
m_state->getPageDelegateScreenPosition(m_candidateDropPosition->page(), m_candidateDropPosition->pageRow(), m_candidateDropPosition->pageColumn());
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Favourites: {
|
|
// get delegate being hovered over in favourites bar
|
|
FolioDelegate *delegate = m_homeScreen->favouritesModel()->getEntryAt(m_candidateDropPosition->favouritesPosition());
|
|
if (!delegate || delegate->type() != FolioDelegate::Folder) {
|
|
return;
|
|
}
|
|
|
|
folder = delegate->folder();
|
|
screenPosition = m_homeScreen->homeScreenState()->getFavouritesDelegateScreenPosition(m_candidateDropPosition->favouritesPosition());
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// open the folder
|
|
m_state->openFolder(screenPosition.x(), screenPosition.y(), folder);
|
|
}
|
|
|
|
void DragState::onLeaveFolderTimerFinished()
|
|
{
|
|
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
|
|
return;
|
|
}
|
|
|
|
// check if the drag position is outside of the folder
|
|
if (m_state->currentFolder()->isDropPositionOutside(getPointerX(), getPointerY())) {
|
|
m_state->closeFolder();
|
|
}
|
|
}
|
|
|
|
void DragState::onChangeFolderPageTimerFinished()
|
|
{
|
|
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
|
|
return;
|
|
}
|
|
|
|
auto *folder = m_state->currentFolder();
|
|
qreal x = getPointerX();
|
|
qreal y = getPointerY();
|
|
|
|
// check if the drag position is outside of the folder
|
|
if (folder->isDropPositionOutside(x, y)) {
|
|
return;
|
|
}
|
|
|
|
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
|
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
|
|
|
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the left edge, go left
|
|
int page = m_state->currentFolderPage() - 1;
|
|
if (page >= 0) {
|
|
m_state->goToFolderPage(page);
|
|
}
|
|
|
|
} else if (x >= rightPagePosition - PAGE_CHANGE_THRESHOLD) {
|
|
// if we are at the right edge, go right
|
|
int page = m_state->currentFolderPage() + 1;
|
|
|
|
// go to page if it exists
|
|
if (page < folder->applications()->numTotalPages()) {
|
|
m_state->goToFolderPage(page);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DragState::onFolderInsertBetweenTimerFinished()
|
|
{
|
|
if (!m_state || (m_state->swipeState() != HomeScreenState::DraggingDelegate) || !m_state->currentFolder()) {
|
|
return;
|
|
}
|
|
|
|
auto *folder = m_state->currentFolder();
|
|
|
|
// update the candidate drop position
|
|
m_candidateDropPosition->setFolder(folder);
|
|
m_candidateDropPosition->setFolderPosition(m_folderInsertBetweenIndex);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Folder);
|
|
|
|
// insert it at this position, shifting existing apps to the side
|
|
// TODO the ghost entry may shift the m_folderInsertBetweenIndex, perhaps we should update??
|
|
folder->applications()->setGhostEntry(m_folderInsertBetweenIndex);
|
|
}
|
|
|
|
void DragState::onFavouritesInsertBetweenTimerFinished()
|
|
{
|
|
// update the candidate drop position
|
|
m_candidateDropPosition->setFavouritesPosition(m_favouritesInsertBetweenIndex);
|
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Favourites);
|
|
|
|
// insert it at this position, shifting existing apps to the side
|
|
m_homeScreen->favouritesModel()->setGhostEntry(m_favouritesInsertBetweenIndex);
|
|
}
|
|
|
|
void DragState::deleteStartPositionDelegate()
|
|
{
|
|
// delete the delegate at the start position
|
|
switch (m_startPosition->location()) {
|
|
case DelegateDragPosition::Pages: {
|
|
PageModel *page = m_homeScreen->pageListModel()->getPage(m_startPosition->page());
|
|
if (page) {
|
|
page->removeDelegate(m_startPosition->pageRow(), m_startPosition->pageColumn());
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Favourites:
|
|
m_homeScreen->favouritesModel()->removeEntry(m_startPosition->favouritesPosition());
|
|
break;
|
|
case DelegateDragPosition::Folder:
|
|
m_startPosition->folder()->removeDelegate(m_startPosition->folderPosition());
|
|
break;
|
|
case DelegateDragPosition::AppDrawer:
|
|
case DelegateDragPosition::WidgetList:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool DragState::createDropPositionDelegate()
|
|
{
|
|
if (!m_dropDelegate) {
|
|
return false;
|
|
}
|
|
|
|
// whether the drop goes successfully
|
|
bool added = false;
|
|
|
|
// creates the delegate at the drop position
|
|
switch (m_candidateDropPosition->location()) {
|
|
case DelegateDragPosition::Pages: {
|
|
// locate the page we are dropping on
|
|
PageModel *page = m_homeScreen->pageListModel()->getPage(m_candidateDropPosition->page());
|
|
if (!page) {
|
|
break;
|
|
}
|
|
|
|
int row = m_candidateDropPosition->pageRow();
|
|
int column = m_candidateDropPosition->pageColumn();
|
|
|
|
// delegate to add
|
|
FolioPageDelegate *delegate = new FolioPageDelegate{row, column, m_dropDelegate, m_homeScreen};
|
|
|
|
// delegate that exists at the drop position
|
|
FolioPageDelegate *existingDelegate = page->getDelegate(row, column);
|
|
|
|
// if a delegate already exists at the spot, check if we can insert/create a folder
|
|
if (existingDelegate) {
|
|
if (delegate->type() == FolioDelegate::Application) {
|
|
if (existingDelegate->type() == FolioDelegate::Folder) {
|
|
// add the app to the existing folder
|
|
|
|
auto existingFolder = existingDelegate->folder();
|
|
existingFolder->addDelegate(delegate, existingFolder->applications()->rowCount());
|
|
|
|
added = true;
|
|
break;
|
|
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
|
// create a folder from the two apps
|
|
|
|
FolioApplicationFolder *folder = new FolioApplicationFolder(m_homeScreen, DEFAULT_FOLDER_NAME);
|
|
folder->addDelegate(delegate, 0);
|
|
folder->addDelegate(existingDelegate, 0);
|
|
FolioPageDelegate *folderDelegate = new FolioPageDelegate{row, column, folder, m_homeScreen};
|
|
|
|
page->removeDelegate(row, column);
|
|
page->addDelegate(folderDelegate);
|
|
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// default behavior for widgets, folders or dropping an app at an empty spot
|
|
|
|
added = page->addDelegate(delegate);
|
|
|
|
// if we couldn't add the delegate, try again but at the start position (return to start)
|
|
if (!added && !isStartPositionEqualDropPosition()) {
|
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
|
added = createDropPositionDelegate();
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Favourites: {
|
|
// delegate that exists at the drop position
|
|
FolioDelegate *existingDelegate = m_homeScreen->favouritesModel()->getEntryAt(m_candidateDropPosition->favouritesPosition());
|
|
|
|
// if a delegate already exists at the spot, check if we can insert/create a folder
|
|
if (existingDelegate) {
|
|
if (m_dropDelegate->type() == FolioDelegate::Application) {
|
|
if (existingDelegate->type() == FolioDelegate::Folder) {
|
|
// add the app to the existing folder
|
|
|
|
auto existingFolder = existingDelegate->folder();
|
|
existingFolder->addDelegate(m_dropDelegate, existingFolder->applications()->rowCount());
|
|
|
|
added = true;
|
|
break;
|
|
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
|
// create a folder from the two apps
|
|
|
|
FolioApplicationFolder *folder = new FolioApplicationFolder(m_homeScreen, DEFAULT_FOLDER_NAME);
|
|
folder->addDelegate(m_dropDelegate, 0);
|
|
folder->addDelegate(existingDelegate, 0);
|
|
FolioDelegate *folderDelegate = new FolioDelegate{folder, m_homeScreen};
|
|
|
|
m_homeScreen->favouritesModel()->removeEntry(m_candidateDropPosition->favouritesPosition());
|
|
m_homeScreen->favouritesModel()->addEntry(m_candidateDropPosition->favouritesPosition(), folderDelegate);
|
|
|
|
added = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// otherwise, just add the delegate at this position
|
|
|
|
added = m_homeScreen->favouritesModel()->addEntry(m_candidateDropPosition->favouritesPosition(), m_dropDelegate);
|
|
|
|
// if we couldn't add the delegate, try again but at the start position
|
|
if (!added && !isStartPositionEqualDropPosition()) {
|
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
|
added = createDropPositionDelegate();
|
|
}
|
|
|
|
// correct position when we delete from an entry earlier in the favourites
|
|
if (added) {
|
|
if (m_startPosition->location() == DelegateDragPosition::Favourites
|
|
&& m_startPosition->favouritesPosition() > m_candidateDropPosition->favouritesPosition()) {
|
|
m_startPosition->setFavouritesPosition(m_startPosition->favouritesPosition() - 1);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::Folder: {
|
|
auto *folder = m_candidateDropPosition->folder();
|
|
if (!folder) {
|
|
break;
|
|
}
|
|
|
|
// only support dropping apps into folders
|
|
if (m_dropDelegate->type() != FolioDelegate::Application) {
|
|
break;
|
|
}
|
|
|
|
added = folder->addDelegate(m_dropDelegate, m_candidateDropPosition->folderPosition());
|
|
|
|
// if we couldn't add the delegate, try again but at the start position
|
|
if (!added && !isStartPositionEqualDropPosition()) {
|
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
|
added = createDropPositionDelegate();
|
|
}
|
|
|
|
if (added) {
|
|
folder->applications()->deleteGhostEntry();
|
|
}
|
|
break;
|
|
}
|
|
case DelegateDragPosition::AppDrawer:
|
|
case DelegateDragPosition::WidgetList:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// if we are dropping a new widget, we need to now create the applet in the containment
|
|
if (added && m_startPosition->location() == DelegateDragPosition::WidgetList && m_dropDelegate->type() == FolioDelegate::Widget && m_homeScreen) {
|
|
Plasma::Applet *applet = m_homeScreen->createApplet(m_createdAppletPluginId);
|
|
|
|
// associate the new delegate with the Plasma::Applet
|
|
m_dropDelegate->widget()->setApplet(applet);
|
|
}
|
|
|
|
return added;
|
|
}
|
|
|
|
bool DragState::isStartPositionEqualDropPosition()
|
|
{
|
|
return m_startPosition->location() == m_candidateDropPosition->location() && m_startPosition->page() == m_candidateDropPosition->page()
|
|
&& m_startPosition->pageRow() == m_candidateDropPosition->pageRow() && m_startPosition->pageColumn() == m_candidateDropPosition->pageColumn()
|
|
&& m_startPosition->favouritesPosition() == m_candidateDropPosition->favouritesPosition()
|
|
&& m_startPosition->folder() == m_candidateDropPosition->folder() && m_startPosition->folderPosition() == m_candidateDropPosition->folderPosition();
|
|
}
|
|
|
|
qreal DragState::getDraggedDelegateX()
|
|
{
|
|
// adjust to get the position of the center of the delegate
|
|
return m_state->delegateDragX() + m_state->pageCellWidth() / 2;
|
|
}
|
|
|
|
qreal DragState::getDraggedDelegateY()
|
|
{
|
|
// adjust to get the position of the center of the delegate
|
|
return m_state->delegateDragY() + m_state->pageCellHeight() / 2;
|
|
}
|
|
|
|
qreal DragState::getPointerX()
|
|
{
|
|
return m_state->delegateDragX() + m_state->delegateDragPointerOffsetX();
|
|
}
|
|
|
|
qreal DragState::getPointerY()
|
|
{
|
|
return m_state->delegateDragY() + m_state->delegateDragPointerOffsetY();
|
|
}
|