diff --git a/build.gradle.kts b/build.gradle.kts index d42fc07db..362628229 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -256,6 +256,11 @@ minecraft.runs.all { } repositories { + // If you have mod jar dependencies in ./libs, you can declare them as a repository like so: + flatDir { + dir("libs") + } + maven { url = uri("https://maven.dbotthepony.ru") } @@ -285,11 +290,6 @@ repositories { name = "Kotlin for Forge" url = uri("https://thedarkcolour.github.io/KotlinForForge/") } - - // If you have mod jar dependencies in ./libs, you can declare them as a repository like so: - flatDir { - dir("libs") - } } fun org.gradle.jvm.tasks.Jar.attachManifest() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt index 6980f74fd..fc1cde0af 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/ExoSuitInventoryScreen.kt @@ -171,15 +171,24 @@ class ExoSuitInventoryScreen(menu: ExoSuitInventoryMenu) : MatteryScreen 4) + ScrollbarBackgroundPanel.padded(this, frame, x, + width = curiosWidth, + height = curiosHeight, alwaysShowScrollbar = true) + else + BackgroundPanel.padded(this, frame, x, + width = curiosWidth, + height = curiosHeight) x -= curiosRect.width curiosRect.x = x for ((slot, cosmetic) in menu.curiosSlots) { - val row = EditablePanel(this, curiosRect, height = AbstractSlotPanel.SIZE) + val row = EditablePanel(this, if (curiosRect is ScrollbarBackgroundPanel) curiosRect.canvas else curiosRect, height = AbstractSlotPanel.SIZE) row.dock = Dock.TOP SlotPanel(this, row, slot).dock = Dock.RIGHT diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AnalogScrollBarPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AnalogScrollBarPanel.kt new file mode 100644 index 000000000..ddb8648bf --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/AnalogScrollBarPanel.kt @@ -0,0 +1,179 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import com.mojang.blaze3d.platform.InputConstants +import com.mojang.blaze3d.vertex.PoseStack +import net.minecraft.client.gui.screens.Screen +import ru.dbotthepony.mc.otm.core.linearInterpolation +import kotlin.math.roundToInt + +open class AnalogScrollBarPanel( + screen: S, + parent: EditablePanel<*>?, + val maxScroll: (panel: AnalogScrollBarPanel<*>) -> Float, + val scrollCallback: (panel: AnalogScrollBarPanel<*>, oldScroll: Float, newScroll: Float) -> Unit = { _, _, _ -> }, + val smoothScrollCallback: (panel: AnalogScrollBarPanel<*>, oldScroll: Float, newScroll: Float) -> Unit = { _, _, _ -> }, + x: Float = 0f, + y: Float = 0f, + height: Float = 20f, + open val allowSmoothScroll: Boolean = true, + open var scrollStep: Float = AbstractSlotPanel.SIZE, + isSlim: Boolean = false +) : EditablePanel(screen, parent, x, y, width = if (isSlim) ScrollBarConstants.SLIM_WIDTH else ScrollBarConstants.WIDTH, height = height) { + inner class Button : EditablePanel(screen, this@AnalogScrollBarPanel, 1f, 1f, this@AnalogScrollBarPanel.width - 2f, 15f) { + var isScrolling = false + private set + + override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + if (this@AnalogScrollBarPanel.width == ScrollBarConstants.SLIM_WIDTH) { + if (isScrolling) { + ScrollBarConstants.scrollSlimBarButtonPress.render(stack, width = width, height = height) + } else if (maxScroll.invoke(this@AnalogScrollBarPanel) <= 0) { + ScrollBarConstants.scrollSlimBarButtonDisabled.render(stack, width = width, height = height) + } else if (isHovered) { + ScrollBarConstants.scrollSlimBarButtonHover.render(stack, width = width, height = height) + } else { + ScrollBarConstants.scrollSlimBarButton.render(stack, width = width, height = height) + } + } else { + if (isScrolling) { + ScrollBarConstants.scrollBarButtonPress.render(stack, width = width, height = height) + } else if (maxScroll.invoke(this@AnalogScrollBarPanel) <= 0) { + ScrollBarConstants.scrollBarButtonDisabled.render(stack, width = width, height = height) + } else if (isHovered) { + ScrollBarConstants.scrollBarButtonHover.render(stack, width = width, height = height) + } else { + ScrollBarConstants.scrollBarButton.render(stack, width = width, height = height) + } + } + } + + private var rememberScroll = 0f + private var rememberY = 0.0 + + override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { + if (maxScroll.invoke(this@AnalogScrollBarPanel) <= 0f) { + return true + } + + if (button == InputConstants.MOUSE_BUTTON_LEFT && tryToGrabMouseInput()) { + isScrolling = true + rememberScroll = scroll + rememberY = y + } + + return true + } + + override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean { + if (isScrolling && button == InputConstants.MOUSE_BUTTON_LEFT) { + isScrolling = false + grabMouseInput = false + return true + } + + return false + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + return this@AnalogScrollBarPanel.mouseScrolledInner(x, y, scroll) + } + + override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean { + if (isScrolling) { + val pixelsPerRow = (this@AnalogScrollBarPanel.height - height) / maxScroll.invoke(this@AnalogScrollBarPanel) + val diff = y - rememberY + + this@AnalogScrollBarPanel.scroll = rememberScroll + (diff / pixelsPerRow).roundToInt() + return true + } + + return false + } + } + + val scrollButton = Button() + + private var lastRender = System.nanoTime() + + override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { + if (width == ScrollBarConstants.SLIM_WIDTH) { + ScrollBarConstants.scrollSlimBarBody.render(stack, y = 2f, height = height - 4f) + ScrollBarConstants.scrollSlimBarTop.render(stack) + ScrollBarConstants.scrollSlimBarBottom.render(stack, y = height - 2f) + } else { + ScrollBarConstants.scrollBarBody.render(stack, y = 2f, height = height - 4f) + ScrollBarConstants.scrollBarTop.render(stack) + ScrollBarConstants.scrollBarBottom.render(stack, y = height - 2f) + } + + val time = System.nanoTime() + val diff = time - lastRender + lastRender = time + + if (scroll != smoothScroll) { + smoothScroll = linearInterpolation(diff / 50_000_000.0, smoothScroll.toDouble(), scroll.toDouble()).toFloat() + } + } + + public override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + this.scroll -= scroll.toFloat() * scrollStep + return true + } + + var scroll = 0f + set(value) { + val newValue = value.coerceAtLeast(0f).coerceAtMost(maxScroll.invoke(this)) + + if (newValue != field) { + val old = field + field = newValue + scrollChanged(old, newValue) + + if (!allowSmoothScroll) { + smoothScroll = newValue + } + } + } + + var smoothScroll = 0f + set(value) { + val newValue = value.coerceAtLeast(0f).coerceAtMost(maxScroll.invoke(this)) + + if (newValue != field) { + val old = field + field = newValue + smoothScrollChanged(old, newValue) + } + } + + fun hardSetScroll(scroll: Float) { + this.scroll = scroll + this.smoothScroll = scroll + } + + open fun updateScrollButtonPosition() { + val maxScroll = this.maxScroll.invoke(this) + + if (maxScroll <= 0f) { + scrollButton.y = 1f + return + } + + val availableHeight = height - scrollButton.height - 2f + scrollButton.y = 1f + availableHeight * (scroll / maxScroll) + } + + open fun scrollChanged(oldScroll: Float, newScroll: Float) { + scrollCallback.invoke(this, oldScroll, newScroll) + updateScrollButtonPosition() + } + + open fun smoothScrollChanged(oldScroll: Float, newScroll: Float) { + smoothScrollCallback.invoke(this, oldScroll, newScroll) + } + + override fun performLayout() { + super.performLayout() + updateScrollButtonPosition() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DiscreteScrollBarPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DiscreteScrollBarPanel.kt index 3ee5a7e42..fe6b77d2d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DiscreteScrollBarPanel.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/DiscreteScrollBarPanel.kt @@ -5,32 +5,45 @@ import com.mojang.blaze3d.vertex.PoseStack import net.minecraft.client.gui.screens.Screen import kotlin.math.roundToInt -open class DiscreteScrollBarPanel @JvmOverloads constructor( +open class DiscreteScrollBarPanel( screen: S, parent: EditablePanel<*>?, - val maxScroll: (panel: DiscreteScrollBarPanel) -> Int, - val scrollCallback: (panel: DiscreteScrollBarPanel, oldScroll: Int, newScroll: Int) -> Unit, + val maxScroll: (panel: DiscreteScrollBarPanel<*>) -> Int, + val scrollCallback: (panel: DiscreteScrollBarPanel<*>, oldScroll: Int, newScroll: Int) -> Unit, x: Float = 0f, y: Float = 0f, - height: Float = 20f -) : EditablePanel(screen, parent, x, y, width = ScrollBarConstants.WIDTH, height = height) { - open inner class Button : EditablePanel(screen, this@DiscreteScrollBarPanel, 1f, 1f, 12f, 15f) { + height: Float = 20f, + isSlim: Boolean = false +) : EditablePanel(screen, parent, x, y, width = if (isSlim) ScrollBarConstants.SLIM_WIDTH else ScrollBarConstants.WIDTH, height = height) { + inner class Button : EditablePanel(screen, this@DiscreteScrollBarPanel, 1f, 1f, this@DiscreteScrollBarPanel.width - 12f, 15f) { var isScrolling = false - protected set + private set override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - if (isScrolling) { - ScrollBarConstants.scrollBarButtonPress.render(stack, width = width, height = height) - } else if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) { - ScrollBarConstants.scrollBarButtonDisabled.render(stack, width = width, height = height) - } else if (isHovered) { - ScrollBarConstants.scrollBarButtonHover.render(stack, width = width, height = height) + if (this@DiscreteScrollBarPanel.width == ScrollBarConstants.SLIM_WIDTH) { + if (isScrolling) { + ScrollBarConstants.scrollSlimBarButtonPress.render(stack, width = width, height = height) + } else if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) { + ScrollBarConstants.scrollSlimBarButtonDisabled.render(stack, width = width, height = height) + } else if (isHovered) { + ScrollBarConstants.scrollSlimBarButtonHover.render(stack, width = width, height = height) + } else { + ScrollBarConstants.scrollSlimBarButton.render(stack, width = width, height = height) + } } else { - ScrollBarConstants.scrollBarButton.render(stack, width = width, height = height) + if (isScrolling) { + ScrollBarConstants.scrollBarButtonPress.render(stack, width = width, height = height) + } else if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) { + ScrollBarConstants.scrollBarButtonDisabled.render(stack, width = width, height = height) + } else if (isHovered) { + ScrollBarConstants.scrollBarButtonHover.render(stack, width = width, height = height) + } else { + ScrollBarConstants.scrollBarButton.render(stack, width = width, height = height) + } } } - protected var rememberScroll = 0 + private var rememberScroll = 0 private var rememberY = 0.0 override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean { @@ -77,9 +90,15 @@ open class DiscreteScrollBarPanel @JvmOverloads constructor( val scrollButton = Button() override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) { - ScrollBarConstants.scrollBarBody.render(stack, y = 2f, height = height - 4) - ScrollBarConstants.scrollBarTop.render(stack) - ScrollBarConstants.scrollBarBottom.render(stack, y = height - 2) + if (width == ScrollBarConstants.SLIM_WIDTH) { + ScrollBarConstants.scrollSlimBarBody.render(stack, y = 2f, height = height - 4f) + ScrollBarConstants.scrollSlimBarTop.render(stack) + ScrollBarConstants.scrollSlimBarBottom.render(stack, y = height - 2f) + } else { + ScrollBarConstants.scrollBarBody.render(stack, y = 2f, height = height - 4f) + ScrollBarConstants.scrollBarTop.render(stack) + ScrollBarConstants.scrollBarBottom.render(stack, y = height - 2f) + } } public override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollBarConstants.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollBarConstants.kt index f0db8929f..5e4b1161e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollBarConstants.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollBarConstants.kt @@ -5,15 +5,25 @@ import ru.dbotthepony.mc.otm.client.render.element object ScrollBarConstants { const val WIDTH = 14f - const val TEXTURE_WIDTH = 14f + const val SLIM_WIDTH = 8f + const val TEXTURE_WIDTH = 22f const val TEXTURE_HEIGHT = 68f val scrollBarTop = WidgetLocation.SCROLL.element(0f, 45f, 14f, 2f, TEXTURE_WIDTH, TEXTURE_HEIGHT) val scrollBarBody = WidgetLocation.SCROLL.element(0f, 46f, 14f, 6f, TEXTURE_WIDTH, TEXTURE_HEIGHT) val scrollBarBottom = WidgetLocation.SCROLL.element(0f, 51f, 14f, 2f, TEXTURE_WIDTH, TEXTURE_HEIGHT) - val scrollBarButton = WidgetLocation.SCROLL.element(0f, 0f, 12f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + val scrollBarButton = WidgetLocation.SCROLL.element(0f, 0f, 12f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) val scrollBarButtonHover = WidgetLocation.SCROLL.element(0f, 15f, 12f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) val scrollBarButtonPress = WidgetLocation.SCROLL.element(0f, 30f, 12f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) val scrollBarButtonDisabled = WidgetLocation.SCROLL.element(0f, 53f, 12f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + + val scrollSlimBarTop = WidgetLocation.SCROLL.element(14f, 45f, 8f, 2f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + val scrollSlimBarBody = WidgetLocation.SCROLL.element(14f, 46f, 8f, 6f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + val scrollSlimBarBottom = WidgetLocation.SCROLL.element(14f, 51f, 8f, 2f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + + val scrollSlimBarButton = WidgetLocation.SCROLL.element(14f, 0f, 6f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + val scrollSlimBarButtonHover = WidgetLocation.SCROLL.element(14f, 15f, 6f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + val scrollSlimBarButtonPress = WidgetLocation.SCROLL.element(14f, 30f, 6f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) + val scrollSlimBarButtonDisabled = WidgetLocation.SCROLL.element(14f, 53f, 6f, 15f, TEXTURE_WIDTH, TEXTURE_HEIGHT) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollbarBackgroundPanel.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollbarBackgroundPanel.kt new file mode 100644 index 000000000..8a6be9bb6 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/screen/panels/ScrollbarBackgroundPanel.kt @@ -0,0 +1,166 @@ +package ru.dbotthepony.mc.otm.client.screen.panels + +import net.minecraft.client.gui.screens.Screen + +open class ScrollbarBackgroundPanel( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 30f, + height: Float = 30f, + alwaysShowScrollbar: Boolean = false, + autoResizeForScrollbar: Boolean = true, + autoMoveForScrollbar: Boolean = true, + scrollStep: Float = AbstractSlotPanel.SIZE, +) : BackgroundPanel(screen, parent, x, y, width, height) { + /** + * Whenever is to hide scrollbar when there is nothing to scroll + */ + var alwaysShowScrollbar = alwaysShowScrollbar + set(value) { + if (field != value) { + field = value + determineScrollbarVisible() + } + } + + private var lastResizedForScrollbar: Boolean = false + private var lastMovedForScrollbar: Boolean = false + + /** + * Whenever is to auto resize width when scrollbar has to be shown + * + * Has no effect if [alwaysShowScrollbar] is true + */ + var autoResizeForScrollbar = autoResizeForScrollbar + set(value) { + if (field != value) { + field = value + determineScrollbarVisible() + } + } + + /** + * Whenever is to auto move panel along X axis when scrollbar has to be shown (given no [dock] is [Dock.NONE]) + * + * Has no effect if [alwaysShowScrollbar] is true + */ + var autoMoveForScrollbar = autoMoveForScrollbar + set(value) { + if (field != value) { + field = value + determineScrollbarVisible() + } + } + + private fun getMaxScroll(it: AnalogScrollBarPanel<*>): Float { + return (canvas.childrenRectHeight - canvas.height).coerceAtLeast(0f) + } + + private fun onScrollUpdate(it: AnalogScrollBarPanel<*>, old: Float, new: Float) { + this.canvas.yOffset = -new + } + + val scrollbar = AnalogScrollBarPanel(screen, this, this::getMaxScroll, smoothScrollCallback = this::onScrollUpdate, isSlim = true, scrollStep = scrollStep) + + fun determineScrollbarVisible() { + if (alwaysShowScrollbar) { + scrollbar.visible = true + + if (lastResizedForScrollbar) { + width -= scrollbar.width + lastResizedForScrollbar = false + } + + if (lastMovedForScrollbar) { + x += scrollbar.width + lastMovedForScrollbar = false + } + } else { + if (canvas.childrenRectHeight > (height - dockPadding.top - dockPadding.bottom)) { + if (!scrollbar.visible) { + scrollbar.visible = true + + if (autoResizeForScrollbar) { + width += scrollbar.width + lastResizedForScrollbar = true + } + + if (autoMoveForScrollbar && dock == Dock.NONE) { + x -= scrollbar.width + lastMovedForScrollbar = true + } + } + } else { + if (scrollbar.visible) { + scrollbar.visible = false + + if (lastResizedForScrollbar) { + width -= scrollbar.width + lastResizedForScrollbar = false + } + + if (lastMovedForScrollbar) { + x += scrollbar.width + lastMovedForScrollbar = false + } + } + } + } + } + + val canvas = object : EditablePanel(screen, this@ScrollbarBackgroundPanel) { + init { + scissor = true + } + + override fun performLayout() { + super.performLayout() + determineScrollbarVisible() + } + + override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean { + scrollbar.mouseScrolledInner(x, y, scroll) + return true + } + } + + fun addPanel(panel: EditablePanel<*>) { + panel.parent = canvas + } + + init { + scrollbar.dock = Dock.RIGHT + canvas.dock = Dock.FILL + scrollbar.visible = alwaysShowScrollbar + } + + companion object { + fun padded( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 30f, + height: Float = 30f, + alwaysShowScrollbar: Boolean = false, + autoResizeForScrollbar: Boolean = true, + autoMoveForScrollbar: Boolean = true, + scrollStep: Float = AbstractSlotPanel.SIZE, + ) = ScrollbarBackgroundPanel(screen, parent, x, y, width + 6f + (if (alwaysShowScrollbar) ScrollBarConstants.SLIM_WIDTH else 0f), height + 6f, alwaysShowScrollbar, autoResizeForScrollbar, autoMoveForScrollbar, scrollStep) + + fun paddedCenter( + screen: S, + parent: EditablePanel<*>?, + x: Float = 0f, + y: Float = 0f, + width: Float = 30f, + height: Float = 30f, + alwaysShowScrollbar: Boolean = false, + autoResizeForScrollbar: Boolean = true, + autoMoveForScrollbar: Boolean = true, + scrollStep: Float = AbstractSlotPanel.SIZE, + ) = ScrollbarBackgroundPanel(screen, parent, x - 3f, y - 3f - (if (alwaysShowScrollbar) ScrollBarConstants.SLIM_WIDTH / 2f else 0f), width + 6f + (if (alwaysShowScrollbar) ScrollBarConstants.SLIM_WIDTH else 0f), height + 6f, alwaysShowScrollbar, autoResizeForScrollbar, autoMoveForScrollbar, scrollStep) + } +} diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.png b/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.png index 6c42618a7..a5c44ed98 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.png and b/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.png differ diff --git a/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.xcf b/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.xcf index 3a296f5d4..756794938 100644 Binary files a/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.xcf and b/src/main/resources/assets/overdrive_that_matters/textures/gui/scroll.xcf differ