Skip to content

Commit

Permalink
Defer icon size validation
Browse files Browse the repository at this point in the history
  • Loading branch information
FWDekker committed Oct 15, 2024
1 parent 86fc14a commit 2261657
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 55 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Changelog
## 9.9.9-unreleased
### Fixed
* Fixed bug reported to also check closed issues when checking for duplicates.
* Hopefully fix "Must be not computed before that call" bug by deferring icon validation to painting. ([#R1](https://github.com/FWDekkerBot/intellij-randomness-issues/issues/1)) ([#R13](https://github.com/FWDekkerBot/intellij-randomness-issues/issues/13)) ([IJPL-163887](https://youtrack.jetbrains.com/issue/IJPL-163887/))
* Fixed bug reporter to also check closed issues when checking for duplicates.


## 3.3.2 -- 2024-09-28
Expand Down
47 changes: 26 additions & 21 deletions src/main/kotlin/com/fwdekker/randomness/Icons.kt
Original file line number Diff line number Diff line change
Expand Up @@ -229,27 +229,10 @@ data class OverlayIcon(val base: Icon, val background: Icon? = null) : Icon {
* @property overlays The various icons that are overlayed on top of [base].
*/
data class OverlayedIcon(val base: Icon, val overlays: List<Icon> = emptyList()) : Icon {
init {
val baseWidth = base.iconWidth
val baseHeight = base.iconHeight

require(baseWidth == baseHeight) {
Triple(
"Base must be square, but was ${baseWidth}x$baseHeight. " +
"Repeat: ${base.iconWidth}x${base.iconHeight}. " +
"Repeat: ${base.iconWidth}x${base.iconHeight}.",
base,
overlays
)
}
overlays.forEachIndexed { idx, item ->
require(item.iconWidth == item.iconHeight) {
Triple("Overlay $idx must be square, but was ${item.iconWidth}x${item.iconHeight}.", base, overlays)
}
}
require(overlays.all { it.iconWidth == it.iconHeight }) { "Overlays must be square." }
require(overlays.map { it.iconWidth }.toSet().size <= 1) { "All overlays must have same size." }
}
/**
* @see validate
*/
private var validated: Boolean = false


/**
Expand All @@ -269,6 +252,8 @@ data class OverlayedIcon(val base: Icon, val overlays: List<Icon> = emptyList())
override fun paintIcon(c: Component?, g: Graphics?, x: Int, y: Int) {
if (c == null || g == null) return

validate()

base.paintIcon(c, g, x, y)
overlays.forEachIndexed { i, overlay ->
val overlaySize = iconWidth.toFloat() / OVERLAYS_PER_ROW
Expand All @@ -290,6 +275,26 @@ data class OverlayedIcon(val base: Icon, val overlays: List<Icon> = emptyList())
override fun getIconHeight() = base.iconHeight


/**
* Lazily validates the relative sizes of the [base] and the [overlays].
*
* This code must *not* be called in the constructor; it should be deferred to some later point. Calling this in the
* constructor *will* cause exceptions, because the constructor is called in the constructor of various actions, and
* actions are constructor before UI scaling is initialized.
*
* See also https://youtrack.jetbrains.com/issue/IJPL-163887/
*/
private fun validate() {
if (validated) return

require(base.iconWidth == base.iconHeight) { "Base must be square." }
require(overlays.all { it.iconWidth == it.iconHeight }) { "All overlays must be square." }
require(overlays.map { it.iconWidth }.toSet().size <= 1) { "All overlays must have same size." }

validated = true
}


/**
* Holds constants.
*/
Expand Down
74 changes: 41 additions & 33 deletions src/test/kotlin/com/fwdekker/randomness/IconsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package com.fwdekker.randomness

import com.fwdekker.randomness.testhelpers.Tags
import com.fwdekker.randomness.testhelpers.afterNonContainer
import com.fwdekker.randomness.testhelpers.beforeNonContainer
import com.fwdekker.randomness.testhelpers.guiGet
import io.kotest.assertions.throwables.shouldThrow
Expand All @@ -15,12 +16,12 @@ import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNot
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldStartWith
import io.kotest.matchers.types.shouldBeSameInstanceAs
import org.assertj.swing.edt.FailOnThreadViolationRepaintManager
import java.awt.Color
import java.awt.Component
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.image.BufferedImage
import javax.swing.Icon
import javax.swing.JLabel
Expand Down Expand Up @@ -212,19 +213,47 @@ object OverlayIconTest : FunSpec({
* Unit tests for [OverlayedIcon].
*/
object OverlayedIconTest : FunSpec({
context("constructor") {
lateinit var image: BufferedImage
lateinit var graphics: Graphics2D


beforeSpec {
FailOnThreadViolationRepaintManager.install()
}

afterSpec {
FailOnThreadViolationRepaintManager.uninstall()
}

beforeNonContainer {
image = BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB)
graphics = image.createGraphics()
}

afterNonContainer {
graphics.dispose()
}


context("deferred validation") {
test("fails if the base image is not square") {
shouldThrow<IllegalArgumentException> { OverlayedIcon(PlainIcon(186, 132), emptyList()) }
.message shouldStartWith "(Base must be square, but was"
val icon = OverlayedIcon(PlainIcon(186, 132), emptyList())

shouldThrow<IllegalArgumentException> { icon.paintIcon(guiGet { JLabel() }, graphics, 0, 0) }
.message shouldBe "Base must be square."
}

test("fails if an overlay is not square") {
shouldThrow<IllegalArgumentException> { OverlayedIcon(PlainIcon(), listOf(PlainIcon(), PlainIcon(38, 40))) }
.message shouldStartWith Regex("\\(Overlay [1-9] must be square, but was")
val icon = OverlayedIcon(PlainIcon(), listOf(PlainIcon(), PlainIcon(38, 40)))

shouldThrow<IllegalArgumentException> { icon.paintIcon(guiGet { JLabel() }, graphics, 0, 0) }
.message shouldBe "All overlays must be square."
}

test("fails if overlays have different sizes") {
shouldThrow<IllegalArgumentException> { OverlayedIcon(PlainIcon(), listOf(PlainIcon(), PlainIcon(34, 34))) }
val icon = OverlayedIcon(PlainIcon(), listOf(PlainIcon(), PlainIcon(34, 34)))

shouldThrow<IllegalArgumentException> { icon.paintIcon(guiGet { JLabel() }, graphics, 0, 0) }
.message shouldBe "All overlays must have same size."
}
}
Expand All @@ -241,45 +270,24 @@ object OverlayedIconTest : FunSpec({


context("paintIcon").config(tags = setOf(Tags.SWING)) {
lateinit var image: BufferedImage


beforeContainer {
FailOnThreadViolationRepaintManager.install()
}

beforeNonContainer {
image = BufferedImage(32, 32, BufferedImage.TYPE_INT_ARGB)
}


test("paints nothing if the component is null") {
with(image.createGraphics()) {
OverlayedIcon(PlainIcon(), listOf(PlainIcon())).paintIcon(null, this, 0, 0)
this.dispose()
}
OverlayedIcon(PlainIcon(), listOf(PlainIcon())).paintIcon(null, graphics, 0, 0)

image.getRGB(0, 0, 32, 32, null, 0, 32).forEach { it shouldBe 0 }
}

test("paints the base icon only if no overlays are specified") {
with(image.createGraphics()) {
val label = JLabel()
val label = guiGet { JLabel() }

OverlayedIcon(PlainIcon(color = Color.GREEN.rgb)).paintIcon(label, this, 0, 0)
this.dispose()
}
OverlayedIcon(PlainIcon(color = Color.GREEN.rgb)).paintIcon(label, graphics, 0, 0)

image.getRGB(0, 0, 32, 32, null, 0, 32).forEach { it shouldBe Color.GREEN.rgb }
}

test("paints the overlays starting in the top-left corner") {
with(image.createGraphics()) {
val label = JLabel().also { it.background = Color.BLUE }
val label = guiGet { JLabel().also { it.background = Color.BLUE } }

OverlayedIcon(PlainIcon(), listOf(PlainIcon(color = Color.BLUE.rgb))).paintIcon(label, this, 0, 0)
this.dispose()
}
OverlayedIcon(PlainIcon(), listOf(PlainIcon(color = Color.BLUE.rgb))).paintIcon(label, graphics, 0, 0)

image.getRGB(0, 0, 16, 16, null, 0, 16).forEach { it shouldBe Color.BLUE.rgb }
image.getRGB(16, 16, 16, 16, null, 0, 16).forEach { it shouldNotBe Color.BLUE.rgb }
Expand Down

0 comments on commit 2261657

Please sign in to comment.