Qt Quick における埋め込みWindow

本稿は「Window embedding in Qt Quick」の抄訳です。
 

Qt 6.7では、Qt Quickにおけるウィンドウ管理のための新しく魅力的なAPIがいくつか追加されました。本ブログ記事では、これらの変更点と、それによって可能になるユースケースについて詳しく見ていきます。

トップレベルウィンドウ

Qt Quickでのウィンドウ管理は、Qt 5.0で追加されたWindow型によって提供されます。この型を使用すると、トップレベルウィンドウを作成および管理することができます。例えば:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    MouseArea {
        anchors.fill: parent
        onClicked: dialog.show();
    }

    Window {
        id: dialog
        flags: Qt.Dialog
        color: "thistle"
    }
}

これにより、メイン・ウィンドウの上の中央にダイアログ・ウィンドウが配置され表示されます:

中央揃えは、ダイアログがメインウィンドウを一時的な親(transientParent)として関連付けられることで実現されます。QWindowでは、この一時的な関係を明示的に管理する必要があり、QWindow::setTransientParent()を使用しますが、Qt Quickでは、QML ドキュメント内の階層構造(Window型の親子関係)に従って自動的に有効になります。

これにより、トップレベルのダイアログ、ポップアップメニュー、ツールチップなどを作成するための基本的な構造が提供されます。

子ウィンドウ

現在のWindow型では対応していないユースケースの一つが子ウィンドウです。子ウィンドウはトップレベルウィンドウ(または別の子ウィンドウ)の内部に存在し、親ウィンドウの位置に追従します。

表面的には、これはQt Quickシーン内の通常のItemと同じように聞こえます。通常、これらの基本的な仕組みで十分ですが、専用の子ウィンドウが役立つケースがあることを、これから説明します。

Qt GUIレベルでは、子ウィンドウはQWindow::setParent()を呼び出すことで管理されます。このため、Qt 6.7のTech previewでは、新たにWindow型にparentプロパティを追加しました。このプロパティを使用することで、ウィンドウのVisual Parentを設定できるようになります。

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    Window {
        id: childWindow
        color: "thistle"
        parent: mainWindow
        visible: true
    }
}

これは、ウィンドウをメイン・ウィンドウの視覚的な子にします:

transientParent プロパティとは異なり、parent プロパティは明示的に設定する必要があり、QML ドキュメント内で他のウィンドウの子として宣言されたウィンドウでは自動的に有効になりません。 また、中央揃えのロジックも組み込まれておらず、QWindow::setParent() の動作と同じです。

Item を親に持つウィンドウ

とはいえ、ウインドウ は Item を視覚的な親として持つこともできます:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    Item {
        id: item
        anchors.centerIn: parent
        width: childWindow.width
        height: childWindow.height

        Window {
            id: childWindow
            color: "thistle"
            parent: item
            visible: true
        }
    }
}

それにより、ウィンドウ位置をItem経由で制御できます:

Z オーダー

また、Window に新しい z プロパティを追加しました。これは、Item の既存のプロパティと一致し、兄弟の子ウィンドウ間のスタック順序を制御できます:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    Window {
        color: "thistle"
        parent: mainWindow
        visible: true
        z: 1
    }

    Window {
        x: 50; y: 50
        color: "darkgrey"
        parent: mainWindow
        visible: true
    }
}

通常、兄弟ウィンドウのスタック順序は、Item の場合と同じようにQMLドキュメント内の順序に従いますが、Zオーダーを設定した場合、兄弟ウィンドウの最初のウィンドウを2番目のウィンドウの上にスタックさせています:

でも、なぜ?

どのような場合に子ウィンドウが役立つのでしょうか?

このようなユースケースの1つに、ウィンドウごとのサーフェスプロパティがあります。現在(Qt6.7 Tech Preview)のQtのレンダリングパイプラインはカラーマネジメントに対応していないため、すべてのアセット(色、画像、動画)の色空間を取得して、それらをターゲットの色空間に一致させることはできません。しかし、QSurfaceFormat::setColorSpace() を使用して、ウィンドウのターゲット色空間を設定することは可能です。ソースアセットが特定の色空間であることが分かっている場合や、独自のカラーマッチングを行う場合は、ウィンドウのターゲット色空間を例えばQColorSpace::AdobeRgbに設定することができます。 Qt は、できるだけ邪魔をせず、ピクセルを OS に渡す際に一切変更を加えずに、OS がウィンドウをディスプレイの最終的なターゲット色空間にカラーマッチングする処理を実行できるようにします(プラットフォームがサポートしている場合)。

現時点では、この使用事例に対応する QML API はありませんが、ウィンドウを可視化する前にウィンドウの表面フォーマットを設定する小さな C++ ヘルパーを使用すれば、macOS では次のようなことが可能です。

