diff --git a/src/effects/desktopgrid/qml/main.qml b/src/effects/desktopgrid/qml/main.qml index bca7a782f9..e9f2afd613 100644 --- a/src/effects/desktopgrid/qml/main.qml +++ b/src/effects/desktopgrid/qml/main.qml @@ -6,6 +6,7 @@ */ import QtQuick 2.12 +import QtQuick.Window 2.12 import QtQuick.Layouts 1.12 import QtGraphicalEffects 1.12 import org.kde.kwin 3.0 as KWinComponents @@ -100,12 +101,36 @@ Rectangle { switchTo(desktopId); } else if (event.key == Qt.Key_Up) { event.accepted = selectNext(WindowHeap.Direction.Up); + if (!event.accepted) { + let view = effect.getView(Qt.TopEdge) + if (view) { + effect.activateView(view) + } + } } else if (event.key == Qt.Key_Down) { event.accepted = selectNext(WindowHeap.Direction.Down); + if (!event.accepted) { + let view = effect.getView(Qt.BottomEdge) + if (view) { + effect.activateView(view) + } + } } else if (event.key == Qt.Key_Left) { event.accepted = selectNext(WindowHeap.Direction.Left); + if (!event.accepted) { + let view = effect.getView(Qt.LeftEdge) + if (view) { + effect.activateView(view) + } + } } else if (event.key == Qt.Key_Right) { event.accepted = selectNext(WindowHeap.Direction.Right); + if (!event.accepted) { + let view = effect.getView(Qt.RightEdge) + if (view) { + effect.activateView(view) + } + } } else if (event.key == Qt.Key_Return || event.key == Qt.Key_Space) { for (let i = 0; i < gridRepeater.count; i++) { if (gridRepeater.itemAt(i).focus) { @@ -229,6 +254,15 @@ Rectangle { height: container.height clientModel: stackModel + Rectangle { + anchors.fill: parent + color: "transparent" + border { + color: PlasmaCore.Theme.highlightColor + width: 1 / grid.scale + } + visible: parent.activeFocus + } TapHandler { acceptedButtons: Qt.LeftButton onTapped: { diff --git a/src/effects/overview/qml/ScreenView.qml b/src/effects/overview/qml/ScreenView.qml index 15044f8b75..56a0f9d19d 100644 --- a/src/effects/overview/qml/ScreenView.qml +++ b/src/effects/overview/qml/ScreenView.qml @@ -42,6 +42,7 @@ FocusScope { Keys.priority: Keys.AfterItem Keys.forwardTo: searchField + Keys.onEnterPressed: { heap.forceActiveFocus(); if (heap.count === 1) { @@ -49,6 +50,31 @@ FocusScope { } } + Keys.onLeftPressed: { + let view = effect.getView(Qt.LeftEdge) + if (view) { + effect.activateView(view) + } + } + Keys.onRightPressed: { + let view = effect.getView(Qt.RightEdge) + if (view) { + effect.activateView(view) + } + } + Keys.onUpPressed: { + let view = effect.getView(Qt.TopEdge) + if (view) { + effect.activateView(view) + } + } + Keys.onDownPressed: { + let view = effect.getView(Qt.BottomEdge) + if (view) { + effect.activateView(view) + } + } + KWinComponents.DesktopBackgroundItem { id: backgroundItem activity: KWinComponents.Workspace.currentActivity diff --git a/src/effects/private/qml/WindowHeapDelegate.qml b/src/effects/private/qml/WindowHeapDelegate.qml index 31efcaeac5..cbcb970886 100644 --- a/src/effects/private/qml/WindowHeapDelegate.qml +++ b/src/effects/private/qml/WindowHeapDelegate.qml @@ -290,7 +290,7 @@ Item { imagePath: "widgets/viewitem" prefix: "hover" z: -1 - visible: !thumb.windowHeap.dragActive && (hoverHandler.hovered || selected) + visible: !thumb.windowHeap.dragActive && (hoverHandler.hovered || selected) && Window.window.activeFocusItem } HoverHandler { diff --git a/src/effects/windowview/qml/main.qml b/src/effects/windowview/qml/main.qml index 5b926f0b5a..b7ed45ec04 100644 --- a/src/effects/windowview/qml/main.qml +++ b/src/effects/windowview/qml/main.qml @@ -38,6 +38,30 @@ Item { Keys.priority: Keys.AfterItem Keys.forwardTo: searchField + Keys.onLeftPressed: { + let view = effect.getView(Qt.LeftEdge) + if (view) { + effect.activateView(view) + } + } + Keys.onRightPressed: { + let view = effect.getView(Qt.RightEdge) + if (view) { + effect.activateView(view) + } + } + Keys.onUpPressed: { + let view = effect.getView(Qt.TopEdge) + if (view) { + effect.activateView(view) + } + } + Keys.onDownPressed: { + let view = effect.getView(Qt.BottomEdge) + if (view) { + effect.activateView(view) + } + } KWinComponents.DesktopBackgroundItem { activity: KWinComponents.Workspace.currentActivity diff --git a/src/libkwineffects/kwinquickeffect.cpp b/src/libkwineffects/kwinquickeffect.cpp index 99bf35f852..990509da11 100644 --- a/src/libkwineffects/kwinquickeffect.cpp +++ b/src/libkwineffects/kwinquickeffect.cpp @@ -220,17 +220,89 @@ QuickSceneView *QuickSceneEffect::viewAt(const QPoint &pos) const return nullptr; } +QuickSceneView *QuickSceneEffect::activeView() const +{ + auto it = std::find_if(d->views.constBegin(), d->views.constEnd(), [](QuickSceneView *v) { + return v->window()->activeFocusItem(); + }); + + QuickSceneView *screenView = nullptr; + + if (it == d->views.constEnd()) { + screenView = d->views.value(effects->activeScreen()); + } else { + screenView = (*it); + } + + return screenView; +} + +KWin::QuickSceneView *QuickSceneEffect::getView(Qt::Edge edge) +{ + auto screenView = activeView(); + + QuickSceneView *candidate = nullptr; + + for (auto *v : d->views) { + switch (edge) { + case Qt::LeftEdge: + if (v->geometry().left() < screenView->geometry().left()) { + // Look for the nearest view from the current + if (!candidate || v->geometry().left() > candidate->geometry().left() || (v->geometry().left() == candidate->geometry().left() && v->geometry().top() > candidate->geometry().top())) { + candidate = v; + } + } + break; + case Qt::TopEdge: + if (v->geometry().top() < screenView->geometry().top()) { + if (!candidate || v->geometry().top() > candidate->geometry().top() || (v->geometry().top() == candidate->geometry().top() && v->geometry().left() > candidate->geometry().left())) { + candidate = v; + } + } + break; + case Qt::RightEdge: + if (v->geometry().right() > screenView->geometry().right()) { + if (!candidate || v->geometry().right() < candidate->geometry().right() || (v->geometry().right() == candidate->geometry().right() && v->geometry().top() > candidate->geometry().top())) { + candidate = v; + } + } + break; + case Qt::BottomEdge: + if (v->geometry().bottom() > screenView->geometry().bottom()) { + if (!candidate || v->geometry().bottom() < candidate->geometry().bottom() || (v->geometry().bottom() == candidate->geometry().bottom() && v->geometry().left() > candidate->geometry().left())) { + candidate = v; + } + } + break; + } + } + + return candidate; +} + void QuickSceneEffect::activateView(QuickSceneView *view) { + if (!view) { + return; + } + + auto *av = activeView(); + // Already properly active? + if (view == av && av->window()->activeFocusItem()) { + return; + } + for (auto *otherView : d->views) { if (otherView == view && !view->window()->activeFocusItem()) { QFocusEvent focusEvent(QEvent::FocusIn, Qt::ActiveWindowFocusReason); qApp->sendEvent(view->window(), &focusEvent); - } else if (otherView->window()->activeFocusItem()) { + } else if (otherView != view && otherView->window()->activeFocusItem()) { QFocusEvent focusEvent(QEvent::FocusOut, Qt::ActiveWindowFocusReason); qApp->sendEvent(otherView->window(), &focusEvent); } } + + Q_EMIT activeViewChanged(view); } // Screen views are repainted just before kwin performs its compositing cycle to avoid stalling for vblank @@ -362,6 +434,9 @@ void QuickSceneEffect::startInternal() addScreen(screen); } + // Ensure one view has an active focus item + activateView(activeView()); + connect(effects, &EffectsHandler::screenAdded, this, &QuickSceneEffect::handleScreenAdded); connect(effects, &EffectsHandler::screenRemoved, this, &QuickSceneEffect::handleScreenRemoved); @@ -424,19 +499,12 @@ void QuickSceneEffect::windowInputMouseEvent(QEvent *event) void QuickSceneEffect::grabbedKeyboardEvent(QKeyEvent *keyEvent) { - auto it = std::find_if(d->views.constBegin(), d->views.constEnd(), [](QuickSceneView *v) { - return v->window()->activeFocusItem(); - }); + auto *screenView = activeView(); - if (it == d->views.constEnd()) { - QuickSceneView *screenView = d->views.value(effects->activeScreen()); - if (screenView) { - activateView(screenView); - screenView->forwardKeyEvent(keyEvent); - } - } else { - (*it)->forwardKeyEvent(keyEvent); - return; + if (screenView) { + // ActiveView may not have an activeFocusItem yet + activateView(screenView); + screenView->forwardKeyEvent(keyEvent); } } @@ -472,3 +540,5 @@ bool QuickSceneEffect::touchUp(qint32 id, quint32 time) } } // namespace KWin + +#include diff --git a/src/libkwineffects/kwinquickeffect.h b/src/libkwineffects/kwinquickeffect.h index 7e2de551ab..f5b07cf633 100644 --- a/src/libkwineffects/kwinquickeffect.h +++ b/src/libkwineffects/kwinquickeffect.h @@ -66,6 +66,7 @@ private: class KWINEFFECTS_EXPORT QuickSceneEffect : public Effect { Q_OBJECT + Q_PROPERTY(QuickSceneView *activeView READ activeView NOTIFY activeViewChanged) public: explicit QuickSceneEffect(QObject *parent = nullptr); @@ -81,6 +82,8 @@ public: */ void setRunning(bool running); + QuickSceneView *activeView() const; + /** * Returns all scene views managed by this effect. If the effect is not running, * this function returns an empty QHash. @@ -92,10 +95,16 @@ public: */ QuickSceneView *viewAt(const QPoint &pos) const; + /** + * Get a view at the given direction from the active view + * Returns null if no other views exist in the given direction + */ + Q_INVOKABLE KWin::QuickSceneView *getView(Qt::Edge edge); + /** * Sets the given @a view as active. It will get a focusin event and all the other views will be set as inactive */ - void activateView(QuickSceneView *view); + Q_INVOKABLE void activateView(QuickSceneView *view); /** * Returns the source URL. @@ -134,6 +143,7 @@ public: Q_SIGNALS: void itemDraggedOutOfScreen(QQuickItem *item, QList screens); void itemDroppedOutOfScreen(const QPointF &globalPos, QQuickItem *item, EffectScreen *screen); + void activeViewChanged(KWin::QuickSceneView *view); protected: /** diff --git a/src/scripting/CMakeLists.txt b/src/scripting/CMakeLists.txt index 88ba5b644d..4c0ee71da4 100644 --- a/src/scripting/CMakeLists.txt +++ b/src/scripting/CMakeLists.txt @@ -10,6 +10,7 @@ if (KWIN_BUILD_KCMS) KF5::Service Qt::DBus Qt::UiTools + kwineffects ) install(TARGETS kcm_kwin4_genericscripted DESTINATION ${KDE_INSTALL_PLUGINDIR}/kwin/effects/configs) endif() diff --git a/src/scripting/scripting.cpp b/src/scripting/scripting.cpp index a990c9aac4..200cbf9000 100644 --- a/src/scripting/scripting.cpp +++ b/src/scripting/scripting.cpp @@ -13,6 +13,7 @@ // own #include "dbuscall.h" #include "desktopbackgrounditem.h" +#include "kwinquickeffect.h" #include "screenedgeitem.h" #include "scripting_logging.h" #include "scriptingutils.h" @@ -658,6 +659,7 @@ void KWin::Scripting::init() qmlRegisterType("org.kde.kwin", 3, 0, "ClientModel"); qmlRegisterType("org.kde.kwin", 3, 0, "ClientFilterModel"); qmlRegisterType("org.kde.kwin", 3, 0, "VirtualDesktopModel"); + qmlRegisterUncreatableType("org.kde.kwin", 3, 0, "SceneView", QStringLiteral("Can't instantiate an object of type SceneView")); qmlRegisterSingletonType("org.kde.kwin", 3, 0, "Workspace", [](QQmlEngine *qmlEngine, QJSEngine *jsEngine) { Q_UNUSED(qmlEngine)