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.
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.
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/>.
18 package net.ktnx.mobileledger.ui;
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;
31 import net.ktnx.mobileledger.utils.Colors;
33 import androidx.annotation.Nullable;
35 public class HueRing extends View {
36 private final int markerWidthDegrees = 10;
37 private Paint ringPaint, initialPaint, currentPaint;
38 private int centerX, centerY;
41 private int initialHueDegrees;
42 private int color, hueDegrees;
44 private float bandWidth;
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);
55 public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
56 super(context, attrs, defStyleAttr);
57 init(Colors.DEFAULT_HUE_DEG);
59 public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
61 super(context, attrs, defStyleAttr, defStyleRes);
62 init(Colors.DEFAULT_HUE_DEG);
64 public HueRing(Context context) {
66 init(Colors.DEFAULT_HUE_DEG);
68 public HueRing(Context context, int initialHueDegrees) {
70 init(initialHueDegrees);
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
81 Shader rainbow = new SweepGradient(0, 0, steps, null);
83 ringPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
84 ringPaint.setShader(rainbow);
85 ringPaint.setStyle(Paint.Style.STROKE);
87 initialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
88 initialPaint.setStyle(Paint.Style.FILL);
90 currentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
91 currentPaint.setStyle(Paint.Style.FILL);
93 setInitialHue(initialHueDegrees);
94 setHue(initialHueDegrees);
96 public int getColor() {
99 public int getHueDegrees() {
102 public void setHue(int hueDegrees) {
103 if (hueDegrees == -1) hueDegrees = Colors.DEFAULT_HUE_DEG;
105 if (hueDegrees != Colors.DEFAULT_HUE_DEG) {
106 // round to 15 degrees
107 int rem = hueDegrees % 15;
108 if (rem < 8) hueDegrees -= rem;
109 else hueDegrees += 15 - rem;
112 this.hueDegrees = hueDegrees;
113 this.color = Colors.getPrimaryColorForHue(hueDegrees);
114 currentPaint.setColor(this.color);
117 private void setHue(float hue) {
118 setHue((int) (360f * hue));
121 protected void onDraw(Canvas canvas) {
122 super.onDraw(canvas);
124 ringPaint.setStrokeWidth((int) bandWidth);
126 canvas.translate(centerX, centerY);
127 canvas.drawOval(ringRect, ringPaint);
129 canvas.drawArc(centerRect, 180, 180, true, initialPaint);
130 canvas.drawArc(centerRect, 0, 180, true, currentPaint);
134 private void drawMarker(Canvas canvas) {
138 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
139 int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
140 int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
141 int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
142 int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
144 if (((widthMode == MeasureSpec.AT_MOST) && (heightMode == MeasureSpec.AT_MOST)) ||
145 ((widthMode == MeasureSpec.EXACTLY) && (heightMode == MeasureSpec.EXACTLY)))
147 diameter = Math.min(widthSize, heightSize);
150 setMeasuredDimension(diameter, diameter);
152 // padding = DimensionUtils.dp2px(getContext(),
153 // getContext().getResources().getDimension(R.dimen.activity_horizontal_margin)) / 2;
155 diameter -= 2 * padding;
156 radius = diameter / 2f;
157 centerX = padding + (int) radius;
160 bandWidth = diameter / 3.5f;
161 ringR = radius - bandWidth / 2f;
163 ringRect = new RectF(-ringR, -ringR, ringR, ringR);
165 innerDiameter = diameter - 2 * bandWidth;
166 centerR = innerDiameter * 0.5f;
167 centerRect = new RectF(-centerR, -centerR, centerR, centerR);
170 public boolean onTouchEvent(MotionEvent event) {
171 switch (event.getAction()) {
172 case MotionEvent.ACTION_DOWN:
173 case MotionEvent.ACTION_MOVE:
174 float x = event.getX() - centerX;
175 float y = event.getY() - centerY;
177 float dist = (float) Math.hypot(x, y);
179 if (dist < centerR) {
181 setHue(initialHueDegrees);
186 float angleRad = (float) Math.atan2(y, x);
187 // angleRad is [-𝜋; +𝜋]
188 float hue = (float) (angleRad / (2 * Math.PI));
189 if (hue < 0) hue += 1;
191 String.format("x=%1.3f, y=%1.3f, angle=%1.3frad, hueDegrees=%1.3f", x, y,
199 public void setInitialHue(int initialHue) {
200 if (initialHue == -1) initialHue = Colors.DEFAULT_HUE_DEG;
201 this.initialHueDegrees = initialHue;
202 this.initialPaint.setColor(Colors.getPrimaryColorForHue(initialHue));