mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-26 14:23:09 +00:00
homescreens/folio: Add widgets support
This commit is contained in:
parent
930f26a23e
commit
4f48f127a4
47 changed files with 2560 additions and 272 deletions
|
|
@ -11,10 +11,13 @@ set(homescreen_SRCS
|
|||
folioapplicationfolder.cpp
|
||||
foliodelegate.cpp
|
||||
foliosettings.cpp
|
||||
foliowidget.cpp
|
||||
pagemodel.cpp
|
||||
pagelistmodel.cpp
|
||||
delegatetoucharea.cpp
|
||||
dragstate.cpp
|
||||
widgetcontainer.cpp
|
||||
widgetsmanager.cpp
|
||||
)
|
||||
|
||||
add_library(org.kde.plasma.mobile.homescreen.folio MODULE ${homescreen_SRCS})
|
||||
|
|
|
|||
|
|
@ -16,11 +16,18 @@ As such, all of the positioning and placement of delegates on the screen are top
|
|||
#### TODO
|
||||
- Add folio/halcyon switcher in initial-start
|
||||
- If an app gets uninstalled, the homescreen UI needs to ensure that delegates are updated
|
||||
- BUG: the position of where things think the dragged icon is during drag-and-drop is slightly off because of the label
|
||||
- BUG: landscape favourites bar duplication when dragging icon from it sometimes
|
||||
- BUG: can't insert delegates in-between very well in landscape favourites bar
|
||||
- can make the touch area only the icon?
|
||||
- FEATURE: add import/export
|
||||
- FEATURE: add widget import/export
|
||||
- FEATURE: keyboard navigation
|
||||
- FEATURE: touchpad navigation
|
||||
- BUG: it's possible to get stuck in an unswipeable state after swiping down from the app drawer
|
||||
- FEATURE: option to darken wallpaper
|
||||
- FEATURE: option to turn off row/column swap
|
||||
- BUG: drag and drop animation when rejected on a different page
|
||||
|
||||
- FEATURE: animate homescreen config opening
|
||||
|
||||
- RESTORE app drawer overshoot
|
||||
|
||||
- PERFORMANCE: ensure that the widget config overlays are in loaders
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include "delegatetoucharea.h"
|
||||
|
||||
#include <QCursor>
|
||||
#include <QGuiApplication>
|
||||
#include <QStyleHints>
|
||||
|
||||
// Some code taken from MouseArea
|
||||
|
||||
|
|
@ -12,7 +14,6 @@ DelegateTouchArea::DelegateTouchArea(QQuickItem *parent)
|
|||
: QQuickItem{parent}
|
||||
, m_pressAndHoldTimer{new QTimer{this}}
|
||||
{
|
||||
// TODO: currently hardcoded 2s press and hold interval
|
||||
m_pressAndHoldTimer->setInterval(600);
|
||||
m_pressAndHoldTimer->setSingleShot(true);
|
||||
connect(m_pressAndHoldTimer, &QTimer::timeout, this, &DelegateTouchArea::startPressAndHold);
|
||||
|
|
@ -24,7 +25,7 @@ DelegateTouchArea::DelegateTouchArea(QQuickItem *parent)
|
|||
|
||||
setAcceptHoverEvents(true);
|
||||
setAcceptTouchEvents(true);
|
||||
// setFiltersChildMouseEvents(true);
|
||||
setFlags(QQuickItem::ItemIsFocusScope);
|
||||
setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
|
||||
}
|
||||
|
||||
|
|
@ -54,19 +55,6 @@ void DelegateTouchArea::setHovered(bool hovered)
|
|||
}
|
||||
}
|
||||
|
||||
bool DelegateTouchArea::dragging()
|
||||
{
|
||||
return m_dragging;
|
||||
}
|
||||
|
||||
void DelegateTouchArea::setDragging(bool dragging)
|
||||
{
|
||||
if (dragging != m_dragging) {
|
||||
m_dragging = dragging;
|
||||
Q_EMIT draggingChanged(dragging);
|
||||
}
|
||||
}
|
||||
|
||||
Qt::CursorShape DelegateTouchArea::cursorShape()
|
||||
{
|
||||
return cursor().shape();
|
||||
|
|
@ -87,6 +75,11 @@ void DelegateTouchArea::unsetCursor()
|
|||
setCursorShape(Qt::ArrowCursor);
|
||||
}
|
||||
|
||||
QPointF DelegateTouchArea::pressPosition()
|
||||
{
|
||||
return m_mouseDownPosition;
|
||||
}
|
||||
|
||||
void DelegateTouchArea::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() & Qt::RightButton) {
|
||||
|
|
@ -178,86 +171,6 @@ void DelegateTouchArea::hoverLeaveEvent(QHoverEvent *event)
|
|||
event->ignore();
|
||||
}
|
||||
|
||||
// bool DelegateTouchArea::childMouseEventFilter(QQuickItem *item, QEvent *event)
|
||||
// {
|
||||
// if (!isVisible() || !isEnabled()) {
|
||||
// handleReleaseEvent(nullptr, false);
|
||||
// return QQuickItem::childMouseEventFilter(item, event);
|
||||
// }
|
||||
//
|
||||
// if (event->isPointerEvent() && event->type() != QEvent::UngrabMouse) {
|
||||
// return filterPointerEvent(item, static_cast<QPointerEvent *>(event));
|
||||
// }
|
||||
//
|
||||
// return QQuickItem::childMouseEventFilter(item, event);
|
||||
// }
|
||||
//
|
||||
// // take exclusive grab from children
|
||||
// bool DelegateTouchArea::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event)
|
||||
// {
|
||||
// // only filter mouse, touch or tablet events
|
||||
// if (!dynamic_cast<QMouseEvent *>(event) && !dynamic_cast<QTabletEvent *>(event) && !dynamic_cast<QTouchEvent *>(event)) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// const auto &firstPoint = event->points().first();
|
||||
//
|
||||
// if (event->pointCount() == 1 && event->exclusiveGrabber(firstPoint) == this) {
|
||||
// // We have an exclusive grab (since we're e.g dragging), but at the same time, we have
|
||||
// // a child with a passive grab (which is why this filter is being called). And because
|
||||
// // of that, we end up getting the same pointer events twice; First in our own event
|
||||
// // handlers (because of the grab), then once more in here, since we filter the child.
|
||||
// // To avoid processing the event twice (e.g avoid calling handleReleaseEvent once more
|
||||
// // from below), we mark the event as filtered, and simply return.
|
||||
// event->setAccepted(true);
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// QPointF localPos = mapFromScene(firstPoint.scenePosition());
|
||||
// bool receiverDisabled = receiver && !receiver->isEnabled();
|
||||
// bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab());
|
||||
//
|
||||
// if ((m_pressAndHeld || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) {
|
||||
// // clone the event, and set the first point's local position
|
||||
// // HACK: we can't change QPointerEvent's points since it's const, so we have to pass localPos into the handlers
|
||||
// QPointerEvent *localizedEvent = event->clone();
|
||||
// localizedEvent->setAccepted(false);
|
||||
//
|
||||
// switch (firstPoint.state()) {
|
||||
// case QEventPoint::State::Updated:
|
||||
// handleMoveEvent(localizedEvent, localPos);
|
||||
// break;
|
||||
// case QEventPoint::State::Pressed:
|
||||
// handlePressEvent(localizedEvent, localPos);
|
||||
// break;
|
||||
// case QEventPoint::State::Released:
|
||||
// handleReleaseEvent(localizedEvent, true);
|
||||
// break;
|
||||
// case QEventPoint::State::Stationary:
|
||||
// case QEventPoint::State::Unknown:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if ((receiver && m_pressAndHeld && !receiverKeepsGrab && receiver != this) || receiverDisabled) {
|
||||
// event->setExclusiveGrabber(firstPoint, this);
|
||||
// }
|
||||
//
|
||||
// bool filtered = m_pressAndHeld || receiverDisabled;
|
||||
// if (filtered) {
|
||||
// event->setAccepted(true);
|
||||
// }
|
||||
//
|
||||
// return filtered;
|
||||
// }
|
||||
//
|
||||
// if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) {
|
||||
// // mouse released, or another item has claimed the grab
|
||||
// handleReleaseEvent(nullptr, false);
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
void DelegateTouchArea::handlePressEvent(QPointerEvent *event, QPointF point)
|
||||
{
|
||||
// ignore multiple press events
|
||||
|
|
@ -266,6 +179,11 @@ void DelegateTouchArea::handlePressEvent(QPointerEvent *event, QPointF point)
|
|||
}
|
||||
|
||||
setPressed(true);
|
||||
forceActiveFocus(Qt::MouseFocusReason);
|
||||
|
||||
m_mouseDownPosition = point;
|
||||
Q_EMIT pressPositionChanged();
|
||||
|
||||
m_pressAndHoldTimer->start();
|
||||
}
|
||||
|
||||
|
|
@ -273,10 +191,10 @@ void DelegateTouchArea::handleReleaseEvent(QPointerEvent *event, bool click)
|
|||
{
|
||||
// NOTE: event can be nullptr!
|
||||
|
||||
bool wasPressed = m_pressed;
|
||||
setPressed(false);
|
||||
setDragging(false);
|
||||
|
||||
if (!m_pressAndHeld && click) {
|
||||
if (!m_pressAndHeld && click && wasPressed) {
|
||||
Q_EMIT clicked();
|
||||
}
|
||||
|
||||
|
|
@ -290,8 +208,9 @@ void DelegateTouchArea::handleReleaseEvent(QPointerEvent *event, bool click)
|
|||
|
||||
void DelegateTouchArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
||||
{
|
||||
if (m_pressAndHeld) {
|
||||
// TODO
|
||||
if (QPointF(point - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
|
||||
m_pressAndHoldTimer->stop();
|
||||
setPressed(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ class DelegateTouchArea : public QQuickItem
|
|||
|
||||
Q_PROPERTY(bool pressed READ pressed NOTIFY pressedChanged FINAL)
|
||||
Q_PROPERTY(bool hovered READ hovered NOTIFY hoveredChanged FINAL)
|
||||
Q_PROPERTY(bool dragging READ dragging NOTIFY draggingChanged FINAL)
|
||||
Q_PROPERTY(Qt::CursorShape cursorShape READ cursorShape WRITE setCursorShape RESET unsetCursor NOTIFY cursorShapeChanged FINAL)
|
||||
Q_PROPERTY(QPointF pressPosition READ pressPosition NOTIFY pressPositionChanged FINAL)
|
||||
|
||||
QML_NAMED_ELEMENT(DelegateTouchArea)
|
||||
|
||||
|
|
@ -29,21 +29,20 @@ public:
|
|||
|
||||
bool pressed();
|
||||
bool hovered();
|
||||
bool dragging();
|
||||
Qt::CursorShape cursorShape();
|
||||
void setCursorShape(Qt::CursorShape cursorShape);
|
||||
void unsetCursor();
|
||||
QPointF pressPosition();
|
||||
|
||||
Q_SIGNALS:
|
||||
void clicked();
|
||||
void rightMousePress();
|
||||
void pressAndHold();
|
||||
void pressAndHoldReleased();
|
||||
void drag(qreal x, qreal y);
|
||||
void pressedChanged(bool pressed);
|
||||
void hoveredChanged(bool hovered);
|
||||
void draggingChanged(bool dragging);
|
||||
void cursorShapeChanged();
|
||||
void pressPositionChanged();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
|
|
@ -54,13 +53,11 @@ protected:
|
|||
void touchUngrabEvent() override;
|
||||
void hoverEnterEvent(QHoverEvent *event) override;
|
||||
void hoverLeaveEvent(QHoverEvent *event) override;
|
||||
// bool childMouseEventFilter(QQuickItem *i, QEvent *e) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void startPressAndHold();
|
||||
|
||||
private:
|
||||
// bool filterPointerEvent(QQuickItem *receiver, QPointerEvent *event);
|
||||
void setPressed(bool pressed);
|
||||
void setHovered(bool hovered);
|
||||
void setDragging(bool dragging);
|
||||
|
|
@ -71,9 +68,9 @@ private:
|
|||
|
||||
bool m_pressed{false};
|
||||
bool m_hovered{false};
|
||||
bool m_dragging{false};
|
||||
bool m_pressAndHeld{false};
|
||||
Qt::CursorShape m_cursorShape{Qt::ArrowCursor};
|
||||
QPointF m_mouseDownPosition{};
|
||||
|
||||
QTimer *m_pressAndHoldTimer{nullptr};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ DragState::DragState(HomeScreenState *state, QObject *parent)
|
|||
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 (HomeScreenState::self()->swipeState() == HomeScreenState::DraggingDelegate) {
|
||||
onDelegateDraggingStarted();
|
||||
|
|
@ -214,8 +215,8 @@ void DragState::onDelegateDragPositionChanged()
|
|||
|
||||
// we want to update the candidate drop position variable in this function!
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
qreal x = getPointerX();
|
||||
qreal y = getPointerY();
|
||||
|
||||
bool inFolder = m_state->viewState() == HomeScreenState::FolderView;
|
||||
bool inFavouritesArea = !inFolder;
|
||||
|
|
@ -258,8 +259,8 @@ void DragState::onDelegateDragPositionChanged()
|
|||
void DragState::onDelegateDragPositionOverFolderViewChanged()
|
||||
{
|
||||
// if the drag position changes while in the folder view
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
qreal x = getPointerX();
|
||||
qreal y = getPointerY();
|
||||
|
||||
auto *folder = m_state->currentFolder();
|
||||
if (!folder) {
|
||||
|
|
@ -311,8 +312,8 @@ void DragState::onDelegateDragPositionOverFavouritesChanged()
|
|||
{
|
||||
// the drag position changed while over the favourites strip
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
qreal x = getPointerX();
|
||||
qreal y = getPointerY();
|
||||
int dropIndex = FavouritesModel::self()->dropInsertPosition(x, y);
|
||||
|
||||
// if the drop position changed, cancel the open folder timer
|
||||
|
|
@ -327,6 +328,11 @@ void DragState::onDelegateDragPositionOverFavouritesChanged()
|
|||
m_favouritesInsertBetweenTimer->stop();
|
||||
}
|
||||
|
||||
// ignore widget drop delegates (since they can't be placed in the favourites)
|
||||
if (m_dropDelegate && m_dropDelegate->type() == FolioDelegate::Widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (FavouritesModel::self()->dropPositionIsEdge(x, y)) {
|
||||
// if we need to make space for the delegate
|
||||
|
||||
|
|
@ -369,16 +375,28 @@ void DragState::onDelegateDragPositionOverPageViewChanged()
|
|||
{
|
||||
// the drag position changed while over the homescreen pages strip
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
qreal y = getDraggedDelegateY();
|
||||
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 = (y - pageVerticalMargin) / m_state->pageCellHeight();
|
||||
int column = (x - pageHorizontalMargin) / m_state->pageCellWidth();
|
||||
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));
|
||||
|
|
@ -488,6 +506,17 @@ void DragState::onDelegateDragFromFolderStarted(FolioApplicationFolder *folder,
|
|||
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{this, -1, 1, 1};
|
||||
setDropDelegate(new FolioDelegate{widget, this});
|
||||
|
||||
// set start location
|
||||
m_startPosition->setLocation(DelegateDragPosition::WidgetList);
|
||||
}
|
||||
|
||||
void DragState::onDelegateDropped()
|
||||
{
|
||||
if (!m_dropDelegate) {
|
||||
|
|
@ -495,7 +524,7 @@ void DragState::onDelegateDropped()
|
|||
}
|
||||
|
||||
// add dropped delegate
|
||||
createDropPositionDelegate();
|
||||
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)
|
||||
|
|
@ -513,8 +542,14 @@ void DragState::onDelegateDropped()
|
|||
m_changePageTimer->stop();
|
||||
m_favouritesInsertBetweenTimer->stop();
|
||||
|
||||
// emit signal
|
||||
Q_EMIT delegateDroppedAndPlaced();
|
||||
// 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()
|
||||
|
|
@ -543,7 +578,7 @@ void DragState::onChangePageTimerFinished()
|
|||
const int leftPagePosition = 0;
|
||||
const int rightPagePosition = m_state->pageWidth();
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
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;
|
||||
|
|
@ -623,7 +658,7 @@ void DragState::onLeaveFolderTimerFinished()
|
|||
}
|
||||
|
||||
// check if the drag position is outside of the folder
|
||||
if (m_state->currentFolder()->isDropPositionOutside(getDraggedDelegateX(), getDraggedDelegateY())) {
|
||||
if (m_state->currentFolder()->isDropPositionOutside(getPointerX(), getPointerY())) {
|
||||
m_state->closeFolder();
|
||||
}
|
||||
}
|
||||
|
|
@ -635,16 +670,17 @@ void DragState::onChangeFolderPageTimerFinished()
|
|||
}
|
||||
|
||||
auto *folder = m_state->currentFolder();
|
||||
qreal x = getPointerX();
|
||||
qreal y = getPointerY();
|
||||
|
||||
// check if the drag position is outside of the folder
|
||||
if (folder->isDropPositionOutside(getDraggedDelegateX(), getDraggedDelegateY())) {
|
||||
if (folder->isDropPositionOutside(x, y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
||||
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
||||
|
||||
qreal x = getDraggedDelegateX();
|
||||
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD) {
|
||||
// if we are at the left edge, go left
|
||||
int page = m_state->currentFolderPage() - 1;
|
||||
|
|
@ -656,12 +692,6 @@ void DragState::onChangeFolderPageTimerFinished()
|
|||
// if we are at the right edge, go right
|
||||
int page = m_state->currentFolderPage() + 1;
|
||||
|
||||
// TODO!!!!
|
||||
// if we are at the right-most page, try to create a new one if the current page isn't empty
|
||||
// if (page == folder->applications()->rowCount() && !PageListModel::self()->isLastPageEmpty()) {
|
||||
// PageListModel::self()->addPageAtEnd();
|
||||
// }
|
||||
|
||||
// go to page if it exists
|
||||
if (page < folder->applications()->numTotalPages()) {
|
||||
m_state->goToFolderPage(page);
|
||||
|
|
@ -683,7 +713,7 @@ void DragState::onFolderInsertBetweenTimerFinished()
|
|||
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, we should update??
|
||||
// TODO the ghost entry may shift the m_folderInsertBetweenIndex, perhaps we should update??
|
||||
folder->applications()->setGhostEntry(m_folderInsertBetweenIndex);
|
||||
}
|
||||
|
||||
|
|
@ -715,17 +745,21 @@ void DragState::deleteStartPositionDelegate()
|
|||
m_startPosition->folder()->removeDelegate(m_startPosition->folderPosition());
|
||||
break;
|
||||
case DelegateDragPosition::AppDrawer:
|
||||
case DelegateDragPosition::WidgetList:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DragState::createDropPositionDelegate()
|
||||
bool DragState::createDropPositionDelegate()
|
||||
{
|
||||
if (!m_dropDelegate) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// whether the drop goes successfully
|
||||
bool added = false;
|
||||
|
||||
// creates the delegate at the drop position
|
||||
switch (m_candidateDropPosition->location()) {
|
||||
case DelegateDragPosition::Pages: {
|
||||
|
|
@ -753,6 +787,7 @@ void DragState::createDropPositionDelegate()
|
|||
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
|
||||
|
|
@ -765,19 +800,20 @@ void DragState::createDropPositionDelegate()
|
|||
page->removeDelegate(row, column);
|
||||
page->addDelegate(folderDelegate);
|
||||
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default behavior for folders or dropping an app at an empty spot
|
||||
// default behavior for widgets, folders or dropping an app at an empty spot
|
||||
|
||||
bool added = page->addDelegate(delegate);
|
||||
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);
|
||||
createDropPositionDelegate();
|
||||
added = createDropPositionDelegate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -794,6 +830,7 @@ void DragState::createDropPositionDelegate()
|
|||
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
|
||||
|
|
@ -806,6 +843,7 @@ void DragState::createDropPositionDelegate()
|
|||
FavouritesModel::self()->removeEntry(m_candidateDropPosition->favouritesPosition());
|
||||
FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), folderDelegate);
|
||||
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -813,12 +851,12 @@ void DragState::createDropPositionDelegate()
|
|||
|
||||
// otherwise, just add the delegate at this position
|
||||
|
||||
bool added = FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), m_dropDelegate);
|
||||
added = FavouritesModel::self()->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);
|
||||
createDropPositionDelegate();
|
||||
added = createDropPositionDelegate();
|
||||
}
|
||||
|
||||
// correct position when we delete from an entry earlier in the favourites
|
||||
|
|
@ -833,33 +871,42 @@ void DragState::createDropPositionDelegate()
|
|||
case DelegateDragPosition::Folder: {
|
||||
auto *folder = m_candidateDropPosition->folder();
|
||||
if (!folder) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
// only support dropping apps into folders
|
||||
if (m_dropDelegate->type() != FolioDelegate::Application) {
|
||||
return;
|
||||
break;
|
||||
}
|
||||
|
||||
bool added = folder->addDelegate(m_dropDelegate, m_candidateDropPosition->folderPosition());
|
||||
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);
|
||||
createDropPositionDelegate();
|
||||
added = createDropPositionDelegate();
|
||||
}
|
||||
|
||||
if (added) {
|
||||
folder->applications()->deleteGhostEntry();
|
||||
|
||||
// TODO correct m_startPosition?
|
||||
}
|
||||
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_state->containment()) {
|
||||
Plasma::Applet *applet = m_state->containment()->createApplet(m_createdAppletPluginId);
|
||||
|
||||
// associate the new delegate with the Plasma::Applet
|
||||
m_dropDelegate->widget()->setApplet(applet);
|
||||
}
|
||||
|
||||
return added;
|
||||
}
|
||||
|
||||
bool DragState::isStartPositionEqualDropPosition()
|
||||
|
|
@ -881,3 +928,13 @@ 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class DelegateDragPosition : public QObject
|
|||
Q_PROPERTY(FolioApplicationFolder *folder READ folder NOTIFY folderChanged)
|
||||
|
||||
public:
|
||||
enum Location { Pages, Favourites, AppDrawer, Folder };
|
||||
enum Location { Pages, Favourites, AppDrawer, Folder, WidgetList };
|
||||
Q_ENUM(Location)
|
||||
|
||||
DelegateDragPosition(QObject *parent = nullptr);
|
||||
|
|
@ -94,6 +94,9 @@ Q_SIGNALS:
|
|||
void dropDelegateChanged();
|
||||
void delegateDroppedAndPlaced();
|
||||
|
||||
// if you drop a new delegate on an invalid spot
|
||||
void newDelegateDropAbandoned();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onDelegateDragPositionChanged();
|
||||
void onDelegateDragPositionOverFolderViewChanged();
|
||||
|
|
@ -105,6 +108,7 @@ private Q_SLOTS:
|
|||
void onDelegateDragFromFavouritesStarted(int position);
|
||||
void onDelegateDragFromAppDrawerStarted(QString storageId);
|
||||
void onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
||||
void onDelegateDragFromWidgetListStarted(QString appletPluginId);
|
||||
void onDelegateDropped();
|
||||
|
||||
void onLeaveCurrentFolder();
|
||||
|
|
@ -120,8 +124,8 @@ private:
|
|||
// deletes the delegate at m_startPosition
|
||||
void deleteStartPositionDelegate();
|
||||
|
||||
// deletes the delegate at m_candidateDropPosition
|
||||
void createDropPositionDelegate();
|
||||
// places the delegate at m_candidateDropPosition, returning whether it was successful
|
||||
bool createDropPositionDelegate();
|
||||
|
||||
// whether m_startPosition = m_candidateDropPosition
|
||||
bool isStartPositionEqualDropPosition();
|
||||
|
|
@ -130,6 +134,10 @@ private:
|
|||
qreal getDraggedDelegateX();
|
||||
qreal getDraggedDelegateY();
|
||||
|
||||
// position of the dragging pointer
|
||||
qreal getPointerX();
|
||||
qreal getPointerY();
|
||||
|
||||
QTimer *m_changePageTimer{nullptr};
|
||||
QTimer *m_openFolderTimer{nullptr};
|
||||
QTimer *m_leaveFolderTimer{nullptr};
|
||||
|
|
@ -152,5 +160,8 @@ private:
|
|||
// this is the original start position of the drag
|
||||
DelegateDragPosition *const m_startPosition{nullptr};
|
||||
|
||||
// when dropping a new widget, this is the applet name
|
||||
QString m_createdAppletPluginId{};
|
||||
|
||||
HomeScreenState *m_state{nullptr};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ void FavouritesModel::moveEntry(int fromRow, int toRow)
|
|||
save();
|
||||
}
|
||||
|
||||
bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
||||
bool FavouritesModel::canAddEntry(int row, FolioDelegate *delegate)
|
||||
{
|
||||
if (!delegate) {
|
||||
return false;
|
||||
|
|
@ -129,6 +129,15 @@ bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
|||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
||||
{
|
||||
if (!canAddEntry(row, delegate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (row == m_delegates.size()) {
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
m_delegates.append({delegate, 0});
|
||||
|
|
@ -143,6 +152,9 @@ bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
|||
endInsertRows();
|
||||
}
|
||||
|
||||
// ensure saves are connected when requested by the delegate
|
||||
connectSaveRequests(delegate);
|
||||
|
||||
evaluateDelegatePositions();
|
||||
|
||||
save();
|
||||
|
|
@ -230,24 +242,24 @@ QJsonArray FavouritesModel::exportToJson()
|
|||
|
||||
void FavouritesModel::save()
|
||||
{
|
||||
if (!m_applet) {
|
||||
if (!m_containment) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray arr = exportToJson();
|
||||
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
||||
|
||||
m_applet->config().writeEntry("Favourites", QString::fromStdString(data.toStdString()));
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
m_containment->config().writeEntry("Favourites", QString::fromStdString(data.toStdString()));
|
||||
Q_EMIT m_containment->configNeedsSaving();
|
||||
}
|
||||
|
||||
void FavouritesModel::load()
|
||||
{
|
||||
if (!m_applet) {
|
||||
if (!m_containment) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Favourites", "{}").toUtf8());
|
||||
QJsonDocument doc = QJsonDocument::fromJson(m_containment->config().readEntry("Favourites", "{}").toUtf8());
|
||||
loadFromJson(doc.array());
|
||||
}
|
||||
|
||||
|
|
@ -262,10 +274,7 @@ void FavouritesModel::loadFromJson(QJsonArray arr)
|
|||
FolioDelegate *delegate = FolioDelegate::fromJson(obj, this);
|
||||
|
||||
if (delegate) {
|
||||
if (delegate->type() == FolioDelegate::Folder) {
|
||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, this, &FavouritesModel::save);
|
||||
}
|
||||
|
||||
connectSaveRequests(delegate);
|
||||
m_delegates.append({delegate, 0});
|
||||
}
|
||||
}
|
||||
|
|
@ -274,9 +283,16 @@ void FavouritesModel::loadFromJson(QJsonArray arr)
|
|||
endResetModel();
|
||||
}
|
||||
|
||||
void FavouritesModel::setApplet(Plasma::Applet *applet)
|
||||
void FavouritesModel::connectSaveRequests(FolioDelegate *delegate)
|
||||
{
|
||||
m_applet = applet;
|
||||
if (delegate->type() == FolioDelegate::Folder && delegate->folder()) {
|
||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, this, &FavouritesModel::save);
|
||||
}
|
||||
}
|
||||
|
||||
void FavouritesModel::setContainment(Plasma::Containment *containment)
|
||||
{
|
||||
m_containment = containment;
|
||||
}
|
||||
|
||||
bool FavouritesModel::dropPositionIsEdge(qreal x, qreal y) const
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include <QQuickItem>
|
||||
#include <QSet>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
#include <Plasma/Containment>
|
||||
|
||||
#include "foliodelegate.h"
|
||||
|
||||
|
|
@ -39,6 +39,7 @@ public:
|
|||
|
||||
Q_INVOKABLE void removeEntry(int row);
|
||||
void moveEntry(int fromRow, int toRow);
|
||||
bool canAddEntry(int row, FolioDelegate *delegate);
|
||||
bool addEntry(int row, FolioDelegate *delegate);
|
||||
FolioDelegate *getEntryAt(int row);
|
||||
|
||||
|
|
@ -64,9 +65,10 @@ public:
|
|||
Q_INVOKABLE void load();
|
||||
void loadFromJson(QJsonArray arr);
|
||||
|
||||
void setApplet(Plasma::Applet *applet);
|
||||
void setContainment(Plasma::Containment *containment);
|
||||
|
||||
private:
|
||||
void connectSaveRequests(FolioDelegate *delegate);
|
||||
void evaluateDelegatePositions(bool emitSignal = true);
|
||||
|
||||
// get the x (or y) position where delegates start being placed
|
||||
|
|
@ -78,5 +80,5 @@ private:
|
|||
|
||||
QList<FavouritesDelegate> m_delegates;
|
||||
|
||||
Plasma::Applet *m_applet{nullptr};
|
||||
Plasma::Containment *m_containment{nullptr};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ FolioApplication *FolioApplication::fromJson(QJsonObject &obj, QObject *parent)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonObject FolioApplication::toJson()
|
||||
QJsonObject FolioApplication::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("type")] = "application";
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ public:
|
|||
FolioApplication(QObject *parent = nullptr, KService::Ptr service = QExplicitlySharedDataPointer<KService>{nullptr});
|
||||
|
||||
static FolioApplication *fromJson(QJsonObject &obj, QObject *parent); // may return nullptr
|
||||
QJsonObject toJson();
|
||||
QJsonObject toJson() const;
|
||||
|
||||
bool running() const;
|
||||
QString name() const;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ FolioApplicationFolder *FolioApplicationFolder::fromJson(QJsonObject &obj, QObje
|
|||
return folder;
|
||||
}
|
||||
|
||||
QJsonObject FolioApplicationFolder::toJson()
|
||||
QJsonObject FolioApplicationFolder::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("type")] = "folder";
|
||||
|
|
@ -213,7 +213,7 @@ void ApplicationFolderModel::moveEntry(int fromRow, int toRow)
|
|||
Q_EMIT m_folder->saveRequested();
|
||||
}
|
||||
|
||||
bool ApplicationFolderModel::addDelegate(FolioDelegate *delegate, int index)
|
||||
bool ApplicationFolderModel::canAddDelegate(FolioDelegate *delegate, int index)
|
||||
{
|
||||
if (index < 0 || index > m_folder->m_delegates.size()) {
|
||||
return false;
|
||||
|
|
@ -223,6 +223,15 @@ bool ApplicationFolderModel::addDelegate(FolioDelegate *delegate, int index)
|
|||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ApplicationFolderModel::addDelegate(FolioDelegate *delegate, int index)
|
||||
{
|
||||
if (!canAddDelegate(delegate, index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index == m_folder->m_delegates.size()) {
|
||||
beginInsertRows(QModelIndex(), index, index);
|
||||
m_folder->m_delegates.append({delegate, 0, 0});
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ public:
|
|||
FolioApplicationFolder(QObject *parent = nullptr, QString name = QString{});
|
||||
|
||||
static FolioApplicationFolder *fromJson(QJsonObject &obj, QObject *parent);
|
||||
QJsonObject toJson();
|
||||
QJsonObject toJson() const;
|
||||
|
||||
QString name() const;
|
||||
void setName(QString &name);
|
||||
|
|
@ -92,6 +92,7 @@ public:
|
|||
|
||||
FolioDelegate *getDelegate(int index);
|
||||
void moveEntry(int fromRow, int toRow);
|
||||
bool canAddDelegate(FolioDelegate *delegate, int index);
|
||||
bool addDelegate(FolioDelegate *delegate, int index);
|
||||
void removeDelegate(int index);
|
||||
QPointF getDelegatePosition(int index);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ FolioDelegate::FolioDelegate(QObject *parent)
|
|||
, m_type{FolioDelegate::None}
|
||||
, m_application{nullptr}
|
||||
, m_folder{nullptr}
|
||||
, m_widget{nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ FolioDelegate::FolioDelegate(FolioApplication *application, QObject *parent)
|
|||
, m_type{FolioDelegate::Application}
|
||||
, m_application{application}
|
||||
, m_folder{nullptr}
|
||||
, m_widget{nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -25,6 +27,16 @@ FolioDelegate::FolioDelegate(FolioApplicationFolder *folder, QObject *parent)
|
|||
, m_type{FolioDelegate::Folder}
|
||||
, m_application{nullptr}
|
||||
, m_folder{folder}
|
||||
, m_widget{nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
FolioDelegate::FolioDelegate(FolioWidget *widget, QObject *parent)
|
||||
: QObject{parent}
|
||||
, m_type{FolioDelegate::Widget}
|
||||
, m_application{nullptr}
|
||||
, m_folder{nullptr}
|
||||
, m_widget{widget}
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +59,13 @@ FolioDelegate *FolioDelegate::fromJson(QJsonObject &obj, QObject *parent)
|
|||
return new FolioDelegate{folder, parent};
|
||||
}
|
||||
|
||||
} else if (type == "widget") {
|
||||
// read widget
|
||||
FolioWidget *widget = FolioWidget::fromJson(obj, parent);
|
||||
|
||||
if (widget) {
|
||||
return new FolioDelegate{widget, parent};
|
||||
}
|
||||
} else if (type == "none") {
|
||||
return new FolioDelegate{parent};
|
||||
}
|
||||
|
|
@ -61,6 +80,8 @@ QJsonObject FolioDelegate::toJson() const
|
|||
return m_application->toJson();
|
||||
case FolioDelegate::Folder:
|
||||
return m_folder->toJson();
|
||||
case FolioDelegate::Widget:
|
||||
return m_widget->toJson();
|
||||
case FolioDelegate::None: {
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("type")] = "none";
|
||||
|
|
@ -86,3 +107,8 @@ FolioApplicationFolder *FolioDelegate::folder()
|
|||
{
|
||||
return m_folder;
|
||||
}
|
||||
|
||||
FolioWidget *FolioDelegate::widget()
|
||||
{
|
||||
return m_widget;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "folioapplication.h"
|
||||
#include "folioapplicationfolder.h"
|
||||
#include "foliowidget.h"
|
||||
|
||||
class FolioApplication;
|
||||
class FolioApplicationFolder;
|
||||
|
|
@ -16,18 +17,21 @@ class FolioDelegate : public QObject
|
|||
Q_PROPERTY(FolioDelegate::Type type READ type CONSTANT)
|
||||
Q_PROPERTY(FolioApplication *application READ application CONSTANT)
|
||||
Q_PROPERTY(FolioApplicationFolder *folder READ folder CONSTANT)
|
||||
Q_PROPERTY(FolioWidget *widget READ widget CONSTANT)
|
||||
|
||||
public:
|
||||
enum Type {
|
||||
None,
|
||||
Application,
|
||||
Folder,
|
||||
Widget,
|
||||
};
|
||||
Q_ENUM(Type)
|
||||
|
||||
FolioDelegate(QObject *parent = nullptr);
|
||||
FolioDelegate(FolioApplication *application, QObject *parent);
|
||||
FolioDelegate(FolioApplicationFolder *folder, QObject *parent);
|
||||
FolioDelegate(FolioWidget *widget, QObject *parent);
|
||||
|
||||
static FolioDelegate *fromJson(QJsonObject &obj, QObject *parent);
|
||||
|
||||
|
|
@ -36,9 +40,11 @@ public:
|
|||
FolioDelegate::Type type();
|
||||
FolioApplication *application();
|
||||
FolioApplicationFolder *folder();
|
||||
FolioWidget *widget();
|
||||
|
||||
protected:
|
||||
FolioDelegate::Type m_type;
|
||||
FolioApplication *m_application{nullptr};
|
||||
FolioApplicationFolder *m_folder{nullptr};
|
||||
FolioWidget *m_widget{nullptr};
|
||||
};
|
||||
|
|
|
|||
272
containments/homescreens/folio/foliowidget.cpp
Normal file
272
containments/homescreens/folio/foliowidget.cpp
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "foliowidget.h"
|
||||
#include "homescreenstate.h"
|
||||
#include "widgetsmanager.h"
|
||||
|
||||
FolioWidget::FolioWidget(QObject *parent, int id, int realGridWidth, int realGridHeight)
|
||||
: QObject{parent}
|
||||
, m_id{id}
|
||||
, m_realGridWidth{realGridWidth}
|
||||
, m_realGridHeight{realGridHeight}
|
||||
, m_applet{nullptr}
|
||||
, m_quickApplet{nullptr}
|
||||
{
|
||||
auto *applet = WidgetsManager::self()->getWidget(id);
|
||||
if (applet) {
|
||||
setApplet(applet);
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
||||
FolioWidget::FolioWidget(QObject *parent, Plasma::Applet *applet, int realGridWidth, int realGridHeight)
|
||||
: QObject{parent}
|
||||
, m_id{applet ? static_cast<int>(applet->id()) : -1}
|
||||
, m_realGridWidth{realGridWidth}
|
||||
, m_realGridHeight{realGridHeight}
|
||||
{
|
||||
setApplet(applet);
|
||||
init();
|
||||
}
|
||||
|
||||
void FolioWidget::init()
|
||||
{
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageOrientationChanged, this, [this]() {
|
||||
Q_EMIT gridWidthChanged();
|
||||
Q_EMIT gridHeightChanged();
|
||||
});
|
||||
|
||||
connect(WidgetsManager::self(), &WidgetsManager::widgetAdded, this, [this](Plasma::Applet *applet) {
|
||||
if (applet && static_cast<int>(applet->id()) == m_id) {
|
||||
setApplet(applet);
|
||||
}
|
||||
});
|
||||
connect(WidgetsManager::self(), &WidgetsManager::widgetRemoved, this, [this](Plasma::Applet *applet) {
|
||||
if (applet && static_cast<int>(applet->id()) == m_id) {
|
||||
setApplet(nullptr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FolioWidget *FolioWidget::fromJson(QJsonObject &obj, QObject *parent)
|
||||
{
|
||||
int id = obj[QStringLiteral("id")].toInt();
|
||||
int gridWidth = obj[QStringLiteral("gridWidth")].toInt();
|
||||
int gridHeight = obj[QStringLiteral("gridHeight")].toInt();
|
||||
return new FolioWidget(parent, id, gridWidth, gridHeight);
|
||||
}
|
||||
|
||||
QJsonObject FolioWidget::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
obj[QStringLiteral("type")] = "widget";
|
||||
obj[QStringLiteral("id")] = m_id;
|
||||
obj[QStringLiteral("gridWidth")] = m_realGridWidth;
|
||||
obj[QStringLiteral("gridHeight")] = m_realGridHeight;
|
||||
return obj;
|
||||
}
|
||||
|
||||
int FolioWidget::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
int FolioWidget::gridWidth() const
|
||||
{
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
return m_realGridWidth;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
return m_realGridHeight;
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
return m_realGridHeight;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
return m_realGridWidth;
|
||||
}
|
||||
return m_realGridWidth;
|
||||
}
|
||||
|
||||
void FolioWidget::setGridWidth(int gridWidth)
|
||||
{
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
setRealGridWidth(gridWidth);
|
||||
break;
|
||||
case HomeScreenState::RotateClockwise: {
|
||||
int oldGridHeight = m_realGridHeight;
|
||||
setRealGridHeight(gridWidth);
|
||||
Q_EMIT realTopLeftPositionChanged(oldGridHeight - gridWidth, 0);
|
||||
break;
|
||||
}
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
setRealGridHeight(gridWidth);
|
||||
break;
|
||||
case HomeScreenState::RotateUpsideDown: {
|
||||
int oldGridWidth = m_realGridWidth;
|
||||
setRealGridWidth(gridWidth);
|
||||
Q_EMIT realTopLeftPositionChanged(0, oldGridWidth - gridWidth);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int FolioWidget::gridHeight() const
|
||||
{
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
return m_realGridHeight;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
return m_realGridWidth;
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
return m_realGridWidth;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
return m_realGridHeight;
|
||||
}
|
||||
return m_realGridHeight;
|
||||
}
|
||||
|
||||
void FolioWidget::setGridHeight(int gridHeight)
|
||||
{
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
setRealGridHeight(gridHeight);
|
||||
break;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
setRealGridWidth(gridHeight);
|
||||
break;
|
||||
case HomeScreenState::RotateCounterClockwise: {
|
||||
int oldGridWidth = m_realGridWidth;
|
||||
setRealGridWidth(gridHeight);
|
||||
Q_EMIT realTopLeftPositionChanged(0, oldGridWidth - gridHeight);
|
||||
break;
|
||||
}
|
||||
case HomeScreenState::RotateUpsideDown: {
|
||||
int oldGridHeight = m_realGridHeight;
|
||||
setRealGridHeight(gridHeight);
|
||||
Q_EMIT realTopLeftPositionChanged(oldGridHeight - gridHeight, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int FolioWidget::realGridWidth() const
|
||||
{
|
||||
return m_realGridWidth;
|
||||
}
|
||||
|
||||
void FolioWidget::setRealGridWidth(int gridWidth)
|
||||
{
|
||||
if (m_realGridWidth != gridWidth) {
|
||||
m_realGridWidth = gridWidth;
|
||||
|
||||
// emit both because realGridWidth could be either gridWidth or gridHeight
|
||||
Q_EMIT gridWidthChanged();
|
||||
Q_EMIT gridHeightChanged();
|
||||
|
||||
Q_EMIT saveRequested();
|
||||
}
|
||||
}
|
||||
|
||||
int FolioWidget::realGridHeight() const
|
||||
{
|
||||
return m_realGridHeight;
|
||||
}
|
||||
|
||||
void FolioWidget::setRealGridHeight(int gridHeight)
|
||||
{
|
||||
if (m_realGridHeight != gridHeight) {
|
||||
m_realGridHeight = gridHeight;
|
||||
|
||||
// emit both because realGridHeight could be either gridWidth or gridHeight
|
||||
Q_EMIT gridWidthChanged();
|
||||
Q_EMIT gridHeightChanged();
|
||||
|
||||
Q_EMIT saveRequested();
|
||||
}
|
||||
}
|
||||
|
||||
GridPosition FolioWidget::topLeftCorner(int row, int column)
|
||||
{
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
return {row, column};
|
||||
case HomeScreenState::RotateClockwise:
|
||||
return {row, column - gridWidth() + 1};
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
return {row - gridHeight() + 1, column};
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
return {row - gridHeight() + 1, column - gridWidth() + 1};
|
||||
}
|
||||
return {row, column};
|
||||
}
|
||||
|
||||
bool FolioWidget::isInBounds(int widgetRow, int widgetColumn, int row, int column)
|
||||
{
|
||||
return (row >= widgetRow) && (row <= widgetRow + gridHeight() - 1) && (column >= widgetColumn) && (column <= widgetColumn + gridWidth() - 1);
|
||||
}
|
||||
|
||||
bool FolioWidget::overlapsWidget(int widgetRow, int widgetColumn, FolioWidget *otherWidget, int otherWidgetRow, int otherWidgetColumn)
|
||||
{
|
||||
if (!otherWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// property: if they overlap, then at least one corner of one widget is in the other widget
|
||||
int widgetMaxRow = widgetRow + gridHeight() - 1;
|
||||
int widgetMaxColumn = widgetColumn + gridWidth() - 1;
|
||||
int otherWidgetMaxRow = otherWidgetRow + otherWidget->gridHeight() - 1;
|
||||
int otherWidgetMaxColumn = otherWidgetColumn + otherWidget->gridWidth() - 1;
|
||||
|
||||
return isInBounds(widgetRow, widgetColumn, otherWidgetRow, otherWidgetColumn) || isInBounds(widgetRow, widgetColumn, otherWidgetMaxRow, otherWidgetColumn)
|
||||
|| isInBounds(widgetRow, widgetColumn, otherWidgetRow, otherWidgetMaxColumn)
|
||||
|| isInBounds(widgetRow, widgetColumn, otherWidgetMaxRow, otherWidgetMaxColumn)
|
||||
|| otherWidget->isInBounds(otherWidgetRow, otherWidgetColumn, widgetRow, widgetColumn)
|
||||
|| otherWidget->isInBounds(otherWidgetRow, otherWidgetColumn, widgetMaxRow, widgetColumn)
|
||||
|| otherWidget->isInBounds(otherWidgetRow, otherWidgetColumn, widgetRow, widgetMaxColumn)
|
||||
|| otherWidget->isInBounds(otherWidgetRow, otherWidgetColumn, widgetMaxRow, widgetMaxColumn);
|
||||
}
|
||||
|
||||
Plasma::Applet *FolioWidget::applet() const
|
||||
{
|
||||
return m_applet;
|
||||
}
|
||||
|
||||
void FolioWidget::setApplet(Plasma::Applet *applet)
|
||||
{
|
||||
m_applet = applet;
|
||||
Q_EMIT appletChanged();
|
||||
|
||||
int id = applet ? applet->id() : -1;
|
||||
if (m_id != id) {
|
||||
m_id = id;
|
||||
Q_EMIT idChanged();
|
||||
|
||||
// ensure the id is saved
|
||||
Q_EMIT saveRequested();
|
||||
}
|
||||
|
||||
if (m_applet) {
|
||||
setVisualApplet(PlasmaQuick::AppletQuickItem::itemForApplet(m_applet));
|
||||
} else {
|
||||
setVisualApplet(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
PlasmaQuick::AppletQuickItem *FolioWidget::visualApplet() const
|
||||
{
|
||||
return m_quickApplet;
|
||||
}
|
||||
|
||||
void FolioWidget::setVisualApplet(PlasmaQuick::AppletQuickItem *quickItem)
|
||||
{
|
||||
m_quickApplet = quickItem;
|
||||
Q_EMIT visualAppletChanged();
|
||||
}
|
||||
|
||||
void FolioWidget::destroyApplet()
|
||||
{
|
||||
if (m_applet) {
|
||||
m_applet->destroy();
|
||||
}
|
||||
}
|
||||
89
containments/homescreens/folio/foliowidget.h
Normal file
89
containments/homescreens/folio/foliowidget.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
#include <PlasmaQuick/AppletQuickItem>
|
||||
|
||||
struct GridPosition {
|
||||
Q_GADGET
|
||||
public:
|
||||
int row;
|
||||
int column;
|
||||
};
|
||||
|
||||
/**
|
||||
* @short Object that represents a widget on the homescreen.
|
||||
*/
|
||||
class FolioWidget : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int id READ id NOTIFY idChanged)
|
||||
Q_PROPERTY(int gridWidth READ gridWidth NOTIFY gridWidthChanged)
|
||||
Q_PROPERTY(int gridHeight READ gridHeight NOTIFY gridHeightChanged)
|
||||
Q_PROPERTY(Plasma::Applet *applet READ applet NOTIFY appletChanged)
|
||||
Q_PROPERTY(PlasmaQuick::AppletQuickItem *visualApplet READ visualApplet NOTIFY visualAppletChanged)
|
||||
|
||||
public:
|
||||
FolioWidget(QObject *parent = nullptr, int id = -1, int gridWidth = 0, int gridHeight = 0);
|
||||
FolioWidget(QObject *parent, Plasma::Applet *applet, int gridWidth, int gridHeight);
|
||||
|
||||
static FolioWidget *fromJson(QJsonObject &obj, QObject *parent);
|
||||
QJsonObject toJson() const;
|
||||
|
||||
int id() const;
|
||||
|
||||
int gridWidth() const;
|
||||
void setGridWidth(int gridWidth);
|
||||
|
||||
int gridHeight() const;
|
||||
void setGridHeight(int gridHeight);
|
||||
|
||||
int realGridWidth() const;
|
||||
void setRealGridWidth(int gridWidth);
|
||||
|
||||
int realGridHeight() const;
|
||||
void setRealGridHeight(int gridHeight);
|
||||
|
||||
// takes in the stored position of the widget (top left when in portrait orientation)
|
||||
// returns the position of the widget corners on a page grid, factoring in the current page orientation
|
||||
GridPosition topLeftCorner(int row, int column);
|
||||
|
||||
// query whether (row, column) is inside this widget, if it was at position (widgetRow, widgetColumn)
|
||||
bool isInBounds(int widgetRow, int widgetColumn, int row, int column);
|
||||
|
||||
bool overlapsWidget(int widgetRow, int widgetColumn, FolioWidget *otherWidget, int otherWidgetRow, int otherWidgetColumn);
|
||||
|
||||
Plasma::Applet *applet() const;
|
||||
void setApplet(Plasma::Applet *applet);
|
||||
|
||||
PlasmaQuick::AppletQuickItem *visualApplet() const;
|
||||
|
||||
Q_INVOKABLE void destroyApplet();
|
||||
|
||||
Q_SIGNALS:
|
||||
void idChanged();
|
||||
void appletChanged();
|
||||
void visualAppletChanged();
|
||||
void gridWidthChanged();
|
||||
void gridHeightChanged();
|
||||
void saveRequested();
|
||||
|
||||
// when we resize while the screen is rotated, the stored top left position
|
||||
// changes, so we need to notify the model
|
||||
void realTopLeftPositionChanged(int offsetRows, int offsetColumns);
|
||||
|
||||
private:
|
||||
void init();
|
||||
void setVisualApplet(PlasmaQuick::AppletQuickItem *quickApplet);
|
||||
|
||||
int m_id = -1;
|
||||
int m_realGridWidth = 1;
|
||||
int m_realGridHeight = 1;
|
||||
|
||||
Plasma::Applet *m_applet = nullptr;
|
||||
PlasmaQuick::AppletQuickItem *m_quickApplet = nullptr;
|
||||
};
|
||||
|
|
@ -11,9 +11,12 @@
|
|||
#include "folioapplicationfolder.h"
|
||||
#include "foliodelegate.h"
|
||||
#include "foliosettings.h"
|
||||
#include "foliowidget.h"
|
||||
#include "homescreenstate.h"
|
||||
#include "pagelistmodel.h"
|
||||
#include "pagemodel.h"
|
||||
#include "widgetcontainer.h"
|
||||
#include "widgetsmanager.h"
|
||||
|
||||
#include <KWindowSystem>
|
||||
|
||||
|
|
@ -31,12 +34,13 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
|||
|
||||
// pre-initialize
|
||||
FolioSettings::self()->setApplet(this);
|
||||
HomeScreenState::self();
|
||||
HomeScreenState::self()->setContainment(this);
|
||||
WidgetsManager::self();
|
||||
|
||||
// models are loaded in main.qml
|
||||
ApplicationListModel::self();
|
||||
FavouritesModel::self()->setApplet(this);
|
||||
PageListModel::self()->setApplet(this);
|
||||
FavouritesModel::self()->setContainment(this);
|
||||
PageListModel::self()->setContainment(this);
|
||||
|
||||
qmlRegisterSingletonType<ApplicationListModel>(uri, 1, 0, "ApplicationListModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||
return ApplicationListModel::self();
|
||||
|
|
@ -60,13 +64,18 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
|||
|
||||
qmlRegisterType<FolioApplication>(uri, 1, 0, "FolioApplication");
|
||||
qmlRegisterType<FolioApplicationFolder>(uri, 1, 0, "FolioApplicationFolder");
|
||||
qmlRegisterType<FolioWidget>(uri, 1, 0, "FolioWidget");
|
||||
qmlRegisterType<FolioDelegate>(uri, 1, 0, "FolioDelegate");
|
||||
qmlRegisterType<PageModel>(uri, 1, 0, "PageModel");
|
||||
qmlRegisterType<FolioPageDelegate>(uri, 1, 0, "FolioPageDelegate");
|
||||
qmlRegisterType<DelegateTouchArea>(uri, 1, 0, "DelegateTouchArea");
|
||||
qmlRegisterType<DelegateDragPosition>(uri, 1, 0, "DelegateDragPosition");
|
||||
qmlRegisterType<WidgetContainer>(uri, 1, 0, "WidgetContainer");
|
||||
|
||||
connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, this, &HomeScreen::showingDesktopChanged);
|
||||
|
||||
connect(this, &Plasma::Containment::appletAdded, this, &HomeScreen::onAppletAdded);
|
||||
connect(this, &Plasma::Containment::appletAboutToBeRemoved, this, &HomeScreen::onAppletAboutToBeRemoved);
|
||||
}
|
||||
|
||||
HomeScreen::~HomeScreen() = default;
|
||||
|
|
@ -76,6 +85,16 @@ void HomeScreen::configChanged()
|
|||
Plasma::Containment::configChanged();
|
||||
}
|
||||
|
||||
void HomeScreen::onAppletAdded(Plasma::Applet *applet, const QRectF &geometryHint)
|
||||
{
|
||||
WidgetsManager::self()->addWidget(applet);
|
||||
}
|
||||
|
||||
void HomeScreen::onAppletAboutToBeRemoved(Plasma::Applet *applet)
|
||||
{
|
||||
WidgetsManager::self()->removeWidget(applet);
|
||||
}
|
||||
|
||||
K_PLUGIN_CLASS(HomeScreen)
|
||||
|
||||
#include "homescreen.moc"
|
||||
|
|
|
|||
|
|
@ -19,4 +19,8 @@ public:
|
|||
|
||||
Q_SIGNALS:
|
||||
void showingDesktopChanged(bool showingDesktop);
|
||||
|
||||
private Q_SLOTS:
|
||||
void onAppletAdded(Plasma::Applet *applet, const QRectF &geometryHint);
|
||||
void onAppletAboutToBeRemoved(Plasma::Applet *applet);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -582,6 +582,26 @@ void HomeScreenState::setDelegateDragY(qreal delegateDragY)
|
|||
Q_EMIT delegateDragYChanged();
|
||||
}
|
||||
|
||||
qreal HomeScreenState::delegateDragPointerOffsetX()
|
||||
{
|
||||
return m_delegateDragPointerOffsetX;
|
||||
}
|
||||
|
||||
void HomeScreenState::setDelegateDragPointerOffsetX(qreal delegateDragPointerOffsetX)
|
||||
{
|
||||
m_delegateDragPointerOffsetX = delegateDragPointerOffsetX;
|
||||
}
|
||||
|
||||
qreal HomeScreenState::delegateDragPointerOffsetY()
|
||||
{
|
||||
return m_delegateDragPointerOffsetY;
|
||||
}
|
||||
|
||||
void HomeScreenState::setDelegateDragPointerOffsetY(qreal delegateDragPointerOffsetY)
|
||||
{
|
||||
m_delegateDragPointerOffsetY = delegateDragPointerOffsetY;
|
||||
}
|
||||
|
||||
int HomeScreenState::currentPage()
|
||||
{
|
||||
return m_pageNum;
|
||||
|
|
@ -659,6 +679,16 @@ QPointF HomeScreenState::getFolderDelegateScreenPosition(int position)
|
|||
return {x, y};
|
||||
}
|
||||
|
||||
Plasma::Containment *HomeScreenState::containment()
|
||||
{
|
||||
return m_containment;
|
||||
}
|
||||
|
||||
void HomeScreenState::setContainment(Plasma::Containment *containment)
|
||||
{
|
||||
m_containment = containment;
|
||||
}
|
||||
|
||||
void HomeScreenState::openAppDrawer()
|
||||
{
|
||||
cancelAppDrawerAnimations();
|
||||
|
|
@ -786,11 +816,13 @@ void HomeScreenState::closeSettingsView()
|
|||
m_closeSettingsAnim->start();
|
||||
}
|
||||
|
||||
void HomeScreenState::startDelegateDrag(qreal startX, qreal startY)
|
||||
void HomeScreenState::startDelegateDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY)
|
||||
{
|
||||
// start drag and drop positions
|
||||
setDelegateDragX(startX);
|
||||
setDelegateDragY(startY);
|
||||
setDelegateDragPointerOffsetX(pointerOffsetX);
|
||||
setDelegateDragPointerOffsetY(pointerOffsetY);
|
||||
|
||||
// end current swipe
|
||||
swipeEnded();
|
||||
|
|
@ -799,21 +831,21 @@ void HomeScreenState::startDelegateDrag(qreal startX, qreal startY)
|
|||
setSwipeState(SwipeState::AwaitingDraggingDelegate);
|
||||
}
|
||||
|
||||
void HomeScreenState::startDelegatePageDrag(qreal startX, qreal startY, int page, int row, int column)
|
||||
void HomeScreenState::startDelegatePageDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, int page, int row, int column)
|
||||
{
|
||||
startDelegateDrag(startX, startY);
|
||||
startDelegateDrag(startX, startY, pointerOffsetX, pointerOffsetY);
|
||||
Q_EMIT delegateDragFromPageStarted(page, row, column);
|
||||
}
|
||||
|
||||
void HomeScreenState::startDelegateFavouritesDrag(qreal startX, qreal startY, int position)
|
||||
void HomeScreenState::startDelegateFavouritesDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, int position)
|
||||
{
|
||||
startDelegateDrag(startX, startY);
|
||||
startDelegateDrag(startX, startY, pointerOffsetX, pointerOffsetY);
|
||||
Q_EMIT delegateDragFromFavouritesStarted(position);
|
||||
}
|
||||
|
||||
void HomeScreenState::startDelegateAppDrawerDrag(qreal startX, qreal startY, QString storageId)
|
||||
void HomeScreenState::startDelegateAppDrawerDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, QString storageId)
|
||||
{
|
||||
startDelegateDrag(startX, startY);
|
||||
startDelegateDrag(startX, startY, pointerOffsetX, pointerOffsetY);
|
||||
Q_EMIT delegateDragFromAppDrawerStarted(storageId);
|
||||
|
||||
// we start dragging the delegate immediately from the app drawer, because we don't have a context menu to deal with!
|
||||
|
|
@ -821,12 +853,27 @@ void HomeScreenState::startDelegateAppDrawerDrag(qreal startX, qreal startY, QSt
|
|||
setSwipeState(SwipeState::DraggingDelegate);
|
||||
}
|
||||
|
||||
void HomeScreenState::startDelegateFolderDrag(qreal startX, qreal startY, FolioApplicationFolder *folder, int position)
|
||||
void HomeScreenState::startDelegateFolderDrag(qreal startX,
|
||||
qreal startY,
|
||||
qreal pointerOffsetX,
|
||||
qreal pointerOffsetY,
|
||||
FolioApplicationFolder *folder,
|
||||
int position)
|
||||
{
|
||||
startDelegateDrag(startX, startY);
|
||||
startDelegateDrag(startX, startY, pointerOffsetX, pointerOffsetY);
|
||||
Q_EMIT delegateDragFromFolderStarted(folder, position);
|
||||
}
|
||||
|
||||
void HomeScreenState::startDelegateWidgetListDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, QString appletPluginId)
|
||||
{
|
||||
startDelegateDrag(startX, startY, pointerOffsetX, pointerOffsetY);
|
||||
Q_EMIT delegateDragFromWidgetListStarted(appletPluginId);
|
||||
|
||||
// we start dragging the delegate immediately from the app drawer, because we don't have a context menu to deal with!
|
||||
// NOTE: this has to happen after delegateDragFromAppDrawerStarted, because slots for that expect SwipeState::AwaitingDraggingDelegate
|
||||
setSwipeState(SwipeState::DraggingDelegate);
|
||||
}
|
||||
|
||||
void HomeScreenState::cancelDelegateDrag()
|
||||
{
|
||||
swipeEnded();
|
||||
|
|
|
|||
|
|
@ -228,12 +228,22 @@ public:
|
|||
qreal searchWidgetY();
|
||||
void setSearchWidgetY(qreal searchWidgetY);
|
||||
|
||||
// the top left x-position of the delegate being dragged
|
||||
qreal delegateDragX();
|
||||
void setDelegateDragX(qreal delegateDragX);
|
||||
|
||||
// the top left y-position of the delegate being dragged
|
||||
qreal delegateDragY();
|
||||
void setDelegateDragY(qreal delegateDragY);
|
||||
|
||||
// the offset from delegateDragX where the mouse/finger is
|
||||
qreal delegateDragPointerOffsetX();
|
||||
void setDelegateDragPointerOffsetX(qreal delegateDragPointerOffsetX);
|
||||
|
||||
// the offset from delegateDragY where the mouse/finger is
|
||||
qreal delegateDragPointerOffsetY();
|
||||
void setDelegateDragPointerOffsetY(qreal delegateDragPointerOffsetY);
|
||||
|
||||
int currentPage();
|
||||
void setCurrentPage(int currentPage);
|
||||
|
||||
|
|
@ -247,6 +257,9 @@ public:
|
|||
Q_INVOKABLE QPointF getFavouritesDelegateScreenPosition(int position);
|
||||
Q_INVOKABLE QPointF getFolderDelegateScreenPosition(int position);
|
||||
|
||||
Plasma::Containment *containment();
|
||||
void setContainment(Plasma::Containment *containment);
|
||||
|
||||
Q_SIGNALS:
|
||||
void swipeStateChanged();
|
||||
void viewStateChanged();
|
||||
|
|
@ -290,6 +303,7 @@ Q_SIGNALS:
|
|||
void delegateDragFromFavouritesStarted(int position);
|
||||
void delegateDragFromAppDrawerStarted(QString storageId);
|
||||
void delegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
||||
void delegateDragFromWidgetListStarted(QString appletPluginId);
|
||||
void pageNumChanged();
|
||||
void folderPageNumChanged();
|
||||
|
||||
|
|
@ -314,10 +328,11 @@ public Q_SLOTS:
|
|||
void openSettingsView();
|
||||
void closeSettingsView();
|
||||
|
||||
void startDelegatePageDrag(qreal startX, qreal startY, int page, int row, int column);
|
||||
void startDelegateFavouritesDrag(qreal startX, qreal startY, int position);
|
||||
void startDelegateAppDrawerDrag(qreal startX, qreal startY, QString storageId);
|
||||
void startDelegateFolderDrag(qreal startX, qreal startY, FolioApplicationFolder *folder, int position);
|
||||
void startDelegatePageDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, int page, int row, int column);
|
||||
void startDelegateFavouritesDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, int position);
|
||||
void startDelegateAppDrawerDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, QString storageId);
|
||||
void startDelegateFolderDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, FolioApplicationFolder *folder, int position);
|
||||
void startDelegateWidgetListDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, QString appletPluginId);
|
||||
void cancelDelegateDrag();
|
||||
|
||||
// from SwipeArea
|
||||
|
|
@ -329,7 +344,7 @@ private:
|
|||
void setViewState(ViewState viewState);
|
||||
void setSwipeState(SwipeState swipeState);
|
||||
|
||||
void startDelegateDrag(qreal startX, qreal startY);
|
||||
void startDelegateDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY);
|
||||
|
||||
void cancelAppDrawerAnimations();
|
||||
void cancelSearchWidgetAnimations();
|
||||
|
|
@ -380,6 +395,8 @@ private:
|
|||
qreal m_searchWidgetY{0};
|
||||
qreal m_delegateDragX{0};
|
||||
qreal m_delegateDragY{0};
|
||||
qreal m_delegateDragPointerOffsetX{0};
|
||||
qreal m_delegateDragPointerOffsetY{0};
|
||||
|
||||
int m_pageNum{0};
|
||||
int m_folderPageNum{0};
|
||||
|
|
@ -397,4 +414,6 @@ private:
|
|||
QPropertyAnimation *m_folderPageAnim{nullptr};
|
||||
QPropertyAnimation *m_openSettingsAnim{nullptr};
|
||||
QPropertyAnimation *m_closeSettingsAnim{nullptr};
|
||||
|
||||
Plasma::Containment *m_containment{nullptr};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,12 +6,13 @@ import QtQuick.Layouts
|
|||
import QtQuick.Controls as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.plasma.plasmoid 2.0
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.plasma.private.mobileshell as MobileShell
|
||||
|
||||
import 'private'
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
|
|
@ -58,26 +59,13 @@ Item {
|
|||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
opacity: 0
|
||||
headerHeight: root.headerHeight
|
||||
}
|
||||
|
||||
// opacity gradient at grid edges
|
||||
OpacityMask {
|
||||
FlickableOpacityGradient {
|
||||
anchors.fill: appDrawerGrid
|
||||
source: appDrawerGrid
|
||||
maskSource: Rectangle {
|
||||
id: mask
|
||||
width: appDrawerGrid.width
|
||||
height: appDrawerGrid.height
|
||||
|
||||
property real gradientPct: (Kirigami.Units.gridUnit * 2) / appDrawerGrid.height
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: appDrawerGrid.atYBeginning ? 'white' : 'transparent' }
|
||||
GradientStop { position: mask.gradientPct; color: 'white' }
|
||||
GradientStop { position: 1.0 - mask.gradientPct; color: 'white' }
|
||||
GradientStop { position: 1.0; color: appDrawerGrid.atYEnd ? 'white' : 'transparent' }
|
||||
}
|
||||
}
|
||||
flickable: appDrawerGrid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ MobileShell.GridView {
|
|||
layer.enabled: true
|
||||
|
||||
property var homeScreen
|
||||
property real headerHeight
|
||||
|
||||
readonly property int reservedSpaceForLabel: Folio.HomeScreenState.pageDelegateLabelHeight
|
||||
readonly property real effectiveContentWidth: width - leftMargin - rightMargin
|
||||
|
|
@ -30,7 +31,7 @@ MobileShell.GridView {
|
|||
leftMargin: horizontalMargin
|
||||
rightMargin: horizontalMargin
|
||||
|
||||
cellWidth: effectiveContentWidth / Math.min(Math.floor(effectiveContentWidth / (Folio.FolioSettings.delegateIconSize + Kirigami.Units.largeSpacing * 3)), 8)
|
||||
cellWidth: effectiveContentWidth / Math.min(Math.floor(effectiveContentWidth / (Folio.FolioSettings.delegateIconSize + Kirigami.Units.largeSpacing * 3.5)), 8)
|
||||
cellHeight: cellWidth + reservedSpaceForLabel
|
||||
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
|
@ -79,11 +80,18 @@ MobileShell.GridView {
|
|||
height: root.cellHeight
|
||||
|
||||
onPressAndHold: {
|
||||
const mappedCoords = root.homeScreen.prepareStartDelegateDrag(model.delegate, delegate.delegateItem);
|
||||
Folio.HomeScreenState.closeAppDrawer();
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(model.delegate, delegate.delegateItem);
|
||||
|
||||
// we need to adjust because app drawer delegates have a different size than regular homescreen delegates
|
||||
const centerX = mappedCoords.x + root.cellWidth / 2;
|
||||
const centerY = mappedCoords.y + root.cellHeight / 2;
|
||||
|
||||
Folio.HomeScreenState.startDelegateAppDrawerDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
centerX - Folio.HomeScreenState.pageCellWidth / 2,
|
||||
centerY - Folio.HomeScreenState.pageCellHeight / 2,
|
||||
delegate.pressPosition.x,
|
||||
delegate.pressPosition.y,
|
||||
model.delegate.application.storageId
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,15 +18,18 @@ Item {
|
|||
|
||||
readonly property real dropAnimationRunning: dragXAnim.running || dragYAnim.running
|
||||
|
||||
// ignore widget dragging, that is not handled by this component
|
||||
readonly property bool isWidgetDrag: Folio.HomeScreenState.dragState.dropDelegate && Folio.HomeScreenState.dragState.dropDelegate.type === Folio.FolioDelegate.Widget
|
||||
|
||||
visible: false
|
||||
x: Math.round(Folio.HomeScreenState.delegateDragX)
|
||||
y: Math.round(Folio.HomeScreenState.delegateDragY)
|
||||
x: Folio.HomeScreenState.delegateDragX
|
||||
y: Folio.HomeScreenState.delegateDragY
|
||||
|
||||
function setXBinding() {
|
||||
x = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragX));
|
||||
x = Qt.binding(() => Folio.HomeScreenState.delegateDragX);
|
||||
}
|
||||
function setYBinding() {
|
||||
y = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragY));
|
||||
y = Qt.binding(() => Folio.HomeScreenState.delegateDragY);
|
||||
}
|
||||
|
||||
// animate drop x
|
||||
|
|
@ -70,7 +73,7 @@ Item {
|
|||
|
||||
// reset and show drag item
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate && !isWidgetDrag) {
|
||||
root.scale = 1.0;
|
||||
root.visible = true;
|
||||
}
|
||||
|
|
@ -78,6 +81,10 @@ Item {
|
|||
|
||||
// save the existing delegate at the spot (this is called before the delegate is dropped)
|
||||
function onDelegateDragEnded() {
|
||||
if (root.isWidgetDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dragState = Folio.HomeScreenState.dragState;
|
||||
let dropPosition = dragState.candidateDropPosition;
|
||||
|
||||
|
|
@ -89,7 +96,7 @@ Item {
|
|||
stateWatcher.delegateDroppedOn = Folio.HomeScreenState.getFavouritesDelegateAt(dropPosition.favouritesPosition);
|
||||
break;
|
||||
case Folio.DelegateDragPosition.Folder:
|
||||
stateWatcher.delegateDroppedOn = null
|
||||
stateWatcher.delegateDroppedOn = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -100,6 +107,10 @@ Item {
|
|||
|
||||
// animate from when the delegate is dropped to its drop position
|
||||
function onDelegateDroppedAndPlaced() {
|
||||
if (root.isWidgetDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dragState = Folio.HomeScreenState.dragState;
|
||||
let dropPosition = dragState.candidateDropPosition;
|
||||
|
||||
|
|
@ -130,6 +141,11 @@ Item {
|
|||
scaleAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
// if the drop has been abandoned, just hide
|
||||
function onNewDelegateDropAbandoned() {
|
||||
root.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
// simulate an icon delegate
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ MouseArea {
|
|||
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
appDelegate.pressPosition.x,
|
||||
appDelegate.pressPosition.y,
|
||||
delegate.index
|
||||
);
|
||||
|
||||
|
|
@ -178,6 +180,8 @@ MouseArea {
|
|||
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
appFolderDelegate.pressPosition.x,
|
||||
appFolderDelegate.pressPosition.y,
|
||||
delegate.index
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -229,6 +229,8 @@ Folio.DelegateTouchArea {
|
|||
Folio.HomeScreenState.startDelegateFolderDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
appDelegate.pressPosition.x,
|
||||
appDelegate.pressPosition.y,
|
||||
root.folder,
|
||||
delegate.index
|
||||
);
|
||||
|
|
|
|||
|
|
@ -29,8 +29,13 @@ Item {
|
|||
|
||||
property Folio.HomeScreenState homeScreenState: Folio.HomeScreenState
|
||||
|
||||
readonly property bool dropAnimationRunning: delegateDragItem.dropAnimationRunning
|
||||
// non-widget drop animation
|
||||
readonly property bool dropAnimationRunning: delegateDragItem.dropAnimationRunning || widgetDragItem.dropAnimationRunning
|
||||
|
||||
// widget that is currently being dragged (or dropped)
|
||||
readonly property Folio.FolioWidget currentlyDraggedWidget: widgetDragItem.widget
|
||||
|
||||
// how much to scale out in the settings mode
|
||||
readonly property real settingsModeHomeScreenScale: 0.8
|
||||
|
||||
onTopMarginChanged: Folio.HomeScreenState.viewTopPadding = root.topMargin
|
||||
|
|
@ -43,7 +48,9 @@ Item {
|
|||
function prepareStartDelegateDrag(delegate, item) {
|
||||
swipeArea.setSkipSwipeThreshold(true);
|
||||
|
||||
delegateDragItem.delegate = delegate;
|
||||
if (delegate) {
|
||||
delegateDragItem.delegate = delegate;
|
||||
}
|
||||
return root.mapFromItem(item, 0, 0);
|
||||
}
|
||||
|
||||
|
|
@ -82,12 +89,18 @@ Item {
|
|||
onHeightChanged: Folio.HomeScreenState.viewHeight = height;
|
||||
}
|
||||
|
||||
// a way of stopping focus
|
||||
FocusScope {
|
||||
id: noFocus
|
||||
}
|
||||
|
||||
// area that can be swiped
|
||||
MobileShell.SwipeArea {
|
||||
id: swipeArea
|
||||
anchors.fill: parent
|
||||
|
||||
interactive: root.interactive &&
|
||||
settings.homeScreenInteractive &&
|
||||
!appDrawer.flickable.moving &&
|
||||
(appDrawer.flickable.atYBeginning || // disable the swipe area when we are swiping in the app drawer, and not in drag-and-drop
|
||||
Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate ||
|
||||
|
|
@ -104,13 +117,25 @@ Item {
|
|||
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||
}
|
||||
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
// ensures that components like the widget settings overlay close when swiping
|
||||
noFocus.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
SettingsComponent {
|
||||
id: settings
|
||||
anchors.fill: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
opacity: Folio.HomeScreenState.settingsOpenProgress
|
||||
visible: opacity > 0
|
||||
z: 1
|
||||
|
||||
// move the settings out of the way if it is not visible
|
||||
// NOTE: we do this instead of setting visible to false, because
|
||||
// it doesn't mess with widget drag and drop
|
||||
y: (opacity > 0) ? 0 : parent.height
|
||||
|
||||
settingsModeHomeScreenScale: root.settingsModeHomeScreenScale
|
||||
homeScreen: root
|
||||
|
||||
|
|
@ -333,6 +358,11 @@ Item {
|
|||
id: delegateDragItem
|
||||
}
|
||||
|
||||
// drag and drop for widgets
|
||||
WidgetDragItem {
|
||||
id: widgetDragItem
|
||||
}
|
||||
|
||||
// bottom app drawer
|
||||
AppDrawer {
|
||||
id: appDrawer
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@
|
|||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Effects
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.plasma.private.mobileshell.state as MobileShellState
|
||||
import org.kde.plasma.private.mobileshell as MobileShell
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.kirigami 2.10 as Kirigami
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import "./delegate"
|
||||
import "./private"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
|
@ -36,17 +39,48 @@ Item {
|
|||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
property var dropPosition: Folio.HomeScreenState.dragState.candidateDropPosition
|
||||
property var dropDelegate: Folio.HomeScreenState.dragState.dropDelegate
|
||||
property bool dropDelegateIsWidget: dropDelegate && dropDelegate.type === Folio.FolioDelegate.Widget
|
||||
|
||||
// only show if it is an empty spot on this page
|
||||
visible: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
||||
dropPosition.location === Folio.DelegateDragPosition.Pages &&
|
||||
dropPosition.page === root.pageNum &&
|
||||
!dropDelegateIsWidget &&
|
||||
Folio.HomeScreenState.getPageDelegateAt(root.pageNum, dropPosition.pageRow, dropPosition.pageColumn) === null
|
||||
|
||||
x: dropPosition.pageColumn * Folio.HomeScreenState.pageCellWidth
|
||||
y: dropPosition.pageRow * Folio.HomeScreenState.pageCellHeight
|
||||
}
|
||||
|
||||
// square that shows when a widget hovers over a spot to drop a delegate on
|
||||
Rectangle {
|
||||
id: widgetDragDropFeedback
|
||||
width: (dropDelegateIsWidget ? dropDelegate.widget.gridWidth : 0) * Folio.HomeScreenState.pageCellWidth
|
||||
height: (dropDelegateIsWidget ? dropDelegate.widget.gridHeight : 0) * Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
property var dropPosition: Folio.HomeScreenState.dragState.candidateDropPosition
|
||||
property var dropDelegate: Folio.HomeScreenState.dragState.dropDelegate
|
||||
property bool dropDelegateIsWidget: dropDelegate && dropDelegate.type === Folio.FolioDelegate.Widget
|
||||
|
||||
// only show if the widget can be placed here
|
||||
visible: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
||||
dropPosition.location === Folio.DelegateDragPosition.Pages &&
|
||||
dropPosition.page === root.pageNum &&
|
||||
dropDelegateIsWidget &&
|
||||
pageModel.canAddDelegate(dropPosition.pageRow, dropPosition.pageColumn, dropDelegate)
|
||||
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
color: Qt.rgba(255, 255, 255, 0.3)
|
||||
|
||||
x: dropPosition.pageColumn * Folio.HomeScreenState.pageCellWidth
|
||||
y: dropPosition.pageRow * Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DelegateShadow {}
|
||||
}
|
||||
|
||||
// repeater of all delegates in the page
|
||||
Repeater {
|
||||
model: root.pageModel
|
||||
|
||||
|
|
@ -69,10 +103,10 @@ Item {
|
|||
dragState.dropDelegate.type === Folio.FolioDelegate.Application &&
|
||||
isDropPositionThis
|
||||
|
||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
implicitWidth: loader.item ? loader.item.implicitWidth : 0
|
||||
implicitHeight: loader.item ? loader.item.implicitHeight : 0
|
||||
width: loader.item ? loader.item.width : 0
|
||||
height: loader.item ? loader.item.height : 0
|
||||
|
||||
x: column * Folio.HomeScreenState.pageCellWidth
|
||||
y: row * Folio.HomeScreenState.pageCellHeight
|
||||
|
|
@ -81,13 +115,17 @@ Item {
|
|||
column >= 0 && column < Folio.HomeScreenState.pageColumns
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
id: loader
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
|
||||
sourceComponent: {
|
||||
if (delegate.pageDelegate.type === Folio.FolioDelegate.Application) {
|
||||
return appComponent;
|
||||
} else if (delegate.pageDelegate.type === Folio.FolioDelegate.Folder) {
|
||||
return folderComponent;
|
||||
} else if (delegate.pageDelegate.type === Folio.FolioDelegate.Widget) {
|
||||
return widgetComponent;
|
||||
} else {
|
||||
return noneComponent;
|
||||
}
|
||||
|
|
@ -110,6 +148,11 @@ Item {
|
|||
turnToFolder: delegate.isAppHoveredOver
|
||||
turnToFolderAnimEnabled: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate
|
||||
|
||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
// do not show if the drop animation is running to this delegate
|
||||
visible: !(root.homeScreen.dropAnimationRunning && delegate.isDropPositionThis)
|
||||
|
||||
|
|
@ -121,6 +164,8 @@ Item {
|
|||
Folio.HomeScreenState.startDelegatePageDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
appDelegate.pressPosition.x,
|
||||
appDelegate.pressPosition.y,
|
||||
root.pageNum,
|
||||
delegate.pageDelegate.row,
|
||||
delegate.pageDelegate.column
|
||||
|
|
@ -173,6 +218,11 @@ Item {
|
|||
name: Folio.FolioSettings.showPagesAppLabels ? delegate.pageDelegate.folder.name : ""
|
||||
folder: delegate.pageDelegate.folder
|
||||
|
||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
||||
width: Folio.HomeScreenState.pageCellWidth
|
||||
height: Folio.HomeScreenState.pageCellHeight
|
||||
|
||||
// do not show if the drop animation is running to this delegate, and the drop delegate is a folder
|
||||
visible: !(root.homeScreen.dropAnimationRunning &&
|
||||
delegate.isDropPositionThis &&
|
||||
|
|
@ -193,6 +243,8 @@ Item {
|
|||
Folio.HomeScreenState.startDelegatePageDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
appFolderDelegate.pressPosition.x,
|
||||
appFolderDelegate.pressPosition.y,
|
||||
root.pageNum,
|
||||
delegate.pageDelegate.row,
|
||||
delegate.pageDelegate.column
|
||||
|
|
@ -237,6 +289,88 @@ Item {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: widgetComponent
|
||||
|
||||
WidgetDelegate {
|
||||
id: widgetDelegate
|
||||
|
||||
// don't reparent applet if the drop animation is running to this delegate
|
||||
// background: there is only one "visual" instance of the widget, once this delegate loads
|
||||
// it will reparent it to here (but we don't want it to happen while the drop animation is running)
|
||||
property bool suppressAppletReparent: (root.homeScreen.currentlyDraggedWidget === delegate.pageDelegate.widget)
|
||||
&& delegate.isDropPositionThis
|
||||
|
||||
visible: !suppressAppletReparent
|
||||
widget: suppressAppletReparent ? null : delegate.pageDelegate.widget
|
||||
|
||||
onStartEditMode: (pressPoint) => {
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(delegate.pageDelegate, widgetDelegate);
|
||||
Folio.HomeScreenState.startDelegatePageDrag(
|
||||
mappedCoords.x,
|
||||
mappedCoords.y,
|
||||
pressPoint.x - mappedCoords.x,
|
||||
pressPoint.y - mappedCoords.y,
|
||||
root.pageNum,
|
||||
delegate.pageDelegate.row,
|
||||
delegate.pageDelegate.column
|
||||
);
|
||||
|
||||
widgetConfig.startOpen();
|
||||
}
|
||||
|
||||
onPressReleased: {
|
||||
// cancel the event if the delegate is not dragged
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate) {
|
||||
Folio.HomeScreenState.cancelDelegateDrag();
|
||||
widgetConfig.fullyOpen();
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: widgetDelegate.editMode
|
||||
layer.effect: DarkenEffect {}
|
||||
|
||||
PC3.ToolTip {
|
||||
visible: widgetDelegate.editMode && pressed
|
||||
text: i18n('Release to configure, drag to move')
|
||||
}
|
||||
|
||||
WidgetDelegateConfig {
|
||||
id: widgetConfig
|
||||
homeScreen: root.homeScreen
|
||||
|
||||
pageModel: root.pageModel
|
||||
pageDelegate: delegate.pageDelegate
|
||||
widget: delegate.pageDelegate.widget
|
||||
|
||||
pageNum: root.pageNum
|
||||
row: delegate.row
|
||||
column: delegate.column
|
||||
|
||||
widgetWidth: widgetDelegate.widgetWidth
|
||||
widgetHeight: widgetDelegate.widgetHeight
|
||||
widgetX: delegate.x + root.anchors.leftMargin + root.homeScreen.leftMargin
|
||||
widgetY: delegate.y + root.anchors.topMargin + root.homeScreen.topMargin
|
||||
|
||||
topWidgetBackgroundPadding: widgetDelegate.topWidgetBackgroundPadding
|
||||
bottomWidgetBackgroundPadding: widgetDelegate.bottomWidgetBackgroundPadding
|
||||
leftWidgetBackgroundPadding: widgetDelegate.leftWidgetBackgroundPadding
|
||||
rightWidgetBackgroundPadding: widgetDelegate.rightWidgetBackgroundPadding
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
onRemoveRequested: {
|
||||
if (widget.applet) {
|
||||
widget.destroyApplet();
|
||||
}
|
||||
root.pageModel.removeDelegate(delegate.row, delegate.column);
|
||||
}
|
||||
|
||||
onClosed: widgetDelegate.editMode = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.ksvg 1.0 as KSvg
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import './delegate'
|
||||
import './private'
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: widgetLoader.item ? widgetLoader.item.width : 0
|
||||
height: widgetLoader.item ? widgetLoader.item.height : 0
|
||||
|
||||
property Folio.FolioWidget widget
|
||||
|
||||
readonly property bool isWidgetDelegate: Folio.HomeScreenState.dragState.dropDelegate && Folio.HomeScreenState.dragState.dropDelegate.type === Folio.FolioDelegate.Widget
|
||||
readonly property bool dropAnimationRunning: dragXAnim.running || dragYAnim.running
|
||||
|
||||
visible: false
|
||||
x: Math.round(Folio.HomeScreenState.delegateDragX)
|
||||
y: Math.round(Folio.HomeScreenState.delegateDragY)
|
||||
|
||||
function startDrag(widget) {
|
||||
root.widget = widget;
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function setXBinding() {
|
||||
x = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragX));
|
||||
}
|
||||
function setYBinding() {
|
||||
y = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragY));
|
||||
}
|
||||
|
||||
// animate drop x
|
||||
XAnimator on x {
|
||||
id: dragXAnim
|
||||
running: false
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onFinished: {
|
||||
root.visible = false;
|
||||
root.widget = null;
|
||||
root.setXBinding();
|
||||
}
|
||||
}
|
||||
|
||||
// animate drop y
|
||||
YAnimator on y {
|
||||
id: dragYAnim
|
||||
running: false
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.OutCubic
|
||||
onFinished: {
|
||||
root.visible = false;
|
||||
root.widget = null;
|
||||
root.setYBinding();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: stateWatcher
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
||||
Folio.HomeScreenState.dragState.dropDelegate &&
|
||||
Folio.HomeScreenState.dragState.dropDelegate.type === Folio.FolioDelegate.Widget) {
|
||||
|
||||
root.startDrag(Folio.HomeScreenState.dragState.dropDelegate.widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState.dragState
|
||||
|
||||
// animate from when the delegate is dropped to its drop position
|
||||
function onDelegateDroppedAndPlaced() {
|
||||
if (!root.isWidgetDelegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
let dragState = Folio.HomeScreenState.dragState;
|
||||
let dropPosition = dragState.candidateDropPosition;
|
||||
|
||||
let pos = Folio.HomeScreenState.getPageDelegateScreenPosition(dropPosition.page, dropPosition.pageRow, dropPosition.pageColumn);
|
||||
|
||||
dragXAnim.to = pos.x;
|
||||
dragYAnim.to = pos.y;
|
||||
dragXAnim.restart();
|
||||
dragYAnim.restart();
|
||||
}
|
||||
|
||||
// if the drop has been abandoned, just hide
|
||||
function onNewDelegateDropAbandoned() {
|
||||
root.visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: widgetLoader
|
||||
|
||||
active: root.widget
|
||||
|
||||
sourceComponent: WidgetDelegate {
|
||||
widget: root.widget
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: DarkenEffect {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import QtQuick.Effects
|
|||
|
||||
import org.kde.kirigami 2.20 as Kirigami
|
||||
|
||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
||||
import org.kde.kquickcontrolsaddons 2.0
|
||||
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
|
|
|||
|
|
@ -0,0 +1,142 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.ksvg 1.0 as KSvg
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import '../private'
|
||||
|
||||
Folio.WidgetContainer {
|
||||
id: root
|
||||
|
||||
property Folio.FolioWidget widget
|
||||
|
||||
readonly property real widgetWidth: widgetHolder.width
|
||||
readonly property real widgetHeight: widgetHolder.height
|
||||
|
||||
readonly property real topWidgetBackgroundPadding: widgetBackground.margins.top
|
||||
readonly property real bottomWidgetBackgroundPadding: widgetBackground.margins.bottom
|
||||
readonly property real leftWidgetBackgroundPadding: widgetBackground.margins.left
|
||||
readonly property real rightWidgetBackgroundPadding: widgetBackground.margins.right
|
||||
|
||||
implicitWidth: (widget ? widget.gridWidth : 0) * Folio.HomeScreenState.pageCellWidth
|
||||
implicitHeight: (widget ? widget.gridHeight : 0) * Folio.HomeScreenState.pageCellHeight
|
||||
width: implicitWidth
|
||||
height: implicitHeight
|
||||
|
||||
// prevent widget contents from going outside of the container
|
||||
clip: true
|
||||
|
||||
function updateVisualApplet() {
|
||||
if (!widget || !widget.visualApplet) {
|
||||
return;
|
||||
}
|
||||
|
||||
widget.visualApplet.expanded = true;
|
||||
|
||||
widget.visualApplet.parent = widgetHolder;
|
||||
widget.visualApplet.anchors.fill = widgetHolder;
|
||||
if (widget.visualApplet.fullRepresentationItem) {
|
||||
widget.visualApplet.fullRepresentationItem.parent = widgetHolder;
|
||||
widget.visualApplet.fullRepresentationItem.anchors.fill = widgetHolder;
|
||||
}
|
||||
}
|
||||
|
||||
onWidgetChanged: updateVisualApplet()
|
||||
|
||||
Component.onCompleted: {
|
||||
updateVisualApplet();
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: widget
|
||||
|
||||
function onVisualAppletChanged() {
|
||||
if (!widget.visualApplet) {
|
||||
return;
|
||||
}
|
||||
|
||||
root.updateVisualApplet();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: widgetComponent
|
||||
anchors.fill: parent
|
||||
|
||||
KSvg.FrameSvgItem {
|
||||
id: widgetBackground
|
||||
anchors.fill: parent
|
||||
enabledBorders: KSvg.FrameSvgItem.AllBorders
|
||||
imagePath: {
|
||||
if (!root.widget || !root.widget.applet || root.widget.applet.effectiveBackgroundHints === PlasmaCore.Types.NoBackground) {
|
||||
return '';
|
||||
} else if (root.widget.applet.effectiveBackgroundHints & PlasmaCore.Types.StandardBackground) {
|
||||
return 'widgets/background';
|
||||
} else if (root.widget.applet.effectiveBackgroundHints & PlasmaCore.Types.TranslucentBackground) {
|
||||
return 'widgets/translucentbackground';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: temporaryBackground
|
||||
anchors.fill: parent
|
||||
visible: root.widget && !root.widget.applet
|
||||
color: Qt.rgba(255, 255, 255, 0.3)
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
}
|
||||
|
||||
Item {
|
||||
id: widgetHolder
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: (root.widget && root.widget.applet && root.widget.applet.constraintHints === PlasmaCore.Types.CanFillArea) ? 0 : widgetBackground.margins.left
|
||||
anchors.rightMargin: (root.widget && root.widget.applet && root.widget.applet.constraintHints === PlasmaCore.Types.CanFillArea) ? 0 : widgetBackground.margins.right
|
||||
anchors.topMargin: (root.widget && root.widget.applet && root.widget.applet.constraintHints === PlasmaCore.Types.CanFillArea) ? 0 : widgetBackground.margins.top
|
||||
anchors.bottomMargin: (root.widget && root.widget.applet && root.widget.applet.constraintHints === PlasmaCore.Types.CanFillArea) ? 0 : widgetBackground.margins.bottom
|
||||
}
|
||||
|
||||
// TODO implement blur behind, see plasma-workspace BasicAppletContainer for how to do this
|
||||
layer.enabled: root.widget && root.widget.applet && root.widget.applet.effectiveBackgroundHints === PlasmaCore.Types.ShadowBackground
|
||||
layer.effect: DelegateShadow {}
|
||||
|
||||
PC3.Label {
|
||||
id: noWidget
|
||||
visible: root.widget && !root.widget.visualApplet
|
||||
color: 'white'
|
||||
wrapMode: Text.Wrap
|
||||
text: i18n('This widget was not found.')
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
PC3.BusyIndicator {
|
||||
id: loadingIndicator
|
||||
anchors.centerIn: parent
|
||||
visible: root.widget && root.widget.applet && root.widget.applet.busy
|
||||
running: visible
|
||||
}
|
||||
|
||||
PC3.Button {
|
||||
id: configurationRequiredButton
|
||||
anchors.centerIn: parent
|
||||
text: i18n('Configure…')
|
||||
icon.name: 'configure'
|
||||
visible: root.widget && root.widget.applet && root.widget.applet.configurationRequired
|
||||
onClicked: root.widget.applet.internalAction('configure').trigger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.core as PlasmaCore
|
||||
import org.kde.ksvg 1.0 as KSvg
|
||||
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import '../private'
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var homeScreen
|
||||
|
||||
property int pageNum
|
||||
property int row
|
||||
property int column
|
||||
|
||||
property real widgetWidth
|
||||
property real widgetHeight
|
||||
property real widgetX
|
||||
property real widgetY
|
||||
|
||||
property real topWidgetBackgroundPadding
|
||||
property real bottomWidgetBackgroundPadding
|
||||
property real leftWidgetBackgroundPadding
|
||||
property real rightWidgetBackgroundPadding
|
||||
|
||||
property Folio.FolioPageDelegate pageDelegate
|
||||
property Folio.FolioWidget widget
|
||||
property var pageModel
|
||||
|
||||
signal removeRequested()
|
||||
signal closed()
|
||||
|
||||
function startOpen() {
|
||||
configOverlay.open();
|
||||
}
|
||||
|
||||
function fullyOpen() {
|
||||
configPopup.open();
|
||||
configOverlay.close();
|
||||
}
|
||||
|
||||
// HACK: this shows the config when we are in the "press to hold" state, prior to mouse release
|
||||
// we can't just open the popup, because the potential drag-and-drop swipe would get lost
|
||||
MouseArea {
|
||||
id: configOverlay
|
||||
parent: root.homeScreen
|
||||
anchors.fill: parent
|
||||
|
||||
width: configPopup.width
|
||||
height: configPopup.height
|
||||
|
||||
opacity: 0
|
||||
visible: opacity > 0
|
||||
|
||||
// in case this gets stuck open over the homescreen, just close on tap
|
||||
onClicked: close()
|
||||
|
||||
NumberAnimation on opacity { id: configOverlayOpacityAnim; duration: 200 }
|
||||
|
||||
function open() {
|
||||
configOverlayOpacityAnim.to = 1;
|
||||
configOverlayOpacityAnim.restart();
|
||||
}
|
||||
|
||||
function animClose() {
|
||||
if (opacity !== 0) {
|
||||
configOverlayOpacityAnim.to = 0;
|
||||
configOverlayOpacityAnim.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
opacity = 0;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
// if we are starting drag-and-drop, close the menu immediately
|
||||
function onSwipeStateChanged() {
|
||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
||||
configOverlay.animClose();
|
||||
root.closed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the config overlay
|
||||
FastBlur {
|
||||
anchors.fill: parent
|
||||
source: configPopup.contentItem
|
||||
radius: 0
|
||||
}
|
||||
}
|
||||
|
||||
// this is the actual interactive popup for widget settings, only
|
||||
// opened when the user releases their press (and doesn't drag)
|
||||
QQC2.Popup {
|
||||
id: configPopup
|
||||
width: root.homeScreen.width
|
||||
height: root.homeScreen.height
|
||||
parent: root.homeScreen
|
||||
|
||||
onClosed: {
|
||||
configOverlay.close(); // ensure overlay is closed
|
||||
root.closed();
|
||||
}
|
||||
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
|
||||
|
||||
readonly property real barWidth: Kirigami.Units.gridUnit * 3.5
|
||||
readonly property real barSpacing: Kirigami.Units.largeSpacing
|
||||
readonly property real minimumBarLength: Kirigami.Units.gridUnit * 8
|
||||
|
||||
background: Item {}
|
||||
QQC2.Overlay.modal: Item {}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation { property: "opacity"; duration: 200; from: 1.0; to: 0.0 }
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Folio.HomeScreenState
|
||||
|
||||
// don't show config overlay if we have navigated to another page
|
||||
function onCurrentPageChanged() {
|
||||
if (configPopup.visible) {
|
||||
configPopup.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: MouseArea {
|
||||
id: configItem
|
||||
|
||||
onClicked: configPopup.close()
|
||||
|
||||
WidgetResizeHandleFrame {
|
||||
id: resizeFrame
|
||||
anchors.fill: parent
|
||||
|
||||
widgetWidth: root.widgetWidth
|
||||
widgetHeight: root.widgetHeight
|
||||
widgetX: root.widgetX + root.leftWidgetBackgroundPadding
|
||||
widgetY: root.widgetY + root.topWidgetBackgroundPadding
|
||||
|
||||
widgetTopMargin: root.topWidgetBackgroundPadding
|
||||
widgetBottomMargin: root.bottomWidgetBackgroundPadding
|
||||
widgetLeftMargin: root.leftWidgetBackgroundPadding
|
||||
widgetRightMargin: root.rightWidgetBackgroundPadding
|
||||
|
||||
widgetRow: root.row
|
||||
widgetColumn: root.column
|
||||
widgetGridWidth: root.widget.gridWidth
|
||||
widgetGridHeight: root.widget.gridHeight
|
||||
|
||||
onWidgetChangeAfterDrag: (widgetRow, widgetColumn, widgetGridWidth, widgetGridHeight) => {
|
||||
if (resizeFrame.lockDrag !== null) triggerWidgetChanges(widgetRow, widgetColumn, widgetGridWidth, widgetGridHeight);
|
||||
}
|
||||
|
||||
function triggerWidgetChanges(widgetRow, widgetColumn, widgetGridWidth, widgetGridHeight) {
|
||||
root.pageModel.moveAndResizeWidgetDelegate(
|
||||
root.pageDelegate,
|
||||
widgetRow,
|
||||
widgetColumn,
|
||||
widgetGridWidth,
|
||||
widgetGridHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PC3.Button {
|
||||
id: button
|
||||
icon.name: 'settings-configure'
|
||||
text: i18n('Options')
|
||||
|
||||
readonly property var handleContainer: resizeFrame.handleContainer
|
||||
x: Math.round(handleContainer.x + (handleContainer.width / 2) - (width / 2))
|
||||
y: Math.round(handleContainer.y + (handleContainer.height / 2) - (height / 2))
|
||||
|
||||
onClicked: contextMenuDialog.open()
|
||||
}
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: contextMenuDialog
|
||||
preferredWidth: Kirigami.Units.gridUnit * 20
|
||||
padding: 0
|
||||
title: i18n('Widget Options')
|
||||
|
||||
// close parent dialog too
|
||||
onClosed: configPopup.close()
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: root.widget.applet ? [...root.widget.applet.contextualActions, configureAppletAction, removeDelegateAction] : [removeDelegateAction]
|
||||
|
||||
delegate: QQC2.ItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
action: modelData
|
||||
text: modelData.text
|
||||
icon.name: modelData.icon.name
|
||||
|
||||
icon.width: Kirigami.Units.gridUnit
|
||||
icon.height: Kirigami.Units.gridUnit
|
||||
|
||||
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
rightPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
|
||||
onClicked: contextMenuDialog.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
id: removeDelegateAction
|
||||
icon.name: 'edit-delete-remove'
|
||||
text: i18n('Remove widget')
|
||||
onTriggered: root.removeRequested()
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
id: configureAppletAction
|
||||
icon.name: 'settings-configure'
|
||||
text: i18n('Configure widget')
|
||||
onTriggered: root.widget.applet.internalAction('configure').trigger();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
|
||||
MultiEffect {
|
||||
colorization: 0.3
|
||||
colorizationColor: 'black'
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as Controls
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
OpacityMask {
|
||||
id: root
|
||||
|
||||
property var flickable
|
||||
|
||||
source: flickable
|
||||
maskSource: Rectangle {
|
||||
id: mask
|
||||
width: flickable.width
|
||||
height: flickable.height
|
||||
|
||||
property real gradientPct: (Kirigami.Units.gridUnit * 2) / flickable.height
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: flickable.atYBeginning ? 'white' : 'transparent' }
|
||||
GradientStop { position: mask.gradientPct; color: 'white' }
|
||||
GradientStop { position: 1.0 - mask.gradientPct; color: 'white' }
|
||||
GradientStop { position: 1.0; color: flickable.atYEnd ? 'white' : 'transparent' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
enum Orientation {
|
||||
Above,
|
||||
Below,
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
enum Position {
|
||||
TopCenter,
|
||||
LeftCenter,
|
||||
RightCenter,
|
||||
BottomCenter
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
|
||||
import '../delegate'
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
height: 10
|
||||
width: 10
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
property int orientation
|
||||
|
||||
signal dragEvent(real leftEdgeDelta, real rightEdgeDelta, real topEdgeDelta, real bottomEdgeDelta)
|
||||
|
||||
drag {
|
||||
target: root
|
||||
axis: {
|
||||
switch (orientation) {
|
||||
case WidgetHandlePosition.TopCenter:
|
||||
return Drag.YAxis;
|
||||
case WidgetHandlePosition.LeftCenter:
|
||||
return Drag.XAxis;
|
||||
case WidgetHandlePosition.RightCenter:
|
||||
return Drag.XAxis;
|
||||
case WidgetHandlePosition.BottomCenter:
|
||||
return Drag.YAxis;
|
||||
}
|
||||
return Drag.XAndYAxis;
|
||||
}
|
||||
}
|
||||
|
||||
property real pressX
|
||||
property real pressY
|
||||
|
||||
onPressed: {
|
||||
pressX = mouseX;
|
||||
pressY = mouseY;
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
// HACK: need to call it twice to work
|
||||
updateDrag();
|
||||
updateDrag();
|
||||
}
|
||||
|
||||
drag { target: root; axis: Drag.XAndYAxis }
|
||||
|
||||
function updateDrag() {
|
||||
if (!drag.active) return;
|
||||
|
||||
const dx = mouseX;
|
||||
const dy = mouseY;
|
||||
|
||||
switch (orientation) {
|
||||
case WidgetHandlePosition.TopCenter:
|
||||
root.dragEvent(0, 0, -dy, 0);
|
||||
break;
|
||||
case WidgetHandlePosition.LeftCenter:
|
||||
root.dragEvent(-dx, 0, 0, 0);
|
||||
break;
|
||||
case WidgetHandlePosition.RightCenter:
|
||||
root.dragEvent(0, dx, 0, 0);
|
||||
break;
|
||||
case WidgetHandlePosition.BottomCenter:
|
||||
root.dragEvent(0, 0, 0, dy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: 'white'
|
||||
radius: width / 2
|
||||
|
||||
transform: Scale {
|
||||
property real scaleFactor: root.pressed ? 1.2 : 1.0
|
||||
|
||||
Behavior on scaleFactor {
|
||||
NumberAnimation { duration: 400; easing.type: Easing.OutExpo }
|
||||
}
|
||||
|
||||
xScale: scaleFactor
|
||||
yScale: scaleFactor
|
||||
origin.x: root.width / 2
|
||||
origin.y: root.height / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,238 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import Qt5Compat.GraphicalEffects
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
|
||||
import '../delegate'
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// given by parent:
|
||||
|
||||
property real widgetWidth
|
||||
property real widgetHeight
|
||||
property real widgetX
|
||||
property real widgetY
|
||||
|
||||
property real widgetTopMargin
|
||||
property real widgetBottomMargin
|
||||
property real widgetLeftMargin
|
||||
property real widgetRightMargin
|
||||
|
||||
property int widgetRow
|
||||
property int widgetColumn
|
||||
property int widgetGridWidth
|
||||
property int widgetGridHeight
|
||||
|
||||
// filled here, given to parent:
|
||||
|
||||
// what the drag intends for the dimensions and position of the widget
|
||||
property int widgetRowAfterDrag: 0
|
||||
property int widgetColumnAfterDrag: 0
|
||||
property int widgetGridWidthAfterDrag: 0
|
||||
property int widgetGridHeightAfterDrag: 0
|
||||
|
||||
property var lockDrag: null
|
||||
|
||||
property alias handleContainer: handleContainer
|
||||
|
||||
signal widgetChangeAfterDrag(int widgetRow, int widgetColumn, int widgetGridWidth, int widgetGridHeight)
|
||||
|
||||
// solely used here:
|
||||
|
||||
property real startDragWidth: 0
|
||||
property real startDragHeight: 0
|
||||
property real startX: 0
|
||||
property real startY: 0
|
||||
|
||||
property int startWidgetRow: 0
|
||||
property int startWidgetColumn: 0
|
||||
|
||||
onWidgetWidthChanged: {
|
||||
if (lockDrag === null) updateDimensions();
|
||||
}
|
||||
onWidgetHeightChanged: {
|
||||
if (lockDrag === null) updateDimensions();
|
||||
}
|
||||
onWidgetXChanged: {
|
||||
if (lockDrag === null) updateDimensions();
|
||||
}
|
||||
onWidgetYChanged: {
|
||||
if (lockDrag === null) updateDimensions();
|
||||
}
|
||||
|
||||
function updateDimensions() {
|
||||
handleContainer.width = widgetWidth;
|
||||
handleContainer.height = widgetHeight;
|
||||
handleContainer.x = widgetX;
|
||||
handleContainer.y = widgetY;
|
||||
}
|
||||
|
||||
function startDrag() {
|
||||
startDragWidth = handleContainer.width;
|
||||
startDragHeight = handleContainer.height;
|
||||
startX = handleContainer.x;
|
||||
startY = handleContainer.y;
|
||||
|
||||
startWidgetRow = root.widgetRow;
|
||||
startWidgetColumn = root.widgetColumn;
|
||||
|
||||
root.widgetChangeAfterDrag(startWidgetRow, startWidgetColumn, root.widgetGridWidth, root.widgetGridHeight);
|
||||
}
|
||||
|
||||
function snapEdges() {
|
||||
lockDrag = null;
|
||||
|
||||
// snaps the bounds to what we ended up at
|
||||
widthAnim.to = widgetWidth;
|
||||
widthAnim.restart();
|
||||
heightAnim.to = widgetHeight;
|
||||
heightAnim.restart();
|
||||
xAnim.to = widgetX;
|
||||
xAnim.restart();
|
||||
yAnim.to = widgetY;
|
||||
yAnim.restart();
|
||||
}
|
||||
|
||||
function pressedHandler(orientation) {
|
||||
if (root.lockDrag !== orientation) {
|
||||
root.startDrag();
|
||||
root.lockDrag = orientation;
|
||||
}
|
||||
}
|
||||
|
||||
function dragHandler(orientation, leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta) {
|
||||
if (root.lockDrag === orientation) {
|
||||
// update the handle container dimensions and position
|
||||
handleContainer.x = root.startX - leftEdgeDelta;
|
||||
handleContainer.y = root.startY - topEdgeDelta;
|
||||
handleContainer.width = root.startDragWidth + rightEdgeDelta + leftEdgeDelta;
|
||||
handleContainer.height = root.startDragHeight + bottomEdgeDelta + topEdgeDelta;
|
||||
|
||||
// update the widget dimensions and position
|
||||
const columnsMovedRight = Math.round((handleContainer.x - root.startX) / Folio.HomeScreenState.pageCellWidth);
|
||||
const rowsMovedDown = Math.round((handleContainer.y - root.startY) / Folio.HomeScreenState.pageCellHeight);
|
||||
|
||||
const realWidgetWidth = handleContainer.width + widgetLeftMargin + widgetRightMargin;
|
||||
const realWidgetHeight = handleContainer.height + widgetTopMargin + widgetBottomMargin;
|
||||
|
||||
const widgetRowAfterDrag = startWidgetRow + rowsMovedDown;
|
||||
const widgetColumnAfterDrag = startWidgetColumn + columnsMovedRight;
|
||||
const widgetGridWidthAfterDrag = Math.round(realWidgetWidth / Folio.HomeScreenState.pageCellWidth);
|
||||
const widgetGridHeightAfterDrag = Math.round(realWidgetHeight / Folio.HomeScreenState.pageCellHeight);
|
||||
|
||||
root.widgetChangeAfterDrag(widgetRowAfterDrag, widgetColumnAfterDrag, widgetGridWidthAfterDrag, widgetGridHeightAfterDrag);
|
||||
}
|
||||
}
|
||||
|
||||
function releaseHandler(orientation) {
|
||||
if (root.lockDrag === orientation) {
|
||||
root.snapEdges();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: handleContainer
|
||||
|
||||
NumberAnimation on width {
|
||||
id: widthAnim
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation on height {
|
||||
id: heightAnim
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation on x {
|
||||
id: xAnim
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
|
||||
NumberAnimation on y {
|
||||
id: yAnim
|
||||
duration: 200
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: resizeOutline
|
||||
color: 'transparent'
|
||||
border.color: 'white'
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
border.width: 1
|
||||
|
||||
anchors.fill: handleContainer
|
||||
anchors.leftMargin: -root.widgetLeftMargin
|
||||
anchors.rightMargin: -root.widgetRightMargin
|
||||
anchors.topMargin: -root.widgetTopMargin
|
||||
anchors.bottomMargin: -root.widgetBottomMargin
|
||||
}
|
||||
|
||||
WidgetResizeHandle {
|
||||
id: topHandle
|
||||
orientation: WidgetHandlePosition.TopCenter
|
||||
|
||||
x: resizeOutline.x + Math.round(resizeOutline.width / 2) - Math.round(width / 2)
|
||||
y: resizeOutline.y - Math.round(height / 2)
|
||||
|
||||
width: Math.round(Math.max(height, resizeOutline.width * 0.3))
|
||||
|
||||
onPressed: pressedHandler(orientation)
|
||||
onDragEvent: (leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta) => dragHandler(orientation, leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta)
|
||||
onReleased: releaseHandler(orientation)
|
||||
}
|
||||
|
||||
WidgetResizeHandle {
|
||||
id: leftHandle
|
||||
orientation: WidgetHandlePosition.LeftCenter
|
||||
|
||||
x: resizeOutline.x - (width / 2)
|
||||
y: resizeOutline.y + (resizeOutline.height / 2) - (height / 2)
|
||||
|
||||
height: Math.round(Math.max(width, resizeOutline.height * 0.3))
|
||||
|
||||
onPressed: pressedHandler(orientation)
|
||||
onDragEvent: (leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta) => dragHandler(orientation, leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta)
|
||||
onReleased: releaseHandler(orientation)
|
||||
}
|
||||
|
||||
WidgetResizeHandle {
|
||||
id: rightHandle
|
||||
orientation: WidgetHandlePosition.RightCenter
|
||||
|
||||
x: resizeOutline.x + resizeOutline.width - (width / 2)
|
||||
y: resizeOutline.y + (resizeOutline.height / 2) - (height / 2)
|
||||
|
||||
height: Math.round(Math.max(width, resizeOutline.height * 0.3))
|
||||
|
||||
onPressed: pressedHandler(orientation)
|
||||
onDragEvent: (leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta) => dragHandler(orientation, leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta)
|
||||
onReleased: releaseHandler(orientation)
|
||||
}
|
||||
|
||||
WidgetResizeHandle {
|
||||
id: bottomHandle
|
||||
orientation: WidgetHandlePosition.BottomCenter
|
||||
|
||||
x: resizeOutline.x + (resizeOutline.width / 2) - (width / 2)
|
||||
y: resizeOutline.y + resizeOutline.height - (height / 2)
|
||||
|
||||
width: Math.round(Math.max(height, resizeOutline.width * 0.3))
|
||||
|
||||
onPressed: pressedHandler(orientation)
|
||||
onDragEvent: (leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta) => dragHandler(orientation, leftEdgeDelta, rightEdgeDelta, topEdgeDelta, bottomEdgeDelta)
|
||||
onReleased: releaseHandler(orientation)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.plasma.plasmoid
|
||||
import org.kde.plasma.private.shell 2.0
|
||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
||||
import org.kde.kirigamiaddons.formcard 1.0 as FormCard
|
||||
import org.kde.plasma.components 3.0 as PC3
|
||||
|
||||
import '../delegate'
|
||||
import '../private'
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
||||
property var homeScreen
|
||||
|
||||
signal requestClose()
|
||||
onClicked: root.requestClose()
|
||||
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.7)
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
id: heading
|
||||
color: 'white'
|
||||
text: i18n("Widgets")
|
||||
font.weight: Font.Bold
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 1.5
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Kirigami.Units.gridUnit * 3 + root.homeScreen.topMargin
|
||||
}
|
||||
|
||||
GridView {
|
||||
id: gridView
|
||||
clip: true
|
||||
reuseItems: true
|
||||
|
||||
opacity: 0 // we display with the opacity gradient below
|
||||
|
||||
anchors.top: heading.bottom
|
||||
anchors.topMargin: Kirigami.Units.gridUnit
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.homeScreen.leftMargin
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: root.homeScreen.rightMargin
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.homeScreen.bottomMargin
|
||||
|
||||
model: widgetExplorer.widgetsModel
|
||||
|
||||
readonly property real maxCellWidth: Kirigami.Units.gridUnit * 20
|
||||
readonly property real intendedCellWidth: Kirigami.Units.gridUnit * 8
|
||||
readonly property int columns: Math.min(5, (width - leftMargin - rightMargin) / intendedCellWidth)
|
||||
|
||||
cellWidth: (width - leftMargin - rightMargin) / columns
|
||||
cellHeight: cellWidth + Kirigami.Units.gridUnit * 3
|
||||
|
||||
readonly property real horizontalMargin: Math.round(width * 0.05)
|
||||
leftMargin: horizontalMargin
|
||||
rightMargin: horizontalMargin
|
||||
|
||||
MouseArea {
|
||||
z: -1
|
||||
anchors.fill: parent
|
||||
onClicked: root.requestClose()
|
||||
}
|
||||
|
||||
delegate: MouseArea {
|
||||
id: delegate
|
||||
width: gridView.cellWidth
|
||||
height: gridView.cellHeight
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
|
||||
readonly property string pluginName: model.pluginName
|
||||
|
||||
onPressAndHold: {
|
||||
root.requestClose();
|
||||
Folio.HomeScreenState.closeSettingsView();
|
||||
|
||||
let mappedCoords = root.homeScreen.prepareStartDelegateDrag(null, delegate);
|
||||
const widthOffset = Folio.HomeScreenState.pageCellWidth / 2;
|
||||
const heightOffset = Folio.HomeScreenState.pageCellHeight / 2;
|
||||
|
||||
Folio.HomeScreenState.startDelegateWidgetListDrag(
|
||||
mappedCoords.x + mouseX - widthOffset,
|
||||
mappedCoords.y + mouseY - heightOffset,
|
||||
widthOffset,
|
||||
heightOffset,
|
||||
pluginName
|
||||
);
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
color: Qt.rgba(255, 255, 255, 0.3)
|
||||
visible: delegate.containsMouse
|
||||
radius: Kirigami.Units.smallSpacing
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Kirigami.Units.largeSpacing
|
||||
|
||||
Item {
|
||||
id: iconWidget
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: delegate.width
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.large
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.large
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
Kirigami.Icon {
|
||||
anchors.centerIn: parent
|
||||
source: model.decoration
|
||||
visible: model.screenshot == ""
|
||||
implicitWidth: Kirigami.Units.iconSizes.large
|
||||
implicitHeight: Kirigami.Units.iconSizes.large
|
||||
}
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: model.screenshot
|
||||
width: Kirigami.Units.iconSizes.large
|
||||
height: Kirigami.Units.iconSizes.large
|
||||
}
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
id: heading
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: delegate.width
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
text: model.name
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.weight: Font.Bold
|
||||
}
|
||||
|
||||
PC3.Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: delegate.width
|
||||
Layout.alignment: Qt.AlignTop
|
||||
// otherwise causes binding loop due to the way the Plasma sets the height
|
||||
height: implicitHeight
|
||||
text: model.description
|
||||
font.pointSize: Kirigami.Theme.smallFont.pointSize
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: heading.lineCount === 1 ? 3 : 2
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// opacity gradient at grid edges
|
||||
FlickableOpacityGradient {
|
||||
anchors.fill: gridView
|
||||
flickable: gridView
|
||||
}
|
||||
|
||||
WidgetExplorer {
|
||||
id: widgetExplorer
|
||||
containment: Plasmoid
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,8 @@ Item {
|
|||
property var homeScreen
|
||||
property real settingsModeHomeScreenScale
|
||||
|
||||
readonly property bool homeScreenInteractive: !appletListViewer.open
|
||||
|
||||
signal requestLeaveSettingsMode()
|
||||
|
||||
MouseArea {
|
||||
|
|
@ -80,17 +82,42 @@ Item {
|
|||
|
||||
PC3.ToolButton {
|
||||
text: 'Widgets'
|
||||
enabled: false
|
||||
display: PC3.ToolButton.TextUnderIcon
|
||||
|
||||
icon.name: 'widget-alternatives'
|
||||
|
||||
implicitHeight: Kirigami.Units.gridUnit * 4
|
||||
implicitWidth: Kirigami.Units.gridUnit * 5
|
||||
|
||||
onClicked: {
|
||||
appletListViewer.open = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppletListViewer {
|
||||
id: appletListViewer
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
property bool open: false
|
||||
onRequestClose: open = false
|
||||
|
||||
opacity: open ? 1 : 0
|
||||
|
||||
// move the settings out of the way if it is not visible
|
||||
// NOTE: we do this instead of setting visible to false, because
|
||||
// it doesn't mess with widget drag and drop
|
||||
y: (opacity === 0) ? appletListViewer.height : 0
|
||||
|
||||
homeScreen: root.homeScreen
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation { duration: Kirigami.Units.shortDuration }
|
||||
}
|
||||
}
|
||||
|
||||
SettingsWindow {
|
||||
id: settingsWindow
|
||||
visible: false
|
||||
|
|
|
|||
|
|
@ -105,24 +105,24 @@ QJsonArray PageListModel::exportToJson()
|
|||
|
||||
void PageListModel::save()
|
||||
{
|
||||
if (!m_applet) {
|
||||
if (!m_containment) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray arr = exportToJson();
|
||||
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
||||
|
||||
m_applet->config().writeEntry("Pages", QString::fromStdString(data.toStdString()));
|
||||
Q_EMIT m_applet->configNeedsSaving();
|
||||
m_containment->config().writeEntry("Pages", QString::fromStdString(data.toStdString()));
|
||||
Q_EMIT m_containment->configNeedsSaving();
|
||||
}
|
||||
|
||||
void PageListModel::load()
|
||||
{
|
||||
if (!m_applet) {
|
||||
if (!m_containment) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pages", "{}").toUtf8());
|
||||
QJsonDocument doc = QJsonDocument::fromJson(m_containment->config().readEntry("Pages", "{}").toUtf8());
|
||||
loadFromJson(doc.array());
|
||||
}
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ void PageListModel::loadFromJson(QJsonArray arr)
|
|||
}
|
||||
}
|
||||
|
||||
void PageListModel::setApplet(Plasma::Applet *applet)
|
||||
void PageListModel::setContainment(Plasma::Containment *containment)
|
||||
{
|
||||
m_applet = applet;
|
||||
m_containment = containment;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include <QAbstractListModel>
|
||||
#include <QList>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
#include <Plasma/Containment>
|
||||
|
||||
class PageListModel : public QAbstractListModel
|
||||
{
|
||||
|
|
@ -38,7 +38,7 @@ public:
|
|||
Q_INVOKABLE void load();
|
||||
void loadFromJson(QJsonArray arr);
|
||||
|
||||
void setApplet(Plasma::Applet *applet);
|
||||
void setContainment(Plasma::Containment *containment);
|
||||
|
||||
Q_SIGNALS:
|
||||
void lengthChanged();
|
||||
|
|
@ -46,5 +46,5 @@ Q_SIGNALS:
|
|||
private:
|
||||
QList<PageModel *> m_pages;
|
||||
|
||||
Plasma::Applet *m_applet{nullptr};
|
||||
Plasma::Containment *m_containment{nullptr};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "pagemodel.h"
|
||||
#include "foliosettings.h"
|
||||
#include "homescreenstate.h"
|
||||
#include "widgetsmanager.h"
|
||||
|
||||
FolioPageDelegate::FolioPageDelegate(int row, int column, QObject *parent)
|
||||
: FolioDelegate{parent}
|
||||
|
|
@ -29,6 +30,14 @@ FolioPageDelegate::FolioPageDelegate(int row, int column, FolioApplicationFolder
|
|||
init();
|
||||
}
|
||||
|
||||
FolioPageDelegate::FolioPageDelegate(int row, int column, FolioWidget *widget, QObject *parent)
|
||||
: FolioDelegate{widget, parent}
|
||||
, m_row{row}
|
||||
, m_column{column}
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
FolioPageDelegate::FolioPageDelegate(int row, int column, FolioDelegate *delegate, QObject *parent)
|
||||
: FolioDelegate{parent}
|
||||
, m_row{row}
|
||||
|
|
@ -37,6 +46,7 @@ FolioPageDelegate::FolioPageDelegate(int row, int column, FolioDelegate *delegat
|
|||
m_type = delegate->type();
|
||||
m_application = delegate->application();
|
||||
m_folder = delegate->folder();
|
||||
m_widget = delegate->widget();
|
||||
|
||||
init();
|
||||
}
|
||||
|
|
@ -52,20 +62,46 @@ void FolioPageDelegate::init()
|
|||
case HomeScreenState::RotateClockwise:
|
||||
m_realRow = HomeScreenState::self()->pageColumns() - m_column - 1;
|
||||
m_realColumn = m_row;
|
||||
|
||||
if (m_widget) {
|
||||
// since top-left in cw is bottom-left in portrait
|
||||
m_realRow -= m_widget->realGridHeight() - 1;
|
||||
}
|
||||
|
||||
break;
|
||||
case HomeScreenState::RotateCounterClockwise: // (0, 4) -> (4, 3)
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
m_realRow = m_column;
|
||||
m_realColumn = HomeScreenState::self()->pageRows() - m_row - 1;
|
||||
|
||||
if (m_widget) {
|
||||
// since top-left in ccw is top-right in portrait
|
||||
m_realColumn -= m_widget->realGridWidth() - 1;
|
||||
}
|
||||
|
||||
break;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
m_realRow = HomeScreenState::self()->pageRows() - m_row - 1;
|
||||
m_realColumn = HomeScreenState::self()->pageColumns() - m_column - 1;
|
||||
|
||||
if (m_widget) {
|
||||
// since top-left in upside-down is bottom-right in portrait
|
||||
m_realRow -= m_widget->realGridHeight() - 1;
|
||||
m_realColumn -= m_widget->realGridWidth() - 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_widget) {
|
||||
connect(m_widget, &FolioWidget::realTopLeftPositionChanged, this, [this](int rowOffset, int columnOffset) {
|
||||
m_realRow += rowOffset;
|
||||
m_realColumn += columnOffset;
|
||||
});
|
||||
}
|
||||
|
||||
connect(HomeScreenState::self(), &HomeScreenState::pageOrientationChanged, this, [this]() {
|
||||
setRow(getTranslatedRow(m_realRow, m_realColumn));
|
||||
setColumn(getTranslatedColumn(m_realRow, m_realColumn));
|
||||
setRowOnly(getTranslatedTopLeftRow(m_realRow, m_realColumn, this));
|
||||
setColumnOnly(getTranslatedTopLeftColumn(m_realRow, m_realColumn, this));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -80,8 +116,8 @@ FolioPageDelegate *FolioPageDelegate::fromJson(QJsonObject &obj, QObject *parent
|
|||
int realRow = obj[QStringLiteral("row")].toInt();
|
||||
int realColumn = obj[QStringLiteral("column")].toInt();
|
||||
|
||||
int row = getTranslatedRow(realRow, realColumn);
|
||||
int column = getTranslatedColumn(realRow, realColumn);
|
||||
int row = getTranslatedTopLeftRow(realRow, realColumn, fd);
|
||||
int column = getTranslatedTopLeftColumn(realRow, realColumn, fd);
|
||||
|
||||
FolioPageDelegate *delegate = new FolioPageDelegate{row, column, fd, parent};
|
||||
fd->deleteLater();
|
||||
|
|
@ -89,6 +125,32 @@ FolioPageDelegate *FolioPageDelegate::fromJson(QJsonObject &obj, QObject *parent
|
|||
return delegate;
|
||||
}
|
||||
|
||||
int FolioPageDelegate::getTranslatedTopLeftRow(int realRow, int realColumn, FolioDelegate *fd)
|
||||
{
|
||||
int row = getTranslatedRow(realRow, realColumn);
|
||||
int column = getTranslatedColumn(realRow, realColumn);
|
||||
|
||||
// special logic to return "top left" for widgets, since they take more than one tile
|
||||
if (fd->type() == FolioDelegate::Widget) {
|
||||
return fd->widget()->topLeftCorner(row, column).row;
|
||||
} else {
|
||||
return row;
|
||||
}
|
||||
}
|
||||
|
||||
int FolioPageDelegate::getTranslatedTopLeftColumn(int realRow, int realColumn, FolioDelegate *fd)
|
||||
{
|
||||
int row = getTranslatedRow(realRow, realColumn);
|
||||
int column = getTranslatedColumn(realRow, realColumn);
|
||||
|
||||
// special logic to return "top left" for widgets, since they take more than one tile
|
||||
if (fd->type() == FolioDelegate::Widget) {
|
||||
return fd->widget()->topLeftCorner(row, column).column;
|
||||
} else {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
|
||||
int FolioPageDelegate::getTranslatedRow(int realRow, int realColumn)
|
||||
{
|
||||
// we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState
|
||||
|
|
@ -136,8 +198,33 @@ int FolioPageDelegate::row()
|
|||
|
||||
void FolioPageDelegate::setRow(int row)
|
||||
{
|
||||
m_row = row;
|
||||
Q_EMIT rowChanged();
|
||||
if (m_row != row) {
|
||||
// adjust stored data too
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
m_realRow = row;
|
||||
break;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
m_realColumn += row - m_row;
|
||||
break;
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
m_realColumn += m_row - row;
|
||||
break;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
m_realRow += m_row - row;
|
||||
break;
|
||||
}
|
||||
|
||||
setRowOnly(row);
|
||||
}
|
||||
}
|
||||
|
||||
void FolioPageDelegate::setRowOnly(int row)
|
||||
{
|
||||
if (m_row != row) {
|
||||
m_row = row;
|
||||
Q_EMIT rowChanged();
|
||||
}
|
||||
}
|
||||
|
||||
int FolioPageDelegate::column()
|
||||
|
|
@ -147,14 +234,51 @@ int FolioPageDelegate::column()
|
|||
|
||||
void FolioPageDelegate::setColumn(int column)
|
||||
{
|
||||
m_column = column;
|
||||
Q_EMIT columnChanged();
|
||||
if (m_column != column) {
|
||||
// adjust stored data too
|
||||
switch (HomeScreenState::self()->pageOrientation()) {
|
||||
case HomeScreenState::RegularPosition:
|
||||
m_realColumn = column;
|
||||
break;
|
||||
case HomeScreenState::RotateClockwise:
|
||||
m_realRow += m_column - column;
|
||||
break;
|
||||
case HomeScreenState::RotateCounterClockwise:
|
||||
m_realRow += column - m_column;
|
||||
break;
|
||||
case HomeScreenState::RotateUpsideDown:
|
||||
m_realColumn += m_column - column;
|
||||
break;
|
||||
}
|
||||
|
||||
setColumnOnly(column);
|
||||
}
|
||||
}
|
||||
|
||||
void FolioPageDelegate::setColumnOnly(int column)
|
||||
{
|
||||
if (m_column != column) {
|
||||
m_column = column;
|
||||
Q_EMIT columnChanged();
|
||||
}
|
||||
}
|
||||
|
||||
PageModel::PageModel(QList<FolioPageDelegate *> delegates, QObject *parent)
|
||||
: QAbstractListModel{parent}
|
||||
, m_delegates{delegates}
|
||||
{
|
||||
connect(WidgetsManager::self(), &WidgetsManager::widgetRemoved, this, [this](Plasma::Applet *applet) {
|
||||
if (applet) {
|
||||
// delete any instance of this widget
|
||||
for (int i = 0; i < m_delegates.size(); i++) {
|
||||
auto *delegate = m_delegates[i];
|
||||
if (delegate->type() == FolioDelegate::Widget && delegate->widget()->applet() == applet) {
|
||||
removeDelegate(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PageModel::~PageModel() = default;
|
||||
|
|
@ -162,7 +286,6 @@ PageModel::~PageModel() = default;
|
|||
PageModel *PageModel::fromJson(QJsonArray &arr, QObject *parent)
|
||||
{
|
||||
QList<FolioPageDelegate *> delegates;
|
||||
QList<FolioPageDelegate *> folderDelegates;
|
||||
|
||||
for (QJsonValueRef r : arr) {
|
||||
QJsonObject obj = r.toObject();
|
||||
|
|
@ -170,18 +293,14 @@ PageModel *PageModel::fromJson(QJsonArray &arr, QObject *parent)
|
|||
FolioPageDelegate *delegate = FolioPageDelegate::fromJson(obj, parent);
|
||||
if (delegate) {
|
||||
delegates.append(delegate);
|
||||
|
||||
if (delegate->type() == FolioDelegate::Folder) {
|
||||
folderDelegates.append(delegate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageModel *model = new PageModel{delegates, parent};
|
||||
|
||||
// ensure folders request saves
|
||||
for (auto *delegate : folderDelegates) {
|
||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, model, &PageModel::save);
|
||||
// ensure delegates can request saves
|
||||
for (auto *delegate : delegates) {
|
||||
model->connectSaveRequests(delegate);
|
||||
}
|
||||
|
||||
return model;
|
||||
|
|
@ -229,42 +348,87 @@ QHash<int, QByteArray> PageModel::roleNames() const
|
|||
|
||||
void PageModel::removeDelegate(int row, int col)
|
||||
{
|
||||
bool removed = false;
|
||||
|
||||
for (int i = 0; i < m_delegates.size(); ++i) {
|
||||
if (m_delegates[i]->row() == row && m_delegates[i]->column() == col) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
|
||||
m_delegates.removeAt(i);
|
||||
endRemoveRows();
|
||||
removeDelegate(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removed = true;
|
||||
void PageModel::removeDelegate(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_delegates.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
|
||||
m_delegates.removeAt(index);
|
||||
endRemoveRows();
|
||||
|
||||
save();
|
||||
}
|
||||
|
||||
bool PageModel::canAddDelegate(int row, int column, FolioDelegate *delegate)
|
||||
{
|
||||
if (row < 0 || row >= HomeScreenState::self()->pageRows() || column < 0 || column >= HomeScreenState::self()->pageColumns()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (delegate->type() == FolioDelegate::Widget) {
|
||||
// inserting a widget...
|
||||
|
||||
// bounds of widget
|
||||
int maxRow = row + delegate->widget()->gridHeight() - 1;
|
||||
int maxColumn = column + delegate->widget()->gridWidth() - 1;
|
||||
|
||||
// check bounds
|
||||
if ((row < 0 || row >= HomeScreenState::self()->pageRows()) || (maxRow < 0 || maxRow >= HomeScreenState::self()->pageRows())
|
||||
|| (column < 0 || column >= HomeScreenState::self()->pageColumns()) || (maxColumn < 0 || maxColumn >= HomeScreenState::self()->pageColumns())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if any delegate exists at any of the spots where the widget is being added
|
||||
for (FolioPageDelegate *d : m_delegates) {
|
||||
if (delegate->widget()->isInBounds(row, column, d->row(), d->column())) {
|
||||
return false;
|
||||
} else if (d->type() == FolioDelegate::Widget) {
|
||||
// 2 widgets overlapping scenario
|
||||
if (d->widget()->overlapsWidget(d->row(), d->column(), delegate->widget(), row, column)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// inserting app or folder...
|
||||
|
||||
// check if there already exists a delegate in this space
|
||||
for (FolioPageDelegate *d : m_delegates) {
|
||||
if (d->row() == row && d->column() == column) {
|
||||
return false;
|
||||
} else if (d->type() == FolioDelegate::Widget && d->widget()->isInBounds(d->row(), d->column(), row, column)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PageModel::addDelegate(FolioPageDelegate *delegate)
|
||||
{
|
||||
if (delegate->row() < 0 || delegate->row() >= HomeScreenState::self()->pageRows() || delegate->column() < 0
|
||||
|| delegate->column() >= HomeScreenState::self()->pageColumns()) {
|
||||
if (!canAddDelegate(delegate->row(), delegate->column(), delegate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check if there already exists a delegate in this space
|
||||
for (FolioPageDelegate *d : m_delegates) {
|
||||
if (d->row() == delegate->row() && d->column() == delegate->column()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
beginInsertRows(QModelIndex(), m_delegates.size(), m_delegates.size());
|
||||
m_delegates.append(delegate);
|
||||
endInsertRows();
|
||||
|
||||
// ensure the delegate requests saves
|
||||
connectSaveRequests(delegate);
|
||||
save();
|
||||
|
||||
return true;
|
||||
|
|
@ -276,15 +440,69 @@ FolioPageDelegate *PageModel::getDelegate(int row, int col)
|
|||
if (d->row() == row && d->column() == col) {
|
||||
return d;
|
||||
}
|
||||
|
||||
// check if this is in a widget's space
|
||||
if (d->type() == FolioDelegate::Widget) {
|
||||
if (d->widget()->isInBounds(d->row(), d->column(), row, col)) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PageModel::moveAndResizeWidgetDelegate(FolioPageDelegate *delegate, int newRow, int newColumn, int newGridWidth, int newGridHeight)
|
||||
{
|
||||
if (delegate->type() != FolioDelegate::Widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (newGridWidth < 1 || newGridHeight < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// test if we can add the delegate with new size and position
|
||||
FolioWidget *testWidget = new FolioWidget(this, 0, 0, 0);
|
||||
// we have to use setGridWidth and setGridHeight since it takes into account the page orientation
|
||||
testWidget->setGridWidth(newGridWidth);
|
||||
testWidget->setGridHeight(newGridHeight);
|
||||
FolioDelegate *testDelegate = new FolioDelegate(testWidget, this);
|
||||
|
||||
// NOT THREAD SAFE!
|
||||
// which is fine, because the GUI isn't multithreaded
|
||||
int index = m_delegates.indexOf(delegate);
|
||||
m_delegates.remove(index); // remove the delegate temporarily, since we don't want it to check overlapping of itself
|
||||
bool canAdd = canAddDelegate(newRow, newColumn, testDelegate);
|
||||
m_delegates.insert(index, delegate); // add it back
|
||||
|
||||
// cleanup test delegate
|
||||
testDelegate->deleteLater();
|
||||
testWidget->deleteLater();
|
||||
|
||||
if (!canAdd) {
|
||||
return;
|
||||
}
|
||||
|
||||
delegate->setRow(newRow);
|
||||
delegate->setColumn(newColumn);
|
||||
delegate->widget()->setGridWidth(newGridWidth);
|
||||
delegate->widget()->setGridHeight(newGridHeight);
|
||||
}
|
||||
|
||||
bool PageModel::isPageEmpty()
|
||||
{
|
||||
return m_delegates.size() == 0;
|
||||
}
|
||||
|
||||
void PageModel::connectSaveRequests(FolioDelegate *delegate)
|
||||
{
|
||||
if (delegate->type() == FolioDelegate::Folder && delegate->folder()) {
|
||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, this, &PageModel::save);
|
||||
} else if (delegate->type() == FolioDelegate::Widget && delegate->widget()) {
|
||||
connect(delegate->widget(), &FolioWidget::saveRequested, this, &PageModel::save);
|
||||
}
|
||||
}
|
||||
|
||||
void PageModel::save()
|
||||
{
|
||||
Q_EMIT saveRequested();
|
||||
|
|
|
|||
|
|
@ -23,9 +23,12 @@ public:
|
|||
FolioPageDelegate(int row = 0, int column = 0, QObject *parent = nullptr);
|
||||
FolioPageDelegate(int row, int column, FolioApplication *application, QObject *parent);
|
||||
FolioPageDelegate(int row, int column, FolioApplicationFolder *folder, QObject *parent);
|
||||
FolioPageDelegate(int row, int column, FolioWidget *widget, QObject *parent);
|
||||
FolioPageDelegate(int row, int column, FolioDelegate *delegate, QObject *parent);
|
||||
|
||||
static FolioPageDelegate *fromJson(QJsonObject &obj, QObject *parent);
|
||||
static int getTranslatedTopLeftRow(int realRow, int realColumn, FolioDelegate *fd);
|
||||
static int getTranslatedTopLeftColumn(int realRow, int realColumn, FolioDelegate *fd);
|
||||
static int getTranslatedRow(int realRow, int realColumn);
|
||||
static int getTranslatedColumn(int realRow, int realColumn);
|
||||
|
||||
|
|
@ -42,6 +45,8 @@ Q_SIGNALS:
|
|||
void columnChanged();
|
||||
|
||||
private:
|
||||
void setRowOnly(int row);
|
||||
void setColumnOnly(int column);
|
||||
void init();
|
||||
|
||||
int m_realRow;
|
||||
|
|
@ -73,9 +78,13 @@ public:
|
|||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void removeDelegate(int row, int col);
|
||||
Q_INVOKABLE void removeDelegate(int index);
|
||||
Q_INVOKABLE bool canAddDelegate(int row, int column, FolioDelegate *delegate);
|
||||
bool addDelegate(FolioPageDelegate *delegate);
|
||||
FolioPageDelegate *getDelegate(int row, int col);
|
||||
|
||||
Q_INVOKABLE void moveAndResizeWidgetDelegate(FolioPageDelegate *delegate, int newRow, int newColumn, int newGridWidth, int newGridHeight);
|
||||
|
||||
bool isPageEmpty();
|
||||
|
||||
public Q_SLOTS:
|
||||
|
|
@ -85,5 +94,6 @@ Q_SIGNALS:
|
|||
void saveRequested();
|
||||
|
||||
private:
|
||||
void connectSaveRequests(FolioDelegate *delegate);
|
||||
QList<FolioPageDelegate *> m_delegates;
|
||||
};
|
||||
|
|
|
|||
131
containments/homescreens/folio/widgetcontainer.cpp
Normal file
131
containments/homescreens/folio/widgetcontainer.cpp
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
|
||||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "widgetcontainer.h"
|
||||
|
||||
#include <QCursor>
|
||||
#include <QGuiApplication>
|
||||
#include <QStyleHints>
|
||||
|
||||
WidgetContainer::WidgetContainer(QQuickItem *parent)
|
||||
: QQuickItem(parent)
|
||||
, m_pressAndHoldTimer{new QTimer{this}}
|
||||
{
|
||||
m_pressAndHoldTimer->setInterval(QGuiApplication::styleHints()->mousePressAndHoldInterval());
|
||||
m_pressAndHoldTimer->setSingleShot(true);
|
||||
connect(m_pressAndHoldTimer, &QTimer::timeout, this, &WidgetContainer::startPressAndHold);
|
||||
|
||||
setFiltersChildMouseEvents(true);
|
||||
setFlags(QQuickItem::ItemIsFocusScope);
|
||||
setActiveFocusOnTab(true);
|
||||
setAcceptedMouseButtons(Qt::LeftButton);
|
||||
|
||||
connect(this, &WidgetContainer::activeFocusChanged, this, &WidgetContainer::onActiveFocusChanged);
|
||||
}
|
||||
|
||||
bool WidgetContainer::editMode() const
|
||||
{
|
||||
return m_editMode;
|
||||
}
|
||||
|
||||
void WidgetContainer::setEditMode(bool editMode)
|
||||
{
|
||||
if (m_editMode != editMode) {
|
||||
m_editMode = editMode;
|
||||
|
||||
if (m_editMode) {
|
||||
setZ(1);
|
||||
|
||||
if (m_pressed) {
|
||||
// sendUngrabRecursive(m_contentItem);
|
||||
QMouseEvent ev(QEvent::MouseButtonPress, mapFromScene(m_mouseDownPosition), m_mouseDownPosition, QPointF(), Qt::LeftButton, {}, {});
|
||||
ev.setExclusiveGrabber(ev.point(0), this);
|
||||
QCoreApplication::sendEvent(this, &ev);
|
||||
}
|
||||
|
||||
} else {
|
||||
setZ(0);
|
||||
}
|
||||
|
||||
Q_EMIT editModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool WidgetContainer::childMouseEventFilter(QQuickItem *item, QEvent *event)
|
||||
{
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseButtonPress: {
|
||||
QMouseEvent *me = static_cast<QMouseEvent *>(event);
|
||||
if (me->buttons() & Qt::LeftButton) {
|
||||
mousePressEvent(me);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseMove: {
|
||||
QMouseEvent *me = static_cast<QMouseEvent *>(event);
|
||||
mouseMoveEvent(me);
|
||||
break;
|
||||
}
|
||||
case QEvent::MouseButtonRelease: {
|
||||
QMouseEvent *me = static_cast<QMouseEvent *>(event);
|
||||
mouseReleaseEvent(me);
|
||||
break;
|
||||
}
|
||||
case QEvent::UngrabMouse:
|
||||
mouseUngrabEvent();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QQuickItem::childMouseEventFilter(item, event);
|
||||
}
|
||||
|
||||
void WidgetContainer::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
forceActiveFocus(Qt::MouseFocusReason);
|
||||
|
||||
m_pressed = true;
|
||||
m_pressAndHoldTimer->start();
|
||||
m_mouseDownPosition = event->scenePosition();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void WidgetContainer::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (!m_editMode && QPointF(event->scenePosition() - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
|
||||
m_pressAndHoldTimer->stop();
|
||||
}
|
||||
|
||||
QQuickItem::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void WidgetContainer::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
Q_EMIT pressReleased();
|
||||
|
||||
m_pressAndHoldTimer->stop();
|
||||
m_pressed = false;
|
||||
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void WidgetContainer::mouseUngrabEvent()
|
||||
{
|
||||
m_pressAndHoldTimer->stop();
|
||||
m_pressed = false;
|
||||
}
|
||||
|
||||
void WidgetContainer::startPressAndHold()
|
||||
{
|
||||
setEditMode(true);
|
||||
Q_EMIT startEditMode(m_mouseDownPosition);
|
||||
}
|
||||
|
||||
void WidgetContainer::onActiveFocusChanged(bool activeFocus)
|
||||
{
|
||||
if (!activeFocus) {
|
||||
setEditMode(false);
|
||||
}
|
||||
}
|
||||
46
containments/homescreens/folio/widgetcontainer.h
Normal file
46
containments/homescreens/folio/widgetcontainer.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QQuickItem>
|
||||
#include <QTimer>
|
||||
|
||||
class WidgetContainer : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged)
|
||||
|
||||
QML_NAMED_ELEMENT(WidgetContainer)
|
||||
|
||||
public:
|
||||
WidgetContainer(QQuickItem *parent = nullptr);
|
||||
|
||||
bool editMode() const;
|
||||
void setEditMode(bool editMode);
|
||||
|
||||
Q_SIGNALS:
|
||||
void editModeChanged();
|
||||
void pressReleased();
|
||||
void startEditMode(QPointF pressPoint);
|
||||
|
||||
protected:
|
||||
bool childMouseEventFilter(QQuickItem *item, QEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseUngrabEvent() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void startPressAndHold();
|
||||
void onActiveFocusChanged(bool activeFocus);
|
||||
|
||||
private:
|
||||
bool m_pressed{false};
|
||||
bool m_editMode{false};
|
||||
QTimer *m_pressAndHoldTimer{nullptr};
|
||||
QPointF m_mouseDownPosition{};
|
||||
};
|
||||
|
||||
QML_DECLARE_TYPE(WidgetContainer)
|
||||
42
containments/homescreens/folio/widgetsmanager.cpp
Normal file
42
containments/homescreens/folio/widgetsmanager.cpp
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "widgetsmanager.h"
|
||||
|
||||
WidgetsManager::WidgetsManager(QObject *parent)
|
||||
: QObject{parent}
|
||||
{
|
||||
}
|
||||
|
||||
WidgetsManager *WidgetsManager::self()
|
||||
{
|
||||
static WidgetsManager *manager = new WidgetsManager{nullptr};
|
||||
return manager;
|
||||
}
|
||||
|
||||
Plasma::Applet *WidgetsManager::getWidget(int id)
|
||||
{
|
||||
for (auto *widget : m_widgets) {
|
||||
if (static_cast<int>(widget->id()) == id) {
|
||||
return widget;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void WidgetsManager::addWidget(Plasma::Applet *applet)
|
||||
{
|
||||
if (!m_widgets.contains(applet)) {
|
||||
m_widgets.push_back(applet);
|
||||
Q_EMIT widgetAdded(applet);
|
||||
}
|
||||
}
|
||||
|
||||
void WidgetsManager::removeWidget(Plasma::Applet *applet)
|
||||
{
|
||||
if (m_widgets.contains(applet)) {
|
||||
m_widgets.remove(m_widgets.indexOf(applet));
|
||||
Q_EMIT widgetRemoved(applet);
|
||||
}
|
||||
}
|
||||
31
containments/homescreens/folio/widgetsmanager.h
Normal file
31
containments/homescreens/folio/widgetsmanager.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// SPDX-FileCopyrightText: 2023 Devin Lin <devin@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <Plasma/Applet>
|
||||
|
||||
// keeps a list of all of instances of Plasma::Applet that are loaded into the containment
|
||||
// allows for FolioWidgets to find their corresponding Plasma::Applet
|
||||
class WidgetsManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WidgetsManager(QObject *parent = nullptr);
|
||||
|
||||
static WidgetsManager *self();
|
||||
|
||||
Plasma::Applet *getWidget(int id);
|
||||
|
||||
void addWidget(Plasma::Applet *applet);
|
||||
void removeWidget(Plasma::Applet *applet);
|
||||
|
||||
Q_SIGNALS:
|
||||
void widgetAdded(Plasma::Applet *applet);
|
||||
void widgetRemoved(Plasma::Applet *applet);
|
||||
|
||||
private:
|
||||
QList<Plasma::Applet *> m_widgets;
|
||||
};
|
||||
Loading…
Reference in a new issue