diff --git a/framework/global/CMakeLists.txt b/framework/global/CMakeLists.txt
index 01d1ff1e46..e63c6f0e1c 100644
--- a/framework/global/CMakeLists.txt
+++ b/framework/global/CMakeLists.txt
@@ -251,9 +251,7 @@ if (MUSE_QT_SUPPORT)
# so that other modules get them transitively.
target_link_libraries(muse_global PUBLIC Qt::Core Qt::Gui)
- # These are needed by the PCH; if we don't make them public, modules
- # that don't use them won't be able to use the PCH with some compilers.
- target_link_libraries(muse_global PUBLIC Qt::Quick Qt::Widgets)
+ target_link_libraries(muse_global PRIVATE Qt::Quick Qt::Widgets)
endif()
target_link_libraries(muse_global PRIVATE ${CMAKE_DL_LIBS})
diff --git a/framework/interactive/iinteractiveuriregister.h b/framework/interactive/iinteractiveuriregister.h
index a886aef320..16eaf4a18b 100644
--- a/framework/interactive/iinteractiveuriregister.h
+++ b/framework/interactive/iinteractiveuriregister.h
@@ -27,10 +27,13 @@
#include "modularity/imoduleinterface.h"
#include "types/uri.h"
#include "interactivetypes.h"
-#include "ui/view/widgetdialog.h"
class QWidget;
+namespace muse::ui {
+class WidgetDialog;
+}
+
namespace muse::interactive {
class IInteractiveUriRegister : MODULE_GLOBAL_INTERFACE
{
diff --git a/framework/tours/internal/toursprovider.cpp b/framework/tours/internal/toursprovider.cpp
index eb80afcd74..98a52ad3ee 100644
--- a/framework/tours/internal/toursprovider.cpp
+++ b/framework/tours/internal/toursprovider.cpp
@@ -19,9 +19,10 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
#include "toursprovider.h"
-#include
+#include
#include "log.h"
@@ -35,7 +36,7 @@ ToursProvider::ToursProvider(const modularity::ContextPtr& iocCtx)
{
connect(&m_openTimer, &QTimer::timeout, this, &ToursProvider::doShow);
- connect(qApp, &QApplication::applicationStateChanged, this, &ToursProvider::onApplicationStateChanged);
+ connect(qApp, &QGuiApplication::applicationStateChanged, this, &ToursProvider::onApplicationStateChanged);
}
void ToursProvider::showTour(const Tour& tour)
diff --git a/framework/ui/CMakeLists.txt b/framework/ui/CMakeLists.txt
index 217a9b1b59..207c23bcc8 100644
--- a/framework/ui/CMakeLists.txt
+++ b/framework/ui/CMakeLists.txt
@@ -95,12 +95,14 @@ target_sources(muse_ui PRIVATE
view/qmltooltip.h
view/qmltranslation.cpp
view/qmltranslation.h
+ view/widgetdialog.h
view/widgetnavigationfix.cpp
view/widgetnavigationfix.h
view/widgetstatestore.cpp
view/widgetstatestore.h
+ view/widgetstyle.cpp
+ view/widgetstyle.h
view/widgetutils.h
- view/widgetdialog.h
)
if (OS_IS_MAC)
@@ -128,7 +130,7 @@ elseif(OS_IS_WIN)
)
# Exclude from Unity Build to avoid Error C2872: 'Uri': ambiguous symbol
- # and avoid error with IconCode::ERROR
+ # and avoid error with IconCode::ERROR
set_source_files_properties(
${CMAKE_CURRENT_LIST_DIR}/internal/platform/windows/windowsplatformtheme.cpp
${CMAKE_CURRENT_LIST_DIR}/internal/platform/windows/winwindowscontroller.cpp
@@ -161,7 +163,8 @@ if (MUSE_LOAD_QML_FROM_SOURCE)
endif()
if (MUSE_QT_SUPPORT)
- target_link_libraries(muse_ui PRIVATE Qt::Quick Qt::Widgets)
+ target_link_libraries(muse_ui PUBLIC Qt::Quick)
+ target_link_libraries(muse_ui PRIVATE Qt::Widgets)
if (OS_IS_LIN)
target_link_libraries(muse_ui PRIVATE Qt::DBus)
diff --git a/framework/ui/api/themeapi.cpp b/framework/ui/api/themeapi.cpp
index 7deb421ad5..dff1f74395 100644
--- a/framework/ui/api/themeapi.cpp
+++ b/framework/ui/api/themeapi.cpp
@@ -22,68 +22,9 @@
#include "themeapi.h"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-
-#include "log.h"
-
using namespace muse::ui;
using namespace muse::api;
-static const QPen NO_BORDER(Qt::transparent, 0);
-static const QBrush NO_FILL(Qt::transparent);
-static const int DEFAULT_RADIUS = 3;
-
-static const int GROUP_BOX_LABEL_SPACING = 2;
-
-//! In QML, a border is drawn _inside_ a rectangle.
-//! In C++, a border would normally be drawn half inside the rectangle, half outside.
-//! In this function, we exactly replicate the behaviour from QML.
-static void drawRoundedRect(QPainter* painter, const QRectF& rect, const qreal radius, const QBrush& brush = NO_FILL,
- const QPen& pen = NO_BORDER)
-{
- IF_ASSERT_FAILED(painter) {
- return;
- }
-
- const qreal bw = pen.width();
-
- if (bw <= 0 || pen.color().alpha() == 0) {
- if (brush == NO_FILL) {
- return;
- }
-
- painter->save();
- painter->setPen(NO_BORDER);
- painter->setBrush(brush);
- painter->drawRoundedRect(rect, radius, radius);
- painter->restore();
- return;
- }
-
- painter->save();
- painter->setPen(NO_BORDER);
- painter->setBrush(brush);
- painter->drawRoundedRect(rect.adjusted(bw, bw, -bw, -bw), radius - bw, radius - bw);
-
- const qreal corr = 0.5 * bw;
- painter->setPen(pen);
- painter->setBrush(NO_FILL);
- painter->drawRoundedRect(rect.adjusted(corr, corr, -corr, -corr), radius - corr, radius - corr);
- painter->restore();
-}
-
struct FontConfig
{
QFont::Weight weight = QFont::Normal;
@@ -96,11 +37,6 @@ ThemeApi::ThemeApi(IApiEngine* e)
setObjectName("UiTheme");
}
-ThemeApi::~ThemeApi()
-{
- delete m_style;
-}
-
void ThemeApi::init()
{
configuration()->currentThemeChanged().onNotify(this, [this]() {
@@ -114,8 +50,6 @@ void ThemeApi::init()
initMusicalFont();
initMusicalTextFont();
calculateDefaultButtonSize();
-
- setupWidgetTheme();
}
void ThemeApi::initThemeValues()
@@ -153,7 +87,6 @@ void ThemeApi::update()
{
calculateDefaultButtonSize();
initThemeValues();
- setupWidgetTheme();
notifyAboutThemeChanged();
}
@@ -451,63 +384,6 @@ void ThemeApi::calculateDefaultButtonSize()
m_defaultButtonSize = std::max(requiredSize, MINIMUM_BUTTON_SIZE);
}
-void ThemeApi::setupWidgetTheme()
-{
- QColor fontPrimaryColorDisabled = fontPrimaryColor();
- fontPrimaryColorDisabled.setAlphaF(itemOpacityDisabled());
-
- QColor linkColorDisabled = linkColor();
- linkColorDisabled.setAlphaF(itemOpacityDisabled());
-
- QColor backgroundPrimaryColorDisabled = backgroundPrimaryColor();
- backgroundPrimaryColorDisabled.setAlphaF(itemOpacityDisabled());
-
- QColor backgroundSecondaryColorDisabled = backgroundSecondaryColor();
- backgroundSecondaryColorDisabled.setAlphaF(itemOpacityDisabled());
-
- QColor buttonColorDisabled = buttonColor();
- buttonColorDisabled.setAlphaF(itemOpacityDisabled());
-
- QPalette palette(QApplication::palette());
- palette.setColor(QPalette::Window, backgroundPrimaryColor());
- palette.setColor(QPalette::Disabled, QPalette::Window, backgroundPrimaryColorDisabled);
- palette.setColor(QPalette::WindowText, fontPrimaryColor());
- palette.setColor(QPalette::Disabled, QPalette::WindowText, fontPrimaryColorDisabled);
-
- palette.setColor(QPalette::Base, backgroundSecondaryColor());
- palette.setColor(QPalette::Disabled, QPalette::Base, backgroundSecondaryColorDisabled);
- palette.setColor(QPalette::AlternateBase, backgroundSecondaryColor());
- palette.setColor(QPalette::Disabled, QPalette::AlternateBase, backgroundSecondaryColorDisabled);
-
- palette.setColor(QPalette::Text, fontPrimaryColor());
- palette.setColor(QPalette::Disabled, QPalette::Text, fontPrimaryColorDisabled);
-
- palette.setColor(QPalette::Link, linkColor());
- palette.setColor(QPalette::Disabled, QPalette::Link, linkColorDisabled);
-
- palette.setColor(QPalette::Button, buttonColor());
- palette.setColor(QPalette::Disabled, QPalette::Button, buttonColorDisabled);
- palette.setColor(QPalette::ButtonText, fontPrimaryColor());
- palette.setColor(QPalette::Disabled, QPalette::ButtonText, fontPrimaryColorDisabled);
-
- palette.setColor(QPalette::ToolTipBase, popupBackgroundColor());
- palette.setColor(QPalette::ToolTipText, fontPrimaryColor());
-
- palette.setColor(QPalette::Highlight, accentColor());
- palette.setColor(QPalette::HighlightedText, fontPrimaryColor());
-
- palette.setColor(QPalette::PlaceholderText, fontPrimaryColor());
- palette.setColor(QPalette::Disabled, QPalette::PlaceholderText, fontPrimaryColorDisabled);
-
- m_style = new ProxyStyle(this);
- QApplication::setStyle(m_style);
- QApplication::setPalette(palette);
-
- QString styleSheet = QString("* { font: %1px \"%2\" } ")
- .arg(QString::number(bodyFont().pixelSize()), bodyFont().family());
- qApp->setStyleSheet(styleSheet);
-}
-
void ThemeApi::notifyAboutThemeChanged()
{
emit themeChanged();
@@ -517,600 +393,3 @@ QVariantMap ThemeApi::extra() const
{
return m_extra;
}
-
-// ====================================================
-// QStyle
-// ====================================================
-
-ProxyStyle::ProxyStyle(ThemeApi* t)
- : QProxyStyle(QStyleFactory::create("Fusion")), m_theme(t)
-{
-}
-
-void ProxyStyle::polish(QWidget* widget)
-{
- QProxyStyle::polish(widget);
-
- if (qobject_cast(widget)
- || qobject_cast(widget)
- || qobject_cast(widget)) {
- // Make hovering work
- widget->setMouseTracking(true);
- widget->setAttribute(Qt::WA_Hover, true);
- }
-}
-
-void ProxyStyle::unpolish(QWidget* widget)
-{
- QProxyStyle::unpolish(widget);
-
- if (qobject_cast(widget)
- || qobject_cast(widget)
- || qobject_cast(widget)) {
- widget->setMouseTracking(false);
- widget->setAttribute(Qt::WA_Hover, false);
- }
-}
-
-void ProxyStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter,
- const QWidget* widget) const
-{
- StyleState styleState;
- styleState.enabled = option->state & State_Enabled;
- styleState.hovered = option->state & State_MouseOver;
- styleState.pressed = option->state & State_Sunken;
- styleState.focused = (option->state & State_KeyboardFocusChange) && (option->state & State_HasFocus);
-
- painter->save();
- painter->setRenderHint(QPainter::Antialiasing, true);
-
- //! NOTE This drawing code is based on the implementation in QML.
- switch (element) {
- // Buttons (and ComboBoxes)
- case QStyle::PE_PanelButtonCommand: {
- auto buttonOption = qstyleoption_cast(option);
- const bool accentButton = (buttonOption && buttonOption->features & QStyleOptionButton::DefaultButton)
- || option->state & State_On;
- const bool flat = (buttonOption && buttonOption->features & QStyleOptionButton::Flat)
- && !(option->state & State_On);
-
- QColor paletteColor = widget ? widget->palette().color(QPalette::Button) : QColor();
- const QColor background = paletteColor.isValid() ? paletteColor : m_theme->buttonColor();
-
- drawButtonBackground(painter, option->rect, styleState, accentButton, flat, background);
- } break;
- case QStyle::PE_FrameDefaultButton: {
- auto buttonOption = qstyleoption_cast(option);
- const bool flat = (buttonOption && buttonOption->features & QStyleOptionButton::Flat)
- && !(option->state & State_On);
- if (flat && styleState.focused) {
- // For other buttons, this is done in `drawButtonBackground`, but that is not called for flat buttons
- drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL,
- QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
- }
- } break;
-
- // Checkboxes
- case QStyle::PE_IndicatorCheckBox: {
- const bool indeterminate = option->state & State_NoChange;
- const bool checked = option->state & State_On;
- const bool inMenu = qobject_cast(widget);
-
- drawCheckboxIndicator(painter, option->rect, styleState, checked, indeterminate, inMenu);
- } break;
-
- // Radio buttons
- case QStyle::PE_IndicatorRadioButton: {
- const bool selected = option->state & State_On;
-
- drawRadioButtonIndicator(painter, option->rect, styleState, selected);
- } break;
-
- case QStyle::PE_FrameLineEdit: {
- const bool editing = option->state & State_HasFocus;
-
- drawLineEditBackground(painter, option->rect, styleState, editing);
- } break;
-
- case QStyle::PE_FrameFocusRect: {
- bool isTreeWidget = option->styleObject && option->styleObject->inherits("QTreeWidget");
- if (isTreeWidget) {
- drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
- }
-
- //! NOTE: need for removing frame focus rectangle
- } break;
-
- // Indicator icons
- case QStyle::PE_IndicatorSpinUp:
- case QStyle::PE_IndicatorSpinDown:
- case QStyle::PE_IndicatorSpinPlus:
- case QStyle::PE_IndicatorSpinMinus: {
- drawIndicatorIcon(painter, option->rect, styleState, element);
- } break;
-
- // ViewItem
- case QStyle::PE_PanelItemViewItem: {
- bool selected = option->state & State_Selected;
-
- drawViewItemBackground(painter, option->rect, styleState, selected);
- } break;
-
- // Toolbar
- case QStyle::PE_PanelToolBar: {
- painter->fillRect(option->rect, m_theme->backgroundPrimaryColor());
- } break;
- case QStyle::PE_IndicatorToolBarHandle: {
- drawToolbarGrip(painter, option->rect, option->state & State_Horizontal);
- } break;
-
- // GroupBox
- case QStyle::PE_FrameGroupBox: {
- drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, QBrush(QColor::fromRgba(0x03000000)),
- QPen(m_theme->strokeColor(), fmax(m_theme->borderWidth(), 1.0)));
- } break;
-
- // Menu
- case QStyle::PE_PanelMenu: {
- drawRoundedRect(painter, option->rect, 1, m_theme->backgroundPrimaryColor(), QPen(m_theme->strokeColor(), m_theme->borderWidth()));
- } break;
- case QStyle::PE_FrameMenu: {
- drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
- } break;
-
- case QStyle::PE_Frame: {
- if (qobject_cast(widget) != nullptr) {
- if (styleState.enabled) {
- drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
- } else {
- QColor penBorderColor = m_theme->strokeColor();
- penBorderColor.setAlphaF(m_theme->itemOpacityDisabled());
- drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL, QPen(penBorderColor, m_theme->borderWidth()));
- }
- }
- } break;
-
- default:
- QProxyStyle::drawPrimitive(element, option, painter, widget);
- break;
- }
-
- painter->restore();
-}
-
-void ProxyStyle::drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex* option, QPainter* painter,
- const QWidget* widget) const
-{
- StyleState styleState;
- styleState.enabled = option->state & State_Enabled;
- styleState.hovered = option->state & State_MouseOver;
- styleState.pressed = option->state & State_Sunken;
- styleState.focused = (option->state & State_KeyboardFocusChange) && (option->state & State_HasFocus);
-
- switch (control) {
- case CC_ScrollBar: {
- QProxyStyle::drawComplexControl(control, option, painter, widget);
-
- if (m_theme->configuration()->isHighContrast()) {
- QRect scrollBarHandle = QProxyStyle::subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
-
- QColor handleColor = m_theme->fontPrimaryColor();
- handleColor.setAlphaF(
- !styleState.enabled ? m_theme->buttonOpacityNormal()
- * m_theme->itemOpacityDisabled() : styleState.pressed ? m_theme->buttonOpacityHit() : styleState.hovered ? m_theme->buttonOpacityHover() : m_theme->buttonOpacityNormal());
-
- drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
- drawRoundedRect(painter, scrollBarHandle, 1, handleColor, NO_BORDER);
- }
- } break;
-
- case CC_SpinBox: {
- QProxyStyle::drawComplexControl(control, option, painter, widget);
-
- QRect spinBoxFrame = QProxyStyle::subControlRect(CC_SpinBox, option, SC_SpinBoxFrame, widget);
- if (styleState.focused) {
- drawRoundedRect(painter, spinBoxFrame, DEFAULT_RADIUS, NO_FILL,
- QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
- }
- } break;
-
- default: QProxyStyle::drawComplexControl(control, option, painter, widget);
- }
-}
-
-QRect ProxyStyle::subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option, QStyle::SubControl subControl,
- const QWidget* widget) const
-{
- //QRect commonStyleRect = QCommonStyle::subControlRect(control, option, subControl, widget);
- QRect proxyStyleRect = QProxyStyle::subControlRect(control, option, subControl, widget);
-
- switch (control) {
- case QStyle::CC_GroupBox:
- if (const QStyleOptionGroupBox* optionGroupBox = qstyleoption_cast(option)) {
- int indicatorWidth = 0;
- int indicatorHeight = 0;
- int indicatorSpacing = 0;
- const QSize textSize = option->fontMetrics.boundingRect(optionGroupBox->text).size() + QSize(2, 2);
-
- const bool checkable = option->subControls & QStyle::SC_GroupBoxCheckBox;
-
- if (checkable) {
- const bool isRadioButtonGroupBox
- =widget && strcmp(widget->metaObject()->className(), "muse::uicomponents::RadioButtonGroupBox") == 0;
-
- indicatorWidth = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorWidth : PM_IndicatorWidth, option, widget);
- indicatorHeight = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, option, widget);
- indicatorSpacing
- = pixelMetric(isRadioButtonGroupBox ? PM_RadioButtonLabelSpacing : PM_CheckBoxLabelSpacing, option, widget);
- }
-
- if (subControl == SC_GroupBoxFrame) {
- int topMargin = std::max(indicatorHeight, textSize.height()) + GROUP_BOX_LABEL_SPACING;
- return option->rect.adjusted(0, topMargin, 0, 0);
- }
-
- if (subControl == SC_GroupBoxContents) {
- int margin = 3;
- int topMargin = margin + std::max(indicatorHeight, textSize.height()) + GROUP_BOX_LABEL_SPACING;
- return option->rect.adjusted(margin, topMargin, -margin, -margin);
- }
-
- const int width = textSize.width() + (checkable ? indicatorWidth + indicatorSpacing : 0);
- QRect rect;
-
- if (option->rect.width() > width) {
- switch (optionGroupBox->textAlignment & Qt::AlignHorizontal_Mask) {
- case Qt::AlignHCenter:
- rect.moveLeft((option->rect.width() - width) / 2);
- break;
- case Qt::AlignRight:
- rect.moveLeft(option->rect.width() - width);
- break;
- }
- }
-
- if (subControl == SC_GroupBoxCheckBox) {
- rect.setWidth(indicatorWidth);
- rect.setHeight(indicatorHeight);
- rect.moveTop(textSize.height() > indicatorHeight ? (textSize.height() - indicatorHeight) / 2 : 0);
- } else if (subControl == SC_GroupBoxLabel) {
- rect.setSize(textSize);
- if (checkable) {
- rect.moveTop(textSize.height() < indicatorHeight ? (indicatorHeight - textSize.height()) / 2 : 0);
- rect.translate(indicatorWidth + indicatorSpacing, 0);
- }
- }
-
- return visualRect(option->direction, option->rect, rect);
- }
- break;
- default:
- break;
- }
-
- return proxyStyleRect;
-}
-
-int ProxyStyle::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option, const QWidget* widget) const
-{
- //! NOTE These metrics are based on the implementation in QML.
- switch (metric) {
- case PM_IndicatorWidth: // Checkbox
- case PM_IndicatorHeight:
- case PM_ExclusiveIndicatorWidth: // Radio button
- case PM_ExclusiveIndicatorHeight:
- return 20;
- case PM_CheckBoxLabelSpacing: // Checkbox
- case PM_RadioButtonLabelSpacing: // Radio button
- return 6;
- case PM_ToolBarHandleExtent: // Toolbars
- return 32;
- default:
- break;
- }
-
- return QProxyStyle::pixelMetric(metric, option, widget);
-}
-
-QSize ProxyStyle::sizeFromContents(QStyle::ContentsType type, const QStyleOption* option, const QSize& contentsSize,
- const QWidget* widget) const
-{
- QSize commonStyleSize = QCommonStyle::sizeFromContents(type, option, contentsSize, widget);
- QSize proxyStyleSize = QProxyStyle::sizeFromContents(type, option, contentsSize, widget);
-
- //! NOTE These calculations are based on the implementation in QML.
- switch (type) {
- case CT_PushButton:
- return QSize(std::max(contentsSize.width() + 32, 132),
- contentsSize.height() + 14);
- case CT_ToolButton:
- return contentsSize.expandedTo(QSize(30, 30));
- case CT_ComboBox:
- case CT_LineEdit:
- return proxyStyleSize.expandedTo(QSize(30, 30));
- case CT_SpinBox:
- return QSize(proxyStyleSize.width(), 26); // results in a height of 30
- case CT_GroupBox: {
- const QGroupBox* groupBox = qobject_cast(widget);
- const bool checkable = groupBox && groupBox->isCheckable();
-
- if (checkable) {
- const bool isRadioButtonGroupBox
- =widget && strcmp(widget->metaObject()->className(), "muse::uicomponents::RadioButtonGroupBox") == 0;
-
- int pm = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, option, widget);
- return commonStyleSize + QSize(0, std::max(pm, option->fontMetrics.height()));
- }
-
- return commonStyleSize + QSize(0, option->fontMetrics.height());
- } break;
- case CT_ItemViewItem:
- return commonStyleSize.expandedTo(QSize(20, 20));
- case CT_MenuItem:
- if (const QStyleOptionMenuItem* optionMenuItem = qstyleoption_cast(option)) {
- if (optionMenuItem->menuItemType == QStyleOptionMenuItem::Separator) {
- return proxyStyleSize;
- }
- }
- return proxyStyleSize.expandedTo(QSize(30, 30));
- default:
- break;
- }
-
- return proxyStyleSize;
-}
-
-QIcon ProxyStyle::standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption* option, const QWidget* widget) const
-{
- switch (standardIcon) {
- case SP_DialogOkButton:
- case SP_DialogCancelButton:
- case SP_DialogHelpButton:
- case SP_DialogOpenButton:
- case SP_DialogSaveButton:
- case SP_DialogCloseButton:
- case SP_DialogApplyButton:
- case SP_DialogResetButton:
- case SP_DialogDiscardButton:
- case SP_DialogYesButton:
- case SP_DialogNoButton:
- case SP_DialogYesToAllButton:
- case SP_DialogNoToAllButton:
- case SP_DialogSaveAllButton:
- case SP_DialogAbortButton:
- case SP_DialogRetryButton:
- case SP_DialogIgnoreButton:
- return {};
- default:
- return QProxyStyle::standardIcon(standardIcon, option, widget);
- }
-}
-
-int ProxyStyle::styleHint(QStyle::StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const
-{
- switch (hint) {
- case SH_DitherDisabledText:
- case SH_EtchDisabledText:
- return false;
- case SH_ItemView_ScrollMode:
- return QAbstractItemView::ScrollPerPixel;
- default:
- break;
- }
-
- return QProxyStyle::styleHint(hint, option, widget, returnData);
-}
-
-// ====================================================
-// QStyle elements drawing
-// ====================================================
-
-void ProxyStyle::drawButtonBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool accentButton, bool flat,
- const QColor& defaultBackground) const
-{
- QColor backgroundColor(accentButton ? m_theme->accentColor() : defaultBackground);
-
- if (styleState.enabled) {
- backgroundColor.setAlphaF(styleState.pressed ? m_theme->buttonOpacityHit()
- : styleState.hovered ? m_theme->buttonOpacityHover()
- : !flat ? m_theme->buttonOpacityNormal()
- : 0.0);
-
- if (m_theme->configuration()->isHighContrast()) {
- QColor penBorderColor(m_theme->strokeColor());
- penBorderColor.setAlphaF(styleState.pressed ? m_theme->buttonOpacityHit()
- : styleState.hovered ? m_theme->buttonOpacityHover()
- : m_theme->buttonOpacityNormal());
-
- drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, m_theme->borderWidth()));
- } else {
- drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, NO_BORDER);
- }
- } else {
- backgroundColor.setAlphaF(flat ? 0.0 : m_theme->buttonOpacityNormal() * m_theme->itemOpacityDisabled());
-
- drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, NO_BORDER);
- }
-
- if (styleState.focused) {
- drawRoundedRect(painter, rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
- }
-}
-
-void ProxyStyle::drawCheckboxIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool checked, bool indeterminate,
- bool inMenu) const
-{
- QColor backgroundColor = m_theme->buttonColor();
- const qreal borderRadius = 2;
-
- if (!inMenu) {
- backgroundColor.setAlphaF(!styleState.enabled ? m_theme->buttonOpacityNormal() * m_theme->itemOpacityDisabled()
- : styleState.pressed ? m_theme->buttonOpacityHit()
- : styleState.hovered ? m_theme->buttonOpacityHover()
- : m_theme->buttonOpacityNormal());
-
- QColor penBorderColor(Qt::transparent);
- int penBorderWidth = 0;
- if (m_theme->configuration()->isHighContrast()) {
- penBorderColor = m_theme->strokeColor();
- penBorderWidth = styleState.enabled ? m_theme->borderWidth() : 0;
- } else {
- penBorderWidth = styleState.enabled && (styleState.hovered || styleState.pressed) ? m_theme->borderWidth() : 0;
- }
-
- penBorderColor.setAlphaF(
- styleState.pressed ? m_theme->buttonOpacityHit() : styleState.hovered ? m_theme->buttonOpacityHover() : m_theme->buttonOpacityNormal());
- drawRoundedRect(painter, rect, borderRadius, backgroundColor, QPen(penBorderColor, penBorderWidth));
- }
-
- if (styleState.focused) {
- drawRoundedRect(painter, rect, borderRadius, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
- }
-
- if (checked || indeterminate) {
- QColor tickColor = m_theme->fontPrimaryColor();
- if (!styleState.enabled) {
- tickColor.setAlphaF(m_theme->itemOpacityDisabled());
- }
- painter->setPen(tickColor);
- painter->setFont(m_theme->iconsFont());
- painter->drawText(rect, Qt::AlignCenter,
- iconCodeToChar(indeterminate ? IconCode::Code::MINUS : IconCode::Code::TICK_RIGHT_ANGLE));
- }
-}
-
-void ProxyStyle::drawRadioButtonIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const
-{
- QColor borderColor = m_theme->fontPrimaryColor();
- QColor backgroundColor = m_theme->textFieldColor();
-
- if (!styleState.enabled) {
- borderColor.setAlphaF(m_theme->itemOpacityDisabled());
- } else if (styleState.pressed) {
- borderColor.setAlphaF(m_theme->buttonOpacityHit());
- } else if (styleState.hovered) {
- borderColor.setAlphaF(m_theme->buttonOpacityHover());
- } else {
- borderColor.setAlphaF(m_theme->buttonOpacityNormal());
- }
-
- const int borderWidth = 1;
- qreal outerCircleRadius = 10; // diameter = 20
-
- if (styleState.focused) {
- const qreal focusCircleRadius = outerCircleRadius;
- const QRect focusCircleRect(rect.center() + QPoint(1, 1) - QPoint(focusCircleRadius, focusCircleRadius),
- QSize(focusCircleRadius, focusCircleRadius) * 2);
- drawRoundedRect(painter, focusCircleRect, focusCircleRadius, NO_FILL,
- QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
-
- outerCircleRadius -= m_theme->navCtrlBorderWidth();
- }
-
- const QRect outerCircleRect(rect.center() + QPoint(1, 1) - QPoint(outerCircleRadius, outerCircleRadius),
- QSize(outerCircleRadius, outerCircleRadius) * 2);
- drawRoundedRect(painter, outerCircleRect, outerCircleRadius, backgroundColor, QPen(borderColor, borderWidth));
-
- if (selected || styleState.pressed) {
- QColor centerColor = m_theme->accentColor();
- const int innerCircleRadius = 5; // diameter = 10
- const QRect innerCircleRect(rect.center() + QPoint(1, 1) - QPoint(innerCircleRadius, innerCircleRadius),
- QSize(innerCircleRadius, innerCircleRadius) * 2);
- drawRoundedRect(painter, innerCircleRect, innerCircleRadius, centerColor);
- }
-}
-
-void ProxyStyle::drawLineEditBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool editing) const
-{
- QColor backgroundColor = m_theme->textFieldColor();
- backgroundColor.setAlphaF(!styleState.enabled ? m_theme->itemOpacityDisabled() : (editing ? 1 : (styleState.hovered ? 0.6 : 1)));
-
- if (styleState.focused) {
- drawRoundedRect(painter, rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
- }
-
- QRect editRect = rect.adjusted(m_theme->navCtrlBorderWidth(), m_theme->navCtrlBorderWidth(),
- -m_theme->navCtrlBorderWidth(), -m_theme->navCtrlBorderWidth());
- QColor penBorderColor = editing ? m_theme->accentColor() : m_theme->strokeColor();
- penBorderColor.setAlphaF(editing ? 1 : (styleState.hovered ? 0.6 : 1));
- int borderWidth = m_theme->configuration()->isHighContrast() ? m_theme->borderWidth() : 1;
-
- if (styleState.enabled) {
- drawRoundedRect(painter, editRect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, borderWidth));
- } else {
- drawRoundedRect(painter, editRect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, 1));
- }
-}
-
-void ProxyStyle::drawIndicatorIcon(QPainter* painter, const QRect& rect, const StyleState& styleState,
- QStyle::PrimitiveElement element) const
-{
- QColor color = m_theme->fontPrimaryColor();
- if (!styleState.enabled) {
- color.setAlphaF(m_theme->itemOpacityDisabled());
- }
- painter->setPen(color);
- painter->setFont(m_theme->iconsFont());
-
- IconCode::Code code;
- switch (element) {
- case QStyle::PE_IndicatorSpinPlus:
- code = IconCode::Code::PLUS;
- break;
- case QStyle::PE_IndicatorSpinMinus:
- code = IconCode::Code::MINUS;
- break;
- case QStyle::PE_IndicatorSpinUp:
- code = IconCode::Code::SMALL_ARROW_UP;
- break;
- case QStyle::PE_IndicatorSpinDown:
- code = IconCode::Code::SMALL_ARROW_DOWN;
- break;
- default:
- return;
- }
-
- painter->drawText(rect, Qt::AlignCenter, iconCodeToChar(code));
- drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth())); //does nothing apparently
-}
-
-void ProxyStyle::drawViewItemBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const
-{
- QColor backgroundColor(Qt::transparent);
- if (selected) {
- backgroundColor = m_theme->accentColor();
- backgroundColor.setAlphaF(
- styleState.enabled ? m_theme->accentOpacityHit() : m_theme->accentOpacityHit() * m_theme->itemOpacityDisabled());
- } else if (styleState.enabled && styleState.pressed) {
- backgroundColor = m_theme->buttonColor();
- backgroundColor.setAlphaF(m_theme->buttonOpacityHit());
- } else if (styleState.enabled && styleState.hovered) {
- backgroundColor = m_theme->buttonColor();
- backgroundColor.setAlphaF(m_theme->buttonOpacityHover());
- }
-
- painter->fillRect(rect, backgroundColor);
-
- if (m_theme->configuration()->isHighContrast()) {
- drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
- }
-
- if (styleState.focused) {
- drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
- }
-}
-
-void ProxyStyle::drawToolbarGrip(QPainter* painter, const QRect& rect, bool horizontal) const
-{
- QColor gripColor(m_theme->fontPrimaryColor());
- gripColor.setAlphaF(0.5);
- painter->setPen(gripColor);
- painter->setFont(m_theme->iconsFont());
-
- int rotation = horizontal ? 0 : 90;
- QRect r = horizontal ? rect : QRect(rect.topLeft() + QPoint(0, -rect.width()),
- rect.size().transposed());
- painter->rotate(rotation);
- painter->drawText(r, Qt::AlignCenter, iconCodeToChar(IconCode::Code::TOOLBAR_GRIP));
- painter->rotate(-rotation);
-}
diff --git a/framework/ui/api/themeapi.h b/framework/ui/api/themeapi.h
index 0beb10f533..d301f88bf7 100644
--- a/framework/ui/api/themeapi.h
+++ b/framework/ui/api/themeapi.h
@@ -23,8 +23,6 @@
#pragma once
#include
-#include
-#include
#include
@@ -34,7 +32,6 @@
#include "async/asyncable.h"
namespace muse::api {
-class ProxyStyle;
/** APIDOC
* Access to the theme
* @namespace Theme
@@ -262,10 +259,8 @@ class ThemeApi : public api::ApiObject, public async::Asyncable
public:
ThemeApi(IApiEngine* e);
- ~ThemeApi();
void init();
- void update();
bool isDark() const;
@@ -321,6 +316,7 @@ class ThemeApi : public api::ApiObject, public async::Asyncable
void themeChanged();
private:
+ void update();
void initThemeValues();
@@ -336,8 +332,6 @@ class ThemeApi : public api::ApiObject, public async::Asyncable
void calculateDefaultButtonSize();
- void setupWidgetTheme();
-
void notifyAboutThemeChanged();
QFont m_bodyFont;
@@ -380,48 +374,5 @@ class ThemeApi : public api::ApiObject, public async::Asyncable
qreal m_buttonOpacityHover = 0;
qreal m_buttonOpacityHit = 0;
qreal m_itemOpacityDisabled = 0;
-
- ProxyStyle* m_style = nullptr;
-};
-
-class ProxyStyle : public QProxyStyle
-{
-public:
- ProxyStyle(ThemeApi* t);
-
- void polish(QWidget* widget) override;
- void unpolish(QWidget* widget) override;
-
- void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override;
- void drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter,
- const QWidget* widget = nullptr) const override;
- QRect subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option, QStyle::SubControl subControl,
- const QWidget* widget = nullptr) const override;
- int pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const override;
- QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& contentsSize,
- const QWidget* widget = nullptr) const override;
- QIcon standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption* option = nullptr,
- const QWidget* widget = nullptr) const override;
- int styleHint(StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr,
- QStyleHintReturn* returnData = nullptr) const override;
-private:
- struct StyleState {
- bool enabled = false;
- bool hovered = false;
- bool pressed = false;
- bool focused = false;
- };
-
- void drawButtonBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool accentButton, bool flat,
- const QColor& defaultBackground) const;
- void drawCheckboxIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool checked, bool indeterminate,
- bool inMenu) const;
- void drawRadioButtonIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const;
- void drawLineEditBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool editing) const;
- void drawIndicatorIcon(QPainter* painter, const QRect& rect, const StyleState& styleState, QStyle::PrimitiveElement element) const;
- void drawViewItemBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const;
- void drawToolbarGrip(QPainter* painter, const QRect& rect, bool horizontal) const;
-
- ThemeApi* m_theme = nullptr;
};
}
diff --git a/framework/ui/internal/uiengine.cpp b/framework/ui/internal/uiengine.cpp
index 5d0a8455a8..3f134c129a 100644
--- a/framework/ui/internal/uiengine.cpp
+++ b/framework/ui/internal/uiengine.cpp
@@ -143,15 +143,6 @@ void UiEngine::addSourceImportPath(const QString& path)
#endif
}
-void UiEngine::updateTheme()
-{
- if (!m_engine) {
- return;
- }
-
- theme()->update();
-}
-
QmlApi* UiEngine::api() const
{
return m_api;
diff --git a/framework/ui/internal/uiengine.h b/framework/ui/internal/uiengine.h
index 90c0af519a..6c233379d1 100644
--- a/framework/ui/internal/uiengine.h
+++ b/framework/ui/internal/uiengine.h
@@ -81,7 +81,6 @@ class UiEngine : public QObject, public IUiEngine, public Contextable
Q_INVOKABLE QStringList allTextFonts() const;
// IUiEngine
- void updateTheme() override;
QQmlApplicationEngine* qmlAppEngine() const override;
QQmlEngine* qmlEngine() const override;
void quit() override;
diff --git a/framework/ui/iuiengine.h b/framework/ui/iuiengine.h
index 7945c147d6..43ab1403df 100644
--- a/framework/ui/iuiengine.h
+++ b/framework/ui/iuiengine.h
@@ -41,7 +41,6 @@ class IUiEngine : MODULE_CONTEXT_INTERFACE
public:
virtual ~IUiEngine() = default;
- virtual void updateTheme() = 0;
virtual QQmlApplicationEngine* qmlAppEngine() const = 0;
virtual QQmlEngine* qmlEngine() const = 0;
virtual void quit() = 0;
diff --git a/framework/ui/qml/Muse/Ui/navigationsection.cpp b/framework/ui/qml/Muse/Ui/navigationsection.cpp
index cd4b79dd71..11b7ffbb12 100644
--- a/framework/ui/qml/Muse/Ui/navigationsection.cpp
+++ b/framework/ui/qml/Muse/Ui/navigationsection.cpp
@@ -19,10 +19,10 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
+
#include "navigationsection.h"
#include
-#include
#include "log.h"
diff --git a/framework/ui/uimodule.cpp b/framework/ui/uimodule.cpp
index 317cac3f03..71378d846d 100644
--- a/framework/ui/uimodule.cpp
+++ b/framework/ui/uimodule.cpp
@@ -39,6 +39,7 @@
#include "internal/navigationcontroller.h"
#include "internal/dragcontroller.h"
#include "view/iconcodes.h"
+#include "view/widgetstyle.h"
#ifdef Q_OS_MAC
#include "internal/platform/macos/macosplatformtheme.h"
@@ -77,10 +78,7 @@ void UiModule::registerExports()
{
m_configuration = std::make_shared();
- //! NOTE At the moment, UiTheme is also QProxyStyle
- //! Inside the theme, QApplication::setStyle(this) is calling and the QStyleSheetStyle becomes as parent.
- //! So, the UiTheme will be deleted when will deleted the application (as a child of QStyleSheetStyle).
- m_theme = new api::ThemeApi(nullptr);
+ m_theme = std::make_shared(nullptr);
#ifdef Q_OS_MAC
m_platformTheme = std::make_shared();
@@ -112,7 +110,7 @@ void UiModule::registerApi()
if (api) {
api->regApiCreator(moduleName(), "MuseInternal.Navigation", new ApiCreator());
api->regApiCreator(moduleName(), "MuseInternal.Keyboard", new ApiCreator());
- api->regApiSingltone(moduleName(), "MuseApi.Theme", m_theme);
+ api->regApiSingltone(moduleName(), "MuseApi.Theme", m_theme.get());
qmlRegisterUncreatableMetaObject(IconCode::staticMetaObject, "MuseApi.Controls", 1, 0, "IconCode",
"Not creatable as it is an enum type");
@@ -142,6 +140,9 @@ void UiModule::onAllInited(const IApplication::RunMode& mode)
}
m_theme->init();
+
+ m_widgetStyle = new WidgetStyle(m_theme); // becomes owned by QApplication
+ m_widgetStyle->init();
}
void UiModule::onDeinit()
diff --git a/framework/ui/uimodule.h b/framework/ui/uimodule.h
index e8a69363df..c398bf6487 100644
--- a/framework/ui/uimodule.h
+++ b/framework/ui/uimodule.h
@@ -31,6 +31,8 @@ class ThemeApi;
namespace muse::ui {
class UiConfiguration;
+class WidgetStyle;
+
#ifdef Q_OS_MAC
class MacOSPlatformTheme;
#elif defined(Q_OS_WIN)
@@ -58,7 +60,8 @@ class UiModule : public modularity::IModuleSetup
private:
std::shared_ptr m_configuration;
- api::ThemeApi* m_theme = nullptr;
+ std::shared_ptr m_theme;
+ WidgetStyle* m_widgetStyle = nullptr; // becomes owned by QApplication
#ifdef Q_OS_MAC
std::shared_ptr m_platformTheme;
diff --git a/framework/ui/view/widgetstyle.cpp b/framework/ui/view/widgetstyle.cpp
new file mode 100644
index 0000000000..e4e4f7848f
--- /dev/null
+++ b/framework/ui/view/widgetstyle.cpp
@@ -0,0 +1,745 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-only
+ * MuseScore-Studio-CLA-applies
+ *
+ * MuseScore Studio
+ * Music Composition & Notation
+ *
+ * Copyright (C) 2026 MuseScore Limited and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "widgetstyle.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "api/themeapi.h"
+#include "iconcodes.h"
+
+#include "log.h"
+
+using namespace muse::ui;
+using muse::api::ThemeApi;
+
+static const QPen NO_BORDER(Qt::transparent, 0);
+static const QBrush NO_FILL(Qt::transparent);
+static const int DEFAULT_RADIUS = 3;
+
+static const int GROUP_BOX_LABEL_SPACING = 2;
+
+//! In QML, a border is drawn _inside_ a rectangle.
+//! In C++, a border would normally be drawn half inside the rectangle, half outside.
+//! In this function, we exactly replicate the behaviour from QML.
+static void drawRoundedRect(QPainter* painter, const QRectF& rect, const qreal radius, const QBrush& brush = NO_FILL,
+ const QPen& pen = NO_BORDER)
+{
+ IF_ASSERT_FAILED(painter) {
+ return;
+ }
+
+ const qreal bw = pen.width();
+
+ if (bw <= 0 || pen.color().alpha() == 0) {
+ if (brush == NO_FILL) {
+ return;
+ }
+
+ painter->save();
+ painter->setPen(NO_BORDER);
+ painter->setBrush(brush);
+ painter->drawRoundedRect(rect, radius, radius);
+ painter->restore();
+ return;
+ }
+
+ painter->save();
+ painter->setPen(NO_BORDER);
+ painter->setBrush(brush);
+ painter->drawRoundedRect(rect.adjusted(bw, bw, -bw, -bw), radius - bw, radius - bw);
+
+ const qreal corr = 0.5 * bw;
+ painter->setPen(pen);
+ painter->setBrush(NO_FILL);
+ painter->drawRoundedRect(rect.adjusted(corr, corr, -corr, -corr), radius - corr, radius - corr);
+ painter->restore();
+}
+
+WidgetStyle::WidgetStyle(std::shared_ptr t)
+ : QProxyStyle(QStyleFactory::create("Fusion")), m_theme(t)
+{
+}
+
+void WidgetStyle::init()
+{
+ connect(m_theme.get(), &ThemeApi::themeChanged, this, &WidgetStyle::update);
+
+ update();
+}
+
+void WidgetStyle::update()
+{
+ QColor fontPrimaryColorDisabled = m_theme->fontPrimaryColor();
+ fontPrimaryColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
+
+ QColor linkColorDisabled = m_theme->linkColor();
+ linkColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
+
+ QColor backgroundPrimaryColorDisabled = m_theme->backgroundPrimaryColor();
+ backgroundPrimaryColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
+
+ QColor backgroundSecondaryColorDisabled = m_theme->backgroundSecondaryColor();
+ backgroundSecondaryColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
+
+ QColor buttonColorDisabled = m_theme->buttonColor();
+ buttonColorDisabled.setAlphaF(m_theme->itemOpacityDisabled());
+
+ QPalette palette(QApplication::palette());
+ palette.setColor(QPalette::Window, m_theme->backgroundPrimaryColor());
+ palette.setColor(QPalette::Disabled, QPalette::Window, backgroundPrimaryColorDisabled);
+ palette.setColor(QPalette::WindowText, m_theme->fontPrimaryColor());
+ palette.setColor(QPalette::Disabled, QPalette::WindowText, fontPrimaryColorDisabled);
+
+ palette.setColor(QPalette::Base, m_theme->backgroundSecondaryColor());
+ palette.setColor(QPalette::Disabled, QPalette::Base, backgroundSecondaryColorDisabled);
+ palette.setColor(QPalette::AlternateBase, m_theme->backgroundSecondaryColor());
+ palette.setColor(QPalette::Disabled, QPalette::AlternateBase, backgroundSecondaryColorDisabled);
+
+ palette.setColor(QPalette::Text, m_theme->fontPrimaryColor());
+ palette.setColor(QPalette::Disabled, QPalette::Text, fontPrimaryColorDisabled);
+
+ palette.setColor(QPalette::Link, m_theme->linkColor());
+ palette.setColor(QPalette::Disabled, QPalette::Link, linkColorDisabled);
+
+ palette.setColor(QPalette::Button, m_theme->buttonColor());
+ palette.setColor(QPalette::Disabled, QPalette::Button, buttonColorDisabled);
+ palette.setColor(QPalette::ButtonText, m_theme->fontPrimaryColor());
+ palette.setColor(QPalette::Disabled, QPalette::ButtonText, fontPrimaryColorDisabled);
+
+ palette.setColor(QPalette::ToolTipBase, m_theme->popupBackgroundColor());
+ palette.setColor(QPalette::ToolTipText, m_theme->fontPrimaryColor());
+
+ palette.setColor(QPalette::Highlight, m_theme->accentColor());
+ palette.setColor(QPalette::HighlightedText, m_theme->fontPrimaryColor());
+
+ palette.setColor(QPalette::PlaceholderText, m_theme->fontPrimaryColor());
+ palette.setColor(QPalette::Disabled, QPalette::PlaceholderText, fontPrimaryColorDisabled);
+
+ QApplication::setStyle(this);
+ QApplication::setPalette(palette);
+
+ QFont font = m_theme->bodyFont();
+ QString styleSheet = QString("* { font: %1px \"%2\" } ")
+ .arg(QString::number(font.pixelSize()), font.family());
+ qApp->setStyleSheet(styleSheet);
+}
+
+void WidgetStyle::polish(QWidget* widget)
+{
+ QProxyStyle::polish(widget);
+
+ if (qobject_cast(widget)
+ || qobject_cast(widget)
+ || qobject_cast(widget)) {
+ // Make hovering work
+ widget->setMouseTracking(true);
+ widget->setAttribute(Qt::WA_Hover, true);
+ }
+}
+
+void WidgetStyle::unpolish(QWidget* widget)
+{
+ QProxyStyle::unpolish(widget);
+
+ if (qobject_cast(widget)
+ || qobject_cast(widget)
+ || qobject_cast(widget)) {
+ widget->setMouseTracking(false);
+ widget->setAttribute(Qt::WA_Hover, false);
+ }
+}
+
+void WidgetStyle::drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter,
+ const QWidget* widget) const
+{
+ StyleState styleState;
+ styleState.enabled = option->state & State_Enabled;
+ styleState.hovered = option->state & State_MouseOver;
+ styleState.pressed = option->state & State_Sunken;
+ styleState.focused = (option->state & State_KeyboardFocusChange) && (option->state & State_HasFocus);
+
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing, true);
+
+ //! NOTE This drawing code is based on the implementation in QML.
+ switch (element) {
+ // Buttons (and ComboBoxes)
+ case QStyle::PE_PanelButtonCommand: {
+ auto buttonOption = qstyleoption_cast(option);
+ const bool accentButton = (buttonOption && buttonOption->features & QStyleOptionButton::DefaultButton)
+ || option->state & State_On;
+ const bool flat = (buttonOption && buttonOption->features & QStyleOptionButton::Flat)
+ && !(option->state & State_On);
+
+ QColor paletteColor = widget ? widget->palette().color(QPalette::Button) : QColor();
+ const QColor background = paletteColor.isValid() ? paletteColor : m_theme->buttonColor();
+
+ drawButtonBackground(painter, option->rect, styleState, accentButton, flat, background);
+ } break;
+ case QStyle::PE_FrameDefaultButton: {
+ auto buttonOption = qstyleoption_cast(option);
+ const bool flat = (buttonOption && buttonOption->features & QStyleOptionButton::Flat)
+ && !(option->state & State_On);
+ if (flat && styleState.focused) {
+ // For other buttons, this is done in `drawButtonBackground`, but that is not called for flat buttons
+ drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL,
+ QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+ }
+ } break;
+
+ // Checkboxes
+ case QStyle::PE_IndicatorCheckBox: {
+ const bool indeterminate = option->state & State_NoChange;
+ const bool checked = option->state & State_On;
+ const bool inMenu = qobject_cast(widget);
+
+ drawCheckboxIndicator(painter, option->rect, styleState, checked, indeterminate, inMenu);
+ } break;
+
+ // Radio buttons
+ case QStyle::PE_IndicatorRadioButton: {
+ const bool selected = option->state & State_On;
+
+ drawRadioButtonIndicator(painter, option->rect, styleState, selected);
+ } break;
+
+ case QStyle::PE_FrameLineEdit: {
+ const bool editing = option->state & State_HasFocus;
+
+ drawLineEditBackground(painter, option->rect, styleState, editing);
+ } break;
+
+ case QStyle::PE_FrameFocusRect: {
+ bool isTreeWidget = option->styleObject && option->styleObject->inherits("QTreeWidget");
+ if (isTreeWidget) {
+ drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+ }
+
+ //! NOTE: need for removing frame focus rectangle
+ } break;
+
+ // Indicator icons
+ case QStyle::PE_IndicatorSpinUp:
+ case QStyle::PE_IndicatorSpinDown:
+ case QStyle::PE_IndicatorSpinPlus:
+ case QStyle::PE_IndicatorSpinMinus: {
+ drawIndicatorIcon(painter, option->rect, styleState, element);
+ } break;
+
+ // ViewItem
+ case QStyle::PE_PanelItemViewItem: {
+ bool selected = option->state & State_Selected;
+
+ drawViewItemBackground(painter, option->rect, styleState, selected);
+ } break;
+
+ // Toolbar
+ case QStyle::PE_PanelToolBar: {
+ painter->fillRect(option->rect, m_theme->backgroundPrimaryColor());
+ } break;
+ case QStyle::PE_IndicatorToolBarHandle: {
+ drawToolbarGrip(painter, option->rect, option->state & State_Horizontal);
+ } break;
+
+ // GroupBox
+ case QStyle::PE_FrameGroupBox: {
+ drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, QBrush(QColor::fromRgba(0x03000000)),
+ QPen(m_theme->strokeColor(), fmax(m_theme->borderWidth(), 1.0)));
+ } break;
+
+ // Menu
+ case QStyle::PE_PanelMenu: {
+ drawRoundedRect(painter, option->rect, 1, m_theme->backgroundPrimaryColor(), QPen(m_theme->strokeColor(), m_theme->borderWidth()));
+ } break;
+ case QStyle::PE_FrameMenu: {
+ drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
+ } break;
+
+ case QStyle::PE_Frame: {
+ if (qobject_cast(widget) != nullptr) {
+ if (styleState.enabled) {
+ drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
+ } else {
+ QColor penBorderColor = m_theme->strokeColor();
+ penBorderColor.setAlphaF(m_theme->itemOpacityDisabled());
+ drawRoundedRect(painter, option->rect, DEFAULT_RADIUS, NO_FILL, QPen(penBorderColor, m_theme->borderWidth()));
+ }
+ }
+ } break;
+
+ default:
+ QProxyStyle::drawPrimitive(element, option, painter, widget);
+ break;
+ }
+
+ painter->restore();
+}
+
+void WidgetStyle::drawComplexControl(QStyle::ComplexControl control, const QStyleOptionComplex* option, QPainter* painter,
+ const QWidget* widget) const
+{
+ StyleState styleState;
+ styleState.enabled = option->state & State_Enabled;
+ styleState.hovered = option->state & State_MouseOver;
+ styleState.pressed = option->state & State_Sunken;
+ styleState.focused = (option->state & State_KeyboardFocusChange) && (option->state & State_HasFocus);
+
+ switch (control) {
+ case CC_ScrollBar: {
+ QProxyStyle::drawComplexControl(control, option, painter, widget);
+
+ if (m_theme->configuration()->isHighContrast()) {
+ QRect scrollBarHandle = QProxyStyle::subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
+
+ QColor handleColor = m_theme->fontPrimaryColor();
+ handleColor.setAlphaF(
+ !styleState.enabled ? m_theme->buttonOpacityNormal()
+ * m_theme->itemOpacityDisabled() : styleState.pressed ? m_theme->buttonOpacityHit() : styleState.hovered ? m_theme->buttonOpacityHover() : m_theme->buttonOpacityNormal());
+
+ drawRoundedRect(painter, option->rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
+ drawRoundedRect(painter, scrollBarHandle, 1, handleColor, NO_BORDER);
+ }
+ } break;
+
+ case CC_SpinBox: {
+ QProxyStyle::drawComplexControl(control, option, painter, widget);
+
+ QRect spinBoxFrame = QProxyStyle::subControlRect(CC_SpinBox, option, SC_SpinBoxFrame, widget);
+ if (styleState.focused) {
+ drawRoundedRect(painter, spinBoxFrame, DEFAULT_RADIUS, NO_FILL,
+ QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+ }
+ } break;
+
+ default: QProxyStyle::drawComplexControl(control, option, painter, widget);
+ }
+}
+
+QRect WidgetStyle::subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option, QStyle::SubControl subControl,
+ const QWidget* widget) const
+{
+ //QRect commonStyleRect = QCommonStyle::subControlRect(control, option, subControl, widget);
+ QRect proxyStyleRect = QProxyStyle::subControlRect(control, option, subControl, widget);
+
+ switch (control) {
+ case QStyle::CC_GroupBox:
+ if (const QStyleOptionGroupBox* optionGroupBox = qstyleoption_cast(option)) {
+ int indicatorWidth = 0;
+ int indicatorHeight = 0;
+ int indicatorSpacing = 0;
+ const QSize textSize = option->fontMetrics.boundingRect(optionGroupBox->text).size() + QSize(2, 2);
+
+ const bool checkable = option->subControls & QStyle::SC_GroupBoxCheckBox;
+
+ if (checkable) {
+ const bool isRadioButtonGroupBox
+ =widget && strcmp(widget->metaObject()->className(), "muse::uicomponents::RadioButtonGroupBox") == 0;
+
+ indicatorWidth = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorWidth : PM_IndicatorWidth, option, widget);
+ indicatorHeight = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, option, widget);
+ indicatorSpacing
+ = pixelMetric(isRadioButtonGroupBox ? PM_RadioButtonLabelSpacing : PM_CheckBoxLabelSpacing, option, widget);
+ }
+
+ if (subControl == SC_GroupBoxFrame) {
+ int topMargin = std::max(indicatorHeight, textSize.height()) + GROUP_BOX_LABEL_SPACING;
+ return option->rect.adjusted(0, topMargin, 0, 0);
+ }
+
+ if (subControl == SC_GroupBoxContents) {
+ int margin = 3;
+ int topMargin = margin + std::max(indicatorHeight, textSize.height()) + GROUP_BOX_LABEL_SPACING;
+ return option->rect.adjusted(margin, topMargin, -margin, -margin);
+ }
+
+ const int width = textSize.width() + (checkable ? indicatorWidth + indicatorSpacing : 0);
+ QRect rect;
+
+ if (option->rect.width() > width) {
+ switch (optionGroupBox->textAlignment & Qt::AlignHorizontal_Mask) {
+ case Qt::AlignHCenter:
+ rect.moveLeft((option->rect.width() - width) / 2);
+ break;
+ case Qt::AlignRight:
+ rect.moveLeft(option->rect.width() - width);
+ break;
+ }
+ }
+
+ if (subControl == SC_GroupBoxCheckBox) {
+ rect.setWidth(indicatorWidth);
+ rect.setHeight(indicatorHeight);
+ rect.moveTop(textSize.height() > indicatorHeight ? (textSize.height() - indicatorHeight) / 2 : 0);
+ } else if (subControl == SC_GroupBoxLabel) {
+ rect.setSize(textSize);
+ if (checkable) {
+ rect.moveTop(textSize.height() < indicatorHeight ? (indicatorHeight - textSize.height()) / 2 : 0);
+ rect.translate(indicatorWidth + indicatorSpacing, 0);
+ }
+ }
+
+ return visualRect(option->direction, option->rect, rect);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return proxyStyleRect;
+}
+
+int WidgetStyle::pixelMetric(QStyle::PixelMetric metric, const QStyleOption* option, const QWidget* widget) const
+{
+ //! NOTE These metrics are based on the implementation in QML.
+ switch (metric) {
+ case PM_IndicatorWidth: // Checkbox
+ case PM_IndicatorHeight:
+ case PM_ExclusiveIndicatorWidth: // Radio button
+ case PM_ExclusiveIndicatorHeight:
+ return 20;
+ case PM_CheckBoxLabelSpacing: // Checkbox
+ case PM_RadioButtonLabelSpacing: // Radio button
+ return 6;
+ case PM_ToolBarHandleExtent: // Toolbars
+ return 32;
+ default:
+ break;
+ }
+
+ return QProxyStyle::pixelMetric(metric, option, widget);
+}
+
+QSize WidgetStyle::sizeFromContents(QStyle::ContentsType type, const QStyleOption* option, const QSize& contentsSize,
+ const QWidget* widget) const
+{
+ QSize commonStyleSize = QCommonStyle::sizeFromContents(type, option, contentsSize, widget);
+ QSize proxyStyleSize = QProxyStyle::sizeFromContents(type, option, contentsSize, widget);
+
+ //! NOTE These calculations are based on the implementation in QML.
+ switch (type) {
+ case CT_PushButton:
+ return QSize(std::max(contentsSize.width() + 32, 132),
+ contentsSize.height() + 14);
+ case CT_ToolButton:
+ return contentsSize.expandedTo(QSize(30, 30));
+ case CT_ComboBox:
+ case CT_LineEdit:
+ return proxyStyleSize.expandedTo(QSize(30, 30));
+ case CT_SpinBox:
+ return QSize(proxyStyleSize.width(), 26); // results in a height of 30
+ case CT_GroupBox: {
+ const QGroupBox* groupBox = qobject_cast(widget);
+ const bool checkable = groupBox && groupBox->isCheckable();
+
+ if (checkable) {
+ const bool isRadioButtonGroupBox
+ =widget && strcmp(widget->metaObject()->className(), "muse::uicomponents::RadioButtonGroupBox") == 0;
+
+ int pm = pixelMetric(isRadioButtonGroupBox ? PM_ExclusiveIndicatorHeight : PM_IndicatorHeight, option, widget);
+ return commonStyleSize + QSize(0, std::max(pm, option->fontMetrics.height()));
+ }
+
+ return commonStyleSize + QSize(0, option->fontMetrics.height());
+ } break;
+ case CT_ItemViewItem:
+ return commonStyleSize.expandedTo(QSize(20, 20));
+ case CT_MenuItem:
+ if (const QStyleOptionMenuItem* optionMenuItem = qstyleoption_cast(option)) {
+ if (optionMenuItem->menuItemType == QStyleOptionMenuItem::Separator) {
+ return proxyStyleSize;
+ }
+ }
+ return proxyStyleSize.expandedTo(QSize(30, 30));
+ default:
+ break;
+ }
+
+ return proxyStyleSize;
+}
+
+QIcon WidgetStyle::standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption* option, const QWidget* widget) const
+{
+ switch (standardIcon) {
+ case SP_DialogOkButton:
+ case SP_DialogCancelButton:
+ case SP_DialogHelpButton:
+ case SP_DialogOpenButton:
+ case SP_DialogSaveButton:
+ case SP_DialogCloseButton:
+ case SP_DialogApplyButton:
+ case SP_DialogResetButton:
+ case SP_DialogDiscardButton:
+ case SP_DialogYesButton:
+ case SP_DialogNoButton:
+ case SP_DialogYesToAllButton:
+ case SP_DialogNoToAllButton:
+ case SP_DialogSaveAllButton:
+ case SP_DialogAbortButton:
+ case SP_DialogRetryButton:
+ case SP_DialogIgnoreButton:
+ return {};
+ default:
+ return QProxyStyle::standardIcon(standardIcon, option, widget);
+ }
+}
+
+int WidgetStyle::styleHint(QStyle::StyleHint hint, const QStyleOption* option, const QWidget* widget, QStyleHintReturn* returnData) const
+{
+ switch (hint) {
+ case SH_DitherDisabledText:
+ case SH_EtchDisabledText:
+ return false;
+ case SH_ItemView_ScrollMode:
+ return QAbstractItemView::ScrollPerPixel;
+ default:
+ break;
+ }
+
+ return QProxyStyle::styleHint(hint, option, widget, returnData);
+}
+
+// ====================================================
+// QStyle elements drawing
+// ====================================================
+
+void WidgetStyle::drawButtonBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool accentButton, bool flat,
+ const QColor& defaultBackground) const
+{
+ QColor backgroundColor(accentButton ? m_theme->accentColor() : defaultBackground);
+
+ if (styleState.enabled) {
+ backgroundColor.setAlphaF(styleState.pressed ? m_theme->buttonOpacityHit()
+ : styleState.hovered ? m_theme->buttonOpacityHover()
+ : !flat ? m_theme->buttonOpacityNormal()
+ : 0.0);
+
+ if (m_theme->configuration()->isHighContrast()) {
+ QColor penBorderColor(m_theme->strokeColor());
+ penBorderColor.setAlphaF(styleState.pressed ? m_theme->buttonOpacityHit()
+ : styleState.hovered ? m_theme->buttonOpacityHover()
+ : m_theme->buttonOpacityNormal());
+
+ drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, m_theme->borderWidth()));
+ } else {
+ drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, NO_BORDER);
+ }
+ } else {
+ backgroundColor.setAlphaF(flat ? 0.0 : m_theme->buttonOpacityNormal() * m_theme->itemOpacityDisabled());
+
+ drawRoundedRect(painter, rect, DEFAULT_RADIUS, backgroundColor, NO_BORDER);
+ }
+
+ if (styleState.focused) {
+ drawRoundedRect(painter, rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+ }
+}
+
+void WidgetStyle::drawCheckboxIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool checked,
+ bool indeterminate,
+ bool inMenu) const
+{
+ QColor backgroundColor = m_theme->buttonColor();
+ const qreal borderRadius = 2;
+
+ if (!inMenu) {
+ backgroundColor.setAlphaF(!styleState.enabled ? m_theme->buttonOpacityNormal() * m_theme->itemOpacityDisabled()
+ : styleState.pressed ? m_theme->buttonOpacityHit()
+ : styleState.hovered ? m_theme->buttonOpacityHover()
+ : m_theme->buttonOpacityNormal());
+
+ QColor penBorderColor(Qt::transparent);
+ int penBorderWidth = 0;
+ if (m_theme->configuration()->isHighContrast()) {
+ penBorderColor = m_theme->strokeColor();
+ penBorderWidth = styleState.enabled ? m_theme->borderWidth() : 0;
+ } else {
+ penBorderWidth = styleState.enabled && (styleState.hovered || styleState.pressed) ? m_theme->borderWidth() : 0;
+ }
+
+ penBorderColor.setAlphaF(
+ styleState.pressed ? m_theme->buttonOpacityHit() : styleState.hovered ? m_theme->buttonOpacityHover() : m_theme->buttonOpacityNormal());
+ drawRoundedRect(painter, rect, borderRadius, backgroundColor, QPen(penBorderColor, penBorderWidth));
+ }
+
+ if (styleState.focused) {
+ drawRoundedRect(painter, rect, borderRadius, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+ }
+
+ if (checked || indeterminate) {
+ QColor tickColor = m_theme->fontPrimaryColor();
+ if (!styleState.enabled) {
+ tickColor.setAlphaF(m_theme->itemOpacityDisabled());
+ }
+ painter->setPen(tickColor);
+ painter->setFont(m_theme->iconsFont());
+ painter->drawText(rect, Qt::AlignCenter,
+ iconCodeToChar(indeterminate ? IconCode::Code::MINUS : IconCode::Code::TICK_RIGHT_ANGLE));
+ }
+}
+
+void WidgetStyle::drawRadioButtonIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const
+{
+ QColor borderColor = m_theme->fontPrimaryColor();
+ QColor backgroundColor = m_theme->textFieldColor();
+
+ if (!styleState.enabled) {
+ borderColor.setAlphaF(m_theme->itemOpacityDisabled());
+ } else if (styleState.pressed) {
+ borderColor.setAlphaF(m_theme->buttonOpacityHit());
+ } else if (styleState.hovered) {
+ borderColor.setAlphaF(m_theme->buttonOpacityHover());
+ } else {
+ borderColor.setAlphaF(m_theme->buttonOpacityNormal());
+ }
+
+ const int borderWidth = 1;
+ qreal outerCircleRadius = 10; // diameter = 20
+
+ if (styleState.focused) {
+ const qreal focusCircleRadius = outerCircleRadius;
+ const QRect focusCircleRect(rect.center() + QPoint(1, 1) - QPoint(focusCircleRadius, focusCircleRadius),
+ QSize(focusCircleRadius, focusCircleRadius) * 2);
+ drawRoundedRect(painter, focusCircleRect, focusCircleRadius, NO_FILL,
+ QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+
+ outerCircleRadius -= m_theme->navCtrlBorderWidth();
+ }
+
+ const QRect outerCircleRect(rect.center() + QPoint(1, 1) - QPoint(outerCircleRadius, outerCircleRadius),
+ QSize(outerCircleRadius, outerCircleRadius) * 2);
+ drawRoundedRect(painter, outerCircleRect, outerCircleRadius, backgroundColor, QPen(borderColor, borderWidth));
+
+ if (selected || styleState.pressed) {
+ QColor centerColor = m_theme->accentColor();
+ const int innerCircleRadius = 5; // diameter = 10
+ const QRect innerCircleRect(rect.center() + QPoint(1, 1) - QPoint(innerCircleRadius, innerCircleRadius),
+ QSize(innerCircleRadius, innerCircleRadius) * 2);
+ drawRoundedRect(painter, innerCircleRect, innerCircleRadius, centerColor);
+ }
+}
+
+void WidgetStyle::drawLineEditBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool editing) const
+{
+ QColor backgroundColor = m_theme->textFieldColor();
+ backgroundColor.setAlphaF(!styleState.enabled ? m_theme->itemOpacityDisabled() : (editing ? 1 : (styleState.hovered ? 0.6 : 1)));
+
+ if (styleState.focused) {
+ drawRoundedRect(painter, rect, DEFAULT_RADIUS, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+ }
+
+ QRect editRect = rect.adjusted(m_theme->navCtrlBorderWidth(), m_theme->navCtrlBorderWidth(),
+ -m_theme->navCtrlBorderWidth(), -m_theme->navCtrlBorderWidth());
+ QColor penBorderColor = editing ? m_theme->accentColor() : m_theme->strokeColor();
+ penBorderColor.setAlphaF(editing ? 1 : (styleState.hovered ? 0.6 : 1));
+ int borderWidth = m_theme->configuration()->isHighContrast() ? m_theme->borderWidth() : 1;
+
+ if (styleState.enabled) {
+ drawRoundedRect(painter, editRect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, borderWidth));
+ } else {
+ drawRoundedRect(painter, editRect, DEFAULT_RADIUS, backgroundColor, QPen(penBorderColor, 1));
+ }
+}
+
+void WidgetStyle::drawIndicatorIcon(QPainter* painter, const QRect& rect, const StyleState& styleState,
+ QStyle::PrimitiveElement element) const
+{
+ QColor color = m_theme->fontPrimaryColor();
+ if (!styleState.enabled) {
+ color.setAlphaF(m_theme->itemOpacityDisabled());
+ }
+ painter->setPen(color);
+ painter->setFont(m_theme->iconsFont());
+
+ IconCode::Code code;
+ switch (element) {
+ case QStyle::PE_IndicatorSpinPlus:
+ code = IconCode::Code::PLUS;
+ break;
+ case QStyle::PE_IndicatorSpinMinus:
+ code = IconCode::Code::MINUS;
+ break;
+ case QStyle::PE_IndicatorSpinUp:
+ code = IconCode::Code::SMALL_ARROW_UP;
+ break;
+ case QStyle::PE_IndicatorSpinDown:
+ code = IconCode::Code::SMALL_ARROW_DOWN;
+ break;
+ default:
+ return;
+ }
+
+ painter->drawText(rect, Qt::AlignCenter, iconCodeToChar(code));
+ drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth())); //does nothing apparently
+}
+
+void WidgetStyle::drawViewItemBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const
+{
+ QColor backgroundColor(Qt::transparent);
+ if (selected) {
+ backgroundColor = m_theme->accentColor();
+ backgroundColor.setAlphaF(
+ styleState.enabled ? m_theme->accentOpacityHit() : m_theme->accentOpacityHit() * m_theme->itemOpacityDisabled());
+ } else if (styleState.enabled && styleState.pressed) {
+ backgroundColor = m_theme->buttonColor();
+ backgroundColor.setAlphaF(m_theme->buttonOpacityHit());
+ } else if (styleState.enabled && styleState.hovered) {
+ backgroundColor = m_theme->buttonColor();
+ backgroundColor.setAlphaF(m_theme->buttonOpacityHover());
+ }
+
+ painter->fillRect(rect, backgroundColor);
+
+ if (m_theme->configuration()->isHighContrast()) {
+ drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->strokeColor(), m_theme->borderWidth()));
+ }
+
+ if (styleState.focused) {
+ drawRoundedRect(painter, rect, 1, NO_FILL, QPen(m_theme->fontPrimaryColor(), m_theme->navCtrlBorderWidth()));
+ }
+}
+
+void WidgetStyle::drawToolbarGrip(QPainter* painter, const QRect& rect, bool horizontal) const
+{
+ QColor gripColor(m_theme->fontPrimaryColor());
+ gripColor.setAlphaF(0.5);
+ painter->setPen(gripColor);
+ painter->setFont(m_theme->iconsFont());
+
+ int rotation = horizontal ? 0 : 90;
+ QRect r = horizontal ? rect : QRect(rect.topLeft() + QPoint(0, -rect.width()),
+ rect.size().transposed());
+ painter->rotate(rotation);
+ painter->drawText(r, Qt::AlignCenter, iconCodeToChar(IconCode::Code::TOOLBAR_GRIP));
+ painter->rotate(-rotation);
+}
diff --git a/framework/ui/view/widgetstyle.h b/framework/ui/view/widgetstyle.h
new file mode 100644
index 0000000000..cb02eed5cf
--- /dev/null
+++ b/framework/ui/view/widgetstyle.h
@@ -0,0 +1,80 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-only
+ * MuseScore-Studio-CLA-applies
+ *
+ * MuseScore Studio
+ * Music Composition & Notation
+ *
+ * Copyright (C) 2026 MuseScore Limited and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include
+
+#include
+
+class QWidget;
+
+namespace muse::api {
+class ThemeApi;
+}
+
+namespace muse::ui {
+class WidgetStyle : public QProxyStyle
+{
+public:
+ WidgetStyle(std::shared_ptr t);
+
+ void init();
+
+ void polish(QWidget* widget) override;
+ void unpolish(QWidget* widget) override;
+
+ void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, const QWidget* widget) const override;
+ void drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter,
+ const QWidget* widget = nullptr) const override;
+ QRect subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex* option, QStyle::SubControl subControl,
+ const QWidget* widget = nullptr) const override;
+ int pixelMetric(PixelMetric metric, const QStyleOption* option, const QWidget* widget) const override;
+ QSize sizeFromContents(ContentsType type, const QStyleOption* option, const QSize& contentsSize,
+ const QWidget* widget = nullptr) const override;
+ QIcon standardIcon(QStyle::StandardPixmap standardIcon, const QStyleOption* option = nullptr,
+ const QWidget* widget = nullptr) const override;
+ int styleHint(StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr,
+ QStyleHintReturn* returnData = nullptr) const override;
+private:
+ struct StyleState {
+ bool enabled = false;
+ bool hovered = false;
+ bool pressed = false;
+ bool focused = false;
+ };
+
+ void update();
+
+ void drawButtonBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool accentButton, bool flat,
+ const QColor& defaultBackground) const;
+ void drawCheckboxIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool checked, bool indeterminate,
+ bool inMenu) const;
+ void drawRadioButtonIndicator(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const;
+ void drawLineEditBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool editing) const;
+ void drawIndicatorIcon(QPainter* painter, const QRect& rect, const StyleState& styleState, QStyle::PrimitiveElement element) const;
+ void drawViewItemBackground(QPainter* painter, const QRect& rect, const StyleState& styleState, bool selected) const;
+ void drawToolbarGrip(QPainter* painter, const QRect& rect, bool horizontal) const;
+
+ std::shared_ptr m_theme;
+};
+}
diff --git a/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt b/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
index 6364f3b897..d42c683ed1 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
+++ b/framework/uicomponents/qml/Muse/UiComponents/CMakeLists.txt
@@ -238,6 +238,11 @@ endif()
# Necessary for the auto-generated sources
target_include_directories(muse_uicomponents_qml PRIVATE internal validators)
+target_link_libraries(muse_uicomponents_qml PRIVATE Qt::Qml)
+
+# For widgetview.cpp
+target_link_libraries(muse_uicomponents_qml PRIVATE Qt::Widgets)
+
if (MUSE_MODULE_UI_TESTS)
add_subdirectory(tests)
endif()
diff --git a/framework/uicomponents/qml/Muse/UiComponents/dialogview.cpp b/framework/uicomponents/qml/Muse/UiComponents/dialogview.cpp
index 4345d625f1..fa8f1adf77 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/dialogview.cpp
+++ b/framework/uicomponents/qml/Muse/UiComponents/dialogview.cpp
@@ -22,10 +22,8 @@
#include "dialogview.h"
-#include
#include
#include
-#include
#ifdef Q_OS_MAC
#include "internal/platform/macos/macoswindowlevelcontroller.h"
@@ -37,8 +35,6 @@ using namespace Qt::Literals::StringLiterals;
using namespace muse::uicomponents;
-static const int DIALOG_WINDOW_FRAME_HEIGHT(20);
-
DialogView::DialogView(QQuickItem* parent)
: WindowView(parent)
{
@@ -127,42 +123,39 @@ void DialogView::updateGeometry()
referenceRect = anchorRect;
}
- QRect dlgRect = viewGeometry();
+ QMargins frameMargins = m_view->frameMargins(); // actual window frame margins (title bar height etc.)
+ QRect dlgRect = viewGeometry(); // content geometry
+ dlgRect = dlgRect.marginsAdded(frameMargins);
// position the dialog in the center of the main window
dlgRect.moveLeft(referenceRect.x() + (referenceRect.width() - dlgRect.width()) / 2);
- dlgRect.moveTop(referenceRect.y() + (referenceRect.height() - dlgRect.height()) / 2 + DIALOG_WINDOW_FRAME_HEIGHT);
-
- dlgRect.moveLeft(dlgRect.x());
- dlgRect.moveTop(dlgRect.y());
+ dlgRect.moveTop(referenceRect.y() + (referenceRect.height() - dlgRect.height()) / 2);
// try to move the dialog if it doesn't fit on the screen
- int titleBarHeight = QApplication::style()->pixelMetric(QStyle::PM_TitleBarHeight);
-
if (dlgRect.left() <= anchorRect.left()) {
- dlgRect.moveLeft(anchorRect.left() + DIALOG_WINDOW_FRAME_HEIGHT);
+ dlgRect.moveLeft(anchorRect.left());
}
- if (dlgRect.top() - titleBarHeight <= anchorRect.top()) {
- dlgRect.moveTop(anchorRect.top() + titleBarHeight + DIALOG_WINDOW_FRAME_HEIGHT);
+ if (dlgRect.top() <= anchorRect.top()) {
+ dlgRect.moveTop(anchorRect.top());
}
if (dlgRect.right() >= anchorRect.right()) {
- dlgRect.moveRight(anchorRect.right() - DIALOG_WINDOW_FRAME_HEIGHT);
+ dlgRect.moveRight(anchorRect.right());
}
if (dlgRect.bottom() >= anchorRect.bottom()) {
- dlgRect.moveBottom(anchorRect.bottom() - DIALOG_WINDOW_FRAME_HEIGHT);
+ dlgRect.moveBottom(anchorRect.bottom());
}
// if after moving the dialog does not fit on the screen, then adjust the size of the dialog
if (!anchorRect.contains(dlgRect)) {
- anchorRect -= QMargins(DIALOG_WINDOW_FRAME_HEIGHT, DIALOG_WINDOW_FRAME_HEIGHT + titleBarHeight,
- DIALOG_WINDOW_FRAME_HEIGHT, DIALOG_WINDOW_FRAME_HEIGHT);
dlgRect = anchorRect.intersected(dlgRect);
}
+ dlgRect = dlgRect.marginsRemoved(frameMargins);
+
m_globalPos = dlgRect.topLeft();
setContentWidth(dlgRect.width());
diff --git a/framework/uicomponents/qml/Muse/UiComponents/internal/popupviewclosecontroller.cpp b/framework/uicomponents/qml/Muse/UiComponents/internal/popupviewclosecontroller.cpp
index 5068c861ee..5130f6cf93 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/internal/popupviewclosecontroller.cpp
+++ b/framework/uicomponents/qml/Muse/UiComponents/internal/popupviewclosecontroller.cpp
@@ -22,7 +22,7 @@
#include "popupviewclosecontroller.h"
-#include
+#include
#include
using namespace muse::uicomponents;
@@ -34,7 +34,7 @@ PopupViewCloseController::PopupViewCloseController(const modularity::ContextPtr&
void PopupViewCloseController::init()
{
- connect(qApp, &QApplication::applicationStateChanged, this, &PopupViewCloseController::onApplicationStateChanged);
+ connect(qApp, &QGuiApplication::applicationStateChanged, this, &PopupViewCloseController::onApplicationStateChanged);
interactive()->currentUriAboutToBeChanged().onNotify(this, [this]() {
notifyAboutClose();
diff --git a/framework/uicomponents/qml/Muse/UiComponents/itemmultiselectionmodel.cpp b/framework/uicomponents/qml/Muse/UiComponents/itemmultiselectionmodel.cpp
index 334904bfcd..7eac8e9599 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/itemmultiselectionmodel.cpp
+++ b/framework/uicomponents/qml/Muse/UiComponents/itemmultiselectionmodel.cpp
@@ -22,7 +22,7 @@
#include "itemmultiselectionmodel.h"
-#include
+#include
using namespace muse::uicomponents;
@@ -59,7 +59,7 @@ QList ItemMultiSelectionModel::selectedRows() const
void ItemMultiSelectionModel::select(const QModelIndex& index)
{
- Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
+ Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
//! NOTE: always treat simultaneously pressed Ctrl and Shift as Ctrl
if (modifiers.testFlag(Qt::ShiftModifier) && modifiers.testFlag(Qt::ControlModifier)) {
diff --git a/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt b/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
index f5c75cac4a..af9f0b5ece 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
+++ b/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
@@ -26,6 +26,7 @@ set(MODULE_TEST_SRC
)
set(MODULE_TEST_LINK
+ Qt::Qml
muse_uicomponents_qml
)
diff --git a/framework/vst/CMakeLists.txt b/framework/vst/CMakeLists.txt
index 6c10896ffb..ba7bf7f336 100644
--- a/framework/vst/CMakeLists.txt
+++ b/framework/vst/CMakeLists.txt
@@ -75,7 +75,7 @@ target_sources(muse_vst PRIVATE
target_link_libraries(muse_vst PRIVATE vst3sdk)
if (MUSE_QT_SUPPORT)
- list(APPEND MODULE_LINK Qt::Qml Qt::Widgets)
+ target_link_libraries(muse_vst PRIVATE Qt::Qml Qt::Widgets)
endif()
target_no_warning(muse_vst -Wno-deprecated-declarations)