package ocelot.desktop.ui.widget import ocelot.desktop.ColorScheme import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D} import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.UiHandler import ocelot.desktop.ui.event.handlers.HoverHandler import ocelot.desktop.ui.event.sources.KeyEvents import ocelot.desktop.ui.event.{MouseEvent, ScrollEvent} import ocelot.desktop.util.Logging import org.lwjgl.input.Keyboard class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandler { inner.size = Size2D.Zero children +:= inner var scrollToEnd = false private var xOffset = 0f private var yOffset = 0f private var xAcceleration = 0f private var yAcceleration = 0f private var dragging = 0 private var mouseOldPos = Vector2D(0, 0) private var vAnim = 0f private var vAnimDir = -1 private var hAnim = 0f private var hAnimDir = -1 def offset: Vector2D = Vector2D(xOffset, yOffset) override def receiveScrollEvents = true override def receiveMouseEvents = true eventHandlers += { case ScrollEvent(offset) => if (bounds.contains(UiHandler.mousePosition)) { if (KeyEvents.isShiftDown) { xAcceleration -= offset * 385f } else { yAcceleration -= offset * 385f scrollToEnd = false } } case event: MouseEvent if event.state == MouseEvent.State.Press => val pos = UiHandler.mousePosition mouseOldPos = pos dragging = if (vThumbBounds.contains(pos)) 1 else if (hThumbBounds.contains(pos)) 2 else 0 if (dragging == 1) { scrollToEnd = false } case event: MouseEvent if event.state == MouseEvent.State.Release => dragging = 0 } override def minimumSize: Size2D = Size2D.Zero override def size_=(value: Size2D): Unit = { super.size_=(value) clampOffsets() } override def shouldClip = true override def draw(g: Graphics): Unit = { if (xOffset.abs > 0 || yOffset.abs > 0) { inner.position = position - Vector2D(xOffset, yOffset) } inner.draw(g) if (vThumbVisible) drawVThumb(g) if (hThumbVisible) drawHThumb(g) } override def update(): Unit = { super.update() recalculateBounds() xAcceleration *= 1f - 5f * UiHandler.dt xAcceleration = xAcceleration.min(1540f).max(-1540f) xOffset += xAcceleration * UiHandler.dt if (scrollToEnd) { yOffset = maxYOffset yAcceleration = 0 if (dragging == 1) { dragging = 0 } } else { yAcceleration *= 1f - 5f * UiHandler.dt yAcceleration = yAcceleration.min(1540f).max(-1540f) yOffset += yAcceleration * UiHandler.dt } clampOffsets() val mousePos = UiHandler.mousePosition vAnimDir = if (isHovered && vThumbHoverArea.contains(mousePos) || dragging == 1) 1 else -1 hAnimDir = if (isHovered && hThumbHoverArea.contains(mousePos) || dragging == 2) 1 else -1 vAnim = math.max(0f, math.min(1f, vAnim + UiHandler.dt / 0.2f * vAnimDir)) hAnim = math.max(0f, math.min(1f, hAnim + UiHandler.dt / 0.2f * hAnimDir)) if (dragging == 0) return if (dragging == 1) { val dy = (mousePos - mouseOldPos).y yOffset += dy / vThumbCoeff } else if (dragging == 2) { val dx = (mousePos - mouseOldPos).x xOffset += dx / hThumbCoeff } clampOffsets() mouseOldPos = mousePos } private def drawVThumb(g: Graphics): Unit = { val b = vThumbBounds g.rect(position.x + size.width - 11, position.y, 10, size.height, ColorScheme("Scrollbar").mapA(_ * vAnim)) g.rect(b.x + 3, b.y, b.w - 6, b.h, ColorScheme("ScrollbarThumb").withAlpha(vAnim * 0.5f + 0.4f)) } private def drawHThumb(g: Graphics): Unit = { val b = hThumbBounds g.rect(position.x, position.y + size.height - 11, size.width - 12, 10, ColorScheme("Scrollbar").mapA(_ * hAnim)) g.rect(b.x, b.y + 3, b.w, b.h - 6, ColorScheme("ScrollbarThumb").withAlpha(hAnim * 0.5f + 0.4f)) } private def maxXOffset: Float = inner.size.width - size.width private def maxYOffset: Float = inner.size.height - size.height private def clampOffsets(): Unit = { xOffset = xOffset.min(maxXOffset).max(0f) yOffset = yOffset.min(maxYOffset).max(0f) } private def vThumbHoverArea: Rect2D = { Rect2D(position.x + size.width - 12, position.y, 12, size.height) } private def vThumbBounds: Rect2D = { val x = position.x + size.width - 12 val y = position.y + yOffset * vThumbCoeff Rect2D(x, y + 2, 12, vThumbCoeff * size.height) } private def hThumbHoverArea: Rect2D = { Rect2D(position.x, position.y + size.height - 12, size.width, 12) } private def hThumbBounds: Rect2D = { val x = position.x + xOffset * hThumbCoeff val y = position.y + size.height - 12 Rect2D(x + 2, y, hThumbCoeff * size.width, 12) } protected def vThumbVisible: Boolean = inner.size.height > size.height protected def hThumbVisible: Boolean = inner.size.width > size.width private def vThumbCoeff: Float = (size.height - 4) / inner.size.height private def hThumbCoeff: Float = (size.width - 14) / inner.size.width }