Split off stuff from CameraStageBaseScreen that isn't the class itself (#3929)

This commit is contained in:
SomeTroglodyte 2021-05-14 10:23:33 +02:00 committed by GitHub
parent d546b2b00e
commit abaa678e2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 261 additions and 249 deletions

View File

@ -11,87 +11,11 @@ import com.badlogic.gdx.graphics.g2d.SpriteBatch
import com.badlogic.gdx.graphics.g2d.TextureAtlas import com.badlogic.gdx.graphics.g2d.TextureAtlas
import com.badlogic.gdx.scenes.scene2d.* import com.badlogic.gdx.scenes.scene2d.*
import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener
import com.badlogic.gdx.scenes.scene2d.utils.Drawable import com.badlogic.gdx.scenes.scene2d.utils.Drawable
import com.badlogic.gdx.utils.viewport.ExtendViewport import com.badlogic.gdx.utils.viewport.ExtendViewport
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.models.Tutorial import com.unciv.models.Tutorial
import com.unciv.models.UncivSound
import com.unciv.models.translations.tr
import com.unciv.ui.tutorials.TutorialController import com.unciv.ui.tutorials.TutorialController
import java.util.HashMap
import kotlin.concurrent.thread
import kotlin.random.Random
/*
* For now, combination keys cannot easily be expressed.
* Pressing Ctrl-Letter will arrive one event for Input.Keys.CONTROL_LEFT and one for the ASCII control code point
* so Ctrl-R can be handled using KeyCharAndCode('\u0012')
* Pressing Alt-Something likewise will fire once for Alt and once for the unmodified keys with no indication Alt is held
* (Exception: international keyboard AltGr-combos)
* An update supporting easy declarations for any modifier combos would need to use Gdx.input.isKeyPressed()
* Gdx seems to omit support for a modifier mask (e.g. Ctrl-Alt-Shift) so we would need to reinvent this
*/
/**
* Represents a key for use in an InputListener keyTyped() handler
*
* Example: KeyCharAndCode('R'), KeyCharAndCode(Input.Keys.F1)
*/
data class KeyCharAndCode(val char: Char, val code: Int) {
// express keys with a Char value
constructor(char: Char): this(char.toLowerCase(), 0)
// express keys that only have a keyCode like F1
constructor(code: Int): this(Char.MIN_VALUE, code)
// helper for use in InputListener keyTyped()
constructor(event: InputEvent?, character: Char)
: this (
character.toLowerCase(),
if (character == Char.MIN_VALUE && event!=null) event.keyCode else 0
)
@ExperimentalStdlibApi
override fun toString(): String {
// debug helper
return when {
char == Char.MIN_VALUE -> Input.Keys.toString(code)
char < ' ' -> "Ctrl-" + Char(char.toInt()+64)
else -> "\"$char\""
}
}
}
class KeyPressDispatcher: HashMap<KeyCharAndCode, (() -> Unit)>() {
private var checkpoint: Set<KeyCharAndCode> = setOf()
// access by Char
operator fun get(char: Char) = this[KeyCharAndCode(char)]
operator fun set(char: Char, action: () -> Unit) {
this[KeyCharAndCode(char)] = action
}
operator fun contains(char: Char) = this.contains(KeyCharAndCode(char))
fun remove(char: Char) = this.remove(KeyCharAndCode(char))
// access by Int keyCodes
operator fun get(code: Int) = this[KeyCharAndCode(code)]
operator fun set(code: Int, action: () -> Unit) {
this[KeyCharAndCode(code)] = action
}
operator fun contains(code: Int) = this.contains(KeyCharAndCode(code))
fun remove(code: Int) = this.remove(KeyCharAndCode(code))
override fun clear() {
checkpoint = setOf()
super.clear()
}
fun setCheckpoint() {
checkpoint = keys.toSet()
}
fun revertToCheckPoint() {
keys.minus(checkpoint).forEach { remove(it) }
}
}
open class CameraStageBaseScreen : Screen { open class CameraStageBaseScreen : Screen {
@ -195,176 +119,3 @@ open class CameraStageBaseScreen : Screen {
} }
} }
fun Button.disable(){
touchable= Touchable.disabled
color= Color.GRAY
}
fun Button.enable() {
color = Color.WHITE
touchable = Touchable.enabled
}
var Button.isEnabled: Boolean
//Todo: Use in PromotionPickerScreen, TradeTable, WorldScreen.updateNextTurnButton
get() = touchable == Touchable.enabled
set(value) = if (value) enable() else disable()
fun colorFromRGB(r: Int, g: Int, b: Int) = Color(r/255f, g/255f, b/255f, 1f)
fun colorFromRGB(rgb:List<Int>) = colorFromRGB(rgb[0],rgb[1],rgb[2])
fun Actor.centerX(parent:Actor){ x = parent.width/2 - width/2 }
fun Actor.centerY(parent:Actor){ y = parent.height/2- height/2}
fun Actor.center(parent:Actor){ centerX(parent); centerY(parent)}
fun Actor.centerX(parent:Stage){ x = parent.width/2 - width/2 }
fun Actor.centerY(parent:Stage){ y = parent.height/2- height/2}
fun Actor.center(parent:Stage){ centerX(parent); centerY(parent)}
/** same as [onClick], but sends the [InputEvent] and coordinates along */
fun Actor.onClickEvent(sound: UncivSound = UncivSound.Click, function: (event: InputEvent?, x: Float, y: Float) -> Unit) {
this.addListener(object : ClickListener() {
override fun clicked(event: InputEvent?, x: Float, y: Float) {
thread(name="Sound") { Sounds.play(sound) }
function(event, x, y)
}
})
}
// If there are other buttons that require special clicks then we'll have an onclick that will accept a string parameter, no worries
fun Actor.onClick(sound: UncivSound = UncivSound.Click, function: () -> Unit) {
onClickEvent(sound) { _, _, _ -> function() }
}
fun Actor.onClick(function: () -> Unit): Actor {
onClick(UncivSound.Click, function)
return this
}
fun Actor.onChange(function: () -> Unit): Actor {
this.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
function()
}
})
return this
}
fun Actor.surroundWithCircle(size: Float, resizeActor: Boolean = true, color: Color = Color.WHITE): IconCircleGroup {
return IconCircleGroup(size,this,resizeActor, color)
}
fun Actor.addBorder(size:Float,color:Color,expandCell:Boolean=false):Table{
val table = Table()
table.pad(size)
table.background = ImageGetter.getBackground(color)
val cell = table.add(this)
if (expandCell) cell.expand()
cell.fill()
table.pack()
return table
}
fun Table.addSeparator(): Cell<Image> {
row()
val image = ImageGetter.getWhiteDot()
val cell = add(image).colspan(columns).height(2f).fill()
row()
return cell
}
fun Table.addSeparatorVertical(): Cell<Image> {
val image = ImageGetter.getWhiteDot()
val cell = add(image).width(2f).fillY()
return cell
}
fun <T : Actor> Table.addCell(actor: T): Table {
add(actor)
return this
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> ArrayList<T>.withItem(item:T): ArrayList<T> {
val newArrayList = ArrayList(this)
newArrayList.add(item)
return newArrayList
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> HashSet<T>.withItem(item:T): HashSet<T> {
val newHashSet = HashSet(this)
newHashSet.add(item)
return newHashSet
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> ArrayList<T>.withoutItem(item:T): ArrayList<T> {
val newArrayList = ArrayList(this)
newArrayList.remove(item)
return newArrayList
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> HashSet<T>.withoutItem(item:T): HashSet<T> {
val newHashSet = HashSet(this)
newHashSet.remove(item)
return newHashSet
}
fun String.toTextButton() = TextButton(this.tr(), CameraStageBaseScreen.skin)
/** also translates */
fun String.toLabel() = Label(this.tr(),CameraStageBaseScreen.skin)
fun Int.toLabel() = this.toString().toLabel()
// We don't want to use setFontSize and setFontColor because they set the font,
// which means we need to rebuild the font cache which means more memory allocation.
fun String.toLabel(fontColor:Color= Color.WHITE, fontSize:Int=18): Label {
var labelStyle = CameraStageBaseScreen.skin.get(Label.LabelStyle::class.java)
if(fontColor!= Color.WHITE || fontSize!=18) { // if we want the default we don't need to create another style
labelStyle = Label.LabelStyle(labelStyle) // clone this to another
labelStyle.fontColor = fontColor
if (fontSize != 18) labelStyle.font = Fonts.font
}
return Label(this.tr(), labelStyle).apply { setFontScale(fontSize/Fonts.ORIGINAL_FONT_SIZE) }
}
fun Label.setFontColor(color:Color): Label { style=Label.LabelStyle(style).apply { fontColor=color }; return this }
fun Label.setFontSize(size:Int): Label {
style = Label.LabelStyle(style)
style.font = Fonts.font
style = style // because we need it to call the SetStyle function. Yuk, I know.
return this.apply { setFontScale(size/Fonts.ORIGINAL_FONT_SIZE) } // for chaining
}
fun <T> List<T>.randomWeighted(weights: List<Float>, random: Random = Random): T {
if (this.isEmpty()) throw NoSuchElementException("Empty list.")
if (this.size != weights.size) throw UnsupportedOperationException("Weights size does not match this list size.")
val totalWeight = weights.sum()
val randDouble = random.nextDouble()
var sum = 0f
for (i in weights.indices) {
sum += weights[i] / totalWeight
if (randDouble <= sum)
return this[i]
}
return this.last()
}

View File

@ -0,0 +1,187 @@
package com.unciv.ui.utils
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.Actor
import com.badlogic.gdx.scenes.scene2d.InputEvent
import com.badlogic.gdx.scenes.scene2d.Stage
import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener
import com.unciv.models.UncivSound
import com.unciv.models.translations.tr
import kotlin.concurrent.thread
import kotlin.random.Random
fun Button.disable(){
touchable= Touchable.disabled
color= Color.GRAY
}
fun Button.enable() {
color = Color.WHITE
touchable = Touchable.enabled
}
var Button.isEnabled: Boolean
//Todo: Use in PromotionPickerScreen, TradeTable, WorldScreen.updateNextTurnButton
get() = touchable == Touchable.enabled
set(value) = if (value) enable() else disable()
fun colorFromRGB(r: Int, g: Int, b: Int) = Color(r/255f, g/255f, b/255f, 1f)
fun colorFromRGB(rgb:List<Int>) = colorFromRGB(rgb[0],rgb[1],rgb[2])
fun Actor.centerX(parent: Actor){ x = parent.width/2 - width/2 }
fun Actor.centerY(parent: Actor){ y = parent.height/2- height/2}
fun Actor.center(parent: Actor){ centerX(parent); centerY(parent)}
fun Actor.centerX(parent: Stage){ x = parent.width/2 - width/2 }
fun Actor.centerY(parent: Stage){ y = parent.height/2- height/2}
fun Actor.center(parent: Stage){ centerX(parent); centerY(parent)}
/** same as [onClick], but sends the [InputEvent] and coordinates along */
fun Actor.onClickEvent(sound: UncivSound = UncivSound.Click, function: (event: InputEvent?, x: Float, y: Float) -> Unit) {
this.addListener(object : ClickListener() {
override fun clicked(event: InputEvent?, x: Float, y: Float) {
thread(name="Sound") { Sounds.play(sound) }
function(event, x, y)
}
})
}
// If there are other buttons that require special clicks then we'll have an onclick that will accept a string parameter, no worries
fun Actor.onClick(sound: UncivSound = UncivSound.Click, function: () -> Unit) {
onClickEvent(sound) { _, _, _ -> function() }
}
fun Actor.onClick(function: () -> Unit): Actor {
onClick(UncivSound.Click, function)
return this
}
fun Actor.onChange(function: () -> Unit): Actor {
this.addListener(object : ChangeListener() {
override fun changed(event: ChangeEvent?, actor: Actor?) {
function()
}
})
return this
}
fun Actor.surroundWithCircle(size: Float, resizeActor: Boolean = true, color: Color = Color.WHITE): IconCircleGroup {
return IconCircleGroup(size,this,resizeActor, color)
}
fun Actor.addBorder(size:Float, color: Color, expandCell:Boolean=false): Table {
val table = Table()
table.pad(size)
table.background = ImageGetter.getBackground(color)
val cell = table.add(this)
if (expandCell) cell.expand()
cell.fill()
table.pack()
return table
}
fun Table.addSeparator(): Cell<Image> {
row()
val image = ImageGetter.getWhiteDot()
val cell = add(image).colspan(columns).height(2f).fill()
row()
return cell
}
fun Table.addSeparatorVertical(): Cell<Image> {
val image = ImageGetter.getWhiteDot()
val cell = add(image).width(2f).fillY()
return cell
}
fun <T : Actor> Table.addCell(actor: T): Table {
add(actor)
return this
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> ArrayList<T>.withItem(item:T): ArrayList<T> {
val newArrayList = ArrayList(this)
newArrayList.add(item)
return newArrayList
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> HashSet<T>.withItem(item:T): HashSet<T> {
val newHashSet = HashSet(this)
newHashSet.add(item)
return newHashSet
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> ArrayList<T>.withoutItem(item:T): ArrayList<T> {
val newArrayList = ArrayList(this)
newArrayList.remove(item)
return newArrayList
}
/**
* Solves concurrent modification problems - everyone who had a reference to the previous arrayList can keep using it because it hasn't changed
*/
fun <T> HashSet<T>.withoutItem(item:T): HashSet<T> {
val newHashSet = HashSet(this)
newHashSet.remove(item)
return newHashSet
}
fun String.toTextButton() = TextButton(this.tr(), CameraStageBaseScreen.skin)
/** also translates */
fun String.toLabel() = Label(this.tr(),CameraStageBaseScreen.skin)
fun Int.toLabel() = this.toString().toLabel()
// We don't want to use setFontSize and setFontColor because they set the font,
// which means we need to rebuild the font cache which means more memory allocation.
fun String.toLabel(fontColor: Color = Color.WHITE, fontSize:Int=18): Label {
var labelStyle = CameraStageBaseScreen.skin.get(Label.LabelStyle::class.java)
if(fontColor!= Color.WHITE || fontSize!=18) { // if we want the default we don't need to create another style
labelStyle = Label.LabelStyle(labelStyle) // clone this to another
labelStyle.fontColor = fontColor
if (fontSize != 18) labelStyle.font = Fonts.font
}
return Label(this.tr(), labelStyle).apply { setFontScale(fontSize/Fonts.ORIGINAL_FONT_SIZE) }
}
fun Label.setFontColor(color: Color): Label { style= Label.LabelStyle(style).apply { fontColor=color }; return this }
fun Label.setFontSize(size:Int): Label {
style = Label.LabelStyle(style)
style.font = Fonts.font
style = style // because we need it to call the SetStyle function. Yuk, I know.
return this.apply { setFontScale(size/ Fonts.ORIGINAL_FONT_SIZE) } // for chaining
}
fun <T> List<T>.randomWeighted(weights: List<Float>, random: Random = Random): T {
if (this.isEmpty()) throw NoSuchElementException("Empty list.")
if (this.size != weights.size) throw UnsupportedOperationException("Weights size does not match this list size.")
val totalWeight = weights.sum()
val randDouble = random.nextDouble()
var sum = 0f
for (i in weights.indices) {
sum += weights[i] / totalWeight
if (randDouble <= sum)
return this[i]
}
return this.last()
}

View File

@ -0,0 +1,74 @@
package com.unciv.ui.utils
import com.badlogic.gdx.Input
import com.badlogic.gdx.scenes.scene2d.InputEvent
import java.util.HashMap
/*
* For now, combination keys cannot easily be expressed.
* Pressing Ctrl-Letter will arrive one event for Input.Keys.CONTROL_LEFT and one for the ASCII control code point
* so Ctrl-R can be handled using KeyCharAndCode('\u0012')
* Pressing Alt-Something likewise will fire once for Alt and once for the unmodified keys with no indication Alt is held
* (Exception: international keyboard AltGr-combos)
* An update supporting easy declarations for any modifier combos would need to use Gdx.input.isKeyPressed()
* Gdx seems to omit support for a modifier mask (e.g. Ctrl-Alt-Shift) so we would need to reinvent this
*/
/**
* Represents a key for use in an InputListener keyTyped() handler
*
* Example: KeyCharAndCode('R'), KeyCharAndCode(Input.Keys.F1)
*/
data class KeyCharAndCode(val char: Char, val code: Int) {
// express keys with a Char value
constructor(char: Char): this(char.toLowerCase(), 0)
// express keys that only have a keyCode like F1
constructor(code: Int): this(Char.MIN_VALUE, code)
// helper for use in InputListener keyTyped()
constructor(event: InputEvent?, character: Char)
: this (
character.toLowerCase(),
if (character == Char.MIN_VALUE && event!=null) event.keyCode else 0
)
@ExperimentalStdlibApi
override fun toString(): String {
// debug helper
return when {
char == Char.MIN_VALUE -> Input.Keys.toString(code)
char < ' ' -> "Ctrl-" + Char(char.toInt()+64)
else -> "\"$char\""
}
}
}
class KeyPressDispatcher: HashMap<KeyCharAndCode, (() -> Unit)>() {
private var checkpoint: Set<KeyCharAndCode> = setOf()
// access by Char
operator fun get(char: Char) = this[KeyCharAndCode(char)]
operator fun set(char: Char, action: () -> Unit) {
this[KeyCharAndCode(char)] = action
}
operator fun contains(char: Char) = this.contains(KeyCharAndCode(char))
fun remove(char: Char) = this.remove(KeyCharAndCode(char))
// access by Int keyCodes
operator fun get(code: Int) = this[KeyCharAndCode(code)]
operator fun set(code: Int, action: () -> Unit) {
this[KeyCharAndCode(code)] = action
}
operator fun contains(code: Int) = this.contains(KeyCharAndCode(code))
fun remove(code: Int) = this.remove(KeyCharAndCode(code))
override fun clear() {
checkpoint = setOf()
super.clear()
}
fun setCheckpoint() {
checkpoint = keys.toSet()
}
fun revertToCheckPoint() {
keys.minus(checkpoint).forEach { remove(it) }
}
}