give the keyboard back when a modal closes
Hiding the layer takes the focus off whatever stood on it and leaves the window with no focus widget at all, so the world view answered no keys afterwards: Escape opened the menu once, closed it, and then did nothing. Each modal now notes what held the keyboard when it opened and gives it back on the way out -- the world view for the first of a stack, the modal beneath for one above it, which was dead the same way. The window's own focus guard now also catches the focus going nowhere, rather than only landing on the wrong widget. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -107,8 +107,11 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir,
|
|||||||
|
|
||||||
connect(qApp, &QApplication::focusChanged, this, [this](QWidget*, QWidget* newWidget) {
|
connect(qApp, &QApplication::focusChanged, this, [this](QWidget*, QWidget* newWidget) {
|
||||||
// A modal holds the focus while it is open, whether it is one of ours on the
|
// A modal holds the focus while it is open, whether it is one of ours on the
|
||||||
// layer or a system message box (REQ-UI-MODAL-CHROME).
|
// layer or a system message box (REQ-UI-MODAL-CHROME). A null widget -- the focus
|
||||||
if (newWidget && newWidget != m_gameWorldView && !QApplication::activeModalWidget()
|
// going nowhere at all -- is caught too: the world view is what answers keys when
|
||||||
|
// no modal is up, so leaving the window with no focus widget would leave the
|
||||||
|
// hotkeys dead (REQ-UI-HOTKEYS).
|
||||||
|
if (newWidget != m_gameWorldView && !QApplication::activeModalWidget()
|
||||||
&& !m_modalLayer->isActive())
|
&& !m_modalLayer->isActive())
|
||||||
{
|
{
|
||||||
m_gameWorldView->setFocus();
|
m_gameWorldView->setFocus();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
@@ -40,6 +41,11 @@ int ModalLayer::execute(ModalDialog& content, const QRect& anchorRect)
|
|||||||
// instead of being cut off by the window edge (REQ-UI-MODAL-CHROME). When the
|
// instead of being cut off by the window edge (REQ-UI-MODAL-CHROME). When the
|
||||||
// content fits -- which is the normal case -- the host is exactly its size and no
|
// content fits -- which is the normal case -- the host is exactly its size and no
|
||||||
// scroll bar appears, so the player sees the modal alone.
|
// scroll bar appears, so the player sees the modal alone.
|
||||||
|
// Noted before anything here touches the focus, and given back when this modal
|
||||||
|
// closes: the game world beneath for the first modal of a stack, and the modal it was
|
||||||
|
// opened from for one above that.
|
||||||
|
QWidget* const focusBefore = QApplication::focusWidget();
|
||||||
|
|
||||||
QScrollArea* host = new QScrollArea(this);
|
QScrollArea* host = new QScrollArea(this);
|
||||||
host->setFrameShape(QFrame::NoFrame);
|
host->setFrameShape(QFrame::NoFrame);
|
||||||
host->setWidgetResizable(false);
|
host->setWidgetResizable(false);
|
||||||
@@ -52,7 +58,7 @@ int ModalLayer::execute(ModalDialog& content, const QRect& anchorRect)
|
|||||||
: (m_stack.empty() ? rect()
|
: (m_stack.empty() ? rect()
|
||||||
: m_stack.back().host->geometry());
|
: m_stack.back().host->geometry());
|
||||||
|
|
||||||
m_stack.push_back(HostedModal{ &content, host });
|
m_stack.push_back(HostedModal{ &content, host, focusBefore });
|
||||||
|
|
||||||
// Placed and shown before the layer itself is, so that the frame the layer first
|
// Placed and shown before the layer itself is, so that the frame the layer first
|
||||||
// paints already carries the modal. Showing the layer first put the dim on screen a
|
// paints already carries the modal. Showing the layer first put the dim on screen a
|
||||||
@@ -90,15 +96,24 @@ int ModalLayer::execute(ModalDialog& content, const QRect& anchorRect)
|
|||||||
content.hide();
|
content.hide();
|
||||||
delete host;
|
delete host;
|
||||||
|
|
||||||
|
QPointer<QWidget> focusAfter;
|
||||||
for (std::size_t i = m_stack.size(); i > 0; --i)
|
for (std::size_t i = m_stack.size(); i > 0; --i)
|
||||||
{
|
{
|
||||||
if (m_stack[i - 1].content == &content)
|
if (m_stack[i - 1].content == &content)
|
||||||
{
|
{
|
||||||
|
focusAfter = m_stack[i - 1].focusBefore;
|
||||||
m_stack.erase(m_stack.begin() + static_cast<std::ptrdiff_t>(i - 1));
|
m_stack.erase(m_stack.begin() + static_cast<std::ptrdiff_t>(i - 1));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateVisibility();
|
updateVisibility();
|
||||||
|
|
||||||
|
// After the layer is hidden, not before: hiding a widget takes the focus off
|
||||||
|
// everything on it, which would undo this again.
|
||||||
|
if (!focusAfter.isNull())
|
||||||
|
{
|
||||||
|
focusAfter->setFocus();
|
||||||
|
}
|
||||||
return content.result();
|
return content.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
|
#include <QPointer>
|
||||||
#include <QRect>
|
#include <QRect>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
@@ -65,6 +66,12 @@ private:
|
|||||||
{
|
{
|
||||||
ModalDialog* content;
|
ModalDialog* content;
|
||||||
QScrollArea* host;
|
QScrollArea* host;
|
||||||
|
// What held the keyboard when this modal opened, given back when it closes.
|
||||||
|
// Hiding the layer, or closing a modal a second one was opened from, leaves the
|
||||||
|
// window with no focus widget at all, and a window with none answers no keys --
|
||||||
|
// Escape included, which is what opens the menu (REQ-UI-GAME-MENU). A QPointer
|
||||||
|
// because a modal may outlive what it took the focus from.
|
||||||
|
QPointer<QWidget> focusBefore;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sizes content to what it asks for, capped at the layer, and centers it on
|
// Sizes content to what it asks for, capped at the layer, and centers it on
|
||||||
|
|||||||
Reference in New Issue
Block a user