Qt 6.5でWindows 11ダークモード対応

本記事は「Dark Mode on Windows 11 with Qt 6.5」の抄訳です。

最近の Windows 10 ビルド、そして Windows 11 ではさらに、ダークテーマが Windows デスクトップの主流なパーソナライズオプションとしてついに登場しました。Qt は長年にわたって MacOS のダークモード設定をサポートしてきましたが、Qt 6.5 では、ダークテーマのより良いサポートを Windows に導入します。

Windows XPのQStyle

Windowsには、テーマ設定やパーソナライズの長い歴史があります。Windows 3.1にはすでにカスタムカーソルやアイコンパックがあり、私はいくつかのフロッピーディスクや後のDVDを最新の映画からインスピレーションを得た壁紙や効果音で満杯にしました。

適切なUIテーマ設定は、Windows XPでUxThemeシステムライブラリのWin32 APIを通じて導入されました。私は2001年にバルセロナで開催されたマイクロソフトのTechEdカンファレンスに行き、このフレームワークで期待できることを確認しました。MFCやWin32 APIを使ってカスタムコントロールを開発していた聴衆には、その可能性が無限にあるように思えたし、怖くも思えた。丸いUI要素、穴の開いたボタン、グラデーション、半透明、アニメーションまで!という話をしたのを覚えています。

私たちは以前、Windows XP プレビュービルドをベースにした Qt の Windows XP スタイルの実装に取り組んでおり、新しいネイティブ API を使用してユーザーインターフェイスコントロールの様々な要素を描画していました。この作業はQStyle APIの設計の多くに影響を与え、来たるQt 3リリースに間に合わせることができました。現在のQStyle APIは、Qt 6のQtWidgetsでもほぼ同じです。Windows XP スタイルの実装は、後に Windows Vista スタイルの基礎となり、Qt 6 では、Qt Quick Controls のネイティブ Windows デスクトップスタイルにその実装を使用しています。

しかし、好みは変わるし、すべての技術が当初の意図通りに進化するわけではありません。おそらく、カーソルやアイコンのライブラリのように、開発者のコミュニティがUxThemeライブラリのために多くのカスタムUIテーマを作成することが期待されていたのでしょう。しかし、実際にはそうはならず、ボタンは角の丸い平らな枠になっただけで、ありがたいことに穴は開いていません。

UxTheme APIはWindows 11でも存在し、Qtはユーザーインターフェイスコントロールの要素をネイティブなWindows Vistaスタイルでレンダリングするために、依然としてこれを使用しています。しかし、私たちが取得できるコントロールアセットは、依然としてWindows VistaのAeroテーマシステムに基づいています。そして、このテーマには「ダークモード」はなく、このAPIを通じてダークコントロールのアセットを取得する方法は文書化されていません。つまり、Qtはシステムからダークパレットを読み取ることができますが、UxThemeベースのVistaスタイルを使用する際には、それを実際に使用することはできません。一部のUI要素にはダークテーマカラーを取得しますが、システムからはライトUIアセットを取得することになり、ユーザーには使用できないUIになってしまいます。

近年、MicrosoftはFluentというデザインシステムを採用しており、Windows 11のシステムUIはこのシステムをベースにしています。しかし、このシステムはUxThemeでは利用できないため、Windows 11システムでは、オペレーティングシステムのUIでさえも、様々なスタイルが混在しているのは当然と言えるかもしれません。比較的新しいWinUIライブラリで書かれていないアプリケーションやシステムダイアログは「Fluent」に見えず、ダークカラースキームをサポートしていないことも多く、深い階層にあるコントロールパネルのいくつかは、まだWindows 2000のように見えます。

Qt 6.5以前

バージョン5.15以降、Qtはダークモードのシステムパレットを使用するか、少なくともウィンドウの装飾についてはダークテーマ尊重の選択方法を提供しています。この選択はQPAプラットフォームのパラメータであり、アプリケーションの起動時にコマンドラインオプションで設定することができます。

> gallery.exe -platform windows:darkmode=1
> gallery.exe -platform windows:darkmode=2

