がりらぼ

WindowsRuntimeの応援ブログ

XAMLでの回転式メニューについて考える

今回はちょっとむずかしい話
ドラッグして回転するようなコントロールつくれたらいいとおもいますよね(

問題はどうやって回転を反映させるか。
たしかストアアプリには回転動作のイベント取得方法があったような気がするけど
WPFでもできるように考えてみる。

まずこういうリングメニューを考えてみる。
f:id:garicchi:20140224110139p:plain

WPFXAML上では、ドラッグはTouchMoveとMouseMoveイベントで検知できる。
TouchEventArgsのGetPositionメソッド、Positionプロパティでタッチした位置を取得できる。

要はこのタッチした位置と、回転角0度の位置との角度をだしたら回転角が算出できる。
f:id:garicchi:20140224110915p:plain

ここで座標をベクトルとして考えると、2点ベクトルの角度を求めるためにはベクトルの内積の計算を行う必要がある。
ベクトルの内積の式はこう
f:id:garicchi:20140224111517p:plain
引用

http://www.geisya.or.jp/~mwm48961/kou2/dot_product0.html

要するにベクトルの内積さえもとまればあとはそれを2点の長さの掛け算で除算してアークコサインしてやれば角度がもとまる。

2点座標のベクトルの内積はこうやってもとまる
f:id:garicchi:20140224111827p:plain

これらをプログラムにすると
まずタッチした座標touchPointと0度の座標firstPointはこう求めることができる

Point rootPoint = new Point(gridRoot.ActualWidth / 2, gridRoot.ActualHeight / 2);

Point touchPoint = new Point(p.X - rootPoint.X, p.Y - rootPoint.Y);

Point firstPoint = new Point(gridRoot.ActualWidth / 2 - rootPoint.X, 0 - rootPoint.Y);

内積

double naiseki = firstPoint.X * touchPoint.X + firstPoint.Y * touchPoint.Y;

2点の長さを求める必要がある

double firstLength = GetPointLnegth(firstPoint);
double touchLength = GetPointLnegth(touchPoint);

public double GetPointLnegth(Point p)
        {
            return Math.Sqrt(p.X*p.X+p.Y*p.Y);
        }

これらをアークコサインして、角度が求まる

180度以上はなんか0度にむかって減っていったので多分数学的になんかあるんだろうけど考えるのがめんどくさかったので
180度以上は360から減算することにした。

double deg = Math.Acos((naiseki / (firstLength * touchLength))) * 180 / Math.PI;
if (p.X < rootPoint.X)
{
    deg = 360 - deg;
}

あとはこれを回転したいコントロールのRotateTransformのAngleプロパティにバインドすればおk

<ed:Arc.RenderTransform>
	<TransformGroup>
		<ScaleTransform/>
		<SkewTransform/>
		<RotateTransform Angle="{Binding Degree}"/>
		<TranslateTransform/>
	</TransformGroup>
</ed:Arc.RenderTransform>


C#の全コードはこちら

        public double GetPointLnegth(Point p)
        {
            return Math.Sqrt(p.X*p.X+p.Y*p.Y);
        }

        

        private void gridSelector_TouchMove(object sender, TouchEventArgs e)
        {
            InputProcess(e.GetTouchPoint(gridRoot).Position);
            
        }

        private void InputProcess(Point p)
        {
            
            Point rootPoint = new Point(gridRoot.ActualWidth / 2, gridRoot.ActualHeight / 2);

            Point touchPoint = new Point(p.X - rootPoint.X, p.Y - rootPoint.Y);

            Point firstPoint = new Point(gridRoot.ActualWidth / 2 - rootPoint.X, 0 - rootPoint.Y);

            double naiseki = firstPoint.X * touchPoint.X + firstPoint.Y * touchPoint.Y;
            double firstLength = GetPointLnegth(firstPoint);
            double touchLength = GetPointLnegth(touchPoint);
            double deg = Math.Acos((naiseki / (firstLength * touchLength))) * 180 / Math.PI;
            if (p.X < rootPoint.X)
            {
                deg = 360 - deg;
            }
           
            _viewModel.PointDegree = deg;
            
        }

        private void gridSelector_MouseMove(object sender, MouseEventArgs e)
        {
            if(e.LeftButton==MouseButtonState.Pressed)
            InputProcess(e.GetPosition(gridRoot));
            
        }

こんな動作をする

あああ - YouTube

おまけ

2点の角度を求めるだけならアークタンジェントのほうが簡単だったかなーとおもいつつやってみたらなんかうまくいかなかったので頑張れる人はアークタンジェントでやってみてほしい