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