Commit 027ca229 authored by Marco Martin's avatar Marco Martin
Browse files

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
parent 369194ab
Pipeline #211985 passed with stage
in 32 minutes
......@@ -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: {
......
......@@ -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
......
......@@ -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 {
......
......@@ -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
......
......@@ -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>
......@@ -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:
/**
......
......@@ -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()
......@@ -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)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment