From 30b2e5e29a52617f5bd3c2e11b1fa3b50498fba3 Mon Sep 17 00:00:00 2001 From: Andreas Tennert Date: Mon, 15 Mar 2021 19:19:50 +0100 Subject: [PATCH] feat: animate the filler widgets --- CHANGELOG | 2 +- .../lcarsde/statusbar/FillerAnimation.kt | 48 ++++++++++ .../atennert/lcarsde/statusbar/StatusBar.kt | 5 + .../statusbar/widgets/StatusFillerWidget.kt | 93 ++++++++++++++++--- 4 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/FillerAnimation.kt diff --git a/CHANGELOG b/CHANGELOG index aa0b103..650d66b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,6 @@ 21.3 * add widget for displaying memory (RAM) status -* randomly hide filler widgets +* animate filler widgets 21.2 * add linking for systems with /usr/lib64 instead of /usr/lib diff --git a/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/FillerAnimation.kt b/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/FillerAnimation.kt new file mode 100644 index 0000000..d544fea --- /dev/null +++ b/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/FillerAnimation.kt @@ -0,0 +1,48 @@ +package de.atennert.lcarsde.statusbar + +import de.atennert.lcarsde.statusbar.widgets.StatusFillerWidget +import kotlinx.coroutines.* +import kotlin.random.Random + +/** + * Controls the update/animation lifecycle for all filler widgets. + */ +class FillerAnimation { + + private val fillerWidgets = mutableListOf() + + private var updateJob: Job? = null + + private val minimalSwitchDelay = 2_000L // ms + private val maximalSwitchDelay = 10_000L // ms + + fun addFillerWidget(fillerWidget: StatusFillerWidget) { + fillerWidgets.add(fillerWidget) + } + + fun clearWidgets() { + fillerWidgets.clear() + } + + private fun animateFiller() { + val widget = fillerWidgets.randomOrNull() ?: return + + widget.update() + } + + fun startAnimation() { + fillerWidgets.forEach(StatusFillerWidget::start) + updateJob = GlobalScope.launch(Dispatchers.Unconfined) { + while (true) { + delay(Random.nextLong(minimalSwitchDelay, maximalSwitchDelay)) + animateFiller() + } + } + } + + fun stopAnimation() { + updateJob?.cancel() + updateJob = null + fillerWidgets.forEach(StatusFillerWidget::stop) + } +} \ No newline at end of file diff --git a/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/StatusBar.kt b/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/StatusBar.kt index af0a09b..198b584 100644 --- a/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/StatusBar.kt +++ b/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/StatusBar.kt @@ -38,6 +38,7 @@ class StatusBar { private val widgetFactory = WidgetFactory(cssProvider) private var widgets = emptyList() + private val fillerAnimation = FillerAnimation() private var currentWidth = 40 // px private var initialized = false @@ -70,6 +71,7 @@ class StatusBar { val (horizontalCells, leftOverPixels) = getCellsAndOverflow() if (initialized) { stop() + fillerAnimation.clearWidgets() for (i in 1..3) { gtk_grid_remove_row(grid.reinterpret(), 0) @@ -153,6 +155,7 @@ class StatusBar { val configuration = WidgetConfiguration("", 0, 0, 2, 1) .withAddedPx(addedPixelsPerFiller[col]) val filler = StatusFillerWidget(configuration, cssProvider) + fillerAnimation.addFillerWidget(filler) gtk_grid_attach(grid.reinterpret(), filler.widget, col * 2, row, 2, 1) } } @@ -171,9 +174,11 @@ class StatusBar { private fun start() { widgets.forEach(StatusWidget::start) + fillerAnimation.startAnimation() } fun stop() { + fillerAnimation.stopAnimation() widgets.forEach(StatusWidget::stop) } } diff --git a/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/widgets/StatusFillerWidget.kt b/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/widgets/StatusFillerWidget.kt index 90a2312..96178df 100644 --- a/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/widgets/StatusFillerWidget.kt +++ b/src/nativeMain/kotlin/de/atennert/lcarsde/statusbar/widgets/StatusFillerWidget.kt @@ -1,33 +1,104 @@ package de.atennert.lcarsde.statusbar.widgets import de.atennert.lcarsde.statusbar.configuration.WidgetConfiguration -import de.atennert.lcarsde.statusbar.extensions.setStyling -import kotlinx.cinterop.CPointer -import kotlinx.cinterop.reinterpret +import de.atennert.lcarsde.statusbar.extensions.gSignalConnect +import kotlinx.cinterop.* import statusbar.* +import kotlin.math.PI import kotlin.random.Random class StatusFillerWidget(widgetConfiguration: WidgetConfiguration, cssProvider: CPointer) : StatusWidget(widgetConfiguration, cssProvider, null) { + private var ref: StableRef? = null + + private var lastColor = "123" + init { - val text = "${Random.nextInt(10000)}".padStart(4, '0') - widget = gtk_label_new(text)!! + widget = gtk_drawing_area_new()!! gtk_widget_set_size_request(widget, widthPx, heightPx) - gtk_label_set_xalign(widget.reinterpret(), 1f) - gtk_label_set_yalign(widget.reinterpret(), 1f) + } + + override fun start() { + ref = StableRef.create(this) + + gSignalConnect(widget, "draw", + staticCFunction { _: CPointer, c: CPointer, p: COpaquePointer -> draw(c, p) }, + ref!!.asCPointer()) - val colorIdx = Random.nextInt(colors.size) - val color = colors[colorIdx] + super.start() - widget.setStyling(cssProvider, "button--$color", "button--long") + update() + } + + override fun stop() { + super.stop() + + ref!!.dispose() } override fun update() { - // Nothing to do + gtk_widget_queue_draw(widget) } companion object { private val colors = arrayOf("c9c", "99c", "f96", "000") + + private fun draw(context: CPointer, ref: COpaquePointer) { + val widget = ref.asStableRef().get() + + val availableColors = colors.filterNot { it == widget.lastColor } + val colorIdx = Random.nextInt(availableColors.size) + val newColor = availableColors[colorIdx] + val text = "${Random.nextInt(10000)}".padStart(4, '0') + widget.lastColor = newColor + + cairo_set_source_rgb(context, convertColor(newColor[0]), convertColor(newColor[1]), convertColor(newColor[2])) + createBorderPath(context, widget) + cairo_fill(context) + + drawText(context, widget, text) + } + + private fun convertColor(char: Char): Double { + return when(char) { + 'f' -> 1.0 + 'c' -> 0.8 + '9' -> 0.6 + '6' -> 0.4 + else -> 0.0 + } + } + + private fun createBorderPath(context: CPointer, widget: StatusFillerWidget) { + cairo_arc(context, 20.0, 20.0, 20.0, 1.0 * PI, 1.5 * PI) + cairo_line_to(context, widget.widthPx - 20.0, 0.0) + cairo_arc(context, widget.widthPx - 20.0, 20.0, 20.0, 1.5 * PI, 2.0 * PI) + cairo_line_to(context, widget.widthPx.toDouble(), widget.heightPx - 20.0) + cairo_arc(context, widget.widthPx - 20.0, widget.heightPx - 20.0, 20.0, 0.0 * PI, 0.5 * PI) + cairo_line_to(context, 20.0, widget.heightPx.toDouble()) + cairo_arc(context, 20.0, widget.heightPx - 20.0, 20.0, 0.5 * PI, 1.0 * PI) + cairo_close_path(context) + } + + private fun drawText(context: CPointer, widget: StatusFillerWidget, text: String) { + cairo_set_source_rgb(context, 0.0, 0.0, 0.0) + val layout = pango_cairo_create_layout(context)!! + + val fontDescription = pango_font_description_from_string("Ubuntu Condensed, 12") + pango_layout_set_font_description(layout, fontDescription) + pango_layout_set_text(layout, text, text.length) + + val layoutSize = IntArray(2) + pango_layout_get_size(layout, layoutSize.refTo(0), layoutSize.refTo(1)) + cairo_move_to(context, + widget.widthPx - (layoutSize[0].toFloat() / 1024.0) - 16, + widget.heightPx - (layoutSize[1].toFloat() / 1024.0) + ) + + pango_cairo_show_layout(context, layout) + pango_font_description_free(fontDescription) + g_object_unref(layout) + } } } \ No newline at end of file