[UWP]CompactOverlay/ミニモードを使ってみる


## CompactOverlay/ミニモードって？

* Windows 10 Creators Updateで追加された「**最前面表示可能なウィンドウ表示モード**」です。
* CompactOverlay、ミニモード、またはPicture in Picture （PinP）とも呼ばれます。（この記事では説明文で「ミニモード」、コード内では「CompactOverlay」と表記）
* ウィンドウ表示モードは現在は「Default」と「CompactOverlay」があります。
* CUの機能を利用する場合は、**UniversalApiContractのバージョン4がサポートされていることを確認した上でAPIを利用していく**ように気をつけていきます。*
* Creators Update現在においては**Windows 10にのみ対応（デスクトップとタブレット）しており、Windows 10 Mobile及びXboxOne（Windows 10 IOTも？）では利用できません**。
* アプリのメインウィンドウを含めて、ApplicationViewを通じて表示モード（ViewMode）を切り替えることができます。

ちなみに2017/08/25時点でWindows10のCreatorsUpdate適用率はだいたい60%弱とのこと。対応する価値はありそうですね。


## ミニモードをサポートしているかを調べる

CU適用済みであってもViewModeの種類によっては対応していないプラットフォームがあるのでチェックしていきます。
ミニモード利用不可能な環境ではミニモード開始のボタンを非表示にするなど対応が必要となります。（そのためのStateTrigger）

単にミニモードをサポートしているかだけチェックするなら

```csharp
var view = ApplicationView.GetForCurrentView();
var isSupported = view.IsViewModeSupported(ApplicationViewMode.CompactOverlay);
```

XamlのVisualStateManager向けのStateTriggerにするなら

```csharp
using Windows.Foundation.Metadata;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;

namespace NicoPlayerHohoema.Views.StateTrigger
{
    public class IsSupportCompactOverlayTrigger : StateTriggerBase
    {
        public IsSupportCompactOverlayTrigger()
        {
            if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4))
            {
                var view = ApplicationView.GetForCurrentView();
                var supported = view.IsViewModeSupported(ApplicationViewMode.CompactOverlay);
                SetActive(supported);
            }
            else
            {
                SetActive(false);
            }
        }
    }
}
```


## ミニモードを検出するStateTrigger

ミニモード開始に反応してUIの表示状態を制御するためのStateTriggerです。

```csharp
using Windows.Foundation.Metadata;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;

namespace NicoPlayerHohoema.Views.StateTrigger
{
    public class CompactOverlayViewModeTrigger : InvertibleStateTrigger
    {
        public CompactOverlayViewModeTrigger()
        {
            if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4))
            {
                Update();
                Window.Current.SizeChanged += Current_SizeChanged;
            }
            else
            {
                SetActiveInvertible(false);
            }
        }

        ~CompactOverlayViewModeTrigger()
        {
            if (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4))
            {
                Window.Current.SizeChanged -= Current_SizeChanged;
            }
        }

        private void Current_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
        {
            Update();
        }

        private void Update()
        {
            var view = ApplicationView.GetForCurrentView();

            SetActiveInvertible(view.ViewMode == ApplicationViewMode.CompactOverlay);
        }
    }

    abstract public class InvertibleStateTrigger : StateTriggerBase
    {
        public bool Inverted { get; set; } = false;

        protected void SetActiveInvertible(bool isActive)
        {
            SetActive(Inverted ? !isActive : isActive);
        }
    }
}
```


## 現在のウィンドウをミニモードに切り替える


```csharp
public static async Task SwitchViewModeCompactOverayAsync(bool isCompactOverlay)
{
    if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 4))
    {
        return;
    }

    var appView = ApplicationView.GetForCurrentView();
    if (appView.IsViewModeSupported(ApplicationViewMode.CompactOverlay))
    {
        if (isCompactOverlay)
        {
            // PC等1080pであればデフォルトで最大サイズ（横500px 縦280px）で表示されるが
            // タブレット等の小サイズ画面ではより小さい幅で表示されます
            // そのため任意に表示サイズを指定してください
            ViewModePreferences compactOptions = ViewModePreferences.CreateDefault(ApplicationViewMode.CompactOverlay);
            compactOptions.CustomSize = new Windows.Foundation.Size(500, 280);
    
            var result = await appView.TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay, compactOptions);
            if (result)
            {
                // タイトルバーにもUIを展開表示するための設定（これは必須ではありませんが、見た目的には必要）
                CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
                appView.TitleBar.ButtonBackgroundColor = Colors.Transparent;
                appView.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
            }
        }
        else
        {
            var result = await appView.TryEnterViewModeAsync(ApplicationViewMode.Default);
            if (result)
            {
                // タイトルバーを透過表示するための設定を元に戻す
                CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = false;
                appView.TitleBar.ButtonBackgroundColor = null;
                appView.TitleBar.ButtonInactiveBackgroundColor = null;
            }
        }
    }
}
```


## 見栄えのための覚書き

* ミニモード中にボタンを置く場合には最小150x150まで正常にボタンが表示されるようにAdaptiveTrigger等で対応します
  * 特にミニモードを解除するためのボタンをミニモード開始時のボタンと近しい場所に配置して、いつでもアクセスできるようにします
* SplitViewを置いている場合、OpenPaneLengthが最小幅150よりも大きい場合に表示が崩れます。この場合ミニモード中にSplitView.OpenPaneLengthが150px以下になるよう対応が必要です。
* ミニモードのお手本としては MS製の「[映画＆テレビ](https://www.microsoft.com/ja-jp/store/p/%E6%98%A0%E7%94%BB-%E3%83%86%E3%83%AC%E3%83%93/9wzdncrfj3p2)」がオススメです。（ローカルのmp4等動画でも利用可能です）

## 参考

[CompactOverlay mode – aka Picture-in-Picture](https://blogs.msdn.microsoft.com/universal-windows-app-model/2017/02/11/compactoverlay-mode-aka-picture-in-picture/)

## 宣伝

拙作の[Windows 10向けのニコニコ動画アプリ Hohoema](https://www.microsoft.com/ja-jp/store/p/hohoema/9nblggh4rxt6)はミニモードにも対応してます。是非お試しください。