diff --git a/src/main/scala/ocelot/desktop/geometry/FloatUtils.scala b/src/main/scala/ocelot/desktop/geometry/FloatUtils.scala index a064b1f..724503b 100644 --- a/src/main/scala/ocelot/desktop/geometry/FloatUtils.scala +++ b/src/main/scala/ocelot/desktop/geometry/FloatUtils.scala @@ -4,6 +4,6 @@ object FloatUtils { implicit class ExtendedFloat(val v: Float) extends AnyVal { def lerp(that: Float, alpha: Float): Float = v * (1 - alpha) + that * alpha - def clamp(min: Float = 0f, max: Float = 1f): Float = v.min(max).max(min) + def clamped(min: Float = 0f, max: Float = 1f): Float = v.min(max).max(min) } } diff --git a/src/main/scala/ocelot/desktop/geometry/Vector3D.scala b/src/main/scala/ocelot/desktop/geometry/Vector3D.scala index 22cd0cf..d0cb9de 100644 --- a/src/main/scala/ocelot/desktop/geometry/Vector3D.scala +++ b/src/main/scala/ocelot/desktop/geometry/Vector3D.scala @@ -45,7 +45,7 @@ case class Vector3D(x: Float, y: Float, z: Float) { ) def angle(that: Vector3D): Float = { - math.acos((dot(that) / length / that.length).clamp(-1, 1)).toFloat + math.acos((dot(that) / length / that.length).clamped(-1, 1)).toFloat } def lerp(that: Vector3D, alpha: Float): Vector3D = { diff --git a/src/main/scala/ocelot/desktop/node/OcelotLogParticleNode.scala b/src/main/scala/ocelot/desktop/node/OcelotLogParticleNode.scala index 765850e..4c91efa 100644 --- a/src/main/scala/ocelot/desktop/node/OcelotLogParticleNode.scala +++ b/src/main/scala/ocelot/desktop/node/OcelotLogParticleNode.scala @@ -39,9 +39,9 @@ trait OcelotLogParticleNode extends Node { private class LogParticle extends Particle(time = -LogParticleGrow, speed = LogParticleMoveSpeed, origin = Some(this)) { private val angle: Float = Random.between(0f, 2 * math.Pi.toFloat * LogParticleMaxAngle) override def draw(g: Graphics): Unit = { - val size = (1 + time / LogParticleGrow).clamp() * LogParticleSize - val offset = time.clamp() * LogParticleMoveDistance - val alpha = 1 - time.clamp() + val size = (1 + time / LogParticleGrow).clamped() * LogParticleSize + val offset = time.clamped() * LogParticleMoveDistance + val alpha = 1 - time.clamped() val r1 = (bounds.w max bounds.h) / math.sqrt(2) + offset + LogParticlePadding val r2 = r1 + size diff --git a/src/main/scala/ocelot/desktop/node/nodes/OcelotBlockNode.scala b/src/main/scala/ocelot/desktop/node/nodes/OcelotBlockNode.scala index e090d51..5d9fdf9 100644 --- a/src/main/scala/ocelot/desktop/node/nodes/OcelotBlockNode.scala +++ b/src/main/scala/ocelot/desktop/node/nodes/OcelotBlockNode.scala @@ -64,7 +64,7 @@ class OcelotBlockNode(val ocelot: OcelotBlock) } private def drawActivity(g: Graphics, icon: IconSource, lastActivity: Long, currentTime: Long): Unit = { - val alpha = (1 - (currentTime - lastActivity) / ActivityFadeOutMs).clamp() + val alpha = (1 - (currentTime - lastActivity) / ActivityFadeOutMs).clamped() if (alpha > 0) { g.sprite( diff --git a/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala b/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala index 7f0422b..6628931 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/ScrollView.scala @@ -92,7 +92,7 @@ class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandle private def nextOffsetVelocity(x: Float, v: Float, dt: Float): (Float, Float) = { var x_ = x var v_ = v - v_ = v_.clamp(-MaxScrollVelocity, MaxScrollVelocity) + v_ = v_.clamped(-MaxScrollVelocity, MaxScrollVelocity) x_ += v_ * dt // exponential decay. v_ *= math.exp(-DecayFactor * UiHandler.dt).toFloat @@ -141,8 +141,8 @@ class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandle val vAnimDir = if (isHovered && vThumbHoverArea.contains(mousePos) || dragState.isVertical) 1 else -1 val hAnimDir = if (isHovered && hThumbHoverArea.contains(mousePos) || dragState.isHorizontal) 1 else -1 - vAnim = (vAnim + UiHandler.dt / 0.2f * vAnimDir).clamp() - hAnim = (hAnim + UiHandler.dt / 0.2f * hAnimDir).clamp() + vAnim = (vAnim + UiHandler.dt / 0.2f * vAnimDir).clamped() + hAnim = (hAnim + UiHandler.dt / 0.2f * hAnimDir).clamped() dragState match { case DragState.Vertical(startOffset, startPos) => @@ -178,8 +178,8 @@ class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandle private def maxYOffset: Float = inner.size.height - size.height private def clampOffsets(resetVelocities: Boolean): Unit = { - val newXOffset = xOffset.clamp(max = maxXOffset) - val newYOffset = yOffset.clamp(max = maxYOffset) + val newXOffset = xOffset.clamped(max = maxXOffset) + val newYOffset = yOffset.clamped(max = maxYOffset) if (resetVelocities) { if (xOffset != newXOffset) { diff --git a/src/main/scala/ocelot/desktop/ui/widget/Slider.scala b/src/main/scala/ocelot/desktop/ui/widget/Slider.scala index 41930e6..ac6062d 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/Slider.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/Slider.scala @@ -29,7 +29,7 @@ class Slider(var value: Float, val text: String, val snapPoints: Int = 0) private val soundInterval = 3.0f private def calculateValue(x: Float): Unit = { - value = ((x - bounds.x - handleWidth / 2f) / (bounds.w - handleWidth)).clamp(0f, 1f) + value = ((x - bounds.x - handleWidth / 2f) / (bounds.w - handleWidth)).clamped(0f, 1f) onValueChanged(value) } diff --git a/src/main/scala/ocelot/desktop/ui/widget/Viewport3DWidget.scala b/src/main/scala/ocelot/desktop/ui/widget/Viewport3DWidget.scala index 58030b1..31c2d6b 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/Viewport3DWidget.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/Viewport3DWidget.scala @@ -113,7 +113,7 @@ abstract class Viewport3DWidget extends Widget with MouseHandler with HoverHandl eventHandlers += { case ScrollEvent(offset) => cameraFinalDistance = (if (offset > 0) cameraFinalDistance / ScrollSpeed else cameraFinalDistance * ScrollSpeed) - .clamp(MinDistance, MaxDistance) + .clamped(MinDistance, MaxDistance) case event @ DragEvent(DragEvent.State.Drag, MouseEvent.Button.Left, _) if KeyEvents.isShiftDown => val basis = cameraFinalTransform.basis diff --git a/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenu.scala b/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenu.scala index 0157248..884b73e 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenu.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenu.scala @@ -1,10 +1,10 @@ package ocelot.desktop.ui.widget.contextmenu import ocelot.desktop.ColorScheme -import ocelot.desktop.geometry.Padding2D +import ocelot.desktop.geometry.{Padding2D, Size2D} import ocelot.desktop.graphics.Graphics -import ocelot.desktop.ui.layout.LinearLayout -import ocelot.desktop.ui.widget.{PaddingBox, Widget} +import ocelot.desktop.ui.layout.{CopyLayout, Layout, LinearLayout} +import ocelot.desktop.ui.widget.{PaddingBox, ScrollView, Widget} import ocelot.desktop.util.animation.ValueAnimation import ocelot.desktop.util.animation.easing.EaseInOutQuad import ocelot.desktop.util.{DrawUtils, Orientation} @@ -12,6 +12,8 @@ import ocelot.desktop.util.{DrawUtils, Orientation} import scala.collection.immutable.ArraySeq class ContextMenu extends Widget { + override protected val layout: Layout = new CopyLayout(this) + private[contextmenu] var isClosing = false private[contextmenu] var isOpening = false @@ -22,7 +24,11 @@ class ContextMenu extends Widget { override protected val layout = new LinearLayout(this, orientation = Orientation.Vertical) } - children :+= new PaddingBox(inner, Padding2D(top = 4, bottom = 4)) + private val scrollViewContents = new PaddingBox(inner, Padding2D(top = 4, bottom = 4)) + + children :+= new ScrollView(scrollViewContents) + + def preferredSize: Size2D = scrollViewContents.minimumSize private[contextmenu] def contextMenus: ContextMenus = _contextMenus diff --git a/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenus.scala b/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenus.scala index 6c84134..2f20db2 100644 --- a/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenus.scala +++ b/src/main/scala/ocelot/desktop/ui/widget/contextmenu/ContextMenus.scala @@ -1,5 +1,6 @@ package ocelot.desktop.ui.widget.contextmenu +import ocelot.desktop.geometry.FloatUtils.ExtendedFloat import ocelot.desktop.geometry.Vector2D import ocelot.desktop.graphics.Graphics import ocelot.desktop.ui.UiHandler @@ -47,17 +48,28 @@ class ContextMenus extends Widget { if (!isSubmenu) for (child <- menus) close(child) menu.recalculateBounds() - menu.size = menu.minimumSize - val size = menu.size - var pos = openPos + def fit(openPos: Float, flipPos: Float, menuSize: Float, size: Float): Float = { + if (openPos + menuSize <= size) { + // fits in the positive direction. + openPos + } else if (flipPos >= menuSize) { + // fits in the negative direction. + flipPos - menuSize + } else { + // lay in the positive direction, moving back to fit, but not outside the edge. + (size - menuSize).max(0) + } + } - if (pos.x + size.width > width) - pos = pos.setX(xFlipPos - size.width) - if (pos.y + size.height > height) - pos = pos.setY(yFlipPos - size.height) + val size = menu.preferredSize + val pos = Vector2D( + fit(openPos.x, xFlipPos, size.width, width), + fit(openPos.y, yFlipPos, size.height, height), + ) menu.position = pos + menu.size = size.copy(height = size.height.clamped(max = height)) menu.contextMenus = this menu.open() diff --git a/src/main/scala/ocelot/desktop/windows/HologramProjectorWindow.scala b/src/main/scala/ocelot/desktop/windows/HologramProjectorWindow.scala index 9699996..99425af 100644 --- a/src/main/scala/ocelot/desktop/windows/HologramProjectorWindow.scala +++ b/src/main/scala/ocelot/desktop/windows/HologramProjectorWindow.scala @@ -174,7 +174,7 @@ class HologramProjectorWindow(val hologramProjectorNode: HologramProjectorNode) val subTick = (System.nanoTime() - workspace.getLastTickNanoTime).toFloat / 1e9f / OcelotDesktop.tpsCounter.dt.max(1e-9f) - val time = workspace.getIngameTime + subTick.clamp(0, 1) + val time = workspace.getIngameTime + subTick.clamped(0, 1) var transform = Transform3D.translate(0.5f, 0.5f, 0.5f) * Transform3D.rotate(