diff --git a/docs/docs/user-guide/widgets/clock.md b/docs/docs/user-guide/widgets/clock.md index 742ccd98..f031c019 100644 --- a/docs/docs/user-guide/widgets/clock.md +++ b/docs/docs/user-guide/widgets/clock.md @@ -11,10 +11,11 @@ The clock widget has two layouts: ## Style -There are five different clock styles: +There are six different clock styles: - Fat digital clock - Boring digital clock +- Orbit clock - [Binary clock](https://en.wikipedia.org/wiki/Binary_clock#Binary-coded_sexagesimal_clocks) - Analog clock - Empty clock; in case you want to disable the clock altogether diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto index d12b9158..12100f01 100644 --- a/preferences/src/main/proto/settings.proto +++ b/preferences/src/main/proto/settings.proto @@ -111,6 +111,7 @@ message Settings { enum ClockStyle { DigitalClock1 = 0; DigitalClock2 = 1; + OrbitClock = 5; BinaryClock = 2; AnalogClock = 3; EmptyClock = 4; diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt index be46cf5d..427817af 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/ClockWidget.kt @@ -135,10 +135,11 @@ fun Clock( ) { val time = LocalTime.current when (style) { - ClockStyle.DigitalClock1 -> DigitalClock1(time, layout) + ClockStyle.DigitalClock1 -> DigitalClock1(time = time, layout = layout) ClockStyle.DigitalClock2 -> DigitalClock2(time, layout) ClockStyle.BinaryClock -> BinaryClock(time, layout) ClockStyle.AnalogClock -> AnalogClock(time, layout) + ClockStyle.OrbitClock -> OrbitClock(time, layout) ClockStyle.EmptyClock -> EmptyClock(time, layout) else -> {} } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt new file mode 100644 index 00000000..b19c26f7 --- /dev/null +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/clocks/OrbitClock.kt @@ -0,0 +1,128 @@ +package de.mm20.launcher2.ui.launcher.widgets.clock.clocks + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.center +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.PathEffect +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.drawText +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.unit.center +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.toOffset +import de.mm20.launcher2.preferences.Settings +import java.util.Calendar +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +@Composable +fun OrbitClock( + time: Long, + layout: Settings.ClockWidgetSettings.ClockWidgetLayout +) { + val verticalLayout = layout == Settings.ClockWidgetSettings.ClockWidgetLayout.Vertical + val date = Calendar.getInstance() + date.timeInMillis = time + val minute = date[Calendar.MINUTE] + val hour = date[Calendar.HOUR_OF_DAY] + + val mu by animateFloatAsState(minute / 60f * 2f * PI.toFloat()) + val heta by animateFloatAsState((hour % 12) / 12f * 2f * PI.toFloat() + (minute / 60f) * 1f / 6f * PI.toFloat()) + + val color = LocalContentColor.current + + val measurer = rememberTextMeasurer() + val textStyle = MaterialTheme.typography.labelMedium + + val strokeWidth = if (verticalLayout) 2.dp else 1.dp + + Canvas(modifier = Modifier + .padding(bottom = if (verticalLayout) 8.dp else 0.dp) + .size(if (verticalLayout) 192.dp else 56.dp) + ) { + val rm = size.width * 0.45f + val rh = size.width * 0.25f + drawCircle( + color = color.copy(alpha = 0.5f), + radius = rm, + style = Stroke(width = strokeWidth.toPx(), + pathEffect = PathEffect.dashPathEffect(floatArrayOf(4.dp.toPx(), 4.dp.toPx())) + ) + ) + drawCircle( + color = color.copy(alpha = 0.5f), + radius = rh, + style = Stroke(width = strokeWidth.toPx(), + pathEffect = PathEffect.dashPathEffect(floatArrayOf(4.dp.toPx(), 4.dp.toPx())), + ) + ) + + val mPos = Offset(x = sin(mu) * rm, y = -cos(mu) * rm) + val hPos = Offset(x = sin(heta) * rh, y = -cos(heta) * rh) + + drawCircle( + color = Color.Black, + radius = size.width * 0.08f, + center = size.center + mPos, + blendMode = BlendMode.DstOut + ) + drawCircle( + color = Color.Black, + radius = size.width * 0.08f, + center = size.center + hPos, + blendMode = BlendMode.DstOut + ) + + if (verticalLayout) { + + val textMResult = measurer.measure( + AnnotatedString(minute.toString()), + maxLines = 1, + style = textStyle + ) + + val textHResult = measurer.measure( + AnnotatedString(hour.toString()), + maxLines = 1, + style = textStyle + ) + + drawText( + textMResult, + color = Color.Black, + topLeft = size.center - textMResult.size.center.toOffset() + mPos + ) + drawText( + textHResult, + color = Color.Black, + topLeft = size.center - textHResult.size.center.toOffset() + hPos + ) + } + + drawCircle( + color = color, + radius = size.width * 0.08f, + center = size.center + hPos, + blendMode = BlendMode.SrcOut + ) + drawCircle( + color = color, + radius = size.width * 0.08f, + center = size.center + mPos, + blendMode = BlendMode.SrcOut + ) + + } +} \ No newline at end of file