Improving toggle visibility and loading behavior

This commit is contained in:
Brad Davis 2015-04-17 16:58:29 -07:00
parent 4b73481604
commit 16efa2a46f
12 changed files with 231 additions and 98 deletions

View file

@ -5,16 +5,21 @@ import QtQuick.Window 2.2
import QtQuick.Controls.Styles 1.3
CustomDialog {
id: dialog
title: "Go to..."
objectName: "AddressBarDialog"
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
height: 128
width: 512
destroyOnCloseButton: false
onVisibleChanged: {
if (!visible) {
reset();
}
}
onEnabledChanged: {
if (enabled) {
addressLine.forceActiveFocus();
}
}

View file

@ -12,7 +12,9 @@ Item {
height: 256
scale: 0.0
enabled: false
visible: false
property int animationDuration: 400
property bool destroyOnInvisible: false
property bool destroyOnCloseButton: true
onEnabledChanged: {
scale = enabled ? 1.0 : 0.0
@ -22,14 +24,24 @@ Item {
visible = (scale != 0.0);
}
Component.onCompleted: {
scale = 1.0
onVisibleChanged: {
if (!visible && destroyOnInvisible) {
console.log("Destroying closed component");
destroy();
}
}
function close() {
if (destroyOnCloseButton) {
destroyOnInvisible = true
}
enabled = false;
}
Behavior on scale {
NumberAnimation {
//This specifies how long the animation takes
duration: 400
duration: dialog.animationDuration
//This selects an easing curve to interpolate with, the default is Easing.Linear
easing.type: Easing.InOutBounce
}
@ -81,8 +93,8 @@ Item {
target: dialog
minimumX: 0
minimumY: 0
maximumX: dialog.parent.width - dialog.width
maximumY: dialog.parent.height - dialog.height
maximumX: dialog.parent ? dialog.parent.width - dialog.width : 0
maximumY: dialog.parent ? dialog.parent.height - dialog.height : 0
}
}
Image {
@ -97,7 +109,7 @@ Item {
MouseArea {
anchors.fill: parent
onClicked: {
dialog.destroy()
dialog.close();
}
}
}

View file

@ -18,6 +18,12 @@ CustomDialog {
}
}
onEnabledChanged: {
if (enabled) {
username.forceActiveFocus();
}
}
function reset() {
username.text = ""
password.text = ""

View file

@ -1,14 +1,9 @@
import Hifi 1.0
import QtQuick 2.3
import "componentCreation.js" as Creator
Item {
Root {
id: root
width: 1280
height: 720
function loadChild(url) {
Creator.createObject(root, url)
}
}

View file

@ -5,12 +5,19 @@ import QtQuick.Dialogs 1.2
import QtQuick.Controls.Styles 1.3
CustomDialog {
title: "Test Dlg"
title: "Test Dialog"
id: testDialog
objectName: "TestDialog"
width: 512
height: 512
animationDuration: 200
onEnabledChanged: {
if (enabled) {
edit.forceActiveFocus();
}
}
Item {
id: clientArea
// The client area
@ -31,6 +38,7 @@ CustomDialog {
CustomTextEdit {
id: edit
anchors.left: parent.left
anchors.leftMargin: 12
anchors.right: parent.right

View file

@ -1,25 +1,34 @@
import Hifi 1.0
import QtQuick 2.3
import "componentCreation.js" as Creator
Item {
Root {
id: root
width: 1280
height: 720
function loadChild(url) {
Creator.createObject(root, url)
CustomButton {
id: messageBox
anchors.right: createDialog.left
anchors.rightMargin: 24
anchors.bottom: parent.bottom
anchors.bottomMargin: 24
text: "Message"
onClicked: {
console.log("Foo")
root.information("a")
console.log("Bar")
}
}
CustomButton {
id: createDialog
anchors.right: parent.right
anchors.rightMargin: 24
anchors.bottom: parent.bottom
anchors.bottomMargin: 24
text: "Test"
text: "Create"
onClicked: {
loadChild("TestDialog.qml");
root.loadChild("TestDialog.qml");
}
}
}

View file

@ -18,10 +18,12 @@ function finishCreation() {
// Error Handling
console.log("Error creating object");
} else {
instance.focus = true;
instance.enabled = true
}
} else if (component.status == Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
} else {
console.log("Unknown component status: " + component.status);
}
}

View file

@ -754,8 +754,8 @@ void Application::initializeUi() {
offscreenUi->create(_glWidget->context()->contextHandle());
offscreenUi->resize(_glWidget->size());
offscreenUi->setProxyWindow(_window->windowHandle());
auto rootQml = PathUtils::resourcesPath() + "qml/Root.qml";
offscreenUi->loadQml(QUrl::fromLocalFile(rootQml));
offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
offscreenUi->load("Root.qml");
offscreenUi->setMouseTranslator([this](const QPointF& p){
if (OculusManager::isConnected()) {
glm::vec2 pos = _applicationOverlay.screenToOverlay(toGlm(p));

View file

@ -35,14 +35,13 @@ void AddressBarDialog::loadAddress(const QString & address) {
}
}
// TODO port to a QML based message box
void AddressBarDialog::displayAddressOfflineMessage() {
QMessageBox::information(nullptr, "Address offline",
OffscreenUi::information("Address offline",
"That user or place is currently offline.");
}
// TODO port to a QML based message box
void AddressBarDialog::displayAddressNotFoundMessage() {
QMessageBox::information(nullptr, "Address not found",
OffscreenUi::information("Address not found",
"There is no address information for that user or place.");
}

View file

@ -12,8 +12,29 @@
#include <QOpenGLFramebufferObject>
#include <QOpenGLDebugLogger>
#include <QGLWidget>
#include <QtQml>
class OffscreenUiRoot : public QQuickItem {
Q_OBJECT
public:
OffscreenUiRoot(QQuickItem *parent = 0);
Q_INVOKABLE void information(const QString & title, const QString & text);
Q_INVOKABLE void loadChild(const QUrl & url) {
DependencyManager::get<OffscreenUi>()->load(url);
}
};
OffscreenUiRoot::OffscreenUiRoot(QQuickItem *parent) : QQuickItem(parent) {
}
void OffscreenUiRoot::information(const QString & title, const QString & text = "foo") {
OffscreenUi::information(title, text);
}
OffscreenUi::OffscreenUi() {
::qmlRegisterType<OffscreenUiRoot>("Hifi", 1, 0, "Root");
}
OffscreenUi::~OffscreenUi() {
@ -62,19 +83,18 @@ void OffscreenUi::create(QOpenGLContext * shareContext) {
// is needed too).
connect(_renderControl, &QQuickRenderControl::renderRequested, this, &OffscreenUi::requestRender);
connect(_renderControl, &QQuickRenderControl::sceneChanged, this, &OffscreenUi::requestUpdate);
connect(_quickWindow, &QQuickWindow::focusObjectChanged, this, [this](QObject *object){
OffscreenUi * p = this;
qDebug() << "Focus changed to " << object;
});
_quickWindow->focusObject();
_qmlComponent = new QQmlComponent(_qmlEngine);
// Initialize the render control and our OpenGL resources.
makeCurrent();
_renderControl->initialize(&_context);
}
void OffscreenUi::addImportPath(const QString & path) {
_qmlEngine->addImportPath(path);
}
void OffscreenUi::resize(const QSize & newSize) {
makeCurrent();
@ -102,10 +122,15 @@ QQmlContext * OffscreenUi::qmlContext() {
return QQmlEngine::contextForObject(_rootItem);
}
void OffscreenUi::loadQml(const QUrl & qmlSource, std::function<void(QQmlContext*)> f) {
void OffscreenUi::setBaseUrl(const QUrl & baseUrl) {
_qmlEngine->setBaseUrl(baseUrl);
}
void OffscreenUi::load(const QUrl & qmlSource, std::function<void(QQmlContext*)> f) {
qDebug() << "Loading QML from URL " << qmlSource;
_qmlComponent->loadUrl(qmlSource);
if (_qmlComponent->isLoading())
connect(_qmlComponent, &QQmlComponent::statusChanged, this, &OffscreenUi::finishQmlLoad);
connect(_qmlComponent, &QQmlComponent::statusChanged, this, [] {});
else
finishQmlLoad();
}
@ -131,30 +156,43 @@ void OffscreenUi::finishQmlLoad() {
return;
}
QObject *rootObject = _qmlComponent->create();
QObject *newObject = _qmlComponent->create();
if (_qmlComponent->isError()) {
QList<QQmlError> errorList = _qmlComponent->errors();
foreach(const QQmlError &error, errorList)
qWarning() << error.url() << error.line() << error;
qFatal("Unable to finish loading QML");
if (!_rootItem) {
qFatal("Unable to finish loading QML root");
}
return;
}
_rootItem = qobject_cast<QQuickItem *>(rootObject);
if (!_rootItem) {
QQuickItem * newItem = qobject_cast<QQuickItem *>(newObject);
if (!newItem) {
qWarning("run: Not a QQuickItem");
delete rootObject;
qFatal("Unable to find root QQuickItem");
delete newObject;
if (!_rootItem) {
qFatal("Unable to find root QQuickItem");
}
return;
}
// Make sure we can assign focus to the root item (critical for
// Make sure we make items focusable (critical for
// supporting keyboard shortcuts)
_rootItem->setFlag(QQuickItem::ItemIsFocusScope, true);
// The root item is ready. Associate it with the window.
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
qDebug() << "Finished setting up QML provider";
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
if (!_rootItem) {
// The root item is ready. Associate it with the window.
_rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize());
} else {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newItem, QQmlEngine::JavaScriptOwnership);
newItem->setParent(_rootItem);
newItem->setParentItem(_rootItem);
newItem->setEnabled(true);
}
}
@ -240,7 +278,7 @@ bool OffscreenUi::eventFilter(QObject * dest, QEvent * e) {
case QEvent::KeyRelease:
{
e->ignore();
if (QApplication::sendEvent(_quickWindow, e)) {
if (QCoreApplication::sendEvent(_quickWindow, e)) {
return e->isAccepted();
}
}
@ -302,38 +340,55 @@ void OffscreenUi::setProxyWindow(QWindow * window) {
void OffscreenUi::show(const QUrl & url, const QString & name) {
QQuickItem * item = _rootItem->findChild<QQuickItem*>(name);
// First load?
if (nullptr == item) {
load(url);
item = _rootItem->findChild<QQuickItem*>(name);
}
if (nullptr != item) {
item->setEnabled(true);
return;
}
item->setEnabled(true);
}
void OffscreenUi::toggle(const QUrl & url, const QString & name) {
QQuickItem * item = _rootItem->findChild<QQuickItem*>(name);
// First load?
if (nullptr == item) {
show(url, name);
load(url);
return;
}
item->setEnabled(!item->isEnabled());
}
// Toggle the visibity AND the enabled flag (otherwise invisible
// dialogs can still swallow keyboard input)
bool newFlag = !item->isEnabled();
item->setEnabled(newFlag);
void OffscreenUi::messageBox(const QString &title, const QString &text,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
ButtonCallback f) {
}
void OffscreenUi::information(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::information(nullptr, title, text, buttons));
}
void OffscreenUi::question(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::question(nullptr, title, text, buttons));
}
void OffscreenUi::warning(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::warning(nullptr, title, text, buttons));
}
void OffscreenUi::critical(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons,
ButtonCallback callback) {
callback(QMessageBox::critical(nullptr, title, text, buttons));
}
void OffscreenUi::load(const QUrl & url) {
qDebug() << "Loading from url: " << url;
QVariant returnedValue;
QVariant msg = url;
QMetaObject::invokeMethod(_rootItem, "loadChild",
Q_RETURN_ARG(QVariant, returnedValue),
Q_ARG(QVariant, msg));
qDebug() << "QML function returned:" << returnedValue.toString();
}
OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {};
#include "OffscreenUi.moc"

View file

@ -12,26 +12,25 @@
#ifndef hifi_OffscreenUi_h
#define hifi_OffscreenUi_h
#include <QOpenGLContext>
#include <QOffscreenSurface>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QQuickItem>
#include <QQuickWindow>
#include <QQuickRenderControl>
#include <QQuickImageProvider>
#include <GLMHelpers.h>
#include <ThreadHelpers.h>
#include <QQueue>
#include <QTimer>
#include <QApplication>
#include <QMessageBox>
#include <atomic>
#include <functional>
#include <GLMHelpers.h>
#include <ThreadHelpers.h>
#include <DependencyManager.h>
#include "FboCache.h"
#include "OffscreenGlCanvas.h"
#include "FboCache.h"
#include <QQuickItem>
class OffscreenUi : public OffscreenGlCanvas, public Dependency {
@ -60,11 +59,14 @@ public:
virtual ~OffscreenUi();
void create(QOpenGLContext * context);
void resize(const QSize & size);
void loadQml(const QUrl & qmlSource, std::function<void(QQmlContext*)> f = [](QQmlContext*) {});
void load(const QUrl & url);
void load(const QUrl & qmlSource, std::function<void(QQmlContext*)> f = [](QQmlContext*) {});
void load(const QString & qmlSourceFile, std::function<void(QQmlContext*)> f = [](QQmlContext*) {}) {
load(QUrl(qmlSourceFile), f);
}
void show(const QUrl & url, const QString & name);
void toggle(const QUrl & url, const QString & name);
void setBaseUrl(const QUrl & baseUrl);
void addImportPath(const QString & path);
QQmlContext * qmlContext();
void pause();
@ -77,6 +79,32 @@ public:
_mouseTranslator = mt;
}
// Messagebox replacement functions
using ButtonCallback = std::function < void(QMessageBox::StandardButton) >;
static ButtonCallback NO_OP_CALLBACK;
static void messageBox(const QString &title, const QString &text,
QMessageBox::Icon icon,
QMessageBox::StandardButtons buttons,
ButtonCallback f);
static void information(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
ButtonCallback callback = NO_OP_CALLBACK);
static void question(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No),
ButtonCallback callback = [](QMessageBox::StandardButton) {});
static void warning(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
ButtonCallback callback = [](QMessageBox::StandardButton) {});
static void critical(const QString &title, const QString &text,
QMessageBox::StandardButtons buttons = QMessageBox::Ok,
ButtonCallback callback = [](QMessageBox::StandardButton) {});
protected:
private slots:

View file

@ -69,6 +69,17 @@ public:
}
};
const QString & getQmlDir() {
static QString dir;
if (dir.isEmpty()) {
QDir path(__FILE__);
path.cdUp();
dir = path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/")) + "/";
qDebug() << "Qml Path: " << dir;
}
return dir;
}
// Create a simple OpenGL window that renders text in various ways
class QTestWindow : public QWindow {
Q_OBJECT
@ -77,7 +88,6 @@ class QTestWindow : public QWindow {
QSize _size;
TextRenderer* _textRenderer[4];
RateCounter fps;
OffscreenUi _offscreenUi;
int testQmlTexture{ 0 };
//ProgramPtr _planeProgam;
//ShapeWrapperPtr _planeShape;
@ -90,11 +100,12 @@ protected:
private:
void resizeWindow(const QSize & size) {
_size = size;
_offscreenUi.resize(_size);
DependencyManager::get<OffscreenUi>()->resize(_size);
}
public:
QTestWindow() {
DependencyManager::set<OffscreenUi>();
setSurfaceType(QSurface::OpenGLSurface);
QSurfaceFormat format;
@ -154,30 +165,30 @@ public:
glClearColor(0.2f, 0.2f, 0.2f, 1);
glDisable(GL_DEPTH_TEST);
_offscreenUi.create(_context);
auto offscreenUi = DependencyManager::get<OffscreenUi>();
offscreenUi->create(_context);
// FIXME, need to switch to a QWindow for mouse and keyboard input to work
_offscreenUi.setProxyWindow(this);
offscreenUi->setProxyWindow(this);
// "#0e7077"
setFramePosition(QPoint(-1000, 0));
resize(QSize(800, 600));
QApplication::applicationDirPath();
QDir path(__FILE__);
path.cdUp();
static const QString f(path.cleanPath(path.absoluteFilePath("../../../interface/resources/qml/TestRoot.qml")));
offscreenUi->setBaseUrl(QUrl::fromLocalFile(getQmlDir()));
offscreenUi->load(QUrl("TestRoot.qml"));
offscreenUi->addImportPath(getQmlDir());
offscreenUi->addImportPath(".");
_offscreenUi.loadQml(QUrl::fromLocalFile(f));
connect(&_offscreenUi, &OffscreenUi::textureUpdated, this, [&](int textureId) {
_offscreenUi.lockTexture(textureId);
connect(offscreenUi.data(), &OffscreenUi::textureUpdated, this, [this, offscreenUi](int textureId) {
offscreenUi->lockTexture(textureId);
assert(!glGetError());
GLuint oldTexture = testQmlTexture;
testQmlTexture = textureId;
if (oldTexture) {
_offscreenUi.releaseTexture(oldTexture);
offscreenUi->releaseTexture(oldTexture);
}
});
installEventFilter(&_offscreenUi);
_offscreenUi.resume();
installEventFilter(offscreenUi.data());
offscreenUi->resume();
}
virtual ~QTestWindow() {
@ -197,8 +208,10 @@ protected:
void keyPressEvent(QKeyEvent *event) {
switch (event->key()) {
case Qt::Key_Slash:
_offscreenUi.toggle(QString("TestDialog.qml"), "TestDialog");
case Qt::Key_L:
if (event->modifiers() & Qt::CTRL) {
DependencyManager::get<OffscreenUi>()->toggle(QString("TestDialog.qml"), "TestDialog");
}
break;
}
QWindow::keyPressEvent(event);
@ -319,6 +332,7 @@ int main(int argc, char** argv) {
QApplication app(argc, argv);
//QLoggingCategory::setFilterRules("qt.quick.mouse.debug = true");
QTestWindow window;
QTimer timer;
timer.setInterval(1);
app.connect(&timer, &QTimer::timeout, &app, [&] {