kluszon
kluszon

Reputation: 415

QML singletone style nested files

I've got a defined style for the QML application as a separate file MyStyle.qml:

pragma Singleton
import QtQuick 2.15
import QtQml 2.15

QtObject {
    property color color1: "#ffffff"
    ...
}

I want to modify it with another file for ultra resolution >2k MyStyle_2k.qml.

MyStyle{
    color1: "#000000"
    ... 
}

The style is registered in main.cpp:

QScreen* screen = QGuiApplication::primaryScreen();
auto screenHeight = screen->geometry().height();

QUrl styleUrl;

if(screenHeight > 1440){
    styleUrl = QUrl("qrc:/gui/style/MyStyle_2k.qml");
}else{
    styleUrl = QUrl("qrc:/gui/style/MyStyle.qml");
}

qmlRegisterSingletonType(styleUrl, "MyStyle", 1, 0, "MyStyle");

Unfortunately, it doesn't work because of errors:

QQmlApplicationEngine failed to load component qrc:/path/other_file.qml: Type MyStyle/MyStyle unavailable :9:1: Composite Singleton Type MyStyle is not creatable.

Is it possible to modify qml singleton by another file?

Upvotes: 1

Views: 388

Answers (3)

kluszon
kluszon

Reputation: 415

The only working solution I found is using single file as singleton Style.qml with state group.

main.cpp

...
QScreen* screen = QGuiApplication::primaryScreen();
auto screenHeight = screen->geometry().height();

    if(screenHeight > 1440){
        engine.rootContext()->setContextProperty("StyleState", "2k");
    }else{
        engine.rootContext()->setContextProperty("StyleState", "");
    }
...

State.qml

pragma Singleton
import QtQuick 2.15
import QtQml 2.15

QtObject {
    id: root

    property var widthA: 50
    property var widthB: 100
    
    property alias customStyle: customState.state // alias for call dircectly from qml

    property var states: StateGroup{
        id: customState
        state: StyleState
        states: [
            State{
                name: "2k"
                PropertyChanges{
                    target: root
                    widthA: 100
                    widthB: 200
                }
            }
        ]
    }
}

Upvotes: 0

iam_peter
iam_peter

Reputation: 3924

You are trying to create an instance of a singleton type which is not possible hence the error message. If you want to create an instance of the style and modify it inside QML you need to use qmlRegisterType().

Or you need to write MyStyle.color1: "#000000" to modify the singleton value.

EDIT: You could create an instantiable style like InternalMyStyle.qml set all the default values there and re-use it in MyStyle.qml and MyStyle_2k.qml as shown below. Both MyStyle.qml and MyStyle_2k.qml are still singletons.

InternalMyStyle.qml

import QtQuick

QtObject {
    property color color1: "#ff00ff"
}

MyStyle.qml

pragma Singleton
import QtQuick

InternalMyStyle {}

MyStyle_2k.qml

pragma Singleton
import QtQuick

InternalMyStyle {
    color1: "#ff0000"
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QScreen>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QScreen *screen = QGuiApplication::primaryScreen();
    auto screenHeight = screen->geometry().height();

    QUrl styleUrl;

    if (screenHeight > 1440)
        styleUrl = QUrl(u"qrc:/untitledMyStyle/MyStyle_2k.qml"_qs);
    else
        styleUrl = QUrl(u"qrc:/untitledMyStyle/MyStyle.qml"_qs);

    qmlRegisterSingletonType(styleUrl, "MyStyle", 1, 0, "MyStyle");

    QQmlApplicationEngine engine;
    const QUrl url(u"qrc:/untitledMyStyle/main.qml"_qs);
    QObject::connect(
        &engine,
        &QQmlApplicationEngine::objectCreated,
        &app,
        [url](QObject *obj, const QUrl &objUrl) {
            if (!obj && url == objUrl)
                QCoreApplication::exit(-1);
        },
        Qt::QueuedConnection);

    engine.load(url);

    return app.exec();
}

CMakeLists.txt

...
qt_add_qml_module(appuntitledMyStyle
    URI untitledMyStyle
    VERSION 1.0
    QML_FILES
        main.qml
        MyStyle.qml
        MyStyle_2k.qml
        InternalMyStyle.qml
)
...

Upvotes: 1

Stephen Quan
Stephen Quan

Reputation: 26299

You should name both of your files MyStyle.qml but organize them in the following way:

qrc:/gui/style/MyStyle.qml
qrc:/gui/style/+highres/MyStyle.qml

Then, you can let Qt know which version of MyStyle.qml to load via the use of file selectors. Note the usage of the plus sign + as a prefix to highres. This is how Qt organizes file overrides based on default or custom file selectors. The easiest way to declare highres as a custom file selector is via QT_FILE_SELECTOR environment variable, e.g.

QScreen* screen = QGuiApplication::primaryScreen();
auto screenHeight = screen->geometry().height();
if (screenHeight > 1440) {
   qputenv("QT_FILE_SELECTOR", QString("highres").toUtf8());
}

This code needs to happen very early on in your main.cpp because QT_FILE_SELECTOR will be checked once and will not be rechecked later on.

From https://doc.qt.io/qt-6/qfileselector.html:

Further selectors will be added from the QT_FILE_SELECTORS environment variable, which when set should be a set of comma separated selectors. Note that this variable will only be read once; selectors may not update if the variable changes while the application is running. The initial set of selectors are evaluated only once, on first use.

If you do not wish to use QT_FILE_SELECTOR environment variable, then, the proper way to define a file selector for QML use is via QQmlFileSelector:

QScreen* screen = QGuiApplication::primaryScreen();
auto screenHeight = screen->geometry().height();
QQmlFileSelector* selector = null;
if (screenHeight > 1440) {
   selector = new QQmlFileSelector(&engine);
   selector->setExtraSelectors(QStringList() << QString("highres"));
}

The advantage of using QQmlFileSelector is you can control the file selectors that are active in runtime. i.e. you can add/remove a file selector. However, this also means that every time you change a file selector, you have to force components that are already loaded to reload. However, it comes at the cost of additional coding requirements, in that, you need to instantiate the QQmlFileSelector configure it, force a reload of components (which is even harder to do for singletons), and clean up code when your program exits (note I have not provided any of this, since, it is left up to you how to instantiate and release QQmlFileSelector consistent to how you manage memory in your app).

For more details, you should consult the documentation directly.

Upvotes: 0

Related Questions