mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-21 18:36:17 -04:00
add badge to display unread messages count, and set flash time to 5s, isolate flash logic in a new class AlternatingStateManager
This commit is contained in:
parent
437f89bffc
commit
16c561b9a1
52
core/src/com/unciv/logic/AlternatingStateManager.kt
Normal file
52
core/src/com/unciv/logic/AlternatingStateManager.kt
Normal file
@ -0,0 +1,52 @@
|
||||
package com.unciv.logic
|
||||
|
||||
import com.unciv.utils.Concurrency
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
class AlternatingStateManager<State>(
|
||||
private val name: String,
|
||||
private val state: State,
|
||||
private val originalState: (State) -> Unit,
|
||||
private val alternateState: (State) -> Unit
|
||||
) {
|
||||
private var job: Job? = null
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun start(
|
||||
duration: Duration = 5.seconds, interval: Duration = 500.milliseconds
|
||||
) {
|
||||
job?.cancel()
|
||||
job = Concurrency.run(name) {
|
||||
val startTime = Clock.System.now()
|
||||
|
||||
var isAlternateState = true
|
||||
while (true) {
|
||||
if (isAlternateState) {
|
||||
alternateState(state)
|
||||
isAlternateState = false
|
||||
} else {
|
||||
originalState(state)
|
||||
isAlternateState = true
|
||||
}
|
||||
|
||||
if (Clock.System.now() - startTime >= duration) {
|
||||
originalState(state)
|
||||
return@run
|
||||
}
|
||||
|
||||
delay(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
job?.cancel()
|
||||
originalState(state)
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import java.util.UUID
|
||||
data class Chat(
|
||||
val gameId: UUID,
|
||||
) {
|
||||
var read = true
|
||||
var unreadCount = 0
|
||||
|
||||
// <civName, message> pairs
|
||||
private val messages: MutableList<Pair<String, String>> = mutableListOf(INITIAL_MESSAGE)
|
||||
@ -100,13 +100,13 @@ object ChatStore {
|
||||
if (gameId.equals(UncivGame.Current.worldScreen?.gameInfo?.gameId?.toUUIDOrNull())) {
|
||||
// ensures that you are not getting notified for your own messages
|
||||
if (UncivGame.Current.worldScreen?.gameInfo?.currentPlayer != incomingChatMsg.civName) {
|
||||
chat.read = false
|
||||
UncivGame.Current.worldScreen?.chatButton?.startFlashing()
|
||||
chat.unreadCount++
|
||||
UncivGame.Current.worldScreen?.chatButton?.triggerChatIndication()
|
||||
}
|
||||
} else {
|
||||
// user is out of world screen or
|
||||
// some other game not currently on screen has a message
|
||||
chat.read = false
|
||||
chat.unreadCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,7 +126,7 @@ object ChatStore {
|
||||
Gdx.app.postRunnable {
|
||||
if (civName != "System") {
|
||||
hasGlobalMessage = chatPopup == null
|
||||
if (hasGlobalMessage) UncivGame.Current.worldScreen?.chatButton?.startFlashing()
|
||||
if (hasGlobalMessage) UncivGame.Current.worldScreen?.chatButton?.triggerChatIndication()
|
||||
}
|
||||
|
||||
chatPopup?.addMessage(civName, message, suffix = "one time")
|
||||
|
@ -2,36 +2,88 @@ package com.unciv.ui.screens.worldscreen.chat
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.AlternatingStateManager
|
||||
import com.unciv.logic.multiplayer.chat.ChatStore
|
||||
import com.unciv.logic.multiplayer.chat.ChatWebSocket
|
||||
import com.unciv.logic.multiplayer.chat.Message
|
||||
import com.unciv.ui.components.extensions.disable
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.images.IconTextButton
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
import com.unciv.utils.Concurrency
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class ChatButton(val worldScreen: WorldScreen) : IconTextButton(
|
||||
"Chat", ImageGetter.getImage("OtherIcons/Chat"), 23
|
||||
), Disposable {
|
||||
) {
|
||||
val chat = ChatStore.getChatByGameId(worldScreen.gameInfo.gameId)
|
||||
|
||||
val badge = "".toTextButton().apply {
|
||||
disable()
|
||||
setColor(Color.valueOf("da1e28"))
|
||||
label.setColor(Color.WHITE)
|
||||
label.setAlignment(Align.center)
|
||||
label.setFontScale(0.2f)
|
||||
}
|
||||
|
||||
val flash = AlternatingStateManager(
|
||||
name = "ChatButton color flash",
|
||||
state = object {
|
||||
val initialColor = fontColor
|
||||
val targetColor = Color.ORANGE
|
||||
}, originalState = { state ->
|
||||
Gdx.app.postRunnable {
|
||||
icon?.color = state.initialColor
|
||||
label.color = state.initialColor
|
||||
}
|
||||
}, alternateState = { state ->
|
||||
Gdx.app.postRunnable {
|
||||
icon?.color = state.targetColor
|
||||
label.color = state.targetColor
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private fun updateBadge(visible: Boolean = false) {
|
||||
badge.height = height / 3
|
||||
badge.setPosition(
|
||||
width - badge.width / 1.5f,
|
||||
height - badge.height / 1.5f
|
||||
)
|
||||
|
||||
badge.isVisible = visible || chat.unreadCount > 0 || ChatStore.hasGlobalMessage
|
||||
|
||||
if (badge.isVisible) {
|
||||
var text = chat.unreadCount.toString()
|
||||
if (ChatStore.hasGlobalMessage) {
|
||||
text += '+'
|
||||
}
|
||||
badge.setText(text)
|
||||
}
|
||||
}
|
||||
|
||||
fun triggerChatIndication() {
|
||||
updateBadge()
|
||||
flash.start()
|
||||
}
|
||||
|
||||
init {
|
||||
width = 95f
|
||||
iconCell.pad(3f).center()
|
||||
addActor(badge)
|
||||
|
||||
if (!chat.read || ChatStore.hasGlobalMessage) {
|
||||
startFlashing()
|
||||
if (chat.unreadCount > 0 || ChatStore.hasGlobalMessage) {
|
||||
updateBadge(true)
|
||||
flash.stop()
|
||||
}
|
||||
|
||||
onClick {
|
||||
stopFlashing()
|
||||
chat.unreadCount = 0
|
||||
updateBadge()
|
||||
flash.stop()
|
||||
|
||||
ChatPopup(chat, worldScreen).open()
|
||||
}
|
||||
|
||||
@ -50,6 +102,7 @@ class ChatButton(val worldScreen: WorldScreen) : IconTextButton(
|
||||
Message.Join(listOf(worldScreen.gameInfo.gameId)),
|
||||
)
|
||||
updatePosition()
|
||||
updateBadge()
|
||||
true
|
||||
} else {
|
||||
ChatWebSocket.stop()
|
||||
@ -61,38 +114,4 @@ class ChatButton(val worldScreen: WorldScreen) : IconTextButton(
|
||||
worldScreen.techPolicyAndDiplomacy.x.coerceAtLeast(1f),
|
||||
worldScreen.techPolicyAndDiplomacy.y - height - 1f
|
||||
)
|
||||
|
||||
private var flashJob: Job? = null
|
||||
|
||||
fun startFlashing(targetColor: Color = Color.ORANGE, interval: Duration = 500.milliseconds) {
|
||||
flashJob?.cancel()
|
||||
flashJob = Concurrency.run("ChatButton color flash") {
|
||||
var isAlternatingColor = true
|
||||
while (true) {
|
||||
Gdx.app.postRunnable {
|
||||
if (isAlternatingColor) {
|
||||
icon?.color = targetColor
|
||||
label.color = targetColor
|
||||
} else {
|
||||
icon?.color = fontColor
|
||||
label.color = fontColor
|
||||
}
|
||||
|
||||
isAlternatingColor = !isAlternatingColor
|
||||
}
|
||||
|
||||
delay(interval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopFlashing() {
|
||||
flashJob?.cancel()
|
||||
icon?.color = fontColor
|
||||
label.color = fontColor
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
flashJob?.cancel()
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ class ChatPopup(
|
||||
)
|
||||
|
||||
init {
|
||||
chat.read = true
|
||||
ChatStore.chatPopup = this
|
||||
ChatStore.hasGlobalMessage = false
|
||||
chatTable.defaults().growX().pad(5f).center()
|
||||
|
@ -1,21 +1,22 @@
|
||||
package com.unciv.ui.screens.worldscreen.status
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Button
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Cell
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.badlogic.gdx.utils.Disposable
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.AlternatingStateManager
|
||||
import com.unciv.logic.event.EventBus
|
||||
import com.unciv.logic.multiplayer.HasMultiplayerGameName
|
||||
import com.unciv.logic.multiplayer.MultiplayerGameNameChanged
|
||||
import com.unciv.logic.multiplayer.MultiplayerGamePreview
|
||||
import com.unciv.logic.multiplayer.MultiplayerGameUpdateEnded
|
||||
import com.unciv.logic.multiplayer.MultiplayerGameUpdateStarted
|
||||
import com.unciv.logic.multiplayer.MultiplayerGameUpdated
|
||||
import com.unciv.logic.multiplayer.MultiplayerGamePreview
|
||||
import com.unciv.logic.multiplayer.isUsersTurn
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.components.extensions.setSize
|
||||
@ -24,20 +25,20 @@ import com.unciv.ui.components.widgets.LoadingImage
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.utils.Concurrency
|
||||
import com.unciv.utils.launchOnGLThread
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class MultiplayerStatusButton(
|
||||
screen: BaseScreen,
|
||||
curGame: MultiplayerGamePreview?
|
||||
) : Button(BaseScreen.skin), Disposable {
|
||||
private var curGameName = curGame?.name
|
||||
private val loadingImage = LoadingImage(style = LoadingImage.Style(
|
||||
idleImageName = "OtherIcons/Multiplayer",
|
||||
idleIconColor = Color.WHITE,
|
||||
minShowTime = 500
|
||||
))
|
||||
private val loadingImage = LoadingImage(
|
||||
style = LoadingImage.Style(
|
||||
idleImageName = "OtherIcons/Multiplayer",
|
||||
idleIconColor = Color.WHITE,
|
||||
minShowTime = 500
|
||||
)
|
||||
)
|
||||
private val turnIndicator = TurnIndicator()
|
||||
private val turnIndicatorCell: Cell<Actor>
|
||||
private val gameNamesWithCurrentTurn = getInitialGamesWithCurrentTurn()
|
||||
@ -96,7 +97,7 @@ class MultiplayerStatusButton(
|
||||
}
|
||||
|
||||
private fun updateTurnIndicator(flash: Boolean = true) {
|
||||
if (gameNamesWithCurrentTurn.size == 0) {
|
||||
if (gameNamesWithCurrentTurn.isEmpty()) {
|
||||
turnIndicatorCell.clearActor()
|
||||
} else {
|
||||
turnIndicatorCell.setActor(turnIndicator)
|
||||
@ -105,7 +106,7 @@ class MultiplayerStatusButton(
|
||||
|
||||
// flash so the user sees an better update
|
||||
if (flash) {
|
||||
turnIndicator.flash()
|
||||
turnIndicator.flash.start(3.seconds)
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,10 +119,9 @@ class MultiplayerStatusButton(
|
||||
|
||||
private class TurnIndicator : HorizontalGroup(), Disposable {
|
||||
val gameAmount = Label("2", BaseScreen.skin)
|
||||
val image: Image
|
||||
private var job: Job? = null
|
||||
val image = ImageGetter.getImage("OtherIcons/ExclamationMark")
|
||||
|
||||
init {
|
||||
image = ImageGetter.getImage("OtherIcons/ExclamationMark")
|
||||
image.setSize(30f)
|
||||
addActor(image)
|
||||
}
|
||||
@ -135,23 +135,25 @@ private class TurnIndicator : HorizontalGroup(), Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
fun flash() {
|
||||
// using a gdx Action would be nicer, but we don't necessarily have continuousRendering on and we still want to flash
|
||||
flash(6, Color.WHITE, Color.ORANGE)
|
||||
}
|
||||
private fun flash(alternations: Int, curColor: Color, nextColor: Color) {
|
||||
if (alternations == 0) return
|
||||
gameAmount.color = nextColor
|
||||
image.color = nextColor
|
||||
job = Concurrency.run("StatusButton color flash") {
|
||||
delay(500)
|
||||
launchOnGLThread {
|
||||
flash(alternations - 1, nextColor, curColor)
|
||||
val flash = AlternatingStateManager(
|
||||
name = "StatusButton color flash",
|
||||
state = object {
|
||||
val initialColor = Color.WHITE
|
||||
val targetColor = Color.ORANGE
|
||||
}, originalState = { state ->
|
||||
Gdx.app.postRunnable {
|
||||
image.color = state.initialColor
|
||||
gameAmount.color = state.initialColor
|
||||
}
|
||||
}, alternateState = { state ->
|
||||
Gdx.app.postRunnable {
|
||||
image.color = state.targetColor
|
||||
gameAmount.color = state.targetColor
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
override fun dispose() {
|
||||
job?.cancel()
|
||||
flash.stop()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user