-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
QML Pt. 1: Add initial support for loading QML skins #3894
Merged
Merged
Changes from 13 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
153a8d2
Skin: Add QmlSkin class
Holzhaus 26395a5
SkinLoader: Disable QML support by default via config option
Holzhaus dc760ab
ControlProxyQml: Add support for CO access from QML
Be-ing 2b98680
ControlProxyQml: Ensure that UI is updated in any case
Holzhaus fc455bd
QML: Add more checks and warnings and expose keyValid/initialized props
Holzhaus 41b1e3e
QML: Rename ControlProxyQml to QmlControlProxy and add to namespace
Holzhaus 80181b0
QML: Register QmlControlProxy as Mixxx.ControlProxy
Holzhaus 9cd7649
QmlSkin: Add private dir() helper for constructing filePaths more eff…
Holzhaus 87e4b82
QML: Remove unused m_emptyString member from QmlControlProxy
Holzhaus c738dc5
QML: Rework QmlControlProxy initialization/reset logic
Holzhaus 7fc5ed0
QML: Prevent QmlControlProxy value/parameter change while not initial…
Holzhaus bf2518e
QML: Prevent QmlControlProxy log spam when using an invalid CO
Holzhaus fedef14
QML: Make QmlControlProxy constructor explicit
Holzhaus 9977cb0
QML: Rename QmlControlProxy::reinitializeOrReset to reinitializeFromKey
Holzhaus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
#include "skin/qml/qmlcontrolproxy.h" | ||
|
||
#include "moc_qmlcontrolproxy.cpp" | ||
|
||
namespace mixxx { | ||
namespace skin { | ||
namespace qml { | ||
|
||
namespace { | ||
constexpr double kDefaultValue = 0.0; | ||
constexpr double kDefaultParameter = 0.0; | ||
} // namespace | ||
|
||
QmlControlProxy::QmlControlProxy(QObject* parent) | ||
: QObject(parent), | ||
m_isComponentComplete(false), | ||
m_pControlProxy(nullptr) { | ||
} | ||
|
||
void QmlControlProxy::componentComplete() { | ||
m_isComponentComplete = true; | ||
reinitializeOrReset(); | ||
} | ||
|
||
bool QmlControlProxy::isKeyValid() const { | ||
return m_coKey.isValid(); | ||
} | ||
|
||
bool QmlControlProxy::isInitialized() const { | ||
return m_pControlProxy != nullptr; | ||
} | ||
|
||
void QmlControlProxy::setGroup(const QString& group) { | ||
if (m_coKey.group == group) { | ||
return; | ||
} | ||
|
||
const bool keyValidBeforeSet = isKeyValid(); | ||
m_coKey.group = group; | ||
emit groupChanged(group); | ||
|
||
const bool keyValidAfterSet = isKeyValid(); | ||
if (keyValidBeforeSet != keyValidAfterSet) { | ||
emit keyValidChanged(keyValidAfterSet); | ||
} | ||
|
||
reinitializeOrReset(); | ||
} | ||
|
||
const QString& QmlControlProxy::getGroup() const { | ||
return m_coKey.group; | ||
} | ||
|
||
void QmlControlProxy::setKey(const QString& key) { | ||
if (m_coKey.item == key) { | ||
return; | ||
} | ||
|
||
const bool keyValidBeforeSet = isKeyValid(); | ||
m_coKey.item = key; | ||
emit keyChanged(key); | ||
|
||
const bool keyValidAfterSet = isKeyValid(); | ||
if (keyValidBeforeSet != keyValidAfterSet) { | ||
emit keyValidChanged(keyValidAfterSet); | ||
} | ||
|
||
reinitializeOrReset(); | ||
} | ||
|
||
const QString& QmlControlProxy::getKey() const { | ||
return m_coKey.item; | ||
} | ||
|
||
void QmlControlProxy::setValue(double newValue) { | ||
if (!isInitialized()) { | ||
emit valueChanged(kDefaultValue); | ||
return; | ||
} | ||
m_pControlProxy->set(newValue); | ||
slotControlProxyValueChanged(newValue); | ||
} | ||
|
||
double QmlControlProxy::getValue() const { | ||
if (!isInitialized()) { | ||
return kDefaultValue; | ||
} | ||
return m_pControlProxy->get(); | ||
} | ||
|
||
void QmlControlProxy::setParameter(double newValue) { | ||
if (!isInitialized()) { | ||
emit parameterChanged(kDefaultValue); | ||
return; | ||
} | ||
m_pControlProxy->setParameter(newValue); | ||
emit valueChanged(m_pControlProxy->get()); | ||
emit parameterChanged(newValue); | ||
} | ||
|
||
double QmlControlProxy::getParameter() const { | ||
if (!isInitialized()) { | ||
return kDefaultParameter; | ||
} | ||
return m_pControlProxy->getParameter(); | ||
} | ||
|
||
void QmlControlProxy::reinitializeOrReset() { | ||
// Just ignore this if the component is still loading, because group or key may not be set yet. | ||
if (!m_isComponentComplete) { | ||
return; | ||
} | ||
|
||
// We don't need to reinitialize or reset the underlying control proxy if | ||
// the CO key didn't change. | ||
if (isInitialized() && m_coKey == m_pControlProxy->getKey()) { | ||
return; | ||
} | ||
|
||
// If the key is invalid, reset the control proxy if necessary. | ||
if (!isKeyValid()) { | ||
qWarning() << "QmlControlProxy: Tried to initialize CO" << m_coKey | ||
<< " with invalid key, resetting..."; | ||
if (isInitialized()) { | ||
m_pControlProxy.reset(); | ||
emit initializedChanged(false); | ||
} | ||
return; | ||
} | ||
|
||
// We don't need to warn here if the control is missing, because we'll do a | ||
// check below and print a warning anyway. If the key is invalid, this will | ||
// still trigger an assertion because we checked the key validity above. If | ||
// it's still invalid, that's a programming error. | ||
std::unique_ptr<ControlProxy> pControlProxy = | ||
std::make_unique<ControlProxy>( | ||
m_coKey, this, ControlFlag::NoWarnIfMissing); | ||
|
||
// This should never happen, but it doesn't hurt to check. | ||
VERIFY_OR_DEBUG_ASSERT(pControlProxy != nullptr) { | ||
qWarning() << "QmlControlProxy: Requested CO " << m_coKey | ||
<< " returned nullptr, resetting..."; | ||
if (isInitialized()) { | ||
m_pControlProxy.reset(); | ||
emit initializedChanged(false); | ||
} | ||
return; | ||
} | ||
|
||
// If the control does not exist, reset the control proxy if necessary. | ||
if (!pControlProxy->valid()) { | ||
qWarning() << "QmlControlProxy: Requested CO" << m_coKey << " does not exist, resetting..."; | ||
if (isInitialized()) { | ||
m_pControlProxy.reset(); | ||
emit initializedChanged(false); | ||
} | ||
return; | ||
} | ||
|
||
// Set the control proxy and emit signal | ||
const bool wasInitialized = isInitialized(); | ||
m_pControlProxy = std::move(pControlProxy); | ||
if (!wasInitialized) { | ||
emit initializedChanged(true); | ||
} | ||
m_pControlProxy->connectValueChanged(this, &QmlControlProxy::slotControlProxyValueChanged); | ||
slotControlProxyValueChanged(m_pControlProxy->get()); | ||
} | ||
|
||
void QmlControlProxy::slotControlProxyValueChanged(double newValue) { | ||
emit valueChanged(newValue); | ||
emit parameterChanged(m_pControlProxy->getParameter()); | ||
} | ||
|
||
} // namespace qml | ||
} // namespace skin | ||
} // namespace mixxx |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
#pragma once | ||
|
||
#include <QObject> | ||
#include <QQmlParserStatus> | ||
#include <QtQml> | ||
#include <memory> | ||
|
||
#include "control/controlproxy.h" | ||
|
||
namespace mixxx { | ||
namespace skin { | ||
namespace qml { | ||
|
||
class QmlControlProxy : public QObject, public QQmlParserStatus { | ||
Q_OBJECT | ||
Q_INTERFACES(QQmlParserStatus) | ||
// The REQUIRED flag only exists in Qt 5.14 and later. | ||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) | ||
Q_PROPERTY(QString group READ getGroup WRITE setGroup NOTIFY groupChanged REQUIRED) | ||
Q_PROPERTY(QString key READ getKey WRITE setKey NOTIFY keyChanged REQUIRED) | ||
#else | ||
Q_PROPERTY(QString group READ getGroup WRITE setGroup NOTIFY groupChanged) | ||
Q_PROPERTY(QString key READ getKey WRITE setKey NOTIFY keyChanged) | ||
#endif | ||
Q_PROPERTY(QString keyValid READ isKeyValid NOTIFY keyValidChanged) | ||
Q_PROPERTY(QString initialized READ isInitialized NOTIFY initializedChanged) | ||
Q_PROPERTY(double value READ getValue WRITE setValue NOTIFY valueChanged) | ||
Q_PROPERTY(double parameter READ getParameter WRITE setParameter NOTIFY parameterChanged) | ||
|
||
public: | ||
explicit QmlControlProxy(QObject* parent = nullptr); | ||
|
||
/// Implementing the QQmlParserStatus interface requires overriding this | ||
/// method, but we don't need it. | ||
// Invoked after class creation, but before any properties have been set. | ||
void classBegin() override{}; | ||
|
||
/// QML cannot pass arguments to C++ constructors so this class needs to | ||
/// rely on the QML object setting the group and key properties to | ||
/// initialize the ControlProxy. We want to deplay the initialization of | ||
/// the underlying ControlProxy until the object has been fully created and | ||
/// all properties (group and key in particular) have been set. Perform | ||
/// some initialization here now that the object is fully created. | ||
void componentComplete() override; | ||
|
||
void setGroup(const QString& group); | ||
uklotzde marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const QString& getGroup() const; | ||
|
||
void setKey(const QString& key); | ||
const QString& getKey() const; | ||
|
||
void setValue(double newValue); | ||
double getValue() const; | ||
|
||
void setParameter(double newValue); | ||
double getParameter() const; | ||
|
||
bool isKeyValid() const; | ||
bool isInitialized() const; | ||
|
||
signals: | ||
void groupChanged(const QString& group); | ||
void keyChanged(const QString& key); | ||
void keyValidChanged(bool valid); | ||
uklotzde marked this conversation as resolved.
Show resolved
Hide resolved
|
||
void initializedChanged(bool initialized); | ||
void valueChanged(double newValue); | ||
void parameterChanged(double newParameter); | ||
|
||
private slots: | ||
/// Emits both the valueChanged and parameterChanged signals | ||
void slotControlProxyValueChanged(double newValue); | ||
uklotzde marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
private: | ||
/// (Re-)Initializes or resets the underlying control proxy. Called for the first time when | ||
/// component construction has been completed. From that moment on, it's | ||
/// called whenever the group or key changes. | ||
void reinitializeOrReset(); | ||
|
||
ConfigKey m_coKey; | ||
|
||
/// Set to true in the componentComplete() method, which is called when the | ||
/// QML object creation is complete. | ||
bool m_isComponentComplete; | ||
std::unique_ptr<ControlProxy> m_pControlProxy; | ||
}; | ||
|
||
} // namespace qml | ||
} // namespace skin | ||
} // namespace mixxx |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is orReset still valid?
It look like the function returns early if it is already initialized
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OrReset is confusing, because resetting a control proxy is different from resetting a smart pointer. It is also not the alternative to initialize.
How about:
ReinitializeFromKey()
Than it is clear that one cane assume that it becomes invalid if the key is invalid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.