mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-11 00:08:58 +07:00
Keyboard bindings - collision check (#9580)
This commit is contained in:
@ -398,7 +398,6 @@
|
|||||||
{"text":"This is a work in progress.","color":"#b22222","starred":true},
|
{"text":"This is a work in progress.","color":"#b22222","starred":true},
|
||||||
{"text":"For technical reasons, only direct keys or Ctrl-Letter combinations can be used.","starred":true},
|
{"text":"For technical reasons, only direct keys or Ctrl-Letter combinations can be used.","starred":true},
|
||||||
{"text":"The Escape key is intentionally excluded from being reassigned.","starred":true},
|
{"text":"The Escape key is intentionally excluded from being reassigned.","starred":true},
|
||||||
{"text":"Currently, there are no checks to prevent conflicting assignments.","starred":true},
|
|
||||||
{},
|
{},
|
||||||
{"text":"Using the Keys page","header":3},
|
{"text":"Using the Keys page","header":3},
|
||||||
{"text":"Each binding has a button with an image looking like this:"},
|
{"text":"Each binding has a button with an image looking like this:"},
|
||||||
@ -407,6 +406,7 @@
|
|||||||
{"text":"Double-click the image to reset the binding to default."},
|
{"text":"Double-click the image to reset the binding to default."},
|
||||||
{},
|
{},
|
||||||
{"text":"Bindings mapped to their default keys are displayed in gray, those reassigned by you in white."},
|
{"text":"Bindings mapped to their default keys are displayed in gray, those reassigned by you in white."},
|
||||||
|
{"text":"Conflicting assignments are marked red. Conflicts can exist across categories, like World Screen / Unit Actions. Note that at the moment, the game does not prevent saving conflicting assignments, though the result may be unexpected."},
|
||||||
{},
|
{},
|
||||||
{"text":"For discussion about missing entries, see the linked github issue.","link":"https://github.com/yairm210/Unciv/issues/8862"}
|
{"text":"For discussion about missing entries, see the linked github issue.","link":"https://github.com/yairm210/Unciv/issues/8862"}
|
||||||
]
|
]
|
||||||
|
@ -68,9 +68,16 @@ class KeyCapturingButton(
|
|||||||
updateLabel()
|
updateLabel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var markConflict = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
updateStyle()
|
||||||
|
}
|
||||||
|
|
||||||
private var savedFocus: Actor? = null
|
private var savedFocus: Actor? = null
|
||||||
private val normalStyle: ImageTextButtonStyle
|
private val normalStyle: ImageTextButtonStyle
|
||||||
private val defaultStyle: ImageTextButtonStyle
|
private val defaultStyle: ImageTextButtonStyle
|
||||||
|
private val conflictStyle: ImageTextButtonStyle
|
||||||
|
|
||||||
init {
|
init {
|
||||||
imageCell.size((style as KeyCapturingButtonStyle).imageSize)
|
imageCell.size((style as KeyCapturingButtonStyle).imageSize)
|
||||||
@ -80,13 +87,23 @@ class KeyCapturingButton(
|
|||||||
normalStyle = style
|
normalStyle = style
|
||||||
defaultStyle = ImageTextButtonStyle(normalStyle)
|
defaultStyle = ImageTextButtonStyle(normalStyle)
|
||||||
defaultStyle.fontColor = Color.GRAY.cpy()
|
defaultStyle.fontColor = Color.GRAY.cpy()
|
||||||
|
conflictStyle = ImageTextButtonStyle(normalStyle)
|
||||||
|
conflictStyle.fontColor = Color.RED.cpy()
|
||||||
addListener(ButtonListener(this))
|
addListener(ButtonListener(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLabel() {
|
private fun updateLabel() {
|
||||||
label.setText(if (current == KeyCharAndCode.UNKNOWN) "" else current.toString())
|
label.setText(if (current == KeyCharAndCode.UNKNOWN) "" else current.toString())
|
||||||
style = if (current == default) defaultStyle else normalStyle
|
updateStyle()
|
||||||
}
|
}
|
||||||
|
private fun updateStyle() {
|
||||||
|
style = when {
|
||||||
|
markConflict -> conflictStyle
|
||||||
|
current == default -> defaultStyle
|
||||||
|
else -> normalStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleKey(code: Int, control: Boolean) {
|
private fun handleKey(code: Int, control: Boolean) {
|
||||||
current = if (control) KeyCharAndCode.ctrlFromCode(code) else KeyCharAndCode(code)
|
current = if (control) KeyCharAndCode.ctrlFromCode(code) else KeyCharAndCode(code)
|
||||||
onKeyHit?.invoke(current)
|
onKeyHit?.invoke(current)
|
||||||
|
@ -12,6 +12,8 @@ enum class KeyboardBinding(
|
|||||||
label: String? = null,
|
label: String? = null,
|
||||||
key: KeyCharAndCode? = null
|
key: KeyCharAndCode? = null
|
||||||
) {
|
) {
|
||||||
|
//region Enum Instances
|
||||||
|
|
||||||
/** Used by [KeyShortcutDispatcher.KeyShortcut] to mark an old-style shortcut with a hardcoded key */
|
/** Used by [KeyShortcutDispatcher.KeyShortcut] to mark an old-style shortcut with a hardcoded key */
|
||||||
None(Category.None, KeyCharAndCode.UNKNOWN),
|
None(Category.None, KeyCharAndCode.UNKNOWN),
|
||||||
|
|
||||||
@ -89,10 +91,22 @@ enum class KeyboardBinding(
|
|||||||
Confirm(Category.Popups, "Confirm Dialog", 'y'),
|
Confirm(Category.Popups, "Confirm Dialog", 'y'),
|
||||||
Cancel(Category.Popups, "Cancel Dialog", 'n'),
|
Cancel(Category.Popups, "Cancel Dialog", 'n'),
|
||||||
;
|
;
|
||||||
|
//endregion
|
||||||
|
|
||||||
enum class Category {
|
enum class Category {
|
||||||
None, WorldScreen, UnitActions, Popups;
|
None,
|
||||||
|
WorldScreen {
|
||||||
|
// Conflict checking within group plus keys assigned to UnitActions are a problem
|
||||||
|
override fun checkConflictsIn() = sequenceOf(this, UnitActions)
|
||||||
|
},
|
||||||
|
UnitActions {
|
||||||
|
// Conflict checking within group disabled, but any key assigned on WorldScreen is a problem
|
||||||
|
override fun checkConflictsIn() = sequenceOf(WorldScreen)
|
||||||
|
},
|
||||||
|
Popups
|
||||||
|
;
|
||||||
val label = unCamelCase(name)
|
val label = unCamelCase(name)
|
||||||
|
open fun checkConflictsIn() = sequenceOf(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
val label: String
|
val label: String
|
||||||
@ -104,12 +118,13 @@ enum class KeyboardBinding(
|
|||||||
this.defaultKey = key ?: KeyCharAndCode(name[0])
|
this.defaultKey = key ?: KeyCharAndCode(name[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers to make enum instance initializations shorter
|
//region Helper constructors
|
||||||
constructor(category: Category, label: String, key: Char) : this(category, label, KeyCharAndCode(key))
|
constructor(category: Category, label: String, key: Char) : this(category, label, KeyCharAndCode(key))
|
||||||
constructor(category: Category, label: String, key: Int) : this(category, label, KeyCharAndCode(key))
|
constructor(category: Category, label: String, key: Int) : this(category, label, KeyCharAndCode(key))
|
||||||
constructor(category: Category, key: KeyCharAndCode) : this(category, null, key)
|
constructor(category: Category, key: KeyCharAndCode) : this(category, null, key)
|
||||||
constructor(category: Category, key: Char) : this(category, KeyCharAndCode(key))
|
constructor(category: Category, key: Char) : this(category, KeyCharAndCode(key))
|
||||||
constructor(category: Category, key: Int) : this(category, KeyCharAndCode(key))
|
constructor(category: Category, key: Int) : this(category, KeyCharAndCode(key))
|
||||||
|
//endregion
|
||||||
|
|
||||||
/** Debug helper */
|
/** Debug helper */
|
||||||
override fun toString() = "$category.$name($defaultKey)"
|
override fun toString() = "$category.$name($defaultKey)"
|
||||||
|
@ -7,6 +7,7 @@ import com.unciv.models.ruleset.RulesetCache
|
|||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.components.ExpanderTab
|
import com.unciv.ui.components.ExpanderTab
|
||||||
import com.unciv.ui.components.KeyCapturingButton
|
import com.unciv.ui.components.KeyCapturingButton
|
||||||
|
import com.unciv.ui.components.input.KeyCharAndCode
|
||||||
import com.unciv.ui.components.input.KeyboardBinding
|
import com.unciv.ui.components.input.KeyboardBinding
|
||||||
import com.unciv.ui.components.TabbedPager
|
import com.unciv.ui.components.TabbedPager
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
@ -47,7 +48,7 @@ class KeyBindingsTab(
|
|||||||
.map { (category, bindings) ->
|
.map { (category, bindings) ->
|
||||||
category to bindings.asSequence()
|
category to bindings.asSequence()
|
||||||
.sortedWith(compareBy(collator) { it.label.tr() })
|
.sortedWith(compareBy(collator) { it.label.tr() })
|
||||||
.map { it to KeyCapturingButton(it.defaultKey) } // associate would materialize a map
|
.map { it to KeyCapturingButton(it.defaultKey) { onKeyHit() } } // associate would materialize a map
|
||||||
.toMap(LinkedHashMap())
|
.toMap(LinkedHashMap())
|
||||||
}
|
}
|
||||||
.sortedBy { it.first.name.tr() }
|
.sortedBy { it.first.name.tr() }
|
||||||
@ -81,6 +82,33 @@ class KeyBindingsTab(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun onKeyHit() {
|
||||||
|
for ((category, bindings) in groupedWidgets) {
|
||||||
|
val scope = category.checkConflictsIn()
|
||||||
|
if (scope.none()) continue
|
||||||
|
|
||||||
|
val usedKeys = mutableSetOf<KeyCharAndCode>()
|
||||||
|
val conflictingKeys = mutableSetOf<KeyCharAndCode>()
|
||||||
|
val widgetsInScope = scope
|
||||||
|
.mapNotNull { groupedWidgets[it] }
|
||||||
|
.flatMap { scopeMap -> scopeMap.map { it.key.category to it.value } }
|
||||||
|
|
||||||
|
for ((scopeCategory ,widget) in widgetsInScope) {
|
||||||
|
val key = widget.current
|
||||||
|
// We shall not use any key of a different category in scope,
|
||||||
|
// nor use a key within this category twice - if this category _is_ in scope.
|
||||||
|
if (key in usedKeys || scopeCategory != category)
|
||||||
|
conflictingKeys += key
|
||||||
|
else
|
||||||
|
usedKeys += key
|
||||||
|
}
|
||||||
|
|
||||||
|
for (widget in bindings.values) {
|
||||||
|
widget.markConflict = widget.current in conflictingKeys
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun save () {
|
fun save () {
|
||||||
for ((binding, widget) in groupedWidgets.asSequence().flatMap { it.value.entries }) {
|
for ((binding, widget) in groupedWidgets.asSequence().flatMap { it.value.entries }) {
|
||||||
keyBindings[binding] = widget.current
|
keyBindings[binding] = widget.current
|
||||||
|
Reference in New Issue
Block a user