]> git.ktnx.net Git - mobile-ledger.git/blob - app/src/main/java/net/ktnx/mobileledger/ui/HueRing.java
fix ring placement in the color selector dialog
[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.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 import net.ktnx.mobileledger.utils.DimensionUtils;
33
34 import androidx.annotation.Nullable;
35
36 public class HueRing extends View {
37     private final int markerWidthDegrees = 10;
38     private Paint ringPaint, initialPaint, currentPaint;
39     private int centerX, centerY;
40     private int diameter;
41     private int padding;
42     private int initialHueDegrees;
43     private int color, hueDegrees;
44     private float radius;
45     private float bandWidth;
46     private float ringR;
47     private float innerDiameter;
48     private float centerR;
49     private RectF centerRect;
50     private RectF ringRect;
51     private int markerOverflow;
52     public HueRing(Context context, @Nullable AttributeSet attrs) {
53         super(context, attrs);
54         init(Colors.DEFAULT_HUE_DEG);
55     }
56     public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
57         super(context, attrs, defStyleAttr);
58         init(Colors.DEFAULT_HUE_DEG);
59     }
60     public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
61                    int defStyleRes) {
62         super(context, attrs, defStyleAttr, defStyleRes);
63         init(Colors.DEFAULT_HUE_DEG);
64     }
65     public HueRing(Context context) {
66         super(context);
67         init(Colors.DEFAULT_HUE_DEG);
68     }
69     public HueRing(Context context, int initialHueDegrees) {
70         super(context);
71         init(initialHueDegrees);
72     }
73     private void init(int initialHueDegrees) {
74         final int[] steps = {0xFF000000 | Colors.getPrimaryColorForHue(0),      // red
75                              0xFF000000 | Colors.getPrimaryColorForHue(60),     // yellow
76                              0xFF000000 | Colors.getPrimaryColorForHue(120),    // green
77                              0xFF000000 | Colors.getPrimaryColorForHue(180),    // cyan
78                              0xFF000000 | Colors.getPrimaryColorForHue(240),    // blue
79                              0xFF000000 | Colors.getPrimaryColorForHue(300),    // magenta
80                              0xFF000000 | Colors.getPrimaryColorForHue(360),    // red, again
81         };
82         Shader rainbow = new SweepGradient(0, 0, steps, null);
83
84         ringPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
85         ringPaint.setShader(rainbow);
86         ringPaint.setStyle(Paint.Style.STROKE);
87
88         initialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
89         initialPaint.setStyle(Paint.Style.FILL);
90
91         currentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
92         currentPaint.setStyle(Paint.Style.FILL);
93
94         setInitialHue(initialHueDegrees);
95         setHue(initialHueDegrees);
96
97         padding = DimensionUtils.dp2px(getContext(), 4);
98     }
99     public int getColor() {
100         return color;
101     }
102     public int getHueDegrees() {
103         return hueDegrees;
104     }
105     public void setHue(int hueDegrees) {
106         if (hueDegrees == -1) hueDegrees = Colors.DEFAULT_HUE_DEG;
107
108         if (hueDegrees != Colors.DEFAULT_HUE_DEG) {
109             // round to 15 degrees
110             int rem = hueDegrees % 15;
111             if (rem < 8) hueDegrees -= rem;
112             else hueDegrees += 15 - rem;
113         }
114
115         this.hueDegrees = hueDegrees;
116         this.color = Colors.getPrimaryColorForHue(hueDegrees);
117         currentPaint.setColor(this.color);
118         invalidate();
119     }
120     private void setHue(float hue) {
121         setHue((int) (360f * hue));
122     }
123     @Override
124     protected void onDraw(Canvas canvas) {
125         super.onDraw(canvas);
126
127         float center = getWidth() / 2f;
128         ringPaint.setStrokeWidth((int) bandWidth);
129
130         canvas.translate(center, center);
131         canvas.drawOval(ringRect, ringPaint);
132
133         canvas.drawArc(centerRect, 180, 180, true, initialPaint);
134         canvas.drawArc(centerRect, 0, 180, true, currentPaint);
135
136         drawMarker(canvas);
137     }
138     private void drawMarker(Canvas canvas) {
139         // TODO
140     }
141     @Override
142     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
143         int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
144         int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
145         int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
146         int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
147
148         if ((widthMode == MeasureSpec.AT_MOST) && (heightMode == MeasureSpec.AT_MOST)) {
149             diameter = Math.min(widthSize, heightSize);
150         }
151         else {
152             setMeasuredDimension(MEASURED_STATE_TOO_SMALL, MEASURED_STATE_TOO_SMALL);
153             return;
154         }
155
156         setMeasuredDimension(diameter, diameter);
157
158 //        padding = DimensionUtils.dp2px(getContext(),
159 //                getContext().getResources().getDimension(R.dimen.activity_horizontal_margin)) / 2;
160         diameter -= 2 * padding;
161         radius = diameter / 2f;
162         centerX = padding + (int) radius;
163         centerY = centerX;
164
165         bandWidth = diameter / 3.5f;
166         ringR = radius - bandWidth / 2f;
167
168         ringRect = new RectF(-ringR, -ringR, ringR, ringR);
169
170         innerDiameter = diameter - 2 * bandWidth;
171         centerR = innerDiameter * 0.5f;
172         centerRect = new RectF(-centerR, -centerR, centerR, centerR);
173     }
174     @Override
175     public boolean onTouchEvent(MotionEvent event) {
176         switch (event.getAction()) {
177             case MotionEvent.ACTION_DOWN:
178             case MotionEvent.ACTION_MOVE:
179                 float x = event.getX() - centerX;
180                 float y = event.getY() - centerY;
181
182                 float dist = (float) Math.hypot(x, y);
183
184                 if (dist < centerR) {
185                     if (y < 0) {
186                         setHue(initialHueDegrees);
187                     }
188
189                     break;
190                 }
191                 float angleRad = (float) Math.atan2(y, x);
192                 // angleRad is [-𝜋; +𝜋]
193                 float hue = (float) (angleRad / (2 * Math.PI));
194                 if (hue < 0) hue += 1;
195                 Log.d("TMP",
196                         String.format("x=%1.3f, y=%1.3f, angle=%1.3frad, hueDegrees=%1.3f", x, y,
197                                 angleRad, hue));
198                 setHue(hue);
199                 break;
200         }
201
202         return true;
203     }
204     public void setInitialHue(int initialHue) {
205         if (initialHue == -1) initialHue = Colors.DEFAULT_HUE_DEG;
206         this.initialHueDegrees = initialHue;
207         this.initialPaint.setColor(Colors.getPrimaryColorForHue(initialHue));
208         invalidate();
209     }
210 }