Win2D その3　レイヤー機能

# Win2Dレイヤー機能について
Win2Dには一般的なペイントソフトにあるようなレイヤー機能があります。
今回はそのレイヤー機能について紹介できたらと思います。
WinUI3のListViewなどを使用してレイヤーの管理などを行っていくのですが、申し訳ありませんがそこについては長くなりそうなので割愛します。

# Win2Dにおけるレイヤー
WinUI3でレイヤーを作成する方法は以下のスクリプトをDraw処理に書くことで実装されます。
```cs
private void Canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    var ds = args.DrawingSession;
    using(ds.CreateLayer(opacity))
    {
        ds.Draw~
    }
}
```
簡単に説明すると`using(DrawingSession.CreateLayer())`で示すと新しくレイヤーが作られ、そのブロックの中でそのレイヤーに対する描画処理が行われていくという形になります。
`CreateLayer()`の引数は`float`でそのレイヤーの透明度を指しています。

# 複数のレイヤーにそれぞれ対応する描画処理を行わせる
ペイントソフトのように、あるレイヤーを指定してそのレイヤーに対して処理を行うということが一般的だと思います。
そうしたことをしたい場合、描画処理を行う`Layer`クラスを作成し、それらに対して`foreach`で回していく形になります。

具体的には以下のようなクラスを作成します。
```cs
public class DrawingLayer
{
    // 描画アイテムをリストとして保持
    public List<IShape> shapes = new List<IShape>();
    public string Name {  get; set; }
    public float Opacity { get; set; }
    
    public DrawingLayer(string name, float opacity)
    {
        Name = name;
        Opacity = opacity;
    }

    // Layerそれぞれに対する描画処理
    public void Draw(CanvasDrawingSession ds, float alpha = 1)
    {
        // 描画リストの中身を取り出してどの描画方法で描画するのかで分岐し、それぞれに対応するメソッドを呼び出す
        foreach(var shape in shapes)
        {
            if(shape.Mode == ShapeMode.Rectangle)
            {
                var rectangle = (RectangleShape) shape;
                ds.DrawRectangle(rectangle.Rect, rectangle.Color, rectangle.Storoke);
            }
            if(shape.Mode == ShapeMode.Circle)
            {
                var circle = (CircleShape) shape;
                ds.DrawCircle...
            }
        }
    }
    // 以下のメソッドをMainWindowから呼び出し、描画アイテムに追加していく
    public void AddRectangle(Rect rect, Color color, int stroke)
    {
        shapes.Add(new RectangleShape(rect, color, stroke));
    }
    public void AddCircle(float x, float y, float rad, Color color, float stroke)
    {
        shapes.Add(new CircleShape(x, y, rad, color, stroke));
    }
}
```

保有するデータ型は以下のようになっています。
```cs
public interface IShape
{
    public ShapeMode Mode { get; set; }
}

public enum ShapeMode
{
    Rectangle,
    Circle,
}

public class RectangleShape : IShape
{
    public ShapeMode Mode { get; set; } = ShapeMode.Rectangle;
    public Rect Rect { get; set; }
    public Color Color { get; set; }
    public int Storoke {  get; set; }

    public RectangleShape(Rect rect, Color color, int stroke) 
    {
        Rect = rect;
        Color = color;
        Storoke = stroke;
    }
}

public class CircleShape : IShape
{
    public ShapeMode Mode { get; set; } = ShapeMode.Circle;
    public float PositionX { get; set; }
    public float PositionY { get; set; }
    public float Radius { get; set; }
    public Color Color { get; set; }
    public float Stroke {  get; set; }

    public CircleShape(float positionX, float positionY, float radius, Color color, float stroke)
    {
        PositionX = positionX;
        PositionY = positionY;
        Radius = radius;
        Color = color;
        Stroke = stroke;
    }
}
```
ようは`IShape`を継承したそれぞれ描画に必要なデータの箱を作って、`ShpaeMode`にそのデータがなんのタイプか明記しておき、`Draw`関数でそのタイプをたよりに、タイプに合うようにキャストしなおして、それぞれに適応する処理を行っていくイメージです。

`shapes`には`IShape`を継承しているデータを入れることができるので、異なる描画処理データを1つのリストで保持することができるようになります。

# Main処理
メイン処理では以下のようになります。
※本来の使い方ではないですが、重要なところは差分追加(+)で表してます。
```diff_c_sharp
public sealed partial class MainWindow : Window
{
    PointerDrag pointerDrag;

    // 上記レイヤークラスのリストを作成する。(UIのためにObservableCollectionとしているがListでよい)
+   private ObservableCollection<DrawingLayer> layers = new ObservableCollection<DrawingLayer>();
    private int CurrentIndex { get; set; }

    public MainWindow()
    {
        this.InitializeComponent();
    }

    private void Canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
    {
        var ds = args.DrawingSession;
        if (layers.Count == 0)
        {
            layers.Add(new DrawingLayer("default", 1.0f));
            CurrentIndex = 0;
        }
        foreach (DrawingLayer layer in layers)
        {
             // ここでは上記レイヤークラスのリスト分レイヤーを作成し、それに対してレイヤークラスの`Draw`関数を呼び出している。
             // ゆえにレイヤークラス単位で描画が行われていることとなる。
+            using(ds.CreateLayer(layer.Opacity))
+            {
+                layer.Draw(ds);
+            }
        }
    }

///
/// 割愛
///

    // マウスリリースをすることで描画アイテムがどのようなデータを持つか決定する。
    // ゆえにこの処理内で描画アイテムを追加すればよい。
    private void Canvas_PointerReleased(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
    {
        if (pointerDrag == null)
            return;
            
        // この部分で現在選択しているレイヤーの`AddRectangle`を呼び出し、描画アイテムリストにデータを追加している
+       layers[CurrentIndex].AddRectangle(new Rect(pointerDrag.StartLocation, pointerDrag.CurrentLocation), PickUpColor, 5);
        pointerDrag = null;
        canvas.Invalidate();
    }

    // レイヤーの追加処理　UIのAddボタンを押すとレイヤーを追加していく(UI部分は割愛)
    private void AddLayerButton_Click(object sender, RoutedEventArgs e)
    {
        int count = layers.Count;
        layers.Add(new DrawingLayer($"layer{count}", 1.0f));
        CurrentIndex = count;
    }
    // レイヤーの追加処理　UIのRemoveボタンを押すと選択しているレイヤーを削除する(UI部分は割愛)
    private void RemoveButton_Click(object sender, RoutedEventArgs e)
    {
        var count = CurrentIndex;
        layers.Remove(layers[CurrentIndex]);
        CurrentIndex = count - 1;
        if(CurrentIndex < 0)
            CurrentIndex = 0;
        canvas.Invalidate();
    }
}
```

この処理によって各レイヤーの描画アイテムを追加していき、レイヤークラスの描画処理にてその描画アイテムをそれぞれ処理を行うことにより、レイヤーごとに描画操作が可能となる。