diff --git a/framework/interactive/internal/interactive.cpp b/framework/interactive/internal/interactive.cpp index 5cc40f992c..a828c4fad4 100644 --- a/framework/interactive/internal/interactive.cpp +++ b/framework/interactive/internal/interactive.cpp @@ -519,6 +519,44 @@ static void setCustomColors(const std::vector& customColors) } } +//! NOTE Qt's default "Basic colors" grid is an unsorted historical palette (a patchwork of +//! seemingly random hues). Replace it once with a grayscale ramp followed by a hue sweep, so it +//! reads as an ordered gradient instead. +static void ensureStandardColorsAreSortedByHue() +{ + static bool initialized = false; + if (initialized) { + return; + } + initialized = true; + + constexpr int GRID_ROWS = 6; + constexpr int GRID_COLS = 8; + constexpr int GRID_SIZE = GRID_ROWS * GRID_COLS; + constexpr int GRAYSCALE_COUNT = GRID_COLS; + + //! NOTE Qt's standard color grid is indexed column-major (index = row + col * GRID_ROWS), + //! so a plain sequential index would run down the first column instead of across the first + //! row. Map our row-major fill order onto Qt's column-major index instead. + auto setColorAt = [](int pos, const QColor& c) { + const int row = pos / GRID_COLS; + const int col = pos % GRID_COLS; + QColorDialog::setStandardColor(row + col * GRID_ROWS, c); + }; + + int pos = 0; + for (int i = 0; i < GRAYSCALE_COUNT; ++i) { + const int v = static_cast(255.0 * i / (GRAYSCALE_COUNT - 1)); + setColorAt(pos++, QColor(v, v, v)); + } + + const int hueSteps = GRID_SIZE - GRAYSCALE_COUNT; + for (int i = 0; i < hueSteps; ++i) { + const qreal hue = qreal(i) / hueSteps; + setColorAt(pos++, QColor::fromHsvF(hue, 0.65, 0.95)); + } +} + async::Promise Interactive::selectColor(const Color& color, const std::string& title, bool allowAlpha) { if (m_isSelectColorOpened) { @@ -531,6 +569,7 @@ async::Promise Interactive::selectColor(const Color& color, const std::st m_isSelectColorOpened = true; + ensureStandardColorsAreSortedByHue(); setCustomColors(uiConfiguration()->colorDialogCustomColors()); return async::make_promise([this, color, title, allowAlpha](auto resolve, auto reject) { @@ -551,6 +590,19 @@ async::Promise Interactive::selectColor(const Color& color, const std::st dlg->setCurrentColor(currentColor); dlg->setOption(QColorDialog::ShowAlphaChannel, allowAlpha); + //! NOTE Force Qt's own cross-platform dialog instead of the OS-native one (e.g. NSColorPanel on + //! macOS), so it picks up the app's theme palette applied globally by WidgetStyle. + dlg->setOption(QColorDialog::DontUseNativeDialog, true); + //! NOTE The app's windows (main window, Preferences, etc.) are QQuickWindows, not QWidgets, so + //! Qt::ApplicationModal alone doesn't block or stack above them: Qt only enforces widget modality + //! within the QWidget world. Force native window creation and set an explicit transient parent + //! so the platform's own window manager (which operates below the Widget/Quick split) keeps this + //! dialog on top of, and modal to, whichever window is actually active. + dlg->setWindowModality(Qt::ApplicationModal); + dlg->winId(); + if (QWindow* dlgWindow = dlg->windowHandle()) { + dlgWindow->setTransientParent(topWindow()); + } QObject::connect(dlg, &QColorDialog::finished, [this, dlg, resolve, reject](int result) { DEFER { @@ -573,7 +625,11 @@ async::Promise Interactive::selectColor(const Color& color, const std::st (void)resolve(Color::fromQColor(selectedColor)); }); - dlg->open(); + //! NOTE Not open(): QDialog::open() unconditionally forces Qt::WindowModal before showing, + //! which would undo the Qt::ApplicationModal set above. show() displays the dialog without + //! touching its modality, and the finished signal still fires the same way once the dialog + //! is accepted/rejected. + dlg->show(); return async::Promise::dummy_result(); }, async::PromiseType::AsyncByBody);