背面のウィンドウ(Display P3部分)はデフォルトのカラースペースですが、前面のウィンドウ(Adobe RBG部分)はAdobe RGBに明示的に設定されています。効果は微妙ですが、色を正確に再現するには重要です。

関連するケースとして、ハイダイナミックレンジ(HDR)があります。これは、ビデオや画像コンテンツが、例えば鏡面反射のハイライトをより詳細に表現するなど、最新のディスプレイの明るさを最大限に活用することを可能にします。HDRコンテンツのレンダリングにはレンダリングパイプラインの変更が必要であり、プラットフォームによっては標準ダイナミックレンジ(SDR)のカラー値の輝度レベルとずれが生じる場合があります。通常、HDRビデオの再生によってアプリケーションのUIの他の部分に影響が及ぶことは望ましくありません。この問題を軽減する一つの方法は、HDRコンテンツ専用のHDR対応子ウィンドウを使用することです。さらに、HDRフォーマットでは色空間も指定されることが多いため、専用ウィンドウを使用することで、前述の通りこの問題も解決できます。

QtではHDRサポートに取り組んでいますので、このユースケースの詳細については、今後の情報にご注目ください。

非Qt Quick ウィンドウの埋め込み

Qt Quick ウィンドウを子ウィンドウにできることはもちろん素晴らしいことですが、ほとんどのアプリケーションでは用途が限られます。 注意深い読者の方なら、ウィンドウItem の子としてサポートすることで、Itemを基準にして子ウィンドウの位置を制御できることに気づかれたと思います。それが、Qt Quickウィンドウ以外でも可能なら良いと思いませんか?

そしてまさにその機能を実現したのが、新しいWindowContainer型です。この型はQt 6.7のTech Previewとして提供されており、Qt Widgetsの QWidget::createWindowContainer() が提供する機能を Qt Quick向けに対応させたものです:

import QtQuick

Window {
    id: mainWindow
    width: 300; height: 300
    visible: true
    color: "darkseagreen"

    WindowContainer {
        window: openGLWindow
        anchors.centerIn: parent
    }
}

コンテナ内のウィンドウは任意のQWindowに設定可能です。この例では、QtのOpenGL Window ExampleからのOpenGLウィンドウを使用し、単純なコンテキストプロパティを介してQMLに公開しています。その結果、以下のようになります:

位置、サイズ、Zオーダー、可視性、クリップといった重要なItemプロパティは、アイテムからウィンドウに引き継がれています。しかし、QWidget::createWindowContainer() の場合と同様に、注意が必要な点があるかもしれません。そのため、もし問題を見つけた場合はぜひお知らせください

埋め込みの成功は、埋め込まれるウィンドウが子ウィンドウとしてどれだけ「適切に動作するか」にも依存します。例えば、Qt WidgetsはトップレベルのQWidget以外に埋め込まれた場合、いくつかの癖があることが知られています(このユースケースを想定して設計されていなかったため)。これについては、将来的に改善することを目指しています。

外部ウィンドウ(Foreign windows)

最後に、QWindowを埋め込むことができるのであれば、いわゆる外部ウィンドウも埋め込むことができるということです。Qtでは、この用語を、Qt以外の場所で作成されたネイティブプラットフォームウィンドウを表すQWindowに対して使用します。これは、ネイティブコードを手動で使用するか、または他のフレームワークを介して作成されます。Qtが期待するネイティブプラットフォームウィンドウの種類(NSViewUIViewHWNDxcb_window_tなど)と同じ種類である限り、QWindow::fromWinId()を使用して、ネイティブウィンドウを表す外部QWindowを作成できます。この外部QWindowは、上記と同じ方法で埋め込むことができます。

これにより、例えばネイティブのマップビュー、ビデオプレイヤー、またはWebビューを埋め込むことが可能になります:

それだけではありません。これらの埋め込まれた外部ウィンドウは「単なる別のウィンドウ」であるため、例えば先ほどの例のように、Qtロゴを含む他のウィンドウをその上(または下)に重ねることが可能です。プラットフォームがアルファチャンネルを持つ子ウィンドウをサポートしていれば(ほとんどのプラットフォームがサポートしています)、これらのウィンドウを半透明にすることもできます。

これにより、ウィンドウを他のアイテム(この場合はウィンドウコンテナ)に親として設定できる機能がなぜ有用なのか、先ほどの説明とつながります。

フィードバックをお待ちしています

手始めに、ウィンドウ埋め込みのマニュアルテストを試してみましょう。これは、Qt Quick ウィンドウ埋め込みのさまざまな機能を紹介するものです。

何か問題が見つかった場合はお知らせください。また、既存の API で対応できないユースケースがある場合もお知らせください。

Happy embedding! 


Blog Topics:

Comments