diff --git a/apps/readest-sync/qml/DecisionDialog.qml b/apps/readest-sync/qml/DecisionDialog.qml index 041108b..6dcb871 100644 --- a/apps/readest-sync/qml/DecisionDialog.qml +++ b/apps/readest-sync/qml/DecisionDialog.qml @@ -1,13 +1,12 @@ import QtQuick -Item { +ModalSurface { id: root objectName: "nativeDecisionDialog" required property var shell z: 60 readonly property var choices: shell.view.actionPresentation ? shell.view.actionPresentation.choices : [] - Rectangle { anchors.fill: parent; color: "#dddddd"; opacity: 0.92 } Rectangle { anchors.centerIn: parent width: Math.min(parent.width - 2 * shell.metrics.largeGap, 1200 * shell.metrics.scale) @@ -33,6 +32,7 @@ Item { model: root.choices delegate: Rectangle { required property var modelData + objectName: "nativeDecisionChoice_" + modelData.command width: dialogColumn.width height: shell.metrics.primaryAction color: choiceTap.pressed ? "black" : "white" @@ -50,6 +50,7 @@ Item { } } Rectangle { + objectName: "nativeDecisionDismiss" width: parent.width height: shell.metrics.primaryAction color: finalTap.pressed ? "black" : "white" diff --git a/apps/readest-sync/qml/ModalSurface.qml b/apps/readest-sync/qml/ModalSurface.qml new file mode 100644 index 0000000..6dd22ce --- /dev/null +++ b/apps/readest-sync/qml/ModalSurface.qml @@ -0,0 +1,12 @@ +import QtQuick + +Item { + Rectangle { anchors.fill: parent; z: -2; color: "#dddddd"; opacity: 0.92 } + // Content remains above the input shield; background taps never reach pages. + MouseArea { + anchors.fill: parent + z: -1 + acceptedButtons: Qt.AllButtons + onWheel: function(wheel) { wheel.accepted = true } + } +} diff --git a/apps/readest-sync/qml/OperationSurface.qml b/apps/readest-sync/qml/OperationSurface.qml index 042e355..db65f8f 100644 --- a/apps/readest-sync/qml/OperationSurface.qml +++ b/apps/readest-sync/qml/OperationSurface.qml @@ -1,11 +1,10 @@ import QtQuick -Item { +ModalSurface { id: root objectName: "nativeOperationSurface" required property var shell z: 50 - Rectangle { anchors.fill: parent; color: "#dddddd"; opacity: 0.92 } Rectangle { anchors.centerIn: parent width: Math.min(parent.width - 2 * shell.metrics.largeGap, 1200 * shell.metrics.scale) @@ -27,6 +26,7 @@ Item { wrapMode: Text.Wrap } Rectangle { + objectName: "nativeOperationCancel" width: parent.width height: shell.metrics.primaryAction color: cancelTap.pressed ? "black" : "white" diff --git a/apps/readest-sync/qml/readest.qrc b/apps/readest-sync/qml/readest.qrc index a316072..25a1d83 100644 --- a/apps/readest-sync/qml/readest.qrc +++ b/apps/readest-sync/qml/readest.qrc @@ -1 +1 @@ -Main.qmlNativeMetrics.qmlNativeHeader.qmlMessageBand.qmlOperationSurface.qmlDecisionDialog.qmlBusyPage.qmlErrorPage.qmlSignInPage.qmlLibraryPage.qmlBookDetailsPage.qmlActionButton.qmlBodyText.qml +Main.qmlNativeMetrics.qmlNativeHeader.qmlMessageBand.qmlModalSurface.qmlOperationSurface.qmlDecisionDialog.qmlBusyPage.qmlErrorPage.qmlSignInPage.qmlLibraryPage.qmlBookDetailsPage.qmlActionButton.qmlBodyText.qml diff --git a/apps/readest-sync/tests/qt_ui_test.cpp b/apps/readest-sync/tests/qt_ui_test.cpp index 4aa9e32..c8a4d35 100644 --- a/apps/readest-sync/tests/qt_ui_test.cpp +++ b/apps/readest-sync/tests/qt_ui_test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -43,14 +44,18 @@ static QQuickItem* find_item(QQuickItem* item,const QString& name) { for(auto* child:item->childItems()) if(auto* found=find_item(child,name)) return found; return nullptr; } -static void tap_book(QQuickWindow* window) { - auto* row=find_item(window->contentItem(),"detailedBookRow"); assert(row); - const auto point=row->mapToScene(QPointF(row->width()/2,row->height()/2)); +static void tap_item(QQuickWindow* window,QQuickItem* item,int hold=0) { + assert(item); + const auto point=item->mapToScene(QPointF(item->width()/2,item->height()/2)); QMouseEvent press(QEvent::MouseButtonPress,point,point,Qt::LeftButton,Qt::LeftButton,Qt::NoModifier); QCoreApplication::sendEvent(window,&press); + if(hold) settle(hold); QMouseEvent release(QEvent::MouseButtonRelease,point,point,Qt::LeftButton,Qt::NoButton,Qt::NoModifier); QCoreApplication::sendEvent(window,&release); settle(); } +static void tap_book(QQuickWindow* window,int hold=0) { + tap_item(window,find_item(window->contentItem(),"detailedBookRow"),hold); +} static ApplicationConfig configAt(const QString& base) { ApplicationConfig config; config.root=(base+"/system/readest-sync").toStdString(); @@ -552,6 +557,8 @@ int main(int argc,char** argv) { QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Menu))); settle(); assert(!libraryPage->property("menuOpen").toBool()); hold_foreground=true; control.refreshLibrary(); settle(); assert(control.busy()); + tap_book(window,QGuiApplication::styleHints()->mousePressAndHoldInterval()+100); + assert(!libraryPage->property("contextOpen").toBool()); tap_book(window); assert(!control.detail()); QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Menu))); settle(); assert(!libraryPage->property("menuOpen").toBool()); @@ -570,6 +577,12 @@ int main(int argc,char** argv) { auto view=menuView; if(!guard.isEmpty()) view[guard]=true; window->setProperty("view",view); settle(); + if(!guard.isEmpty()) { + tap_book(window,QGuiApplication::styleHints()->mousePressAndHoldInterval()+100); + assert(!libraryPage->property("contextOpen").toBool()); + tap_book(window); + assert(!libraryPage->property("activating").toBool()); + } for(bool hardware:{false,true}) { libraryPage->setProperty("menuOpen",false); if(hardware) QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Menu))); @@ -578,6 +591,33 @@ int main(int argc,char** argv) { } } window->setProperty("view",QVariant::fromValue(&control)); settle(); + // Dialog controls must still receive real taps above the shared shield. + for(const auto& mode:{QString("busy"),QString("decision"),QString("blocking")}) { + auto view=engine.toScriptValue(menuView); + view.setProperty(mode,true); + view.setProperty("lastCommand",QString()); + view.setProperty("close",engine.evaluate("(function() { this.lastCommand = 'close'; })")); + view.setProperty("cancelDecision",engine.evaluate("(function() { this.lastCommand = 'cancel'; })")); + view.setProperty("acknowledgeBlocking",engine.evaluate("(function() { this.lastCommand = 'acknowledge'; })")); + view.setProperty("runAction",engine.evaluate("(function(command) { this.lastCommand = command; })")); + view.setProperty("actionPresentation",engine.toScriptValue(QVariantMap{{"choices",QVariantList{ + QVariantMap{{"command","usePocketBook"},{"text","Use PocketBook position"}}, + QVariantMap{{"command","useReadest"},{"text","Use Readest position"}}}}})); + window->setProperty("view",QVariant::fromValue(view)); settle(); + if(mode=="decision") { + for(const auto& command:{QString("usePocketBook"),QString("useReadest")}) { + tap_item(window,find_item(window->contentItem(),"nativeDecisionChoice_"+command)); + assert(view.property("lastCommand").toString()==command); + } + } + tap_item(window,find_item(window->contentItem(),mode=="busy"?"nativeOperationCancel":"nativeDecisionDismiss")); + assert(view.property("lastCommand").toString()==(mode=="busy"?"close":mode=="decision"?"cancel":"acknowledge")); + } + window->setProperty("view",QVariant::fromValue(&control)); settle(); + tap_book(window,QGuiApplication::styleHints()->mousePressAndHoldInterval()+100); + assert(libraryPage->property("contextOpen").toBool()); + QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Back))); settle(); + assert(!libraryPage->property("contextOpen").toBool()); libraryPage->setProperty("menuOpen",true); settle(); assert(window->findChild("nativeLibraryMenu")->isVisible()); QMetaObject::invokeMethod(window,"handleHardwareButton",Q_ARG(QVariant,QVariant(Qt::Key_Back))); settle();