PowerAppsで円スライダーを作った話

PowerAppsに標準で用意されているスライダーは1方向です。
勉強中に2次元いけるんじゃない？って思って試したら出来たので共有します。
ちょっと動きが不安定なので改善の余地はありますが、考え方が伝わればよいかなと思います。

## こんなのできました
円のスライダー
![円スライダー2.gif](https://qiita-image-store.s3.amazonaws.com/0/97601/44444e7d-8327-5838-d56f-18ab10759a25.gif)

## どうやって作ったか
スライダーの位置をずらしながら円の上を操作しているように見せかけています。
スライダーを透明にしているので、実際に見えているボタンとレールは単なるアイコンで実際にそれを操作しているわけではありません。

![円スライダー解説用1.gif](https://qiita-image-store.s3.amazonaws.com/0/97601/194f1ed8-1918-315f-a734-b3815d02d430.gif)

## 詳細
初期設定をどこかで行います。
今回はReset用のボタンを挿入してOnSelectに配置しました。

```c#:btnReset.Onselect
UpdateContext({Const:{sRight:1,sTop:2,sLeft:3,sBottom:4}});
UpdateContext({radius:100,angle:180,centerX:500,centerY:300,sliderPos:Const.sLeft,sliderThickness:50,tStart:false});
Reset(Slider);
UpdateContext({nicoX:1000,nicoY:300}) // スマイルアイコン用
```

### スライダーの位置をボタンとともに動かす
ボタンを表示している位置とスライダーの位置を合わせるため、ボタンに重なるようにスライダー動かします。
また1方向にしか動かせないスライダーを2方向に動かすため、一定条件を満たしたときに縦と横を切り替えます。
今回は```Slider.Max=145、Slider.Min=35```としたため、```Slider.Vale>135、Slider.Vale<45```の時に縦と横を切り替えるようにしました。スライダーの上下左右の情報は```sliderPos```に保管しています。
また、切り替えはスライダーだけでは実装できないのでタイマーを挿入して以下のように設定しスライダーを監視させます。
タイマーがsliderPosを変更していきます。

```c#:SliderPosTimer
Start = tStart
Duaration = 10
Repeat = true
OnTimerStart = If(Slider.Value>135,If(sliderPos=Const.sTop || sliderPos =Const.sLeft,UpdateContext({sliderPos:Mod(sliderPos,4)-1}),UpdateContext({sliderPos:Mod(sliderPos,4)+1})));
               If(Slider.Value<45 ,If(sliderPos=Const.sTop || sliderPos =Const.sLeft,UpdateContext({sliderPos:Mod(sliderPos,4)+1}),UpdateContext({sliderPos:Mod(sliderPos+2,4)+1})));
               UpdateContext({angle: Switch(sliderPos,Const.sRight,Mod(Slider.Value-90,360),Const.sTop,-Slider.Value+180,Const.sLeft,-Slider.Value+270,Const.sBottom,Slider.Value+180)})
```
sliderPosの値を受けて、スライダーを動くようにします。

```c#:Slider
X = centerX + Switch(
    sliderPos,
    Const.sRight,
        Cos((-Slider.Value+90)/180 * Pi()) * radius-sliderThickness/2,
    Const.sLeft,
        Cos((Slider.Value+90)/180 * Pi()) * radius-sliderThickness/2,
    -radius
)
Y = centerY + Switch(
    sliderPos,
    Const.sTop,
        Sin((-Slider.Value)/180 * Pi()) * radius-sliderThickness/2,
    Const.sBottom,
        Sin((Slider.Value)/180 * Pi()) * radius-sliderThickness/2,
    -radius
)
Width  = If(Mod(sliderPos,2)=1,sliderThickness,radius*2)
Height = If(Mod(sliderPos,2)=0,sliderThickness,radius*2)
Layout = If(Mod(sliderPos,2)=1,Vertical,Horizontal)
Visible = false // テスト中はtrueのほうがよい
Onselect = UpdateContext({tStart:false}); 
           UpdateContext({tStart:true})  // タイマーを起動する
OnChange = UpdateContext({tStart:false}) // タイマーを停止する
Min = 35
Max = 145
```

### 見せかけのボタンとレールを追加する。
円を二つ用意してボタンとレールのようにします。

```c#:SliderBtnCircle
X = centerX-15 + Cos(
    Switch(
        sliderPos,
        Const.sRight,
            Mod(Slider.Value-90,360),
        Const.sTop,
            -Slider.Value + 180,
        Const.sLeft,
            -Slider.Value + 270,
        Const.sBottom,
            Slider.Value + 180
    )/180 * Pi()
) * radius
Y = centerY-15-Sin(
    Switch(
        sliderPos,
        Const.sRight,
            Mod(Slider.Value-90,360),
        Const.sTop,
            -Slider.Value + 180,
        Const.sLeft,
            -Slider.Value + 270,
        Const.sBottom,
            Slider.Value + 180
    )/180 * Pi()
) * radius
```

```c#:SliderRailCircle
X = centerX-radius
Y = centerY-radius
```

以上で円のスライダーが完成します。

### スマイルマークを動かす
ここからは蛇足ですが、スマイルマーク(iconSmile)を動かすには以下の設定をすればOKです。

```c#:SliderPosTimer.OnTimerEnd
UpdateContext({nicoX:nicoX+Cos(angle/180*Pi())*10});
UpdateContext({nicoY:nicoY-Sin(angle/180*Pi())*10})
```
```c#:iconSmile
X = nicoX
Y = nicoY
```

## 課題
スライダーの円をなぞる分には問題がないのですが、ちょっとずれたりするとボタンが暴れます。
もう少し位置や縦横の切り替えタイミングを調整すれば解決できるかもしれません。
仕組みもまだまだ複雑なのでもっと簡略化したいです。手ごろに使えるようなところまで落とし込みたいですね。
（行列使えばいける・・・？）