Merge branch 'feature/scrollable-context-menu' into develop

This commit is contained in:
Fingercomp 2025-08-14 17:30:34 +03:00
commit 9ced3b99b4
No known key found for this signature in database
GPG Key ID: BBC71CEE45D86E37
10 changed files with 43 additions and 25 deletions

View File

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

View File

@ -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 = {

View File

@ -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

View File

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

View File

@ -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) {

View File

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

View File

@ -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

View File

@ -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

View File

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

View File

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