HistoryChart: Fix transparency
@@ -26,8 +26,8 @@ import org.isoron.platform.gui.AndroidDataView
|
||||
import org.isoron.platform.time.JavaLocalDateFormatter
|
||||
import org.isoron.uhabits.core.models.Habit
|
||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
|
||||
import org.isoron.uhabits.core.ui.views.DarkTheme
|
||||
import org.isoron.uhabits.core.ui.views.HistoryChart
|
||||
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||
import org.isoron.uhabits.core.utils.DateUtils
|
||||
import org.isoron.uhabits.widgets.views.GraphWidgetView
|
||||
import java.util.Locale
|
||||
@@ -51,7 +51,7 @@ class HistoryWidget(
|
||||
habit = habit,
|
||||
isSkipEnabled = prefs.isSkipEnabled,
|
||||
firstWeekday = prefs.firstWeekday,
|
||||
theme = DarkTheme(),
|
||||
theme = WidgetTheme(),
|
||||
)
|
||||
(widgetView.dataView as AndroidDataView).apply {
|
||||
(this.view as HistoryChart).series = model.series
|
||||
@@ -65,7 +65,7 @@ class HistoryWidget(
|
||||
view = HistoryChart(
|
||||
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||
paletteColor = habit.color,
|
||||
theme = DarkTheme(),
|
||||
theme = WidgetTheme(),
|
||||
dateFormatter = JavaLocalDateFormatter(Locale.getDefault())
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:background="#ffff00">
|
||||
|
||||
<org.isoron.platform.gui.AndroidTestView
|
||||
android:layout_width="500dp"
|
||||
|
||||
BIN
android/uhabits-core/assets/test/views/BarChart/themeDark.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
android/uhabits-core/assets/test/views/BarChart/themeWidget.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 42 KiB |
@@ -52,9 +52,16 @@ interface Canvas {
|
||||
fun setTextAlign(align: TextAlign)
|
||||
fun toImage(): Image
|
||||
|
||||
/**
|
||||
* Fills entire canvas with the current color.
|
||||
*/
|
||||
fun fill() {
|
||||
fillRect(0.0, 0.0, getWidth(), getHeight())
|
||||
}
|
||||
|
||||
fun drawTestImage() {
|
||||
// Draw grey background
|
||||
setColor(Color(0x303030))
|
||||
// Draw transparent background
|
||||
setColor(Color(0.1, 0.1, 0.1, 0.5))
|
||||
fillRect(0.0, 0.0, 500.0, 400.0)
|
||||
|
||||
// Draw center rectangle
|
||||
@@ -63,7 +70,7 @@ interface Canvas {
|
||||
drawRect(100.0, 100.0, 300.0, 200.0)
|
||||
|
||||
// Draw squares, circles and arcs
|
||||
setColor(Color(0xFFFF00))
|
||||
setColor(Color.YELLOW)
|
||||
setStrokeWidth(1.0)
|
||||
drawRect(0.0, 0.0, 100.0, 100.0)
|
||||
fillCircle(50.0, 50.0, 30.0)
|
||||
@@ -75,7 +82,7 @@ interface Canvas {
|
||||
fillArc(50.0, 350.0, 30.0, 45.0, 90.0)
|
||||
|
||||
// Draw two red crossing lines
|
||||
setColor(Color(0xFF0000))
|
||||
setColor(Color.RED)
|
||||
setStrokeWidth(2.0)
|
||||
drawLine(0.0, 0.0, 500.0, 400.0)
|
||||
drawLine(500.0, 0.0, 0.0, 400.0)
|
||||
@@ -83,7 +90,7 @@ interface Canvas {
|
||||
// Draw text
|
||||
setFont(Font.BOLD)
|
||||
setFontSize(50.0)
|
||||
setColor(Color(0x00FF00))
|
||||
setColor(Color.GREEN)
|
||||
setTextAlign(TextAlign.CENTER)
|
||||
drawText("HELLO", 250.0, 100.0)
|
||||
setTextAlign(TextAlign.RIGHT)
|
||||
|
||||
@@ -23,9 +23,8 @@ data class Color(
|
||||
val red: Double,
|
||||
val green: Double,
|
||||
val blue: Double,
|
||||
val alpha: Double
|
||||
val alpha: Double,
|
||||
) {
|
||||
|
||||
val luminosity: Double
|
||||
get() {
|
||||
return 0.21 * red + 0.72 * green + 0.07 * blue
|
||||
@@ -53,4 +52,18 @@ data class Color(
|
||||
val relativeLuminosity = (l1 + 0.05) / (l2 + 0.05)
|
||||
return if (relativeLuminosity >= 1) relativeLuminosity else 1 / relativeLuminosity
|
||||
}
|
||||
|
||||
fun withAlpha(newAlpha: Double) = Color(red, green, blue, newAlpha)
|
||||
|
||||
companion object {
|
||||
val TRANSPARENT = Color(0.0, 0.0, 0.0, 0.0)
|
||||
val RED = Color(1.0, 0.0, 0.0, 1.0)
|
||||
val GREEN = Color(0.0, 1.0, 0.0, 1.0)
|
||||
val BLUE = Color(1.0, 0.0, 1.0, 1.0)
|
||||
val YELLOW = Color(1.0, 1.0, 0.0, 1.0)
|
||||
val MAGENTA = Color(1.0, 0.0, 1.0, 1.0)
|
||||
val CYAN = Color(0.0, 1.0, 1.0, 1.0)
|
||||
val WHITE = Color(1.0, 1.0, 1.0, 1.0)
|
||||
val BLACK = Color(0.0, 0.0, 0.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ class BarChart(
|
||||
var barMargin = 3.0
|
||||
var barWidth = 12.0
|
||||
var nGridlines = 6
|
||||
var backgroundColor = theme.cardBackgroundColor
|
||||
|
||||
override val dataColumnWidth: Double
|
||||
get() = barWidth + barMargin * 2
|
||||
@@ -67,8 +66,8 @@ class BarChart(
|
||||
var maxValue = series.map { it.max()!! }.max()!!
|
||||
maxValue = max(maxValue, 1.0)
|
||||
|
||||
canvas.setColor(backgroundColor)
|
||||
canvas.fillRect(0.0, 0.0, width, height)
|
||||
canvas.setColor(theme.cardBackgroundColor)
|
||||
canvas.fill()
|
||||
|
||||
fun barGroupOffset(c: Int) = marginLeft + paddingLeft +
|
||||
(c) * barGroupWidth
|
||||
@@ -97,13 +96,6 @@ class BarChart(
|
||||
canvas.fillCircle(x + barWidth - r, y + r, r)
|
||||
canvas.setFontSize(theme.smallTextSize)
|
||||
canvas.setTextAlign(TextAlign.CENTER)
|
||||
canvas.setColor(backgroundColor)
|
||||
canvas.fillRect(
|
||||
x - barMargin,
|
||||
y - theme.smallTextSize * 1.25,
|
||||
barWidth + 2 * barMargin,
|
||||
theme.smallTextSize * 1.0
|
||||
)
|
||||
canvas.setColor(colors[s])
|
||||
canvas.drawText(
|
||||
value.toShortString(),
|
||||
@@ -119,12 +111,7 @@ class BarChart(
|
||||
fun drawMajorGrid() {
|
||||
canvas.setStrokeWidth(1.0)
|
||||
if (nSeries > 1) {
|
||||
canvas.setColor(
|
||||
backgroundColor.blendWith(
|
||||
theme.lowContrastTextColor,
|
||||
0.5
|
||||
)
|
||||
)
|
||||
canvas.setColor(theme.lowContrastTextColor.withAlpha(0.5))
|
||||
for (c in 0 until nColumns - 1) {
|
||||
val x = barGroupOffset(c)
|
||||
canvas.drawLine(x, paddingTop, x, paddingTop + maxBarHeight)
|
||||
@@ -141,8 +128,6 @@ class BarChart(
|
||||
|
||||
fun drawAxis() {
|
||||
val y = paddingTop + maxBarHeight
|
||||
canvas.setColor(backgroundColor)
|
||||
canvas.fillRect(0.0, y, width, height - y)
|
||||
canvas.setColor(theme.lowContrastTextColor)
|
||||
canvas.drawLine(0.0, y, width, y)
|
||||
canvas.setColor(theme.mediumContrastTextColor)
|
||||
|
||||
@@ -33,7 +33,7 @@ class HistoryChart(
|
||||
var today: LocalDate,
|
||||
var paletteColor: PaletteColor,
|
||||
var theme: Theme,
|
||||
var dateFormatter: LocalDateFormatter
|
||||
var dateFormatter: LocalDateFormatter,
|
||||
) : DataView {
|
||||
|
||||
enum class Square {
|
||||
@@ -61,8 +61,10 @@ class HistoryChart(
|
||||
override fun draw(canvas: Canvas) {
|
||||
val width = canvas.getWidth()
|
||||
val height = canvas.getHeight()
|
||||
|
||||
canvas.setColor(theme.cardBackgroundColor)
|
||||
canvas.fillRect(0.0, 0.0, width, height)
|
||||
canvas.fill()
|
||||
|
||||
squareSize = round((height - 2 * padding) / 8.0)
|
||||
canvas.setFontSize(height * 0.06)
|
||||
|
||||
@@ -98,7 +100,7 @@ class HistoryChart(
|
||||
canvas: Canvas,
|
||||
column: Int,
|
||||
topDate: LocalDate,
|
||||
topOffset: Int
|
||||
topOffset: Int,
|
||||
) {
|
||||
drawHeader(canvas, column, topDate)
|
||||
repeat(7) { row ->
|
||||
@@ -150,7 +152,7 @@ class HistoryChart(
|
||||
width: Double,
|
||||
height: Double,
|
||||
date: LocalDate,
|
||||
offset: Int
|
||||
offset: Int,
|
||||
) {
|
||||
|
||||
val value = if (offset >= series.size) Square.OFF else series[offset]
|
||||
@@ -187,9 +189,13 @@ class HistoryChart(
|
||||
}
|
||||
}
|
||||
|
||||
val c1 = squareColor.contrast(theme.cardBackgroundColor)
|
||||
val c2 = squareColor.contrast(theme.mediumContrastTextColor)
|
||||
val textColor = if (c1 > c2) theme.cardBackgroundColor else theme.mediumContrastTextColor
|
||||
val textColor = if (theme.cardBackgroundColor == Color.TRANSPARENT) {
|
||||
theme.highContrastTextColor
|
||||
} else {
|
||||
val c1 = squareColor.contrast(theme.cardBackgroundColor)
|
||||
val c2 = squareColor.contrast(theme.mediumContrastTextColor)
|
||||
if (c1 > c2) theme.cardBackgroundColor else theme.mediumContrastTextColor
|
||||
}
|
||||
|
||||
canvas.setColor(textColor)
|
||||
canvas.setTextAlign(TextAlign.CENTER)
|
||||
|
||||
@@ -66,9 +66,9 @@ abstract class Theme {
|
||||
val regularTextSize = 17.0
|
||||
}
|
||||
|
||||
class LightTheme : Theme()
|
||||
open class LightTheme : Theme()
|
||||
|
||||
class DarkTheme : Theme() {
|
||||
open class DarkTheme : Theme() {
|
||||
override val appBackgroundColor = Color(0x212121)
|
||||
override val cardBackgroundColor = Color(0x303030)
|
||||
override val headerBackgroundColor = Color(0x212121)
|
||||
@@ -108,3 +108,10 @@ class DarkTheme : Theme() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WidgetTheme : LightTheme() {
|
||||
override val cardBackgroundColor = Color.TRANSPARENT
|
||||
override val highContrastTextColor = Color.WHITE
|
||||
override val mediumContrastTextColor = Color.WHITE.withAlpha(0.50)
|
||||
override val lowContrastTextColor = Color.WHITE.withAlpha(0.10)
|
||||
}
|
||||
@@ -46,6 +46,18 @@ class BarChartTest {
|
||||
assertRenders(300, 200, "$base/base.png", component)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDrawDarkTheme() = runBlocking {
|
||||
component.theme = DarkTheme()
|
||||
assertRenders(300, 200, "$base/themeDark.png", component)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDrawWidgetTheme() = runBlocking {
|
||||
component.theme = WidgetTheme()
|
||||
assertRenders(300, 200, "$base/themeWidget.png", component)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDrawWithOffset() = runBlocking {
|
||||
component.dataOffset = 5
|
||||
|
||||
@@ -31,6 +31,7 @@ import org.isoron.uhabits.core.ui.views.HistoryChart.Square.HATCHED
|
||||
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.OFF
|
||||
import org.isoron.uhabits.core.ui.views.HistoryChart.Square.ON
|
||||
import org.isoron.uhabits.core.ui.views.LightTheme
|
||||
import org.isoron.uhabits.core.ui.views.WidgetTheme
|
||||
import org.junit.Test
|
||||
import java.util.Locale
|
||||
|
||||
@@ -70,7 +71,6 @@ class HistoryChartTest {
|
||||
}
|
||||
|
||||
// TODO: Label overflow
|
||||
// TODO: Transparent
|
||||
// TODO: onClick
|
||||
// TODO: HistoryEditorDialog
|
||||
// TODO: Remove excessive padding on widgets
|
||||
@@ -89,7 +89,13 @@ class HistoryChartTest {
|
||||
@Test
|
||||
fun testDrawDarkTheme() = runBlocking {
|
||||
view.theme = DarkTheme()
|
||||
assertRenders(400, 200, "$base/dark.png", view)
|
||||
assertRenders(400, 200, "$base/themeDark.png", view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDrawWidgetTheme() = runBlocking {
|
||||
view.theme = WidgetTheme()
|
||||
assertRenders(400, 200, "$base/themeWidget.png", view)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||