]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/HueRing.java
wrap Log.d calls, skipping them on non-debug builds
[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 MoLe. 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.Path;
24 import android.graphics.RectF;
25 import android.graphics.Shader;
26 import android.graphics.SweepGradient;
27 import android.util.AttributeSet;
28 import android.view.MotionEvent;
29 import android.view.View;
30
31 import net.ktnx.mobileledger.utils.Colors;
32 import net.ktnx.mobileledger.utils.DimensionUtils;
33
34 import androidx.annotation.Nullable;
35
36 import static net.ktnx.mobileledger.utils.Logger.debug;
37
38 public class HueRing extends View {
39     private static final int hueStepDegrees = 15;
40     private Paint ringPaint, initialPaint, currentPaint, markerPaint;
41     private int centerX, centerY;
42     private int diameter;
43     private int padding;
44     private int initialHueDegrees;
45     private int color, hueDegrees;
46     private float outerR;
47     private float innerR;
48     private float bandWidth;
49     private float ringR;
50     private float innerDiameter;
51     private float centerR;
52     private RectF centerRect;
53     private RectF ringRect;
54     private int markerOverflow;
55     private int markerStrokeWidth;
56     public HueRing(Context context, @Nullable AttributeSet attrs) {
57         super(context, attrs);
58         init(Colors.DEFAULT_HUE_DEG);
59     }
60     public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
61         super(context, attrs, defStyleAttr);
62         init(Colors.DEFAULT_HUE_DEG);
63     }
64     public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
65                    int defStyleRes) {
66         super(context, attrs, defStyleAttr, defStyleRes);
67         init(Colors.DEFAULT_HUE_DEG);
68     }
69     public HueRing(Context context) {
70         super(context);
71         init(Colors.DEFAULT_HUE_DEG);
72     }
73     public HueRing(Context context, int initialHueDegrees) {
74         super(context);
75         init(initialHueDegrees);
76     }
77     private void init(int initialHueDegrees) {
78         final int[] steps = {Colors.getPrimaryColorForHue(0),      // red
79                              Colors.getPrimaryColorForHue(60),     // yellow
80                              Colors.getPrimaryColorForHue(120),    // green
81                              Colors.getPrimaryColorForHue(180),    // cyan
82                              Colors.getPrimaryColorForHue(240),    // blue
83                              Colors.getPrimaryColorForHue(300),    // magenta
84                              Colors.getPrimaryColorForHue(360),    // red, again
85         };
86         Shader rainbow = new SweepGradient(0, 0, steps, null);
87
88         ringPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
89         ringPaint.setShader(rainbow);
90         ringPaint.setStyle(Paint.Style.STROKE);
91
92         initialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
93         initialPaint.setStyle(Paint.Style.FILL);
94
95         currentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
96         currentPaint.setStyle(Paint.Style.FILL);
97
98         setInitialHue(initialHueDegrees);
99         setHue(initialHueDegrees);
100
101         markerStrokeWidth = DimensionUtils.dp2px(getContext(), 4);
102
103         padding = markerStrokeWidth * 2 + 2;
104
105         markerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
106         markerPaint.setStyle(Paint.Style.STROKE);
107         markerPaint.setColor(0xa0000000);
108         markerPaint.setStrokeWidth(markerStrokeWidth);
109     }
110     public int getColor() {
111         return color;
112     }
113     public int getHueDegrees() {
114         return hueDegrees;
115     }
116     public void setHue(int hueDegrees) {
117         if (hueDegrees == -1) hueDegrees = Colors.DEFAULT_HUE_DEG;
118
119         if (hueDegrees != Colors.DEFAULT_HUE_DEG) {
120             // round to 15 degrees
121             int rem = hueDegrees % hueStepDegrees;
122             if (rem < (hueStepDegrees / 2)) hueDegrees -= rem;
123             else hueDegrees += hueStepDegrees - rem;
124         }
125
126         this.hueDegrees = hueDegrees;
127         this.color = Colors.getPrimaryColorForHue(hueDegrees);
128         currentPaint.setColor(this.color);
129         invalidate();
130     }
131     private void setHue(float hue) {
132         setHue((int) (360f * hue));
133     }
134     @Override
135     protected void onDraw(Canvas canvas) {
136         super.onDraw(canvas);
137
138         float center = getWidth() / 2f;
139         ringPaint.setStrokeWidth((int) bandWidth);
140
141         canvas.save();
142         canvas.translate(center, center);
143         canvas.drawOval(ringRect, ringPaint);
144
145         canvas.drawArc(centerRect, 180, 180, true, initialPaint);
146         canvas.drawArc(centerRect, 0, 180, true, currentPaint);
147
148         canvas.restore();
149         drawMarker(canvas, center);
150     }
151     private void drawMarker(Canvas canvas, float center) {
152         float leftRadians = (float) Math.toRadians(-hueStepDegrees / 2f);
153         float rightRadians = (float) Math.toRadians(hueStepDegrees / 2f);
154         float sl = (float) Math.sin(leftRadians);
155         float sr = (float) Math.sin(rightRadians);
156         float cl = (float) Math.cos(leftRadians);
157         float cr = (float) Math.cos(rightRadians);
158         float innerEdge = innerR - 1.5f * markerStrokeWidth;
159         float outerEdge = outerR + 1.5f + markerStrokeWidth;
160         Path p = new Path();
161 //        p.arcTo(-innerEdge, -innerEdge, innerEdge, innerEdge, -hueStepDegrees / 2f,
162 //                hueStepDegrees, true);
163 //        p.lineTo(outerEdge * cr, outerEdge * sr);
164         p.arcTo(-outerEdge, -outerEdge, outerEdge, outerEdge, hueStepDegrees / 2f,
165                 -hueStepDegrees, false);
166 //        p.close();
167         canvas.save();
168         canvas.translate(center, center);
169         canvas.rotate(hueDegrees, 0, 0);
170         canvas.drawPath(p, markerPaint);
171         canvas.restore();
172     }
173     @Override
174     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
175         int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
176         int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
177         int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
178         int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
179
180         if ((widthMode == MeasureSpec.AT_MOST) && (heightMode == MeasureSpec.AT_MOST)) {
181             diameter = Math.min(widthSize, heightSize);
182         }
183         else {
184             setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
185             return;
186         }
187
188         setMeasuredDimension(diameter, diameter);
189
190 //        padding = DimensionUtils.dp2px(getContext(),
191 //                getContext().getResources().getDimension(R.dimen.activity_horizontal_margin)) / 2;
192         diameter -= 2 * padding;
193         outerR = diameter / 2f;
194         centerX = padding + (int) outerR;
195         centerY = centerX;
196
197         bandWidth = diameter / 3.5f;
198         ringR = outerR - bandWidth / 2f;
199         innerR = outerR - bandWidth;
200
201         ringRect = new RectF(-ringR, -ringR, ringR, ringR);
202
203         innerDiameter = diameter - 2 * bandWidth;
204         centerR = innerDiameter * 0.5f;
205         centerRect = new RectF(-centerR, -centerR, centerR, centerR);
206     }
207     @Override
208     public boolean onTouchEvent(MotionEvent event) {
209         switch (event.getAction()) {
210             case MotionEvent.ACTION_DOWN:
211             case MotionEvent.ACTION_MOVE:
212                 float x = event.getX() - centerX;
213                 float y = event.getY() - centerY;
214
215                 float dist = (float) Math.hypot(x, y);
216
217                 if (dist < centerR) {
218                     if (y < 0) {
219                         setHue(initialHueDegrees);
220                     }
221
222                     break;
223                 }
224                 float angleRad = (float) Math.atan2(y, x);
225                 // angleRad is [-𝜋; +𝜋]
226                 float hue = (float) (angleRad / (2 * Math.PI));
227                 if (hue < 0) hue += 1;
228                 debug("TMP",
229                         String.format("x=%1.3f, y=%1.3f, angle=%1.3frad, hueDegrees=%1.3f", x, y,
230                                 angleRad, hue));
231                 setHue(hue);
232                 break;
233         }
234
235         return true;
236     }
237     public void setInitialHue(int initialHue) {
238         if (initialHue == -1) initialHue = Colors.DEFAULT_HUE_DEG;
239         this.initialHueDegrees = initialHue;
240         this.initialPaint.setColor(Colors.getPrimaryColorForHue(initialHue));
241         invalidate();
242     }
243 }