When an arrow key is not accepted look for adjacent views

When no qml items manage the arrow key event, the root item will manage it looking to give focus to a view in the given direction derived from the arrow key

BUG:455783
This commit is contained in:
Marco Martin 2022-08-02 15:59:26 +00:00
parent 369194ab72
commit 027ca22908
8 changed files with 182 additions and 15 deletions

View file

@ -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: {

View file

@ -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

View file

@ -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 {

View file

@ -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

View file

@ -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 <moc_kwinquickeffect.cpp>

View file

@ -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<EffectScreen *> screens);
void itemDroppedOutOfScreen(const QPointF &globalPos, QQuickItem *item, EffectScreen *screen);
void activeViewChanged(KWin::QuickSceneView *view);
protected:
/**

View file

@ -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()

View file

@ -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<ScriptingModels::V3::ClientModel>("org.kde.kwin", 3, 0, "ClientModel");
qmlRegisterType<ScriptingModels::V3::ClientFilterModel>("org.kde.kwin", 3, 0, "ClientFilterModel");
qmlRegisterType<ScriptingModels::V3::VirtualDesktopModel>("org.kde.kwin", 3, 0, "VirtualDesktopModel");
qmlRegisterUncreatableType<KWin::QuickSceneView>("org.kde.kwin", 3, 0, "SceneView", QStringLiteral("Can't instantiate an object of type SceneView"));
qmlRegisterSingletonType<DeclarativeScriptWorkspaceWrapper>("org.kde.kwin", 3, 0, "Workspace", [](QQmlEngine *qmlEngine, QJSEngine *jsEngine) {
Q_UNUSED(qmlEngine)