-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathBouncingLogo.kt
More file actions
155 lines (131 loc) · 5.03 KB
/
Copy pathBouncingLogo.kt
File metadata and controls
155 lines (131 loc) · 5.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import config.Preferences
import config.Preferences.IS_DEBUG
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.useContents
import platform.AppKit.*
import platform.CoreGraphics.CGColorCreateSRGB
import platform.Foundation.NSBundle
import platform.Foundation.NSMakeRect
import platform.ScreenSaver.ScreenSaverView
import util.debugLog
import kotlin.math.pow
import kotlin.math.sqrt
import kotlin.random.Random
@OptIn(ExperimentalForeignApi::class)
class BouncingLogo(
private val view: ScreenSaverView,
private val bundle: NSBundle,
private val images: List<String>,
) {
private enum class Side {
Left,
Right,
Top,
Bottom,
}
private var xDelta = if (Random.nextBoolean()) 1.0 else -1.0
private var yDelta = if (Random.nextBoolean()) 1.0 else -1.0
// Initialized later based on image size
private var logoWidth = 0.0
private var logoHeight = 0.0
private val screenWidth = view.frame.useContents { this.size.width }
private val screenHeight = view.frame.useContents { this.size.height }
private var xPos: Double
private var yPos: Double
// Magic numbers 🪄✨
private var pxScale = ((screenWidth / 1728) + (screenHeight / 1117)) / 2
private val speed = pxScale * Preferences.SPEED / 10.0 * Random.nextDouble(0.9, 1.1)
init {
val margin = Preferences.LOGO_SIZE * pxScale
xPos = Random.nextDouble(margin, screenWidth - margin)
yPos = Random.nextDouble(margin, screenHeight - margin)
}
private val right: Double get() = xPos + logoWidth / 2
private val left: Double get() = xPos - logoWidth / 2
private val top: Double get() = yPos + logoHeight / 2
private val bottom: Double get() = yPos - logoHeight / 2
private var index = Random.nextInt(images.size)
private val imageView = NSImageView().apply {
if (Preferences.IS_DEBUG) {
wantsLayer = true
layer?.setBackgroundColor(CGColorCreateSRGB(1.0, 1.0, 1.0, 1.0))
}
imageScaling = NSImageScaleProportionallyUpOrDown
image = loadImage(index)
frame = NSMakeRect(x = xPos - logoWidth / 2, y = yPos - logoHeight / 2, w = logoWidth, h = logoHeight)
view.addSubview(this)
}
private val debugTextView by lazy {
NSTextView().apply {
textColor = NSColor.redColor
drawsBackground = false
view.addSubview(this)
}
}
fun draw() {
imageView.frame = NSMakeRect(x = xPos - logoWidth / 2, y = yPos - logoHeight / 2, w = logoWidth, h = logoHeight)
if (IS_DEBUG) {
debugTextView.frame = NSMakeRect(x = xPos - logoWidth / 2, y = yPos - logoHeight / 2 - 40.0, w = 200.0, h = 40.0)
debugTextView.string = """
${images[index]} (${logoWidth.toInt()}x${logoHeight.toInt()})
x: ${xPos.toInt()} y: ${yPos.toInt()}
speed $speed
""".trimIndent()
}
}
fun animateOneFrame() {
xPos += xDelta * speed
yPos += yDelta * speed
when {
xDelta > 0 && right >= screenWidth -> {
debugLog { "bounce(Right), $right (lw $logoWidth, lh $logoHeight) (x $xPos y $yPos)" }
xDelta = -1.0
bounce(Side.Right)
}
yDelta > 0 && top >= screenHeight -> {
debugLog { "bounce(Top), $top (lw $logoWidth, lh $logoHeight) (x $xPos y $yPos)" }
yDelta = -1.0
bounce(Side.Top)
}
xDelta < 0 && left <= 0 -> {
debugLog { "bounce(Left), $left (lw $logoWidth, lh $logoHeight) (x $xPos y $yPos)" }
xDelta = 1.0
bounce(Side.Left)
}
yDelta < 0 && bottom <= 0 -> {
debugLog { "bounce(Bottom), $bottom (lw $logoWidth, lh $logoHeight) (x $xPos y $yPos)" }
yDelta = 1.0
bounce(Side.Bottom)
}
}
}
private fun bounce(side: Side) {
index = (index + 1) % images.size
imageView.image = loadImage(index)
when (side) {
Side.Left -> xPos = logoWidth / 2
Side.Right -> xPos = screenWidth - logoWidth / 2
Side.Top -> yPos = screenHeight - logoHeight / 2
Side.Bottom -> yPos = logoHeight / 2
}
}
/**
* Sets logo width and heigh amounts so that their ratio matches
* the image ratio and their total area is LOGO_AREA.
*/
private fun loadImage(index: Int): NSImage {
return bundle.imageForResource(images[index])!!.also { img ->
val (w, h) = img.size.useContents { width to height }
debugLog { "Loaded image ${images[index]}, w $w h $h" }
val area = (Preferences.LOGO_SIZE.toDouble() * pxScale).pow(2)
logoHeight = sqrt(area / (w / h))
logoWidth = area / logoHeight
}
}
fun dispose() {
imageView.removeFromSuperview()
if (IS_DEBUG) {
debugTextView.removeFromSuperview()
}
}
}