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.scenes.scene2d.*
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.utils.viewport.ExtendViewport
import com.unciv.UncivGame
import com.unciv.models.Tutorial
import com.unciv.models.UncivSound
import com.unciv.models.translations.tr
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 {
@ -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) }
}
}