mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-15 10:18:26 +07:00
Upgraded music player popup (#9514)
* Popups get the ability to scroll only the content without the buttons * Centralize LoadingPopup * Split non-WorldScreenMenuPopup classes off from that file * Linting * Nicer music playback dialog * Translation templates
This commit is contained in:
@ -785,6 +785,9 @@ Currently playing: [title] =
|
|||||||
Download music =
|
Download music =
|
||||||
Downloading... =
|
Downloading... =
|
||||||
Could not download music! =
|
Could not download music! =
|
||||||
|
—Paused— =
|
||||||
|
—Default— =
|
||||||
|
—History— =
|
||||||
|
|
||||||
## Advanced tab
|
## Advanced tab
|
||||||
Advanced =
|
Advanced =
|
||||||
|
@ -7,8 +7,8 @@ import com.badlogic.gdx.files.FileHandle
|
|||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.multiplayer.storage.DropBox
|
import com.unciv.logic.multiplayer.storage.DropBox
|
||||||
import com.unciv.models.metadata.GameSettings
|
import com.unciv.models.metadata.GameSettings
|
||||||
|
import com.unciv.utils.Concurrency
|
||||||
import com.unciv.utils.Log
|
import com.unciv.utils.Log
|
||||||
import com.unciv.utils.debug
|
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import java.util.Timer
|
import java.util.Timer
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -58,10 +58,35 @@ class MusicController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Container for track info - used for [onChange] and [getHistory].
|
||||||
|
*
|
||||||
|
* [toString] returns a prettified label: "Modname: Track".
|
||||||
|
* No track playing is reported as a MusicTrackInfo instance with all
|
||||||
|
* fields empty, for which _`toString`_ returns "—Paused—".
|
||||||
|
*/
|
||||||
|
data class MusicTrackInfo(val mod: String, val track: String, val type: String) {
|
||||||
|
/** Used for display, not only debugging */
|
||||||
|
override fun toString() = if (track.isEmpty()) "—Paused—" // using em-dash U+2014
|
||||||
|
else if (mod.isEmpty()) track else "$mod: $track"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** Parse a path - must be relative to `Gdx.files.local` */
|
||||||
|
fun parse(fileName: String): MusicTrackInfo {
|
||||||
|
if (fileName.isEmpty())
|
||||||
|
return MusicTrackInfo("", "", "")
|
||||||
|
val fileNameParts = fileName.split('/')
|
||||||
|
val modName = if (fileNameParts.size > 1 && fileNameParts[0] == "mods") fileNameParts[1] else ""
|
||||||
|
var trackName = fileNameParts[if (fileNameParts.size > 3 && fileNameParts[2] == "music") 3 else 1]
|
||||||
|
val type = gdxSupportedFileExtensions.firstOrNull {trackName.endsWith(".$it") } ?: ""
|
||||||
|
trackName = trackName.removeSuffix(".$type")
|
||||||
|
return MusicTrackInfo(modName, trackName, type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//region Fields
|
//region Fields
|
||||||
/** mirrors [GameSettings.musicVolume] - use [setVolume] to update */
|
/** mirrors [GameSettings.musicVolume] - use [setVolume] to update */
|
||||||
var baseVolume: Float = UncivGame.Current.settings.musicVolume
|
private var baseVolume: Float = UncivGame.Current.settings.musicVolume
|
||||||
private set
|
|
||||||
|
|
||||||
/** Pause in seconds between tracks unless [chooseTrack] is called to force a track change */
|
/** Pause in seconds between tracks unless [chooseTrack] is called to force a track change */
|
||||||
var silenceLength: Float
|
var silenceLength: Float
|
||||||
@ -102,8 +127,8 @@ class MusicController {
|
|||||||
/** Keeps paths of recently played track to reduce repetition */
|
/** Keeps paths of recently played track to reduce repetition */
|
||||||
private val musicHistory = ArrayDeque<String>(musicHistorySize)
|
private val musicHistory = ArrayDeque<String>(musicHistorySize)
|
||||||
|
|
||||||
/** One potential listener gets notified when track changes */
|
/** These listeners get notified when track changes */
|
||||||
private var onTrackChangeListener: ((String)->Unit)? = null
|
private var onTrackChangeListeners = mutableListOf<(MusicTrackInfo)->Unit>()
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
//region Pure functions
|
//region Pure functions
|
||||||
@ -127,13 +152,17 @@ class MusicController {
|
|||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Registers a callback that will be called with the new track name every time it changes.
|
/** Registers a callback that will be called with the new track every time it changes.
|
||||||
* The track name will be prettified ("Modname: Track" instead of "mods/Modname/music/Track.ogg").
|
|
||||||
*
|
*
|
||||||
* Will be called on a background thread, so please decouple UI access on the receiving side.
|
* The track is given as [MusicTrackInfo], which has a `toString` that returns it prettified.
|
||||||
|
*
|
||||||
|
* Several callbacks can be registered, but a onChange(null) clears them all.
|
||||||
|
*
|
||||||
|
* Callbacks will be safely called on the GL thread.
|
||||||
*/
|
*/
|
||||||
fun onChange(listener: ((String)->Unit)?) {
|
fun onChange(listener: ((MusicTrackInfo)->Unit)?) {
|
||||||
onTrackChangeListener = listener
|
if (listener == null) onTrackChangeListeners.clear()
|
||||||
|
else onTrackChangeListeners.add(listener)
|
||||||
fireOnChange()
|
fireOnChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +176,14 @@ class MusicController {
|
|||||||
return current?.isPlaying() == true
|
return current?.isPlaying() == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return Sequence of most recently played tracks, oldest first, current last */
|
||||||
|
fun getHistory() = musicHistory.asSequence().map { MusicTrackInfo.parse(it) }
|
||||||
|
|
||||||
|
/** @return Sequence of all available and enabled music tracks */
|
||||||
|
fun getAllMusicFileInfo() = getAllMusicFiles().map {
|
||||||
|
MusicTrackInfo.parse(it.path())
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
//region Internal helpers
|
//region Internal helpers
|
||||||
|
|
||||||
@ -233,7 +270,7 @@ class MusicController {
|
|||||||
clearNext()
|
clearNext()
|
||||||
clearCurrent()
|
clearCurrent()
|
||||||
musicHistory.clear()
|
musicHistory.clear()
|
||||||
debug("MusicController shut down.")
|
Log.debug("MusicController shut down.")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun audioExceptionHandler(ex: Throwable, music: Music) {
|
private fun audioExceptionHandler(ex: Throwable, music: Music) {
|
||||||
@ -267,7 +304,7 @@ class MusicController {
|
|||||||
yield(getFile(musicPath))
|
yield(getFile(musicPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get sequence of all existing music files */
|
/** Get a sequence of all existing music files */
|
||||||
private fun getAllMusicFiles() = getMusicFolders()
|
private fun getAllMusicFiles() = getMusicFolders()
|
||||||
.filter { it.exists() && it.isDirectory }
|
.filter { it.exists() && it.isDirectory }
|
||||||
.flatMap { it.list().asSequence() }
|
.flatMap { it.list().asSequence() }
|
||||||
@ -302,25 +339,18 @@ class MusicController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun fireOnChange() {
|
private fun fireOnChange() {
|
||||||
if (onTrackChangeListener == null) return
|
if (onTrackChangeListeners.isEmpty()) return
|
||||||
val fileName = currentlyPlaying()
|
Concurrency.runOnGLThread {
|
||||||
if (fileName.isEmpty()) {
|
fireOnChange(MusicTrackInfo.parse(currentlyPlaying()))
|
||||||
fireOnChange(fileName)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
val fileNameParts = fileName.split('/')
|
|
||||||
val modName = if (fileNameParts.size > 1 && fileNameParts[0] == "mods") fileNameParts[1] else ""
|
|
||||||
var trackName = fileNameParts[if (fileNameParts.size > 3 && fileNameParts[2] == "music") 3 else 1]
|
|
||||||
for (extension in gdxSupportedFileExtensions)
|
|
||||||
trackName = trackName.removeSuffix(".$extension")
|
|
||||||
fireOnChange(modName + (if (modName.isEmpty()) "" else ": ") + trackName)
|
|
||||||
}
|
}
|
||||||
private fun fireOnChange(trackLabel: String) {
|
private fun fireOnChange(trackInfo: MusicTrackInfo) {
|
||||||
try {
|
try {
|
||||||
onTrackChangeListener?.invoke(trackLabel)
|
for (listener in onTrackChangeListeners)
|
||||||
|
listener.invoke(trackInfo)
|
||||||
} catch (ex: Throwable) {
|
} catch (ex: Throwable) {
|
||||||
debug("onTrackChange event invoke failed", ex)
|
Log.debug("onTrackChange event invoke failed", ex)
|
||||||
onTrackChangeListener = null
|
onTrackChangeListeners.clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,10 +377,10 @@ class MusicController {
|
|||||||
* @param flags a set of optional flags to tune the choice and playback.
|
* @param flags a set of optional flags to tune the choice and playback.
|
||||||
* @return `true` = success, `false` = no match, no playback change
|
* @return `true` = success, `false` = no match, no playback change
|
||||||
*/
|
*/
|
||||||
fun chooseTrack (
|
fun chooseTrack(
|
||||||
prefix: String = "",
|
prefix: String = "",
|
||||||
suffix: String = MusicMood.Ambient,
|
suffix: String = MusicMood.Ambient,
|
||||||
flags: EnumSet<MusicTrackChooserFlags> = EnumSet.of(MusicTrackChooserFlags.SuffixMustMatch)
|
flags: EnumSet<MusicTrackChooserFlags> = MusicTrackChooserFlags.default
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (baseVolume == 0f) return false
|
if (baseVolume == 0f) return false
|
||||||
|
|
||||||
@ -358,9 +388,20 @@ class MusicController {
|
|||||||
|
|
||||||
if (musicFile == null) {
|
if (musicFile == null) {
|
||||||
// MustMatch flags at work or Music folder empty
|
// MustMatch flags at work or Music folder empty
|
||||||
debug("No music found for prefix=%s, suffix=%s, flags=%s", prefix, suffix, flags)
|
Log.debug("No music found for prefix=%s, suffix=%s, flags=%s", prefix, suffix, flags)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
Log.debug("Track chosen: %s for prefix=%s, suffix=%s, flags=%s", musicFile.path(), prefix, suffix, flags)
|
||||||
|
return startTrack(musicFile, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initiate playback of a _specific_ track by handle - part of [chooseTrack].
|
||||||
|
* Manages fade-over from the previous track.
|
||||||
|
*/
|
||||||
|
private fun startTrack(
|
||||||
|
musicFile: FileHandle,
|
||||||
|
flags: EnumSet<MusicTrackChooserFlags> = MusicTrackChooserFlags.default
|
||||||
|
): Boolean {
|
||||||
if (musicFile.path() == currentlyPlaying())
|
if (musicFile.path() == currentlyPlaying())
|
||||||
return true // picked file already playing
|
return true // picked file already playing
|
||||||
if (!musicFile.exists())
|
if (!musicFile.exists())
|
||||||
@ -374,7 +415,7 @@ class MusicController {
|
|||||||
state = ControllerState.Silence // will retry after one silence period
|
state = ControllerState.Silence // will retry after one silence period
|
||||||
next = null
|
next = null
|
||||||
}, onSuccess = {
|
}, onSuccess = {
|
||||||
debug("Music queued: %s for prefix=%s, suffix=%s, flags=%s", musicFile.path(), prefix, suffix, flags)
|
Log.debug("Music queued: %s", musicFile.path())
|
||||||
|
|
||||||
if (musicHistory.size >= musicHistorySize) musicHistory.removeFirst()
|
if (musicHistory.size >= musicHistorySize) musicHistory.removeFirst()
|
||||||
musicHistory.addLast(musicFile.path())
|
musicHistory.addLast(musicFile.path())
|
||||||
@ -401,10 +442,10 @@ class MusicController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Variant of [chooseTrack] that tries several moods ([suffixes]) until a match is chosen */
|
/** Variant of [chooseTrack] that tries several moods ([suffixes]) until a match is chosen */
|
||||||
fun chooseTrack (
|
fun chooseTrack(
|
||||||
prefix: String = "",
|
prefix: String = "",
|
||||||
suffixes: List<String>,
|
suffixes: List<String>,
|
||||||
flags: EnumSet<MusicTrackChooserFlags> = EnumSet.noneOf(MusicTrackChooserFlags::class.java)
|
flags: EnumSet<MusicTrackChooserFlags> = MusicTrackChooserFlags.none
|
||||||
): Boolean {
|
): Boolean {
|
||||||
for (suffix in suffixes) {
|
for (suffix in suffixes) {
|
||||||
if (chooseTrack(prefix, suffix, flags)) return true
|
if (chooseTrack(prefix, suffix, flags)) return true
|
||||||
@ -412,13 +453,23 @@ class MusicController {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Initiate playback of a _specific_ track by a [MusicTrackInfo] instance */
|
||||||
|
fun startTrack(trackInfo: MusicTrackInfo): Boolean {
|
||||||
|
if (trackInfo.track.isEmpty()) return false
|
||||||
|
val path = trackInfo.run {
|
||||||
|
if (mod.isEmpty()) "$musicPath/$track.$type"
|
||||||
|
else "mods/$mod/$musicPath/$track.$type"
|
||||||
|
}
|
||||||
|
return startTrack(getFile(path))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pause playback with fade-out
|
* Pause playback with fade-out
|
||||||
*
|
*
|
||||||
* @param speedFactor accelerate (>1) or slow down (<1) the fade-out. Clamped to 1/1000..1000.
|
* @param speedFactor accelerate (>1) or slow down (<1) the fade-out. Clamped to 1/1000..1000.
|
||||||
*/
|
*/
|
||||||
fun pause(speedFactor: Float = 1f) {
|
fun pause(speedFactor: Float = 1f) {
|
||||||
debug("MusicTrackController.pause called")
|
Log.debug("MusicTrackController.pause called")
|
||||||
val controller = current
|
val controller = current
|
||||||
if ((state != ControllerState.Playing && state != ControllerState.PlaySingle) || controller == null) return
|
if ((state != ControllerState.Playing && state != ControllerState.PlaySingle) || controller == null) return
|
||||||
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
|
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
|
||||||
@ -435,7 +486,7 @@ class MusicController {
|
|||||||
* @param speedFactor accelerate (>1) or slow down (<1) the fade-in. Clamped to 1/1000..1000.
|
* @param speedFactor accelerate (>1) or slow down (<1) the fade-in. Clamped to 1/1000..1000.
|
||||||
*/
|
*/
|
||||||
fun resume(speedFactor: Float = 1f) {
|
fun resume(speedFactor: Float = 1f) {
|
||||||
debug("MusicTrackController.resume called")
|
Log.debug("MusicTrackController.resume called")
|
||||||
if (state == ControllerState.Pause && current != null) {
|
if (state == ControllerState.Pause && current != null) {
|
||||||
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
|
val fadingStep = defaultFadingStep * speedFactor.coerceIn(0.001f..1000f)
|
||||||
current!!.startFade(MusicTrackController.State.FadeIn, fadingStep)
|
current!!.startFade(MusicTrackController.State.FadeIn, fadingStep)
|
||||||
@ -447,6 +498,7 @@ class MusicController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Fade out then shutdown with a given [duration] in seconds, defaults to a 'slow' fade (4.5s) */
|
/** Fade out then shutdown with a given [duration] in seconds, defaults to a 'slow' fade (4.5s) */
|
||||||
|
@Suppress("unused") // might be useful instead of gracefulShutdown
|
||||||
fun fadeoutToSilence(duration: Float = defaultFadeDuration * 5) {
|
fun fadeoutToSilence(duration: Float = defaultFadeDuration * 5) {
|
||||||
val fadingStep = 1f / ticksPerSecond / duration
|
val fadingStep = 1f / ticksPerSecond / duration
|
||||||
current?.startFade(MusicTrackController.State.FadeOut, fadingStep)
|
current?.startFade(MusicTrackController.State.FadeOut, fadingStep)
|
||||||
|
@ -17,6 +17,7 @@ enum class MusicTrackChooserFlags {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// EnumSet factories
|
// EnumSet factories
|
||||||
|
val default: EnumSet<MusicTrackChooserFlags> = EnumSet.of(SuffixMustMatch)
|
||||||
/** EnumSet.of([PlayDefaultFile], [PlaySingle]) */
|
/** EnumSet.of([PlayDefaultFile], [PlaySingle]) */
|
||||||
val setPlayDefault: EnumSet<MusicTrackChooserFlags> = EnumSet.of(PlayDefaultFile, PlaySingle)
|
val setPlayDefault: EnumSet<MusicTrackChooserFlags> = EnumSet.of(PlayDefaultFile, PlaySingle)
|
||||||
/** EnumSet.of([PrefixMustMatch], [PlaySingle]) */
|
/** EnumSet.of([PrefixMustMatch], [PlaySingle]) */
|
||||||
|
@ -16,6 +16,7 @@ import com.unciv.ui.components.extensions.onClick
|
|||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
import com.unciv.ui.components.extensions.toTextButton
|
import com.unciv.ui.components.extensions.toTextButton
|
||||||
import com.unciv.ui.components.extensions.toImageButton
|
import com.unciv.ui.components.extensions.toImageButton
|
||||||
|
import com.unciv.ui.popups.Popup
|
||||||
import com.unciv.utils.Concurrency
|
import com.unciv.utils.Concurrency
|
||||||
import com.unciv.utils.launchOnGLThread
|
import com.unciv.utils.launchOnGLThread
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
@ -154,9 +155,10 @@ private fun addMusicCurrentlyPlaying(table: Table, music: MusicController) {
|
|||||||
label.wrap = true
|
label.wrap = true
|
||||||
table.add(label).padTop(20f).colspan(2).fillX().row()
|
table.add(label).padTop(20f).colspan(2).fillX().row()
|
||||||
music.onChange {
|
music.onChange {
|
||||||
Concurrency.runOnGLThread {
|
label.setText("Currently playing: [$it]".tr())
|
||||||
label.setText("Currently playing: [$it]".tr())
|
}
|
||||||
}
|
table.firstAscendant(Popup::class.java)?.run {
|
||||||
|
closeListeners.add { music.onChange(null) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,123 @@
|
|||||||
package com.unciv.ui.screens.worldscreen.mainmenu
|
package com.unciv.ui.screens.worldscreen.mainmenu
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||||
|
import com.badlogic.gdx.utils.Align
|
||||||
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.models.metadata.GameSettings
|
||||||
|
import com.unciv.models.metadata.ScreenSize
|
||||||
|
import com.unciv.ui.audio.MusicController
|
||||||
|
import com.unciv.ui.components.ExpanderTab
|
||||||
|
import com.unciv.ui.components.Fonts
|
||||||
|
import com.unciv.ui.components.extensions.onClick
|
||||||
|
import com.unciv.ui.components.extensions.setSize
|
||||||
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.popups.Popup
|
import com.unciv.ui.popups.Popup
|
||||||
import com.unciv.ui.popups.options.addMusicControls
|
import com.unciv.ui.popups.options.addMusicControls
|
||||||
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||||
|
|
||||||
class WorldScreenMusicPopup(val worldScreen: WorldScreen) : Popup(worldScreen) {
|
class WorldScreenMusicPopup(
|
||||||
|
worldScreen: WorldScreen
|
||||||
|
) : Popup(worldScreen, maxSizePercentage = calcSize(worldScreen)) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// 3/4 of the screen is just a bit too small on small screen settings
|
||||||
|
private fun calcSize(worldScreen: WorldScreen) = when(worldScreen.game.settings.screenSize) {
|
||||||
|
ScreenSize.Tiny -> 0.95f
|
||||||
|
ScreenSize.Small -> 0.85f
|
||||||
|
else -> 0.75f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val musicController = UncivGame.Current.musicController
|
||||||
|
private val trackStyle: TextButton.TextButtonStyle
|
||||||
|
private val historyExpander: ExpanderTab
|
||||||
|
private val visualMods = worldScreen.game.settings.visualMods
|
||||||
|
private val mods = worldScreen.gameInfo.gameParameters.mods
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val musicController = UncivGame.Current.musicController
|
|
||||||
val settings = UncivGame.Current.settings
|
val settings = UncivGame.Current.settings
|
||||||
|
|
||||||
|
getScrollPane()!!.setScrollingDisabled(true, false)
|
||||||
defaults().fillX()
|
defaults().fillX()
|
||||||
addMusicControls(this, settings, musicController)
|
|
||||||
|
val sk = BaseScreen.skinStrings
|
||||||
|
|
||||||
|
// Make the list flat but with mouse-over highlighting and visual click-down feedback
|
||||||
|
// by making them buttons instead of labels, but with a style that has no NinePatches
|
||||||
|
// as backgrounds, just tinted whiteDot. As default, but skinnable.
|
||||||
|
val up = sk.getUiBackground("WorldScreenMusicPopup/TrackList/Up", tintColor = skin.getColor("color"))
|
||||||
|
val down = sk.getUiBackground("WorldScreenMusicPopup/TrackList/Down", tintColor = skin.getColor("positive"))
|
||||||
|
val over = sk.getUiBackground("WorldScreenMusicPopup/TrackList/Over", tintColor = skin.getColor("highlight"))
|
||||||
|
trackStyle = TextButton.TextButtonStyle(up, down, null, Fonts.font)
|
||||||
|
trackStyle.over = over
|
||||||
|
trackStyle.disabled = up
|
||||||
|
trackStyle.disabledFontColor = Color.LIGHT_GRAY
|
||||||
|
|
||||||
|
addMusicMods(settings)
|
||||||
|
historyExpander = addHistory()
|
||||||
|
addMusicControls(bottomTable, settings, musicController)
|
||||||
addCloseButton().colspan(2)
|
addCloseButton().colspan(2)
|
||||||
|
|
||||||
|
musicController.onChange {
|
||||||
|
historyExpander.innerTable.clear()
|
||||||
|
historyExpander.innerTable.updateTrackList(musicController.getHistory())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun String.toSmallUntranslatedButton(rightSide: Boolean = false) =
|
||||||
|
TextButton(this, trackStyle).apply {
|
||||||
|
label.setFontScale(14 / Fonts.ORIGINAL_FONT_SIZE)
|
||||||
|
label.setAlignment(if (rightSide) Align.right else Align.left)
|
||||||
|
labelCell.pad(5f)
|
||||||
|
isDisabled = rightSide
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addMusicMods(settings: GameSettings) {
|
||||||
|
val modsToTracks = musicController.getAllMusicFileInfo().groupBy{ it.mod }
|
||||||
|
val collator = settings.getCollatorFromLocale()
|
||||||
|
val modsSorted = modsToTracks.entries.asSequence()
|
||||||
|
.sortedWith(compareBy(collator) { it.key })
|
||||||
|
for ((modLabel, trackList) in modsSorted) {
|
||||||
|
val tracksSorted = trackList.asSequence()
|
||||||
|
.sortedWith(compareBy(collator) { it.track })
|
||||||
|
addTrackList(modLabel.ifEmpty { "—Default—" }, tracksSorted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addHistory() = addTrackList("—History—", musicController.getHistory())
|
||||||
|
|
||||||
|
private fun addTrackList(title: String, tracks: Sequence<MusicController.MusicTrackInfo>): ExpanderTab {
|
||||||
|
// Note title is either a mod name or something that cannot be a mod name (thanks to the em-dashes)
|
||||||
|
val icon = when (title) {
|
||||||
|
in mods -> "OtherIcons/Mods"
|
||||||
|
in visualMods -> "UnitPromotionIcons/Scouting"
|
||||||
|
else -> null
|
||||||
|
}?.let { ImageGetter.getImage(it).apply { setSize(18f) } }
|
||||||
|
val expander = ExpanderTab(title, Constants.defaultFontSize, icon,
|
||||||
|
startsOutOpened = false, defaultPad = 0f, headerPad = 5f,
|
||||||
|
persistenceID = "MusicPopup.$title",
|
||||||
|
) {
|
||||||
|
it.updateTrackList(tracks)
|
||||||
|
}
|
||||||
|
add(expander).colspan(2).growX().row()
|
||||||
|
|
||||||
|
return expander
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Table.updateTrackList(tracks: Sequence<MusicController.MusicTrackInfo>) {
|
||||||
|
for (entry in tracks) {
|
||||||
|
val trackLabel = entry.track.toSmallUntranslatedButton()
|
||||||
|
trackLabel.onClick { musicController.startTrack(entry) }
|
||||||
|
add(trackLabel).fillX()
|
||||||
|
add(entry.type.toSmallUntranslatedButton(true)).right().row()
|
||||||
|
// Note - displaying the file extension is meant as modder help, and could possibly
|
||||||
|
// be extended to a modder tool - maybe display eligibility for known triggers?
|
||||||
|
// Might also be gated by a setting so casual users won't see it?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,9 @@ These shapes are used all over Unciv and can be replaced to make a lot of UI ele
|
|||||||
| WorldScreen/TopBar/ | ResourceTable | null | |
|
| WorldScreen/TopBar/ | ResourceTable | null | |
|
||||||
| WorldScreen/TopBar/ | RightAttachment | roundedEdgeRectangle | |
|
| WorldScreen/TopBar/ | RightAttachment | roundedEdgeRectangle | |
|
||||||
| WorldScreen/TopBar/ | StatsTable | null | |
|
| WorldScreen/TopBar/ | StatsTable | null | |
|
||||||
|
| WorldScreenMusicPopup/TrackList/ | Down", tintColor = skin.getColor("positive | null | |
|
||||||
|
| WorldScreenMusicPopup/TrackList/ | Over", tintColor = skin.getColor("highlight | null | |
|
||||||
|
| WorldScreenMusicPopup/TrackList/ | Up", tintColor = skin.getColor("color | null | |
|
||||||
<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION_END -->
|
<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION_END -->
|
||||||
|
|
||||||
## SkinConfig
|
## SkinConfig
|
||||||
|
Reference in New Issue
Block a user