|
|
|
@ -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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|