mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 15:03: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
|
folioapplicationfolder.cpp
|
||||||
foliodelegate.cpp
|
foliodelegate.cpp
|
||||||
foliosettings.cpp
|
foliosettings.cpp
|
||||||
|
foliowidget.cpp
|
||||||
pagemodel.cpp
|
pagemodel.cpp
|
||||||
pagelistmodel.cpp
|
pagelistmodel.cpp
|
||||||
delegatetoucharea.cpp
|
delegatetoucharea.cpp
|
||||||
dragstate.cpp
|
dragstate.cpp
|
||||||
|
widgetcontainer.cpp
|
||||||
|
widgetsmanager.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(org.kde.plasma.mobile.homescreen.folio MODULE ${homescreen_SRCS})
|
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
|
#### TODO
|
||||||
- Add folio/halcyon switcher in initial-start
|
- Add folio/halcyon switcher in initial-start
|
||||||
- If an app gets uninstalled, the homescreen UI needs to ensure that delegates are updated
|
- 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: landscape favourites bar duplication when dragging icon from it sometimes
|
||||||
- BUG: can't insert delegates in-between very well in landscape favourites bar
|
- BUG: can't insert delegates in-between very well in landscape favourites bar
|
||||||
- can make the touch area only the icon?
|
- can make the touch area only the icon?
|
||||||
- FEATURE: add import/export
|
- FEATURE: add widget import/export
|
||||||
- FEATURE: keyboard navigation
|
- FEATURE: keyboard navigation
|
||||||
- FEATURE: touchpad 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 "delegatetoucharea.h"
|
||||||
|
|
||||||
#include <QCursor>
|
#include <QCursor>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QStyleHints>
|
||||||
|
|
||||||
// Some code taken from MouseArea
|
// Some code taken from MouseArea
|
||||||
|
|
||||||
|
|
@ -12,7 +14,6 @@ DelegateTouchArea::DelegateTouchArea(QQuickItem *parent)
|
||||||
: QQuickItem{parent}
|
: QQuickItem{parent}
|
||||||
, m_pressAndHoldTimer{new QTimer{this}}
|
, m_pressAndHoldTimer{new QTimer{this}}
|
||||||
{
|
{
|
||||||
// TODO: currently hardcoded 2s press and hold interval
|
|
||||||
m_pressAndHoldTimer->setInterval(600);
|
m_pressAndHoldTimer->setInterval(600);
|
||||||
m_pressAndHoldTimer->setSingleShot(true);
|
m_pressAndHoldTimer->setSingleShot(true);
|
||||||
connect(m_pressAndHoldTimer, &QTimer::timeout, this, &DelegateTouchArea::startPressAndHold);
|
connect(m_pressAndHoldTimer, &QTimer::timeout, this, &DelegateTouchArea::startPressAndHold);
|
||||||
|
|
@ -24,7 +25,7 @@ DelegateTouchArea::DelegateTouchArea(QQuickItem *parent)
|
||||||
|
|
||||||
setAcceptHoverEvents(true);
|
setAcceptHoverEvents(true);
|
||||||
setAcceptTouchEvents(true);
|
setAcceptTouchEvents(true);
|
||||||
// setFiltersChildMouseEvents(true);
|
setFlags(QQuickItem::ItemIsFocusScope);
|
||||||
setAcceptedMouseButtons(Qt::LeftButton | Qt::RightButton);
|
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()
|
Qt::CursorShape DelegateTouchArea::cursorShape()
|
||||||
{
|
{
|
||||||
return cursor().shape();
|
return cursor().shape();
|
||||||
|
|
@ -87,6 +75,11 @@ void DelegateTouchArea::unsetCursor()
|
||||||
setCursorShape(Qt::ArrowCursor);
|
setCursorShape(Qt::ArrowCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QPointF DelegateTouchArea::pressPosition()
|
||||||
|
{
|
||||||
|
return m_mouseDownPosition;
|
||||||
|
}
|
||||||
|
|
||||||
void DelegateTouchArea::mousePressEvent(QMouseEvent *event)
|
void DelegateTouchArea::mousePressEvent(QMouseEvent *event)
|
||||||
{
|
{
|
||||||
if (event->button() & Qt::RightButton) {
|
if (event->button() & Qt::RightButton) {
|
||||||
|
|
@ -178,86 +171,6 @@ void DelegateTouchArea::hoverLeaveEvent(QHoverEvent *event)
|
||||||
event->ignore();
|
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)
|
void DelegateTouchArea::handlePressEvent(QPointerEvent *event, QPointF point)
|
||||||
{
|
{
|
||||||
// ignore multiple press events
|
// ignore multiple press events
|
||||||
|
|
@ -266,6 +179,11 @@ void DelegateTouchArea::handlePressEvent(QPointerEvent *event, QPointF point)
|
||||||
}
|
}
|
||||||
|
|
||||||
setPressed(true);
|
setPressed(true);
|
||||||
|
forceActiveFocus(Qt::MouseFocusReason);
|
||||||
|
|
||||||
|
m_mouseDownPosition = point;
|
||||||
|
Q_EMIT pressPositionChanged();
|
||||||
|
|
||||||
m_pressAndHoldTimer->start();
|
m_pressAndHoldTimer->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,10 +191,10 @@ void DelegateTouchArea::handleReleaseEvent(QPointerEvent *event, bool click)
|
||||||
{
|
{
|
||||||
// NOTE: event can be nullptr!
|
// NOTE: event can be nullptr!
|
||||||
|
|
||||||
|
bool wasPressed = m_pressed;
|
||||||
setPressed(false);
|
setPressed(false);
|
||||||
setDragging(false);
|
|
||||||
|
|
||||||
if (!m_pressAndHeld && click) {
|
if (!m_pressAndHeld && click && wasPressed) {
|
||||||
Q_EMIT clicked();
|
Q_EMIT clicked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,8 +208,9 @@ void DelegateTouchArea::handleReleaseEvent(QPointerEvent *event, bool click)
|
||||||
|
|
||||||
void DelegateTouchArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
void DelegateTouchArea::handleMoveEvent(QPointerEvent *event, QPointF point)
|
||||||
{
|
{
|
||||||
if (m_pressAndHeld) {
|
if (QPointF(point - m_mouseDownPosition).manhattanLength() >= QGuiApplication::styleHints()->startDragDistance()) {
|
||||||
// TODO
|
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 pressed READ pressed NOTIFY pressedChanged FINAL)
|
||||||
Q_PROPERTY(bool hovered READ hovered NOTIFY hoveredChanged 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(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)
|
QML_NAMED_ELEMENT(DelegateTouchArea)
|
||||||
|
|
||||||
|
|
@ -29,21 +29,20 @@ public:
|
||||||
|
|
||||||
bool pressed();
|
bool pressed();
|
||||||
bool hovered();
|
bool hovered();
|
||||||
bool dragging();
|
|
||||||
Qt::CursorShape cursorShape();
|
Qt::CursorShape cursorShape();
|
||||||
void setCursorShape(Qt::CursorShape cursorShape);
|
void setCursorShape(Qt::CursorShape cursorShape);
|
||||||
void unsetCursor();
|
void unsetCursor();
|
||||||
|
QPointF pressPosition();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void clicked();
|
void clicked();
|
||||||
void rightMousePress();
|
void rightMousePress();
|
||||||
void pressAndHold();
|
void pressAndHold();
|
||||||
void pressAndHoldReleased();
|
void pressAndHoldReleased();
|
||||||
void drag(qreal x, qreal y);
|
|
||||||
void pressedChanged(bool pressed);
|
void pressedChanged(bool pressed);
|
||||||
void hoveredChanged(bool hovered);
|
void hoveredChanged(bool hovered);
|
||||||
void draggingChanged(bool dragging);
|
|
||||||
void cursorShapeChanged();
|
void cursorShapeChanged();
|
||||||
|
void pressPositionChanged();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mousePressEvent(QMouseEvent *event) override;
|
void mousePressEvent(QMouseEvent *event) override;
|
||||||
|
|
@ -54,13 +53,11 @@ protected:
|
||||||
void touchUngrabEvent() override;
|
void touchUngrabEvent() override;
|
||||||
void hoverEnterEvent(QHoverEvent *event) override;
|
void hoverEnterEvent(QHoverEvent *event) override;
|
||||||
void hoverLeaveEvent(QHoverEvent *event) override;
|
void hoverLeaveEvent(QHoverEvent *event) override;
|
||||||
// bool childMouseEventFilter(QQuickItem *i, QEvent *e) override;
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void startPressAndHold();
|
void startPressAndHold();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// bool filterPointerEvent(QQuickItem *receiver, QPointerEvent *event);
|
|
||||||
void setPressed(bool pressed);
|
void setPressed(bool pressed);
|
||||||
void setHovered(bool hovered);
|
void setHovered(bool hovered);
|
||||||
void setDragging(bool dragging);
|
void setDragging(bool dragging);
|
||||||
|
|
@ -71,9 +68,9 @@ private:
|
||||||
|
|
||||||
bool m_pressed{false};
|
bool m_pressed{false};
|
||||||
bool m_hovered{false};
|
bool m_hovered{false};
|
||||||
bool m_dragging{false};
|
|
||||||
bool m_pressAndHeld{false};
|
bool m_pressAndHeld{false};
|
||||||
Qt::CursorShape m_cursorShape{Qt::ArrowCursor};
|
Qt::CursorShape m_cursorShape{Qt::ArrowCursor};
|
||||||
|
QPointF m_mouseDownPosition{};
|
||||||
|
|
||||||
QTimer *m_pressAndHoldTimer{nullptr};
|
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::delegateDragFromAppDrawerStarted, this, &DragState::onDelegateDragFromAppDrawerStarted);
|
||||||
connect(m_state, &HomeScreenState::delegateDragFromFavouritesStarted, this, &DragState::onDelegateDragFromFavouritesStarted);
|
connect(m_state, &HomeScreenState::delegateDragFromFavouritesStarted, this, &DragState::onDelegateDragFromFavouritesStarted);
|
||||||
connect(m_state, &HomeScreenState::delegateDragFromFolderStarted, this, &DragState::onDelegateDragFromFolderStarted);
|
connect(m_state, &HomeScreenState::delegateDragFromFolderStarted, this, &DragState::onDelegateDragFromFolderStarted);
|
||||||
|
connect(m_state, &HomeScreenState::delegateDragFromWidgetListStarted, this, &DragState::onDelegateDragFromWidgetListStarted);
|
||||||
connect(m_state, &HomeScreenState::swipeStateChanged, this, [this]() {
|
connect(m_state, &HomeScreenState::swipeStateChanged, this, [this]() {
|
||||||
if (HomeScreenState::self()->swipeState() == HomeScreenState::DraggingDelegate) {
|
if (HomeScreenState::self()->swipeState() == HomeScreenState::DraggingDelegate) {
|
||||||
onDelegateDraggingStarted();
|
onDelegateDraggingStarted();
|
||||||
|
|
@ -214,8 +215,8 @@ void DragState::onDelegateDragPositionChanged()
|
||||||
|
|
||||||
// we want to update the candidate drop position variable in this function!
|
// we want to update the candidate drop position variable in this function!
|
||||||
|
|
||||||
qreal x = getDraggedDelegateX();
|
qreal x = getPointerX();
|
||||||
qreal y = getDraggedDelegateY();
|
qreal y = getPointerY();
|
||||||
|
|
||||||
bool inFolder = m_state->viewState() == HomeScreenState::FolderView;
|
bool inFolder = m_state->viewState() == HomeScreenState::FolderView;
|
||||||
bool inFavouritesArea = !inFolder;
|
bool inFavouritesArea = !inFolder;
|
||||||
|
|
@ -258,8 +259,8 @@ void DragState::onDelegateDragPositionChanged()
|
||||||
void DragState::onDelegateDragPositionOverFolderViewChanged()
|
void DragState::onDelegateDragPositionOverFolderViewChanged()
|
||||||
{
|
{
|
||||||
// if the drag position changes while in the folder view
|
// if the drag position changes while in the folder view
|
||||||
qreal x = getDraggedDelegateX();
|
qreal x = getPointerX();
|
||||||
qreal y = getDraggedDelegateY();
|
qreal y = getPointerY();
|
||||||
|
|
||||||
auto *folder = m_state->currentFolder();
|
auto *folder = m_state->currentFolder();
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
|
|
@ -311,8 +312,8 @@ void DragState::onDelegateDragPositionOverFavouritesChanged()
|
||||||
{
|
{
|
||||||
// the drag position changed while over the favourites strip
|
// the drag position changed while over the favourites strip
|
||||||
|
|
||||||
qreal x = getDraggedDelegateX();
|
qreal x = getPointerX();
|
||||||
qreal y = getDraggedDelegateY();
|
qreal y = getPointerY();
|
||||||
int dropIndex = FavouritesModel::self()->dropInsertPosition(x, y);
|
int dropIndex = FavouritesModel::self()->dropInsertPosition(x, y);
|
||||||
|
|
||||||
// if the drop position changed, cancel the open folder timer
|
// if the drop position changed, cancel the open folder timer
|
||||||
|
|
@ -327,6 +328,11 @@ void DragState::onDelegateDragPositionOverFavouritesChanged()
|
||||||
m_favouritesInsertBetweenTimer->stop();
|
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 (FavouritesModel::self()->dropPositionIsEdge(x, y)) {
|
||||||
// if we need to make space for the delegate
|
// 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
|
// the drag position changed while over the homescreen pages strip
|
||||||
|
|
||||||
qreal x = getDraggedDelegateX();
|
qreal delegateX = getDraggedDelegateX();
|
||||||
qreal y = getDraggedDelegateY();
|
qreal delegateY = getDraggedDelegateY();
|
||||||
|
qreal x = getPointerX();
|
||||||
|
qreal y = getPointerY();
|
||||||
int page = m_state->currentPage();
|
int page = m_state->currentPage();
|
||||||
|
|
||||||
// calculate the row and column the delegate is over
|
// calculate the row and column the delegate is over
|
||||||
qreal pageHorizontalMargin = (m_state->pageWidth() - m_state->pageContentWidth()) / 2;
|
qreal pageHorizontalMargin = (m_state->pageWidth() - m_state->pageContentWidth()) / 2;
|
||||||
qreal pageVerticalMargin = (m_state->pageHeight() - m_state->pageContentHeight()) / 2;
|
qreal pageVerticalMargin = (m_state->pageHeight() - m_state->pageContentHeight()) / 2;
|
||||||
|
|
||||||
int row = (y - pageVerticalMargin) / m_state->pageCellHeight();
|
int row = 0;
|
||||||
int column = (x - pageHorizontalMargin) / m_state->pageCellWidth();
|
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
|
// ensure it's in bounds
|
||||||
row = std::max(0, std::min(m_state->pageRows() - 1, row));
|
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);
|
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()
|
void DragState::onDelegateDropped()
|
||||||
{
|
{
|
||||||
if (!m_dropDelegate) {
|
if (!m_dropDelegate) {
|
||||||
|
|
@ -495,7 +524,7 @@ void DragState::onDelegateDropped()
|
||||||
}
|
}
|
||||||
|
|
||||||
// add dropped delegate
|
// add dropped delegate
|
||||||
createDropPositionDelegate();
|
bool success = createDropPositionDelegate();
|
||||||
|
|
||||||
// delete empty pages at the end if they exist
|
// 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)
|
// (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_changePageTimer->stop();
|
||||||
m_favouritesInsertBetweenTimer->stop();
|
m_favouritesInsertBetweenTimer->stop();
|
||||||
|
|
||||||
// emit signal
|
// emit corresponding signal
|
||||||
Q_EMIT delegateDroppedAndPlaced();
|
// -> 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()
|
void DragState::onLeaveCurrentFolder()
|
||||||
|
|
@ -543,7 +578,7 @@ void DragState::onChangePageTimerFinished()
|
||||||
const int leftPagePosition = 0;
|
const int leftPagePosition = 0;
|
||||||
const int rightPagePosition = m_state->pageWidth();
|
const int rightPagePosition = m_state->pageWidth();
|
||||||
|
|
||||||
qreal x = getDraggedDelegateX();
|
qreal x = getPointerX();
|
||||||
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
if (qAbs(leftPagePosition - x) <= PAGE_CHANGE_THRESHOLD) {
|
||||||
// if we are at the left edge, go left
|
// if we are at the left edge, go left
|
||||||
int page = m_state->currentPage() - 1;
|
int page = m_state->currentPage() - 1;
|
||||||
|
|
@ -623,7 +658,7 @@ void DragState::onLeaveFolderTimerFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if the drag position is outside of the folder
|
// 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();
|
m_state->closeFolder();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -635,16 +670,17 @@ void DragState::onChangeFolderPageTimerFinished()
|
||||||
}
|
}
|
||||||
|
|
||||||
auto *folder = m_state->currentFolder();
|
auto *folder = m_state->currentFolder();
|
||||||
|
qreal x = getPointerX();
|
||||||
|
qreal y = getPointerY();
|
||||||
|
|
||||||
// check if the drag position is outside of the folder
|
// check if the drag position is outside of the folder
|
||||||
if (folder->isDropPositionOutside(getDraggedDelegateX(), getDraggedDelegateY())) {
|
if (folder->isDropPositionOutside(x, y)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
const qreal leftPagePosition = folder->applications()->leftMarginFromScreenEdge();
|
||||||
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
const qreal rightPagePosition = m_state->viewWidth() - leftPagePosition;
|
||||||
|
|
||||||
qreal x = getDraggedDelegateX();
|
|
||||||
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD) {
|
if (x <= leftPagePosition + PAGE_CHANGE_THRESHOLD) {
|
||||||
// if we are at the left edge, go left
|
// if we are at the left edge, go left
|
||||||
int page = m_state->currentFolderPage() - 1;
|
int page = m_state->currentFolderPage() - 1;
|
||||||
|
|
@ -656,12 +692,6 @@ void DragState::onChangeFolderPageTimerFinished()
|
||||||
// if we are at the right edge, go right
|
// if we are at the right edge, go right
|
||||||
int page = m_state->currentFolderPage() + 1;
|
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
|
// go to page if it exists
|
||||||
if (page < folder->applications()->numTotalPages()) {
|
if (page < folder->applications()->numTotalPages()) {
|
||||||
m_state->goToFolderPage(page);
|
m_state->goToFolderPage(page);
|
||||||
|
|
@ -683,7 +713,7 @@ void DragState::onFolderInsertBetweenTimerFinished()
|
||||||
m_candidateDropPosition->setLocation(DelegateDragPosition::Folder);
|
m_candidateDropPosition->setLocation(DelegateDragPosition::Folder);
|
||||||
|
|
||||||
// insert it at this position, shifting existing apps to the side
|
// 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);
|
folder->applications()->setGhostEntry(m_folderInsertBetweenIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -715,17 +745,21 @@ void DragState::deleteStartPositionDelegate()
|
||||||
m_startPosition->folder()->removeDelegate(m_startPosition->folderPosition());
|
m_startPosition->folder()->removeDelegate(m_startPosition->folderPosition());
|
||||||
break;
|
break;
|
||||||
case DelegateDragPosition::AppDrawer:
|
case DelegateDragPosition::AppDrawer:
|
||||||
|
case DelegateDragPosition::WidgetList:
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DragState::createDropPositionDelegate()
|
bool DragState::createDropPositionDelegate()
|
||||||
{
|
{
|
||||||
if (!m_dropDelegate) {
|
if (!m_dropDelegate) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// whether the drop goes successfully
|
||||||
|
bool added = false;
|
||||||
|
|
||||||
// creates the delegate at the drop position
|
// creates the delegate at the drop position
|
||||||
switch (m_candidateDropPosition->location()) {
|
switch (m_candidateDropPosition->location()) {
|
||||||
case DelegateDragPosition::Pages: {
|
case DelegateDragPosition::Pages: {
|
||||||
|
|
@ -753,6 +787,7 @@ void DragState::createDropPositionDelegate()
|
||||||
auto existingFolder = existingDelegate->folder();
|
auto existingFolder = existingDelegate->folder();
|
||||||
existingFolder->addDelegate(delegate, existingFolder->applications()->rowCount());
|
existingFolder->addDelegate(delegate, existingFolder->applications()->rowCount());
|
||||||
|
|
||||||
|
added = true;
|
||||||
break;
|
break;
|
||||||
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
||||||
// create a folder from the two apps
|
// create a folder from the two apps
|
||||||
|
|
@ -765,19 +800,20 @@ void DragState::createDropPositionDelegate()
|
||||||
page->removeDelegate(row, column);
|
page->removeDelegate(row, column);
|
||||||
page->addDelegate(folderDelegate);
|
page->addDelegate(folderDelegate);
|
||||||
|
|
||||||
|
added = true;
|
||||||
break;
|
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 we couldn't add the delegate, try again but at the start position (return to start)
|
||||||
if (!added && !isStartPositionEqualDropPosition()) {
|
if (!added && !isStartPositionEqualDropPosition()) {
|
||||||
m_candidateDropPosition->copyFrom(m_startPosition);
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
||||||
createDropPositionDelegate();
|
added = createDropPositionDelegate();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -794,6 +830,7 @@ void DragState::createDropPositionDelegate()
|
||||||
auto existingFolder = existingDelegate->folder();
|
auto existingFolder = existingDelegate->folder();
|
||||||
existingFolder->addDelegate(m_dropDelegate, existingFolder->applications()->rowCount());
|
existingFolder->addDelegate(m_dropDelegate, existingFolder->applications()->rowCount());
|
||||||
|
|
||||||
|
added = true;
|
||||||
break;
|
break;
|
||||||
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
} else if (existingDelegate->type() == FolioDelegate::Application && !isStartPositionEqualDropPosition()) {
|
||||||
// create a folder from the two apps
|
// create a folder from the two apps
|
||||||
|
|
@ -806,6 +843,7 @@ void DragState::createDropPositionDelegate()
|
||||||
FavouritesModel::self()->removeEntry(m_candidateDropPosition->favouritesPosition());
|
FavouritesModel::self()->removeEntry(m_candidateDropPosition->favouritesPosition());
|
||||||
FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), folderDelegate);
|
FavouritesModel::self()->addEntry(m_candidateDropPosition->favouritesPosition(), folderDelegate);
|
||||||
|
|
||||||
|
added = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -813,12 +851,12 @@ void DragState::createDropPositionDelegate()
|
||||||
|
|
||||||
// otherwise, just add the delegate at this position
|
// 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 we couldn't add the delegate, try again but at the start position
|
||||||
if (!added && !isStartPositionEqualDropPosition()) {
|
if (!added && !isStartPositionEqualDropPosition()) {
|
||||||
m_candidateDropPosition->copyFrom(m_startPosition);
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
||||||
createDropPositionDelegate();
|
added = createDropPositionDelegate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// correct position when we delete from an entry earlier in the favourites
|
// correct position when we delete from an entry earlier in the favourites
|
||||||
|
|
@ -833,33 +871,42 @@ void DragState::createDropPositionDelegate()
|
||||||
case DelegateDragPosition::Folder: {
|
case DelegateDragPosition::Folder: {
|
||||||
auto *folder = m_candidateDropPosition->folder();
|
auto *folder = m_candidateDropPosition->folder();
|
||||||
if (!folder) {
|
if (!folder) {
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only support dropping apps into folders
|
// only support dropping apps into folders
|
||||||
if (m_dropDelegate->type() != FolioDelegate::Application) {
|
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 we couldn't add the delegate, try again but at the start position
|
||||||
if (!added && !isStartPositionEqualDropPosition()) {
|
if (!added && !isStartPositionEqualDropPosition()) {
|
||||||
m_candidateDropPosition->copyFrom(m_startPosition);
|
m_candidateDropPosition->copyFrom(m_startPosition);
|
||||||
createDropPositionDelegate();
|
added = createDropPositionDelegate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (added) {
|
if (added) {
|
||||||
folder->applications()->deleteGhostEntry();
|
folder->applications()->deleteGhostEntry();
|
||||||
|
|
||||||
// TODO correct m_startPosition?
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case DelegateDragPosition::AppDrawer:
|
case DelegateDragPosition::AppDrawer:
|
||||||
|
case DelegateDragPosition::WidgetList:
|
||||||
default:
|
default:
|
||||||
break;
|
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()
|
bool DragState::isStartPositionEqualDropPosition()
|
||||||
|
|
@ -881,3 +928,13 @@ qreal DragState::getDraggedDelegateY()
|
||||||
// adjust to get the position of the center of the delegate
|
// adjust to get the position of the center of the delegate
|
||||||
return m_state->delegateDragY() + m_state->pageCellHeight() / 2;
|
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)
|
Q_PROPERTY(FolioApplicationFolder *folder READ folder NOTIFY folderChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Location { Pages, Favourites, AppDrawer, Folder };
|
enum Location { Pages, Favourites, AppDrawer, Folder, WidgetList };
|
||||||
Q_ENUM(Location)
|
Q_ENUM(Location)
|
||||||
|
|
||||||
DelegateDragPosition(QObject *parent = nullptr);
|
DelegateDragPosition(QObject *parent = nullptr);
|
||||||
|
|
@ -94,6 +94,9 @@ Q_SIGNALS:
|
||||||
void dropDelegateChanged();
|
void dropDelegateChanged();
|
||||||
void delegateDroppedAndPlaced();
|
void delegateDroppedAndPlaced();
|
||||||
|
|
||||||
|
// if you drop a new delegate on an invalid spot
|
||||||
|
void newDelegateDropAbandoned();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void onDelegateDragPositionChanged();
|
void onDelegateDragPositionChanged();
|
||||||
void onDelegateDragPositionOverFolderViewChanged();
|
void onDelegateDragPositionOverFolderViewChanged();
|
||||||
|
|
@ -105,6 +108,7 @@ private Q_SLOTS:
|
||||||
void onDelegateDragFromFavouritesStarted(int position);
|
void onDelegateDragFromFavouritesStarted(int position);
|
||||||
void onDelegateDragFromAppDrawerStarted(QString storageId);
|
void onDelegateDragFromAppDrawerStarted(QString storageId);
|
||||||
void onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
void onDelegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
||||||
|
void onDelegateDragFromWidgetListStarted(QString appletPluginId);
|
||||||
void onDelegateDropped();
|
void onDelegateDropped();
|
||||||
|
|
||||||
void onLeaveCurrentFolder();
|
void onLeaveCurrentFolder();
|
||||||
|
|
@ -120,8 +124,8 @@ private:
|
||||||
// deletes the delegate at m_startPosition
|
// deletes the delegate at m_startPosition
|
||||||
void deleteStartPositionDelegate();
|
void deleteStartPositionDelegate();
|
||||||
|
|
||||||
// deletes the delegate at m_candidateDropPosition
|
// places the delegate at m_candidateDropPosition, returning whether it was successful
|
||||||
void createDropPositionDelegate();
|
bool createDropPositionDelegate();
|
||||||
|
|
||||||
// whether m_startPosition = m_candidateDropPosition
|
// whether m_startPosition = m_candidateDropPosition
|
||||||
bool isStartPositionEqualDropPosition();
|
bool isStartPositionEqualDropPosition();
|
||||||
|
|
@ -130,6 +134,10 @@ private:
|
||||||
qreal getDraggedDelegateX();
|
qreal getDraggedDelegateX();
|
||||||
qreal getDraggedDelegateY();
|
qreal getDraggedDelegateY();
|
||||||
|
|
||||||
|
// position of the dragging pointer
|
||||||
|
qreal getPointerX();
|
||||||
|
qreal getPointerY();
|
||||||
|
|
||||||
QTimer *m_changePageTimer{nullptr};
|
QTimer *m_changePageTimer{nullptr};
|
||||||
QTimer *m_openFolderTimer{nullptr};
|
QTimer *m_openFolderTimer{nullptr};
|
||||||
QTimer *m_leaveFolderTimer{nullptr};
|
QTimer *m_leaveFolderTimer{nullptr};
|
||||||
|
|
@ -152,5 +160,8 @@ private:
|
||||||
// this is the original start position of the drag
|
// this is the original start position of the drag
|
||||||
DelegateDragPosition *const m_startPosition{nullptr};
|
DelegateDragPosition *const m_startPosition{nullptr};
|
||||||
|
|
||||||
|
// when dropping a new widget, this is the applet name
|
||||||
|
QString m_createdAppletPluginId{};
|
||||||
|
|
||||||
HomeScreenState *m_state{nullptr};
|
HomeScreenState *m_state{nullptr};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ void FavouritesModel::moveEntry(int fromRow, int toRow)
|
||||||
save();
|
save();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
bool FavouritesModel::canAddEntry(int row, FolioDelegate *delegate)
|
||||||
{
|
{
|
||||||
if (!delegate) {
|
if (!delegate) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -129,6 +129,15 @@ bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
||||||
|
{
|
||||||
|
if (!canAddEntry(row, delegate)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (row == m_delegates.size()) {
|
if (row == m_delegates.size()) {
|
||||||
beginInsertRows(QModelIndex(), row, row);
|
beginInsertRows(QModelIndex(), row, row);
|
||||||
m_delegates.append({delegate, 0});
|
m_delegates.append({delegate, 0});
|
||||||
|
|
@ -143,6 +152,9 @@ bool FavouritesModel::addEntry(int row, FolioDelegate *delegate)
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure saves are connected when requested by the delegate
|
||||||
|
connectSaveRequests(delegate);
|
||||||
|
|
||||||
evaluateDelegatePositions();
|
evaluateDelegatePositions();
|
||||||
|
|
||||||
save();
|
save();
|
||||||
|
|
@ -230,24 +242,24 @@ QJsonArray FavouritesModel::exportToJson()
|
||||||
|
|
||||||
void FavouritesModel::save()
|
void FavouritesModel::save()
|
||||||
{
|
{
|
||||||
if (!m_applet) {
|
if (!m_containment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray arr = exportToJson();
|
QJsonArray arr = exportToJson();
|
||||||
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
m_applet->config().writeEntry("Favourites", QString::fromStdString(data.toStdString()));
|
m_containment->config().writeEntry("Favourites", QString::fromStdString(data.toStdString()));
|
||||||
Q_EMIT m_applet->configNeedsSaving();
|
Q_EMIT m_containment->configNeedsSaving();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FavouritesModel::load()
|
void FavouritesModel::load()
|
||||||
{
|
{
|
||||||
if (!m_applet) {
|
if (!m_containment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Favourites", "{}").toUtf8());
|
QJsonDocument doc = QJsonDocument::fromJson(m_containment->config().readEntry("Favourites", "{}").toUtf8());
|
||||||
loadFromJson(doc.array());
|
loadFromJson(doc.array());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,10 +274,7 @@ void FavouritesModel::loadFromJson(QJsonArray arr)
|
||||||
FolioDelegate *delegate = FolioDelegate::fromJson(obj, this);
|
FolioDelegate *delegate = FolioDelegate::fromJson(obj, this);
|
||||||
|
|
||||||
if (delegate) {
|
if (delegate) {
|
||||||
if (delegate->type() == FolioDelegate::Folder) {
|
connectSaveRequests(delegate);
|
||||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, this, &FavouritesModel::save);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_delegates.append({delegate, 0});
|
m_delegates.append({delegate, 0});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -274,9 +283,16 @@ void FavouritesModel::loadFromJson(QJsonArray arr)
|
||||||
endResetModel();
|
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
|
bool FavouritesModel::dropPositionIsEdge(qreal x, qreal y) const
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
|
|
||||||
#include <Plasma/Applet>
|
#include <Plasma/Containment>
|
||||||
|
|
||||||
#include "foliodelegate.h"
|
#include "foliodelegate.h"
|
||||||
|
|
||||||
|
|
@ -39,6 +39,7 @@ public:
|
||||||
|
|
||||||
Q_INVOKABLE void removeEntry(int row);
|
Q_INVOKABLE void removeEntry(int row);
|
||||||
void moveEntry(int fromRow, int toRow);
|
void moveEntry(int fromRow, int toRow);
|
||||||
|
bool canAddEntry(int row, FolioDelegate *delegate);
|
||||||
bool addEntry(int row, FolioDelegate *delegate);
|
bool addEntry(int row, FolioDelegate *delegate);
|
||||||
FolioDelegate *getEntryAt(int row);
|
FolioDelegate *getEntryAt(int row);
|
||||||
|
|
||||||
|
|
@ -64,9 +65,10 @@ public:
|
||||||
Q_INVOKABLE void load();
|
Q_INVOKABLE void load();
|
||||||
void loadFromJson(QJsonArray arr);
|
void loadFromJson(QJsonArray arr);
|
||||||
|
|
||||||
void setApplet(Plasma::Applet *applet);
|
void setContainment(Plasma::Containment *containment);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void connectSaveRequests(FolioDelegate *delegate);
|
||||||
void evaluateDelegatePositions(bool emitSignal = true);
|
void evaluateDelegatePositions(bool emitSignal = true);
|
||||||
|
|
||||||
// get the x (or y) position where delegates start being placed
|
// get the x (or y) position where delegates start being placed
|
||||||
|
|
@ -78,5 +80,5 @@ private:
|
||||||
|
|
||||||
QList<FavouritesDelegate> m_delegates;
|
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;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject FolioApplication::toJson()
|
QJsonObject FolioApplication::toJson() const
|
||||||
{
|
{
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj[QStringLiteral("type")] = "application";
|
obj[QStringLiteral("type")] = "application";
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public:
|
||||||
FolioApplication(QObject *parent = nullptr, KService::Ptr service = QExplicitlySharedDataPointer<KService>{nullptr});
|
FolioApplication(QObject *parent = nullptr, KService::Ptr service = QExplicitlySharedDataPointer<KService>{nullptr});
|
||||||
|
|
||||||
static FolioApplication *fromJson(QJsonObject &obj, QObject *parent); // may return nullptr
|
static FolioApplication *fromJson(QJsonObject &obj, QObject *parent); // may return nullptr
|
||||||
QJsonObject toJson();
|
QJsonObject toJson() const;
|
||||||
|
|
||||||
bool running() const;
|
bool running() const;
|
||||||
QString name() const;
|
QString name() const;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ FolioApplicationFolder *FolioApplicationFolder::fromJson(QJsonObject &obj, QObje
|
||||||
return folder;
|
return folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject FolioApplicationFolder::toJson()
|
QJsonObject FolioApplicationFolder::toJson() const
|
||||||
{
|
{
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj[QStringLiteral("type")] = "folder";
|
obj[QStringLiteral("type")] = "folder";
|
||||||
|
|
@ -213,7 +213,7 @@ void ApplicationFolderModel::moveEntry(int fromRow, int toRow)
|
||||||
Q_EMIT m_folder->saveRequested();
|
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()) {
|
if (index < 0 || index > m_folder->m_delegates.size()) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -223,6 +223,15 @@ bool ApplicationFolderModel::addDelegate(FolioDelegate *delegate, int index)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApplicationFolderModel::addDelegate(FolioDelegate *delegate, int index)
|
||||||
|
{
|
||||||
|
if (!canAddDelegate(delegate, index)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (index == m_folder->m_delegates.size()) {
|
if (index == m_folder->m_delegates.size()) {
|
||||||
beginInsertRows(QModelIndex(), index, index);
|
beginInsertRows(QModelIndex(), index, index);
|
||||||
m_folder->m_delegates.append({delegate, 0, 0});
|
m_folder->m_delegates.append({delegate, 0, 0});
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public:
|
||||||
FolioApplicationFolder(QObject *parent = nullptr, QString name = QString{});
|
FolioApplicationFolder(QObject *parent = nullptr, QString name = QString{});
|
||||||
|
|
||||||
static FolioApplicationFolder *fromJson(QJsonObject &obj, QObject *parent);
|
static FolioApplicationFolder *fromJson(QJsonObject &obj, QObject *parent);
|
||||||
QJsonObject toJson();
|
QJsonObject toJson() const;
|
||||||
|
|
||||||
QString name() const;
|
QString name() const;
|
||||||
void setName(QString &name);
|
void setName(QString &name);
|
||||||
|
|
@ -92,6 +92,7 @@ public:
|
||||||
|
|
||||||
FolioDelegate *getDelegate(int index);
|
FolioDelegate *getDelegate(int index);
|
||||||
void moveEntry(int fromRow, int toRow);
|
void moveEntry(int fromRow, int toRow);
|
||||||
|
bool canAddDelegate(FolioDelegate *delegate, int index);
|
||||||
bool addDelegate(FolioDelegate *delegate, int index);
|
bool addDelegate(FolioDelegate *delegate, int index);
|
||||||
void removeDelegate(int index);
|
void removeDelegate(int index);
|
||||||
QPointF getDelegatePosition(int index);
|
QPointF getDelegatePosition(int index);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ FolioDelegate::FolioDelegate(QObject *parent)
|
||||||
, m_type{FolioDelegate::None}
|
, m_type{FolioDelegate::None}
|
||||||
, m_application{nullptr}
|
, m_application{nullptr}
|
||||||
, m_folder{nullptr}
|
, m_folder{nullptr}
|
||||||
|
, m_widget{nullptr}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ FolioDelegate::FolioDelegate(FolioApplication *application, QObject *parent)
|
||||||
, m_type{FolioDelegate::Application}
|
, m_type{FolioDelegate::Application}
|
||||||
, m_application{application}
|
, m_application{application}
|
||||||
, m_folder{nullptr}
|
, m_folder{nullptr}
|
||||||
|
, m_widget{nullptr}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,6 +27,16 @@ FolioDelegate::FolioDelegate(FolioApplicationFolder *folder, QObject *parent)
|
||||||
, m_type{FolioDelegate::Folder}
|
, m_type{FolioDelegate::Folder}
|
||||||
, m_application{nullptr}
|
, m_application{nullptr}
|
||||||
, m_folder{folder}
|
, 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};
|
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") {
|
} else if (type == "none") {
|
||||||
return new FolioDelegate{parent};
|
return new FolioDelegate{parent};
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +80,8 @@ QJsonObject FolioDelegate::toJson() const
|
||||||
return m_application->toJson();
|
return m_application->toJson();
|
||||||
case FolioDelegate::Folder:
|
case FolioDelegate::Folder:
|
||||||
return m_folder->toJson();
|
return m_folder->toJson();
|
||||||
|
case FolioDelegate::Widget:
|
||||||
|
return m_widget->toJson();
|
||||||
case FolioDelegate::None: {
|
case FolioDelegate::None: {
|
||||||
QJsonObject obj;
|
QJsonObject obj;
|
||||||
obj[QStringLiteral("type")] = "none";
|
obj[QStringLiteral("type")] = "none";
|
||||||
|
|
@ -86,3 +107,8 @@ FolioApplicationFolder *FolioDelegate::folder()
|
||||||
{
|
{
|
||||||
return m_folder;
|
return m_folder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FolioWidget *FolioDelegate::widget()
|
||||||
|
{
|
||||||
|
return m_widget;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "folioapplication.h"
|
#include "folioapplication.h"
|
||||||
#include "folioapplicationfolder.h"
|
#include "folioapplicationfolder.h"
|
||||||
|
#include "foliowidget.h"
|
||||||
|
|
||||||
class FolioApplication;
|
class FolioApplication;
|
||||||
class FolioApplicationFolder;
|
class FolioApplicationFolder;
|
||||||
|
|
@ -16,18 +17,21 @@ class FolioDelegate : public QObject
|
||||||
Q_PROPERTY(FolioDelegate::Type type READ type CONSTANT)
|
Q_PROPERTY(FolioDelegate::Type type READ type CONSTANT)
|
||||||
Q_PROPERTY(FolioApplication *application READ application CONSTANT)
|
Q_PROPERTY(FolioApplication *application READ application CONSTANT)
|
||||||
Q_PROPERTY(FolioApplicationFolder *folder READ folder CONSTANT)
|
Q_PROPERTY(FolioApplicationFolder *folder READ folder CONSTANT)
|
||||||
|
Q_PROPERTY(FolioWidget *widget READ widget CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Type {
|
enum Type {
|
||||||
None,
|
None,
|
||||||
Application,
|
Application,
|
||||||
Folder,
|
Folder,
|
||||||
|
Widget,
|
||||||
};
|
};
|
||||||
Q_ENUM(Type)
|
Q_ENUM(Type)
|
||||||
|
|
||||||
FolioDelegate(QObject *parent = nullptr);
|
FolioDelegate(QObject *parent = nullptr);
|
||||||
FolioDelegate(FolioApplication *application, QObject *parent);
|
FolioDelegate(FolioApplication *application, QObject *parent);
|
||||||
FolioDelegate(FolioApplicationFolder *folder, QObject *parent);
|
FolioDelegate(FolioApplicationFolder *folder, QObject *parent);
|
||||||
|
FolioDelegate(FolioWidget *widget, QObject *parent);
|
||||||
|
|
||||||
static FolioDelegate *fromJson(QJsonObject &obj, QObject *parent);
|
static FolioDelegate *fromJson(QJsonObject &obj, QObject *parent);
|
||||||
|
|
||||||
|
|
@ -36,9 +40,11 @@ public:
|
||||||
FolioDelegate::Type type();
|
FolioDelegate::Type type();
|
||||||
FolioApplication *application();
|
FolioApplication *application();
|
||||||
FolioApplicationFolder *folder();
|
FolioApplicationFolder *folder();
|
||||||
|
FolioWidget *widget();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
FolioDelegate::Type m_type;
|
FolioDelegate::Type m_type;
|
||||||
FolioApplication *m_application{nullptr};
|
FolioApplication *m_application{nullptr};
|
||||||
FolioApplicationFolder *m_folder{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 "folioapplicationfolder.h"
|
||||||
#include "foliodelegate.h"
|
#include "foliodelegate.h"
|
||||||
#include "foliosettings.h"
|
#include "foliosettings.h"
|
||||||
|
#include "foliowidget.h"
|
||||||
#include "homescreenstate.h"
|
#include "homescreenstate.h"
|
||||||
#include "pagelistmodel.h"
|
#include "pagelistmodel.h"
|
||||||
#include "pagemodel.h"
|
#include "pagemodel.h"
|
||||||
|
#include "widgetcontainer.h"
|
||||||
|
#include "widgetsmanager.h"
|
||||||
|
|
||||||
#include <KWindowSystem>
|
#include <KWindowSystem>
|
||||||
|
|
||||||
|
|
@ -31,12 +34,13 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
||||||
|
|
||||||
// pre-initialize
|
// pre-initialize
|
||||||
FolioSettings::self()->setApplet(this);
|
FolioSettings::self()->setApplet(this);
|
||||||
HomeScreenState::self();
|
HomeScreenState::self()->setContainment(this);
|
||||||
|
WidgetsManager::self();
|
||||||
|
|
||||||
// models are loaded in main.qml
|
// models are loaded in main.qml
|
||||||
ApplicationListModel::self();
|
ApplicationListModel::self();
|
||||||
FavouritesModel::self()->setApplet(this);
|
FavouritesModel::self()->setContainment(this);
|
||||||
PageListModel::self()->setApplet(this);
|
PageListModel::self()->setContainment(this);
|
||||||
|
|
||||||
qmlRegisterSingletonType<ApplicationListModel>(uri, 1, 0, "ApplicationListModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
qmlRegisterSingletonType<ApplicationListModel>(uri, 1, 0, "ApplicationListModel", [](QQmlEngine *, QJSEngine *) -> QObject * {
|
||||||
return ApplicationListModel::self();
|
return ApplicationListModel::self();
|
||||||
|
|
@ -60,13 +64,18 @@ HomeScreen::HomeScreen(QObject *parent, const KPluginMetaData &data, const QVari
|
||||||
|
|
||||||
qmlRegisterType<FolioApplication>(uri, 1, 0, "FolioApplication");
|
qmlRegisterType<FolioApplication>(uri, 1, 0, "FolioApplication");
|
||||||
qmlRegisterType<FolioApplicationFolder>(uri, 1, 0, "FolioApplicationFolder");
|
qmlRegisterType<FolioApplicationFolder>(uri, 1, 0, "FolioApplicationFolder");
|
||||||
|
qmlRegisterType<FolioWidget>(uri, 1, 0, "FolioWidget");
|
||||||
qmlRegisterType<FolioDelegate>(uri, 1, 0, "FolioDelegate");
|
qmlRegisterType<FolioDelegate>(uri, 1, 0, "FolioDelegate");
|
||||||
qmlRegisterType<PageModel>(uri, 1, 0, "PageModel");
|
qmlRegisterType<PageModel>(uri, 1, 0, "PageModel");
|
||||||
qmlRegisterType<FolioPageDelegate>(uri, 1, 0, "FolioPageDelegate");
|
qmlRegisterType<FolioPageDelegate>(uri, 1, 0, "FolioPageDelegate");
|
||||||
qmlRegisterType<DelegateTouchArea>(uri, 1, 0, "DelegateTouchArea");
|
qmlRegisterType<DelegateTouchArea>(uri, 1, 0, "DelegateTouchArea");
|
||||||
qmlRegisterType<DelegateDragPosition>(uri, 1, 0, "DelegateDragPosition");
|
qmlRegisterType<DelegateDragPosition>(uri, 1, 0, "DelegateDragPosition");
|
||||||
|
qmlRegisterType<WidgetContainer>(uri, 1, 0, "WidgetContainer");
|
||||||
|
|
||||||
connect(KWindowSystem::self(), &KWindowSystem::showingDesktopChanged, this, &HomeScreen::showingDesktopChanged);
|
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;
|
HomeScreen::~HomeScreen() = default;
|
||||||
|
|
@ -76,6 +85,16 @@ void HomeScreen::configChanged()
|
||||||
Plasma::Containment::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)
|
K_PLUGIN_CLASS(HomeScreen)
|
||||||
|
|
||||||
#include "homescreen.moc"
|
#include "homescreen.moc"
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,8 @@ public:
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void showingDesktopChanged(bool showingDesktop);
|
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();
|
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()
|
int HomeScreenState::currentPage()
|
||||||
{
|
{
|
||||||
return m_pageNum;
|
return m_pageNum;
|
||||||
|
|
@ -659,6 +679,16 @@ QPointF HomeScreenState::getFolderDelegateScreenPosition(int position)
|
||||||
return {x, y};
|
return {x, y};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Plasma::Containment *HomeScreenState::containment()
|
||||||
|
{
|
||||||
|
return m_containment;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeScreenState::setContainment(Plasma::Containment *containment)
|
||||||
|
{
|
||||||
|
m_containment = containment;
|
||||||
|
}
|
||||||
|
|
||||||
void HomeScreenState::openAppDrawer()
|
void HomeScreenState::openAppDrawer()
|
||||||
{
|
{
|
||||||
cancelAppDrawerAnimations();
|
cancelAppDrawerAnimations();
|
||||||
|
|
@ -786,11 +816,13 @@ void HomeScreenState::closeSettingsView()
|
||||||
m_closeSettingsAnim->start();
|
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
|
// start drag and drop positions
|
||||||
setDelegateDragX(startX);
|
setDelegateDragX(startX);
|
||||||
setDelegateDragY(startY);
|
setDelegateDragY(startY);
|
||||||
|
setDelegateDragPointerOffsetX(pointerOffsetX);
|
||||||
|
setDelegateDragPointerOffsetY(pointerOffsetY);
|
||||||
|
|
||||||
// end current swipe
|
// end current swipe
|
||||||
swipeEnded();
|
swipeEnded();
|
||||||
|
|
@ -799,21 +831,21 @@ void HomeScreenState::startDelegateDrag(qreal startX, qreal startY)
|
||||||
setSwipeState(SwipeState::AwaitingDraggingDelegate);
|
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);
|
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);
|
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);
|
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!
|
// 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);
|
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);
|
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()
|
void HomeScreenState::cancelDelegateDrag()
|
||||||
{
|
{
|
||||||
swipeEnded();
|
swipeEnded();
|
||||||
|
|
|
||||||
|
|
@ -228,12 +228,22 @@ public:
|
||||||
qreal searchWidgetY();
|
qreal searchWidgetY();
|
||||||
void setSearchWidgetY(qreal searchWidgetY);
|
void setSearchWidgetY(qreal searchWidgetY);
|
||||||
|
|
||||||
|
// the top left x-position of the delegate being dragged
|
||||||
qreal delegateDragX();
|
qreal delegateDragX();
|
||||||
void setDelegateDragX(qreal delegateDragX);
|
void setDelegateDragX(qreal delegateDragX);
|
||||||
|
|
||||||
|
// the top left y-position of the delegate being dragged
|
||||||
qreal delegateDragY();
|
qreal delegateDragY();
|
||||||
void setDelegateDragY(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();
|
int currentPage();
|
||||||
void setCurrentPage(int currentPage);
|
void setCurrentPage(int currentPage);
|
||||||
|
|
||||||
|
|
@ -247,6 +257,9 @@ public:
|
||||||
Q_INVOKABLE QPointF getFavouritesDelegateScreenPosition(int position);
|
Q_INVOKABLE QPointF getFavouritesDelegateScreenPosition(int position);
|
||||||
Q_INVOKABLE QPointF getFolderDelegateScreenPosition(int position);
|
Q_INVOKABLE QPointF getFolderDelegateScreenPosition(int position);
|
||||||
|
|
||||||
|
Plasma::Containment *containment();
|
||||||
|
void setContainment(Plasma::Containment *containment);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void swipeStateChanged();
|
void swipeStateChanged();
|
||||||
void viewStateChanged();
|
void viewStateChanged();
|
||||||
|
|
@ -290,6 +303,7 @@ Q_SIGNALS:
|
||||||
void delegateDragFromFavouritesStarted(int position);
|
void delegateDragFromFavouritesStarted(int position);
|
||||||
void delegateDragFromAppDrawerStarted(QString storageId);
|
void delegateDragFromAppDrawerStarted(QString storageId);
|
||||||
void delegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
void delegateDragFromFolderStarted(FolioApplicationFolder *folder, int position);
|
||||||
|
void delegateDragFromWidgetListStarted(QString appletPluginId);
|
||||||
void pageNumChanged();
|
void pageNumChanged();
|
||||||
void folderPageNumChanged();
|
void folderPageNumChanged();
|
||||||
|
|
||||||
|
|
@ -314,10 +328,11 @@ public Q_SLOTS:
|
||||||
void openSettingsView();
|
void openSettingsView();
|
||||||
void closeSettingsView();
|
void closeSettingsView();
|
||||||
|
|
||||||
void startDelegatePageDrag(qreal startX, qreal startY, int page, int row, int column);
|
void startDelegatePageDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, int page, int row, int column);
|
||||||
void startDelegateFavouritesDrag(qreal startX, qreal startY, int position);
|
void startDelegateFavouritesDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, int position);
|
||||||
void startDelegateAppDrawerDrag(qreal startX, qreal startY, QString storageId);
|
void startDelegateAppDrawerDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY, QString storageId);
|
||||||
void startDelegateFolderDrag(qreal startX, qreal startY, FolioApplicationFolder *folder, int position);
|
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();
|
void cancelDelegateDrag();
|
||||||
|
|
||||||
// from SwipeArea
|
// from SwipeArea
|
||||||
|
|
@ -329,7 +344,7 @@ private:
|
||||||
void setViewState(ViewState viewState);
|
void setViewState(ViewState viewState);
|
||||||
void setSwipeState(SwipeState swipeState);
|
void setSwipeState(SwipeState swipeState);
|
||||||
|
|
||||||
void startDelegateDrag(qreal startX, qreal startY);
|
void startDelegateDrag(qreal startX, qreal startY, qreal pointerOffsetX, qreal pointerOffsetY);
|
||||||
|
|
||||||
void cancelAppDrawerAnimations();
|
void cancelAppDrawerAnimations();
|
||||||
void cancelSearchWidgetAnimations();
|
void cancelSearchWidgetAnimations();
|
||||||
|
|
@ -380,6 +395,8 @@ private:
|
||||||
qreal m_searchWidgetY{0};
|
qreal m_searchWidgetY{0};
|
||||||
qreal m_delegateDragX{0};
|
qreal m_delegateDragX{0};
|
||||||
qreal m_delegateDragY{0};
|
qreal m_delegateDragY{0};
|
||||||
|
qreal m_delegateDragPointerOffsetX{0};
|
||||||
|
qreal m_delegateDragPointerOffsetY{0};
|
||||||
|
|
||||||
int m_pageNum{0};
|
int m_pageNum{0};
|
||||||
int m_folderPageNum{0};
|
int m_folderPageNum{0};
|
||||||
|
|
@ -397,4 +414,6 @@ private:
|
||||||
QPropertyAnimation *m_folderPageAnim{nullptr};
|
QPropertyAnimation *m_folderPageAnim{nullptr};
|
||||||
QPropertyAnimation *m_openSettingsAnim{nullptr};
|
QPropertyAnimation *m_openSettingsAnim{nullptr};
|
||||||
QPropertyAnimation *m_closeSettingsAnim{nullptr};
|
QPropertyAnimation *m_closeSettingsAnim{nullptr};
|
||||||
|
|
||||||
|
Plasma::Containment *m_containment{nullptr};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ import QtQuick.Layouts
|
||||||
import QtQuick.Controls as Controls
|
import QtQuick.Controls as Controls
|
||||||
import Qt5Compat.GraphicalEffects
|
import Qt5Compat.GraphicalEffects
|
||||||
|
|
||||||
import org.kde.plasma.plasmoid 2.0
|
|
||||||
import org.kde.plasma.components 3.0 as PC3
|
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 org.kde.plasma.private.mobileshell as MobileShell
|
||||||
|
|
||||||
|
import 'private'
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
|
@ -58,26 +59,13 @@ Item {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
headerHeight: root.headerHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// opacity gradient at grid edges
|
// opacity gradient at grid edges
|
||||||
OpacityMask {
|
FlickableOpacityGradient {
|
||||||
anchors.fill: appDrawerGrid
|
anchors.fill: appDrawerGrid
|
||||||
source: appDrawerGrid
|
flickable: 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' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ MobileShell.GridView {
|
||||||
layer.enabled: true
|
layer.enabled: true
|
||||||
|
|
||||||
property var homeScreen
|
property var homeScreen
|
||||||
|
property real headerHeight
|
||||||
|
|
||||||
readonly property int reservedSpaceForLabel: Folio.HomeScreenState.pageDelegateLabelHeight
|
readonly property int reservedSpaceForLabel: Folio.HomeScreenState.pageDelegateLabelHeight
|
||||||
readonly property real effectiveContentWidth: width - leftMargin - rightMargin
|
readonly property real effectiveContentWidth: width - leftMargin - rightMargin
|
||||||
|
|
@ -30,7 +31,7 @@ MobileShell.GridView {
|
||||||
leftMargin: horizontalMargin
|
leftMargin: horizontalMargin
|
||||||
rightMargin: 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
|
cellHeight: cellWidth + reservedSpaceForLabel
|
||||||
|
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
@ -79,11 +80,18 @@ MobileShell.GridView {
|
||||||
height: root.cellHeight
|
height: root.cellHeight
|
||||||
|
|
||||||
onPressAndHold: {
|
onPressAndHold: {
|
||||||
|
const mappedCoords = root.homeScreen.prepareStartDelegateDrag(model.delegate, delegate.delegateItem);
|
||||||
Folio.HomeScreenState.closeAppDrawer();
|
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(
|
Folio.HomeScreenState.startDelegateAppDrawerDrag(
|
||||||
mappedCoords.x,
|
centerX - Folio.HomeScreenState.pageCellWidth / 2,
|
||||||
mappedCoords.y,
|
centerY - Folio.HomeScreenState.pageCellHeight / 2,
|
||||||
|
delegate.pressPosition.x,
|
||||||
|
delegate.pressPosition.y,
|
||||||
model.delegate.application.storageId
|
model.delegate.application.storageId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,18 @@ Item {
|
||||||
|
|
||||||
readonly property real dropAnimationRunning: dragXAnim.running || dragYAnim.running
|
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
|
visible: false
|
||||||
x: Math.round(Folio.HomeScreenState.delegateDragX)
|
x: Folio.HomeScreenState.delegateDragX
|
||||||
y: Math.round(Folio.HomeScreenState.delegateDragY)
|
y: Folio.HomeScreenState.delegateDragY
|
||||||
|
|
||||||
function setXBinding() {
|
function setXBinding() {
|
||||||
x = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragX));
|
x = Qt.binding(() => Folio.HomeScreenState.delegateDragX);
|
||||||
}
|
}
|
||||||
function setYBinding() {
|
function setYBinding() {
|
||||||
y = Qt.binding(() => Math.round(Folio.HomeScreenState.delegateDragY));
|
y = Qt.binding(() => Folio.HomeScreenState.delegateDragY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// animate drop x
|
// animate drop x
|
||||||
|
|
@ -70,7 +73,7 @@ Item {
|
||||||
|
|
||||||
// reset and show drag item
|
// reset and show drag item
|
||||||
function onSwipeStateChanged() {
|
function onSwipeStateChanged() {
|
||||||
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate) {
|
if (Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate && !isWidgetDrag) {
|
||||||
root.scale = 1.0;
|
root.scale = 1.0;
|
||||||
root.visible = true;
|
root.visible = true;
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +81,10 @@ Item {
|
||||||
|
|
||||||
// save the existing delegate at the spot (this is called before the delegate is dropped)
|
// save the existing delegate at the spot (this is called before the delegate is dropped)
|
||||||
function onDelegateDragEnded() {
|
function onDelegateDragEnded() {
|
||||||
|
if (root.isWidgetDrag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let dragState = Folio.HomeScreenState.dragState;
|
let dragState = Folio.HomeScreenState.dragState;
|
||||||
let dropPosition = dragState.candidateDropPosition;
|
let dropPosition = dragState.candidateDropPosition;
|
||||||
|
|
||||||
|
|
@ -89,7 +96,7 @@ Item {
|
||||||
stateWatcher.delegateDroppedOn = Folio.HomeScreenState.getFavouritesDelegateAt(dropPosition.favouritesPosition);
|
stateWatcher.delegateDroppedOn = Folio.HomeScreenState.getFavouritesDelegateAt(dropPosition.favouritesPosition);
|
||||||
break;
|
break;
|
||||||
case Folio.DelegateDragPosition.Folder:
|
case Folio.DelegateDragPosition.Folder:
|
||||||
stateWatcher.delegateDroppedOn = null
|
stateWatcher.delegateDroppedOn = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +107,10 @@ Item {
|
||||||
|
|
||||||
// animate from when the delegate is dropped to its drop position
|
// animate from when the delegate is dropped to its drop position
|
||||||
function onDelegateDroppedAndPlaced() {
|
function onDelegateDroppedAndPlaced() {
|
||||||
|
if (root.isWidgetDrag) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let dragState = Folio.HomeScreenState.dragState;
|
let dragState = Folio.HomeScreenState.dragState;
|
||||||
let dropPosition = dragState.candidateDropPosition;
|
let dropPosition = dragState.candidateDropPosition;
|
||||||
|
|
||||||
|
|
@ -130,6 +141,11 @@ Item {
|
||||||
scaleAnim.restart();
|
scaleAnim.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if the drop has been abandoned, just hide
|
||||||
|
function onNewDelegateDropAbandoned() {
|
||||||
|
root.visible = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// simulate an icon delegate
|
// simulate an icon delegate
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,8 @@ MouseArea {
|
||||||
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||||
mappedCoords.x,
|
mappedCoords.x,
|
||||||
mappedCoords.y,
|
mappedCoords.y,
|
||||||
|
appDelegate.pressPosition.x,
|
||||||
|
appDelegate.pressPosition.y,
|
||||||
delegate.index
|
delegate.index
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -178,6 +180,8 @@ MouseArea {
|
||||||
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
Folio.HomeScreenState.startDelegateFavouritesDrag(
|
||||||
mappedCoords.x,
|
mappedCoords.x,
|
||||||
mappedCoords.y,
|
mappedCoords.y,
|
||||||
|
appFolderDelegate.pressPosition.x,
|
||||||
|
appFolderDelegate.pressPosition.y,
|
||||||
delegate.index
|
delegate.index
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -229,6 +229,8 @@ Folio.DelegateTouchArea {
|
||||||
Folio.HomeScreenState.startDelegateFolderDrag(
|
Folio.HomeScreenState.startDelegateFolderDrag(
|
||||||
mappedCoords.x,
|
mappedCoords.x,
|
||||||
mappedCoords.y,
|
mappedCoords.y,
|
||||||
|
appDelegate.pressPosition.x,
|
||||||
|
appDelegate.pressPosition.y,
|
||||||
root.folder,
|
root.folder,
|
||||||
delegate.index
|
delegate.index
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -29,8 +29,13 @@ Item {
|
||||||
|
|
||||||
property Folio.HomeScreenState homeScreenState: Folio.HomeScreenState
|
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
|
readonly property real settingsModeHomeScreenScale: 0.8
|
||||||
|
|
||||||
onTopMarginChanged: Folio.HomeScreenState.viewTopPadding = root.topMargin
|
onTopMarginChanged: Folio.HomeScreenState.viewTopPadding = root.topMargin
|
||||||
|
|
@ -43,7 +48,9 @@ Item {
|
||||||
function prepareStartDelegateDrag(delegate, item) {
|
function prepareStartDelegateDrag(delegate, item) {
|
||||||
swipeArea.setSkipSwipeThreshold(true);
|
swipeArea.setSkipSwipeThreshold(true);
|
||||||
|
|
||||||
delegateDragItem.delegate = delegate;
|
if (delegate) {
|
||||||
|
delegateDragItem.delegate = delegate;
|
||||||
|
}
|
||||||
return root.mapFromItem(item, 0, 0);
|
return root.mapFromItem(item, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,12 +89,18 @@ Item {
|
||||||
onHeightChanged: Folio.HomeScreenState.viewHeight = height;
|
onHeightChanged: Folio.HomeScreenState.viewHeight = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a way of stopping focus
|
||||||
|
FocusScope {
|
||||||
|
id: noFocus
|
||||||
|
}
|
||||||
|
|
||||||
// area that can be swiped
|
// area that can be swiped
|
||||||
MobileShell.SwipeArea {
|
MobileShell.SwipeArea {
|
||||||
id: swipeArea
|
id: swipeArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
interactive: root.interactive &&
|
interactive: root.interactive &&
|
||||||
|
settings.homeScreenInteractive &&
|
||||||
!appDrawer.flickable.moving &&
|
!appDrawer.flickable.moving &&
|
||||||
(appDrawer.flickable.atYBeginning || // disable the swipe area when we are swiping in the app drawer, and not in drag-and-drop
|
(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 ||
|
Folio.HomeScreenState.swipeState === Folio.HomeScreenState.AwaitingDraggingDelegate ||
|
||||||
|
|
@ -104,13 +117,25 @@ Item {
|
||||||
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
homeScreenState.swipeMoved(totalDeltaX, totalDeltaY, deltaX, deltaY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onPressedChanged: {
|
||||||
|
if (pressed) {
|
||||||
|
// ensures that components like the widget settings overlay close when swiping
|
||||||
|
noFocus.forceActiveFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsComponent {
|
SettingsComponent {
|
||||||
id: settings
|
id: settings
|
||||||
anchors.fill: parent
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
opacity: Folio.HomeScreenState.settingsOpenProgress
|
opacity: Folio.HomeScreenState.settingsOpenProgress
|
||||||
visible: opacity > 0
|
|
||||||
z: 1
|
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
|
settingsModeHomeScreenScale: root.settingsModeHomeScreenScale
|
||||||
homeScreen: root
|
homeScreen: root
|
||||||
|
|
||||||
|
|
@ -333,6 +358,11 @@ Item {
|
||||||
id: delegateDragItem
|
id: delegateDragItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// drag and drop for widgets
|
||||||
|
WidgetDragItem {
|
||||||
|
id: widgetDragItem
|
||||||
|
}
|
||||||
|
|
||||||
// bottom app drawer
|
// bottom app drawer
|
||||||
AppDrawer {
|
AppDrawer {
|
||||||
id: appDrawer
|
id: appDrawer
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,16 @@
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Window
|
import QtQuick.Window
|
||||||
import QtQuick.Layouts
|
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.state as MobileShellState
|
||||||
import org.kde.plasma.private.mobileshell as MobileShell
|
import org.kde.plasma.private.mobileshell as MobileShell
|
||||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
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 "./delegate"
|
||||||
|
import "./private"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
@ -36,17 +39,48 @@ Item {
|
||||||
height: Folio.HomeScreenState.pageCellHeight
|
height: Folio.HomeScreenState.pageCellHeight
|
||||||
|
|
||||||
property var dropPosition: Folio.HomeScreenState.dragState.candidateDropPosition
|
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
|
// only show if it is an empty spot on this page
|
||||||
visible: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
visible: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate &&
|
||||||
dropPosition.location === Folio.DelegateDragPosition.Pages &&
|
dropPosition.location === Folio.DelegateDragPosition.Pages &&
|
||||||
dropPosition.page === root.pageNum &&
|
dropPosition.page === root.pageNum &&
|
||||||
|
!dropDelegateIsWidget &&
|
||||||
Folio.HomeScreenState.getPageDelegateAt(root.pageNum, dropPosition.pageRow, dropPosition.pageColumn) === null
|
Folio.HomeScreenState.getPageDelegateAt(root.pageNum, dropPosition.pageRow, dropPosition.pageColumn) === null
|
||||||
|
|
||||||
x: dropPosition.pageColumn * Folio.HomeScreenState.pageCellWidth
|
x: dropPosition.pageColumn * Folio.HomeScreenState.pageCellWidth
|
||||||
y: dropPosition.pageRow * Folio.HomeScreenState.pageCellHeight
|
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 {
|
Repeater {
|
||||||
model: root.pageModel
|
model: root.pageModel
|
||||||
|
|
||||||
|
|
@ -69,10 +103,10 @@ Item {
|
||||||
dragState.dropDelegate.type === Folio.FolioDelegate.Application &&
|
dragState.dropDelegate.type === Folio.FolioDelegate.Application &&
|
||||||
isDropPositionThis
|
isDropPositionThis
|
||||||
|
|
||||||
implicitWidth: Folio.HomeScreenState.pageCellWidth
|
implicitWidth: loader.item ? loader.item.implicitWidth : 0
|
||||||
implicitHeight: Folio.HomeScreenState.pageCellHeight
|
implicitHeight: loader.item ? loader.item.implicitHeight : 0
|
||||||
width: Folio.HomeScreenState.pageCellWidth
|
width: loader.item ? loader.item.width : 0
|
||||||
height: Folio.HomeScreenState.pageCellHeight
|
height: loader.item ? loader.item.height : 0
|
||||||
|
|
||||||
x: column * Folio.HomeScreenState.pageCellWidth
|
x: column * Folio.HomeScreenState.pageCellWidth
|
||||||
y: row * Folio.HomeScreenState.pageCellHeight
|
y: row * Folio.HomeScreenState.pageCellHeight
|
||||||
|
|
@ -81,13 +115,17 @@ Item {
|
||||||
column >= 0 && column < Folio.HomeScreenState.pageColumns
|
column >= 0 && column < Folio.HomeScreenState.pageColumns
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
anchors.fill: parent
|
id: loader
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.left: parent.left
|
||||||
|
|
||||||
sourceComponent: {
|
sourceComponent: {
|
||||||
if (delegate.pageDelegate.type === Folio.FolioDelegate.Application) {
|
if (delegate.pageDelegate.type === Folio.FolioDelegate.Application) {
|
||||||
return appComponent;
|
return appComponent;
|
||||||
} else if (delegate.pageDelegate.type === Folio.FolioDelegate.Folder) {
|
} else if (delegate.pageDelegate.type === Folio.FolioDelegate.Folder) {
|
||||||
return folderComponent;
|
return folderComponent;
|
||||||
|
} else if (delegate.pageDelegate.type === Folio.FolioDelegate.Widget) {
|
||||||
|
return widgetComponent;
|
||||||
} else {
|
} else {
|
||||||
return noneComponent;
|
return noneComponent;
|
||||||
}
|
}
|
||||||
|
|
@ -110,6 +148,11 @@ Item {
|
||||||
turnToFolder: delegate.isAppHoveredOver
|
turnToFolder: delegate.isAppHoveredOver
|
||||||
turnToFolderAnimEnabled: Folio.HomeScreenState.swipeState === Folio.HomeScreenState.DraggingDelegate
|
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
|
// do not show if the drop animation is running to this delegate
|
||||||
visible: !(root.homeScreen.dropAnimationRunning && delegate.isDropPositionThis)
|
visible: !(root.homeScreen.dropAnimationRunning && delegate.isDropPositionThis)
|
||||||
|
|
||||||
|
|
@ -121,6 +164,8 @@ Item {
|
||||||
Folio.HomeScreenState.startDelegatePageDrag(
|
Folio.HomeScreenState.startDelegatePageDrag(
|
||||||
mappedCoords.x,
|
mappedCoords.x,
|
||||||
mappedCoords.y,
|
mappedCoords.y,
|
||||||
|
appDelegate.pressPosition.x,
|
||||||
|
appDelegate.pressPosition.y,
|
||||||
root.pageNum,
|
root.pageNum,
|
||||||
delegate.pageDelegate.row,
|
delegate.pageDelegate.row,
|
||||||
delegate.pageDelegate.column
|
delegate.pageDelegate.column
|
||||||
|
|
@ -173,6 +218,11 @@ Item {
|
||||||
name: Folio.FolioSettings.showPagesAppLabels ? delegate.pageDelegate.folder.name : ""
|
name: Folio.FolioSettings.showPagesAppLabels ? delegate.pageDelegate.folder.name : ""
|
||||||
folder: delegate.pageDelegate.folder
|
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
|
// do not show if the drop animation is running to this delegate, and the drop delegate is a folder
|
||||||
visible: !(root.homeScreen.dropAnimationRunning &&
|
visible: !(root.homeScreen.dropAnimationRunning &&
|
||||||
delegate.isDropPositionThis &&
|
delegate.isDropPositionThis &&
|
||||||
|
|
@ -193,6 +243,8 @@ Item {
|
||||||
Folio.HomeScreenState.startDelegatePageDrag(
|
Folio.HomeScreenState.startDelegatePageDrag(
|
||||||
mappedCoords.x,
|
mappedCoords.x,
|
||||||
mappedCoords.y,
|
mappedCoords.y,
|
||||||
|
appFolderDelegate.pressPosition.x,
|
||||||
|
appFolderDelegate.pressPosition.y,
|
||||||
root.pageNum,
|
root.pageNum,
|
||||||
delegate.pageDelegate.row,
|
delegate.pageDelegate.row,
|
||||||
delegate.pageDelegate.column
|
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.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
import org.kde.plasma.components 3.0 as PlasmaComponents
|
|
||||||
import org.kde.kquickcontrolsaddons 2.0
|
import org.kde.kquickcontrolsaddons 2.0
|
||||||
|
|
||||||
import org.kde.private.mobile.homescreen.folio 1.0 as Folio
|
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 var homeScreen
|
||||||
property real settingsModeHomeScreenScale
|
property real settingsModeHomeScreenScale
|
||||||
|
|
||||||
|
readonly property bool homeScreenInteractive: !appletListViewer.open
|
||||||
|
|
||||||
signal requestLeaveSettingsMode()
|
signal requestLeaveSettingsMode()
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|
@ -80,17 +82,42 @@ Item {
|
||||||
|
|
||||||
PC3.ToolButton {
|
PC3.ToolButton {
|
||||||
text: 'Widgets'
|
text: 'Widgets'
|
||||||
enabled: false
|
|
||||||
display: PC3.ToolButton.TextUnderIcon
|
display: PC3.ToolButton.TextUnderIcon
|
||||||
|
|
||||||
icon.name: 'widget-alternatives'
|
icon.name: 'widget-alternatives'
|
||||||
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 4
|
implicitHeight: Kirigami.Units.gridUnit * 4
|
||||||
implicitWidth: Kirigami.Units.gridUnit * 5
|
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 {
|
SettingsWindow {
|
||||||
id: settingsWindow
|
id: settingsWindow
|
||||||
visible: false
|
visible: false
|
||||||
|
|
|
||||||
|
|
@ -105,24 +105,24 @@ QJsonArray PageListModel::exportToJson()
|
||||||
|
|
||||||
void PageListModel::save()
|
void PageListModel::save()
|
||||||
{
|
{
|
||||||
if (!m_applet) {
|
if (!m_containment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray arr = exportToJson();
|
QJsonArray arr = exportToJson();
|
||||||
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
QByteArray data = QJsonDocument(arr).toJson(QJsonDocument::Compact);
|
||||||
|
|
||||||
m_applet->config().writeEntry("Pages", QString::fromStdString(data.toStdString()));
|
m_containment->config().writeEntry("Pages", QString::fromStdString(data.toStdString()));
|
||||||
Q_EMIT m_applet->configNeedsSaving();
|
Q_EMIT m_containment->configNeedsSaving();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageListModel::load()
|
void PageListModel::load()
|
||||||
{
|
{
|
||||||
if (!m_applet) {
|
if (!m_containment) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(m_applet->config().readEntry("Pages", "{}").toUtf8());
|
QJsonDocument doc = QJsonDocument::fromJson(m_containment->config().readEntry("Pages", "{}").toUtf8());
|
||||||
loadFromJson(doc.array());
|
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 <QAbstractListModel>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
|
||||||
#include <Plasma/Applet>
|
#include <Plasma/Containment>
|
||||||
|
|
||||||
class PageListModel : public QAbstractListModel
|
class PageListModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
|
|
@ -38,7 +38,7 @@ public:
|
||||||
Q_INVOKABLE void load();
|
Q_INVOKABLE void load();
|
||||||
void loadFromJson(QJsonArray arr);
|
void loadFromJson(QJsonArray arr);
|
||||||
|
|
||||||
void setApplet(Plasma::Applet *applet);
|
void setContainment(Plasma::Containment *containment);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void lengthChanged();
|
void lengthChanged();
|
||||||
|
|
@ -46,5 +46,5 @@ Q_SIGNALS:
|
||||||
private:
|
private:
|
||||||
QList<PageModel *> m_pages;
|
QList<PageModel *> m_pages;
|
||||||
|
|
||||||
Plasma::Applet *m_applet{nullptr};
|
Plasma::Containment *m_containment{nullptr};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "pagemodel.h"
|
#include "pagemodel.h"
|
||||||
#include "foliosettings.h"
|
#include "foliosettings.h"
|
||||||
#include "homescreenstate.h"
|
#include "homescreenstate.h"
|
||||||
|
#include "widgetsmanager.h"
|
||||||
|
|
||||||
FolioPageDelegate::FolioPageDelegate(int row, int column, QObject *parent)
|
FolioPageDelegate::FolioPageDelegate(int row, int column, QObject *parent)
|
||||||
: FolioDelegate{parent}
|
: FolioDelegate{parent}
|
||||||
|
|
@ -29,6 +30,14 @@ FolioPageDelegate::FolioPageDelegate(int row, int column, FolioApplicationFolder
|
||||||
init();
|
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)
|
FolioPageDelegate::FolioPageDelegate(int row, int column, FolioDelegate *delegate, QObject *parent)
|
||||||
: FolioDelegate{parent}
|
: FolioDelegate{parent}
|
||||||
, m_row{row}
|
, m_row{row}
|
||||||
|
|
@ -37,6 +46,7 @@ FolioPageDelegate::FolioPageDelegate(int row, int column, FolioDelegate *delegat
|
||||||
m_type = delegate->type();
|
m_type = delegate->type();
|
||||||
m_application = delegate->application();
|
m_application = delegate->application();
|
||||||
m_folder = delegate->folder();
|
m_folder = delegate->folder();
|
||||||
|
m_widget = delegate->widget();
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
@ -52,20 +62,46 @@ void FolioPageDelegate::init()
|
||||||
case HomeScreenState::RotateClockwise:
|
case HomeScreenState::RotateClockwise:
|
||||||
m_realRow = HomeScreenState::self()->pageColumns() - m_column - 1;
|
m_realRow = HomeScreenState::self()->pageColumns() - m_column - 1;
|
||||||
m_realColumn = m_row;
|
m_realColumn = m_row;
|
||||||
|
|
||||||
|
if (m_widget) {
|
||||||
|
// since top-left in cw is bottom-left in portrait
|
||||||
|
m_realRow -= m_widget->realGridHeight() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case HomeScreenState::RotateCounterClockwise: // (0, 4) -> (4, 3)
|
case HomeScreenState::RotateCounterClockwise:
|
||||||
m_realRow = m_column;
|
m_realRow = m_column;
|
||||||
m_realColumn = HomeScreenState::self()->pageRows() - m_row - 1;
|
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;
|
break;
|
||||||
case HomeScreenState::RotateUpsideDown:
|
case HomeScreenState::RotateUpsideDown:
|
||||||
m_realRow = HomeScreenState::self()->pageRows() - m_row - 1;
|
m_realRow = HomeScreenState::self()->pageRows() - m_row - 1;
|
||||||
m_realColumn = HomeScreenState::self()->pageColumns() - m_column - 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;
|
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]() {
|
connect(HomeScreenState::self(), &HomeScreenState::pageOrientationChanged, this, [this]() {
|
||||||
setRow(getTranslatedRow(m_realRow, m_realColumn));
|
setRowOnly(getTranslatedTopLeftRow(m_realRow, m_realColumn, this));
|
||||||
setColumn(getTranslatedColumn(m_realRow, m_realColumn));
|
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 realRow = obj[QStringLiteral("row")].toInt();
|
||||||
int realColumn = obj[QStringLiteral("column")].toInt();
|
int realColumn = obj[QStringLiteral("column")].toInt();
|
||||||
|
|
||||||
int row = getTranslatedRow(realRow, realColumn);
|
int row = getTranslatedTopLeftRow(realRow, realColumn, fd);
|
||||||
int column = getTranslatedColumn(realRow, realColumn);
|
int column = getTranslatedTopLeftColumn(realRow, realColumn, fd);
|
||||||
|
|
||||||
FolioPageDelegate *delegate = new FolioPageDelegate{row, column, fd, parent};
|
FolioPageDelegate *delegate = new FolioPageDelegate{row, column, fd, parent};
|
||||||
fd->deleteLater();
|
fd->deleteLater();
|
||||||
|
|
@ -89,6 +125,32 @@ FolioPageDelegate *FolioPageDelegate::fromJson(QJsonObject &obj, QObject *parent
|
||||||
return delegate;
|
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)
|
int FolioPageDelegate::getTranslatedRow(int realRow, int realColumn)
|
||||||
{
|
{
|
||||||
// we have to use the "real" rows and columns, so fetch them from FolioSettings instead of HomeScreenState
|
// 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)
|
void FolioPageDelegate::setRow(int row)
|
||||||
{
|
{
|
||||||
m_row = row;
|
if (m_row != row) {
|
||||||
Q_EMIT rowChanged();
|
// 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()
|
int FolioPageDelegate::column()
|
||||||
|
|
@ -147,14 +234,51 @@ int FolioPageDelegate::column()
|
||||||
|
|
||||||
void FolioPageDelegate::setColumn(int column)
|
void FolioPageDelegate::setColumn(int column)
|
||||||
{
|
{
|
||||||
m_column = column;
|
if (m_column != column) {
|
||||||
Q_EMIT columnChanged();
|
// 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)
|
PageModel::PageModel(QList<FolioPageDelegate *> delegates, QObject *parent)
|
||||||
: QAbstractListModel{parent}
|
: QAbstractListModel{parent}
|
||||||
, m_delegates{delegates}
|
, 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;
|
PageModel::~PageModel() = default;
|
||||||
|
|
@ -162,7 +286,6 @@ PageModel::~PageModel() = default;
|
||||||
PageModel *PageModel::fromJson(QJsonArray &arr, QObject *parent)
|
PageModel *PageModel::fromJson(QJsonArray &arr, QObject *parent)
|
||||||
{
|
{
|
||||||
QList<FolioPageDelegate *> delegates;
|
QList<FolioPageDelegate *> delegates;
|
||||||
QList<FolioPageDelegate *> folderDelegates;
|
|
||||||
|
|
||||||
for (QJsonValueRef r : arr) {
|
for (QJsonValueRef r : arr) {
|
||||||
QJsonObject obj = r.toObject();
|
QJsonObject obj = r.toObject();
|
||||||
|
|
@ -170,18 +293,14 @@ PageModel *PageModel::fromJson(QJsonArray &arr, QObject *parent)
|
||||||
FolioPageDelegate *delegate = FolioPageDelegate::fromJson(obj, parent);
|
FolioPageDelegate *delegate = FolioPageDelegate::fromJson(obj, parent);
|
||||||
if (delegate) {
|
if (delegate) {
|
||||||
delegates.append(delegate);
|
delegates.append(delegate);
|
||||||
|
|
||||||
if (delegate->type() == FolioDelegate::Folder) {
|
|
||||||
folderDelegates.append(delegate);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PageModel *model = new PageModel{delegates, parent};
|
PageModel *model = new PageModel{delegates, parent};
|
||||||
|
|
||||||
// ensure folders request saves
|
// ensure delegates can request saves
|
||||||
for (auto *delegate : folderDelegates) {
|
for (auto *delegate : delegates) {
|
||||||
connect(delegate->folder(), &FolioApplicationFolder::saveRequested, model, &PageModel::save);
|
model->connectSaveRequests(delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
|
|
@ -229,42 +348,87 @@ QHash<int, QByteArray> PageModel::roleNames() const
|
||||||
|
|
||||||
void PageModel::removeDelegate(int row, int col)
|
void PageModel::removeDelegate(int row, int col)
|
||||||
{
|
{
|
||||||
bool removed = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < m_delegates.size(); ++i) {
|
for (int i = 0; i < m_delegates.size(); ++i) {
|
||||||
if (m_delegates[i]->row() == row && m_delegates[i]->column() == col) {
|
if (m_delegates[i]->row() == row && m_delegates[i]->column() == col) {
|
||||||
beginRemoveRows(QModelIndex(), i, i);
|
removeDelegate(i);
|
||||||
// HACK: do not deleteLater(), because the delegate might still be used somewhere else
|
break;
|
||||||
m_delegates.removeAt(i);
|
}
|
||||||
endRemoveRows();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
return true;
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PageModel::addDelegate(FolioPageDelegate *delegate)
|
bool PageModel::addDelegate(FolioPageDelegate *delegate)
|
||||||
{
|
{
|
||||||
if (delegate->row() < 0 || delegate->row() >= HomeScreenState::self()->pageRows() || delegate->column() < 0
|
if (!canAddDelegate(delegate->row(), delegate->column(), delegate)) {
|
||||||
|| delegate->column() >= HomeScreenState::self()->pageColumns()) {
|
|
||||||
return false;
|
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());
|
beginInsertRows(QModelIndex(), m_delegates.size(), m_delegates.size());
|
||||||
m_delegates.append(delegate);
|
m_delegates.append(delegate);
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
|
|
||||||
|
// ensure the delegate requests saves
|
||||||
|
connectSaveRequests(delegate);
|
||||||
save();
|
save();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -276,15 +440,69 @@ FolioPageDelegate *PageModel::getDelegate(int row, int col)
|
||||||
if (d->row() == row && d->column() == col) {
|
if (d->row() == row && d->column() == col) {
|
||||||
return d;
|
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;
|
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()
|
bool PageModel::isPageEmpty()
|
||||||
{
|
{
|
||||||
return m_delegates.size() == 0;
|
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()
|
void PageModel::save()
|
||||||
{
|
{
|
||||||
Q_EMIT saveRequested();
|
Q_EMIT saveRequested();
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,12 @@ public:
|
||||||
FolioPageDelegate(int row = 0, int column = 0, QObject *parent = nullptr);
|
FolioPageDelegate(int row = 0, int column = 0, QObject *parent = nullptr);
|
||||||
FolioPageDelegate(int row, int column, FolioApplication *application, QObject *parent);
|
FolioPageDelegate(int row, int column, FolioApplication *application, QObject *parent);
|
||||||
FolioPageDelegate(int row, int column, FolioApplicationFolder *folder, 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);
|
FolioPageDelegate(int row, int column, FolioDelegate *delegate, QObject *parent);
|
||||||
|
|
||||||
static FolioPageDelegate *fromJson(QJsonObject &obj, 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 getTranslatedRow(int realRow, int realColumn);
|
||||||
static int getTranslatedColumn(int realRow, int realColumn);
|
static int getTranslatedColumn(int realRow, int realColumn);
|
||||||
|
|
||||||
|
|
@ -42,6 +45,8 @@ Q_SIGNALS:
|
||||||
void columnChanged();
|
void columnChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setRowOnly(int row);
|
||||||
|
void setColumnOnly(int column);
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
int m_realRow;
|
int m_realRow;
|
||||||
|
|
@ -73,9 +78,13 @@ public:
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_INVOKABLE void removeDelegate(int row, int col);
|
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);
|
bool addDelegate(FolioPageDelegate *delegate);
|
||||||
FolioPageDelegate *getDelegate(int row, int col);
|
FolioPageDelegate *getDelegate(int row, int col);
|
||||||
|
|
||||||
|
Q_INVOKABLE void moveAndResizeWidgetDelegate(FolioPageDelegate *delegate, int newRow, int newColumn, int newGridWidth, int newGridHeight);
|
||||||
|
|
||||||
bool isPageEmpty();
|
bool isPageEmpty();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
|
|
@ -85,5 +94,6 @@ Q_SIGNALS:
|
||||||
void saveRequested();
|
void saveRequested();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void connectSaveRequests(FolioDelegate *delegate);
|
||||||
QList<FolioPageDelegate *> m_delegates;
|
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