From 2f035e55f73c66933087813a79ebc18fa4160156 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Fri, 25 Mar 2022 15:28:36 +0100 Subject: [PATCH] Improved Widgets - Fixing Tabbed Pager Scrolling (#6413) * Fix TabbedPager problems when fixedContent wider than widget - Sync ScrollPanes approach V2 * UncivTooltip no longer has a reason to limit itself to Group --- core/src/com/unciv/ui/utils/AutoScrollPane.kt | 12 +- core/src/com/unciv/ui/utils/TabbedPager.kt | 416 +++++++++++++----- core/src/com/unciv/ui/utils/UncivTooltip.kt | 8 +- 3 files changed, 307 insertions(+), 129 deletions(-) diff --git a/core/src/com/unciv/ui/utils/AutoScrollPane.kt b/core/src/com/unciv/ui/utils/AutoScrollPane.kt index b0403fa085..279c9b9d04 100644 --- a/core/src/com/unciv/ui/utils/AutoScrollPane.kt +++ b/core/src/com/unciv/ui/utils/AutoScrollPane.kt @@ -32,22 +32,22 @@ import com.badlogic.gdx.scenes.scene2d.utils.ClickListener */ open class AutoScrollPane(widget: Actor?, style: ScrollPaneStyle = ScrollPaneStyle()): ScrollPane(widget,style) { - constructor(widget: Actor, skin: Skin) : this(widget,skin.get(ScrollPaneStyle::class.java)) - constructor(widget: Actor, skin: Skin, styleName: String) : this(widget,skin.get(styleName,ScrollPaneStyle::class.java)) + constructor(widget: Actor?, skin: Skin) : this(widget,skin.get(ScrollPaneStyle::class.java)) + constructor(widget: Actor?, skin: Skin, styleName: String) : this(widget,skin.get(styleName,ScrollPaneStyle::class.java)) private var savedFocus: Actor? = null init { this.addListener (object : ClickListener() { override fun enter(event: InputEvent?, x: Float, y: Float, pointer: Int, fromActor: Actor?) { - if (stage == null) - return + if (stage == null) return + if (fromActor?.isDescendantOf(this@AutoScrollPane) == true) return if (savedFocus == null) savedFocus = stage.scrollFocus stage.scrollFocus = this@AutoScrollPane } override fun exit(event: InputEvent?, x: Float, y: Float, pointer: Int, toActor: Actor?) { - if (stage == null) - return + if (stage == null) return + if (toActor?.isDescendantOf(this@AutoScrollPane) == true) return if (stage.scrollFocus == this@AutoScrollPane) stage.scrollFocus = savedFocus savedFocus = null } diff --git a/core/src/com/unciv/ui/utils/TabbedPager.kt b/core/src/com/unciv/ui/utils/TabbedPager.kt index 2be97e4a3b..a3008b4b4a 100644 --- a/core/src/com/unciv/ui/utils/TabbedPager.kt +++ b/core/src/com/unciv/ui/utils/TabbedPager.kt @@ -1,19 +1,18 @@ package com.unciv.ui.utils import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.graphics.g3d.model.Animation -import com.badlogic.gdx.scenes.scene2d.Actor -import com.badlogic.gdx.scenes.scene2d.Group +import com.badlogic.gdx.scenes.scene2d.* import com.badlogic.gdx.scenes.scene2d.ui.* +import com.badlogic.gdx.scenes.scene2d.utils.ActorGestureListener +import com.badlogic.gdx.utils.Align import com.unciv.Constants import com.unciv.UncivGame import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip -import kotlin.math.min + /* Unimplemented ideas: Use fixedContent for OptionsPopup mod check tab - `scrollAlign: Align` property controls initial content scroll position (currently it's Align.top) */ /** @@ -23,24 +22,28 @@ import kotlin.math.min * [replaced][replacePage] or dynamically added after the Widget is already shown. * Pages are automatically scrollable, switching pages preserves scroll positions individually. + * The widget optionally supports "fixed content", an additional Actor that will be inserted above + * the regular content of any page. It will scroll only horizontally, and its scroll position will + * synchronize bidirectionally with the scrolling of the main content. + * * Pages can be disabled or secret - any 'secret' pages added require a later call to [askForPassword] * to activate them (or discard if the password is wrong). * * The size parameters are lower and upper bounds of the page content area. The widget will always report * these bounds (plus header height) as layout properties min/max-Width/Height, and measure the content * area of added pages and set the reported pref-W/H to their maximum within these bounds. But, if a - * maximum is not specified, that coordinate will grow with content unlimited, and layout max-W/H will - * always report the same as pref-W/H. + * maximum is not specified, that coordinate will grow with content up to screen size, and layout + * max-W/H will always report the same as pref-W/H. * * [keyPressDispatcher] is optional and works with the `shortcutKey` parameter of [addPage] to support key bindings with tooltips. */ -//region Fields and initialization +//region Fields @Suppress("MemberVisibilityCanBePrivate", "unused") // All member are part of our API class TabbedPager( - private val minimumWidth: Float = 0f, - private var maximumWidth: Float = Float.MAX_VALUE, - private val minimumHeight: Float = 0f, - private var maximumHeight: Float = Float.MAX_VALUE, + minimumWidth: Float = 0f, + maximumWidth: Float = Float.MAX_VALUE, + minimumHeight: Float = 0f, + maximumHeight: Float = Float.MAX_VALUE, private val headerFontSize: Int = Constants.defaultFontSize, private val headerFontColor: Color = Color.WHITE, private val highlightColor: Color = Color.BLUE, @@ -51,38 +54,8 @@ class TabbedPager( capacity: Int = 4 ) : Table() { - private class PageState( - caption: String, - var content: Actor, - var fixedContent: WidgetGroup? = null, - var disabled: Boolean = false, - val onActivation: ((Int, String) -> Unit)? = null, - val onDeactivation: ((Int, String, Float) -> Unit)? = null, - icon: Actor? = null, - iconSize: Float = 0f, - val shortcutKey: KeyCharAndCode = KeyCharAndCode.UNKNOWN, - pager: TabbedPager - ) { - var scrollX = 0f - var scrollY = 0f - - val button = IconTextButton(caption, icon, pager.headerFontSize, pager.headerFontColor).apply { - if (icon != null) { - if (iconSize != 0f) - iconCell!!.size(iconSize) - iconCell!!.padRight(pager.headerPadding * 0.5f) - } - } - var buttonX = 0f - var buttonW = 0f - } - - private var preferredWidth = minimumWidth - private val growMaxWidth = maximumWidth == Float.MAX_VALUE - private val limitWidth = maximumWidth - private var preferredHeight = minimumHeight - private val growMaxHeight = maximumHeight == Float.MAX_VALUE - private val limitHeight = maximumHeight + private val dimW: DimensionMeasurement + private val dimH: DimensionMeasurement private val pages = ArrayList(capacity) @@ -93,26 +66,207 @@ class TabbedPager( private set private val header = Table(BaseScreen.skin) - private val headerScroll = AutoScrollPane(header) + private val headerScroll = LinkedScrollPane(horizontalOnly = true, header) private var headerHeight = 0f - private val contentScroll = AutoScrollPane(null) - private val fixedContentWrapper = Container() + private val fixedContentScroll = LinkedScrollPane(horizontalOnly = true) + private val fixedContentScrollCell: Cell + private val contentScroll = LinkedScrollPane(horizontalOnly = false, linkTo = fixedContentScroll) private val deferredSecretPages = ArrayDeque(0) private var askPasswordLock = false + //endregion + //region Private Classes + + private class PageState( + caption: String, + var content: Actor, + var fixedContent: Actor?, + var disabled: Boolean, + val onActivation: ((Int, String) -> Unit)?, + val onDeactivation: ((Int, String, Float) -> Unit)?, + icon: Actor?, + iconSize: Float, + val shortcutKey: KeyCharAndCode, + var scrollAlign: Int, + val syncScroll: Boolean, + pager: TabbedPager + ) { + var fixedHeight = 0f + var scrollX = 0f + var scrollY = 0f + + val button = IconTextButton(caption, icon, pager.headerFontSize, pager.headerFontColor).apply { + name = caption // enable finding pages by untranslated caption without needing our own field + if (icon != null) { + if (iconSize != 0f) + iconCell!!.size(iconSize) + iconCell!!.padRight(pager.headerPadding * 0.5f) + } + } + var buttonX = 0f + var buttonW = 0f + + val caption: String + get() = button.name + + override fun toString() = "PageState($caption, key=$shortcutKey, disabled=$disabled, content:${content.javaClass.simpleName}, fixedContent:${fixedContent?.javaClass?.simpleName})" + } + + private data class DimensionMeasurement( + var min: Float, + var pref: Float, + var max: Float, + val limit: Float, + val growMax: Boolean + ) { + constructor(limit: Float) : this(0f, 0f, 0f, limit, true) + companion object { + fun from(min: Float, max: Float, limit: Float): DimensionMeasurement { + if (max == Float.MAX_VALUE) + return DimensionMeasurement(min, min, 0f, limit, true) + val fixedMax = max.coerceAtMost(limit) + return DimensionMeasurement(min, min, fixedMax, fixedMax, false) + } + } + fun measure(newMin: Float, newPref: Float, newMax: Float): Boolean { + var needLayout = false + newMin.coerceAtMost(limit) + .let { if (it > min) { min = it; needLayout = true } } + newPref.coerceAtLeast(min).coerceAtMost(limit) + .let { if (it > pref) { pref = it; needLayout = true } } + if (!growMax) return needLayout + newMax.coerceAtLeast(pref).coerceAtMost(limit) + .let { if (it > max) { max = it; needLayout = true } } + return needLayout + } + fun measureWidth(group: WidgetGroup?): Boolean { + if (group == null) return false + group.packIfNeeded() + return measure(group.minWidth, group.prefWidth, group.maxWidth) + } + fun measureHeight(group: WidgetGroup?): Boolean { + if (group == null) return false + group.packIfNeeded() + return measure(group.minHeight, group.prefHeight, group.maxHeight) + } + fun combine(header: Float, top: DimensionMeasurement, bottom: DimensionMeasurement) { + min = (header + top.min + bottom.min).coerceAtLeast(min).coerceAtMost(limit) + pref = (header + top.pref + bottom.pref).coerceAtLeast(pref).coerceIn(min..limit) + if (growMax) + max = (header + top.max + bottom.max).coerceAtLeast(max).coerceIn(pref..limit) + } + } + + private class LinkedScrollPane( + horizontalOnly: Boolean, + widget: Actor? = null, + linkTo: LinkedScrollPane? = null + ) : AutoScrollPane(widget, BaseScreen.skin) { + val linkedScrolls = mutableSetOf() + var enableSync = true + + init { + if (horizontalOnly) + setScrollingDisabled(false, true) + setOverscroll(false, false) + setScrollbarsOnTop(true) + setupFadeScrollBars(0f, 0f) + + if (linkTo != null) { + linkedScrolls += linkTo + linkTo.linkedScrolls += this + } + } + + private fun sync(update: Boolean = true) { + if (!enableSync) return + for (linkedScroll in linkedScrolls) { + if (linkedScroll.scrollX == this.scrollX) continue + linkedScroll.scrollX = this.scrollX + if (update) linkedScroll.updateVisualScroll() + } + } + + override fun addScrollListener() { + super.addScrollListener() + val oldListener = listeners.removeIndex(listeners.size-1) as InputListener + addListener(object : InputListener() { + override fun scrolled(event: InputEvent?, x: Float, y: Float, amountX: Float, amountY: Float): Boolean { + val toReturn = oldListener.scrolled(event, x, y, amountX, amountY) + sync(false) + return toReturn + } + }) + } + + override fun addCaptureListener() { + super.addCaptureListener() + val oldListener = captureListeners.removeIndex(0) as InputListener + addCaptureListener(object : InputListener() { + override fun touchDown(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int): Boolean { + val toReturn = oldListener.touchDown(event, x, y, pointer, button) + sync() + return toReturn + } + override fun touchDragged(event: InputEvent?, x: Float, y: Float, pointer: Int) { + oldListener.touchDragged(event, x, y, pointer) + sync() + } + override fun touchUp(event: InputEvent?, x: Float, y: Float, pointer: Int, button: Int) { + oldListener.touchUp(event, x, y, pointer, button) + sync() + } + override fun mouseMoved(event: InputEvent?, x: Float, y: Float): Boolean { + // syncing here leads to stutter + return oldListener.mouseMoved(event, x, y) + } + }) + } + + override fun getFlickScrollListener(): ActorGestureListener { + val stdFlickListener = super.getFlickScrollListener() + val newFlickListener = object: ActorGestureListener() { + override fun pan(event: InputEvent?, x: Float, y: Float, deltaX: Float, deltaY: Float) { + stdFlickListener.pan(event, x, y, deltaX, deltaY) + sync() + } + override fun fling(event: InputEvent?, velocityX: Float, velocityY: Float, button: Int) { + stdFlickListener.fling(event, velocityX, velocityY, button) + sync() + } + } + return newFlickListener + } + + override fun act(delta: Float) { + val wasFlinging = isFlinging + super.act(delta) + if (wasFlinging) sync() + } + } + + //endregion + //region Initialization + init { + val screen = (if (UncivGame.isCurrentInitialized()) UncivGame.Current.screen else null) as? BaseScreen + val (screenWidth, screenHeight) = (screen?.stage?.run { width to height }) ?: (Float.MAX_VALUE to Float.MAX_VALUE) + dimW = DimensionMeasurement.from(minimumWidth, maximumWidth, screenWidth) + dimH = DimensionMeasurement.from(minimumHeight, maximumHeight, screenHeight) + background = ImageGetter.getBackground(backgroundColor) + header.defaults().pad(headerPadding, headerPadding * 0.5f) - headerScroll.setOverscroll(false,false) - headerScroll.setScrollingDisabled(false, true) // Measure header height, most likely its final value removePage(addPage("Dummy")) add(headerScroll).growX().minHeight(headerHeight).row() if (separatorColor != Color.CLEAR) addSeparator(separatorColor) - add(fixedContentWrapper).growX().row() + + fixedContentScrollCell = add(fixedContentScroll) + fixedContentScrollCell.growX().row() add(contentScroll).grow().row() } @@ -120,22 +274,22 @@ class TabbedPager( //region Widget interface // The following are part of the Widget interface and serve dynamic sizing - override fun getPrefWidth() = preferredWidth + override fun getPrefWidth() = dimW.pref fun setPrefWidth(width: Float) { - if (width !in minimumWidth..maximumWidth) throw IllegalArgumentException() - preferredWidth = width + if (width !in dimW.min..dimW.max) throw IllegalArgumentException() + dimW.pref = width invalidateHierarchy() } - override fun getPrefHeight() = preferredHeight + headerHeight + override fun getPrefHeight() = dimH.pref fun setPrefHeight(height: Float) { - if (height - headerHeight !in minimumHeight..maximumHeight) throw IllegalArgumentException() - preferredHeight = height - headerHeight + if (height !in dimH.min..dimH.max) throw IllegalArgumentException() + dimH.pref = height invalidateHierarchy() } - override fun getMinWidth() = minimumWidth - override fun getMaxWidth() = maximumWidth - override fun getMinHeight() = headerHeight + minimumHeight - override fun getMaxHeight() = headerHeight + maximumHeight + override fun getMinWidth() = dimW.min + override fun getMaxWidth() = dimW.max + override fun getMinHeight() = dimH.min + override fun getMaxHeight() = dimH.max //endregion //region API @@ -144,53 +298,79 @@ class TabbedPager( fun pageCount() = pages.size /** @return index of a page by its (untranslated) caption, or -1 if no such page exists */ - fun getPageIndex(caption: String) = pages.indexOfLast { it.button.name == caption } + fun getPageIndex(caption: String) = pages.indexOfLast { it.caption == caption } /** Change the selected page by using its index. * @param index Page number or -1 to deselect the current page. + * @param centerButton `true` centers the page's header button, `false` ensures it is visible. * @return `true` if the page was successfully changed. */ - fun selectPage(index: Int): Boolean { + fun selectPage(index: Int, centerButton: Boolean = true): Boolean { if (index !in -1 until pages.size) return false if (activePage == index) return false if (index >= 0 && pages[index].disabled) return false + if (activePage != -1) { - pages[activePage].apply { - onDeactivation?.invoke(activePage, button.name, contentScroll.scrollY) - button.color = Color.WHITE - fixedContentWrapper.actor = null - scrollX = contentScroll.scrollX - scrollY = contentScroll.scrollY - contentScroll.removeActor(content) - } + val page = pages[activePage] + page.onDeactivation?.invoke(activePage, page.caption, contentScroll.scrollY) + page.button.color = Color.WHITE + fixedContentScroll.actor = null + page.scrollX = contentScroll.scrollX + page.scrollY = contentScroll.scrollY + contentScroll.actor = null } + activePage = index + if (index != -1) { - pages[index].apply { - button.color = highlightColor - fixedContentWrapper.actor = fixedContent - contentScroll.actor = content - contentScroll.layout() - if (scrollX < 0f) // was marked to center on first show - scrollX = ((content.width - this@TabbedPager.width) / 2).coerceIn(0f, contentScroll.maxX) - contentScroll.scrollX = scrollX - contentScroll.scrollY = scrollY - contentScroll.updateVisualScroll() - headerScroll.let { - it.scrollX = (buttonX + (buttonW - it.width) / 2).coerceIn(0f, it.maxX) - } - onActivation?.invoke(index, button.name) + val page = pages[index] + page.button.color = highlightColor + + if (page.scrollAlign != 0) { + if (Align.isCenterHorizontal(page.scrollAlign)) + page.scrollX = (page.content.width - this.width) / 2 + else if (Align.isRight(page.scrollAlign)) + page.scrollX = Float.MAX_VALUE // ScrollPane _will_ clamp this + if (Align.isCenterVertical(page.scrollAlign)) + page.scrollY = (page.content.height - this.height) / 2 + else if (Align.isBottom(page.scrollAlign)) + page.scrollY = Float.MAX_VALUE // ScrollPane _will_ clamp this + page.scrollAlign = 0 // once only } + + fixedContentScroll.actor = page.fixedContent + fixedContentScroll.height = page.fixedHeight + fixedContentScrollCell.minHeight(page.fixedHeight) + fixedContentScroll.layout() + fixedContentScroll.scrollX = page.scrollX + fixedContentScroll.updateVisualScroll() + fixedContentScroll.enableSync = page.syncScroll + + contentScroll.actor = page.content + contentScroll.layout() + contentScroll.scrollX = page.scrollX + contentScroll.scrollY = page.scrollY + contentScroll.updateVisualScroll() + contentScroll.enableSync = page.syncScroll + + if (centerButton) + // centering is nice when selectPage is called programmatically + headerScroll.scrollX = page.buttonX + (page.buttonW - headerScroll.width) / 2 + else + // when coming from a tap/click, can we at least ensure no part of it is outside the visible area + headerScroll.run { scrollX = scrollX.coerceIn((page.buttonX + page.buttonW - scrollWidth)..page.buttonX) } + page.onActivation?.invoke(index, page.caption) } return true } /** Change the selected page by using its caption. * @param caption Caption of the page to select. A nonexistent name will deselect the current page. + * @param centerButton `true` centers the page's header button, `false` ensures it is visible. * @return `true` if the page was successfully changed. */ - fun selectPage(caption: String) = selectPage(getPageIndex(caption)) - private fun selectPage(page: PageState) = selectPage(getPageIndex(page)) + fun selectPage(caption: String, centerButton: Boolean = true) = selectPage(getPageIndex(caption), centerButton) + private fun selectPage(page: PageState) = selectPage(getPageIndex(page), centerButton = false) /** Change the disabled property of a page by its index. * @return previous value or `false` if index invalid. @@ -243,34 +423,41 @@ class TabbedPager( if (index !in 0 until pages.size) return val isActive = index == activePage if (isActive) selectPage(-1) - pages[index].content = content + pages[index].let { + it.content = content + measureContent(it) + } if (isActive) selectPage(index) } /** Replace a page's [content] and [fixedContent] by its [index]. */ - fun replacePage(index: Int, content: Actor, fixedContent: WidgetGroup?) { + fun replacePage(index: Int, content: Actor, fixedContent: Actor?) { if (index !in 0 until pages.size) return val isActive = index == activePage if (isActive) selectPage(-1) - pages[index].content = content - pages[index].fixedContent = fixedContent + pages[index].let { + it.content = content + it.fixedContent = fixedContent + measureContent(it) + } if (isActive) selectPage(index) } /** Replace a page's [content] by its [caption]. */ fun replacePage(caption: String, content: Actor) = replacePage(getPageIndex(caption), content) /** Replace a page's [content] and [fixedContent] by its [caption]. */ - fun replacePage(caption: String, content: Actor, fixedContent: WidgetGroup?) = replacePage(getPageIndex(caption), content, fixedContent) + fun replacePage(caption: String, content: Actor, fixedContent: Actor?) = replacePage(getPageIndex(caption), content, fixedContent) /** Add a page! * @param caption Text to be shown on the header button (automatically translated), can later be used to reference the page in other calls. - * @param content Actor to show when this page is selected. - * @param icon Actor, typically an [Image], to show before the caption. + * @param content [Actor] to show in the lower area when this page is selected. + * @param icon Actor, typically an [Image], to show before the caption on the header button. * @param iconSize Size for [icon] - if not zero, the icon is wrapped to allow a [setSize] even on [Image] which ignores size. - * @param insertBefore -1 to add at the end or index of existing page to insert this before + * @param insertBefore -1 to add at the end, or index of existing page to insert this before it. * @param secret Marks page as 'secret'. A password is asked once per [TabbedPager] and if it does not match the has passed in the constructor the page and all subsequent secret pages are dropped. * @param disabled Initial disabled state. Disabled pages cannot be selected even with [selectPage], their button is dimmed. * @param shortcutKey Optional keyboard key to associate - goes to the [KeyPressDispatcher] passed in the constructor. - * @param fixedContent Optional second content [WidgetGroup], will be placed outside the tab's [ScrollPane] between header and [content]. + * @param syncScroll If on, the ScrollPanes for [content] and [fixedContent] will synchronize horizontally. + * @param fixedContent Optional second content [Actor], will be placed outside the tab's main [ScrollPane] between header and [content]. Scrolls horizontally only. * @param onDeactivation _Optional_ callback called when this page is hidden. Lambda arguments are page index and caption, and scrollY of the tab's [ScrollPane]. * @param onActivation _Optional_ callback called when this page is shown (per actual change to this page, not per header click). Lambda arguments are page index and caption. * @return The new page's index or -1 if it could not be immediately added (secret). @@ -284,7 +471,9 @@ class TabbedPager( secret: Boolean = false, disabled: Boolean = false, shortcutKey: KeyCharAndCode = KeyCharAndCode.UNKNOWN, - fixedContent: WidgetGroup? = null, + scrollAlign: Int = Align.top, + syncScroll: Boolean = true, + fixedContent: Actor? = null, onDeactivation: ((Int, String, Float) -> Unit)? = null, onActivation: ((Int, String) -> Unit)? = null ): Int { @@ -299,10 +488,11 @@ class TabbedPager( icon = icon, iconSize = iconSize, shortcutKey = shortcutKey, + scrollAlign = scrollAlign, + syncScroll = syncScroll, pager = this ) page.button.apply { - name = caption // enable finding pages by untranslated caption without needing our own field isEnabled = !disabled onClick { selectPage(page) @@ -358,20 +548,15 @@ class TabbedPager( private fun getPageIndex(page: PageState) = pages.indexOf(page) - private fun measureContent(group: WidgetGroup) { - group.packIfNeeded() - var needLayout = false - val contentWidth = min(group.width, limitWidth) - if (contentWidth > preferredWidth) { - preferredWidth = contentWidth - needLayout = true - } - val contentHeight = min(group.height, limitHeight) - if (contentHeight > preferredHeight) { - preferredHeight = contentHeight - needLayout = true - } - if (needLayout && activePage >= 0) invalidateHierarchy() + private fun measureContent(page: PageState) { + val dimFixedH = DimensionMeasurement(dimH.limit) + val dimContentH = DimensionMeasurement(dimH.limit) + dimW.measureWidth(page.fixedContent as? WidgetGroup) + dimFixedH.measureHeight(page.fixedContent as? WidgetGroup) + page.fixedHeight = dimFixedH.min + dimW.measureWidth(page.content as? WidgetGroup) + dimContentH.measureHeight(page.content as? WidgetGroup) + dimH.combine(headerHeight, dimFixedH, dimContentH) } private fun addAndShowPage(page: PageState, insertBefore: Int): Int { @@ -393,14 +578,7 @@ class TabbedPager( for (i in newIndex + 1 until pages.size) pages[i].buttonX += page.buttonW - // Content Sizing - if (page.fixedContent != null) measureContent(page.fixedContent!!) - (page.content as? WidgetGroup)?.let { - measureContent(it) - page.scrollX = -1f // mark to center later when all pages are measured - } - if (growMaxWidth) maximumWidth = minimumWidth - if (growMaxHeight) maximumHeight = minimumHeight + measureContent(page) keyPressDispatcher?.set(page.shortcutKey) { selectPage(newIndex) } diff --git a/core/src/com/unciv/ui/utils/UncivTooltip.kt b/core/src/com/unciv/ui/utils/UncivTooltip.kt index bfb6148db9..b97b1f664f 100644 --- a/core/src/com/unciv/ui/utils/UncivTooltip.kt +++ b/core/src/com/unciv/ui/utils/UncivTooltip.kt @@ -24,7 +24,7 @@ import com.unciv.models.translations.tr * - because Gdx auto layout reports wrong dimensions on scaled actors. */ class UncivTooltip ( - val target: Group, + val target: Actor, val content: T, val targetAlign: Int = Align.topRight, val tipAlign: Int = Align.topRight, @@ -160,7 +160,7 @@ class UncivTooltip ( * @param always override requirement: presence of physical keyboard * @param tipAlign Point on the Tooltip to align with the top right of the [target] */ - fun Group.addTooltip(text: String, size: Float = 26f, always: Boolean = false, tipAlign: Int = Align.top) { + fun Actor.addTooltip(text: String, size: Float = 26f, always: Boolean = false, tipAlign: Int = Align.top) { if (!(always || KeyPressDispatcher.keyboardAvailable) || text.isEmpty()) return val label = text.toLabel(ImageGetter.getBlue(), 38) @@ -199,7 +199,7 @@ class UncivTooltip ( * @param size _Vertical_ size of the entire Tooltip including background * @param always override requirement: presence of physical keyboard */ - fun Group.addTooltip(char: Char, size: Float = 26f, always: Boolean = false) { + fun Actor.addTooltip(char: Char, size: Float = 26f, always: Boolean = false) { addTooltip((if (char in "Ii") 'i' else char.uppercaseChar()).toString(), size, always) } @@ -211,7 +211,7 @@ class UncivTooltip ( * @param size _Vertical_ size of the entire Tooltip including background * @param always override requirement: presence of physical keyboard */ - fun Group.addTooltip(key: KeyCharAndCode, size: Float = 26f, always: Boolean = false) { + fun Actor.addTooltip(key: KeyCharAndCode, size: Float = 26f, always: Boolean = false) { if (key != KeyCharAndCode.UNKNOWN) addTooltip(key.toString().tr(), size, always) }