HueRing: allow selecting the default hue which is not divisable by 15
[mobile-ledger.git] / app / src / main / java / net / ktnx / mobileledger / ui / HueRing.java
1 /*
2  * Copyright © 2019 Damyan Ivanov.
3  *  This file is part of MoLe.
4  *  MoLe is free software: you can distribute it and/or modify it
5  *  under the term of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your opinion), any later version.
8  *
9  *  MoLe is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  *  GNU General Public License terms for details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
16  */
17
18 package net.ktnx.mobileledger.ui;
19
20 import android.content.Context;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.RectF;
24 import android.graphics.Shader;
25 import android.graphics.SweepGradient;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.view.MotionEvent;
29 import android.view.View;
30
31 import net.ktnx.mobileledger.utils.Colors;
32
33 import androidx.annotation.Nullable;
34
35 public class HueRing extends View {
36     private final int markerWidthDegrees = 10;
37     private Paint ringPaint, initialPaint, currentPaint;
38     private int centerX, centerY;
39     private int diameter;
40     private int padding;
41     private int initialHueDegrees;
42     private int color, hueDegrees;
43     private float radius;
44     private float bandWidth;
45     private float ringR;
46     private float innerDiameter;
47     private float centerR;
48     private RectF centerRect;
49     private RectF ringRect;
50     private int markerOverflow;
51     public HueRing(Context context, @Nullable AttributeSet attrs) {
52         super(context, attrs);
53         init(Colors.DEFAULT_HUE_DEG);
54     }
55     public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
56         super(context, attrs, defStyleAttr);
57         init(Colors.DEFAULT_HUE_DEG);
58     }
59     public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
60                    int defStyleRes) {
61         super(context, attrs, defStyleAttr, defStyleRes);
62         init(Colors.DEFAULT_HUE_DEG);
63     }
64     public HueRing(Context context) {
65         super(context);
66         init(Colors.DEFAULT_HUE_DEG);
67     }
68     public HueRing(Context context, int initialHueDegrees) {
69         super(context);
70         init(initialHueDegrees);
71     }
72     private void init(int initialHueDegrees) {
73         final int[] steps = {0xFF000000 | Colors.getPrimaryColorForHue(0),      // red
74                              0xFF000000 | Colors.getPrimaryColorForHue(60),     // yellow
75                              0xFF000000 | Colors.getPrimaryColorForHue(120),    // green
76                              0xFF000000 | Colors.getPrimaryColorForHue(180),    // cyan
77                              0xFF000000 | Colors.getPrimaryColorForHue(240),    // blue
78                              0xFF000000 | Colors.getPrimaryColorForHue(300),    // magenta
79                              0xFF000000 | Colors.getPrimaryColorForHue(360),    // red, again
80         };
81         Shader rainbow = new SweepGradient(0, 0, steps, null);
82
83         ringPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
84         ringPaint.setShader(rainbow);
85         ringPaint.setStyle(Paint.Style.STROKE);
86
87         initialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
88         initialPaint.setStyle(Paint.Style.FILL);
89
90         currentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
91         currentPaint.setStyle(Paint.Style.FILL);
92
93         setInitialHue(initialHueDegrees);
94         setHue(initialHueDegrees);
95     }
96     public int getColor() {
97         return color;
98     }
99     public int getHueDegrees() {
100         return hueDegrees;
101     }
102     public void setHue(int hueDegrees) {
103         if (hueDegrees != Colors.DEFAULT_HUE_DEG) {
104             // round to 15 degrees
105             int rem = hueDegrees % 15;
106             if (rem < 8) hueDegrees -= rem;
107             else hueDegrees += 15 - rem;
108         }
109
110         this.hueDegrees = hueDegrees;
111         this.color = Colors.getPrimaryColorForHue(hueDegrees);
112         currentPaint.setColor(this.color);
113         invalidate();
114     }
115     private void setHue(float hue) {
116         setHue((int) (360f * hue));
117     }
118     @Override
119     protected void onDraw(Canvas canvas) {
120         super.onDraw(canvas);
121
122         ringPaint.setStrokeWidth((int) bandWidth);
123
124         canvas.translate(centerX, centerY);
125         canvas.drawOval(ringRect, ringPaint);
126
127         canvas.drawArc(centerRect, 180, 180, true, initialPaint);
128         canvas.drawArc(centerRect, 0, 180, true, currentPaint);
129
130         drawMarker(canvas);
131     }
132     private void drawMarker(Canvas canvas) {
133         // TODO
134     }
135     @Override
136     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
137         int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
138         int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
139         int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
140         int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
141
142         if (((widthMode == MeasureSpec.AT_MOST) && (heightMode == MeasureSpec.AT_MOST)) ||
143             ((widthMode == MeasureSpec.EXACTLY) && (heightMode == MeasureSpec.EXACTLY)))
144         {
145             diameter = Math.min(widthSize, heightSize);
146         }
147
148         setMeasuredDimension(diameter, diameter);
149
150 //        padding = DimensionUtils.dp2px(getContext(),
151 //                getContext().getResources().getDimension(R.dimen.activity_horizontal_margin)) / 2;
152         padding = 0;
153         diameter -= 2 * padding;
154         radius = diameter / 2f;
155         centerX = padding + (int) radius;
156         centerY = centerX;
157
158         bandWidth = diameter / 3.5f;
159         ringR = radius - bandWidth / 2f;
160
161         ringRect = new RectF(-ringR, -ringR, ringR, ringR);
162
163         innerDiameter = diameter - 2 * bandWidth;
164         centerR = innerDiameter * 0.5f;
165         centerRect = new RectF(-centerR, -centerR, centerR, centerR);
166     }
167     @Override
168     public boolean onTouchEvent(MotionEvent event) {
169         switch (event.getAction()) {
170             case MotionEvent.ACTION_DOWN:
171             case MotionEvent.ACTION_MOVE:
172                 float x = event.getX() - centerX;
173                 float y = event.getY() - centerY;
174
175                 float dist = (float) Math.hypot(x, y);
176
177                 if (dist < centerR) {
178                     if (y < 0) {
179                         setHue(initialHueDegrees);
180                     }
181
182                     break;
183                 }
184                 float angleRad = (float) Math.atan2(y, x);
185                 // angleRad is [-𝜋; +𝜋]
186                 float hue = (float) (angleRad / (2 * Math.PI));
187                 if (hue < 0) hue += 1;
188                 Log.d("TMP",
189                         String.format("x=%1.3f, y=%1.3f, angle=%1.3frad, hueDegrees=%1.3f", x, y,
190                                 angleRad, hue));
191                 setHue(hue);
192                 break;
193         }
194
195         return true;
196     }
197     public void setInitialHue(int initialHue) {
198         if (initialHue == -1) initialHue = Colors.DEFAULT_HUE_DEG;
199         this.initialHueDegrees = initialHue;
200         this.initialPaint.setColor(Colors.getPrimaryColorForHue(initialHue));
201         invalidate();
202     }
203 }