mirror of
https://github.com/yairm210/Unciv.git
synced 2025-03-15 04:14:44 +07:00
Notifications can be "selected" (#9182)
* Allow "selecting" notifications * NotificationsScroll remembers position relative to topRight * NotificationsScroll tries to scroll selection into view * Fix notification selection and scroll-into-view * User option to control enlarging selected notifications * Post-merge missed changes * Move, flip and reword "Enlarge" option
This commit is contained in:
parent
ab1d823477
commit
4f30d27d0b
@ -785,6 +785,8 @@ Font size multiplier =
|
||||
Default Font =
|
||||
|
||||
Enable Easter Eggs =
|
||||
Enlarge selected notifications =
|
||||
|
||||
Order trade offers by amount =
|
||||
Enable display cutout (requires restart) =
|
||||
|
||||
|
@ -108,11 +108,13 @@ data class LocationAction(var locations: ArrayList<Vector2> = ArrayList()) : Not
|
||||
constructor(locations: Sequence<Vector2>) : this(locations.toCollection(ArrayList()))
|
||||
constructor(vararg locations: Vector2?) : this(locations.asSequence().filterNotNull())
|
||||
|
||||
@Transient
|
||||
private var index = 0
|
||||
|
||||
override fun execute(worldScreen: WorldScreen) {
|
||||
if (locations.isNotEmpty()) {
|
||||
var index = locations.indexOf(worldScreen.mapHolder.selectedTile?.position)
|
||||
index = ++index % locations.size // cycle through tiles
|
||||
worldScreen.mapHolder.setCenterPosition(locations[index], selectUnit = false)
|
||||
index = ++index % locations.size // cycle through tiles
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,6 +119,9 @@ class GameSettings {
|
||||
/** NotificationScroll on Word Screen visibility control - mapped to NotificationsScroll.UserSetting enum */
|
||||
var notificationScroll: String = ""
|
||||
|
||||
/** If on, selected notifications are drawn enlarged with wider padding */
|
||||
var enlargeSelectedNotification = true
|
||||
|
||||
/** used to migrate from older versions of the settings */
|
||||
var version: Int? = null
|
||||
|
||||
|
@ -76,6 +76,8 @@ fun advancedTab(
|
||||
addSetUserId(this, settings)
|
||||
|
||||
addEasterEggsCheckBox(this, settings)
|
||||
|
||||
addEnlargeNotificationsCheckBox(this, settings)
|
||||
}
|
||||
|
||||
private fun addCutoutCheckbox(table: Table, optionsPopup: OptionsPopup) {
|
||||
@ -346,3 +348,9 @@ private fun addEasterEggsCheckBox(table: Table, settings: GameSettings) {
|
||||
val checkbox = "Enable Easter Eggs".toCheckBox(settings.enableEasterEggs) { settings.enableEasterEggs = it }
|
||||
table.add(checkbox).colspan(2).row()
|
||||
}
|
||||
|
||||
private fun addEnlargeNotificationsCheckBox(table: Table, settings: GameSettings) {
|
||||
val checkbox = "Enlarge selected notifications"
|
||||
.toCheckBox(settings.enlargeSelectedNotification) { settings.enlargeSelectedNotification = it }
|
||||
table.add(checkbox).colspan(2).row()
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.unciv.GUI
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.civilization.Civilization
|
||||
import com.unciv.logic.civilization.LocationAction
|
||||
import com.unciv.logic.civilization.Notification
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.ui.components.ColorMarkupLabel
|
||||
@ -90,10 +91,12 @@ class NotificationsOverviewTable(
|
||||
notificationTable.add(label).width(worldScreen.stage.width/2 - iconSize * notification.icons.size)
|
||||
notificationTable.background = BaseScreen.skinStrings.getUiBackground("OverviewScreen/NotificationOverviewTable/Notification", BaseScreen.skinStrings.roundedEdgeRectangleShape)
|
||||
notificationTable.touchable = Touchable.enabled
|
||||
notificationTable.onClick {
|
||||
UncivGame.Current.resetToWorldScreen()
|
||||
notification.action?.execute(worldScreen)
|
||||
}
|
||||
if (notification.action != null)
|
||||
notificationTable.onClick {
|
||||
worldScreen.notificationsScroll.oneTimeNotification = notification
|
||||
UncivGame.Current.resetToWorldScreen()
|
||||
notification.action?.execute(worldScreen)
|
||||
}
|
||||
|
||||
notification.addNotificationIconsTo(notificationTable, worldScreen.gameInfo.ruleset, iconSize)
|
||||
|
||||
|
@ -13,6 +13,7 @@ import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.GUI
|
||||
import com.unciv.logic.civilization.Notification
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
import com.unciv.ui.components.AutoScrollPane as ScrollPane
|
||||
import com.unciv.ui.components.ColorMarkupLabel
|
||||
import com.unciv.ui.components.WrappableLabel
|
||||
import com.unciv.ui.components.extensions.onClick
|
||||
@ -21,7 +22,6 @@ import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
import com.unciv.ui.images.IconCircleGroup
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.components.AutoScrollPane as ScrollPane
|
||||
|
||||
/*TODO
|
||||
* Un-hiding the notifications when new ones arrive is a little pointless due to Categories:
|
||||
@ -51,6 +51,12 @@ class NotificationsScroll(
|
||||
const val categoryTopPad = 15f
|
||||
/** Spacing between rightmost Label edge and right Screen limit */
|
||||
const val rightPadToScreenEdge = 10f
|
||||
/** Extra right padding when there's a selection */
|
||||
const val selectionExtraRightPad = 12f
|
||||
/** Padding within ListItem, included in clickable area, except to the right */
|
||||
const val listItemPad = 3f
|
||||
/** Top/Bottom padding within ListItem replaces [listItemPad] for the [highlightNotification] */
|
||||
const val selectedListItemPad = 15f
|
||||
/** Extra spacing between the outer edges of the category header decoration lines and
|
||||
* the left and right edges of the widest notification label - this is the background's
|
||||
* edge radius and looks (subjectively) nice. */
|
||||
@ -59,8 +65,11 @@ class NotificationsScroll(
|
||||
const val restoreButtonSize = 42f
|
||||
/** Distance of restore button to TileInfoTable and screen edge */
|
||||
const val restoreButtonPad = 12f
|
||||
/** Background tint for [oneTimeNotification] */
|
||||
private val oneTimeNotificationColor = Color.valueOf("fceea8")
|
||||
}
|
||||
|
||||
//region private fields
|
||||
private var notificationsHash: Int = 0
|
||||
|
||||
private var notificationsTable = Table()
|
||||
@ -68,6 +77,7 @@ class NotificationsScroll(
|
||||
private var bottomSpacerCell: Cell<Actor?>? = null
|
||||
|
||||
private val maxEntryWidth = worldScreen.stage.width * maxWidthOfStage * inverseScaleFactor
|
||||
|
||||
/** For category header decoration lines */
|
||||
private val minCategoryLineWidth = worldScreen.stage.width * 0.075f
|
||||
/** Show restoreButton when less than this much of the pane is left */
|
||||
@ -77,6 +87,20 @@ class NotificationsScroll(
|
||||
|
||||
private var userSetting = UserSetting.Visible
|
||||
private var userSettingChanged = false
|
||||
private var enlargeHighlight = false
|
||||
|
||||
/** onClick sets this to request highlighting on the next update (which it then triggers) */
|
||||
private var clickedNotification: Notification? = null
|
||||
/** Set _only_ during updateContent to draw the actual highlighting */
|
||||
private var highlightNotification: Notification? = null
|
||||
/** Set _only_ during updateContent to draw the highlighted entry with a colored background */
|
||||
private var coloredHighlight = false
|
||||
/** Used once after updateContent to scroll the highlighted notification into view */
|
||||
private var selectedCell: Cell<ListItem>? = null
|
||||
//endregion
|
||||
|
||||
/** Display one additional notification once, to re-show an entry from the history in overview */
|
||||
var oneTimeNotification: Notification? = null
|
||||
|
||||
init {
|
||||
actor = notificationsTable.right()
|
||||
@ -111,12 +135,14 @@ class NotificationsScroll(
|
||||
coveredNotificationsBottom: Float
|
||||
) {
|
||||
if (getUserSettingCheckDisabled()) return
|
||||
enlargeHighlight = GUI.getSettings().enlargeSelectedNotification
|
||||
|
||||
val previousScrollX = when {
|
||||
isScrollingDisabledX -> width // switching from Permanent - scrollX and maxX are 0
|
||||
userSetting.static -> maxX // Permanent: fully visible
|
||||
notificationsTable.hasChildren() -> scrollX // save current scroll
|
||||
else -> 0f // Swiching Hidden to Dynamic - animate "in" only
|
||||
// Remember scroll position _relative to topRight_
|
||||
val previousScrollXinv = when {
|
||||
isScrollingDisabledX -> 0f // switching from Permanent - scrollX and maxX are 0
|
||||
userSetting.static -> 0f // Permanent: fully visible
|
||||
notificationsTable.hasChildren() -> maxX - scrollX // save current scroll
|
||||
else -> maxX // Swiching Hidden to Dynamic - animate "in" only
|
||||
}
|
||||
val previousScrollY = scrollY
|
||||
|
||||
@ -128,8 +154,17 @@ class NotificationsScroll(
|
||||
updateSpacers(coveredNotificationsTop, coveredNotificationsBottom)
|
||||
}
|
||||
|
||||
scrollX = previousScrollX
|
||||
scrollY = previousScrollY
|
||||
scrollX = maxX - previousScrollXinv
|
||||
scrollY = if (selectedCell == null) {
|
||||
previousScrollY
|
||||
} else selectedCell!!.let {
|
||||
val actualBottom = (it.actorY + notificationsTable.y) * scaleFactor
|
||||
val actualTop = (it.actorY + it.actorHeight + notificationsTable.y) * scaleFactor
|
||||
val fullyVisible = actualBottom >= coveredNotificationsBottom && actualTop <= stage.height - coveredNotificationsTop
|
||||
val centeredBottom = (stage.height - coveredNotificationsTop + coveredNotificationsBottom - it.actorHeight * scaleFactor) / 2
|
||||
val centeredScrollY = centeredBottom * inverseScaleFactor - it.actorY + maxY
|
||||
if (fullyVisible) previousScrollY else centeredScrollY
|
||||
}
|
||||
updateVisualScroll()
|
||||
|
||||
applyUserSettingChange()
|
||||
@ -152,14 +187,36 @@ class NotificationsScroll(
|
||||
coveredNotificationsTop: Float,
|
||||
coveredNotificationsBottom: Float
|
||||
): Boolean {
|
||||
// no news? - keep our list as it is
|
||||
val newHash = notifications.hashCode()
|
||||
if (notificationsHash == newHash) return false
|
||||
selectedCell = null
|
||||
|
||||
// Detect what to draw and if there's any changes part 1
|
||||
if (oneTimeNotification == null && clickedNotification != null)
|
||||
oneTimeNotification = clickedNotification // reselecting can keep a "one-time" in the list
|
||||
val newHash = notifications.hashCode() + oneTimeNotification.hashCode() * 31
|
||||
|
||||
// Determine highlight
|
||||
coloredHighlight = false
|
||||
var additionalNotification = emptySequence<Notification>()
|
||||
highlightNotification = if (oneTimeNotification == null) clickedNotification
|
||||
else oneTimeNotification!!.apply {
|
||||
if (this !in notifications) {
|
||||
additionalNotification = sequenceOf(this)
|
||||
coloredHighlight = true
|
||||
}
|
||||
oneTimeNotification = null
|
||||
}
|
||||
clickedNotification = null
|
||||
|
||||
// Detect change part 2 - early exit if no re-render needed
|
||||
// Note no change detection for highlightNotification - if there's a selection we always
|
||||
// need the redraw to determine the selectedCell, to enable scroll-into-view
|
||||
if (notificationsHash == newHash && highlightNotification == null) return false
|
||||
notificationsHash = newHash
|
||||
|
||||
// Rebuild the notifications list
|
||||
notificationsTable.clear()
|
||||
notificationsTable.pack() // forget last width!
|
||||
if (notifications.isEmpty()) return true
|
||||
if (notifications.isEmpty() && additionalNotification.none()) return true
|
||||
|
||||
val categoryHeaders = mutableListOf<CategoryHeader>()
|
||||
val itemWidths = mutableListOf<Float>()
|
||||
@ -170,7 +227,7 @@ class NotificationsScroll(
|
||||
|
||||
val backgroundDrawable = BaseScreen.skinStrings.getUiBackground("WorldScreen/Notification", BaseScreen.skinStrings.roundedEdgeRectangleShape)
|
||||
|
||||
val orderedNotifications = notifications.asReversed()
|
||||
val orderedNotifications = (additionalNotification + notifications.asReversed())
|
||||
.groupBy { NotificationCategory.safeValueOf(it.category) ?: NotificationCategory.General }
|
||||
.toSortedMap() // This sorts by Category ordinal, so far intentional - the order of the grouped lists are unaffected
|
||||
for ((category, categoryNotifications) in orderedNotifications) {
|
||||
@ -184,7 +241,9 @@ class NotificationsScroll(
|
||||
for (notification in categoryNotifications) {
|
||||
val item = ListItem(notification, backgroundDrawable)
|
||||
itemWidths.add(item.itemWidth)
|
||||
notificationsTable.add(item).right().row()
|
||||
val itemCell = notificationsTable.add(item)
|
||||
if (notification == highlightNotification) selectedCell = itemCell
|
||||
itemCell.right().row()
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +257,7 @@ class NotificationsScroll(
|
||||
bottomSpacerCell = notificationsTable.add()
|
||||
.height(coveredNotificationsBottom * inverseScaleFactor).expandY()
|
||||
notificationsTable.row()
|
||||
highlightNotification = null // no longer needed
|
||||
return true
|
||||
}
|
||||
|
||||
@ -220,10 +280,14 @@ class NotificationsScroll(
|
||||
add(label)
|
||||
captionWidth = prefWidth // of this wrapper including background rims
|
||||
captionWidth
|
||||
}).pad(3f)
|
||||
}).pad(listItemPad)
|
||||
val rightPad = categoryHorizontalPad + rightPadToScreenEdge + (
|
||||
if (!enlargeHighlight || highlightNotification == null) 0f
|
||||
else selectionExtraRightPad
|
||||
)
|
||||
rightLineCell = add(ImageGetter.getWhiteDot())
|
||||
.minHeight(2f).width(minCategoryLineWidth)
|
||||
.padRight(categoryHorizontalPad + rightPadToScreenEdge)
|
||||
.padRight(rightPad)
|
||||
}
|
||||
|
||||
/** Equalizes width by adjusting length of the decoration lines.
|
||||
@ -247,11 +311,19 @@ class NotificationsScroll(
|
||||
val itemWidth: Float
|
||||
|
||||
init {
|
||||
val listItem = Table()
|
||||
listItem.background = backgroundDrawable
|
||||
val isSelected = notification === highlightNotification // Notification does not implement equality contract
|
||||
val isEnlarged = isSelected && enlargeHighlight
|
||||
val labelFontSize = if (isEnlarged) fontSize + fontSize / 2 else fontSize
|
||||
val itemIconSize = if (isEnlarged) iconSize * 1.5f else iconSize
|
||||
val topBottomPad = if (isEnlarged) selectedListItemPad else listItemPad
|
||||
|
||||
val maxLabelWidth = maxEntryWidth - (iconSize + 5f) * notification.icons.size - 10f
|
||||
val label = WrappableLabel(notification.text, maxLabelWidth, Color.BLACK, fontSize, hideIcons = true)
|
||||
val listItem = Table()
|
||||
listItem.background = if (!isSelected || !coloredHighlight) backgroundDrawable else {
|
||||
BaseScreen.skinStrings.getUiBackground("WorldScreen/Notification", BaseScreen.skinStrings.roundedEdgeRectangleShape, oneTimeNotificationColor)
|
||||
}
|
||||
|
||||
val maxLabelWidth = maxEntryWidth - (itemIconSize + 5f) * notification.icons.size - 10f
|
||||
val label = WrappableLabel(notification.text, maxLabelWidth, Color.BLACK, labelFontSize, hideIcons = true)
|
||||
label.setAlignment(Align.center)
|
||||
if (label.prefWidth > maxLabelWidth * scaleFactor) { // can't explain why the comparison needs scaleFactor
|
||||
label.wrap = true
|
||||
@ -260,15 +332,19 @@ class NotificationsScroll(
|
||||
listItem.add(label).padRight(10f)
|
||||
}
|
||||
|
||||
notification.addNotificationIconsTo(listItem, worldScreen.gameInfo.ruleset, iconSize)
|
||||
notification.addNotificationIconsTo(listItem, worldScreen.gameInfo.ruleset, itemIconSize)
|
||||
|
||||
itemWidth = listItem.prefWidth // includes the background NinePatch's leftWidth+rightWidth
|
||||
|
||||
// using a large click area with no gap in between each message item.
|
||||
// this avoids accidentally clicking in between the messages, resulting in a map click
|
||||
add(listItem).pad(3f, 3f, 3f, rightPadToScreenEdge)
|
||||
add(listItem).pad(topBottomPad, listItemPad, topBottomPad, rightPadToScreenEdge)
|
||||
touchable = Touchable.enabled
|
||||
onClick { notification.action?.execute(worldScreen) }
|
||||
onClick {
|
||||
notification.action?.execute(worldScreen)
|
||||
clickedNotification = notification
|
||||
GUI.setUpdateWorldOnNextRender()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ class WorldScreen(
|
||||
private val zoomController = ZoomButtonPair(mapHolder)
|
||||
internal val minimapWrapper = MinimapHolder(mapHolder)
|
||||
private val bottomTileInfoTable = TileInfoTable(viewingCiv)
|
||||
private val notificationsScroll = NotificationsScroll(this)
|
||||
internal val notificationsScroll = NotificationsScroll(this)
|
||||
internal val nextTurnButton = NextTurnButton()
|
||||
private val statusButtons = StatusButtons(nextTurnButton)
|
||||
private val tutorialTaskTable = Table().apply {
|
||||
|
Loading…
Reference in New Issue
Block a user