prototype of new shelf UI
This commit is contained in:
parent
8324bff154
commit
766a9e10ee
19 changed files with 537 additions and 129 deletions
Binary file not shown.
|
|
@ -5,7 +5,7 @@ import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking
|
|||
import net.liquidev.dawd3.common.*
|
||||
import net.liquidev.dawd3.item.PatchCableItem
|
||||
import net.liquidev.dawd3.net.DisconnectPort
|
||||
import net.liquidev.dawd3.ui.Rack
|
||||
import net.liquidev.dawd3.ui.RackScreen
|
||||
import net.minecraft.block.*
|
||||
import net.minecraft.block.entity.BlockEntity
|
||||
import net.minecraft.client.MinecraftClient
|
||||
|
|
@ -98,7 +98,7 @@ class DeviceBlock(private val descriptor: AnyDeviceBlockDescriptor) :
|
|||
if (usedPortName == null && !player.isSneaking) {
|
||||
// We have to do a little dance such that the server executes this code too and doesn't
|
||||
// try to perform the item action if the UI is opened.
|
||||
val adjacentDevices = Rack.collectAdjacentDevices(world, pos)
|
||||
val adjacentDevices = RackScreen.collectAdjacentDevices(world, pos)
|
||||
val shouldOpenUI = adjacentDevices.any { blockPos ->
|
||||
val blockEntityAtPosition =
|
||||
world.getBlockEntity(blockPos) as? DeviceBlockEntity ?: return@any false
|
||||
|
|
@ -106,7 +106,7 @@ class DeviceBlock(private val descriptor: AnyDeviceBlockDescriptor) :
|
|||
}
|
||||
if (shouldOpenUI) {
|
||||
if (world is ClientWorld) {
|
||||
MinecraftClient.getInstance().setScreen(Rack(world, adjacentDevices))
|
||||
MinecraftClient.getInstance().setScreen(RackScreen(world, adjacentDevices))
|
||||
}
|
||||
return ActionResult.success(world.isClient)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import net.liquidev.dawd3.audio.device.ControlSet
|
|||
import net.liquidev.dawd3.audio.device.DeviceInstance
|
||||
import net.liquidev.dawd3.block.device.descriptor.FaceTextures
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.block.AbstractBlock
|
||||
import net.minecraft.block.Material
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
|
|
@ -41,7 +41,7 @@ interface DeviceBlockDescriptor<out CS : DeviceBlockDescriptor.ClientState, out
|
|||
}
|
||||
|
||||
interface UI<out Controls : ControlSet> {
|
||||
fun open(controls: @UnsafeVariance Controls, x: Float, y: Float): Widget
|
||||
fun open(controls: @UnsafeVariance Controls, x: Float, y: Float): Window
|
||||
}
|
||||
|
||||
val ui: UI<Controls>?
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import net.liquidev.dawd3.block.device.PhysicalPort
|
|||
import net.liquidev.dawd3.block.device.descriptor.FaceTextures
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.liquidev.dawd3.ui.widget.Knob
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.text.Text
|
||||
|
|
@ -39,7 +38,7 @@ object KnobBlockDescriptor : DeviceBlockDescriptor<KnobBlockDescriptor.ClientSta
|
|||
ClientState(controls)
|
||||
|
||||
override val ui = object : DeviceBlockDescriptor.UI<ConstantDevice.Controls> {
|
||||
override fun open(controls: ConstantDevice.Controls, x: Float, y: Float): Widget =
|
||||
override fun open(controls: ConstantDevice.Controls, x: Float, y: Float) =
|
||||
Window(x, y, 48f, 48f, Text.translatable("block.dawd3.knob")).apply {
|
||||
children.add(
|
||||
Knob(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import net.liquidev.dawd3.block.device.PhysicalPort
|
|||
import net.liquidev.dawd3.ui.units.AmplitudeValue
|
||||
import net.liquidev.dawd3.ui.units.PercentageValue
|
||||
import net.liquidev.dawd3.ui.widget.Knob
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.text.Text
|
||||
|
|
@ -45,7 +44,7 @@ object AmplifierBlockDescriptor : DeviceBlockDescriptor<AmplifierBlockDescriptor
|
|||
ClientState(controls)
|
||||
|
||||
override val ui = object : DeviceBlockDescriptor.UI<AmplifierDevice.Controls> {
|
||||
override fun open(controls: AmplifierDevice.Controls, x: Float, y: Float): Widget =
|
||||
override fun open(controls: AmplifierDevice.Controls, x: Float, y: Float) =
|
||||
Window(
|
||||
x,
|
||||
y,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import net.liquidev.dawd3.block.device.PhysicalPort
|
|||
import net.liquidev.dawd3.block.device.descriptor.FaceTextures
|
||||
import net.liquidev.dawd3.common.Cuboids
|
||||
import net.liquidev.dawd3.ui.widget.Knob
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.text.Text
|
||||
|
|
@ -43,7 +42,7 @@ object ModulatorBlockDescriptor : DeviceBlockDescriptor<ModulatorBlockDescriptor
|
|||
ClientState(controls)
|
||||
|
||||
override val ui = object : DeviceBlockDescriptor.UI<FmaDevice.Controls> {
|
||||
override fun open(controls: FmaDevice.Controls, x: Float, y: Float): Widget =
|
||||
override fun open(controls: FmaDevice.Controls, x: Float, y: Float) =
|
||||
Window(x, y, 78f, 48f, Text.translatable("block.dawd3.modulator")).apply {
|
||||
children.add(
|
||||
Knob(
|
||||
|
|
|
|||
|
|
@ -364,4 +364,12 @@ object Render {
|
|||
val x = centerX - width / 2
|
||||
tooltip(matrices, x, y, text, atlas, tooltip)
|
||||
}
|
||||
|
||||
inline fun colorized(r: Float, g: Float, b: Float, a: Float, then: () -> Unit) {
|
||||
val (oldR, oldG, oldB, oldA) = RenderSystem.getShaderColor()
|
||||
RenderSystem.setShaderColor(r, g, b, a)
|
||||
RenderSystem.enableBlend() // sigh
|
||||
then()
|
||||
RenderSystem.setShaderColor(oldR, oldG, oldB, oldA)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
package net.liquidev.dawd3.ui
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import net.liquidev.dawd3.Mod
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||
import net.liquidev.dawd3.render.Atlas
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.render.Sprite
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.rack.Rack
|
||||
import net.minecraft.client.MinecraftClient
|
||||
import net.minecraft.client.gui.screen.Screen
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
|
|
@ -15,86 +15,39 @@ import net.minecraft.text.Text
|
|||
import net.minecraft.util.Identifier
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import net.minecraft.world.World
|
||||
import kotlin.math.max
|
||||
|
||||
class Rack(
|
||||
class RackScreen(
|
||||
val world: ClientWorld,
|
||||
shownDevices: Iterable<BlockPos>,
|
||||
) : Screen(Text.translatable("screen.dawd3.rack.title")) {
|
||||
private data class OpenWidget(
|
||||
val blockPosition: BlockPos,
|
||||
val widget: Widget,
|
||||
)
|
||||
|
||||
private var openWidgets =
|
||||
ArrayList(shownDevices.mapNotNull { blockPosition ->
|
||||
val blockEntity = world.getBlockEntity(blockPosition) as DeviceBlockEntity
|
||||
blockEntity.descriptor.ui
|
||||
?.open(blockEntity.controls, 0f, 0f)
|
||||
?.let { widget -> OpenWidget(blockPosition, widget) }
|
||||
})
|
||||
|
||||
private fun layoutWindows() {
|
||||
var x = 16f
|
||||
var y = 16f
|
||||
var rowHeight = 0f
|
||||
for (openWidget in openWidgets) {
|
||||
if (x + openWidget.widget.width >= width - 16) {
|
||||
x = 16f
|
||||
y += rowHeight + 8f
|
||||
rowHeight = 0f
|
||||
}
|
||||
openWidget.widget.x = x
|
||||
openWidget.widget.y = y
|
||||
x += openWidget.widget.width + 8f
|
||||
rowHeight = max(rowHeight, openWidget.widget.height)
|
||||
}
|
||||
}
|
||||
private val rootWidget = Rack(width.toFloat(), height.toFloat(), world, shownDevices)
|
||||
|
||||
override fun init() {
|
||||
super.init()
|
||||
layoutWindows()
|
||||
rootWidget.width = width.toFloat()
|
||||
rootWidget.height = height.toFloat()
|
||||
rootWidget.reflow()
|
||||
}
|
||||
|
||||
override fun resize(client: MinecraftClient?, width: Int, height: Int) {
|
||||
super.resize(client, width, height)
|
||||
layoutWindows()
|
||||
rootWidget.width = width.toFloat()
|
||||
rootWidget.height = height.toFloat()
|
||||
rootWidget.reflow()
|
||||
}
|
||||
|
||||
override fun render(matrices: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||
renderBackground(matrices)
|
||||
|
||||
// Handle blocks being destroyed behind our back.
|
||||
openWidgets.removeIf { world.getBlockEntity(it.blockPosition) !is DeviceBlockEntity }
|
||||
if (openWidgets.isEmpty()) {
|
||||
close()
|
||||
}
|
||||
|
||||
for (openWidget in openWidgets) {
|
||||
openWidget.widget.draw(matrices, mouseX.toFloat(), mouseY.toFloat(), delta)
|
||||
}
|
||||
|
||||
Render.sprite(
|
||||
matrices,
|
||||
8f,
|
||||
height - badge.height - 8,
|
||||
badge.width * 2,
|
||||
badge.height * 2,
|
||||
atlas,
|
||||
badge
|
||||
)
|
||||
// For some silly reason blending is disabled by default, so let's enable it for rendering
|
||||
// widgets.
|
||||
RenderSystem.enableBlend()
|
||||
rootWidget.draw(matrices, mouseX.toFloat(), mouseY.toFloat(), delta)
|
||||
RenderSystem.disableBlend()
|
||||
}
|
||||
|
||||
private fun propagateEvent(event: Event): Boolean {
|
||||
for (openWidget in openWidgets) {
|
||||
if (
|
||||
openWidget.widget.event(
|
||||
EventContext(world, openWidget.blockPosition),
|
||||
event.relativeTo(openWidget.widget.x, openWidget.widget.y)
|
||||
)
|
||||
) return true
|
||||
}
|
||||
return false
|
||||
return rootWidget.event(Unit, event).eventConsumed
|
||||
}
|
||||
|
||||
override fun mouseMoved(mouseX: Double, mouseY: Double) {
|
||||
|
|
@ -129,10 +82,6 @@ class Rack(
|
|||
val smallFont = Identifier(Mod.id, "altopixel")
|
||||
val smallText = Style.EMPTY.withFont(smallFont)!!
|
||||
|
||||
// TODO: This "adjacent device" system is kind of janky but it allows for pretty nice
|
||||
// organization of your devices into multiple racks of sorts. Maybe in the future we can
|
||||
// think of collecting non-adjacent devices as well.
|
||||
|
||||
fun collectAdjacentDevices(world: World, position: BlockPos): HashSet<BlockPos> {
|
||||
val result = hashSetOf<BlockPos>()
|
||||
collectAdjacentDevicesRec(result, world, position)
|
||||
|
|
@ -45,7 +45,14 @@ data class MouseMove(
|
|||
MouseMove(mouseX - x, mouseY - y, absoluteMouseX, absoluteMouseY)
|
||||
}
|
||||
|
||||
class EventContext(
|
||||
class DeviceEventContext(
|
||||
val world: ClientWorld,
|
||||
val blockPosition: BlockPos,
|
||||
)
|
||||
|
||||
open class Message(val eventConsumed: Boolean) {
|
||||
companion object {
|
||||
val eventIgnored = Message(eventConsumed = false)
|
||||
val eventUsed = Message(eventConsumed = true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
package net.liquidev.dawd3.ui.widget
|
||||
|
||||
import net.liquidev.dawd3.ui.Event
|
||||
import net.liquidev.dawd3.ui.EventContext
|
||||
import net.liquidev.dawd3.ui.Message
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
|
||||
abstract class Container(x: Float, y: Float) : Widget(x, y) {
|
||||
abstract val children: List<Widget>
|
||||
abstract class Container<in C>(x: Float, y: Float) : Widget<C, Message>(x, y) {
|
||||
abstract val children: List<Widget<C, Message>>
|
||||
|
||||
override fun drawContent(
|
||||
matrices: MatrixStack,
|
||||
|
|
@ -18,7 +18,7 @@ abstract class Container(x: Float, y: Float) : Widget(x, y) {
|
|||
}
|
||||
}
|
||||
|
||||
override fun event(context: EventContext, event: Event): Boolean {
|
||||
override fun event(context: C, event: Event): Message {
|
||||
return propagateEvent(context, event, children.reversed())
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +35,7 @@ class Knob(
|
|||
val unit: FloatUnit = RawValue,
|
||||
// Pick a default sensitivity such that for pitch ranges we move by steps of 0.25.
|
||||
val sensitivity: Float = (max - min) * 0.25f / 96f,
|
||||
) : Widget(x, y) {
|
||||
) : DeviceWidget(x, y) {
|
||||
override val width = size
|
||||
override val height = size + 4f
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ class Knob(
|
|||
private var draggingInfo: DraggingInfo? = null
|
||||
|
||||
private val controlLabel =
|
||||
Text.translatable(control.descriptor.name.toTranslationKey()).setStyle(Rack.smallText)
|
||||
Text.translatable(control.descriptor.name.toTranslationKey()).setStyle(RackScreen.smallText)
|
||||
|
||||
override fun drawContent(
|
||||
matrices: MatrixStack,
|
||||
|
|
@ -72,7 +72,7 @@ class Knob(
|
|||
Radians(min(valueAngle, zeroAngle)),
|
||||
Radians(max(valueAngle, zeroAngle)),
|
||||
vertexCount = 16,
|
||||
Rack.atlas,
|
||||
RackScreen.atlas,
|
||||
coloredStrip(color)
|
||||
)
|
||||
Render.arcOutline(
|
||||
|
|
@ -84,7 +84,7 @@ class Knob(
|
|||
startAngle,
|
||||
endAngle,
|
||||
vertexCount = 16,
|
||||
Rack.atlas,
|
||||
RackScreen.atlas,
|
||||
blackStrip
|
||||
)
|
||||
Render.line(
|
||||
|
|
@ -94,7 +94,7 @@ class Knob(
|
|||
centerX + cos(valueAngle) * radius,
|
||||
centerY + sin(valueAngle) * radius,
|
||||
lineThickness / 2,
|
||||
Rack.atlas,
|
||||
RackScreen.atlas,
|
||||
blackStrip
|
||||
)
|
||||
|
||||
|
|
@ -112,13 +112,13 @@ class Knob(
|
|||
centerX,
|
||||
height - 4,
|
||||
Text.literal(unit.display(control.value)),
|
||||
Rack.atlas,
|
||||
RackScreen.atlas,
|
||||
Tooltip.dark
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun event(context: EventContext, event: Event): Boolean {
|
||||
override fun event(context: DeviceEventContext, event: Event): Message {
|
||||
val client = MinecraftClient.getInstance()
|
||||
when (event) {
|
||||
is MouseButton -> if (event.button == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
|
||||
|
|
@ -138,7 +138,7 @@ class Knob(
|
|||
event.absoluteMouseX * guiScale,
|
||||
event.absoluteMouseY * guiScale,
|
||||
)
|
||||
return true
|
||||
return Message.eventUsed
|
||||
}
|
||||
Action.Up -> {
|
||||
val draggingInfo = draggingInfo
|
||||
|
|
@ -174,12 +174,12 @@ class Knob(
|
|||
draggingInfo.previousMouseY = event.absoluteMouseY
|
||||
// Consume the events so that other controls don't get triggered while the knob
|
||||
// is being dragged.
|
||||
return true
|
||||
return Message.eventUsed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return Message.eventIgnored
|
||||
}
|
||||
|
||||
private fun isFineTuning(): Boolean =
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
package net.liquidev.dawd3.ui.widget
|
||||
|
||||
import net.liquidev.dawd3.ui.DeviceEventContext
|
||||
import net.liquidev.dawd3.ui.Event
|
||||
import net.liquidev.dawd3.ui.EventContext
|
||||
import net.liquidev.dawd3.ui.Message
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
|
||||
abstract class Widget(var x: Float, var y: Float) {
|
||||
abstract class Widget<in C, out M : Message>(var x: Float, var y: Float) {
|
||||
abstract val width: Float
|
||||
abstract val height: Float
|
||||
|
||||
|
|
@ -15,37 +16,44 @@ abstract class Widget(var x: Float, var y: Float) {
|
|||
deltaTime: Float,
|
||||
)
|
||||
|
||||
/** Returns false to propagate the event, or true to consume it. */
|
||||
abstract fun event(context: EventContext, event: Event): Boolean
|
||||
abstract fun event(context: C, event: Event): M
|
||||
|
||||
fun draw(matrices: MatrixStack, mouseX: Float, mouseY: Float, deltaTime: Float) {
|
||||
inline fun drawInside(matrices: MatrixStack, draw: () -> Unit) {
|
||||
matrices.push()
|
||||
matrices.translate(x.toDouble(), y.toDouble(), 0.0)
|
||||
drawContent(matrices, mouseX - x, mouseY - y, deltaTime)
|
||||
draw()
|
||||
matrices.pop()
|
||||
}
|
||||
|
||||
fun draw(matrices: MatrixStack, mouseX: Float, mouseY: Float, deltaTime: Float) {
|
||||
drawInside(matrices) { drawContent(matrices, mouseX - x, mouseY - y, deltaTime) }
|
||||
}
|
||||
|
||||
/** Can be overridden to reflow the children's layout. */
|
||||
open fun reflow() {}
|
||||
|
||||
fun containsRelativePoint(x: Float, y: Float) =
|
||||
x >= 0 && y >= 0 && x <= width && y <= height
|
||||
|
||||
companion object {
|
||||
/** Propagates the event through the given iterable, returns whether it was consumed in the end. */
|
||||
fun propagateEvent(
|
||||
context: EventContext,
|
||||
fun <C> propagateEvent(
|
||||
context: C,
|
||||
event: Event,
|
||||
through: Iterable<Widget>,
|
||||
): Boolean {
|
||||
through: Iterable<Widget<C, Message>>,
|
||||
): Message {
|
||||
for (widget in through) {
|
||||
if (
|
||||
widget.event(
|
||||
context,
|
||||
event.relativeTo(widget.x, widget.y)
|
||||
)
|
||||
) {
|
||||
return true
|
||||
val message = widget.event(
|
||||
context,
|
||||
event.relativeTo(widget.x, widget.y)
|
||||
)
|
||||
if (message.eventConsumed) {
|
||||
return message
|
||||
}
|
||||
}
|
||||
return false
|
||||
return Message.eventIgnored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias DeviceWidget = Widget<DeviceEventContext, Message>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ package net.liquidev.dawd3.ui.widget
|
|||
|
||||
import net.liquidev.dawd3.render.NinePatch
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.ui.Rack
|
||||
import net.liquidev.dawd3.ui.*
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.text.Text
|
||||
import org.lwjgl.glfw.GLFW
|
||||
|
||||
class Window(
|
||||
x: Float,
|
||||
|
|
@ -12,9 +13,9 @@ class Window(
|
|||
override val width: Float,
|
||||
override val height: Float,
|
||||
val title: Text,
|
||||
) : Container(x, y) {
|
||||
) : Container<DeviceEventContext>(x, y) {
|
||||
|
||||
override val children = mutableListOf<Widget>()
|
||||
override val children = mutableListOf<DeviceWidget>()
|
||||
|
||||
override fun drawContent(
|
||||
matrices: MatrixStack,
|
||||
|
|
@ -22,8 +23,8 @@ class Window(
|
|||
mouseY: Float,
|
||||
deltaTime: Float,
|
||||
) {
|
||||
Render.ninePatch(matrices, 2f, 2f, width, height, Rack.atlas, windowShadow)
|
||||
Render.ninePatch(matrices, 0f, 0f, width, height, Rack.atlas, windowBackground)
|
||||
Render.ninePatch(matrices, 2f, 2f, width, height, RackScreen.atlas, windowShadow)
|
||||
Render.ninePatch(matrices, 0f, 0f, width, height, RackScreen.atlas, windowBackground)
|
||||
|
||||
Render.textCentered(
|
||||
matrices,
|
||||
|
|
@ -36,6 +37,27 @@ class Window(
|
|||
super.drawContent(matrices, mouseX, mouseY, deltaTime)
|
||||
}
|
||||
|
||||
override fun event(context: DeviceEventContext, event: Event): Message {
|
||||
val childrenMessage = super.event(context, event)
|
||||
if (childrenMessage.eventConsumed) {
|
||||
return childrenMessage
|
||||
}
|
||||
|
||||
if (event is MouseButton && event.button == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
|
||||
if (containsRelativePoint(event.mouseX, event.mouseY) && event.action == Action.Down) {
|
||||
return BeginDrag(this, event.mouseX, event.mouseY)
|
||||
}
|
||||
}
|
||||
|
||||
return Message.eventIgnored
|
||||
}
|
||||
|
||||
data class BeginDrag(
|
||||
val window: Window,
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
) : Message(eventConsumed = true)
|
||||
|
||||
companion object {
|
||||
val windowBackground = NinePatch(
|
||||
u = 0f, v = 0f,
|
||||
|
|
|
|||
|
|
@ -3,10 +3,11 @@ package net.liquidev.dawd3.ui.widget.keyboard
|
|||
import net.liquidev.dawd3.audio.device.FloatControl
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.render.Sprite
|
||||
import net.liquidev.dawd3.ui.DeviceEventContext
|
||||
import net.liquidev.dawd3.ui.Event
|
||||
import net.liquidev.dawd3.ui.EventContext
|
||||
import net.liquidev.dawd3.ui.Rack
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.Message
|
||||
import net.liquidev.dawd3.ui.RackScreen
|
||||
import net.liquidev.dawd3.ui.widget.DeviceWidget
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
|
||||
class Key(
|
||||
|
|
@ -15,7 +16,7 @@ class Key(
|
|||
val type: Type,
|
||||
val note: Float,
|
||||
val pitchControl: FloatControl,
|
||||
) : Widget(x, y) {
|
||||
) : DeviceWidget(x, y) {
|
||||
|
||||
enum class Type(
|
||||
val width: Float,
|
||||
|
|
@ -55,7 +56,7 @@ class Key(
|
|||
matrices,
|
||||
x = 0f,
|
||||
y = 0f,
|
||||
Rack.atlas,
|
||||
RackScreen.atlas,
|
||||
if (isPressed) type.pressedSprite else type.idleSprite
|
||||
)
|
||||
if (pitchControl.value == note) {
|
||||
|
|
@ -63,13 +64,13 @@ class Key(
|
|||
matrices,
|
||||
x = width / 2f - type.currentPitchSprite.width / 2f,
|
||||
y = height - type.currentPitchSprite.height - 2,
|
||||
Rack.atlas,
|
||||
RackScreen.atlas,
|
||||
type.currentPitchSprite
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun event(context: EventContext, event: Event) = false
|
||||
override fun event(context: DeviceEventContext, event: Event) = Message.eventIgnored
|
||||
|
||||
internal fun triggerDown() {
|
||||
isPressed = true
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class Keyboard(
|
|||
firstNote: Int,
|
||||
lastNote: Int,
|
||||
private val controls: KeyboardDevice.Controls,
|
||||
) : Container(x, y) {
|
||||
) : Container<DeviceEventContext>(x, y) {
|
||||
|
||||
override val children = run {
|
||||
var whiteX = 0f
|
||||
|
|
@ -37,7 +37,7 @@ class Keyboard(
|
|||
|
||||
private var pressedKey: Key? = null
|
||||
|
||||
override fun event(context: EventContext, event: Event): Boolean {
|
||||
override fun event(context: DeviceEventContext, event: Event): Message {
|
||||
if (event is MouseButton && event.button == GLFW.GLFW_MOUSE_BUTTON_LEFT) {
|
||||
mouseIsDown = event.action == Action.Down && containsRelativePoint(
|
||||
event.mouseX,
|
||||
|
|
@ -48,20 +48,20 @@ class Keyboard(
|
|||
} else {
|
||||
setPressedKey(context, null)
|
||||
}
|
||||
return mouseIsDown && pressedKey != null
|
||||
return if (mouseIsDown && pressedKey != null) Message.eventUsed else Message.eventIgnored
|
||||
}
|
||||
|
||||
if (event is MouseMove && mouseIsDown) {
|
||||
setPressedKey(context, findPressedKey(event.mouseX.toInt(), event.mouseY.toInt()))
|
||||
}
|
||||
|
||||
return false
|
||||
return Message.eventIgnored
|
||||
}
|
||||
|
||||
private fun findPressedKey(mouseX: Int, mouseY: Int): Key? =
|
||||
children.findLast { it.containsRelativePoint(mouseX - it.x, mouseY - it.y) }
|
||||
|
||||
private fun setPressedKey(context: EventContext, newKey: Key?) {
|
||||
private fun setPressedKey(context: DeviceEventContext, newKey: Key?) {
|
||||
val previousKey = pressedKey
|
||||
pressedKey = newKey
|
||||
if (newKey != previousKey) {
|
||||
|
|
|
|||
275
src/main/kotlin/net/liquidev/dawd3/ui/widget/rack/Rack.kt
Normal file
275
src/main/kotlin/net/liquidev/dawd3/ui/widget/rack/Rack.kt
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
package net.liquidev.dawd3.ui.widget.rack
|
||||
|
||||
import net.liquidev.dawd3.block.device.DeviceBlockEntity
|
||||
import net.liquidev.dawd3.render.NinePatch
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.render.TextureStrip
|
||||
import net.liquidev.dawd3.ui.*
|
||||
import net.liquidev.dawd3.ui.widget.DeviceWidget
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import net.minecraft.client.world.ClientWorld
|
||||
import net.minecraft.util.math.BlockPos
|
||||
import org.lwjgl.glfw.GLFW
|
||||
|
||||
class Rack(
|
||||
override var width: Float,
|
||||
override var height: Float,
|
||||
private val world: ClientWorld,
|
||||
shownDevices: Iterable<BlockPos>,
|
||||
) : Widget<Unit, Message>(0f, 0f) {
|
||||
|
||||
private val sidebar = Sidebar(0f, 0f, 160f, 0f)
|
||||
private val blockPositionsByWidget = hashMapOf<DeviceWidget, BlockPos>()
|
||||
|
||||
private val shelves = MutableList(10) { Shelf(0f, 0f, width) }
|
||||
|
||||
enum class DragPlace {
|
||||
Sidebar,
|
||||
Shelf,
|
||||
}
|
||||
|
||||
data class DraggedWindow(
|
||||
val window: Window,
|
||||
val source: DragPlace,
|
||||
val byX: Float,
|
||||
val byY: Float,
|
||||
|
||||
val sourceShelfIndex: Int = 0, // Only relevant when source == DragPlace.Shelf
|
||||
)
|
||||
|
||||
private var draggedWindow: DraggedWindow? = null
|
||||
|
||||
init {
|
||||
for (blockPosition in shownDevices) {
|
||||
val blockEntity = world.getBlockEntity(blockPosition) as? DeviceBlockEntity ?: continue
|
||||
val widget = blockEntity.descriptor.ui?.open(blockEntity.controls, x = 0f, y = 0f)
|
||||
if (widget != null) {
|
||||
sidebar.windows.add(widget)
|
||||
blockPositionsByWidget[widget] = blockPosition
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <W : DeviceWidget> removeStaleWidgetsFromContainer(container: MutableList<W>) {
|
||||
container.removeAll { world.getBlockEntity(blockPositionsByWidget[it]) !is DeviceBlockEntity }
|
||||
}
|
||||
|
||||
override fun drawContent(
|
||||
matrices: MatrixStack,
|
||||
mouseX: Float,
|
||||
mouseY: Float,
|
||||
deltaTime: Float,
|
||||
) {
|
||||
removeStaleWidgetsFromContainer(sidebar.windows)
|
||||
|
||||
for (shelf in shelves) {
|
||||
shelf.draw(matrices, mouseX, mouseY, deltaTime)
|
||||
}
|
||||
|
||||
sidebar.draw(matrices, mouseX, mouseY, deltaTime)
|
||||
|
||||
val draggedWindow = draggedWindow
|
||||
if (draggedWindow != null) {
|
||||
Render.ninePatch(
|
||||
matrices,
|
||||
mouseX - draggedWindow.byX,
|
||||
mouseY - draggedWindow.byY,
|
||||
draggedWindow.window.width,
|
||||
draggedWindow.window.height,
|
||||
RackScreen.atlas,
|
||||
windowDraggingPreview,
|
||||
)
|
||||
|
||||
val destination = findDragDestination(mouseX, mouseY)
|
||||
println(destination)
|
||||
when (destination?.place) {
|
||||
DragPlace.Sidebar -> {
|
||||
sidebar.drawInside(matrices) {
|
||||
val y = if (sidebar.windows.size == 0) {
|
||||
Sidebar.padding
|
||||
} else if (destination.indexInPlace == sidebar.windows.size) {
|
||||
val window = sidebar.windows.last()
|
||||
window.y + window.height - Sidebar.spacingBetweenWindows / 2f
|
||||
} else {
|
||||
val window = sidebar.windows[destination.indexInPlace]
|
||||
window.y - Sidebar.spacingBetweenWindows / 2f
|
||||
}
|
||||
Render.line(
|
||||
matrices,
|
||||
Sidebar.padding,
|
||||
y,
|
||||
sidebar.width - Sidebar.padding,
|
||||
y,
|
||||
thickness = 0.5f,
|
||||
RackScreen.atlas,
|
||||
windowDestinationPreview
|
||||
)
|
||||
}
|
||||
}
|
||||
DragPlace.Shelf -> {
|
||||
val shelf = shelves[destination.shelfIndex]
|
||||
shelf.drawInside(matrices) {
|
||||
val x = if (shelf.windows.size == 0) {
|
||||
Shelf.padding
|
||||
} else if (destination.indexInPlace == shelf.windows.size) {
|
||||
val window = shelf.windows.last()
|
||||
window.x + window.width + Shelf.spacingBetweenWindows / 2f
|
||||
} else {
|
||||
val window = shelf.windows[destination.indexInPlace]
|
||||
window.x - Shelf.spacingBetweenWindows / 2f
|
||||
}
|
||||
Render.line(
|
||||
matrices,
|
||||
x,
|
||||
Shelf.padding,
|
||||
x,
|
||||
shelf.height - Shelf.padding,
|
||||
thickness = 0.5f,
|
||||
RackScreen.atlas,
|
||||
windowDestinationPreview
|
||||
)
|
||||
}
|
||||
}
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendEventToWindows(
|
||||
windows: List<Window>,
|
||||
event: Event,
|
||||
source: DragPlace,
|
||||
sourceShelfIndex: Int = 0,
|
||||
): Message {
|
||||
for (window in windows) {
|
||||
val blockPosition = blockPositionsByWidget[window]!!
|
||||
val windowRelativeEvent = event.relativeTo(window.x, window.y)
|
||||
val windowMessage =
|
||||
window.event(DeviceEventContext(world, blockPosition), windowRelativeEvent)
|
||||
if (windowMessage is Window.BeginDrag) {
|
||||
draggedWindow =
|
||||
DraggedWindow(
|
||||
window,
|
||||
source,
|
||||
windowMessage.x,
|
||||
windowMessage.y,
|
||||
sourceShelfIndex
|
||||
)
|
||||
}
|
||||
if (windowMessage.eventConsumed) return Message.eventUsed
|
||||
}
|
||||
return Message.eventIgnored
|
||||
}
|
||||
|
||||
override fun event(context: Unit, event: Event): Message {
|
||||
val sidebarWindowMessage =
|
||||
sendEventToWindows(
|
||||
sidebar.windows,
|
||||
event.relativeTo(sidebar.x, sidebar.y),
|
||||
DragPlace.Sidebar
|
||||
)
|
||||
if (sidebarWindowMessage.eventConsumed) {
|
||||
return sidebarWindowMessage
|
||||
}
|
||||
|
||||
shelves.forEachIndexed { index, shelf ->
|
||||
val shelfWindowMessage =
|
||||
sendEventToWindows(
|
||||
shelf.windows,
|
||||
event.relativeTo(shelf.x, shelf.y),
|
||||
DragPlace.Shelf,
|
||||
index,
|
||||
)
|
||||
if (shelfWindowMessage.eventConsumed) {
|
||||
return shelfWindowMessage
|
||||
}
|
||||
}
|
||||
|
||||
if (event is MouseButton && event.button == GLFW.GLFW_MOUSE_BUTTON_LEFT && event.action == Action.Up) {
|
||||
val draggedWindow = draggedWindow
|
||||
if (draggedWindow != null) {
|
||||
val destination = findDragDestination(event.mouseX, event.mouseY)
|
||||
if (destination != null) {
|
||||
removeWindowFromSource(
|
||||
draggedWindow.source,
|
||||
draggedWindow.sourceShelfIndex,
|
||||
draggedWindow.window,
|
||||
)
|
||||
when (destination.place) {
|
||||
DragPlace.Sidebar -> sidebar.windows.add(
|
||||
destination.indexInPlace,
|
||||
draggedWindow.window
|
||||
)
|
||||
DragPlace.Shelf -> shelves[destination.shelfIndex].windows.add(
|
||||
destination.indexInPlace,
|
||||
draggedWindow.window
|
||||
)
|
||||
}
|
||||
reflow()
|
||||
}
|
||||
}
|
||||
this.draggedWindow = null
|
||||
}
|
||||
|
||||
return Message.eventIgnored
|
||||
}
|
||||
|
||||
internal data class DragDestination(
|
||||
val place: DragPlace,
|
||||
val indexInPlace: Int,
|
||||
val shelfIndex: Int = 0,
|
||||
)
|
||||
|
||||
private fun findDragDestination(mouseX: Float, mouseY: Float): DragDestination? {
|
||||
val indexInSidebar = sidebar.findDragDestination(mouseX - sidebar.x, mouseY - sidebar.y)
|
||||
if (indexInSidebar != null) {
|
||||
return DragDestination(DragPlace.Sidebar, indexInSidebar)
|
||||
}
|
||||
|
||||
shelves.forEachIndexed { shelfIndex, shelf ->
|
||||
val indexInShelf = shelf.findDragDestination(mouseX - shelf.x, mouseY - shelf.y)
|
||||
if (indexInShelf != null) {
|
||||
return DragDestination(DragPlace.Shelf, indexInShelf, shelfIndex)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun removeWindowFromSource(
|
||||
source: DragPlace,
|
||||
indexOfSource: Int,
|
||||
window: Window,
|
||||
) {
|
||||
when (source) {
|
||||
DragPlace.Sidebar -> {
|
||||
sidebar.windows.remove(window)
|
||||
}
|
||||
DragPlace.Shelf -> {
|
||||
shelves[indexOfSource].windows.remove(window)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun reflow() {
|
||||
sidebar.x = width - sidebar.width
|
||||
sidebar.height = height
|
||||
sidebar.reflow()
|
||||
|
||||
var dy = 0f
|
||||
for (shelf in shelves) {
|
||||
shelf.reflow()
|
||||
shelf.y = dy
|
||||
shelf.width = width
|
||||
dy += shelf.height
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val windowDraggingPreview =
|
||||
NinePatch(u = 48f, v = 0f, width = 8f, height = 8f, border = 2f)
|
||||
private val windowDestinationPreview = TextureStrip(25f, 16f, 25f, 32f)
|
||||
}
|
||||
}
|
||||
76
src/main/kotlin/net/liquidev/dawd3/ui/widget/rack/Shelf.kt
Normal file
76
src/main/kotlin/net/liquidev/dawd3/ui/widget/rack/Shelf.kt
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package net.liquidev.dawd3.ui.widget.rack
|
||||
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.render.TextureStrip
|
||||
import net.liquidev.dawd3.ui.Event
|
||||
import net.liquidev.dawd3.ui.Message
|
||||
import net.liquidev.dawd3.ui.RackScreen
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
import kotlin.math.max
|
||||
|
||||
class Shelf(x: Float, y: Float, override var width: Float) : Widget<Nothing, Message>(x, y) {
|
||||
override var height = 128f
|
||||
private set
|
||||
|
||||
val windows = mutableListOf<Window>()
|
||||
|
||||
override fun drawContent(
|
||||
matrices: MatrixStack,
|
||||
mouseX: Float,
|
||||
mouseY: Float,
|
||||
deltaTime: Float,
|
||||
) {
|
||||
for (window in windows) {
|
||||
window.draw(matrices, mouseX, mouseY, deltaTime)
|
||||
}
|
||||
Render.colorized(1f, 1f, 1f, 0.1f) {
|
||||
Render.line(
|
||||
matrices,
|
||||
0f,
|
||||
height,
|
||||
width,
|
||||
height,
|
||||
thickness = 0.5f,
|
||||
RackScreen.atlas,
|
||||
spacer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Events here are not handled directly by the Shelf, but rather by the parent rack
|
||||
// screen which has extra context that needs to be passed down to windows. Hence this cannot
|
||||
// be called (context is Nothing.)
|
||||
override fun event(context: Nothing, event: Event): Message {
|
||||
return Message.eventIgnored
|
||||
}
|
||||
|
||||
override fun reflow() {
|
||||
height = padding * 2f + (windows.maxOfOrNull { it.height } ?: 0f)
|
||||
height = max(64f, height)
|
||||
|
||||
var dx = 0f
|
||||
for (window in windows) {
|
||||
window.x = padding + dx
|
||||
window.y = padding
|
||||
dx += window.width + spacingBetweenWindows
|
||||
}
|
||||
}
|
||||
|
||||
internal fun findDragDestination(mouseX: Float, mouseY: Float): Int? {
|
||||
return if (containsRelativePoint(mouseX, mouseY)) {
|
||||
val maybeIndex = windows.indexOfFirst { mouseX < it.x + it.width / 2f }
|
||||
if (maybeIndex == -1) windows.size else maybeIndex
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val padding = 8f
|
||||
const val spacingBetweenWindows = 8f
|
||||
|
||||
private val spacer = TextureStrip(25f, 16f, 25f, 32f)
|
||||
}
|
||||
}
|
||||
65
src/main/kotlin/net/liquidev/dawd3/ui/widget/rack/Sidebar.kt
Normal file
65
src/main/kotlin/net/liquidev/dawd3/ui/widget/rack/Sidebar.kt
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package net.liquidev.dawd3.ui.widget.rack
|
||||
|
||||
import net.liquidev.dawd3.render.NinePatch
|
||||
import net.liquidev.dawd3.render.Render
|
||||
import net.liquidev.dawd3.ui.Event
|
||||
import net.liquidev.dawd3.ui.Message
|
||||
import net.liquidev.dawd3.ui.RackScreen
|
||||
import net.liquidev.dawd3.ui.widget.Widget
|
||||
import net.liquidev.dawd3.ui.widget.Window
|
||||
import net.minecraft.client.util.math.MatrixStack
|
||||
|
||||
class Sidebar(
|
||||
x: Float,
|
||||
y: Float,
|
||||
override val width: Float,
|
||||
override var height: Float,
|
||||
) : Widget<Nothing, Message>(x, y) {
|
||||
|
||||
val windows = mutableListOf<Window>()
|
||||
|
||||
override fun drawContent(
|
||||
matrices: MatrixStack,
|
||||
mouseX: Float,
|
||||
mouseY: Float,
|
||||
deltaTime: Float,
|
||||
) {
|
||||
Render.ninePatch(matrices, 0f, 0f, width, height, RackScreen.atlas, background)
|
||||
|
||||
for (window in windows) {
|
||||
window.draw(matrices, mouseX, mouseY, deltaTime)
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Events here are not handled directly by the Sidebar, but rather by the parent rack
|
||||
// screen which has extra context that needs to be passed down to windows. Hence this cannot
|
||||
// be called (context is Nothing.)
|
||||
override fun event(context: Nothing, event: Event): Message {
|
||||
return Message.eventIgnored
|
||||
}
|
||||
|
||||
override fun reflow() {
|
||||
var dy = 0f
|
||||
for (window in windows) {
|
||||
window.x = padding
|
||||
window.y = padding + dy
|
||||
dy += window.height + spacingBetweenWindows
|
||||
}
|
||||
}
|
||||
|
||||
internal fun findDragDestination(mouseX: Float, mouseY: Float): Int? {
|
||||
return if (containsRelativePoint(mouseX, mouseY)) {
|
||||
val maybeIndex = windows.indexOfFirst { mouseY < it.y + it.height / 2f }
|
||||
if (maybeIndex == -1) windows.size else maybeIndex
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val background = NinePatch(u = 40f, v = 0f, width = 8f, height = 8f, border = 2f)
|
||||
|
||||
const val padding = 8f
|
||||
const val spacingBetweenWindows = 8f
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 510 B After Width: | Height: | Size: 551 B |
Reference in a new issue