Unciv/desktop/src/com/unciv/app/desktop/DesktopDisplay.kt
SomeTroglodyte 987f67d9cd
[code quality] Reorg, clean up and comment a few things (#10527)
* UncivGame is a pure class file again, GUI split off

* Purify GameSettings step 1 - non-multiplayer nested classes

* Purify GameSettings step 2 - multiplayer nested classes

* Purify GameParameters - BaseRuleset to own file

* Rework WindowState to centralize minimum/maximum treatment

* Rename MultiplayerTurnNotifierDesktop to UncivWindowListener

* Clarifications on what the WindowListener actually does (and now the attention-getting does something on non-Windows too)
2023-11-19 23:52:15 +02:00

136 lines
5.6 KiB
Kotlin

package com.unciv.app.desktop
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.Graphics.Monitor
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Graphics
import com.unciv.models.metadata.GameSettings
import com.unciv.models.translations.tr
import com.unciv.utils.PlatformDisplay
import com.unciv.utils.ScreenMode
import java.awt.GraphicsConfiguration
import java.awt.GraphicsDevice
import java.awt.GraphicsEnvironment
import java.awt.Toolkit
import kotlin.math.roundToInt
enum class DesktopScreenMode : ScreenMode {
// Order will be preserved in Options Popup
Windowed {
override fun activate(settings: GameSettings) {
Gdx.graphics.setUndecorated(false)
val isFillingDesktop = setWindowedMode(settings)
if (isFillingDesktop)
getWindow()?.maximizeWindow()
}
override fun hasUserSelectableSize() = true
},
Fullscreen {
override fun activate(settings: GameSettings) {
Gdx.graphics.setFullscreenMode(Gdx.graphics.displayMode)
}
override fun hasUserSelectableSize() = false
},
Borderless {
override fun activate(settings: GameSettings) {
getWindow()?.restoreWindow()
Gdx.graphics.setUndecorated(true)
setWindowedMode(settings)
}
override fun hasUserSelectableSize() = true
},
;
override fun getId() = ordinal
override fun toString() = name.tr()
abstract fun activate(settings: GameSettings)
/** @return `true` if window fills entire desktop */
protected fun setWindowedMode(settings: GameSettings): Boolean {
// Calling AWT after Gdx is fully initialized seems icky, but seems to have no side effects
// Found no equivalent in Gdx - available _desktop_ surface without taskbars etc
// *for the primary monitor* - no saving window sizes that span over several monitors
val maximumWindowBounds = getMaximumWindowBounds()
// Make sure an inappropriate saved size doesn't make the window unusable
val (width, height) = settings.windowState.coerceIn(maximumWindowBounds)
// Kludge - see also DesktopLauncher - without, moving the window might revert to the size stored in config
(Lwjgl3Application::class.java).getDeclaredField("config").run {
isAccessible = true
get(Gdx.app) as Lwjgl3ApplicationConfiguration
}.setWindowedMode(width, height)
Gdx.graphics.setWindowedMode(width, height)
return width == maximumWindowBounds.width && height == maximumWindowBounds.height
}
companion object {
operator fun get(id: Int) = values()[id]
private fun getWindow() = (Gdx.graphics as? Lwjgl3Graphics)?.window
/** Replacement for buggy `GraphicsEnvironment.maximumWindowBounds` */
// Notes: maximumWindowBounds seems to scale by the High DPI setting on Windows,
// and it always uses the default device. GraphicsConfiguration.getBounds() delivers x/y
// as true pixels, while width/height are similarly scaled. Toolkit.getScreenInsets
// delivers scaled values (observed - no documentation found).
internal fun getMaximumWindowBounds(
monitor: Monitor = Lwjgl3ApplicationConfiguration.getPrimaryMonitor()
): java.awt.Rectangle {
// Identify AWT equivalent to Gdx monitor
val graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment()
for (device in graphicsEnvironment.screenDevices) {
for (config in device.configurations) {
val bounds = config.bounds
if (bounds.x == monitor.virtualX && bounds.y == monitor.virtualY)
return getMaximumWindowBounds(device, config, bounds)
}
}
// Fallback should that fail (this is without insets)
val mode = Lwjgl3ApplicationConfiguration.getDisplayMode(monitor)
return java.awt.Rectangle(monitor.virtualX, monitor.virtualY, mode.width, mode.height)
}
private fun getMaximumWindowBounds(
device: GraphicsDevice,
config: GraphicsConfiguration,
bounds: java.awt.Rectangle
): java.awt.Rectangle {
val displayWidth = device.displayMode.width
val displayHeight = device.displayMode.height
val scalePercent = (displayWidth.toDouble() / bounds.width * 100).roundToInt() * 0.01
val insets = Toolkit.getDefaultToolkit().getScreenInsets(config)
val unscaledInsetLeft = (insets.left * scalePercent).roundToInt()
val unscaledInsetRight = (insets.right * scalePercent).roundToInt()
val unscaledInsetTop = (insets.top * scalePercent).roundToInt()
val unscaledInsetBottom = (insets.bottom * scalePercent).roundToInt()
return java.awt.Rectangle(
bounds.x + unscaledInsetLeft,
bounds.y + unscaledInsetTop,
displayWidth - unscaledInsetLeft - unscaledInsetRight,
displayHeight - unscaledInsetTop - unscaledInsetBottom
)
}
}
}
class DesktopDisplay : PlatformDisplay {
override fun getScreenModes() =
DesktopScreenMode.values().associateBy { it.getId() }
override fun setScreenMode(id: Int, settings: GameSettings) {
DesktopScreenMode[id].activate(settings)
}
override fun hasUserSelectableSize(id: Int) =
DesktopScreenMode[id].hasUserSelectableSize()
}