または、環境変数(main 関数で qsetenv("QT_QPA_PLATFORM", "windows:darkmode=[1|2]") を使って設定することが望ましい)を通して設定します。

darkmode の値'1'は、ウィンドウ装飾のテーマ化を可能にし、カスタムパレットと対応するスタイルを実装するアプリケーションは、一貫したウィンドウフレームとタイトルバーを持つことができるようにします。また、'2'の値は、Qtがダークシステムパレットを読み込んで使用するようにします。Qt 6.4 までのデフォルトでは、どちらもサポートしていませんでした。

Qt 6.4 では、アプリケーションのデフォルトパレットに依存するデフォルトの動作を変更しました。アプリケーションパレットがダークの場合、ウィンドウは自動的にダークのウィンドウ装飾を使用します。パレットがダークかライトかを判断するために、ウィンドウの色をテキストの色と比較します。

static bool shouldApplyDarkFrame()
{
// ...
const QPalette defaultPalette;
return defaultPalette.color(QPalette::WindowText).lightness()
> defaultPalette.color(QPalette::Window).lightness();
}

アプリケーションでは、ダークパレットに似合うスタイルを使い、ダークパレットを設定し、自動的にダークなウィンドウタイトルとフレームを取得することができます。

int main(int argc, char **argv)
{
QApplication app(argc, argv);
    app.setStyle("fusion"); // looks good with dark color scheme
    app.setPalette(myDarkPalette());

    MainWindow mainWindow;
    mainWindow.show();
    return app.exec();
}

問題はやはり、darkmode=2が指定されない限り、ダークパレットはシステムから読み込まれず、手作業で作成する必要があることです。また、darkmode=2 が指定された場合、スタイルがダークなカラースキームで動作することができない場合でも、ダークパレットが使用されます。また、システムがダーク色調で動作しているかライト色調で動作しているかを調べる Qt API はありません。

Qt 6.5 の方法

Qt 6.5では、既存のアプリケーションに変更を加えないようにしながら、次のステップに進んでいます。

QStyleHints には新しいプロパティ colorSchemeがあり、Qt::ColorScheme の列挙値、Qt::ColorScheme::Light または Qt::ColorScheme::Dark(または該当する配色がないシステムでは Qt::ColorScheme::Unknown) を保持しています。これにより、アプリケーションは、ユーザーのシステムの好みを尊重しながら、手作りのアプリケーションパレットがダークかライトかを判断することができます。また、通常のプロパティと同様に、変更通知シグナル QStyleHints::colorSchemeChangedが付属しており、アプリケーションはシステムのカラースキームの変更に反応することが可能です。

しかし、理想を言えば、もうパレットを手作りする必要はありません。特に、高コントラストのユーザーインターフェースなどを必要とするユーザーのことを考えると、ユーザーの好みを覆すのは、率直に言って危険なことなのです。

Qt では、ユーザーの好みに応じたシステムパレットを常に読み込むようになりました。ちなみに Qt 6.5 では Gnome デスクトップでも読み込むようになりました。しかし、上記説明の通り、すべてのスタイルがどのパレットでもきれいに見えるわけではなく、Windows Vista スタイルは間違いなくそうではありません。そこで、QStyleの既存の仕上げインフラストラクチャ(およびQt Quickスタイルの同等のテーマ オーバーライド)を使用して、スタイルがそのシステム パレットを無視し、別のパレットで上書きするか決められます。Windows Vista スタイルは、常にシステム パレットをライト システム パレットに置き換えます。「Fusion」やクラシックな Windows スタイルなど、どのパレットでもうまく機能するスタイルでは、アプリケーション パレットは変更されずに残ります。

The widget gallery example running on Windows 11, left side with default Windows Vista style, right side with the Fusion style.

このスクリーンショットは、ウィジェット「ギャラリー」の例です(私のローカル開発ブランチのものですが、Qt 6.5 ベータパッケージでも同じ結果が得られます)。左側ではWindowsのデフォルトのスタイルを実行しており、右側では -style fusionコマンドラインオプションで起動したものです。Qt Windows版のデフォルトのスタイルは変更しませんので、デフォルトでは、アプリケーションは Windows Vista のスタイルを使用し、ダークテーマで動作する Windows 11 システム上でもライトシステムパレットが使用されます。ウィンドウのフレームは、アプリケーションのパレットと一致します。

