Hue selection dialog
authorDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 3 Mar 2019 07:21:13 +0000 (09:21 +0200)
committerDamyan Ivanov <dam+mobileledger@ktnx.net>
Sun, 3 Mar 2019 07:21:13 +0000 (09:21 +0200)
app/src/main/java/net/ktnx/mobileledger/ui/HueRing.java [new file with mode: 0644]
app/src/main/java/net/ktnx/mobileledger/ui/HueRingDialog.java [new file with mode: 0644]
app/src/main/res/layout/hue_dialog.xml [new file with mode: 0644]

diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/HueRing.java b/app/src/main/java/net/ktnx/mobileledger/ui/HueRing.java
new file mode 100644 (file)
index 0000000..493e665
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright © 2019 Damyan Ivanov.
+ *  This file is part of MoLe.
+ *  MoLe is free software: you can distribute it and/or modify it
+ *  under the term of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your opinion), any later version.
+ *
+ *  MoLe is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License terms for details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import net.ktnx.mobileledger.utils.Colors;
+
+import androidx.annotation.Nullable;
+
+public class HueRing extends View {
+    private final int markerWidthDegrees = 10;
+    private Paint ringPaint, initialPaint, currentPaint;
+    private int centerX, centerY;
+    private int diameter;
+    private int padding;
+    private int initialHueDegrees;
+    private int color, hueDegrees;
+    private float radius;
+    private float bandWidth;
+    private float ringR;
+    private float innerDiameter;
+    private float centerR;
+    private RectF centerRect;
+    private RectF ringRect;
+    private int markerOverflow;
+    public HueRing(Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+        init(Colors.DEFAULT_HUE_DEG);
+    }
+    public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(Colors.DEFAULT_HUE_DEG);
+    }
+    public HueRing(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+                   int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(Colors.DEFAULT_HUE_DEG);
+    }
+    public HueRing(Context context) {
+        super(context);
+        init(Colors.DEFAULT_HUE_DEG);
+    }
+    public HueRing(Context context, int initialHueDegrees) {
+        super(context);
+        init(initialHueDegrees);
+    }
+    private void init(int initialHueDegrees) {
+        final int[] steps = {0xFF000000 | Colors.getPrimaryColorForHue(0),      // red
+                             0xFF000000 | Colors.getPrimaryColorForHue(60),     // yellow
+                             0xFF000000 | Colors.getPrimaryColorForHue(120),    // green
+                             0xFF000000 | Colors.getPrimaryColorForHue(180),    // cyan
+                             0xFF000000 | Colors.getPrimaryColorForHue(240),    // blue
+                             0xFF000000 | Colors.getPrimaryColorForHue(300),    // magenta
+                             0xFF000000 | Colors.getPrimaryColorForHue(360),    // red, again
+        };
+        Shader rainbow = new SweepGradient(0, 0, steps, null);
+
+        ringPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        ringPaint.setShader(rainbow);
+        ringPaint.setStyle(Paint.Style.STROKE);
+
+        initialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        initialPaint.setStyle(Paint.Style.FILL);
+
+        currentPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+        currentPaint.setStyle(Paint.Style.FILL);
+
+        setInitialHue(initialHueDegrees);
+        setHue(initialHueDegrees);
+    }
+    public int getColor() {
+        return color;
+    }
+    public int getHueDegrees() {
+        return hueDegrees;
+    }
+    public void setHue(int hueDegrees) {
+        // round to 15 degrees
+        int rem = hueDegrees % 15;
+        if (rem < 8) hueDegrees -= rem;
+        else hueDegrees += 15 - rem;
+
+        this.hueDegrees = hueDegrees;
+        this.color = Colors.getPrimaryColorForHue(hueDegrees);
+        currentPaint.setColor(this.color);
+        invalidate();
+    }
+    private void setHue(float hue) {
+        setHue((int) (360f * hue));
+    }
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        ringPaint.setStrokeWidth((int) bandWidth);
+
+        canvas.translate(centerX, centerY);
+        canvas.drawOval(ringRect, ringPaint);
+
+        canvas.drawArc(centerRect, 180, 180, true, initialPaint);
+        canvas.drawArc(centerRect, 0, 180, true, currentPaint);
+
+        drawMarker(canvas);
+    }
+    private void drawMarker(Canvas canvas) {
+        // TODO
+    }
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthMode = View.MeasureSpec.getMode(widthMeasureSpec);
+        int widthSize = View.MeasureSpec.getSize(widthMeasureSpec);
+        int heightMode = View.MeasureSpec.getMode(heightMeasureSpec);
+        int heightSize = View.MeasureSpec.getSize(heightMeasureSpec);
+
+        if (((widthMode == MeasureSpec.AT_MOST) && (heightMode == MeasureSpec.AT_MOST)) ||
+            ((widthMode == MeasureSpec.EXACTLY) && (heightMode == MeasureSpec.EXACTLY)))
+        {
+            diameter = Math.min(widthSize, heightSize);
+        }
+
+        setMeasuredDimension(diameter, diameter);
+
+//        padding = DimensionUtils.dp2px(getContext(),
+//                getContext().getResources().getDimension(R.dimen.activity_horizontal_margin)) / 2;
+        padding = 0;
+        diameter -= 2 * padding;
+        radius = diameter / 2f;
+        centerX = padding + (int) radius;
+        centerY = centerX;
+
+        bandWidth = diameter / 3.5f;
+        ringR = radius - bandWidth / 2f;
+
+        ringRect = new RectF(-ringR, -ringR, ringR, ringR);
+
+        innerDiameter = diameter - 2 * bandWidth;
+        centerR = innerDiameter * 0.5f;
+        centerRect = new RectF(-centerR, -centerR, centerR, centerR);
+    }
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        switch (event.getAction()) {
+            case MotionEvent.ACTION_DOWN:
+            case MotionEvent.ACTION_MOVE:
+                float x = event.getX() - centerX;
+                float y = event.getY() - centerY;
+
+                float dist = (float) Math.hypot(x, y);
+
+                if (dist < centerR) {
+                    if (y < 0) {
+                        setHue(initialHueDegrees);
+                    }
+
+                    break;
+                }
+                float angleRad = (float) Math.atan2(y, x);
+                // angleRad is [-𝜋; +𝜋]
+                float hue = (float) (angleRad / (2 * Math.PI));
+                if (hue < 0) hue += 1;
+                Log.d("TMP",
+                        String.format("x=%1.3f, y=%1.3f, angle=%1.3frad, hueDegrees=%1.3f", x, y,
+                                angleRad, hue));
+                setHue(hue);
+                break;
+        }
+
+        return true;
+    }
+    public void setInitialHue(int initialHue) {
+        if (initialHue == -1) initialHue = Colors.DEFAULT_HUE_DEG;
+        this.initialHueDegrees = initialHue;
+        this.initialPaint.setColor(Colors.getPrimaryColorForHue(initialHue));
+        invalidate();
+    }
+}
diff --git a/app/src/main/java/net/ktnx/mobileledger/ui/HueRingDialog.java b/app/src/main/java/net/ktnx/mobileledger/ui/HueRingDialog.java
new file mode 100644 (file)
index 0000000..fc3d7db
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright © 2019 Damyan Ivanov.
+ *  This file is part of MoLe.
+ *  MoLe is free software: you can distribute it and/or modify it
+ *  under the term of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your opinion), any later version.
+ *
+ *  MoLe is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License terms for details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+package net.ktnx.mobileledger.ui;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+
+import net.ktnx.mobileledger.R;
+import net.ktnx.mobileledger.utils.Colors;
+
+import androidx.annotation.NonNull;
+
+public class HueRingDialog extends Dialog {
+    private int initialHue;
+    private HueRing hueRing;
+    private HueSelectedListener listener;
+
+    public HueRingDialog(@NonNull Context context, int initialHue) {
+        super(context);
+        this.initialHue = initialHue;
+    }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.hue_dialog);
+        hueRing = findViewById(R.id.ring);
+        hueRing.setInitialHue(initialHue);
+        hueRing.setHue(initialHue);
+
+        findViewById(R.id.btn_ok).setOnClickListener(v -> {
+            if (listener != null) listener.onHueSelected(hueRing.getHueDegrees());
+
+            dismiss();
+        });
+
+        findViewById(R.id.btn_cancel).setOnClickListener(v -> dismiss());
+
+        findViewById(R.id.btn_default)
+                .setOnClickListener(v -> hueRing.setHue(Colors.DEFAULT_HUE_DEG));
+    }
+    public void setColorSelectedListener(HueSelectedListener listener) {
+        this.listener = listener;
+    }
+    public interface HueSelectedListener {
+        void onHueSelected(int hue);
+    }
+}
diff --git a/app/src/main/res/layout/hue_dialog.xml b/app/src/main/res/layout/hue_dialog.xml
new file mode 100644 (file)
index 0000000..ad2ce3d
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright © 2019 Damyan Ivanov.
+  ~  This file is part of MoLe.
+  ~  MoLe is free software: you can distribute it and/or modify it
+  ~  under the term of the GNU General Public License as published by
+  ~  the Free Software Foundation, either version 3 of the License, or
+  ~  (at your opinion), any later version.
+  ~
+  ~  MoLe is distributed in the hope that it will be useful,
+  ~  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  ~  GNU General Public License terms for details.
+  ~
+  ~  You should have received a copy of the GNU General Public License
+  ~  along with Mobile-Ledger. If not, see <https://www.gnu.org/licenses/>.
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingStart="@dimen/activity_horizontal_margin"
+    android:paddingTop="@dimen/activity_vertical_margin"
+    android:paddingEnd="@dimen/activity_horizontal_margin"
+    android:paddingBottom="@dimen/activity_vertical_margin">
+
+    <net.ktnx.mobileledger.ui.HueRing
+        android:id="@+id/ring"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toTopOf="@id/button_pane"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/button_pane"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/activity_horizontal_margin"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/ring">
+
+        <TextView
+            android:id="@+id/btn_cancel"
+            style="@style/Base.TextAppearance.AppCompat.Button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/btn_cancel"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/btn_default"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/btn_default"
+            style="@style/Base.TextAppearance.AppCompat.Button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/default_color_btn"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/btn_ok"
+            app:layout_constraintStart_toEndOf="@id/btn_cancel"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/btn_ok"
+            style="@style/Base.TextAppearance.AppCompat.Button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/btn_ok"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/btn_default"
+            app:layout_constraintTop_toTopOf="parent" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file