mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-09 10:29:02 +07:00
Allow image overlay and changing world wrap in map editor (#9493)
This commit is contained in:
parent
9bbd3b416e
commit
42b35bce4f
@ -208,7 +208,8 @@
|
||||
"font": "button",
|
||||
"fontColor": "color",
|
||||
"downFontColor": "pressed",
|
||||
"overFontColor": "highlight"
|
||||
"overFontColor": "highlight",
|
||||
"disabledFontColor": "gray"
|
||||
}
|
||||
},
|
||||
"com.badlogic.gdx.scenes.scene2d.ui.Label$LabelStyle": {
|
||||
|
@ -514,6 +514,14 @@ River generation failed! =
|
||||
Please don't use step 'Landmass' with map type 'Empty', create a new empty map instead. =
|
||||
This map has errors: =
|
||||
The incompatible elements have been removed. =
|
||||
Current map: World Wrap =
|
||||
Overlay image =
|
||||
Click to choose a file =
|
||||
Choose an image =
|
||||
Overlay transparency: =
|
||||
Invalid overlay image =
|
||||
World wrap is incompatible with an overlay and was deactivated. =
|
||||
An overlay image is incompatible with world wrap and was deactivated. =
|
||||
|
||||
## Map/Tool names
|
||||
My new map =
|
||||
@ -1208,11 +1216,13 @@ Default Focus =
|
||||
Please enter a new name for your city =
|
||||
Please select a tile for this building's [improvement] =
|
||||
|
||||
# Ask for text or numbers popup UI
|
||||
# Specialized Popups - Ask for text or numbers, file picker
|
||||
|
||||
Invalid input! Please enter a different string. =
|
||||
Invalid input! Please enter a valid number. =
|
||||
Please enter some text =
|
||||
Please enter a file name =
|
||||
File name: =
|
||||
|
||||
# Technology UI
|
||||
|
||||
|
@ -2,9 +2,17 @@ package com.unciv.ui.screens.mapeditorscreen
|
||||
|
||||
import com.badlogic.gdx.Application
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.graphics.Texture
|
||||
import com.badlogic.gdx.graphics.g2d.TextureRegion
|
||||
import com.badlogic.gdx.scenes.scene2d.Group
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Image
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.map.MapParameters
|
||||
import com.unciv.logic.map.MapShape
|
||||
import com.unciv.logic.map.MapSize
|
||||
import com.unciv.logic.map.MapSizeNew
|
||||
import com.unciv.logic.map.TileMap
|
||||
@ -20,10 +28,13 @@ import com.unciv.ui.components.tilegroups.TileGroup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.components.KeyCharAndCode
|
||||
import com.unciv.ui.components.KeyboardPanningListener
|
||||
import com.unciv.ui.images.ImageWithCustomSize
|
||||
import com.unciv.ui.popups.ToastPopup
|
||||
import com.unciv.ui.screens.basescreen.RecreateOnResize
|
||||
import com.unciv.ui.screens.worldscreen.ZoomButtonPair
|
||||
import com.unciv.utils.Concurrency
|
||||
import com.unciv.utils.Dispatcher
|
||||
import com.unciv.utils.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
@ -125,6 +136,34 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
|
||||
|
||||
fun getToolsWidth() = stage.width * 0.4f
|
||||
|
||||
fun setWorldWrap(newValue: Boolean) {
|
||||
if (newValue == tileMap.mapParameters.worldWrap) return
|
||||
setWorldWrapFixOddWidth(newValue)
|
||||
if (newValue && overlayFile != null) {
|
||||
overlayFile = null
|
||||
ToastPopup("An overlay image is incompatible with world wrap and was deactivated.", stage, 4000)
|
||||
tabs.options.update()
|
||||
}
|
||||
recreateMapHolder()
|
||||
}
|
||||
|
||||
private fun setWorldWrapFixOddWidth(newValue: Boolean) = tileMap.mapParameters.run {
|
||||
// Turning *off* WW and finding an odd width means it must have been rounded
|
||||
// down by the TileMap constructor - fix so we can turn it back on later
|
||||
if (worldWrap && mapSize.width % 2 != 0 && shape == MapShape.rectangular)
|
||||
mapSize.width--
|
||||
worldWrap = newValue
|
||||
}
|
||||
|
||||
private fun recreateMapHolder(actionWhileRemoved: ()->Unit = {}) {
|
||||
val savedScale = mapHolder.scaleX
|
||||
clearOverlayImages()
|
||||
mapHolder.remove()
|
||||
actionWhileRemoved()
|
||||
mapHolder = newMapHolder()
|
||||
mapHolder.zoom(savedScale)
|
||||
}
|
||||
|
||||
private fun newMapHolder(): EditorMapHolder {
|
||||
ImageGetter.setNewRuleset(ruleset)
|
||||
// setNewRuleset is missing some graphics - those "EmojiIcons"&co already rendered as font characters
|
||||
@ -138,18 +177,20 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
|
||||
tileMap.setStartingLocationsTransients()
|
||||
UncivGame.Current.translations.translationActiveMods = ruleset.mods
|
||||
|
||||
val result = EditorMapHolder(this, tileMap) {
|
||||
val newHolder = EditorMapHolder(this, tileMap) {
|
||||
tileClickHandler?.invoke(it)
|
||||
}
|
||||
for (oldPanningListener in stage.root.listeners.filterIsInstance<KeyboardPanningListener>())
|
||||
stage.removeListener(oldPanningListener) // otherwise they accumulate
|
||||
result.mapPanningSpeed = UncivGame.Current.settings.mapPanningSpeed
|
||||
stage.addListener(KeyboardPanningListener(result, allowWASD = false))
|
||||
newHolder.mapPanningSpeed = UncivGame.Current.settings.mapPanningSpeed
|
||||
stage.addListener(KeyboardPanningListener(newHolder, allowWASD = false))
|
||||
if (Gdx.app.type == Application.ApplicationType.Desktop)
|
||||
result.isAutoScrollEnabled = UncivGame.Current.settings.mapAutoScroll
|
||||
newHolder.isAutoScrollEnabled = UncivGame.Current.settings.mapAutoScroll
|
||||
|
||||
stage.root.addActorAt(0, result)
|
||||
stage.scrollFocus = result
|
||||
addOverlayToMapHolder(newHolder.actor as Group) // That's the initially empty Group ZoomableScrollPane allocated
|
||||
|
||||
stage.root.addActorAt(0, newHolder)
|
||||
stage.scrollFocus = newHolder
|
||||
|
||||
isDirty = true
|
||||
modsTabNeedsRefresh = true
|
||||
@ -157,15 +198,16 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
|
||||
naturalWondersNeedRefresh = true
|
||||
|
||||
if (UncivGame.Current.settings.showZoomButtons) {
|
||||
zoomController = ZoomButtonPair(result)
|
||||
zoomController = ZoomButtonPair(newHolder)
|
||||
zoomController!!.setPosition(10f, 10f)
|
||||
stage.addActor(zoomController)
|
||||
}
|
||||
|
||||
return result
|
||||
return newHolder
|
||||
}
|
||||
|
||||
fun loadMap(map: TileMap, newRuleset: Ruleset? = null, selectPage: Int = 0) {
|
||||
clearOverlayImages()
|
||||
mapHolder.remove()
|
||||
tileMap = map
|
||||
ruleset = newRuleset ?: RulesetCache.getComplexRuleset(map.mapParameters)
|
||||
@ -181,11 +223,12 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
|
||||
}
|
||||
|
||||
fun applyRuleset(newRuleset: Ruleset, newBaseRuleset: String, mods: LinkedHashSet<String>) {
|
||||
mapHolder.remove()
|
||||
tileMap.mapParameters.baseRuleset = newBaseRuleset
|
||||
tileMap.mapParameters.mods = mods
|
||||
tileMap.ruleset = newRuleset
|
||||
ruleset = newRuleset
|
||||
recreateMapHolder {
|
||||
tileMap.mapParameters.baseRuleset = newBaseRuleset
|
||||
tileMap.mapParameters.mods = mods
|
||||
tileMap.ruleset = newRuleset
|
||||
ruleset = newRuleset
|
||||
}
|
||||
mapHolder = newMapHolder()
|
||||
modsTabNeedsRefresh = false
|
||||
}
|
||||
@ -251,4 +294,74 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
|
||||
job.cancel()
|
||||
jobs.clear()
|
||||
}
|
||||
|
||||
//region Overlay Image
|
||||
|
||||
// To support world wrap with an overlay, one could maybe do up to tree versions of the same
|
||||
// Image tiled side by side (therefore "clearOverlayImages"), they _could_ use the same Texture
|
||||
// instance - that part works. But how to position and clip them properly escapes me - better
|
||||
// coders are welcome to try. To work around, we simply turn world wrap off when an overlay is
|
||||
// loaded, and allow to freely turn WW on and off. After all, the distinction becomes relevant
|
||||
// *only* when a game is started, units move, and tile neighbors get a meaning.
|
||||
|
||||
private var imageOverlay: Image? = null
|
||||
|
||||
internal var overlayFile: FileHandle? = null
|
||||
set(value) {
|
||||
field = value
|
||||
overlayFileChanged(value)
|
||||
}
|
||||
|
||||
internal var overlayAlpha = 0.33f
|
||||
set(value) {
|
||||
field = value
|
||||
overlayAlphaChanged(value)
|
||||
}
|
||||
|
||||
private fun clearOverlayImages() {
|
||||
val oldImage = imageOverlay ?: return
|
||||
imageOverlay = null
|
||||
oldImage.remove()
|
||||
(oldImage.drawable as? TextureRegionDrawable)?.region?.texture?.dispose()
|
||||
}
|
||||
|
||||
private fun overlayFileChanged(value: FileHandle?) {
|
||||
clearOverlayImages()
|
||||
if (value == null) return
|
||||
if (tileMap.mapParameters.worldWrap) {
|
||||
setWorldWrapFixOddWidth(false)
|
||||
ToastPopup("World wrap is incompatible with an overlay and was deactivated.", stage, 4000)
|
||||
tabs.options.update()
|
||||
}
|
||||
recreateMapHolder()
|
||||
}
|
||||
|
||||
private fun overlayAlphaChanged(value: Float) {
|
||||
imageOverlay?.color?.a = value
|
||||
}
|
||||
|
||||
private fun addOverlayToMapHolder(newHolderContent: Group) {
|
||||
clearOverlayImages()
|
||||
if (overlayFile == null) return
|
||||
|
||||
try {
|
||||
val texture = Texture(overlayFile)
|
||||
texture.setFilter(Texture.TextureFilter.Linear, Texture.TextureFilter.Linear)
|
||||
imageOverlay = ImageWithCustomSize(TextureRegion(texture))
|
||||
} catch (ex: Throwable) {
|
||||
Log.error("Invalid overlay image", ex)
|
||||
overlayFile = null
|
||||
ToastPopup("Invalid overlay image", stage, 3000)
|
||||
tabs.options.update()
|
||||
return
|
||||
}
|
||||
|
||||
imageOverlay?.apply {
|
||||
touchable = Touchable.disabled
|
||||
setFillParent(true)
|
||||
color.a = overlayAlpha
|
||||
newHolderContent.addActor(this)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
@ -1,17 +1,21 @@
|
||||
package com.unciv.ui.screens.mapeditorscreen.tabs
|
||||
|
||||
import com.badlogic.gdx.Gdx
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.ButtonGroup
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
|
||||
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.logic.files.FileChooser
|
||||
import com.unciv.logic.files.MapSaver
|
||||
import com.unciv.logic.map.MapShape
|
||||
import com.unciv.logic.map.MapSize
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen
|
||||
import com.unciv.ui.popups.ToastPopup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.components.KeyCharAndCode
|
||||
import com.unciv.ui.components.TabbedPager
|
||||
import com.unciv.ui.components.UncivSlider
|
||||
import com.unciv.ui.components.extensions.addSeparator
|
||||
import com.unciv.ui.components.extensions.isEnabled
|
||||
import com.unciv.ui.components.extensions.keyShortcuts
|
||||
@ -20,6 +24,9 @@ import com.unciv.ui.components.extensions.onClick
|
||||
import com.unciv.ui.components.extensions.toCheckBox
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.popups.ToastPopup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen
|
||||
import com.unciv.utils.Log
|
||||
|
||||
class MapEditorOptionsTab(
|
||||
@ -30,6 +37,9 @@ class MapEditorOptionsTab(
|
||||
private val tileMatchGroup = ButtonGroup<CheckBox>()
|
||||
private val copyMapButton = "Copy to clipboard".toTextButton()
|
||||
private val pasteMapButton = "Load copied data".toTextButton()
|
||||
private val worldWrapCheckBox: CheckBox
|
||||
private val overlayFileButton= TextButton(null, BaseScreen.skin)
|
||||
private val overlayAlphaSlider: UncivSlider
|
||||
|
||||
private var seedToCopy = ""
|
||||
private var tileMatchFuzziness = TileMatchFuzziness.CompleteMatch
|
||||
@ -41,6 +51,7 @@ class MapEditorOptionsTab(
|
||||
BaseTerrain("Base terrain only"),
|
||||
LandOrWater("Land or water only"),
|
||||
}
|
||||
|
||||
init {
|
||||
top()
|
||||
defaults().pad(10f)
|
||||
@ -64,10 +75,47 @@ class MapEditorOptionsTab(
|
||||
add("Map copy and paste".toLabel(Color.GOLD)).row()
|
||||
copyMapButton.onActivation { copyHandler() }
|
||||
copyMapButton.keyShortcuts.add(KeyCharAndCode.ctrl('c'))
|
||||
add(copyMapButton).row()
|
||||
pasteMapButton.onActivation { pasteHandler() }
|
||||
pasteMapButton.keyShortcuts.add(KeyCharAndCode.ctrl('v'))
|
||||
add(pasteMapButton).row()
|
||||
add(Table().apply {
|
||||
add(copyMapButton).padRight(15f)
|
||||
add(pasteMapButton)
|
||||
}).row()
|
||||
addSeparator(Color.GRAY)
|
||||
|
||||
worldWrapCheckBox = "Current map: World Wrap".toCheckBox(editorScreen.tileMap.mapParameters.worldWrap) {
|
||||
editorScreen.setWorldWrap(it)
|
||||
}
|
||||
add(worldWrapCheckBox).growX().row()
|
||||
addSeparator(Color.GRAY)
|
||||
|
||||
add("Overlay image".toLabel(Color.GOLD)).row()
|
||||
overlayFileButton.style = TextButton.TextButtonStyle(overlayFileButton.style)
|
||||
showOverlayFileName()
|
||||
overlayFileButton.onClick {
|
||||
// TODO - to allow accessing files *outside the app scope* on Android, switch to
|
||||
// [UncivFiles.saverLoader] and teach PlatformSaverLoader to deliver a stream or
|
||||
// ByteArray or PixMap instead of doing a text file load using system/JVM default encoding..
|
||||
// Then we'd need to make a *managed* PixMap-based Texture out of that, because only
|
||||
// managed will survive GL context loss automatically. Cespenar says "could get messy".
|
||||
FileChooser.createLoadDialog(stage, "Choose an image", editorScreen.overlayFile) {
|
||||
success: Boolean, file: FileHandle ->
|
||||
if (!success) return@createLoadDialog
|
||||
editorScreen.overlayFile = file
|
||||
showOverlayFileName()
|
||||
}.apply {
|
||||
filter = FileChooser.createExtensionFilter("png", "jpg", "jpeg")
|
||||
}.open()
|
||||
}
|
||||
add(overlayFileButton).fillX().row()
|
||||
|
||||
overlayAlphaSlider = UncivSlider(0f, 1f, 0.05f, initial = editorScreen.overlayAlpha) {
|
||||
editorScreen.overlayAlpha = it
|
||||
}
|
||||
add(Table().apply {
|
||||
add("Overlay opacity:".toLabel(alignment = Align.left)).left()
|
||||
add(overlayAlphaSlider).right()
|
||||
}).row()
|
||||
}
|
||||
|
||||
private fun copyHandler() {
|
||||
@ -85,10 +133,42 @@ class MapEditorOptionsTab(
|
||||
}
|
||||
}
|
||||
|
||||
private fun showOverlayFileName() = overlayFileButton.run {
|
||||
if (editorScreen.overlayFile == null) {
|
||||
setText("Click to choose a file")
|
||||
style.fontColor.a = 0.5f
|
||||
} else {
|
||||
setText(editorScreen.overlayFile!!.path())
|
||||
style.fontColor.a = 1f
|
||||
}
|
||||
}
|
||||
|
||||
/** Check whether we can flip world wrap without ruining geometry */
|
||||
private fun canChangeWorldWrap(): Boolean {
|
||||
val params = editorScreen.tileMap.mapParameters
|
||||
// Can't change for hexagonal at all, as non-ww must always have an odd number of columns and ww nust have an even number of columns
|
||||
if (params.shape != MapShape.rectangular) return false
|
||||
// Too small?
|
||||
if (params.mapSize.radius < MapSize.Tiny.radius) return false
|
||||
// Even-width rectangular have no problems, but that has not necessarily been saved in mapSize!
|
||||
if (params.mapSize.width % 2 == 0) return true
|
||||
// The recorded width may have been reduced to even by the TileMap constructor.
|
||||
// In such a case we allow turning WW off, and editorScreen.setWorldWrap will fix the width.
|
||||
return (params.worldWrap)
|
||||
}
|
||||
|
||||
fun update() {
|
||||
pasteMapButton.isEnabled = Gdx.app.clipboard.hasContents()
|
||||
worldWrapCheckBox.isChecked = editorScreen.tileMap.mapParameters.worldWrap
|
||||
worldWrapCheckBox.isDisabled = !canChangeWorldWrap()
|
||||
showOverlayFileName()
|
||||
}
|
||||
|
||||
override fun activated(index: Int, caption: String, pager: TabbedPager) {
|
||||
seedToCopy = editorScreen.tileMap.mapParameters.seed.toString()
|
||||
seedLabel.setText("Current map RNG seed: [$seedToCopy]".tr())
|
||||
pasteMapButton.isEnabled = Gdx.app.clipboard.hasContents()
|
||||
update()
|
||||
overlayAlphaSlider.value = editorScreen.overlayAlpha
|
||||
}
|
||||
|
||||
override fun deactivated(index: Int, caption: String, pager: TabbedPager) {
|
||||
|
Loading…
Reference in New Issue
Block a user