しかし今、アプリケーションはユーザーの好みの配色を尊重しながら、ダークモードのサポートを簡単に有効にすることができます。Qt Quickアプリケーションでは、以下のようになります。

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickStyle>

using namespace Qt::StringLiterals;

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

     QQuickStyle::setStyle("fusion");
     QQmlApplicationEngine engine;

     engine.load(QUrl(u"qrc:/main.qml"_s));

     return app.exec();
}

Fusionスタイルは、弊社がWindows 11で推奨するスタイルです。ダークパレットとライトパレットによく似合い、Fluentデザイン言語ではありませんが、Windows 11のデスクトップにうまく溶け込んでいます。

その他考えるべきこと

ダークなカラースキームの支持を選ぶ前に、アプリケーションで確認すべきことがいくつかあります。

独自のスタイルを作成したり、スタイルシートを使用している開発者は、特定の色をハードコードしている可能性があります。例えば、無効なボタンのテキストを少し薄くして、「グレーアウト」したように見せることは珍しくありません。しかし、配色が暗い場合は、代わりに明るいテキストを少し暗くするのが一般的です。

また、グラフなど特定のビジュアル要素にカスタムカラーを定義しているアプリケーションでは、背景が暗く前景が明るいときに選択した色が適切であることの確認が必要です。例えば、ハードコードされた色を含むリッチテキストを表示する場合など、特定のUI要素についてダークパレットとライトパレットを切り替えるオプションをユーザーに提供することも検討すべきかもしれません。

Qt 6.5以降

Qtの将来のバージョンで追加したいと思うような、単純な(しかし必ずしも容易ではない)機能がいくつかあります。

パレットの色を評価して、パレットがダークかライトかを判断するQPalette::colorSchemeメンバを追加するとよいでしょう。カスタムビジュアル要素に対するアプリケーションの色の選択は、QStyleHints::colorSchemeの値ではなく、実際に使用されるパレットに基づく必要があります。上記の単純なshouldApplyDarkFrame()ヘルパーのようなメンバー関数があれば簡単に実現できます。

よくある要望は、QStyleHints::colorScheme を書き込み可能なプロパティにして、アプリケーションがシステム設定に関係なく、ウィンドウフレームを含む特定の配色を強制できるようにすることです。Windowsでは、それ以外はライトのシステムでダークウィンドウの装飾を強制することはできないと思われますが、macOSでは、各ウィンドウで個別に外観を制御することができます。

Windowsでダークシステムパレットを読む場合、QPalette::Highlightのカラーロールにテーマのアクセントカラーを使用します。しかし、アクセント色は他の場所でも使用され、コントロールによっては異なる色が使用されるため、これは完全には正しくありません(例えば、Windows 11の「赤」のダークテーマでは、ハイライトは明るい赤で、フォーカスフレームはより淡いオレンジ色です)。QPaletteには現在Accentのカラーロールがなく、それを追加するには少し複雑です。それは、QPaletteの内部データ構造は21色のロールを超えると、かなり変更する必要があるためです(すでに21色になっています)。

FigmaによるFluentデザイン

そして、Fluentデザインシステムを実装したスタイルをQtにも導入したいと考えています。Fluentデザインシステムは、オープンソースのFigmaパッケージとして提供されており、Brookは最近、Figma DesignからQt Quick Controlsを作成する方法についてブログを書いています。我々は、Figma のデザインに含まれるビジュアルアセットを Qt Quick のユーザーインターフェイスや、もしかしたら QtWidgets の UI で利用できるようにしたいと考えています。これは、インポート/エクスポート・ブリッジ、Qt Design Studio との統合、ビルドシステムツールの組み合わせで実現できるかもしれません。Microsoft が提供する Fluent デザインに基づく Windows 上の Qt アプリケーションのネイティブルックは、この機能の良いベンチマークとなるでしょう。


Blog Topics:

Comments