Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
Shadowfacts | d6ca951838 | |
Shadowfacts | 7e71a1e03d | |
Shadowfacts | d782d43e1b | |
Shadowfacts | 03fd8ff699 | |
Shadowfacts | 0ba1033518 | |
Shadowfacts | dbf450e487 | |
Shadowfacts | 4e32bfb510 | |
Shadowfacts | 21d6a0a168 | |
Shadowfacts | 5bd4993f9d | |
Shadowfacts | f6f4c12d03 | |
Shadowfacts | 4effe2f1b4 |
|
@ -0,0 +1,2 @@
|
||||||
|
# reformat
|
||||||
|
f6f4c12d0304c945c03ab70556048ee8d78e4019
|
|
@ -1,7 +1,7 @@
|
||||||
plugins {
|
plugins {
|
||||||
id "fabric-loom" version "0.12.9"
|
id "fabric-loom" version "0.12.9"
|
||||||
id "maven-publish"
|
id "maven-publish"
|
||||||
id "org.jetbrains.kotlin.jvm" version "1.6.10"
|
id "org.jetbrains.kotlin.jvm" version "1.7.10"
|
||||||
}
|
}
|
||||||
|
|
||||||
archivesBaseName = project.archives_base_name
|
archivesBaseName = project.archives_base_name
|
||||||
|
@ -79,9 +79,9 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
|
// PSA: Some older mods, compiled on Loom 0.2.1, might have outdated Maven POMs.
|
||||||
// You may need to force-disable transitiveness on them.
|
// You may need to force-disable transitiveness on them.
|
||||||
modImplementation "alexiil.mc.lib:libblockattributes-all:${project.libblockattributes_version}"
|
// modImplementation "alexiil.mc.lib:libblockattributes-all:${project.libblockattributes_version}"
|
||||||
include "alexiil.mc.lib:libblockattributes-core:${project.libblockattributes_version}"
|
// include "alexiil.mc.lib:libblockattributes-core:${project.libblockattributes_version}"
|
||||||
include "alexiil.mc.lib:libblockattributes-items:${project.libblockattributes_version}"
|
// include "alexiil.mc.lib:libblockattributes-items:${project.libblockattributes_version}"
|
||||||
|
|
||||||
implementation project(":kiwi-java")
|
implementation project(":kiwi-java")
|
||||||
include project(":kiwi-java")
|
include project(":kiwi-java")
|
||||||
|
|
|
@ -17,71 +17,72 @@ import java.lang.invoke.MethodHandles
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object PhyConPluginClient: ClientModInitializer, REIClientPlugin, AbstractTerminalScreen.SearchQueryListener {
|
object PhyConPluginClient : ClientModInitializer, REIClientPlugin, AbstractTerminalScreen.SearchQueryListener {
|
||||||
|
|
||||||
private val logger = LogManager.getLogger()
|
private val logger = LogManager.getLogger()
|
||||||
private var isHighlightingHandle: MethodHandle? = null
|
private var isHighlightingHandle: MethodHandle? = null
|
||||||
|
|
||||||
override fun onInitializeClient() {
|
override fun onInitializeClient() {
|
||||||
ClientScreenInputEvent.MOUSE_RELEASED_PRE.register { client, screen, mouseX, mouseY, button ->
|
ClientScreenInputEvent.MOUSE_RELEASED_PRE.register { client, screen, mouseX, mouseY, button ->
|
||||||
if (screen is AbstractTerminalScreen<*, *>) {
|
if (screen is AbstractTerminalScreen<*, *>) {
|
||||||
REIRuntime.getInstance().searchTextField?.also {
|
REIRuntime.getInstance().searchTextField?.also {
|
||||||
if (it.isFocused) {
|
if (it.isFocused) {
|
||||||
screen.terminalVC.searchField.resignFirstResponder()
|
screen.terminalVC.searchField.resignFirstResponder()
|
||||||
} else {
|
} else {
|
||||||
screen.terminalVC.searchField.becomeFirstResponder()
|
screen.terminalVC.searchField.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventResult.pass()
|
EventResult.pass()
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractTerminalScreen.searchQueryListener = this
|
AbstractTerminalScreen.searchQueryListener = this
|
||||||
try {
|
try {
|
||||||
val clazz = Class.forName("me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField")
|
val clazz = Class.forName("me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField")
|
||||||
isHighlightingHandle = MethodHandles.publicLookup().findStaticGetter(clazz, "isHighlighting", Boolean::class.java)
|
isHighlightingHandle =
|
||||||
} catch (e: ReflectiveOperationException) {
|
MethodHandles.publicLookup().findStaticGetter(clazz, "isHighlighting", Boolean::class.java)
|
||||||
logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e)
|
} catch (e: ReflectiveOperationException) {
|
||||||
}
|
logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun registerScreens(registry: ScreenRegistry) {
|
override fun registerScreens(registry: ScreenRegistry) {
|
||||||
registry.exclusionZones().register(AbstractTerminalScreen::class.java) {
|
registry.exclusionZones().register(AbstractTerminalScreen::class.java) {
|
||||||
val screen = MinecraftClient.getInstance().currentScreen as AbstractTerminalScreen<*, *>
|
val screen = MinecraftClient.getInstance().currentScreen as AbstractTerminalScreen<*, *>
|
||||||
val view = screen.terminalVC.settingsView
|
val view = screen.terminalVC.settingsView
|
||||||
val rect = view.convert(view.bounds, to = null)
|
val rect = view.convert(view.bounds, to = null)
|
||||||
listOf(
|
listOf(
|
||||||
Rectangle(rect.left.toInt(), rect.top.toInt(), view.bounds.width.toInt(), view.bounds.height.toInt())
|
Rectangle(rect.left.toInt(), rect.top.toInt(), view.bounds.width.toInt(), view.bounds.height.toInt())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun terminalSearchQueryChanged(newValue: String) {
|
override fun terminalSearchQueryChanged(newValue: String) {
|
||||||
if (shouldSync()) {
|
if (shouldSync()) {
|
||||||
REIRuntime.getInstance().searchTextField?.text = newValue
|
REIRuntime.getInstance().searchTextField?.text = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requestTerminalSearchFieldUpdate(): String? {
|
override fun requestTerminalSearchFieldUpdate(): String? {
|
||||||
return if (shouldSync()) {
|
return if (shouldSync()) {
|
||||||
REIRuntime.getInstance().searchTextField?.text
|
REIRuntime.getInstance().searchTextField?.text
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun shouldSync(): Boolean {
|
private fun shouldSync(): Boolean {
|
||||||
return when (PhysicalConnectivityClient.terminalSettings[PhyConPluginCommon.REI_SYNC_KEY]) {
|
return when (PhysicalConnectivityClient.terminalSettings[PhyConPluginCommon.REI_SYNC_KEY]) {
|
||||||
REISyncMode.OFF -> false
|
REISyncMode.OFF -> false
|
||||||
REISyncMode.ON -> true
|
REISyncMode.ON -> true
|
||||||
REISyncMode.HIGHLIGHT_ONLY -> {
|
REISyncMode.HIGHLIGHT_ONLY -> {
|
||||||
if (isHighlightingHandle != null) {
|
if (isHighlightingHandle != null) {
|
||||||
isHighlightingHandle!!.invoke() as Boolean
|
isHighlightingHandle!!.invoke() as Boolean
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,51 +18,55 @@ import java.util.stream.IntStream
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object PhyConPluginCommon: REIServerPlugin, PhyConPlugin {
|
object PhyConPluginCommon : REIServerPlugin, PhyConPlugin {
|
||||||
const val MODID = "phycon_rei"
|
const val MODID = "phycon_rei"
|
||||||
|
|
||||||
lateinit var REI_SYNC_KEY: TerminalSettingKey<REISyncMode>
|
lateinit var REI_SYNC_KEY: TerminalSettingKey<REISyncMode>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun registerMenuInfo(registry: MenuInfoRegistry) {
|
override fun registerMenuInfo(registry: MenuInfoRegistry) {
|
||||||
registry.register(CategoryIdentifier.of("minecraft", "plugins/crafting"), CraftingTerminalScreenHandler::class.java, SimpleMenuInfoProvider.of(::TerminalInfo))
|
registry.register(
|
||||||
}
|
CategoryIdentifier.of("minecraft", "plugins/crafting"),
|
||||||
|
CraftingTerminalScreenHandler::class.java,
|
||||||
|
SimpleMenuInfoProvider.of(::TerminalInfo)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override fun initializePhyCon(api: PhyConAPI) {
|
override fun initializePhyCon(api: PhyConAPI) {
|
||||||
REI_SYNC_KEY = api.registerTerminalSetting(Identifier(MODID, "rei_sync"), REISyncMode.OFF)
|
REI_SYNC_KEY = api.registerTerminalSetting(Identifier(MODID, "rei_sync"), REISyncMode.OFF)
|
||||||
}
|
}
|
||||||
|
|
||||||
class TerminalInfo<D: SimpleGridMenuDisplay>(
|
class TerminalInfo<D : SimpleGridMenuDisplay>(
|
||||||
private val display: D,
|
private val display: D,
|
||||||
): SimpleGridMenuInfo<CraftingTerminalScreenHandler, D> {
|
) : SimpleGridMenuInfo<CraftingTerminalScreenHandler, D> {
|
||||||
|
|
||||||
override fun getCraftingResultSlotIndex(menu: CraftingTerminalScreenHandler): Int {
|
override fun getCraftingResultSlotIndex(menu: CraftingTerminalScreenHandler): Int {
|
||||||
return menu.resultSlot.id
|
return menu.resultSlot.id
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getInputStackSlotIds(context: MenuInfoContext<CraftingTerminalScreenHandler, *, D>): IntStream {
|
override fun getInputStackSlotIds(context: MenuInfoContext<CraftingTerminalScreenHandler, *, D>): IntStream {
|
||||||
return IntStream.range(context.menu.craftingSlotsStart, context.menu.craftingSlotsEnd)
|
return IntStream.range(context.menu.craftingSlotsStart, context.menu.craftingSlotsEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getInventorySlots(context: MenuInfoContext<CraftingTerminalScreenHandler, *, D>): Iterable<SlotAccessor> {
|
override fun getInventorySlots(context: MenuInfoContext<CraftingTerminalScreenHandler, *, D>): Iterable<SlotAccessor> {
|
||||||
val slots = super.getInventorySlots(context).toMutableList()
|
val slots = super.getInventorySlots(context).toMutableList()
|
||||||
for (i in (context.menu.bufferSlotsStart until context.menu.bufferSlotsEnd)) {
|
for (i in (context.menu.bufferSlotsStart until context.menu.bufferSlotsEnd)) {
|
||||||
slots.add(SlotAccessor.fromSlot(context.menu.getSlot(i)))
|
slots.add(SlotAccessor.fromSlot(context.menu.getSlot(i)))
|
||||||
}
|
}
|
||||||
return slots
|
return slots
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCraftingWidth(menu: CraftingTerminalScreenHandler): Int {
|
override fun getCraftingWidth(menu: CraftingTerminalScreenHandler): Int {
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCraftingHeight(menu: CraftingTerminalScreenHandler): Int {
|
override fun getCraftingHeight(menu: CraftingTerminalScreenHandler): Int {
|
||||||
return 3
|
return 3
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDisplay(): D {
|
override fun getDisplay(): D {
|
||||||
return display
|
return display
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import net.shadowfacts.phycon.api.TerminalSetting
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
enum class REISyncMode: TerminalSetting {
|
enum class REISyncMode : TerminalSetting {
|
||||||
OFF,
|
OFF,
|
||||||
ON,
|
ON,
|
||||||
HIGHLIGHT_ONLY;
|
HIGHLIGHT_ONLY;
|
||||||
|
|
|
@ -14,18 +14,27 @@ import techreborn.init.TRContent
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object PhyConTR: ModInitializer {
|
object PhyConTR : ModInitializer {
|
||||||
|
|
||||||
override fun onInitialize() {
|
override fun onInitialize() {
|
||||||
TRContent.StorageUnit.values().forEach {
|
TRContent.StorageUnit.values().forEach {
|
||||||
ItemAttributes.GROUPED_INV.setBlockAdder(AttributeSourceType.COMPAT_WRAPPER, it.block, ::addStorageUnitGroupedInv)
|
ItemAttributes.GROUPED_INV.setBlockAdder(
|
||||||
}
|
AttributeSourceType.COMPAT_WRAPPER,
|
||||||
}
|
it.block,
|
||||||
|
::addStorageUnitGroupedInv
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun addStorageUnitGroupedInv(world: World, pos: BlockPos, state: BlockState, to: AttributeList<GroupedItemInv>) {
|
private fun addStorageUnitGroupedInv(
|
||||||
(world.getBlockEntity(pos) as? StorageUnitBaseBlockEntity)?.also { su ->
|
world: World,
|
||||||
to.offer(StorageUnitWrapper(su))
|
pos: BlockPos,
|
||||||
}
|
state: BlockState,
|
||||||
}
|
to: AttributeList<GroupedItemInv>
|
||||||
|
) {
|
||||||
|
(world.getBlockEntity(pos) as? StorageUnitBaseBlockEntity)?.also { su ->
|
||||||
|
to.offer(StorageUnitWrapper(su))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,67 +15,67 @@ import kotlin.math.min
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class StorageUnitWrapper(
|
class StorageUnitWrapper(
|
||||||
val be: StorageUnitBaseBlockEntity,
|
val be: StorageUnitBaseBlockEntity,
|
||||||
): GroupedItemInv {
|
) : GroupedItemInv {
|
||||||
|
|
||||||
override fun getStoredStacks(): Set<ItemStack> {
|
override fun getStoredStacks(): Set<ItemStack> {
|
||||||
val set = ItemStackCollections.set()
|
val set = ItemStackCollections.set()
|
||||||
if (!be.storedStack.isEmpty) {
|
if (!be.storedStack.isEmpty) {
|
||||||
set.add(be.storedStack)
|
set.add(be.storedStack)
|
||||||
}
|
}
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTotalCapacity(): Int {
|
override fun getTotalCapacity(): Int {
|
||||||
return be.maxCapacity
|
return be.maxCapacity
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic {
|
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic {
|
||||||
// todo: should spaceAddable really be zero? that's what SimpleGroupedItemInv does
|
// todo: should spaceAddable really be zero? that's what SimpleGroupedItemInv does
|
||||||
return if (be.storedStack.isEmpty) {
|
return if (be.storedStack.isEmpty) {
|
||||||
GroupedItemInvView.ItemInvStatistic(filter, 0, 0, totalCapacity)
|
GroupedItemInvView.ItemInvStatistic(filter, 0, 0, totalCapacity)
|
||||||
} else if (filter.matches(be.storedStack)) {
|
} else if (filter.matches(be.storedStack)) {
|
||||||
// don't use the storedAmount field, it's only used on the client for rendering
|
// don't use the storedAmount field, it's only used on the client for rendering
|
||||||
val amount = be.getStoredAmount()
|
val amount = be.getStoredAmount()
|
||||||
GroupedItemInvView.ItemInvStatistic(filter, amount, 0, totalCapacity - amount)
|
GroupedItemInvView.ItemInvStatistic(filter, amount, 0, totalCapacity - amount)
|
||||||
} else {
|
} else {
|
||||||
GroupedItemInvView.ItemInvStatistic(filter, 0, 0, 0)
|
GroupedItemInvView.ItemInvStatistic(filter, 0, 0, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attemptInsertion(filter: ItemStack, simulation: Simulation): ItemStack {
|
override fun attemptInsertion(filter: ItemStack, simulation: Simulation): ItemStack {
|
||||||
if (simulation.isAction) {
|
if (simulation.isAction) {
|
||||||
return be.processInput(filter)
|
return be.processInput(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (be.storedStack.isEmpty) {
|
if (be.storedStack.isEmpty) {
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ItemStackUtil.areEqualIgnoreAmounts(be.storedStack, filter)) {
|
if (!ItemStackUtil.areEqualIgnoreAmounts(be.storedStack, filter)) {
|
||||||
return filter
|
return filter
|
||||||
}
|
}
|
||||||
|
|
||||||
val availableCapacity = totalCapacity - be.getStoredAmount()
|
val availableCapacity = totalCapacity - be.getStoredAmount()
|
||||||
return if (availableCapacity >= filter.count) {
|
return if (availableCapacity >= filter.count) {
|
||||||
ItemStack.EMPTY
|
ItemStack.EMPTY
|
||||||
} else {
|
} else {
|
||||||
filter.copyWithCount(filter.count - availableCapacity)
|
filter.copyWithCount(filter.count - availableCapacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun attemptExtraction(filter: ItemFilter, maxAmount: Int, simulation: Simulation): ItemStack {
|
override fun attemptExtraction(filter: ItemFilter, maxAmount: Int, simulation: Simulation): ItemStack {
|
||||||
if (be.storedStack.isEmpty || !filter.matches(be.storedStack)) {
|
if (be.storedStack.isEmpty || !filter.matches(be.storedStack)) {
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
val extracted = min(maxAmount, be.getStoredAmount())
|
val extracted = min(maxAmount, be.getStoredAmount())
|
||||||
|
|
||||||
if (simulation.isAction) {
|
if (simulation.isAction) {
|
||||||
be.storedStack.decrement(extracted)
|
be.storedStack.decrement(extracted)
|
||||||
}
|
}
|
||||||
|
|
||||||
return be.storedStack.copyWithCount(extracted)
|
return be.storedStack.copyWithCount(extracted)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ public interface Interface {
|
||||||
|
|
||||||
void send(@NotNull EthernetFrame frame);
|
void send(@NotNull EthernetFrame frame);
|
||||||
|
|
||||||
default void cableDisconnected() {}
|
default void cableDisconnected() {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@ public interface NetworkDevice {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The IP address of this device.
|
* The IP address of this device.
|
||||||
*
|
* <p>
|
||||||
* If a device has not been assigned an address by a DHCP server, it may self-assign a randomly generated one.
|
* If a device has not been assigned an address by a DHCP server, it may self-assign a randomly generated one.
|
||||||
*
|
* <p>
|
||||||
* The address of a network device should never be the broadcast address.
|
* The address of a network device should never be the broadcast address.
|
||||||
*
|
*
|
||||||
* @return The IP address of this device.
|
* @return The IP address of this device.
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package net.shadowfacts.phycon.api;
|
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.Attribute;
|
|
||||||
import alexiil.mc.lib.attributes.Attributes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author shadowfacts
|
|
||||||
*/
|
|
||||||
public class PhyAttributes {
|
|
||||||
|
|
||||||
public static final Attribute<PacketSink> PACKET_SINK = Attributes.create(PacketSink.class);
|
|
||||||
|
|
||||||
}
|
|
|
@ -13,6 +13,7 @@ public interface TerminalSetting {
|
||||||
|
|
||||||
int[] getUV();
|
int[] getUV();
|
||||||
|
|
||||||
@Nullable Text getTooltip();
|
@Nullable
|
||||||
|
Text getTooltip();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,14 @@ public final class IPAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Random ipAddressRandom = new Random();
|
private static final Random ipAddressRandom = new Random();
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static IPAddress random() {
|
public static IPAddress random() {
|
||||||
return random(ipAddressRandom);
|
return random(ipAddressRandom);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
|
private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static IPAddress parse(String s) {
|
public static IPAddress parse(String s) {
|
||||||
Matcher matcher = IP_PATTERN.matcher(s);
|
Matcher matcher = IP_PATTERN.matcher(s);
|
||||||
|
|
|
@ -22,6 +22,7 @@ public final class MACAddress {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Random macAddressRandom = new Random();
|
private static final Random macAddressRandom = new Random();
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static MACAddress random() {
|
public static MACAddress random() {
|
||||||
return random(macAddressRandom);
|
return random(macAddressRandom);
|
||||||
|
@ -61,7 +62,7 @@ public final class MACAddress {
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
MACAddress that = (MACAddress)o;
|
MACAddress that = (MACAddress) o;
|
||||||
return address == that.address;
|
return address == that.address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class MixinHandledScreen {
|
||||||
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V")
|
at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V")
|
||||||
)
|
)
|
||||||
private void drawSlotUnderlay(MatrixStack matrixStack, Slot slot, CallbackInfo ci) {
|
private void drawSlotUnderlay(MatrixStack matrixStack, Slot slot, CallbackInfo ci) {
|
||||||
if ((Object)this instanceof AbstractTerminalScreen<?, ?> self) {
|
if ((Object) this instanceof AbstractTerminalScreen<?, ?> self) {
|
||||||
self.drawSlotUnderlay(matrixStack, slot);
|
self.drawSlotUnderlay(matrixStack, slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ public class MixinHandledScreen {
|
||||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V")
|
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/render/item/ItemRenderer;renderGuiItemOverlay(Lnet/minecraft/client/font/TextRenderer;Lnet/minecraft/item/ItemStack;IILjava/lang/String;)V")
|
||||||
)
|
)
|
||||||
private void drawSlotAmount(ItemRenderer itemRenderer, TextRenderer textRenderer, ItemStack stack, int x, int y, @Nullable String countLabel, MatrixStack matrixStack, Slot slot) {
|
private void drawSlotAmount(ItemRenderer itemRenderer, TextRenderer textRenderer, ItemStack stack, int x, int y, @Nullable String countLabel, MatrixStack matrixStack, Slot slot) {
|
||||||
if ((Object)this instanceof AbstractTerminalScreen<?, ?> self) {
|
if ((Object) this instanceof AbstractTerminalScreen<?, ?> self) {
|
||||||
AbstractTerminalScreenHandler<?> handler = self.getScreenHandler();
|
AbstractTerminalScreenHandler<?> handler = self.getScreenHandler();
|
||||||
if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) {
|
if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) {
|
||||||
self.drawNetworkSlotAmount(stack, x, y);
|
self.drawNetworkSlotAmount(stack, x, y);
|
||||||
|
|
|
@ -7,13 +7,13 @@ import net.shadowfacts.cacao.window.Window
|
||||||
*/
|
*/
|
||||||
interface AbstractCacaoScreen {
|
interface AbstractCacaoScreen {
|
||||||
|
|
||||||
val windows: List<Window>
|
val windows: List<Window>
|
||||||
|
|
||||||
fun <T: Window> addWindow(window: T, index: Int): T
|
fun <T : Window> addWindow(window: T, index: Int): T
|
||||||
fun <T: Window> addWindow(window: T): T
|
fun <T : Window> addWindow(window: T): T
|
||||||
|
|
||||||
fun removeWindow(window: Window)
|
fun removeWindow(window: Window)
|
||||||
|
|
||||||
fun screenWillAppear()
|
fun screenWillAppear()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,177 +20,177 @@ import java.util.*
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
open class CacaoHandledScreen<Handler: ScreenHandler>(
|
open class CacaoHandledScreen<Handler : ScreenHandler>(
|
||||||
handler: Handler,
|
handler: Handler,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
title: Text,
|
title: Text,
|
||||||
): HandledScreen<Handler>(handler, playerInv, title), AbstractCacaoScreen {
|
) : HandledScreen<Handler>(handler, playerInv, title), AbstractCacaoScreen {
|
||||||
|
|
||||||
private val _windows = LinkedList<Window>()
|
private val _windows = LinkedList<Window>()
|
||||||
|
|
||||||
override val windows: List<Window> = _windows
|
override val windows: List<Window> = _windows
|
||||||
|
|
||||||
private var hasAppeared = false
|
private var hasAppeared = false
|
||||||
|
|
||||||
|
|
||||||
override fun <T: Window> addWindow(window: T, index: Int): T {
|
override fun <T : Window> addWindow(window: T, index: Int): T {
|
||||||
if (window is ScreenHandlerWindow && window.screenHandler != handler) {
|
if (window is ScreenHandlerWindow && window.screenHandler != handler) {
|
||||||
throw RuntimeException("Adding ScreenHandlerWindow to CacaoHandledScreen with different screen handler is not supported")
|
throw RuntimeException("Adding ScreenHandlerWindow to CacaoHandledScreen with different screen handler is not supported")
|
||||||
}
|
}
|
||||||
if (hasAppeared) {
|
if (hasAppeared) {
|
||||||
window.viewController.viewWillAppear()
|
window.viewController.viewWillAppear()
|
||||||
}
|
}
|
||||||
|
|
||||||
_windows.add(index, window)
|
_windows.add(index, window)
|
||||||
|
|
||||||
window.screen = this
|
window.screen = this
|
||||||
window.wasAdded()
|
window.wasAdded()
|
||||||
window.resize(width, height)
|
window.resize(width, height)
|
||||||
|
|
||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T : Window> addWindow(window: T): T {
|
override fun <T : Window> addWindow(window: T): T {
|
||||||
return addWindow(window, _windows.size)
|
return addWindow(window, _windows.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeWindow(window: Window) {
|
override fun removeWindow(window: Window) {
|
||||||
_windows.remove(window)
|
_windows.remove(window)
|
||||||
if (windows.isEmpty()) {
|
if (windows.isEmpty()) {
|
||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun screenWillAppear() {
|
override fun screenWillAppear() {
|
||||||
windows.forEach {
|
windows.forEach {
|
||||||
it.viewController.viewWillAppear()
|
it.viewController.viewWillAppear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
windows.forEach {
|
windows.forEach {
|
||||||
it.resize(width, height)
|
it.resize(width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
super.close()
|
super.close()
|
||||||
|
|
||||||
windows.forEach {
|
windows.forEach {
|
||||||
it.viewController.viewWillDisappear()
|
it.viewController.viewWillDisappear()
|
||||||
it.viewController.viewDidDisappear()
|
it.viewController.viewDidDisappear()
|
||||||
|
|
||||||
it.firstResponder = null
|
it.firstResponder = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
|
override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||||
val mouse = Point(mouseX, mouseY)
|
val mouse = Point(mouseX, mouseY)
|
||||||
|
|
||||||
matrixStack.push()
|
matrixStack.push()
|
||||||
matrixStack.translate(0.0, 0.0, -350.0)
|
matrixStack.translate(0.0, 0.0, -350.0)
|
||||||
|
|
||||||
for (i in windows.indices) {
|
for (i in windows.indices) {
|
||||||
val it = windows[i]
|
val it = windows[i]
|
||||||
|
|
||||||
if (i == windows.size - 1) {
|
if (i == windows.size - 1) {
|
||||||
renderBackground(matrixStack)
|
renderBackground(matrixStack)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it is ScreenHandlerWindow) {
|
if (it is ScreenHandlerWindow) {
|
||||||
if (i == windows.size - 1) {
|
if (i == windows.size - 1) {
|
||||||
super.render(matrixStack, mouseX, mouseY, delta)
|
super.render(matrixStack, mouseX, mouseY, delta)
|
||||||
} else {
|
} else {
|
||||||
// if the screen handler window is not the frontmost, we fake the mouse x/y to disable the slot mouseover effect
|
// if the screen handler window is not the frontmost, we fake the mouse x/y to disable the slot mouseover effect
|
||||||
super.render(matrixStack, -1, -1, delta)
|
super.render(matrixStack, -1, -1, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixStack.pop()
|
matrixStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
it.draw(matrixStack, mouse, delta)
|
it.draw(matrixStack, mouse, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
|
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||||
val window = windows.lastOrNull()
|
val window = windows.lastOrNull()
|
||||||
val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
||||||
return if (result == true) {
|
return if (result == true) {
|
||||||
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK)
|
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK)
|
||||||
true
|
true
|
||||||
} else if (window is ScreenHandlerWindow) {
|
} else if (window is ScreenHandlerWindow) {
|
||||||
super.mouseClicked(mouseX, mouseY, button)
|
super.mouseClicked(mouseX, mouseY, button)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||||
val window = windows.lastOrNull()
|
val window = windows.lastOrNull()
|
||||||
val startPoint = Point(mouseX, mouseY)
|
val startPoint = Point(mouseX, mouseY)
|
||||||
val delta = Point(deltaX, deltaY)
|
val delta = Point(deltaX, deltaY)
|
||||||
val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button))
|
val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button))
|
||||||
return if (result == true) {
|
return if (result == true) {
|
||||||
true
|
true
|
||||||
} else if (window is ScreenHandlerWindow) {
|
} else if (window is ScreenHandlerWindow) {
|
||||||
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||||
val window = windows.lastOrNull()
|
val window = windows.lastOrNull()
|
||||||
val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
||||||
return if (result == true) {
|
return if (result == true) {
|
||||||
true
|
true
|
||||||
} else if (window is ScreenHandlerWindow) {
|
} else if (window is ScreenHandlerWindow) {
|
||||||
super.mouseReleased(mouseX, mouseY, button)
|
super.mouseReleased(mouseX, mouseY, button)
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
|
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
|
||||||
val window = windows.lastOrNull()
|
val window = windows.lastOrNull()
|
||||||
val result = window?.mouseScrolled(Point(mouseX, mouseY), amount)
|
val result = window?.mouseScrolled(Point(mouseX, mouseY), amount)
|
||||||
return result == true
|
return result == true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||||
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
|
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
|
||||||
windows.lastOrNull()?.removeFromScreen()
|
windows.lastOrNull()?.removeFromScreen()
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||||
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun charTyped(char: Char, modifiers: Int): Boolean {
|
override fun charTyped(char: Char, modifiers: Int): Boolean {
|
||||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||||
if (findResponder { it.charTyped(char, modifiersSet) }) {
|
if (findResponder { it.charTyped(char, modifiersSet) }) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.charTyped(char, modifiers)
|
return super.charTyped(char, modifiers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun shouldCloseOnEsc(): Boolean {
|
override fun shouldCloseOnEsc(): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,166 +20,167 @@ import java.util.*
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
open class CacaoScreen(title: Text = LiteralText("CacaoScreen")): Screen(title), AbstractCacaoScreen {
|
open class CacaoScreen(title: Text = LiteralText("CacaoScreen")) : Screen(title), AbstractCacaoScreen {
|
||||||
|
|
||||||
// _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods.
|
// _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods.
|
||||||
private val _windows = LinkedList<Window>()
|
private val _windows = LinkedList<Window>()
|
||||||
/**
|
|
||||||
* The list of windows that belong to this screen.
|
|
||||||
*
|
|
||||||
* The window at the end of this list is the active window is the only window that will receive input events.
|
|
||||||
*
|
|
||||||
* This list should never be modified directly, only by using the [addWindow]/[removeWindow] methods.
|
|
||||||
*/
|
|
||||||
override val windows: List<Window> = _windows
|
|
||||||
|
|
||||||
private var hasAppeared = false
|
/**
|
||||||
|
* The list of windows that belong to this screen.
|
||||||
|
*
|
||||||
|
* The window at the end of this list is the active window is the only window that will receive input events.
|
||||||
|
*
|
||||||
|
* This list should never be modified directly, only by using the [addWindow]/[removeWindow] methods.
|
||||||
|
*/
|
||||||
|
override val windows: List<Window> = _windows
|
||||||
|
|
||||||
/**
|
private var hasAppeared = false
|
||||||
* Adds the given window to this screen's window list at the given position.
|
|
||||||
*
|
|
||||||
* @param window The Window to add to this screen.
|
|
||||||
* @param index The index to insert the window into the window list at.
|
|
||||||
* @return The window that was added, as a convenience.
|
|
||||||
*/
|
|
||||||
override fun <T: Window> addWindow(window: T, index: Int): T {
|
|
||||||
if (hasAppeared) {
|
|
||||||
window.viewController.viewWillAppear()
|
|
||||||
}
|
|
||||||
|
|
||||||
_windows.add(index, window)
|
/**
|
||||||
|
* Adds the given window to this screen's window list at the given position.
|
||||||
|
*
|
||||||
|
* @param window The Window to add to this screen.
|
||||||
|
* @param index The index to insert the window into the window list at.
|
||||||
|
* @return The window that was added, as a convenience.
|
||||||
|
*/
|
||||||
|
override fun <T : Window> addWindow(window: T, index: Int): T {
|
||||||
|
if (hasAppeared) {
|
||||||
|
window.viewController.viewWillAppear()
|
||||||
|
}
|
||||||
|
|
||||||
window.screen = this
|
_windows.add(index, window)
|
||||||
window.wasAdded()
|
|
||||||
window.resize(width, height)
|
|
||||||
|
|
||||||
return window
|
window.screen = this
|
||||||
}
|
window.wasAdded()
|
||||||
|
window.resize(width, height)
|
||||||
|
|
||||||
/**
|
return window
|
||||||
* Adds the given window to the end of this screen's window list, making it the active window.
|
}
|
||||||
*/
|
|
||||||
override fun <T : Window> addWindow(window: T): T {
|
|
||||||
return addWindow(window, _windows.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given window from this screen's window list.
|
* Adds the given window to the end of this screen's window list, making it the active window.
|
||||||
*/
|
*/
|
||||||
override fun removeWindow(window: Window) {
|
override fun <T : Window> addWindow(window: T): T {
|
||||||
_windows.remove(window)
|
return addWindow(window, _windows.size)
|
||||||
if (windows.isEmpty()) {
|
}
|
||||||
close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun screenWillAppear() {
|
/**
|
||||||
windows.forEach {
|
* Removes the given window from this screen's window list.
|
||||||
it.viewController.viewWillAppear()
|
*/
|
||||||
}
|
override fun removeWindow(window: Window) {
|
||||||
}
|
_windows.remove(window)
|
||||||
|
if (windows.isEmpty()) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun screenWillAppear() {
|
||||||
super.init()
|
windows.forEach {
|
||||||
|
it.viewController.viewWillAppear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
windows.forEach {
|
override fun init() {
|
||||||
it.resize(width, height)
|
super.init()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
windows.forEach {
|
||||||
super.close()
|
it.resize(width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
windows.forEach {
|
override fun close() {
|
||||||
it.viewController.viewWillDisappear()
|
super.close()
|
||||||
it.viewController.viewDidDisappear()
|
|
||||||
|
|
||||||
// resign the current first responder (if any)
|
windows.forEach {
|
||||||
it.firstResponder = null
|
it.viewController.viewWillDisappear()
|
||||||
}
|
it.viewController.viewDidDisappear()
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
// resign the current first responder (if any)
|
||||||
if (client != null) {
|
it.firstResponder = null
|
||||||
// workaround this.minecraft sometimes being null causing a crash
|
}
|
||||||
renderBackground(matrixStack)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val mouse = Point(mouseX, mouseY)
|
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||||
windows.forEach {
|
if (client != null) {
|
||||||
it.draw(matrixStack, mouse, delta)
|
// workaround this.minecraft sometimes being null causing a crash
|
||||||
}
|
renderBackground(matrixStack)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
val mouse = Point(mouseX, mouseY)
|
||||||
val window = windows.lastOrNull()
|
windows.forEach {
|
||||||
val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
it.draw(matrixStack, mouse, delta)
|
||||||
return if (result == true) {
|
}
|
||||||
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK)
|
}
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||||
val window = windows.lastOrNull()
|
val window = windows.lastOrNull()
|
||||||
val startPoint = Point(mouseX, mouseY)
|
val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
||||||
val delta = Point(deltaX, deltaY)
|
return if (result == true) {
|
||||||
val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button))
|
RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK)
|
||||||
return result == true
|
true
|
||||||
}
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean {
|
||||||
val window = windows.lastOrNull()
|
val window = windows.lastOrNull()
|
||||||
val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
val startPoint = Point(mouseX, mouseY)
|
||||||
return result == true
|
val delta = Point(deltaX, deltaY)
|
||||||
}
|
val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button))
|
||||||
|
return result == true
|
||||||
|
}
|
||||||
|
|
||||||
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
|
override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean {
|
||||||
val window = windows.lastOrNull()
|
val window = windows.lastOrNull()
|
||||||
val result = window?.mouseScrolled(Point(mouseX, mouseY), amount)
|
val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button))
|
||||||
return result == true
|
return result == true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean {
|
||||||
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
|
val window = windows.lastOrNull()
|
||||||
windows.lastOrNull()?.removeFromScreen()
|
val result = window?.mouseScrolled(Point(mouseX, mouseY), amount)
|
||||||
return true
|
return result == true
|
||||||
} else {
|
}
|
||||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
|
||||||
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return super.keyPressed(keyCode, scanCode, modifiers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun keyReleased(i: Int, j: Int, k: Int): Boolean {
|
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||||
return super.keyReleased(i, j, k)
|
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
|
||||||
}
|
windows.lastOrNull()?.removeFromScreen()
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||||
|
if (findResponder { it.keyPressed(keyCode, modifiersSet) }) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun charTyped(char: Char, modifiers: Int): Boolean {
|
override fun keyReleased(i: Int, j: Int, k: Int): Boolean {
|
||||||
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
return super.keyReleased(i, j, k)
|
||||||
if (findResponder { it.charTyped(char, modifiersSet) }) {
|
}
|
||||||
return true
|
|
||||||
}
|
|
||||||
return super.charTyped(char, modifiers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shouldCloseOnEsc(): Boolean {
|
override fun charTyped(char: Char, modifiers: Int): Boolean {
|
||||||
return false
|
val modifiersSet by lazy { KeyModifiers(modifiers) }
|
||||||
}
|
if (findResponder { it.charTyped(char, modifiersSet) }) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.charTyped(char, modifiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldCloseOnEsc(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AbstractCacaoScreen.findResponder(fn: (Responder) -> Boolean): Boolean {
|
fun AbstractCacaoScreen.findResponder(fn: (Responder) -> Boolean): Boolean {
|
||||||
var responder = windows.lastOrNull()?.firstResponder
|
var responder = windows.lastOrNull()?.firstResponder
|
||||||
while (responder != null) {
|
while (responder != null) {
|
||||||
if (fn(responder)) {
|
if (fn(responder)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
responder = responder.nextResponder
|
responder = responder.nextResponder
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,22 +11,22 @@ import no.birkett.kiwi.Variable
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class LayoutVariable(
|
class LayoutVariable(
|
||||||
val view: View?,
|
val view: View?,
|
||||||
val layoutGuide: LayoutGuide?,
|
val layoutGuide: LayoutGuide?,
|
||||||
val property: String,
|
val property: String,
|
||||||
): Variable("LayoutVariable") {
|
) : Variable("LayoutVariable") {
|
||||||
|
|
||||||
constructor(view: View, property: String): this(view, null, property)
|
constructor(view: View, property: String) : this(view, null, property)
|
||||||
constructor(layoutGuide: LayoutGuide, property: String): this(null, layoutGuide, property)
|
constructor(layoutGuide: LayoutGuide, property: String) : this(null, layoutGuide, property)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if ((view == null) == (layoutGuide == null)) {
|
if ((view == null) == (layoutGuide == null)) {
|
||||||
throw RuntimeException("LayoutVariable must be constructed with either a view or layout guide")
|
throw RuntimeException("LayoutVariable must be constructed with either a view or layout guide")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getName() = "${view ?: layoutGuide}.$property"
|
override fun getName() = "${view ?: layoutGuide}.$property"
|
||||||
|
|
||||||
override fun toString() = "LayoutVariable(name=$name, value=$value)"
|
override fun toString() = "LayoutVariable(name=$name, value=$value)"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
# Cacao
|
# Cacao
|
||||||
|
|
||||||
Cacao is a UI framework for Fabric/Minecraft mods based on Apple's [Cocoa](https://en.wikipedia.org/wiki/Cocoa_(API)
|
Cacao is a UI framework for Fabric/Minecraft mods based on Apple's [Cocoa](https://en.wikipedia.org/wiki/Cocoa_(API)
|
||||||
UI toolkit.
|
UI toolkit.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
### Screen
|
### Screen
|
||||||
|
|
||||||
A [CacaoScreen][] is the object that acts as the interface between Minecraft GUI code and the Cacao framework.
|
A [CacaoScreen][] is the object that acts as the interface between Minecraft GUI code and the Cacao framework.
|
||||||
|
|
||||||
The CacaoScreen draws Cacao views on screen and passes Minecraft input events to the appropriate Views. The CacaoScreen
|
The CacaoScreen draws Cacao views on screen and passes Minecraft input events to the appropriate Views. The CacaoScreen
|
||||||
|
@ -12,6 +15,7 @@ owns a group of [Window](#window) objects which are displayed on screen, one on
|
||||||
[CacaoScreen]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt
|
[CacaoScreen]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt
|
||||||
|
|
||||||
### Window
|
### Window
|
||||||
|
|
||||||
A [Window][] object has a root [View Controller](#view-controller) that it displays on screen.
|
A [Window][] object has a root [View Controller](#view-controller) that it displays on screen.
|
||||||
|
|
||||||
The Window occupies the entire screen space and translates events from the screen to the root View Controller's View.
|
The Window occupies the entire screen space and translates events from the screen to the root View Controller's View.
|
||||||
|
@ -21,6 +25,7 @@ view hierarchy.
|
||||||
[Window]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/Window.kt
|
[Window]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/Window.kt
|
||||||
|
|
||||||
### View Controller
|
### View Controller
|
||||||
|
|
||||||
A [ViewController][] object owns a view, receives lifecycle events for it, and is generally used to control the view.
|
A [ViewController][] object owns a view, receives lifecycle events for it, and is generally used to control the view.
|
||||||
|
|
||||||
Each View Controller has a single root [View](#view) which in turn may have subviews.
|
Each View Controller has a single root [View](#view) which in turn may have subviews.
|
||||||
|
@ -28,6 +33,7 @@ Each View Controller has a single root [View](#view) which in turn may have subv
|
||||||
[ViewController]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/viewcontroller/ViewController.kt
|
[ViewController]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/viewcontroller/ViewController.kt
|
||||||
|
|
||||||
### View
|
### View
|
||||||
|
|
||||||
A [View][] object represents a single view on screen. It handles drawing, positioning, and directly handles input.
|
A [View][] object represents a single view on screen. It handles drawing, positioning, and directly handles input.
|
||||||
|
|
||||||
[View]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/view/View.kt
|
[View]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/view/View.kt
|
||||||
|
|
|
@ -12,89 +12,89 @@ import net.shadowfacts.cacao.window.Window
|
||||||
*/
|
*/
|
||||||
interface Responder {
|
interface Responder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The window that this responder is part of.
|
* The window that this responder is part of.
|
||||||
*/
|
*/
|
||||||
val window: Window?
|
val window: Window?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this responder is the first responder of its window.
|
* Whether this responder is the first responder of its window.
|
||||||
*
|
*
|
||||||
* `false` if [window] is null.
|
* `false` if [window] is null.
|
||||||
*
|
*
|
||||||
* @see Window.firstResponder
|
* @see Window.firstResponder
|
||||||
*/
|
*/
|
||||||
val isFirstResponder: Boolean
|
val isFirstResponder: Boolean
|
||||||
get() = window?.firstResponder === this
|
get() = window?.firstResponder === this
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The next responder in the chain after this. The next responder will receive an event if this responder did not
|
* The next responder in the chain after this. The next responder will receive an event if this responder did not
|
||||||
* accept it.
|
* accept it.
|
||||||
*
|
*
|
||||||
* The next responder may be `null` if this responder is at the end of the chain.
|
* The next responder may be `null` if this responder is at the end of the chain.
|
||||||
*/
|
*/
|
||||||
val nextResponder: Responder?
|
val nextResponder: Responder?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes this responder become the window's first responder.
|
* Makes this responder become the window's first responder.
|
||||||
* @throws RuntimeException if [window] is null
|
* @throws RuntimeException if [window] is null
|
||||||
* @see Window.firstResponder
|
* @see Window.firstResponder
|
||||||
*/
|
*/
|
||||||
fun becomeFirstResponder() {
|
fun becomeFirstResponder() {
|
||||||
if (window == null) {
|
if (window == null) {
|
||||||
throw RuntimeException("Cannot become first responder while not in Window")
|
throw RuntimeException("Cannot become first responder while not in Window")
|
||||||
}
|
}
|
||||||
window!!.firstResponder = this
|
window!!.firstResponder = this
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called immediately after this responder has become the window's first responder.
|
* Called immediately after this responder has become the window's first responder.
|
||||||
* @see Window.firstResponder
|
* @see Window.firstResponder
|
||||||
*/
|
*/
|
||||||
fun didBecomeFirstResponder() {}
|
fun didBecomeFirstResponder() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes this object as the window's first responder.
|
* Removes this object as the window's first responder.
|
||||||
* @throws RuntimeException if [window] is null
|
* @throws RuntimeException if [window] is null
|
||||||
* @see Window.firstResponder
|
* @see Window.firstResponder
|
||||||
*/
|
*/
|
||||||
fun resignFirstResponder() {
|
fun resignFirstResponder() {
|
||||||
if (window == null) {
|
if (window == null) {
|
||||||
throw RuntimeException("Cannot resign first responder while not in Window")
|
throw RuntimeException("Cannot resign first responder while not in Window")
|
||||||
}
|
}
|
||||||
window!!.firstResponder = null
|
window!!.firstResponder = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called immediately before this object is removed as the window's first responder.
|
* Called immediately before this object is removed as the window's first responder.
|
||||||
* @see Window.firstResponder
|
* @see Window.firstResponder
|
||||||
*/
|
*/
|
||||||
fun didResignFirstResponder() {}
|
fun didResignFirstResponder() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a character has been typed.
|
* Called when a character has been typed.
|
||||||
*
|
*
|
||||||
* @param char The character that was typed.
|
* @param char The character that was typed.
|
||||||
* @param modifiers The key modifiers that were held down when the character was typed.
|
* @param modifiers The key modifiers that were held down when the character was typed.
|
||||||
* @return Whether this responder accepted the event. If `true`, it will not be passed to the next responder.
|
* @return Whether this responder accepted the event. If `true`, it will not be passed to the next responder.
|
||||||
*/
|
*/
|
||||||
fun charTyped(char: Char, modifiers: KeyModifiers): Boolean {
|
fun charTyped(char: Char, modifiers: KeyModifiers): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a keyboard key is pressed.
|
* Called when a keyboard key is pressed.
|
||||||
*
|
*
|
||||||
* If the pressed key is a typed character, [charTyped] will also be called. The order in which the methods are
|
* If the pressed key is a typed character, [charTyped] will also be called. The order in which the methods are
|
||||||
* invoked is undefined and should not be relied upon.
|
* invoked is undefined and should not be relied upon.
|
||||||
*
|
*
|
||||||
* @param keyCode The integer code of the key that was pressed.
|
* @param keyCode The integer code of the key that was pressed.
|
||||||
* @param modifiers The key modifiers that were held down when the character was typed.
|
* @param modifiers The key modifiers that were held down when the character was typed.
|
||||||
* @return Whether this responder accepted the event. If `true`, it will not be passed to the next responder.
|
* @return Whether this responder accepted the event. If `true`, it will not be passed to the next responder.
|
||||||
* @see org.lwjgl.glfw.GLFW for key code constants
|
* @see org.lwjgl.glfw.GLFW for key code constants
|
||||||
*/
|
*/
|
||||||
fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean {
|
fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ package net.shadowfacts.cacao.geometry
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
enum class Axis {
|
enum class Axis {
|
||||||
HORIZONTAL, VERTICAL;
|
HORIZONTAL, VERTICAL;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the axis that is perpendicular to this one.
|
* Gets the axis that is perpendicular to this one.
|
||||||
*/
|
*/
|
||||||
val perpendicular: Axis
|
val perpendicular: Axis
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
HORIZONTAL -> VERTICAL
|
HORIZONTAL -> VERTICAL
|
||||||
VERTICAL -> HORIZONTAL
|
VERTICAL -> HORIZONTAL
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,16 +6,18 @@ package net.shadowfacts.cacao.geometry
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
enum class AxisPosition {
|
enum class AxisPosition {
|
||||||
/**
|
/**
|
||||||
* Top for vertical, left for horizontal.
|
* Top for vertical, left for horizontal.
|
||||||
*/
|
*/
|
||||||
LEADING,
|
LEADING,
|
||||||
/**
|
|
||||||
* Center X/Y.
|
/**
|
||||||
*/
|
* Center X/Y.
|
||||||
CENTER,
|
*/
|
||||||
/**
|
CENTER,
|
||||||
* Bottom for vertical, right for horizontal.
|
|
||||||
*/
|
/**
|
||||||
TRAILING;
|
* Bottom for vertical, right for horizontal.
|
||||||
|
*/
|
||||||
|
TRAILING;
|
||||||
}
|
}
|
|
@ -10,40 +10,40 @@ import kotlin.math.pow
|
||||||
*/
|
*/
|
||||||
data class BezierCurve(private val points: Array<Point>) {
|
data class BezierCurve(private val points: Array<Point>) {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (points.size != 4) {
|
if (points.size != 4) {
|
||||||
throw RuntimeException("Cubic bezier curve must have exactly four points")
|
throw RuntimeException("Cubic bezier curve must have exactly four points")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun point(time: Double): Point {
|
fun point(time: Double): Point {
|
||||||
val x = coordinate(time, Axis.HORIZONTAL)
|
val x = coordinate(time, Axis.HORIZONTAL)
|
||||||
val y = coordinate(time, Axis.VERTICAL)
|
val y = coordinate(time, Axis.VERTICAL)
|
||||||
return Point(x, y)
|
return Point(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun coordinate(t: Double, axis: Axis): Double {
|
private fun coordinate(t: Double, axis: Axis): Double {
|
||||||
// B(t)=(1-t)^3*p0+3(1-t)^2*t*p1+3(1-t)*t^2*p2+t^3*p3
|
// B(t)=(1-t)^3*p0+3(1-t)^2*t*p1+3(1-t)*t^2*p2+t^3*p3
|
||||||
val p0 = points[0][axis]
|
val p0 = points[0][axis]
|
||||||
val p1 = points[1][axis]
|
val p1 = points[1][axis]
|
||||||
val p2 = points[2][axis]
|
val p2 = points[2][axis]
|
||||||
val p3 = points[3][axis]
|
val p3 = points[3][axis]
|
||||||
return ((1 - t).pow(3) * p0) + (3 * (1 - t).pow(2) * t * p1) + (3 * (1 - t) * t.pow(2) * p2) + (t.pow(3) * p3)
|
return ((1 - t).pow(3) * p0) + (3 * (1 - t).pow(2) * t * p1) + (3 * (1 - t) * t.pow(2) * p2) + (t.pow(3) * p3)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
other as BezierCurve
|
other as BezierCurve
|
||||||
|
|
||||||
if (!points.contentEquals(other.points)) return false
|
if (!points.contentEquals(other.points)) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return points.contentHashCode()
|
return points.contentHashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,25 +7,25 @@ package net.shadowfacts.cacao.geometry
|
||||||
*/
|
*/
|
||||||
data class Point(val x: Double, val y: Double) {
|
data class Point(val x: Double, val y: Double) {
|
||||||
|
|
||||||
constructor(x: Int, y: Int): this(x.toDouble(), y.toDouble())
|
constructor(x: Int, y: Int) : this(x.toDouble(), y.toDouble())
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ORIGIN = Point(0.0, 0.0)
|
val ORIGIN = Point(0.0, 0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun plus(other: Point): Point {
|
operator fun plus(other: Point): Point {
|
||||||
return Point(x + other.x, y + other.y)
|
return Point(x + other.x, y + other.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun minus(other: Point): Point {
|
operator fun minus(other: Point): Point {
|
||||||
return Point(x - other.x, y - other.y)
|
return Point(x - other.x, y - other.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(axis: Axis): Double {
|
operator fun get(axis: Axis): Double {
|
||||||
return when (axis) {
|
return when (axis) {
|
||||||
Axis.HORIZONTAL -> x
|
Axis.HORIZONTAL -> x
|
||||||
Axis.VERTICAL -> y
|
Axis.VERTICAL -> y
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,35 +7,35 @@ package net.shadowfacts.cacao.geometry
|
||||||
*/
|
*/
|
||||||
data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) {
|
data class Rect(val left: Double, val top: Double, val width: Double, val height: Double) {
|
||||||
|
|
||||||
constructor(origin: Point, size: Size): this(origin.x, origin.y, size.width, size.height)
|
constructor(origin: Point, size: Size) : this(origin.x, origin.y, size.width, size.height)
|
||||||
|
|
||||||
val right: Double by lazy {
|
val right: Double by lazy {
|
||||||
left + width
|
left + width
|
||||||
}
|
}
|
||||||
val bottom: Double by lazy {
|
val bottom: Double by lazy {
|
||||||
top + height
|
top + height
|
||||||
}
|
}
|
||||||
|
|
||||||
val midX: Double by lazy {
|
val midX: Double by lazy {
|
||||||
left + width / 2
|
left + width / 2
|
||||||
}
|
}
|
||||||
val midY: Double by lazy {
|
val midY: Double by lazy {
|
||||||
top + height / 2
|
top + height / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
val origin: Point by lazy {
|
val origin: Point by lazy {
|
||||||
Point(left, top)
|
Point(left, top)
|
||||||
}
|
}
|
||||||
val center: Point by lazy {
|
val center: Point by lazy {
|
||||||
Point(midX, midY)
|
Point(midX, midY)
|
||||||
}
|
}
|
||||||
|
|
||||||
val size: Size by lazy {
|
val size: Size by lazy {
|
||||||
Size(width, height)
|
Size(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun contains(point: Point): Boolean {
|
operator fun contains(point: Point): Boolean {
|
||||||
return point.x >= left && point.x < right && point.y >= top && point.y < bottom
|
return point.x >= left && point.x < right && point.y >= top && point.y < bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,27 +11,27 @@ package net.shadowfacts.cacao.util
|
||||||
*/
|
*/
|
||||||
data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 255) {
|
data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int = 255) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a color from the packed RGB color.
|
* Constructs a color from the packed RGB color.
|
||||||
*/
|
*/
|
||||||
constructor(rgb: Int, alpha: Int = 255): this(rgb shr 16, (rgb shr 8) and 255, rgb and 255, alpha)
|
constructor(rgb: Int, alpha: Int = 255) : this(rgb shr 16, (rgb shr 8) and 255, rgb and 255, alpha)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ARGB packed representation of this color.
|
* The ARGB packed representation of this color.
|
||||||
*/
|
*/
|
||||||
val argb: Int
|
val argb: Int
|
||||||
get() = ((alpha and 255) shl 24) or ((red and 255) shl 16) or ((green and 255) shl 8) or (blue and 255)
|
get() = ((alpha and 255) shl 24) or ((red and 255) shl 16) or ((green and 255) shl 8) or (blue and 255)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val CLEAR = Color(0, alpha = 0)
|
val CLEAR = Color(0, alpha = 0)
|
||||||
val WHITE = Color(0xffffff)
|
val WHITE = Color(0xffffff)
|
||||||
val BLACK = Color(0)
|
val BLACK = Color(0)
|
||||||
val RED = Color(0xff0000)
|
val RED = Color(0xff0000)
|
||||||
val GREEN = Color(0x00ff00)
|
val GREEN = Color(0x00ff00)
|
||||||
val BLUE = Color(0x0000ff)
|
val BLUE = Color(0x0000ff)
|
||||||
val MAGENTA = Color(0xfc46e4)
|
val MAGENTA = Color(0xfc46e4)
|
||||||
|
|
||||||
val TEXT = Color(0x404040)
|
val TEXT = Color(0x404040)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,16 @@ package net.shadowfacts.cacao.util
|
||||||
*/
|
*/
|
||||||
object EnumHelper {
|
object EnumHelper {
|
||||||
|
|
||||||
fun <E: Enum<E>> next(value: E): E {
|
fun <E : Enum<E>> next(value: E): E {
|
||||||
val constants = value.declaringClass.enumConstants
|
val constants = value.declaringClass.enumConstants
|
||||||
val index = constants.indexOf(value) + 1
|
val index = constants.indexOf(value) + 1
|
||||||
return if (index < constants.size) constants[index] else constants.first()
|
return if (index < constants.size) constants[index] else constants.first()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <E: Enum<E>> previous(value: E): E {
|
fun <E : Enum<E>> previous(value: E): E {
|
||||||
val constants = value.declaringClass.enumConstants
|
val constants = value.declaringClass.enumConstants
|
||||||
val index = constants.indexOf(value) - 1
|
val index = constants.indexOf(value) - 1
|
||||||
return if (index >= 0) constants[index] else constants.last()
|
return if (index >= 0) constants[index] else constants.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -10,26 +10,26 @@ import org.lwjgl.glfw.GLFW
|
||||||
*/
|
*/
|
||||||
class KeyModifiers(val value: Int) {
|
class KeyModifiers(val value: Int) {
|
||||||
|
|
||||||
val shift: Boolean
|
val shift: Boolean
|
||||||
get() = this[GLFW.GLFW_MOD_SHIFT]
|
get() = this[GLFW.GLFW_MOD_SHIFT]
|
||||||
|
|
||||||
val control: Boolean
|
val control: Boolean
|
||||||
get() = this[GLFW.GLFW_MOD_CONTROL]
|
get() = this[GLFW.GLFW_MOD_CONTROL]
|
||||||
|
|
||||||
val alt: Boolean
|
val alt: Boolean
|
||||||
get() = this[GLFW.GLFW_MOD_ALT]
|
get() = this[GLFW.GLFW_MOD_ALT]
|
||||||
|
|
||||||
val command: Boolean
|
val command: Boolean
|
||||||
get() = this[GLFW.GLFW_MOD_SUPER]
|
get() = this[GLFW.GLFW_MOD_SUPER]
|
||||||
|
|
||||||
val capsLock: Boolean
|
val capsLock: Boolean
|
||||||
get() = this[GLFW.GLFW_MOD_CAPS_LOCK]
|
get() = this[GLFW.GLFW_MOD_CAPS_LOCK]
|
||||||
|
|
||||||
val numLock: Boolean
|
val numLock: Boolean
|
||||||
get() = this[GLFW.GLFW_MOD_NUM_LOCK]
|
get() = this[GLFW.GLFW_MOD_NUM_LOCK]
|
||||||
|
|
||||||
private operator fun get(mod: Int): Boolean {
|
private operator fun get(mod: Int): Boolean {
|
||||||
return (value and mod) == mod
|
return (value and mod) == mod
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,19 +15,24 @@ import net.shadowfacts.cacao.view.View
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class LayoutGuide(
|
class LayoutGuide(
|
||||||
val owningView: View,
|
val owningView: View,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val leftAnchor: LayoutVariable = LayoutVariable(this, "left")
|
val leftAnchor: LayoutVariable = LayoutVariable(this, "left")
|
||||||
val rightAnchor: LayoutVariable = LayoutVariable(this, "right")
|
val rightAnchor: LayoutVariable = LayoutVariable(this, "right")
|
||||||
val topAnchor: LayoutVariable = LayoutVariable(this, "top")
|
val topAnchor: LayoutVariable = LayoutVariable(this, "top")
|
||||||
val bottomAnchor: LayoutVariable = LayoutVariable(this, "bottom")
|
val bottomAnchor: LayoutVariable = LayoutVariable(this, "bottom")
|
||||||
val widthAnchor: LayoutVariable = LayoutVariable(this, "width")
|
val widthAnchor: LayoutVariable = LayoutVariable(this, "width")
|
||||||
val heightAnchor: LayoutVariable = LayoutVariable(this, "height")
|
val heightAnchor: LayoutVariable = LayoutVariable(this, "height")
|
||||||
val centerXAnchor: LayoutVariable = LayoutVariable(this, "centerX")
|
val centerXAnchor: LayoutVariable = LayoutVariable(this, "centerX")
|
||||||
val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY")
|
val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY")
|
||||||
|
|
||||||
val frame: Rect
|
val frame: Rect
|
||||||
get() = Rect(leftAnchor.value - owningView.leftAnchor.value, topAnchor.value - owningView.topAnchor.value, widthAnchor.value, heightAnchor.value)
|
get() = Rect(
|
||||||
|
leftAnchor.value - owningView.leftAnchor.value,
|
||||||
|
topAnchor.value - owningView.topAnchor.value,
|
||||||
|
widthAnchor.value,
|
||||||
|
heightAnchor.value
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,43 +14,43 @@ import kotlin.NoSuchElementException
|
||||||
*/
|
*/
|
||||||
object LowestCommonAncestor {
|
object LowestCommonAncestor {
|
||||||
|
|
||||||
fun <Node> find(node1: Node, node2: Node, parent: Node.() -> Node?): Node? {
|
fun <Node> find(node1: Node, node2: Node, parent: Node.() -> Node?): Node? {
|
||||||
@Suppress("NAME_SHADOWING") var node1: Node? = node1
|
@Suppress("NAME_SHADOWING") var node1: Node? = node1
|
||||||
@Suppress("NAME_SHADOWING") var node2: Node? = node2
|
@Suppress("NAME_SHADOWING") var node2: Node? = node2
|
||||||
|
|
||||||
val parent1 = LinkedList<Node>()
|
val parent1 = LinkedList<Node>()
|
||||||
while (node1 != null) {
|
while (node1 != null) {
|
||||||
parent1.push(node1)
|
parent1.push(node1)
|
||||||
node1 = node1.parent()
|
node1 = node1.parent()
|
||||||
}
|
}
|
||||||
|
|
||||||
val parent2 = LinkedList<Node>()
|
val parent2 = LinkedList<Node>()
|
||||||
while (node2 != null) {
|
while (node2 != null) {
|
||||||
parent2.push(node2)
|
parent2.push(node2)
|
||||||
node2 = node2.parent()
|
node2 = node2.parent()
|
||||||
}
|
}
|
||||||
|
|
||||||
// paths don't converge on the same root element
|
// paths don't converge on the same root element
|
||||||
if (parent1.first != parent2.first) {
|
if (parent1.first != parent2.first) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldNode: Node? = null
|
var oldNode: Node? = null
|
||||||
while (node1 == node2 && parent1.isNotEmpty() && parent2.isNotEmpty()) {
|
while (node1 == node2 && parent1.isNotEmpty() && parent2.isNotEmpty()) {
|
||||||
oldNode = node1
|
oldNode = node1
|
||||||
node1 = parent1.popOrNull()
|
node1 = parent1.popOrNull()
|
||||||
node2 = parent2.popOrNull()
|
node2 = parent2.popOrNull()
|
||||||
}
|
}
|
||||||
return if (node1 == node2) node1!!
|
return if (node1 == node2) node1!!
|
||||||
else oldNode!!
|
else oldNode!!
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> LinkedList<T>.popOrNull(): T? {
|
private fun <T> LinkedList<T>.popOrNull(): T? {
|
||||||
return try {
|
return try {
|
||||||
pop()
|
pop()
|
||||||
} catch (e: NoSuchElementException) {
|
} catch (e: NoSuchElementException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,16 +4,16 @@ package net.shadowfacts.cacao.util
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
enum class MouseButton {
|
enum class MouseButton {
|
||||||
LEFT, RIGHT, MIDDLE, UNKNOWN;
|
LEFT, RIGHT, MIDDLE, UNKNOWN;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromMC(button: Int): MouseButton {
|
fun fromMC(button: Int): MouseButton {
|
||||||
return when (button) {
|
return when (button) {
|
||||||
0 -> LEFT
|
0 -> LEFT
|
||||||
1 -> RIGHT
|
1 -> RIGHT
|
||||||
2 -> MIDDLE
|
2 -> MIDDLE
|
||||||
else -> UNKNOWN
|
else -> UNKNOWN
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -23,107 +23,153 @@ import kotlin.math.roundToInt
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object RenderHelper: DrawableHelper() {
|
object RenderHelper : DrawableHelper() {
|
||||||
|
|
||||||
val disabled = (System.getProperty("cacao.drawing.disabled") ?: "false").toBoolean()
|
val disabled = (System.getProperty("cacao.drawing.disabled") ?: "false").toBoolean()
|
||||||
|
|
||||||
// TODO: find a better place for this
|
// TODO: find a better place for this
|
||||||
fun playSound(event: SoundEvent) {
|
fun playSound(event: SoundEvent) {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(event, 1f))
|
MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(event, 1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a solid [rect] filled with the given [color].
|
* Draws a solid [rect] filled with the given [color].
|
||||||
*/
|
*/
|
||||||
fun fill(matrixStack: MatrixStack, rect: Rect, color: Color) {
|
fun fill(matrixStack: MatrixStack, rect: Rect, color: Color) {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
fill(matrixStack, rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt(), color.argb)
|
fill(matrixStack, rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt(), color.argb)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binds and draws the given [texture] filling the [rect].
|
* Binds and draws the given [texture] filling the [rect].
|
||||||
*/
|
*/
|
||||||
fun draw(matrixStack: MatrixStack, rect: Rect, texture: Texture) {
|
fun draw(matrixStack: MatrixStack, rect: Rect, texture: Texture) {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
||||||
RenderSystem.setShaderTexture(0, texture.location)
|
RenderSystem.setShaderTexture(0, texture.location)
|
||||||
draw(matrixStack, rect.left, rect.top, texture.u, texture.v, rect.width, rect.height, texture.width, texture.height)
|
draw(
|
||||||
}
|
matrixStack,
|
||||||
|
rect.left,
|
||||||
|
rect.top,
|
||||||
|
texture.u,
|
||||||
|
texture.v,
|
||||||
|
rect.width,
|
||||||
|
rect.height,
|
||||||
|
texture.width,
|
||||||
|
texture.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun drawLine(start: Point, end: Point, z: Double, width: Float, color: Color) {
|
fun drawLine(start: Point, end: Point, z: Double, width: Float, color: Color) {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
|
|
||||||
RenderSystem.lineWidth(width)
|
RenderSystem.lineWidth(width)
|
||||||
val tessellator = Tessellator.getInstance()
|
RenderSystem.enableBlend()
|
||||||
val buffer = tessellator.buffer
|
RenderSystem.disableTexture()
|
||||||
buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.POSITION_COLOR)
|
RenderSystem.defaultBlendFunc()
|
||||||
buffer.vertex(start.x, start.y, z).color(color).next()
|
RenderSystem.setShader(GameRenderer::getPositionColorShader)
|
||||||
buffer.vertex(end.x, end.y, z).color(color).next()
|
val tessellator = Tessellator.getInstance()
|
||||||
tessellator.draw()
|
val buffer = tessellator.buffer
|
||||||
}
|
buffer.begin(VertexFormat.DrawMode.LINES, VertexFormats.POSITION_COLOR)
|
||||||
|
buffer.vertex(start.x, start.y, z).color(color).next()
|
||||||
|
buffer.vertex(end.x, end.y, z).color(color).next()
|
||||||
|
tessellator.draw()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the bound texture with the given screen and texture position and size.
|
* Draws the bound texture with the given screen and texture position and size.
|
||||||
*/
|
*/
|
||||||
fun draw(matrixStack: MatrixStack, x: Double, y: Double, u: Int, v: Int, width: Double, height: Double, textureWidth: Int, textureHeight: Int) {
|
fun draw(
|
||||||
if (disabled) return
|
matrixStack: MatrixStack,
|
||||||
val uStart = u.toFloat() / textureWidth
|
x: Double,
|
||||||
val uEnd = (u + width).toFloat() / textureWidth
|
y: Double,
|
||||||
val vStart = v.toFloat() / textureHeight
|
u: Int,
|
||||||
val vEnd = (v + height).toFloat() / textureHeight
|
v: Int,
|
||||||
drawTexturedQuad(matrixStack.peek().positionMatrix, x, x + width, y, y + height, 0.0, uStart, uEnd, vStart, vEnd)
|
width: Double,
|
||||||
}
|
height: Double,
|
||||||
|
textureWidth: Int,
|
||||||
|
textureHeight: Int
|
||||||
|
) {
|
||||||
|
if (disabled) return
|
||||||
|
val uStart = u.toFloat() / textureWidth
|
||||||
|
val uEnd = (u + width).toFloat() / textureWidth
|
||||||
|
val vStart = v.toFloat() / textureHeight
|
||||||
|
val vEnd = (v + height).toFloat() / textureHeight
|
||||||
|
drawTexturedQuad(
|
||||||
|
matrixStack.peek().positionMatrix,
|
||||||
|
x,
|
||||||
|
x + width,
|
||||||
|
y,
|
||||||
|
y + height,
|
||||||
|
0.0,
|
||||||
|
uStart,
|
||||||
|
uEnd,
|
||||||
|
vStart,
|
||||||
|
vEnd
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Copied from net.minecraft.client.gui.DrawableHelper
|
// Copied from net.minecraft.client.gui.DrawableHelper
|
||||||
// TODO: use an access transformer to just call minecraft's impl
|
// TODO: use an access transformer to just call minecraft's impl
|
||||||
private fun drawTexturedQuad(matrix: Matrix4f, x0: Double, x1: Double, y0: Double, y1: Double, z: Double, u0: Float, u1: Float, v0: Float, v1: Float) {
|
private fun drawTexturedQuad(
|
||||||
val bufferBuilder = Tessellator.getInstance().buffer
|
matrix: Matrix4f,
|
||||||
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE)
|
x0: Double,
|
||||||
bufferBuilder.vertex(matrix, x0.toFloat(), y1.toFloat(), z.toFloat()).texture(u0, v1).next()
|
x1: Double,
|
||||||
bufferBuilder.vertex(matrix, x1.toFloat(), y1.toFloat(), z.toFloat()).texture(u1, v1).next()
|
y0: Double,
|
||||||
bufferBuilder.vertex(matrix, x1.toFloat(), y0.toFloat(), z.toFloat()).texture(u1, v0).next()
|
y1: Double,
|
||||||
bufferBuilder.vertex(matrix, x0.toFloat(), y0.toFloat(), z.toFloat()).texture(u0, v0).next()
|
z: Double,
|
||||||
bufferBuilder.end()
|
u0: Float,
|
||||||
BufferRenderer.draw(bufferBuilder)
|
u1: Float,
|
||||||
}
|
v0: Float,
|
||||||
|
v1: Float
|
||||||
|
) {
|
||||||
|
val bufferBuilder = Tessellator.getInstance().buffer
|
||||||
|
bufferBuilder.begin(VertexFormat.DrawMode.QUADS, VertexFormats.POSITION_TEXTURE)
|
||||||
|
bufferBuilder.vertex(matrix, x0.toFloat(), y1.toFloat(), z.toFloat()).texture(u0, v1).next()
|
||||||
|
bufferBuilder.vertex(matrix, x1.toFloat(), y1.toFloat(), z.toFloat()).texture(u1, v1).next()
|
||||||
|
bufferBuilder.vertex(matrix, x1.toFloat(), y0.toFloat(), z.toFloat()).texture(u1, v0).next()
|
||||||
|
bufferBuilder.vertex(matrix, x0.toFloat(), y0.toFloat(), z.toFloat()).texture(u0, v0).next()
|
||||||
|
bufferBuilder.end()
|
||||||
|
BufferRenderer.draw(bufferBuilder)
|
||||||
|
}
|
||||||
|
|
||||||
fun drawTooltip(matrixStack: MatrixStack, text: Text, mouse: Point) {
|
fun drawTooltip(matrixStack: MatrixStack, text: Text, mouse: Point) {
|
||||||
drawTooltip(matrixStack, listOf(text.asOrderedText()), mouse)
|
drawTooltip(matrixStack, listOf(text.asOrderedText()), mouse)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun drawTooltip(matrixStack: MatrixStack, texts: List<Text>, mouse: Point) {
|
fun drawTooltip(matrixStack: MatrixStack, texts: List<Text>, mouse: Point) {
|
||||||
drawTooltip(matrixStack, texts.map(Text::asOrderedText), mouse)
|
drawTooltip(matrixStack, texts.map(Text::asOrderedText), mouse)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val dummyScreen = object: Screen(LiteralText("")) {
|
private val dummyScreen = object : Screen(LiteralText("")) {
|
||||||
init {
|
init {
|
||||||
textRenderer = MinecraftClient.getInstance().textRenderer
|
textRenderer = MinecraftClient.getInstance().textRenderer
|
||||||
itemRenderer = MinecraftClient.getInstance().itemRenderer
|
itemRenderer = MinecraftClient.getInstance().itemRenderer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmName("drawOrderedTooltip")
|
@JvmName("drawOrderedTooltip")
|
||||||
fun drawTooltip(matrixStack: MatrixStack, texts: List<OrderedText>, mouse: Point) {
|
fun drawTooltip(matrixStack: MatrixStack, texts: List<OrderedText>, mouse: Point) {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
if (texts.isEmpty()) return
|
if (texts.isEmpty()) return
|
||||||
|
|
||||||
val client = MinecraftClient.getInstance()
|
val client = MinecraftClient.getInstance()
|
||||||
dummyScreen.width = client.window.scaledWidth
|
dummyScreen.width = client.window.scaledWidth
|
||||||
dummyScreen.height = client.window.scaledHeight
|
dummyScreen.height = client.window.scaledHeight
|
||||||
dummyScreen.renderOrderedTooltip(matrixStack, texts, mouse.x.roundToInt(), mouse.y.roundToInt())
|
dummyScreen.renderOrderedTooltip(matrixStack, texts, mouse.x.roundToInt(), mouse.y.roundToInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.lwjgl.opengl.GL11.glColor4f
|
* @see org.lwjgl.opengl.GL11.glColor4f
|
||||||
*/
|
*/
|
||||||
fun color(r: Float, g: Float, b: Float, alpha: Float) {
|
fun color(r: Float, g: Float, b: Float, alpha: Float) {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
RenderSystem.setShaderColor(r, g, b, alpha)
|
RenderSystem.setShaderColor(r, g, b, alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun VertexConsumer.color(color: Color): VertexConsumer {
|
private fun VertexConsumer.color(color: Color): VertexConsumer {
|
||||||
return color(color.red, color.green, color.blue, color.alpha)
|
return color(color.red, color.green, color.blue, color.alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,5 +10,5 @@ import no.birkett.kiwi.Variable
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
fun Constraint.getVariables(): List<Variable> {
|
fun Constraint.getVariables(): List<Variable> {
|
||||||
return expression.terms.map(Term::getVariable)
|
return expression.terms.map(Term::getVariable)
|
||||||
}
|
}
|
|
@ -5,20 +5,20 @@ import kotlin.reflect.KProperty
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class ObservableLateInitProperty<T: Any>(val observer: (T) -> Unit) {
|
class ObservableLateInitProperty<T : Any>(val observer: (T) -> Unit) {
|
||||||
|
|
||||||
lateinit var storage: T
|
lateinit var storage: T
|
||||||
|
|
||||||
val isInitialized: Boolean
|
val isInitialized: Boolean
|
||||||
get() = this::storage.isInitialized
|
get() = this::storage.isInitialized
|
||||||
|
|
||||||
operator fun getValue(thisRef: Any, property: KProperty<*>): T {
|
operator fun getValue(thisRef: Any, property: KProperty<*>): T {
|
||||||
return storage
|
return storage
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
|
operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
|
||||||
storage = value
|
storage = value
|
||||||
observer(value)
|
observer(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,14 +7,14 @@ import kotlin.reflect.KProperty
|
||||||
*/
|
*/
|
||||||
class ObservableLazyProperty<Value>(val create: () -> Value, val onCreate: () -> Unit) {
|
class ObservableLazyProperty<Value>(val create: () -> Value, val onCreate: () -> Unit) {
|
||||||
|
|
||||||
var storage: Value? = null
|
var storage: Value? = null
|
||||||
|
|
||||||
operator fun getValue(thisRef: Any, property: KProperty<*>): Value {
|
operator fun getValue(thisRef: Any, property: KProperty<*>): Value {
|
||||||
if (storage == null) {
|
if (storage == null) {
|
||||||
storage = create()
|
storage = create()
|
||||||
onCreate()
|
onCreate()
|
||||||
}
|
}
|
||||||
return storage!!
|
return storage!!
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -6,19 +6,19 @@ import kotlin.reflect.KProperty
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class ResettableLazyProperty<Value>(val initializer: () -> Value) {
|
class ResettableLazyProperty<Value>(val initializer: () -> Value) {
|
||||||
var value: Value? = null
|
var value: Value? = null
|
||||||
|
|
||||||
val isInitialized: Boolean
|
val isInitialized: Boolean
|
||||||
get() = value != null
|
get() = value != null
|
||||||
|
|
||||||
operator fun getValue(thisRef: Any, property: KProperty<*>): Value {
|
operator fun getValue(thisRef: Any, property: KProperty<*>): Value {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = initializer()
|
value = initializer()
|
||||||
}
|
}
|
||||||
return value!!
|
return value!!
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
value = null
|
value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,47 +15,53 @@ import net.minecraft.util.Identifier
|
||||||
* @param centerWidth The width of the center patch.
|
* @param centerWidth The width of the center patch.
|
||||||
* @param centerHeight The height of the center patch.
|
* @param centerHeight The height of the center patch.
|
||||||
*/
|
*/
|
||||||
data class NinePatchTexture(val texture: Texture, val cornerWidth: Int, val cornerHeight: Int, val centerWidth: Int, val centerHeight: Int) {
|
data class NinePatchTexture(
|
||||||
|
val texture: Texture,
|
||||||
|
val cornerWidth: Int,
|
||||||
|
val cornerHeight: Int,
|
||||||
|
val centerWidth: Int,
|
||||||
|
val centerHeight: Int
|
||||||
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val PANEL_BG = NinePatchTexture(Texture(Identifier("textures/gui/demo_background.png"), 0, 0), 5, 5, 238, 156)
|
val PANEL_BG = NinePatchTexture(Texture(Identifier("textures/gui/demo_background.png"), 0, 0), 5, 5, 238, 156)
|
||||||
|
|
||||||
val BUTTON_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 66), 3, 3, 194, 14)
|
val BUTTON_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 66), 3, 3, 194, 14)
|
||||||
val BUTTON_HOVERED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 86), 3, 3, 194, 14)
|
val BUTTON_HOVERED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 86), 3, 3, 194, 14)
|
||||||
val BUTTON_DISABLED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 46), 3, 3, 194, 14)
|
val BUTTON_DISABLED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 46), 3, 3, 194, 14)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Corners
|
// Corners
|
||||||
val topLeft by lazy {
|
val topLeft by lazy {
|
||||||
texture
|
texture
|
||||||
}
|
}
|
||||||
val topRight by lazy {
|
val topRight by lazy {
|
||||||
Texture(texture.location, texture.u + cornerWidth + centerWidth, texture.v, texture.width, texture.height)
|
Texture(texture.location, texture.u + cornerWidth + centerWidth, texture.v, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
val bottomLeft by lazy {
|
val bottomLeft by lazy {
|
||||||
Texture(texture.location, texture.u, texture.v + cornerHeight + centerHeight, texture.width, texture.height)
|
Texture(texture.location, texture.u, texture.v + cornerHeight + centerHeight, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
val bottomRight by lazy {
|
val bottomRight by lazy {
|
||||||
Texture(texture.location, topRight.u, bottomLeft.v, texture.width, texture.height)
|
Texture(texture.location, topRight.u, bottomLeft.v, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edges
|
// Edges
|
||||||
val topMiddle by lazy {
|
val topMiddle by lazy {
|
||||||
Texture(texture.location, texture.u + cornerWidth, texture.v, texture.width, texture.height)
|
Texture(texture.location, texture.u + cornerWidth, texture.v, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
val bottomMiddle by lazy {
|
val bottomMiddle by lazy {
|
||||||
Texture(texture.location, topMiddle.u, bottomLeft.v, texture.width, texture.height)
|
Texture(texture.location, topMiddle.u, bottomLeft.v, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
val leftMiddle by lazy {
|
val leftMiddle by lazy {
|
||||||
Texture(texture.location, texture.u, texture.v + cornerHeight, texture.width, texture.height)
|
Texture(texture.location, texture.u, texture.v + cornerHeight, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
val rightMiddle by lazy {
|
val rightMiddle by lazy {
|
||||||
Texture(texture.location, topRight.u, leftMiddle.v, texture.width, texture.height)
|
Texture(texture.location, topRight.u, leftMiddle.v, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Center
|
// Center
|
||||||
val center by lazy {
|
val center by lazy {
|
||||||
Texture(texture.location, texture.u + cornerWidth, texture.v + cornerHeight, texture.width, texture.height)
|
Texture(texture.location, texture.u + cornerWidth, texture.v + cornerHeight, texture.width, texture.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,30 +9,30 @@ import net.shadowfacts.cacao.util.RenderHelper
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class BezierCurveView(val curve: BezierCurve): View() {
|
class BezierCurveView(val curve: BezierCurve) : View() {
|
||||||
|
|
||||||
private val points by lazy {
|
private val points by lazy {
|
||||||
val step = 0.05
|
val step = 0.05
|
||||||
var t = 0.0
|
var t = 0.0
|
||||||
val points = mutableListOf<Point>()
|
val points = mutableListOf<Point>()
|
||||||
while (t <= 1) {
|
while (t <= 1) {
|
||||||
points.add(curve.point(t))
|
points.add(curve.point(t))
|
||||||
t += step
|
t += step
|
||||||
}
|
}
|
||||||
points
|
points
|
||||||
}
|
}
|
||||||
|
|
||||||
var lineWidth = 3f
|
var lineWidth = 3f
|
||||||
var lineColor = Color.BLACK
|
var lineColor = Color.BLACK
|
||||||
|
|
||||||
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
matrixStack.push()
|
matrixStack.push()
|
||||||
matrixStack.scale(bounds.width.toFloat(), bounds.height.toFloat(), 1f)
|
matrixStack.scale(bounds.width.toFloat(), bounds.height.toFloat(), 1f)
|
||||||
for ((index, point) in points.withIndex()) {
|
for ((index, point) in points.withIndex()) {
|
||||||
val next = points.getOrNull(index + 1) ?: break
|
val next = points.getOrNull(index + 1) ?: break
|
||||||
RenderHelper.drawLine(point, next, zIndex, lineWidth, lineColor)
|
RenderHelper.drawLine(point, next, zIndex, lineWidth, lineColor)
|
||||||
}
|
}
|
||||||
matrixStack.pop()
|
matrixStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,103 +14,103 @@ import net.shadowfacts.kiwidsl.dsl
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class DialogView(
|
class DialogView(
|
||||||
val title: Text,
|
val title: Text,
|
||||||
val message: Text,
|
val message: Text,
|
||||||
val buttonTypes: Array<ButtonType>,
|
val buttonTypes: Array<ButtonType>,
|
||||||
val iconTexture: Texture?,
|
val iconTexture: Texture?,
|
||||||
val buttonCallback: (ButtonType, Window) -> Unit
|
val buttonCallback: (ButtonType, Window) -> Unit
|
||||||
): View() {
|
) : View() {
|
||||||
|
|
||||||
interface ButtonType {
|
interface ButtonType {
|
||||||
val localizedName: Text
|
val localizedName: Text
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DefaultButtonType: ButtonType {
|
enum class DefaultButtonType : ButtonType {
|
||||||
CANCEL, CONFIRM, OK, CLOSE;
|
CANCEL, CONFIRM, OK, CLOSE;
|
||||||
|
|
||||||
override val localizedName: Text
|
override val localizedName: Text
|
||||||
get() = LiteralText(name.lowercase().replaceFirstChar(Char::titlecase)) // todo: actually localize me
|
get() = LiteralText(name.lowercase().replaceFirstChar(Char::titlecase)) // todo: actually localize me
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var background: NinePatchView
|
private lateinit var background: NinePatchView
|
||||||
private lateinit var hStack: StackView
|
private lateinit var hStack: StackView
|
||||||
private var iconView: TextureView? = null
|
private var iconView: TextureView? = null
|
||||||
private lateinit var vStack: StackView
|
private lateinit var vStack: StackView
|
||||||
private lateinit var messageLabel: Label
|
private lateinit var messageLabel: Label
|
||||||
private var buttonContainer: View? = null
|
private var buttonContainer: View? = null
|
||||||
private var buttonStack: StackView? = null
|
private var buttonStack: StackView? = null
|
||||||
|
|
||||||
override fun wasAdded() {
|
override fun wasAdded() {
|
||||||
background = addSubview(NinePatchView(NinePatchTexture.PANEL_BG).apply { zIndex = -1.0 })
|
background = addSubview(NinePatchView(NinePatchTexture.PANEL_BG).apply { zIndex = -1.0 })
|
||||||
|
|
||||||
hStack = addSubview(StackView(Axis.HORIZONTAL, StackView.Distribution.LEADING, spacing = 8.0))
|
hStack = addSubview(StackView(Axis.HORIZONTAL, StackView.Distribution.LEADING, spacing = 8.0))
|
||||||
|
|
||||||
if (iconTexture != null) {
|
if (iconTexture != null) {
|
||||||
iconView = hStack.addArrangedSubview(TextureView(iconTexture))
|
iconView = hStack.addArrangedSubview(TextureView(iconTexture))
|
||||||
}
|
}
|
||||||
|
|
||||||
vStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL, spacing = 4.0))
|
vStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL, spacing = 4.0))
|
||||||
|
|
||||||
vStack.addArrangedSubview(Label(title, shadow = false).apply {
|
vStack.addArrangedSubview(Label(title, shadow = false).apply {
|
||||||
textColor = Color(0x404040)
|
textColor = Color(0x404040)
|
||||||
})
|
})
|
||||||
messageLabel = vStack.addArrangedSubview(Label(message, shadow = false).apply {
|
messageLabel = vStack.addArrangedSubview(Label(message, shadow = false).apply {
|
||||||
textColor = Color(0x404040)
|
textColor = Color(0x404040)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (buttonTypes.isNotEmpty()) {
|
if (buttonTypes.isNotEmpty()) {
|
||||||
buttonContainer = vStack.addArrangedSubview(View())
|
buttonContainer = vStack.addArrangedSubview(View())
|
||||||
buttonStack = buttonContainer!!.addSubview(StackView(Axis.HORIZONTAL))
|
buttonStack = buttonContainer!!.addSubview(StackView(Axis.HORIZONTAL))
|
||||||
for (type in buttonTypes) {
|
for (type in buttonTypes) {
|
||||||
buttonStack!!.addArrangedSubview(Button(Label(type.localizedName)).apply {
|
buttonStack!!.addArrangedSubview(Button(Label(type.localizedName)).apply {
|
||||||
handler = {
|
handler = {
|
||||||
this@DialogView.buttonCallback(type, this@DialogView.window!!)
|
this@DialogView.buttonCallback(type, this@DialogView.window!!)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.wasAdded()
|
super.wasAdded()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createInternalConstraints() {
|
override fun createInternalConstraints() {
|
||||||
super.createInternalConstraints()
|
super.createInternalConstraints()
|
||||||
|
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
centerXAnchor equalTo window!!.centerXAnchor
|
centerXAnchor equalTo window!!.centerXAnchor
|
||||||
centerYAnchor equalTo window!!.centerYAnchor
|
centerYAnchor equalTo window!!.centerYAnchor
|
||||||
|
|
||||||
widthAnchor greaterThanOrEqualTo 175
|
widthAnchor greaterThanOrEqualTo 175
|
||||||
|
|
||||||
background.leftAnchor equalTo leftAnchor - 8
|
background.leftAnchor equalTo leftAnchor - 8
|
||||||
background.rightAnchor equalTo rightAnchor + 8
|
background.rightAnchor equalTo rightAnchor + 8
|
||||||
background.topAnchor equalTo topAnchor - 8
|
background.topAnchor equalTo topAnchor - 8
|
||||||
background.bottomAnchor equalTo bottomAnchor + 8
|
background.bottomAnchor equalTo bottomAnchor + 8
|
||||||
|
|
||||||
hStack.leftAnchor equalTo leftAnchor
|
hStack.leftAnchor equalTo leftAnchor
|
||||||
hStack.rightAnchor equalTo rightAnchor
|
hStack.rightAnchor equalTo rightAnchor
|
||||||
hStack.topAnchor equalTo topAnchor
|
hStack.topAnchor equalTo topAnchor
|
||||||
hStack.bottomAnchor equalTo bottomAnchor
|
hStack.bottomAnchor equalTo bottomAnchor
|
||||||
|
|
||||||
if (iconView != null) {
|
if (iconView != null) {
|
||||||
hStack.bottomAnchor greaterThanOrEqualTo iconView!!.bottomAnchor
|
hStack.bottomAnchor greaterThanOrEqualTo iconView!!.bottomAnchor
|
||||||
}
|
}
|
||||||
hStack.bottomAnchor greaterThanOrEqualTo vStack.bottomAnchor
|
hStack.bottomAnchor greaterThanOrEqualTo vStack.bottomAnchor
|
||||||
|
|
||||||
if (iconView != null) {
|
if (iconView != null) {
|
||||||
iconView!!.widthAnchor equalTo 30
|
iconView!!.widthAnchor equalTo 30
|
||||||
iconView!!.heightAnchor equalTo 30
|
iconView!!.heightAnchor equalTo 30
|
||||||
}
|
}
|
||||||
|
|
||||||
messageLabel.heightAnchor greaterThanOrEqualTo 50
|
messageLabel.heightAnchor greaterThanOrEqualTo 50
|
||||||
|
|
||||||
if (buttonContainer != null) {
|
if (buttonContainer != null) {
|
||||||
buttonStack!!.heightAnchor equalTo buttonContainer!!.heightAnchor
|
buttonStack!!.heightAnchor equalTo buttonContainer!!.heightAnchor
|
||||||
buttonStack!!.centerYAnchor equalTo buttonContainer!!.centerYAnchor
|
buttonStack!!.centerYAnchor equalTo buttonContainer!!.centerYAnchor
|
||||||
|
|
||||||
buttonStack!!.rightAnchor equalTo buttonContainer!!.rightAnchor
|
buttonStack!!.rightAnchor equalTo buttonContainer!!.rightAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,117 +26,117 @@ import kotlin.math.min
|
||||||
* wrapping.
|
* wrapping.
|
||||||
*/
|
*/
|
||||||
class Label(
|
class Label(
|
||||||
text: Text,
|
text: Text,
|
||||||
var shadow: Boolean = false,
|
var shadow: Boolean = false,
|
||||||
val maxLines: Int = 0,
|
val maxLines: Int = 0,
|
||||||
val wrappingMode: WrappingMode = WrappingMode.WRAP,
|
val wrappingMode: WrappingMode = WrappingMode.WRAP,
|
||||||
var textAlignment: TextAlignment = TextAlignment.LEFT
|
var textAlignment: TextAlignment = TextAlignment.LEFT
|
||||||
): View() {
|
) : View() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val textRenderer: TextRenderer
|
private val textRenderer: TextRenderer
|
||||||
get() = MinecraftClient.getInstance().textRenderer
|
get() = MinecraftClient.getInstance().textRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class WrappingMode {
|
enum class WrappingMode {
|
||||||
WRAP, NO_WRAP
|
WRAP, NO_WRAP
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class TextAlignment {
|
enum class TextAlignment {
|
||||||
LEFT, CENTER, RIGHT
|
LEFT, CENTER, RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
text: String,
|
text: String,
|
||||||
shadow: Boolean = false,
|
shadow: Boolean = false,
|
||||||
maxLines: Int = 0,
|
maxLines: Int = 0,
|
||||||
wrappingMode: WrappingMode = WrappingMode.WRAP,
|
wrappingMode: WrappingMode = WrappingMode.WRAP,
|
||||||
textAlignment: TextAlignment = TextAlignment.LEFT,
|
textAlignment: TextAlignment = TextAlignment.LEFT,
|
||||||
): this(LiteralText(text), shadow, maxLines, wrappingMode, textAlignment)
|
) : this(LiteralText(text), shadow, maxLines, wrappingMode, textAlignment)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The text of this label. Mutating this field will update the intrinsic content size and trigger a layout.
|
* The text of this label. Mutating this field will update the intrinsic content size and trigger a layout.
|
||||||
*/
|
*/
|
||||||
var text: Text = text
|
var text: Text = text
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
// todo: uhhhh
|
// todo: uhhhh
|
||||||
updateIntrinsicContentSize(true)
|
updateIntrinsicContentSize(true)
|
||||||
// todo: setNeedsLayout instead of force unwrapping window
|
// todo: setNeedsLayout instead of force unwrapping window
|
||||||
window!!.layout()
|
window!!.layout()
|
||||||
}
|
}
|
||||||
private lateinit var lines: List<OrderedText>
|
private lateinit var lines: List<OrderedText>
|
||||||
|
|
||||||
var textColor = Color.WHITE
|
var textColor = Color.WHITE
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
textColorARGB = value.argb
|
textColorARGB = value.argb
|
||||||
}
|
}
|
||||||
private var textColorARGB: Int = textColor.argb
|
private var textColorARGB: Int = textColor.argb
|
||||||
|
|
||||||
override fun wasAdded() {
|
override fun wasAdded() {
|
||||||
super.wasAdded()
|
super.wasAdded()
|
||||||
|
|
||||||
updateIntrinsicContentSize(false)
|
updateIntrinsicContentSize(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateIntrinsicContentSize(canWrap: Boolean, isFromDidLayout: Boolean = false): Boolean {
|
private fun updateIntrinsicContentSize(canWrap: Boolean, isFromDidLayout: Boolean = false): Boolean {
|
||||||
if (RenderHelper.disabled) return false
|
if (RenderHelper.disabled) return false
|
||||||
|
|
||||||
val oldSize = intrinsicContentSize
|
val oldSize = intrinsicContentSize
|
||||||
// don't wrap until we've laid out without wrapping to ensure the current bounds reflect the maximum available space
|
// don't wrap until we've laid out without wrapping to ensure the current bounds reflect the maximum available space
|
||||||
if (wrappingMode == WrappingMode.WRAP && canWrap && hasSolver && isFromDidLayout) {
|
if (wrappingMode == WrappingMode.WRAP && canWrap && hasSolver && isFromDidLayout) {
|
||||||
val lines = textRenderer.wrapLines(text, bounds.width.toInt())
|
val lines = textRenderer.wrapLines(text, bounds.width.toInt())
|
||||||
val height = (if (maxLines == 0) lines.size else min(lines.size, maxLines)) * textRenderer.fontHeight
|
val height = (if (maxLines == 0) lines.size else min(lines.size, maxLines)) * textRenderer.fontHeight
|
||||||
intrinsicContentSize = Size(bounds.width, height.toDouble())
|
intrinsicContentSize = Size(bounds.width, height.toDouble())
|
||||||
} else {
|
} else {
|
||||||
val width = textRenderer.getWidth(text)
|
val width = textRenderer.getWidth(text)
|
||||||
val height = textRenderer.fontHeight
|
val height = textRenderer.fontHeight
|
||||||
intrinsicContentSize = Size(width.toDouble(), height.toDouble())
|
intrinsicContentSize = Size(width.toDouble(), height.toDouble())
|
||||||
}
|
}
|
||||||
return oldSize != intrinsicContentSize
|
return oldSize != intrinsicContentSize
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
if (!this::lines.isInitialized) {
|
if (!this::lines.isInitialized) {
|
||||||
computeLines()
|
computeLines()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0 until lines.size) {
|
for (i in 0 until lines.size) {
|
||||||
val x = when (textAlignment) {
|
val x = when (textAlignment) {
|
||||||
TextAlignment.LEFT -> 0.0
|
TextAlignment.LEFT -> 0.0
|
||||||
TextAlignment.CENTER -> (bounds.width - textRenderer.getWidth(lines[i])) / 2
|
TextAlignment.CENTER -> (bounds.width - textRenderer.getWidth(lines[i])) / 2
|
||||||
TextAlignment.RIGHT -> bounds.width - textRenderer.getWidth(lines[i])
|
TextAlignment.RIGHT -> bounds.width - textRenderer.getWidth(lines[i])
|
||||||
}
|
}
|
||||||
val y = i * textRenderer.fontHeight
|
val y = i * textRenderer.fontHeight
|
||||||
if (shadow) {
|
if (shadow) {
|
||||||
textRenderer.drawWithShadow(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB)
|
textRenderer.drawWithShadow(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB)
|
||||||
} else {
|
} else {
|
||||||
textRenderer.draw(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB)
|
textRenderer.draw(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun didLayout() {
|
override fun didLayout() {
|
||||||
super.didLayout()
|
super.didLayout()
|
||||||
|
|
||||||
computeLines()
|
computeLines()
|
||||||
if (updateIntrinsicContentSize(true, true)) {
|
if (updateIntrinsicContentSize(true, true)) {
|
||||||
// if the intrinsic content size changes, relayout
|
// if the intrinsic content size changes, relayout
|
||||||
window!!.layout()
|
window!!.layout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeLines() {
|
private fun computeLines() {
|
||||||
if (wrappingMode == WrappingMode.WRAP) {
|
if (wrappingMode == WrappingMode.WRAP) {
|
||||||
var lines = textRenderer.wrapLines(text, bounds.width.toInt())
|
var lines = textRenderer.wrapLines(text, bounds.width.toInt())
|
||||||
if (maxLines > 0 && maxLines < lines.size) {
|
if (maxLines > 0 && maxLines < lines.size) {
|
||||||
lines = lines.dropLast(lines.size - maxLines)
|
lines = lines.dropLast(lines.size - maxLines)
|
||||||
}
|
}
|
||||||
this.lines = lines
|
this.lines = lines
|
||||||
} else {
|
} else {
|
||||||
this.lines = listOf(text.asOrderedText())
|
this.lines = listOf(text.asOrderedText())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,121 +17,260 @@ import kotlin.math.roundToInt
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
* @param ninePatch The nine patch texture that this view will draw.
|
* @param ninePatch The nine patch texture that this view will draw.
|
||||||
*/
|
*/
|
||||||
open class NinePatchView(val ninePatch: NinePatchTexture): View() {
|
open class NinePatchView(val ninePatch: NinePatchTexture) : View() {
|
||||||
|
|
||||||
// Corners
|
// Corners
|
||||||
private val topLeftDelegate = ResettableLazyProperty {
|
private val topLeftDelegate = ResettableLazyProperty {
|
||||||
Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
|
Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
|
||||||
}
|
}
|
||||||
protected open val topLeft by topLeftDelegate
|
protected open val topLeft by topLeftDelegate
|
||||||
|
|
||||||
private val topRightDelegate = ResettableLazyProperty {
|
private val topRightDelegate = ResettableLazyProperty {
|
||||||
Rect(bounds.width - ninePatch.cornerWidth, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
|
Rect(
|
||||||
}
|
bounds.width - ninePatch.cornerWidth,
|
||||||
protected open val topRight by topRightDelegate
|
0.0,
|
||||||
|
ninePatch.cornerWidth.toDouble(),
|
||||||
|
ninePatch.cornerHeight.toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
protected open val topRight by topRightDelegate
|
||||||
|
|
||||||
private val bottomLeftDelegate = ResettableLazyProperty {
|
private val bottomLeftDelegate = ResettableLazyProperty {
|
||||||
Rect(0.0, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
|
Rect(
|
||||||
}
|
0.0,
|
||||||
protected open val bottomLeft by bottomLeftDelegate
|
bounds.height - ninePatch.cornerHeight,
|
||||||
|
ninePatch.cornerWidth.toDouble(),
|
||||||
|
ninePatch.cornerHeight.toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
protected open val bottomLeft by bottomLeftDelegate
|
||||||
|
|
||||||
private val bottomRightDelegate = ResettableLazyProperty {
|
private val bottomRightDelegate = ResettableLazyProperty {
|
||||||
Rect(bounds.width - ninePatch.cornerWidth, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble())
|
Rect(
|
||||||
}
|
bounds.width - ninePatch.cornerWidth,
|
||||||
protected open val bottomRight by bottomRightDelegate
|
bounds.height - ninePatch.cornerHeight,
|
||||||
|
ninePatch.cornerWidth.toDouble(),
|
||||||
|
ninePatch.cornerHeight.toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
protected open val bottomRight by bottomRightDelegate
|
||||||
|
|
||||||
|
|
||||||
// Edges
|
// Edges
|
||||||
private val topMiddleDelegate = ResettableLazyProperty {
|
private val topMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, ninePatch.cornerHeight.toDouble())
|
Rect(
|
||||||
}
|
ninePatch.cornerWidth.toDouble(),
|
||||||
protected open val topMiddle by topMiddleDelegate
|
topLeft.top,
|
||||||
|
bounds.width - 2 * ninePatch.cornerWidth,
|
||||||
|
ninePatch.cornerHeight.toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
protected open val topMiddle by topMiddleDelegate
|
||||||
|
|
||||||
private val bottomMiddleDelegate = ResettableLazyProperty {
|
private val bottomMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(topMiddle.left, bottomLeft.top, topMiddle.width, topMiddle.height)
|
Rect(topMiddle.left, bottomLeft.top, topMiddle.width, topMiddle.height)
|
||||||
}
|
}
|
||||||
protected open val bottomMiddle by bottomMiddleDelegate
|
protected open val bottomMiddle by bottomMiddleDelegate
|
||||||
|
|
||||||
private val leftMiddleDelegate = ResettableLazyProperty {
|
private val leftMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(topLeft.left, ninePatch.cornerHeight.toDouble(), ninePatch.cornerWidth.toDouble(), bounds.height - 2 * ninePatch.cornerHeight)
|
Rect(
|
||||||
}
|
topLeft.left,
|
||||||
protected open val leftMiddle by leftMiddleDelegate
|
ninePatch.cornerHeight.toDouble(),
|
||||||
|
ninePatch.cornerWidth.toDouble(),
|
||||||
|
bounds.height - 2 * ninePatch.cornerHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
protected open val leftMiddle by leftMiddleDelegate
|
||||||
|
|
||||||
private val rightMiddleDelegate = ResettableLazyProperty {
|
private val rightMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(topRight.left, leftMiddle.top, leftMiddle.width, leftMiddle.height)
|
Rect(topRight.left, leftMiddle.top, leftMiddle.width, leftMiddle.height)
|
||||||
}
|
}
|
||||||
protected open val rightMiddle by rightMiddleDelegate
|
protected open val rightMiddle by rightMiddleDelegate
|
||||||
|
|
||||||
|
|
||||||
// Center
|
// Center
|
||||||
private val centerDelegate = ResettableLazyProperty {
|
private val centerDelegate = ResettableLazyProperty {
|
||||||
Rect(topLeft.right, topLeft.bottom, topMiddle.width, leftMiddle.height)
|
Rect(topLeft.right, topLeft.bottom, topMiddle.width, leftMiddle.height)
|
||||||
}
|
}
|
||||||
protected open val center by centerDelegate
|
protected open val center by centerDelegate
|
||||||
|
|
||||||
private val delegates = listOf(topLeftDelegate, topRightDelegate, bottomLeftDelegate, bottomRightDelegate, topMiddleDelegate, bottomMiddleDelegate, leftMiddleDelegate, rightMiddleDelegate, centerDelegate)
|
private val delegates = listOf(
|
||||||
|
topLeftDelegate,
|
||||||
|
topRightDelegate,
|
||||||
|
bottomLeftDelegate,
|
||||||
|
bottomRightDelegate,
|
||||||
|
topMiddleDelegate,
|
||||||
|
bottomMiddleDelegate,
|
||||||
|
leftMiddleDelegate,
|
||||||
|
rightMiddleDelegate,
|
||||||
|
centerDelegate
|
||||||
|
)
|
||||||
|
|
||||||
override fun didLayout() {
|
override fun didLayout() {
|
||||||
super.didLayout()
|
super.didLayout()
|
||||||
|
|
||||||
delegates.forEach(ResettableLazyProperty<Rect>::reset)
|
delegates.forEach(ResettableLazyProperty<Rect>::reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
drawCorners(matrixStack)
|
drawCorners(matrixStack)
|
||||||
drawEdges(matrixStack)
|
drawEdges(matrixStack)
|
||||||
drawCenter(matrixStack)
|
drawCenter(matrixStack)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawCorners(matrixStack: MatrixStack) {
|
private fun drawCorners(matrixStack: MatrixStack) {
|
||||||
RenderHelper.draw(matrixStack, topLeft, ninePatch.topLeft)
|
RenderHelper.draw(matrixStack, topLeft, ninePatch.topLeft)
|
||||||
RenderHelper.draw(matrixStack, topRight, ninePatch.topRight)
|
RenderHelper.draw(matrixStack, topRight, ninePatch.topRight)
|
||||||
RenderHelper.draw(matrixStack, bottomLeft, ninePatch.bottomLeft)
|
RenderHelper.draw(matrixStack, bottomLeft, ninePatch.bottomLeft)
|
||||||
RenderHelper.draw(matrixStack, bottomRight, ninePatch.bottomRight)
|
RenderHelper.draw(matrixStack, bottomRight, ninePatch.bottomRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun drawEdges(matrixStack: MatrixStack) {
|
private fun drawEdges(matrixStack: MatrixStack) {
|
||||||
// Horizontal
|
// Horizontal
|
||||||
for (i in 0 until (topMiddle.width.roundToInt() / ninePatch.centerWidth)) {
|
for (i in 0 until (topMiddle.width.roundToInt() / ninePatch.centerWidth)) {
|
||||||
RenderHelper.draw(matrixStack, topMiddle.left + i * ninePatch.centerWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, ninePatch.centerWidth.toDouble(), topMiddle.height, ninePatch.texture.width, ninePatch.texture.height)
|
RenderHelper.draw(
|
||||||
RenderHelper.draw(matrixStack, bottomMiddle.left + i * ninePatch.centerWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, ninePatch.centerWidth.toDouble(), bottomMiddle.height, ninePatch.texture.width, ninePatch.texture.height)
|
matrixStack,
|
||||||
}
|
topMiddle.left + i * ninePatch.centerWidth,
|
||||||
val remWidth = topMiddle.width.roundToInt() % ninePatch.centerWidth
|
topMiddle.top,
|
||||||
if (remWidth > 0) {
|
ninePatch.topMiddle.u,
|
||||||
RenderHelper.draw(matrixStack, topMiddle.right - remWidth, topMiddle.top, ninePatch.topMiddle.u, ninePatch.topMiddle.v, remWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
ninePatch.topMiddle.v,
|
||||||
RenderHelper.draw(matrixStack, bottomMiddle.right - remWidth, bottomMiddle.top, ninePatch.bottomMiddle.u, ninePatch.bottomMiddle.v, remWidth.toDouble(), ninePatch.cornerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
ninePatch.centerWidth.toDouble(),
|
||||||
}
|
topMiddle.height,
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
RenderHelper.draw(
|
||||||
|
matrixStack,
|
||||||
|
bottomMiddle.left + i * ninePatch.centerWidth,
|
||||||
|
bottomMiddle.top,
|
||||||
|
ninePatch.bottomMiddle.u,
|
||||||
|
ninePatch.bottomMiddle.v,
|
||||||
|
ninePatch.centerWidth.toDouble(),
|
||||||
|
bottomMiddle.height,
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val remWidth = topMiddle.width.roundToInt() % ninePatch.centerWidth
|
||||||
|
if (remWidth > 0) {
|
||||||
|
RenderHelper.draw(
|
||||||
|
matrixStack,
|
||||||
|
topMiddle.right - remWidth,
|
||||||
|
topMiddle.top,
|
||||||
|
ninePatch.topMiddle.u,
|
||||||
|
ninePatch.topMiddle.v,
|
||||||
|
remWidth.toDouble(),
|
||||||
|
ninePatch.cornerHeight.toDouble(),
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
RenderHelper.draw(
|
||||||
|
matrixStack,
|
||||||
|
bottomMiddle.right - remWidth,
|
||||||
|
bottomMiddle.top,
|
||||||
|
ninePatch.bottomMiddle.u,
|
||||||
|
ninePatch.bottomMiddle.v,
|
||||||
|
remWidth.toDouble(),
|
||||||
|
ninePatch.cornerHeight.toDouble(),
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Vertical
|
// Vertical
|
||||||
for (i in 0 until (leftMiddle.height.roundToInt() / ninePatch.centerHeight)) {
|
for (i in 0 until (leftMiddle.height.roundToInt() / ninePatch.centerHeight)) {
|
||||||
RenderHelper.draw(matrixStack, leftMiddle.left, leftMiddle.top + i * ninePatch.centerHeight, ninePatch.leftMiddle.u, ninePatch.leftMiddle.v, ninePatch.cornerWidth.toDouble(), ninePatch.centerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
RenderHelper.draw(
|
||||||
RenderHelper.draw(matrixStack, rightMiddle.left, rightMiddle.top + i * ninePatch.centerHeight, ninePatch.rightMiddle.u, ninePatch.rightMiddle.v, ninePatch.cornerWidth.toDouble(), ninePatch.centerHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
matrixStack,
|
||||||
}
|
leftMiddle.left,
|
||||||
val remHeight = leftMiddle.height.roundToInt() % ninePatch.centerHeight
|
leftMiddle.top + i * ninePatch.centerHeight,
|
||||||
if (remHeight > 0) {
|
ninePatch.leftMiddle.u,
|
||||||
RenderHelper.draw(matrixStack, leftMiddle.left, leftMiddle.bottom - remHeight, ninePatch.leftMiddle.u, ninePatch.leftMiddle.v, ninePatch.cornerWidth.toDouble(), remHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
ninePatch.leftMiddle.v,
|
||||||
RenderHelper.draw(matrixStack, rightMiddle.left, rightMiddle.bottom - remHeight, ninePatch.rightMiddle.u, ninePatch.rightMiddle.v, ninePatch.cornerWidth.toDouble(), remHeight.toDouble(), ninePatch.texture.width, ninePatch.texture.height)
|
ninePatch.cornerWidth.toDouble(),
|
||||||
}
|
ninePatch.centerHeight.toDouble(),
|
||||||
}
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
RenderHelper.draw(
|
||||||
|
matrixStack,
|
||||||
|
rightMiddle.left,
|
||||||
|
rightMiddle.top + i * ninePatch.centerHeight,
|
||||||
|
ninePatch.rightMiddle.u,
|
||||||
|
ninePatch.rightMiddle.v,
|
||||||
|
ninePatch.cornerWidth.toDouble(),
|
||||||
|
ninePatch.centerHeight.toDouble(),
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val remHeight = leftMiddle.height.roundToInt() % ninePatch.centerHeight
|
||||||
|
if (remHeight > 0) {
|
||||||
|
RenderHelper.draw(
|
||||||
|
matrixStack,
|
||||||
|
leftMiddle.left,
|
||||||
|
leftMiddle.bottom - remHeight,
|
||||||
|
ninePatch.leftMiddle.u,
|
||||||
|
ninePatch.leftMiddle.v,
|
||||||
|
ninePatch.cornerWidth.toDouble(),
|
||||||
|
remHeight.toDouble(),
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
RenderHelper.draw(
|
||||||
|
matrixStack,
|
||||||
|
rightMiddle.left,
|
||||||
|
rightMiddle.bottom - remHeight,
|
||||||
|
ninePatch.rightMiddle.u,
|
||||||
|
ninePatch.rightMiddle.v,
|
||||||
|
ninePatch.cornerWidth.toDouble(),
|
||||||
|
remHeight.toDouble(),
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun drawCenter(matrixStack: MatrixStack) {
|
private fun drawCenter(matrixStack: MatrixStack) {
|
||||||
for (i in 0 until (center.height.roundToInt() / ninePatch.centerHeight)) {
|
for (i in 0 until (center.height.roundToInt() / ninePatch.centerHeight)) {
|
||||||
drawCenterRow(matrixStack, center.top + i * ninePatch.centerHeight.toDouble(), ninePatch.centerHeight.toDouble())
|
drawCenterRow(
|
||||||
}
|
matrixStack,
|
||||||
val remHeight = center.height.roundToInt() % ninePatch.centerHeight
|
center.top + i * ninePatch.centerHeight.toDouble(),
|
||||||
if (remHeight > 0) {
|
ninePatch.centerHeight.toDouble()
|
||||||
drawCenterRow(matrixStack, center.bottom - remHeight, remHeight.toDouble())
|
)
|
||||||
}
|
}
|
||||||
}
|
val remHeight = center.height.roundToInt() % ninePatch.centerHeight
|
||||||
|
if (remHeight > 0) {
|
||||||
|
drawCenterRow(matrixStack, center.bottom - remHeight, remHeight.toDouble())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun drawCenterRow(matrixStack: MatrixStack, y: Double, height: Double) {
|
private fun drawCenterRow(matrixStack: MatrixStack, y: Double, height: Double) {
|
||||||
for (i in 0 until (center.width.roundToInt() / ninePatch.centerWidth)) {
|
for (i in 0 until (center.width.roundToInt() / ninePatch.centerWidth)) {
|
||||||
RenderHelper.draw(matrixStack, center.left + i * ninePatch.centerWidth, y, ninePatch.center.u, ninePatch.center.v, ninePatch.centerWidth.toDouble(), height, ninePatch.texture.width, ninePatch.texture.height)
|
RenderHelper.draw(
|
||||||
}
|
matrixStack,
|
||||||
val remWidth = center.width.roundToInt() % ninePatch.centerWidth
|
center.left + i * ninePatch.centerWidth,
|
||||||
if (remWidth > 0) {
|
y,
|
||||||
RenderHelper.draw(matrixStack, center.right - remWidth, y, ninePatch.center.u, ninePatch.center.v, remWidth.toDouble(), height, ninePatch.texture.width, ninePatch.texture.height)
|
ninePatch.center.u,
|
||||||
}
|
ninePatch.center.v,
|
||||||
}
|
ninePatch.centerWidth.toDouble(),
|
||||||
|
height,
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val remWidth = center.width.roundToInt() % ninePatch.centerWidth
|
||||||
|
if (remWidth > 0) {
|
||||||
|
RenderHelper.draw(
|
||||||
|
matrixStack,
|
||||||
|
center.right - remWidth,
|
||||||
|
y,
|
||||||
|
ninePatch.center.u,
|
||||||
|
ninePatch.center.v,
|
||||||
|
remWidth.toDouble(),
|
||||||
|
height,
|
||||||
|
ninePatch.texture.width,
|
||||||
|
ninePatch.texture.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,251 +22,265 @@ import java.util.*
|
||||||
* @param spacing The distance between arranged subviews along the primary axis.
|
* @param spacing The distance between arranged subviews along the primary axis.
|
||||||
*/
|
*/
|
||||||
open class StackView(
|
open class StackView(
|
||||||
val axis: Axis,
|
val axis: Axis,
|
||||||
val distribution: Distribution = Distribution.FILL,
|
val distribution: Distribution = Distribution.FILL,
|
||||||
val spacing: Double = 0.0
|
val spacing: Double = 0.0
|
||||||
): View() {
|
) : View() {
|
||||||
|
|
||||||
// the internal, mutable list of arranged subviews
|
// the internal, mutable list of arranged subviews
|
||||||
private val _arrangedSubviews = LinkedList<View>()
|
private val _arrangedSubviews = LinkedList<View>()
|
||||||
/**
|
|
||||||
* The list of arranged subviews belonging to this stack view.
|
|
||||||
* This list should never be mutated directly, only be calling the [addArrangedSubview]/[removeArrangedSubview]
|
|
||||||
* methods.
|
|
||||||
*/
|
|
||||||
val arrangedSubviews: List<View> = _arrangedSubviews
|
|
||||||
|
|
||||||
private var leadingConnection: Constraint? = null
|
/**
|
||||||
private var trailingConnection: Constraint? = null
|
* The list of arranged subviews belonging to this stack view.
|
||||||
private var arrangedSubviewConnections = mutableListOf<Constraint>()
|
* This list should never be mutated directly, only be calling the [addArrangedSubview]/[removeArrangedSubview]
|
||||||
|
* methods.
|
||||||
|
*/
|
||||||
|
val arrangedSubviews: List<View> = _arrangedSubviews
|
||||||
|
|
||||||
/**
|
private var leadingConnection: Constraint? = null
|
||||||
* Adds an arranged subview to this view.
|
private var trailingConnection: Constraint? = null
|
||||||
* Arranged subviews are laid out according to the stack. If you wish to add a subview that is laid out separately,
|
private var arrangedSubviewConnections = mutableListOf<Constraint>()
|
||||||
* use the normal [addSubview] method.
|
|
||||||
*
|
|
||||||
* @param view The view to add.
|
|
||||||
* @param index The index in this stack to add the view at.
|
|
||||||
* By default, adds the view to the end of the stack.
|
|
||||||
* @return The view that was added, as a convenience.
|
|
||||||
*/
|
|
||||||
fun <T: View> addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T {
|
|
||||||
addSubview(view)
|
|
||||||
_arrangedSubviews.add(index, view)
|
|
||||||
|
|
||||||
addConstraintsForArrangedView(view, index)
|
/**
|
||||||
|
* Adds an arranged subview to this view.
|
||||||
|
* Arranged subviews are laid out according to the stack. If you wish to add a subview that is laid out separately,
|
||||||
|
* use the normal [addSubview] method.
|
||||||
|
*
|
||||||
|
* @param view The view to add.
|
||||||
|
* @param index The index in this stack to add the view at.
|
||||||
|
* By default, adds the view to the end of the stack.
|
||||||
|
* @return The view that was added, as a convenience.
|
||||||
|
*/
|
||||||
|
fun <T : View> addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T {
|
||||||
|
addSubview(view)
|
||||||
|
_arrangedSubviews.add(index, view)
|
||||||
|
|
||||||
return view
|
addConstraintsForArrangedView(view, index)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return view
|
||||||
* Removes the given arranged subview from this stack view's arranged subviews.
|
}
|
||||||
*/
|
|
||||||
fun removeArrangedSubview(view: View) {
|
|
||||||
val index = arrangedSubviews.indexOf(view)
|
|
||||||
if (index < 0) {
|
|
||||||
throw RuntimeException("Cannot remove view that is not arranged subview")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index == 0) {
|
/**
|
||||||
solver.removeConstraint(leadingConnection)
|
* Removes the given arranged subview from this stack view's arranged subviews.
|
||||||
val next = arrangedSubviews.getOrNull(1)
|
*/
|
||||||
if (next != null) {
|
fun removeArrangedSubview(view: View) {
|
||||||
solver.dsl {
|
val index = arrangedSubviews.indexOf(view)
|
||||||
leadingConnection = anchor(LEADING) equalTo anchor(LEADING, next)
|
if (index < 0) {
|
||||||
}
|
throw RuntimeException("Cannot remove view that is not arranged subview")
|
||||||
} else {
|
}
|
||||||
leadingConnection = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index == arrangedSubviews.size - 1) {
|
|
||||||
solver.removeConstraint(trailingConnection)
|
|
||||||
val prev = arrangedSubviews.getOrNull(arrangedSubviews.size - 2)
|
|
||||||
if (prev != null) {
|
|
||||||
solver.dsl {
|
|
||||||
trailingConnection = anchor(TRAILING) equalTo anchor(TRAILING, prev)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
trailingConnection = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the removed view is in the middle
|
if (index == 0) {
|
||||||
if (arrangedSubviews.size >= 3 && index > 0 && index < arrangedSubviews.size - 1) {
|
solver.removeConstraint(leadingConnection)
|
||||||
val prev = arrangedSubviews[index - 1]
|
val next = arrangedSubviews.getOrNull(1)
|
||||||
val next = arrangedSubviews[index + 1]
|
if (next != null) {
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
solver.removeConstraint(arrangedSubviewConnections[index - 1])
|
leadingConnection = anchor(LEADING) equalTo anchor(LEADING, next)
|
||||||
solver.removeConstraint(arrangedSubviewConnections[index])
|
}
|
||||||
|
} else {
|
||||||
|
leadingConnection = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index == arrangedSubviews.size - 1) {
|
||||||
|
solver.removeConstraint(trailingConnection)
|
||||||
|
val prev = arrangedSubviews.getOrNull(arrangedSubviews.size - 2)
|
||||||
|
if (prev != null) {
|
||||||
|
solver.dsl {
|
||||||
|
trailingConnection = anchor(TRAILING) equalTo anchor(TRAILING, prev)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trailingConnection = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: double check me
|
// if the removed view is in the middle
|
||||||
arrangedSubviewConnections[index - 1] = anchor(TRAILING, prev) equalTo anchor(LEADING, next)
|
if (arrangedSubviews.size >= 3 && index > 0 && index < arrangedSubviews.size - 1) {
|
||||||
arrangedSubviewConnections.removeAt(index)
|
val prev = arrangedSubviews[index - 1]
|
||||||
}
|
val next = arrangedSubviews[index + 1]
|
||||||
}
|
solver.dsl {
|
||||||
|
solver.removeConstraint(arrangedSubviewConnections[index - 1])
|
||||||
|
solver.removeConstraint(arrangedSubviewConnections[index])
|
||||||
|
|
||||||
_arrangedSubviews.remove(view)
|
// todo: double check me
|
||||||
removeSubview(view)
|
arrangedSubviewConnections[index - 1] = anchor(TRAILING, prev) equalTo anchor(LEADING, next)
|
||||||
}
|
arrangedSubviewConnections.removeAt(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun removeSubview(view: View) {
|
_arrangedSubviews.remove(view)
|
||||||
if (arrangedSubviews.contains(view)) {
|
removeSubview(view)
|
||||||
removeArrangedSubview(view)
|
}
|
||||||
} else {
|
|
||||||
super.removeSubview(view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addConstraintsForArrangedView(view: View, index: Int) {
|
override fun removeSubview(view: View) {
|
||||||
if (index == 0) {
|
if (arrangedSubviews.contains(view)) {
|
||||||
if (leadingConnection != null) {
|
removeArrangedSubview(view)
|
||||||
solver.removeConstraint(leadingConnection)
|
} else {
|
||||||
}
|
super.removeSubview(view)
|
||||||
solver.dsl {
|
}
|
||||||
leadingConnection = anchor(LEADING) equalTo anchor(LEADING, view)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index == arrangedSubviews.size - 1) {
|
|
||||||
if (trailingConnection != null) {
|
|
||||||
solver.removeConstraint(trailingConnection)
|
|
||||||
}
|
|
||||||
solver.dsl {
|
|
||||||
trailingConnection = anchor(TRAILING, view) equalTo anchor(TRAILING)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (arrangedSubviews.size > 1) {
|
|
||||||
solver.dsl {
|
|
||||||
val previous = arrangedSubviews.getOrNull(index - 1)
|
|
||||||
val next = arrangedSubviews.getOrNull(index + 1)
|
|
||||||
if (next != null) {
|
|
||||||
arrangedSubviewConnections.add(index, anchor(TRAILING, view) equalTo (anchor(LEADING, next) + spacing))
|
|
||||||
}
|
|
||||||
if (previous != null) {
|
|
||||||
arrangedSubviewConnections.add(index - 1, anchor(TRAILING, previous) equalTo (anchor(LEADING, view) - spacing))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
solver.dsl {
|
|
||||||
when (distribution) {
|
|
||||||
Distribution.LEADING ->
|
|
||||||
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
|
|
||||||
Distribution.TRAILING ->
|
|
||||||
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
|
|
||||||
Distribution.FILL -> {
|
|
||||||
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
|
|
||||||
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
|
|
||||||
}
|
|
||||||
Distribution.CENTER ->
|
|
||||||
perpAnchor(CENTER) equalTo perpAnchor(CENTER, view)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun anchor(position: AxisPosition, view: View = this): LayoutVariable {
|
private fun addConstraintsForArrangedView(view: View, index: Int) {
|
||||||
return view.getAnchor(axis, position)
|
if (index == 0) {
|
||||||
}
|
if (leadingConnection != null) {
|
||||||
private fun perpAnchor(position: AxisPosition, view: View = this): LayoutVariable {
|
solver.removeConstraint(leadingConnection)
|
||||||
return view.getAnchor(axis.perpendicular, position)
|
}
|
||||||
}
|
solver.dsl {
|
||||||
|
leadingConnection = anchor(LEADING) equalTo anchor(LEADING, view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index == arrangedSubviews.size - 1) {
|
||||||
|
if (trailingConnection != null) {
|
||||||
|
solver.removeConstraint(trailingConnection)
|
||||||
|
}
|
||||||
|
solver.dsl {
|
||||||
|
trailingConnection = anchor(TRAILING, view) equalTo anchor(TRAILING)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arrangedSubviews.size > 1) {
|
||||||
|
solver.dsl {
|
||||||
|
val previous = arrangedSubviews.getOrNull(index - 1)
|
||||||
|
val next = arrangedSubviews.getOrNull(index + 1)
|
||||||
|
if (next != null) {
|
||||||
|
arrangedSubviewConnections.add(
|
||||||
|
index,
|
||||||
|
anchor(TRAILING, view) equalTo (anchor(LEADING, next) + spacing)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (previous != null) {
|
||||||
|
arrangedSubviewConnections.add(
|
||||||
|
index - 1,
|
||||||
|
anchor(TRAILING, previous) equalTo (anchor(LEADING, view) - spacing)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
solver.dsl {
|
||||||
|
when (distribution) {
|
||||||
|
Distribution.LEADING ->
|
||||||
|
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
|
||||||
|
|
||||||
/**
|
Distribution.TRAILING ->
|
||||||
* Defines the modes of how content is distributed in a stack view along the perpendicular axis (i.e. the
|
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
|
||||||
* non-primary axis).
|
|
||||||
*
|
Distribution.FILL -> {
|
||||||
* ASCII-art examples are shown below in a stack view with the primary axis [Axis.VERTICAL].
|
perpAnchor(LEADING) equalTo perpAnchor(LEADING, view)
|
||||||
*/
|
perpAnchor(TRAILING) equalTo perpAnchor(TRAILING, view)
|
||||||
enum class Distribution {
|
}
|
||||||
/**
|
|
||||||
* The leading edges of arranged subviews are pinned to the leading edge of the stack view.
|
Distribution.CENTER ->
|
||||||
* ```
|
perpAnchor(CENTER) equalTo perpAnchor(CENTER, view)
|
||||||
* ┌─────────────────────────────┐
|
}
|
||||||
* │┌─────────────┐ │
|
}
|
||||||
* ││ │ │
|
}
|
||||||
* ││ │ │
|
|
||||||
* ││ │ │
|
private fun anchor(position: AxisPosition, view: View = this): LayoutVariable {
|
||||||
* │└─────────────┘ │
|
return view.getAnchor(axis, position)
|
||||||
* │┌─────────┐ │
|
}
|
||||||
* ││ │ │
|
|
||||||
* ││ │ │
|
private fun perpAnchor(position: AxisPosition, view: View = this): LayoutVariable {
|
||||||
* ││ │ │
|
return view.getAnchor(axis.perpendicular, position)
|
||||||
* │└─────────┘ │
|
}
|
||||||
* │┌───────────────────────────┐│
|
|
||||||
* ││ ││
|
/**
|
||||||
* ││ ││
|
* Defines the modes of how content is distributed in a stack view along the perpendicular axis (i.e. the
|
||||||
* ││ ││
|
* non-primary axis).
|
||||||
* │└───────────────────────────┘│
|
*
|
||||||
* └─────────────────────────────┘
|
* ASCII-art examples are shown below in a stack view with the primary axis [Axis.VERTICAL].
|
||||||
* ```
|
*/
|
||||||
*/
|
enum class Distribution {
|
||||||
LEADING,
|
/**
|
||||||
/**
|
* The leading edges of arranged subviews are pinned to the leading edge of the stack view.
|
||||||
* The centers of the arranged subviews are pinned to the center of the stack view.
|
* ```
|
||||||
* ```
|
* ┌─────────────────────────────┐
|
||||||
* ┌─────────────────────────────┐
|
* │┌─────────────┐ │
|
||||||
* │ ┌─────────────┐ │
|
* ││ │ │
|
||||||
* │ │ │ │
|
* ││ │ │
|
||||||
* │ │ │ │
|
* ││ │ │
|
||||||
* │ │ │ │
|
* │└─────────────┘ │
|
||||||
* │ └─────────────┘ │
|
* │┌─────────┐ │
|
||||||
* │ ┌─────────┐ │
|
* ││ │ │
|
||||||
* │ │ │ │
|
* ││ │ │
|
||||||
* │ │ │ │
|
* ││ │ │
|
||||||
* │ │ │ │
|
* │└─────────┘ │
|
||||||
* │ └─────────┘ │
|
* │┌───────────────────────────┐│
|
||||||
* │┌───────────────────────────┐│
|
* ││ ││
|
||||||
* ││ ││
|
* ││ ││
|
||||||
* ││ ││
|
* ││ ││
|
||||||
* ││ ││
|
* │└───────────────────────────┘│
|
||||||
* │└───────────────────────────┘│
|
* └─────────────────────────────┘
|
||||||
* └─────────────────────────────┘
|
* ```
|
||||||
* ```
|
*/
|
||||||
*/
|
LEADING,
|
||||||
CENTER,
|
|
||||||
/**
|
/**
|
||||||
* The trailing edges of arranged subviews are pinned to the leading edge of the stack view.
|
* The centers of the arranged subviews are pinned to the center of the stack view.
|
||||||
* ```
|
* ```
|
||||||
* ┌─────────────────────────────┐
|
* ┌─────────────────────────────┐
|
||||||
* │ ┌─────────────┐│
|
* │ ┌─────────────┐ │
|
||||||
* │ │ ││
|
* │ │ │ │
|
||||||
* │ │ ││
|
* │ │ │ │
|
||||||
* │ │ ││
|
* │ │ │ │
|
||||||
* │ └─────────────┘│
|
* │ └─────────────┘ │
|
||||||
* │ ┌─────────┐│
|
* │ ┌─────────┐ │
|
||||||
* │ │ ││
|
* │ │ │ │
|
||||||
* │ │ ││
|
* │ │ │ │
|
||||||
* │ │ ││
|
* │ │ │ │
|
||||||
* │ └─────────┘│
|
* │ └─────────┘ │
|
||||||
* │┌───────────────────────────┐│
|
* │┌───────────────────────────┐│
|
||||||
* ││ ││
|
* ││ ││
|
||||||
* ││ ││
|
* ││ ││
|
||||||
* ││ ││
|
* ││ ││
|
||||||
* │└───────────────────────────┘│
|
* │└───────────────────────────┘│
|
||||||
* └─────────────────────────────┘
|
* └─────────────────────────────┘
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
TRAILING,
|
CENTER,
|
||||||
/**
|
|
||||||
* The arranged subviews fill the perpendicular axis of the stack view.
|
/**
|
||||||
* ```
|
* The trailing edges of arranged subviews are pinned to the leading edge of the stack view.
|
||||||
* ┌─────────────────────────────┐
|
* ```
|
||||||
* │┌───────────────────────────┐│
|
* ┌─────────────────────────────┐
|
||||||
* ││ ││
|
* │ ┌─────────────┐│
|
||||||
* ││ ││
|
* │ │ ││
|
||||||
* ││ ││
|
* │ │ ││
|
||||||
* │└───────────────────────────┘│
|
* │ │ ││
|
||||||
* │┌───────────────────────────┐│
|
* │ └─────────────┘│
|
||||||
* ││ ││
|
* │ ┌─────────┐│
|
||||||
* ││ ││
|
* │ │ ││
|
||||||
* ││ ││
|
* │ │ ││
|
||||||
* │└───────────────────────────┘│
|
* │ │ ││
|
||||||
* │┌───────────────────────────┐│
|
* │ └─────────┘│
|
||||||
* ││ ││
|
* │┌───────────────────────────┐│
|
||||||
* ││ ││
|
* ││ ││
|
||||||
* ││ ││
|
* ││ ││
|
||||||
* │└───────────────────────────┘│
|
* ││ ││
|
||||||
* └─────────────────────────────┘
|
* │└───────────────────────────┘│
|
||||||
* ```
|
* └─────────────────────────────┘
|
||||||
*/
|
* ```
|
||||||
FILL
|
*/
|
||||||
}
|
TRAILING,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The arranged subviews fill the perpendicular axis of the stack view.
|
||||||
|
* ```
|
||||||
|
* ┌─────────────────────────────┐
|
||||||
|
* │┌───────────────────────────┐│
|
||||||
|
* ││ ││
|
||||||
|
* ││ ││
|
||||||
|
* ││ ││
|
||||||
|
* │└───────────────────────────┘│
|
||||||
|
* │┌───────────────────────────┐│
|
||||||
|
* ││ ││
|
||||||
|
* ││ ││
|
||||||
|
* ││ ││
|
||||||
|
* │└───────────────────────────┘│
|
||||||
|
* │┌───────────────────────────┐│
|
||||||
|
* ││ ││
|
||||||
|
* ││ ││
|
||||||
|
* ││ ││
|
||||||
|
* │└───────────────────────────┘│
|
||||||
|
* └─────────────────────────────┘
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
FILL
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ import net.shadowfacts.cacao.util.texture.Texture
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class TextureView(var texture: Texture?): View() {
|
class TextureView(var texture: Texture?) : View() {
|
||||||
|
|
||||||
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
texture?.also {
|
texture?.also {
|
||||||
RenderHelper.draw(matrixStack, bounds, it)
|
RenderHelper.draw(matrixStack, bounds, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,502 +20,521 @@ import kotlin.math.floor
|
||||||
*
|
*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
open class View(): Responder {
|
open class View() : Responder {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The window whose view hierarchy this view belongs to.
|
* The window whose view hierarchy this view belongs to.
|
||||||
* Not initialized until the root view in this hierarchy has been added to a hierarchy,
|
* Not initialized until the root view in this hierarchy has been added to a hierarchy,
|
||||||
* using it before that will throw a runtime exception.
|
* using it before that will throw a runtime exception.
|
||||||
*/
|
*/
|
||||||
override var window: Window? = null
|
override var window: Window? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The next responder after this one.
|
* The next responder after this one.
|
||||||
* For views, the next responder is the view's superview.
|
* For views, the next responder is the view's superview.
|
||||||
*/
|
*/
|
||||||
override val nextResponder: Responder?
|
override val nextResponder: Responder?
|
||||||
// todo: should the view controller be a Responder?
|
// todo: should the view controller be a Responder?
|
||||||
get() = superview
|
get() = superview
|
||||||
|
|
||||||
private val solverDelegate = ObservableLateInitProperty<Solver> {
|
private val solverDelegate = ObservableLateInitProperty<Solver> {
|
||||||
for (v in subviews) {
|
for (v in subviews) {
|
||||||
v.solver = it
|
v.solver = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* The constraint solver used by the [Window] this view belongs to.
|
|
||||||
* Not initialized until [wasAdded] called, using it before that will throw a runtime exception.
|
|
||||||
*/
|
|
||||||
var solver: Solver by solverDelegate
|
|
||||||
|
|
||||||
val hasSolver: Boolean
|
/**
|
||||||
get() = solverDelegate.isInitialized
|
* The constraint solver used by the [Window] this view belongs to.
|
||||||
|
* Not initialized until [wasAdded] called, using it before that will throw a runtime exception.
|
||||||
|
*/
|
||||||
|
var solver: Solver by solverDelegate
|
||||||
|
|
||||||
/**
|
val hasSolver: Boolean
|
||||||
* Layout anchor for the left edge of this view in the window's coordinate system.
|
get() = solverDelegate.isInitialized
|
||||||
*/
|
|
||||||
val leftAnchor = LayoutVariable(this, "left")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the right edge of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val rightAnchor = LayoutVariable(this, "right")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the top edge of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val topAnchor = LayoutVariable(this, "top")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the bottom edge of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val bottomAnchor = LayoutVariable(this, "bottom")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the width of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val widthAnchor = LayoutVariable(this, "width")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the height of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val heightAnchor = LayoutVariable(this, "height")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the center X position of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val centerXAnchor = LayoutVariable(this, "centerX")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the center Y position of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val centerYAnchor = LayoutVariable(this, "centerY")
|
|
||||||
|
|
||||||
private val _layoutGuides = LinkedList<LayoutGuide>()
|
/**
|
||||||
|
* Layout anchor for the left edge of this view in the window's coordinate system.
|
||||||
|
*/
|
||||||
|
val leftAnchor = LayoutVariable(this, "left")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All the layout guides attached to this view.
|
* Layout anchor for the right edge of this view in the window's coordinate system.
|
||||||
*
|
*/
|
||||||
* To add a layout guide, call [addLayoutGuide].
|
val rightAnchor = LayoutVariable(this, "right")
|
||||||
*
|
|
||||||
* @see LayoutGuide
|
|
||||||
*/
|
|
||||||
val layoutGuides: List<LayoutGuide> = _layoutGuides
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this view uses constraint-based layout.
|
* Layout anchor for the top edge of this view in the window's coordinate system.
|
||||||
* If `false`, the view's `frame` must be set manually and the layout anchors may not be used.
|
*/
|
||||||
* Note: some views (such as [StackView] require arranged subviews to use constraint based layout.
|
val topAnchor = LayoutVariable(this, "top")
|
||||||
*
|
|
||||||
* Default is `true`.
|
|
||||||
*/
|
|
||||||
var usesConstraintBasedLayout = true
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The rectangle for this view in the coordinate system of its superview view (or the window, if there is no superview).
|
* Layout anchor for the bottom edge of this view in the window's coordinate system.
|
||||||
* If using constraint based layout, this property has zero dimensions until [didLayout] called.
|
*/
|
||||||
* Otherwise, this must be set manually.
|
val bottomAnchor = LayoutVariable(this, "bottom")
|
||||||
* Setting this property updates the [bounds].
|
|
||||||
*/
|
|
||||||
var frame = Rect(0.0, 0.0, 0.0, 0.0)
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
bounds = Rect(Point.ORIGIN, value.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The rectangle for this view in its own coordinate system.
|
* Layout anchor for the width of this view in the window's coordinate system.
|
||||||
* If using constraint based layout, this property has zero dimensions until [didLayout] called.
|
*/
|
||||||
* Otherwise, this will be initialized when [frame] is set.
|
val widthAnchor = LayoutVariable(this, "width")
|
||||||
*/
|
|
||||||
var bounds = Rect(0.0, 0.0, 0.0, 0.0)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The position on the Z-axis of this view.
|
* Layout anchor for the height of this view in the window's coordinate system.
|
||||||
* Views are rendered from lowest Z index to highest. Clicks are handled from highest to lowest.
|
*/
|
||||||
*/
|
val heightAnchor = LayoutVariable(this, "height")
|
||||||
var zIndex: Double = 0.0
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The intrinsic size of this view's content. May be null if the view doesn't have any content or there is no
|
* Layout anchor for the center X position of this view in the window's coordinate system.
|
||||||
* intrinsic size.
|
*/
|
||||||
*
|
val centerXAnchor = LayoutVariable(this, "centerX")
|
||||||
* Setting this creates/updates [no.birkett.kiwi.Strength.MEDIUM] constraints on this view's width/height using
|
|
||||||
* the size.
|
|
||||||
*/
|
|
||||||
var intrinsicContentSize: Size? = null
|
|
||||||
set(value) {
|
|
||||||
updateIntrinsicContentSizeConstraints(intrinsicContentSize, value)
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
private var intrinsicContentSizeWidthConstraint: Constraint? = null
|
|
||||||
private var intrinsicContentSizeHeightConstraint: Constraint? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The background color of this view.
|
* Layout anchor for the center Y position of this view in the window's coordinate system.
|
||||||
*/
|
*/
|
||||||
var backgroundColor = Color.CLEAR
|
val centerYAnchor = LayoutVariable(this, "centerY")
|
||||||
|
|
||||||
var respondsToDragging = false
|
private val _layoutGuides = LinkedList<LayoutGuide>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This view's parent view. If `null`, this view is a top-level view in the [Window].
|
* All the layout guides attached to this view.
|
||||||
*/
|
*
|
||||||
var superview: View? = null
|
* To add a layout guide, call [addLayoutGuide].
|
||||||
// _subviews is the internal, mutable object since we only want it to be mutated by the add/removeSubview methods
|
*
|
||||||
private val _subviews = LinkedList<View>()
|
* @see LayoutGuide
|
||||||
private var subviewsSortedByZIndex: List<View> = listOf()
|
*/
|
||||||
|
val layoutGuides: List<LayoutGuide> = _layoutGuides
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of all the subviews of this view.
|
* Whether this view uses constraint-based layout.
|
||||||
* This list should never by mutated directly, only by the [addSubview]/[removeSubview] methods.
|
* If `false`, the view's `frame` must be set manually and the layout anchors may not be used.
|
||||||
*/
|
* Note: some views (such as [StackView] require arranged subviews to use constraint based layout.
|
||||||
val subviews: List<View> = _subviews
|
*
|
||||||
|
* Default is `true`.
|
||||||
|
*/
|
||||||
|
var usesConstraintBasedLayout = true
|
||||||
|
|
||||||
constructor(frame: Rect): this() {
|
/**
|
||||||
this.usesConstraintBasedLayout = false
|
* The rectangle for this view in the coordinate system of its superview view (or the window, if there is no superview).
|
||||||
this.frame = frame
|
* If using constraint based layout, this property has zero dimensions until [didLayout] called.
|
||||||
}
|
* Otherwise, this must be set manually.
|
||||||
|
* Setting this property updates the [bounds].
|
||||||
|
*/
|
||||||
|
var frame = Rect(0.0, 0.0, 0.0, 0.0)
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
bounds = Rect(Point.ORIGIN, value.size)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method for retrieve the anchor for a specific position on the given axis.
|
* The rectangle for this view in its own coordinate system.
|
||||||
*/
|
* If using constraint based layout, this property has zero dimensions until [didLayout] called.
|
||||||
fun getAnchor(axis: Axis, position: AxisPosition): LayoutVariable {
|
* Otherwise, this will be initialized when [frame] is set.
|
||||||
return when (axis) {
|
*/
|
||||||
Axis.HORIZONTAL ->
|
var bounds = Rect(0.0, 0.0, 0.0, 0.0)
|
||||||
when (position) {
|
|
||||||
AxisPosition.LEADING -> leftAnchor
|
|
||||||
AxisPosition.CENTER -> centerXAnchor
|
|
||||||
AxisPosition.TRAILING -> rightAnchor
|
|
||||||
}
|
|
||||||
Axis.VERTICAL ->
|
|
||||||
when (position) {
|
|
||||||
AxisPosition.LEADING -> topAnchor
|
|
||||||
AxisPosition.CENTER -> centerYAnchor
|
|
||||||
AxisPosition.TRAILING -> bottomAnchor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given subview as a child of this view.
|
* The position on the Z-axis of this view.
|
||||||
*
|
* Views are rendered from lowest Z index to highest. Clicks are handled from highest to lowest.
|
||||||
* @param view The view to add.
|
*/
|
||||||
* @return The view that was added, as a convenience.
|
var zIndex: Double = 0.0
|
||||||
*/
|
|
||||||
fun <T: View> addSubview(view: T): T {
|
|
||||||
_subviews.add(view)
|
|
||||||
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
|
|
||||||
|
|
||||||
view.superview = this
|
/**
|
||||||
if (hasSolver) {
|
* The intrinsic size of this view's content. May be null if the view doesn't have any content or there is no
|
||||||
view.solver = solver
|
* intrinsic size.
|
||||||
}
|
*
|
||||||
view.window = window
|
* Setting this creates/updates [no.birkett.kiwi.Strength.MEDIUM] constraints on this view's width/height using
|
||||||
|
* the size.
|
||||||
|
*/
|
||||||
|
var intrinsicContentSize: Size? = null
|
||||||
|
set(value) {
|
||||||
|
updateIntrinsicContentSizeConstraints(intrinsicContentSize, value)
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
private var intrinsicContentSizeWidthConstraint: Constraint? = null
|
||||||
|
private var intrinsicContentSizeHeightConstraint: Constraint? = null
|
||||||
|
|
||||||
view.wasAdded()
|
/**
|
||||||
|
* The background color of this view.
|
||||||
|
*/
|
||||||
|
var backgroundColor = Color.CLEAR
|
||||||
|
|
||||||
return view
|
var respondsToDragging = false
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given view from this view's children and removes all constraints that connect the subview or any of
|
* This view's parent view. If `null`, this view is a top-level view in the [Window].
|
||||||
* its children (recursively) to a view outside of the subview's hierarchy. Constraints internal to the subview's
|
*/
|
||||||
* hierarchy (e.g., one between the subview and its child) will be left in place.
|
var superview: View? = null
|
||||||
*
|
|
||||||
* This method may be overridden by layout-providing views (such as [StackView]) to update its layout when a managed
|
|
||||||
* subview is removed.
|
|
||||||
*
|
|
||||||
* @param view The view to removed as a child of this view.
|
|
||||||
* @throws RuntimeException If the given [view] is not a subview of this view.
|
|
||||||
*/
|
|
||||||
open fun removeSubview(view: View) {
|
|
||||||
if (view.superview !== this) {
|
|
||||||
throw RuntimeException("Cannot remove subview whose superview is not this view")
|
|
||||||
}
|
|
||||||
|
|
||||||
_subviews.remove(view)
|
// _subviews is the internal, mutable object since we only want it to be mutated by the add/removeSubview methods
|
||||||
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
|
private val _subviews = LinkedList<View>()
|
||||||
|
private var subviewsSortedByZIndex: List<View> = listOf()
|
||||||
|
|
||||||
view.superview = null
|
/**
|
||||||
|
* The list of all the subviews of this view.
|
||||||
|
* This list should never by mutated directly, only by the [addSubview]/[removeSubview] methods.
|
||||||
|
*/
|
||||||
|
val subviews: List<View> = _subviews
|
||||||
|
|
||||||
// we need to remove constraints for this subview that cross the boundary between the subview and ourself
|
constructor(frame: Rect) : this() {
|
||||||
val constraintsToRemove = solver.constraints.filter { constraint ->
|
this.usesConstraintBasedLayout = false
|
||||||
val variables = constraint.getVariables().mapNotNull { it as? LayoutVariable }
|
this.frame = frame
|
||||||
|
}
|
||||||
|
|
||||||
for (a in 0 until variables.size - 1) {
|
/**
|
||||||
for (b in a + 1 until variables.size) {
|
* Helper method for retrieve the anchor for a specific position on the given axis.
|
||||||
// if the variable views have no common ancestor after the removed view's superview is unset,
|
*/
|
||||||
// the constraint crossed the this<->view boundary and should be removed
|
fun getAnchor(axis: Axis, position: AxisPosition): LayoutVariable {
|
||||||
val ancestor = LowestCommonAncestor.find(variables[a].viewOrLayoutGuideView, variables[b].viewOrLayoutGuideView, View::superview)
|
return when (axis) {
|
||||||
if (ancestor == null) {
|
Axis.HORIZONTAL ->
|
||||||
return@filter true
|
when (position) {
|
||||||
}
|
AxisPosition.LEADING -> leftAnchor
|
||||||
}
|
AxisPosition.CENTER -> centerXAnchor
|
||||||
}
|
AxisPosition.TRAILING -> rightAnchor
|
||||||
false
|
}
|
||||||
}
|
|
||||||
constraintsToRemove.forEach(solver::removeConstraint)
|
|
||||||
|
|
||||||
// todo: does this need to be reset
|
Axis.VERTICAL ->
|
||||||
|
when (position) {
|
||||||
|
AxisPosition.LEADING -> topAnchor
|
||||||
|
AxisPosition.CENTER -> centerYAnchor
|
||||||
|
AxisPosition.TRAILING -> bottomAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given subview as a child of this view.
|
||||||
|
*
|
||||||
|
* @param view The view to add.
|
||||||
|
* @return The view that was added, as a convenience.
|
||||||
|
*/
|
||||||
|
fun <T : View> addSubview(view: T): T {
|
||||||
|
_subviews.add(view)
|
||||||
|
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
|
||||||
|
|
||||||
|
view.superview = this
|
||||||
|
if (hasSolver) {
|
||||||
|
view.solver = solver
|
||||||
|
}
|
||||||
|
view.window = window
|
||||||
|
|
||||||
|
view.wasAdded()
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the given view from this view's children and removes all constraints that connect the subview or any of
|
||||||
|
* its children (recursively) to a view outside of the subview's hierarchy. Constraints internal to the subview's
|
||||||
|
* hierarchy (e.g., one between the subview and its child) will be left in place.
|
||||||
|
*
|
||||||
|
* This method may be overridden by layout-providing views (such as [StackView]) to update its layout when a managed
|
||||||
|
* subview is removed.
|
||||||
|
*
|
||||||
|
* @param view The view to removed as a child of this view.
|
||||||
|
* @throws RuntimeException If the given [view] is not a subview of this view.
|
||||||
|
*/
|
||||||
|
open fun removeSubview(view: View) {
|
||||||
|
if (view.superview !== this) {
|
||||||
|
throw RuntimeException("Cannot remove subview whose superview is not this view")
|
||||||
|
}
|
||||||
|
|
||||||
|
_subviews.remove(view)
|
||||||
|
subviewsSortedByZIndex = subviews.sortedBy(View::zIndex)
|
||||||
|
|
||||||
|
view.superview = null
|
||||||
|
|
||||||
|
// we need to remove constraints for this subview that cross the boundary between the subview and ourself
|
||||||
|
val constraintsToRemove = solver.constraints.filter { constraint ->
|
||||||
|
val variables = constraint.getVariables().mapNotNull { it as? LayoutVariable }
|
||||||
|
|
||||||
|
for (a in 0 until variables.size - 1) {
|
||||||
|
for (b in a + 1 until variables.size) {
|
||||||
|
// if the variable views have no common ancestor after the removed view's superview is unset,
|
||||||
|
// the constraint crossed the this<->view boundary and should be removed
|
||||||
|
val ancestor = LowestCommonAncestor.find(
|
||||||
|
variables[a].viewOrLayoutGuideView,
|
||||||
|
variables[b].viewOrLayoutGuideView,
|
||||||
|
View::superview
|
||||||
|
)
|
||||||
|
if (ancestor == null) {
|
||||||
|
return@filter true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
constraintsToRemove.forEach(solver::removeConstraint)
|
||||||
|
|
||||||
|
// todo: does this need to be reset
|
||||||
// view.solver = null
|
// view.solver = null
|
||||||
view.window = null
|
view.window = null
|
||||||
|
|
||||||
// todo: is this necessary?
|
// todo: is this necessary?
|
||||||
// view.wasRemoved()
|
// view.wasRemoved()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and returns a new layout guide with this view as its owner.
|
* Creates and returns a new layout guide with this view as its owner.
|
||||||
*/
|
*/
|
||||||
fun addLayoutGuide(): LayoutGuide {
|
fun addLayoutGuide(): LayoutGuide {
|
||||||
val guide = LayoutGuide(this)
|
val guide = LayoutGuide(this)
|
||||||
_layoutGuides.add(guide)
|
_layoutGuides.add(guide)
|
||||||
if (hasSolver) {
|
if (hasSolver) {
|
||||||
guide.attachTo(solver)
|
guide.attachTo(solver)
|
||||||
}
|
}
|
||||||
return guide
|
return guide
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes this view from its superview, if it has one.
|
* Removes this view from its superview, if it has one.
|
||||||
*/
|
*/
|
||||||
fun removeFromSuperview() {
|
fun removeFromSuperview() {
|
||||||
superview?.removeSubview(this)
|
superview?.removeSubview(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds all subviews that contain the given point.
|
* Finds all subviews that contain the given point.
|
||||||
*
|
*
|
||||||
* @param point The point to find subviews for, in the coordinate system of this view.
|
* @param point The point to find subviews for, in the coordinate system of this view.
|
||||||
* @return All views that contain the given point.
|
* @return All views that contain the given point.
|
||||||
*/
|
*/
|
||||||
fun subviewsAtPoint(point: Point): List<View> {
|
fun subviewsAtPoint(point: Point): List<View> {
|
||||||
return subviews.filter { point in it.frame }
|
return subviews.filter { point in it.frame }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to find a subview which contains the given point.
|
* Attempts to find a subview which contains the given point.
|
||||||
* If multiple subviews contain the given point, which one this method returns is undefined.
|
* If multiple subviews contain the given point, which one this method returns is undefined.
|
||||||
* [subviewsAtPoint] may be used, and the resulting List sorted by [View.zIndex].
|
* [subviewsAtPoint] may be used, and the resulting List sorted by [View.zIndex].
|
||||||
*
|
*
|
||||||
* @param point The point to find a subview for, in the coordinate system of this view.
|
* @param point The point to find a subview for, in the coordinate system of this view.
|
||||||
* @return The view, if any, that contains the given point.
|
* @return The view, if any, that contains the given point.
|
||||||
*/
|
*/
|
||||||
fun subviewAtPoint(point: Point): View? {
|
fun subviewAtPoint(point: Point): View? {
|
||||||
return subviews.firstOrNull { point in it.frame }
|
return subviews.firstOrNull { point in it.frame }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when this view was added to a view hierarchy.
|
* Called when this view was added to a view hierarchy.
|
||||||
* If overridden, the super-class method must be called.
|
* If overridden, the super-class method must be called.
|
||||||
*/
|
*/
|
||||||
open fun wasAdded() {
|
open fun wasAdded() {
|
||||||
createInternalConstraints()
|
createInternalConstraints()
|
||||||
updateIntrinsicContentSizeConstraints(null, intrinsicContentSize)
|
updateIntrinsicContentSizeConstraints(null, intrinsicContentSize)
|
||||||
|
|
||||||
layoutGuides.forEach {
|
layoutGuides.forEach {
|
||||||
it.attachTo(solver)
|
it.attachTo(solver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called during [wasAdded] to add any constraints to the [solver] that are internal to this view.
|
* Called during [wasAdded] to add any constraints to the [solver] that are internal to this view.
|
||||||
* If overridden, the super-class method must be called.
|
* If overridden, the super-class method must be called.
|
||||||
*/
|
*/
|
||||||
protected open fun createInternalConstraints() {
|
protected open fun createInternalConstraints() {
|
||||||
if (!usesConstraintBasedLayout) return
|
if (!usesConstraintBasedLayout) return
|
||||||
|
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
rightAnchor equalTo (leftAnchor + widthAnchor)
|
rightAnchor equalTo (leftAnchor + widthAnchor)
|
||||||
bottomAnchor equalTo (topAnchor + heightAnchor)
|
bottomAnchor equalTo (topAnchor + heightAnchor)
|
||||||
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
||||||
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) {
|
private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) {
|
||||||
if (!usesConstraintBasedLayout || !hasSolver) return
|
if (!usesConstraintBasedLayout || !hasSolver) return
|
||||||
|
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
solver.removeConstraint(intrinsicContentSizeWidthConstraint!!)
|
solver.removeConstraint(intrinsicContentSizeWidthConstraint!!)
|
||||||
solver.removeConstraint(intrinsicContentSizeHeightConstraint!!)
|
solver.removeConstraint(intrinsicContentSizeHeightConstraint!!)
|
||||||
}
|
}
|
||||||
if (new != null) {
|
if (new != null) {
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
this@View.intrinsicContentSizeWidthConstraint = (widthAnchor.equalTo(new.width, strength = MEDIUM))
|
this@View.intrinsicContentSizeWidthConstraint = (widthAnchor.equalTo(new.width, strength = MEDIUM))
|
||||||
this@View.intrinsicContentSizeHeightConstraint = (heightAnchor.equalTo(new.height, strength = MEDIUM))
|
this@View.intrinsicContentSizeHeightConstraint = (heightAnchor.equalTo(new.height, strength = MEDIUM))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after this view has been laid-out.
|
* Called after this view has been laid-out.
|
||||||
* If overridden, the super-class method must be called.
|
* If overridden, the super-class method must be called.
|
||||||
*/
|
*/
|
||||||
open fun didLayout() {
|
open fun didLayout() {
|
||||||
if (usesConstraintBasedLayout) {
|
if (usesConstraintBasedLayout) {
|
||||||
val superviewLeft = superview?.leftAnchor?.value ?: 0.0
|
val superviewLeft = superview?.leftAnchor?.value ?: 0.0
|
||||||
val superviewTop = superview?.topAnchor?.value ?: 0.0
|
val superviewTop = superview?.topAnchor?.value ?: 0.0
|
||||||
frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value)
|
frame = Rect(
|
||||||
bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value)
|
leftAnchor.value - superviewLeft,
|
||||||
}
|
topAnchor.value - superviewTop,
|
||||||
|
widthAnchor.value,
|
||||||
|
heightAnchor.value
|
||||||
|
)
|
||||||
|
bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value)
|
||||||
|
}
|
||||||
|
|
||||||
subviews.forEach(View::didLayout)
|
subviews.forEach(View::didLayout)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to draw this view.
|
* Called to draw this view.
|
||||||
* This method should not be called directly, it is called by the parent view/window.
|
* This method should not be called directly, it is called by the parent view/window.
|
||||||
* This method generally should not be overridden, but it is left open for internal framework use.
|
* This method generally should not be overridden, but it is left open for internal framework use.
|
||||||
* Use [drawContent] to draw any custom content.
|
* Use [drawContent] to draw any custom content.
|
||||||
*
|
*
|
||||||
* @param mouse The position of the mouse in the coordinate system of this view.
|
* @param mouse The position of the mouse in the coordinate system of this view.
|
||||||
* @param delta The time since the last frame.
|
* @param delta The time since the last frame.
|
||||||
*/
|
*/
|
||||||
open fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
open fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
matrixStack.push()
|
matrixStack.push()
|
||||||
matrixStack.translate(frame.left, frame.top, 0.0)
|
matrixStack.translate(frame.left, frame.top, 0.0)
|
||||||
|
|
||||||
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
||||||
|
|
||||||
drawContent(matrixStack, mouse, delta)
|
drawContent(matrixStack, mouse, delta)
|
||||||
|
|
||||||
subviewsSortedByZIndex.forEach {
|
subviewsSortedByZIndex.forEach {
|
||||||
val mouseInView = convert(mouse, to = it)
|
val mouseInView = convert(mouse, to = it)
|
||||||
it.draw(matrixStack, mouseInView, delta)
|
it.draw(matrixStack, mouseInView, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
matrixStack.pop()
|
matrixStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called during [draw] to draw content that's part of this view.
|
* Called during [draw] to draw content that's part of this view.
|
||||||
* During this method, the OpenGL coordinate system has been translated so the origin is at the top left corner
|
* During this method, the OpenGL coordinate system has been translated so the origin is at the top left corner
|
||||||
* of this view. Be careful not to translate additionally, and not to draw outside the [bounds] of the view.
|
* of this view. Be careful not to translate additionally, and not to draw outside the [bounds] of the view.
|
||||||
*
|
*
|
||||||
* @param mouse The position of the mouse in the coordinate system of this view.
|
* @param mouse The position of the mouse in the coordinate system of this view.
|
||||||
* @param delta The time since the last frame.
|
* @param delta The time since the last frame.
|
||||||
*/
|
*/
|
||||||
open fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {}
|
open fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when this view is clicked.
|
* Called when this view is clicked.
|
||||||
*
|
*
|
||||||
* The base implementation of this method forwards the click event to the first subview (sorted by [zIndex]) that
|
* The base implementation of this method forwards the click event to the first subview (sorted by [zIndex]) that
|
||||||
* contains the clicked point. Additionally, any subviews of this view that do not contain the clicked point receive
|
* contains the clicked point. Additionally, any subviews of this view that do not contain the clicked point receive
|
||||||
* the [mouseClickedOutside] event. If multiple views contain the point, any after one that returns `true` from this
|
* the [mouseClickedOutside] event. If multiple views contain the point, any after one that returns `true` from this
|
||||||
* method will not receive the event or the click-outside event.
|
* method will not receive the event or the click-outside event.
|
||||||
*
|
*
|
||||||
* If overridden, the super-class method does not have to be called. Intentionally not calling it may be used
|
* If overridden, the super-class method does not have to be called. Intentionally not calling it may be used
|
||||||
* to prevent [subviews] from receiving click events.
|
* to prevent [subviews] from receiving click events.
|
||||||
*
|
*
|
||||||
* @param point The point in the coordinate system of this view that the mouse was clicked.
|
* @param point The point in the coordinate system of this view that the mouse was clicked.
|
||||||
* @param mouseButton The mouse button used to click.
|
* @param mouseButton The mouse button used to click.
|
||||||
* @return Whether the mouse click was handled by this view or any subviews.
|
* @return Whether the mouse click was handled by this view or any subviews.
|
||||||
*/
|
*/
|
||||||
open fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
open fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
val (inside, outside) = subviews.partition { point in it.frame }
|
val (inside, outside) = subviews.partition { point in it.frame }
|
||||||
val view = inside.maxByOrNull(View::zIndex)
|
val view = inside.maxByOrNull(View::zIndex)
|
||||||
var result = false
|
var result = false
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val pointInView = convert(point, to = view)
|
val pointInView = convert(point, to = view)
|
||||||
result = view.mouseClicked(pointInView, mouseButton)
|
result = view.mouseClicked(pointInView, mouseButton)
|
||||||
}
|
}
|
||||||
for (v in outside) {
|
for (v in outside) {
|
||||||
val pointInV = convert(point, to = v)
|
val pointInV = convert(point, to = v)
|
||||||
v.mouseClickedOutside(pointInV, mouseButton)
|
v.mouseClickedOutside(pointInV, mouseButton)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the mouse was clicked outside this view.
|
* Called when the mouse was clicked outside this view.
|
||||||
*
|
*
|
||||||
* The base implementation of this method simply forwards the event to all of this view's subviews.
|
* The base implementation of this method simply forwards the event to all of this view's subviews.
|
||||||
*
|
*
|
||||||
* @param point The clicked point _in the coordinate space of this view_.
|
* @param point The clicked point _in the coordinate space of this view_.
|
||||||
* @param mouseButton The mouse button used to click.
|
* @param mouseButton The mouse button used to click.
|
||||||
*/
|
*/
|
||||||
open fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
open fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
||||||
for (view in subviews) {
|
for (view in subviews) {
|
||||||
val pointInView = convert(point, to = view)
|
val pointInView = convert(point, to = view)
|
||||||
view.mouseClickedOutside(pointInView, mouseButton)
|
view.mouseClickedOutside(pointInView, mouseButton)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean {
|
open fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean {
|
||||||
val view = subviewsAtPoint(startPoint).maxByOrNull(View::zIndex)
|
val view = subviewsAtPoint(startPoint).maxByOrNull(View::zIndex)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val startInView = convert(startPoint, to = view)
|
val startInView = convert(startPoint, to = view)
|
||||||
return view.mouseDragged(startInView, delta, mouseButton)
|
return view.mouseDragged(startInView, delta, mouseButton)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun mouseDragEnded(point: Point, mouseButton: MouseButton) {
|
open fun mouseDragEnded(point: Point, mouseButton: MouseButton) {
|
||||||
val view = subviewsAtPoint(point).maxByOrNull(View::zIndex)
|
val view = subviewsAtPoint(point).maxByOrNull(View::zIndex)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val pointInView = convert(point, to = view)
|
val pointInView = convert(point, to = view)
|
||||||
return view.mouseDragEnded(pointInView, mouseButton)
|
return view.mouseDragEnded(pointInView, mouseButton)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun mouseScrolled(point: Point, amount: Double): Boolean {
|
open fun mouseScrolled(point: Point, amount: Double): Boolean {
|
||||||
val view = subviewsAtPoint(point).maxByOrNull(View::zIndex)
|
val view = subviewsAtPoint(point).maxByOrNull(View::zIndex)
|
||||||
if (view != null) {
|
if (view != null) {
|
||||||
val pointInView = convert(point, to = view)
|
val pointInView = convert(point, to = view)
|
||||||
return view.mouseScrolled(pointInView, amount)
|
return view.mouseScrolled(pointInView, amount)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given point in this view's coordinate system to the coordinate system of another view or the window.
|
* Converts the given point in this view's coordinate system to the coordinate system of another view or the window.
|
||||||
*
|
*
|
||||||
* @param point The point to convert, in the coordinate system of this view.
|
* @param point The point to convert, in the coordinate system of this view.
|
||||||
* @param to The view to convert to. If `null`, it will be converted to the window's coordinate system.
|
* @param to The view to convert to. If `null`, it will be converted to the window's coordinate system.
|
||||||
* @return The point in the coordinate system of the [to] view.
|
* @return The point in the coordinate system of the [to] view.
|
||||||
*/
|
*/
|
||||||
fun convert(point: Point, to: View?): Point {
|
fun convert(point: Point, to: View?): Point {
|
||||||
if (to != null) {
|
if (to != null) {
|
||||||
val ancestor = LowestCommonAncestor.find(this, to, View::superview)
|
val ancestor = LowestCommonAncestor.find(this, to, View::superview)
|
||||||
@Suppress("NAME_SHADOWING") var point = point
|
@Suppress("NAME_SHADOWING") var point = point
|
||||||
|
|
||||||
// Convert up to the LCA
|
// Convert up to the LCA
|
||||||
var view: View? = this
|
var view: View? = this
|
||||||
while (view != null && view != ancestor) {
|
while (view != null && view != ancestor) {
|
||||||
point = Point(point.x + view.frame.left, point.y + view.frame.top)
|
point = Point(point.x + view.frame.left, point.y + view.frame.top)
|
||||||
view = view.superview
|
view = view.superview
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert back down to the other view
|
// Convert back down to the other view
|
||||||
view = to
|
view = to
|
||||||
while (view != null && view != ancestor) {
|
while (view != null && view != ancestor) {
|
||||||
point = Point(point.x - view.frame.left, point.y - view.frame.top)
|
point = Point(point.x - view.frame.left, point.y - view.frame.top)
|
||||||
view = view.superview
|
view = view.superview
|
||||||
}
|
}
|
||||||
|
|
||||||
return point
|
return point
|
||||||
} else {
|
} else {
|
||||||
return Point(leftAnchor.value + point.x, topAnchor.value + point.y)
|
return Point(leftAnchor.value + point.x, topAnchor.value + point.y)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given rectangle in this view's coordinate system to the coordinate system of another view or the window.
|
* Converts the given rectangle in this view's coordinate system to the coordinate system of another view or the window.
|
||||||
*
|
*
|
||||||
* @param rect The rectangle to convert, in the coordinate system of this view.
|
* @param rect The rectangle to convert, in the coordinate system of this view.
|
||||||
* @param to The view to convert to. If `null`, it will be converted to the window's coordinate system.
|
* @param to The view to convert to. If `null`, it will be converted to the window's coordinate system.
|
||||||
* @return The rectangle in the coordinate system of the [to] view.
|
* @return The rectangle in the coordinate system of the [to] view.
|
||||||
*/
|
*/
|
||||||
fun convert(rect: Rect, to: View?): Rect {
|
fun convert(rect: Rect, to: View?): Rect {
|
||||||
return Rect(convert(rect.origin, to), rect.size)
|
return Rect(convert(rect.origin, to), rect.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LayoutGuide.attachTo(solver: Solver) {
|
private fun LayoutGuide.attachTo(solver: Solver) {
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
rightAnchor equalTo (leftAnchor + widthAnchor)
|
rightAnchor equalTo (leftAnchor + widthAnchor)
|
||||||
bottomAnchor equalTo (topAnchor + heightAnchor)
|
bottomAnchor equalTo (topAnchor + heightAnchor)
|
||||||
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
||||||
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val LayoutVariable.viewOrLayoutGuideView: View
|
private val LayoutVariable.viewOrLayoutGuideView: View
|
||||||
get() = view ?: layoutGuide!!.owningView
|
get() = view ?: layoutGuide!!.owningView
|
||||||
|
|
|
@ -22,134 +22,136 @@ import kotlin.math.floor
|
||||||
* Will be added as a subview of the button and laid out using constraints.
|
* Will be added as a subview of the button and laid out using constraints.
|
||||||
* @param padding The padding between the [content] and the edges of the button.
|
* @param padding The padding between the [content] and the edges of the button.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractButton<Impl: AbstractButton<Impl>>(val content: View, val padding: Double = 4.0): View() {
|
abstract class AbstractButton<Impl : AbstractButton<Impl>>(val content: View, val padding: Double = 4.0) : View() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function that handles when this button is clicked.
|
* The function that handles when this button is clicked.
|
||||||
* The parameter is the type of the concrete button implementation that was used.
|
* The parameter is the type of the concrete button implementation that was used.
|
||||||
*/
|
*/
|
||||||
var handler: ((Impl) -> Unit)? = null
|
var handler: ((Impl) -> Unit)? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the button is disabled.
|
* Whether the button is disabled.
|
||||||
* Disabled buttons have a different background ([disabledBackground]) and do not receive click events.
|
* Disabled buttons have a different background ([disabledBackground]) and do not receive click events.
|
||||||
*/
|
*/
|
||||||
var disabled = false
|
var disabled = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The normal background view to draw behind the button content. It will be added as a subview during [wasAdded],
|
* The normal background view to draw behind the button content. It will be added as a subview during [wasAdded],
|
||||||
* so all background view properties must be specified prior to the button being added to a view hierarchy.
|
* so all background view properties must be specified prior to the button being added to a view hierarchy.
|
||||||
*
|
*
|
||||||
* The background will fill the entire button (going beneath the content [padding]).
|
* The background will fill the entire button (going beneath the content [padding]).
|
||||||
* There are also [hoveredBackground] and [disabledBackground] for those states.
|
* There are also [hoveredBackground] and [disabledBackground] for those states.
|
||||||
* If a [backgroundColor] is specified, it will be drawn behind the background View and thus not visible
|
* If a [backgroundColor] is specified, it will be drawn behind the background View and thus not visible
|
||||||
* unless the background view is not fully opaque.
|
* unless the background view is not fully opaque.
|
||||||
*/
|
*/
|
||||||
var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG)
|
var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG)
|
||||||
set(value) {
|
set(value) {
|
||||||
field?.removeFromSuperview()
|
field?.removeFromSuperview()
|
||||||
field = value
|
field = value
|
||||||
value?.also(::addBackground)
|
value?.also(::addBackground)
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* The background to draw when the button is hovered over by the mouse.
|
|
||||||
* If `null`, the normal [background] will be used.
|
|
||||||
* @see background
|
|
||||||
*/
|
|
||||||
var hoveredBackground: View? = NinePatchView(NinePatchTexture.BUTTON_HOVERED_BG)
|
|
||||||
set(value) {
|
|
||||||
field?.removeFromSuperview()
|
|
||||||
field = value
|
|
||||||
value?.also(::addBackground)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* The background to draw when the button is [disabled].
|
|
||||||
* If `null`, the normal [background] will be used.
|
|
||||||
* @see background
|
|
||||||
*/
|
|
||||||
var disabledBackground: View? = NinePatchView(NinePatchTexture.BUTTON_DISABLED_BG)
|
|
||||||
set(value) {
|
|
||||||
field?.removeFromSuperview()
|
|
||||||
field = value
|
|
||||||
value?.also(::addBackground)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tooltip text shown when this button is hovered.
|
* The background to draw when the button is hovered over by the mouse.
|
||||||
*/
|
* If `null`, the normal [background] will be used.
|
||||||
var tooltip: Text? = null
|
* @see background
|
||||||
|
*/
|
||||||
|
var hoveredBackground: View? = NinePatchView(NinePatchTexture.BUTTON_HOVERED_BG)
|
||||||
|
set(value) {
|
||||||
|
field?.removeFromSuperview()
|
||||||
|
field = value
|
||||||
|
value?.also(::addBackground)
|
||||||
|
}
|
||||||
|
|
||||||
override fun wasAdded() {
|
/**
|
||||||
solver.dsl {
|
* The background to draw when the button is [disabled].
|
||||||
addSubview(content)
|
* If `null`, the normal [background] will be used.
|
||||||
content.centerXAnchor equalTo centerXAnchor
|
* @see background
|
||||||
content.centerYAnchor equalTo centerYAnchor
|
*/
|
||||||
|
var disabledBackground: View? = NinePatchView(NinePatchTexture.BUTTON_DISABLED_BG)
|
||||||
|
set(value) {
|
||||||
|
field?.removeFromSuperview()
|
||||||
|
field = value
|
||||||
|
value?.also(::addBackground)
|
||||||
|
}
|
||||||
|
|
||||||
content.leftAnchor.lessThanOrEqualTo((leftAnchor + padding), WEAK)
|
/**
|
||||||
content.rightAnchor.greaterThanOrEqualTo(rightAnchor - padding, WEAK)
|
* The tooltip text shown when this button is hovered.
|
||||||
content.topAnchor.lessThanOrEqualTo(topAnchor + padding, WEAK)
|
*/
|
||||||
content.bottomAnchor.greaterThanOrEqualTo(bottomAnchor - padding, WEAK)
|
var tooltip: Text? = null
|
||||||
}
|
|
||||||
|
|
||||||
listOfNotNull(background, hoveredBackground, disabledBackground).forEach(::addBackground)
|
override fun wasAdded() {
|
||||||
|
solver.dsl {
|
||||||
|
addSubview(content)
|
||||||
|
content.centerXAnchor equalTo centerXAnchor
|
||||||
|
content.centerYAnchor equalTo centerYAnchor
|
||||||
|
|
||||||
super.wasAdded()
|
content.leftAnchor.lessThanOrEqualTo((leftAnchor + padding), WEAK)
|
||||||
}
|
content.rightAnchor.greaterThanOrEqualTo(rightAnchor - padding, WEAK)
|
||||||
|
content.topAnchor.lessThanOrEqualTo(topAnchor + padding, WEAK)
|
||||||
|
content.bottomAnchor.greaterThanOrEqualTo(bottomAnchor - padding, WEAK)
|
||||||
|
}
|
||||||
|
|
||||||
private fun addBackground(view: View) {
|
listOfNotNull(background, hoveredBackground, disabledBackground).forEach(::addBackground)
|
||||||
if (superview != null && hasSolver) {
|
|
||||||
addSubview(view)
|
|
||||||
solver.dsl {
|
|
||||||
view.leftAnchor equalTo leftAnchor
|
|
||||||
view.rightAnchor equalTo rightAnchor
|
|
||||||
view.topAnchor equalTo topAnchor
|
|
||||||
view.bottomAnchor equalTo bottomAnchor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
super.wasAdded()
|
||||||
matrixStack.push()
|
}
|
||||||
matrixStack.translate(frame.left, frame.top, 0.0)
|
|
||||||
|
|
||||||
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
private fun addBackground(view: View) {
|
||||||
|
if (superview != null && hasSolver) {
|
||||||
|
addSubview(view)
|
||||||
|
solver.dsl {
|
||||||
|
view.leftAnchor equalTo leftAnchor
|
||||||
|
view.rightAnchor equalTo rightAnchor
|
||||||
|
view.topAnchor equalTo topAnchor
|
||||||
|
view.bottomAnchor equalTo bottomAnchor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// don't need to convert mouse to background coordinate system
|
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
// the edges are all pinned, so the coordinate space is the same
|
matrixStack.push()
|
||||||
getCurrentBackground(mouse)?.draw(matrixStack, mouse, delta)
|
matrixStack.translate(frame.left, frame.top, 0.0)
|
||||||
|
|
||||||
val mouseInContent = convert(mouse, to = content)
|
RenderHelper.fill(matrixStack, bounds, backgroundColor)
|
||||||
content.draw(matrixStack, mouseInContent, delta)
|
|
||||||
|
|
||||||
// don't draw subviews, otherwise all background views + content will get drawn
|
// don't need to convert mouse to background coordinate system
|
||||||
|
// the edges are all pinned, so the coordinate space is the same
|
||||||
|
getCurrentBackground(mouse)?.draw(matrixStack, mouse, delta)
|
||||||
|
|
||||||
matrixStack.pop()
|
val mouseInContent = convert(mouse, to = content)
|
||||||
|
content.draw(matrixStack, mouseInContent, delta)
|
||||||
|
|
||||||
if (tooltip != null && mouse in bounds) {
|
// don't draw subviews, otherwise all background views + content will get drawn
|
||||||
window!!.drawTooltip(listOf(tooltip!!))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
matrixStack.pop()
|
||||||
if (disabled) return false
|
|
||||||
|
|
||||||
// We can perform an unchecked cast here because we are certain that Impl will be the concrete implementation
|
if (tooltip != null && mouse in bounds) {
|
||||||
// of AbstractButton.
|
window!!.drawTooltip(listOf(tooltip!!))
|
||||||
// For example, an implementing class may be defined as such: `class Button: AbstractButton<Button>`
|
}
|
||||||
@Suppress("UNCHECKED_CAST")
|
}
|
||||||
handler?.invoke(this as Impl)
|
|
||||||
|
|
||||||
return true
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
}
|
if (disabled) return false
|
||||||
|
|
||||||
protected open fun getCurrentBackground(mouse: Point): View? {
|
// We can perform an unchecked cast here because we are certain that Impl will be the concrete implementation
|
||||||
return if (disabled) {
|
// of AbstractButton.
|
||||||
disabledBackground ?: background
|
// For example, an implementing class may be defined as such: `class Button: AbstractButton<Button>`
|
||||||
} else if (mouse in bounds) {
|
@Suppress("UNCHECKED_CAST")
|
||||||
hoveredBackground ?: background
|
handler?.invoke(this as Impl)
|
||||||
} else {
|
|
||||||
background
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
protected open fun getCurrentBackground(mouse: Point): View? {
|
||||||
|
return if (disabled) {
|
||||||
|
disabledBackground ?: background
|
||||||
|
} else if (mouse in bounds) {
|
||||||
|
hoveredBackground ?: background
|
||||||
|
} else {
|
||||||
|
background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,11 @@ import net.shadowfacts.cacao.view.View
|
||||||
* @param handler The handler function to invoke when this button is pressed.
|
* @param handler The handler function to invoke when this button is pressed.
|
||||||
*/
|
*/
|
||||||
class Button(
|
class Button(
|
||||||
content: View,
|
content: View,
|
||||||
padding: Double = 4.0,
|
padding: Double = 4.0,
|
||||||
handler: ((Button) -> Unit)? = null
|
handler: ((Button) -> Unit)? = null
|
||||||
): AbstractButton<Button>(content, padding) {
|
) : AbstractButton<Button>(content, padding) {
|
||||||
init {
|
init {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,62 +33,62 @@ import net.shadowfacts.kiwidsl.dsl
|
||||||
* Positioning of content views is handled by the dropdown.
|
* Positioning of content views is handled by the dropdown.
|
||||||
* @param updateView A function for updating the view used as the button's 'label' that's visible even when the dropdown isn't.
|
* @param updateView A function for updating the view used as the button's 'label' that's visible even when the dropdown isn't.
|
||||||
*/
|
*/
|
||||||
class DropdownButton<Value, ContentView: View>(
|
class DropdownButton<Value, ContentView : View>(
|
||||||
val initialValue: Value,
|
val initialValue: Value,
|
||||||
val allValues: Iterable<Value>,
|
val allValues: Iterable<Value>,
|
||||||
val createView: (Value) -> ContentView,
|
val createView: (Value) -> ContentView,
|
||||||
val updateView: (newValue: Value, view: ContentView) -> Unit,
|
val updateView: (newValue: Value, view: ContentView) -> Unit,
|
||||||
padding: Double = 4.0
|
padding: Double = 4.0
|
||||||
): AbstractButton<DropdownButton<Value, ContentView>>(
|
) : AbstractButton<DropdownButton<Value, ContentView>>(
|
||||||
StackView(Axis.HORIZONTAL),
|
StackView(Axis.HORIZONTAL),
|
||||||
padding
|
padding
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DROPDOWN_INDICATOR = Texture(Identifier("asmr", "textures/gui/dropdown.png"), 0, 0)
|
val DROPDOWN_INDICATOR = Texture(Identifier("asmr", "textures/gui/dropdown.png"), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val stackView: StackView
|
private val stackView: StackView
|
||||||
get() = content as StackView
|
get() = content as StackView
|
||||||
|
|
||||||
private val contentView: ContentView
|
private val contentView: ContentView
|
||||||
get() = stackView.arrangedSubviews.first() as ContentView
|
get() = stackView.arrangedSubviews.first() as ContentView
|
||||||
|
|
||||||
private lateinit var dropdownIndicator: TextureView
|
private lateinit var dropdownIndicator: TextureView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently selected [Value] of the dropdown.
|
* The currently selected [Value] of the dropdown.
|
||||||
*/
|
*/
|
||||||
var value: Value = initialValue
|
var value: Value = initialValue
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
updateView(value, contentView)
|
updateView(value, contentView)
|
||||||
// todo: setNeedsLayout instead of force unwrapping window
|
// todo: setNeedsLayout instead of force unwrapping window
|
||||||
window!!.layout()
|
window!!.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun wasAdded() {
|
override fun wasAdded() {
|
||||||
super.wasAdded()
|
super.wasAdded()
|
||||||
|
|
||||||
stackView.addArrangedSubview(createView(initialValue))
|
stackView.addArrangedSubview(createView(initialValue))
|
||||||
dropdownIndicator = stackView.addArrangedSubview(TextureView(DROPDOWN_INDICATOR))
|
dropdownIndicator = stackView.addArrangedSubview(TextureView(DROPDOWN_INDICATOR))
|
||||||
|
|
||||||
solver.dsl {
|
solver.dsl {
|
||||||
dropdownIndicator.widthAnchor equalTo 9
|
dropdownIndicator.widthAnchor equalTo 9
|
||||||
dropdownIndicator.heightAnchor equalTo 9
|
dropdownIndicator.heightAnchor equalTo 9
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
return if (mouseButton == MouseButton.LEFT || mouseButton == MouseButton.RIGHT) {
|
return if (mouseButton == MouseButton.LEFT || mouseButton == MouseButton.RIGHT) {
|
||||||
showDropdown()
|
showDropdown()
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
super.mouseClicked(point, mouseButton)
|
super.mouseClicked(point, mouseButton)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showDropdown() {
|
private fun showDropdown() {
|
||||||
// val dropdownWindow = window.screen.addWindow(Window())
|
// val dropdownWindow = window.screen.addWindow(Window())
|
||||||
// val dropdownBackground = dropdownWindow.addView(NinePatchView(NinePatchTexture.BUTTON_BG).apply {
|
// val dropdownBackground = dropdownWindow.addView(NinePatchView(NinePatchTexture.BUTTON_BG).apply {
|
||||||
// zIndex = -1.0
|
// zIndex = -1.0
|
||||||
|
@ -131,76 +131,96 @@ class DropdownButton<Value, ContentView: View>(
|
||||||
// dropdownBackground.bottomAnchor equalTo stack.bottomAnchor
|
// dropdownBackground.bottomAnchor equalTo stack.bottomAnchor
|
||||||
// }
|
// }
|
||||||
// dropdownWindow.layout()
|
// dropdownWindow.layout()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun valueSelected(value: Value) {
|
private fun valueSelected(value: Value) {
|
||||||
this.value = value
|
this.value = value
|
||||||
handler?.invoke(this)
|
handler?.invoke(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DropdownItemBackgroundView(
|
private class DropdownItemBackgroundView(
|
||||||
private val first: Boolean,
|
private val first: Boolean,
|
||||||
private val last: Boolean,
|
private val last: Boolean,
|
||||||
ninePatch: NinePatchTexture
|
ninePatch: NinePatchTexture
|
||||||
): NinePatchView(ninePatch) {
|
) : NinePatchView(ninePatch) {
|
||||||
|
|
||||||
// Corners
|
// Corners
|
||||||
private val topLeftDelegate = ResettableLazyProperty {
|
private val topLeftDelegate = ResettableLazyProperty {
|
||||||
super.topLeft
|
super.topLeft
|
||||||
Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), if (first) ninePatch.cornerHeight.toDouble() else 0.0)
|
Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), if (first) ninePatch.cornerHeight.toDouble() else 0.0)
|
||||||
}
|
}
|
||||||
override val topLeft by topLeftDelegate
|
override val topLeft by topLeftDelegate
|
||||||
|
|
||||||
private val topRightDelegate = ResettableLazyProperty {
|
private val topRightDelegate = ResettableLazyProperty {
|
||||||
Rect(bounds.width - ninePatch.cornerWidth, 0.0, topLeft.width, topLeft.height)
|
Rect(bounds.width - ninePatch.cornerWidth, 0.0, topLeft.width, topLeft.height)
|
||||||
}
|
}
|
||||||
override val topRight by topRightDelegate
|
override val topRight by topRightDelegate
|
||||||
|
|
||||||
private val bottomLeftDelegate = ResettableLazyProperty {
|
private val bottomLeftDelegate = ResettableLazyProperty {
|
||||||
Rect(topLeft.left, bounds.height - ninePatch.cornerHeight, topLeft.width, if (last) ninePatch.cornerHeight.toDouble() else 0.0)
|
Rect(
|
||||||
}
|
topLeft.left,
|
||||||
override val bottomLeft by bottomLeftDelegate
|
bounds.height - ninePatch.cornerHeight,
|
||||||
|
topLeft.width,
|
||||||
|
if (last) ninePatch.cornerHeight.toDouble() else 0.0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val bottomLeft by bottomLeftDelegate
|
||||||
|
|
||||||
private val bottomRightDelegate = ResettableLazyProperty {
|
private val bottomRightDelegate = ResettableLazyProperty {
|
||||||
Rect(topRight.left, bottomLeft.top, topLeft.width, bottomLeft.height)
|
Rect(topRight.left, bottomLeft.top, topLeft.width, bottomLeft.height)
|
||||||
}
|
}
|
||||||
override val bottomRight by bottomRightDelegate
|
override val bottomRight by bottomRightDelegate
|
||||||
|
|
||||||
// Edges
|
// Edges
|
||||||
private val topMiddleDelegate = ResettableLazyProperty {
|
private val topMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, topLeft.height)
|
Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, topLeft.height)
|
||||||
}
|
}
|
||||||
override val topMiddle by topMiddleDelegate
|
override val topMiddle by topMiddleDelegate
|
||||||
|
|
||||||
private val bottomMiddleDelegate = ResettableLazyProperty {
|
private val bottomMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(topMiddle.left, bottomLeft.top, topMiddle.width, bottomLeft.height)
|
Rect(topMiddle.left, bottomLeft.top, topMiddle.width, bottomLeft.height)
|
||||||
}
|
}
|
||||||
override val bottomMiddle by bottomMiddleDelegate
|
override val bottomMiddle by bottomMiddleDelegate
|
||||||
|
|
||||||
private val leftMiddleDelegate = ResettableLazyProperty {
|
private val leftMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(topLeft.left, topLeft.bottom, topLeft.width, bounds.height - (if (first && last) 2 else if (first || last) 1 else 0) * ninePatch.cornerHeight)
|
Rect(
|
||||||
}
|
topLeft.left,
|
||||||
override val leftMiddle by leftMiddleDelegate
|
topLeft.bottom,
|
||||||
|
topLeft.width,
|
||||||
|
bounds.height - (if (first && last) 2 else if (first || last) 1 else 0) * ninePatch.cornerHeight
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override val leftMiddle by leftMiddleDelegate
|
||||||
|
|
||||||
private val rightMiddleDelegate = ResettableLazyProperty {
|
private val rightMiddleDelegate = ResettableLazyProperty {
|
||||||
Rect(topRight.left, topRight.bottom, topRight.width, leftMiddle.height)
|
Rect(topRight.left, topRight.bottom, topRight.width, leftMiddle.height)
|
||||||
}
|
}
|
||||||
override val rightMiddle by rightMiddleDelegate
|
override val rightMiddle by rightMiddleDelegate
|
||||||
|
|
||||||
// Center
|
// Center
|
||||||
private val centerDelegate = ResettableLazyProperty {
|
private val centerDelegate = ResettableLazyProperty {
|
||||||
Rect(topLeft.right, topMiddle.bottom, topMiddle.width, leftMiddle.height)
|
Rect(topLeft.right, topMiddle.bottom, topMiddle.width, leftMiddle.height)
|
||||||
}
|
}
|
||||||
override val center by centerDelegate
|
override val center by centerDelegate
|
||||||
|
|
||||||
private val delegates = listOf(topLeftDelegate, topRightDelegate, bottomLeftDelegate, bottomRightDelegate, topMiddleDelegate, bottomMiddleDelegate, leftMiddleDelegate, rightMiddleDelegate, centerDelegate)
|
private val delegates = listOf(
|
||||||
|
topLeftDelegate,
|
||||||
|
topRightDelegate,
|
||||||
|
bottomLeftDelegate,
|
||||||
|
bottomRightDelegate,
|
||||||
|
topMiddleDelegate,
|
||||||
|
bottomMiddleDelegate,
|
||||||
|
leftMiddleDelegate,
|
||||||
|
rightMiddleDelegate,
|
||||||
|
centerDelegate
|
||||||
|
)
|
||||||
|
|
||||||
override fun didLayout() {
|
override fun didLayout() {
|
||||||
super.didLayout()
|
super.didLayout()
|
||||||
|
|
||||||
delegates.forEach(ResettableLazyProperty<Rect>::reset)
|
delegates.forEach(ResettableLazyProperty<Rect>::reset)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -16,38 +16,38 @@ import net.shadowfacts.cacao.view.Label
|
||||||
* @param initialValue The initial enum value for this button.
|
* @param initialValue The initial enum value for this button.
|
||||||
* @param localizer A function that takes an enum value and converts into a [Text] for the button's label.
|
* @param localizer A function that takes an enum value and converts into a [Text] for the button's label.
|
||||||
*/
|
*/
|
||||||
class EnumButton<E: Enum<E>>(
|
class EnumButton<E : Enum<E>>(
|
||||||
initialValue: E,
|
initialValue: E,
|
||||||
val localizer: (E) -> Text
|
val localizer: (E) -> Text
|
||||||
): AbstractButton<EnumButton<E>>(
|
) : AbstractButton<EnumButton<E>>(
|
||||||
Label(localizer(initialValue), shadow = true)
|
Label(localizer(initialValue), shadow = true)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val label: Label
|
private val label: Label
|
||||||
get() = content as Label
|
get() = content as Label
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current value of the enum button.
|
* The current value of the enum button.
|
||||||
* Updating this property will use the [localizer] to update the label.
|
* Updating this property will use the [localizer] to update the label.
|
||||||
*/
|
*/
|
||||||
var value: E = initialValue
|
var value: E = initialValue
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
label.text = localizer(value)
|
label.text = localizer(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
value = when (mouseButton) {
|
value = when (mouseButton) {
|
||||||
MouseButton.LEFT -> EnumHelper.next(value)
|
MouseButton.LEFT -> EnumHelper.next(value)
|
||||||
MouseButton.RIGHT -> EnumHelper.previous(value)
|
MouseButton.RIGHT -> EnumHelper.previous(value)
|
||||||
else -> {
|
else -> {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.mouseClicked(point, mouseButton)
|
return super.mouseClicked(point, mouseButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,54 +17,54 @@ import net.shadowfacts.cacao.view.View
|
||||||
* @param handler The handler function to invoke when this button is pressed.
|
* @param handler The handler function to invoke when this button is pressed.
|
||||||
*/
|
*/
|
||||||
class ToggleButton(
|
class ToggleButton(
|
||||||
initialState: Boolean,
|
initialState: Boolean,
|
||||||
handler: ((ToggleButton) -> Unit)? = null,
|
handler: ((ToggleButton) -> Unit)? = null,
|
||||||
): AbstractButton<ToggleButton>(TextureView(if (initialState) ON else OFF), padding = 0.0) {
|
) : AbstractButton<ToggleButton>(TextureView(if (initialState) ON else OFF), padding = 0.0) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val OFF = Texture(Identifier("textures/gui/checkbox.png"), 0, 0, 64, 64)
|
val OFF = Texture(Identifier("textures/gui/checkbox.png"), 0, 0, 64, 64)
|
||||||
val OFF_HOVERED = Texture(Identifier("textures/gui/checkbox.png"), 20, 0, 64, 64)
|
val OFF_HOVERED = Texture(Identifier("textures/gui/checkbox.png"), 20, 0, 64, 64)
|
||||||
val ON = Texture(Identifier("textures/gui/checkbox.png"), 0, 20, 64, 64)
|
val ON = Texture(Identifier("textures/gui/checkbox.png"), 0, 20, 64, 64)
|
||||||
val ON_HOVERED = Texture(Identifier("textures/gui/checkbox.png"), 20, 20, 64, 64)
|
val ON_HOVERED = Texture(Identifier("textures/gui/checkbox.png"), 20, 20, 64, 64)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val textureView: TextureView
|
private val textureView: TextureView
|
||||||
get() = content as TextureView
|
get() = content as TextureView
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The button's current on/off state.
|
* The button's current on/off state.
|
||||||
* Updating this property updates the button's texture.
|
* Updating this property updates the button's texture.
|
||||||
*/
|
*/
|
||||||
var state: Boolean = initialState
|
var state: Boolean = initialState
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
intrinsicContentSize = Size(20.0, 20.0)
|
intrinsicContentSize = Size(20.0, 20.0)
|
||||||
|
|
||||||
background = null
|
background = null
|
||||||
disabledBackground = null
|
disabledBackground = null
|
||||||
hoveredBackground = null
|
hoveredBackground = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
if (!disabled && (mouseButton == MouseButton.LEFT || mouseButton == MouseButton.RIGHT)) {
|
if (!disabled && (mouseButton == MouseButton.LEFT || mouseButton == MouseButton.RIGHT)) {
|
||||||
state = !state
|
state = !state
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.mouseClicked(point, mouseButton)
|
return super.mouseClicked(point, mouseButton)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
val hovered = mouse in bounds
|
val hovered = mouse in bounds
|
||||||
textureView.texture = if (state) {
|
textureView.texture = if (state) {
|
||||||
if (hovered) ON_HOVERED else ON
|
if (hovered) ON_HOVERED else ON
|
||||||
} else {
|
} else {
|
||||||
if (hovered) OFF_HOVERED else OFF
|
if (hovered) OFF_HOVERED else OFF
|
||||||
}
|
}
|
||||||
|
|
||||||
super.draw(matrixStack, mouse, delta)
|
super.draw(matrixStack, mouse, delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCurrentBackground(mouse: Point) = null
|
override fun getCurrentBackground(mouse: Point) = null
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,157 +20,158 @@ import org.lwjgl.glfw.GLFW
|
||||||
* the exact type of text field.
|
* the exact type of text field.
|
||||||
* @param initialText The initial value of the text field.
|
* @param initialText The initial value of the text field.
|
||||||
*/
|
*/
|
||||||
abstract class AbstractTextField<Impl: AbstractTextField<Impl>>(
|
abstract class AbstractTextField<Impl : AbstractTextField<Impl>>(
|
||||||
initialText: String
|
initialText: String
|
||||||
): View() {
|
) : View() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function that is invoked when the text in this text field changes.
|
* A function that is invoked when the text in this text field changes.
|
||||||
*/
|
*/
|
||||||
var handler: ((Impl) -> Unit)? = null
|
var handler: ((Impl) -> Unit)? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the text field is disabled.
|
* Whether the text field is disabled.
|
||||||
* Disabled text fields cannot be interacted with.
|
* Disabled text fields cannot be interacted with.
|
||||||
*/
|
*/
|
||||||
var disabled = false
|
var disabled = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this text field is focused (i.e. [isFirstResponder]) and receives key events.
|
* Whether this text field is focused (i.e. [isFirstResponder]) and receives key events.
|
||||||
*/
|
*/
|
||||||
val focused: Boolean
|
val focused: Boolean
|
||||||
get() = isFirstResponder
|
get() = isFirstResponder
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current text of this text field.
|
* The current text of this text field.
|
||||||
*/
|
*/
|
||||||
var text: String
|
var text: String
|
||||||
get() = minecraftWidget.text
|
get() = minecraftWidget.text
|
||||||
set(value) {
|
set(value) {
|
||||||
minecraftWidget.text = value
|
minecraftWidget.text = value
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The maximum length of text that this text field can hold.
|
* The maximum length of text that this text field can hold.
|
||||||
*
|
*
|
||||||
* Defaults to the Minecraft text field's maximum length (currently 32, subject to change).
|
* Defaults to the Minecraft text field's maximum length (currently 32, subject to change).
|
||||||
*/
|
*/
|
||||||
var maxLength: Int
|
var maxLength: Int
|
||||||
get() = (minecraftWidget as TextFieldWidgetAccessor).cacao_getMaxLength()
|
get() = (minecraftWidget as TextFieldWidgetAccessor).cacao_getMaxLength()
|
||||||
set(value) {
|
set(value) {
|
||||||
minecraftWidget.setMaxLength(value)
|
minecraftWidget.setMaxLength(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the Minecraft builtin black background and border are drawn. Defaults to true.
|
* Whether the Minecraft builtin black background and border are drawn. Defaults to true.
|
||||||
*/
|
*/
|
||||||
var drawBackground = true
|
var drawBackground = true
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
minecraftWidget.setDrawsBackground(value)
|
minecraftWidget.setDrawsBackground(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var originInWindow: Point
|
private lateinit var originInWindow: Point
|
||||||
private var minecraftWidget = ProxyWidget()
|
private var minecraftWidget = ProxyWidget()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
minecraftWidget.text = initialText
|
minecraftWidget.text = initialText
|
||||||
minecraftWidget.setTextPredicate { this.validate(it) }
|
minecraftWidget.setTextPredicate { this.validate(it) }
|
||||||
minecraftWidget.setDrawsBackground(drawBackground)
|
minecraftWidget.setDrawsBackground(drawBackground)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A function used by subclasses to determine whether a proposed value is acceptable for this field.
|
* A function used by subclasses to determine whether a proposed value is acceptable for this field.
|
||||||
*/
|
*/
|
||||||
abstract fun validate(proposedText: String): Boolean
|
abstract fun validate(proposedText: String): Boolean
|
||||||
|
|
||||||
override fun didLayout() {
|
override fun didLayout() {
|
||||||
super.didLayout()
|
super.didLayout()
|
||||||
|
|
||||||
originInWindow = convert(bounds.origin, to = null)
|
originInWindow = convert(bounds.origin, to = null)
|
||||||
|
|
||||||
// offset View dimensions by 1 on each side because TextFieldWidget draws the border outside its dimensions
|
// offset View dimensions by 1 on each side because TextFieldWidget draws the border outside its dimensions
|
||||||
minecraftWidget.x = originInWindow.x.toInt() + 1
|
minecraftWidget.x = originInWindow.x.toInt() + 1
|
||||||
minecraftWidget.y = originInWindow.y.toInt() + 1
|
minecraftWidget.y = originInWindow.y.toInt() + 1
|
||||||
minecraftWidget.width = bounds.width.toInt() - 2
|
minecraftWidget.width = bounds.width.toInt() - 2
|
||||||
minecraftWidget.height = bounds.height.toInt() - 2
|
minecraftWidget.height = bounds.height.toInt() - 2
|
||||||
|
|
||||||
// after dimensions change call setText on the widget to make sure its internal scroll position is up-to-date
|
// after dimensions change call setText on the widget to make sure its internal scroll position is up-to-date
|
||||||
minecraftWidget.text = text
|
minecraftWidget.text = text
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
matrixStack.push()
|
matrixStack.push()
|
||||||
matrixStack.translate(-originInWindow.x, -originInWindow.y, 0.0)
|
matrixStack.translate(-originInWindow.x, -originInWindow.y, 0.0)
|
||||||
|
|
||||||
val mouseXInWindow = (mouse.x + originInWindow.x).toInt()
|
val mouseXInWindow = (mouse.x + originInWindow.x).toInt()
|
||||||
val mouseYInWindow = (mouse.y + originInWindow.y).toInt()
|
val mouseYInWindow = (mouse.y + originInWindow.y).toInt()
|
||||||
minecraftWidget.render(matrixStack, mouseXInWindow, mouseYInWindow, delta)
|
minecraftWidget.render(matrixStack, mouseXInWindow, mouseYInWindow, delta)
|
||||||
|
|
||||||
matrixStack.pop()
|
matrixStack.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
if (focused) {
|
if (focused) {
|
||||||
val mouseXInWindow = (point.x + originInWindow.x)
|
val mouseXInWindow = (point.x + originInWindow.x)
|
||||||
val mouseYInWindow = (point.y + originInWindow.y)
|
val mouseYInWindow = (point.y + originInWindow.y)
|
||||||
minecraftWidget.mouseClicked(mouseXInWindow, mouseYInWindow, mouseButton.ordinal)
|
minecraftWidget.mouseClicked(mouseXInWindow, mouseYInWindow, mouseButton.ordinal)
|
||||||
} else {
|
} else {
|
||||||
becomeFirstResponder()
|
becomeFirstResponder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// don't play sound when interacting with text field
|
// don't play sound when interacting with text field
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
||||||
if (focused) {
|
if (focused) {
|
||||||
resignFirstResponder()
|
resignFirstResponder()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun didBecomeFirstResponder() {
|
override fun didBecomeFirstResponder() {
|
||||||
super.didBecomeFirstResponder()
|
super.didBecomeFirstResponder()
|
||||||
minecraftWidget.setTextFieldFocused(true)
|
minecraftWidget.setTextFieldFocused(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun didResignFirstResponder() {
|
override fun didResignFirstResponder() {
|
||||||
super.didResignFirstResponder()
|
super.didResignFirstResponder()
|
||||||
minecraftWidget.setTextFieldFocused(false)
|
minecraftWidget.setTextFieldFocused(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun charTyped(char: Char, modifiers: KeyModifiers): Boolean {
|
override fun charTyped(char: Char, modifiers: KeyModifiers): Boolean {
|
||||||
val oldText = text
|
val oldText = text
|
||||||
val result = minecraftWidget.charTyped(char, modifiers.value)
|
val result = minecraftWidget.charTyped(char, modifiers.value)
|
||||||
if (text != oldText) {
|
if (text != oldText) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
handler?.invoke(this as Impl)
|
handler?.invoke(this as Impl)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean {
|
override fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean {
|
||||||
val oldText = text
|
val oldText = text
|
||||||
// scanCode isn't used by TextFieldWidget, hopefully this doesn't break :/
|
// scanCode isn't used by TextFieldWidget, hopefully this doesn't break :/
|
||||||
val result = minecraftWidget.keyPressed(keyCode, -1, modifiers.value)
|
val result = minecraftWidget.keyPressed(keyCode, -1, modifiers.value)
|
||||||
if (text != oldText) {
|
if (text != oldText) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
handler?.invoke(this as Impl)
|
handler?.invoke(this as Impl)
|
||||||
}
|
}
|
||||||
return result || (isFirstResponder && keyCode != GLFW.GLFW_KEY_ESCAPE)
|
return result || (isFirstResponder && keyCode != GLFW.GLFW_KEY_ESCAPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tick() {
|
fun tick() {
|
||||||
minecraftWidget.tick()
|
minecraftWidget.tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: label for the TextFieldWidget?
|
// todo: label for the TextFieldWidget?
|
||||||
private class ProxyWidget: TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 0, 0, LiteralText("")) {
|
private class ProxyWidget :
|
||||||
// AbstractButtonWidget.height is protected
|
TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 0, 0, LiteralText("")) {
|
||||||
fun setHeight(height: Int) {
|
// AbstractButtonWidget.height is protected
|
||||||
this.height = height
|
fun setHeight(height: Int) {
|
||||||
}
|
this.height = height
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,43 +4,43 @@ package net.shadowfacts.cacao.view.textfield
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
open class NumberField(
|
open class NumberField(
|
||||||
initialValue: Int,
|
initialValue: Int,
|
||||||
handler: ((NumberField) -> Unit)? = null,
|
handler: ((NumberField) -> Unit)? = null,
|
||||||
): AbstractTextField<NumberField>(initialValue.toString()) {
|
) : AbstractTextField<NumberField>(initialValue.toString()) {
|
||||||
|
|
||||||
var number: Int?
|
var number: Int?
|
||||||
get() {
|
get() {
|
||||||
return if (isTextTemporarilyAllowed(text)) {
|
return if (isTextTemporarilyAllowed(text)) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
Integer.parseInt(text)
|
Integer.parseInt(text)
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
text = value?.toString() ?: ""
|
text = value?.toString() ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var validator: ((Int) -> Boolean)? = null
|
var validator: ((Int) -> Boolean)? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun validate(proposedText: String): Boolean {
|
override fun validate(proposedText: String): Boolean {
|
||||||
return isTextTemporarilyAllowed(proposedText) || try {
|
return isTextTemporarilyAllowed(proposedText) || try {
|
||||||
val value = Integer.parseInt(proposedText)
|
val value = Integer.parseInt(proposedText)
|
||||||
validator?.invoke(value) ?: true
|
validator?.invoke(value) ?: true
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isTextTemporarilyAllowed(s: String): Boolean {
|
private fun isTextTemporarilyAllowed(s: String): Boolean {
|
||||||
return s.isEmpty() || s == "-"
|
return s.isEmpty() || s == "-"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,14 @@ package net.shadowfacts.cacao.view.textfield
|
||||||
* @param handler A function that is invoked when the value of the text field changes.
|
* @param handler A function that is invoked when the value of the text field changes.
|
||||||
*/
|
*/
|
||||||
open class TextField(
|
open class TextField(
|
||||||
initialText: String,
|
initialText: String,
|
||||||
handler: ((TextField) -> Unit)? = null
|
handler: ((TextField) -> Unit)? = null
|
||||||
): AbstractTextField<TextField>(initialText) {
|
) : AbstractTextField<TextField>(initialText) {
|
||||||
init {
|
init {
|
||||||
this.handler = handler
|
this.handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun validate(proposedText: String): Boolean {
|
override fun validate(proposedText: String): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,266 +34,267 @@ import java.lang.RuntimeException
|
||||||
* @param onTabChange A function invoked immediately after the active tab has changed (and the content view has been
|
* @param onTabChange A function invoked immediately after the active tab has changed (and the content view has been
|
||||||
* updated).
|
* updated).
|
||||||
*/
|
*/
|
||||||
class TabViewController<T: TabViewController.Tab>(
|
class TabViewController<T : TabViewController.Tab>(
|
||||||
val tabs: List<T>,
|
val tabs: List<T>,
|
||||||
initialTab: T = tabs.first(),
|
initialTab: T = tabs.first(),
|
||||||
val onTabChange: ((T) -> Unit)? = null
|
val onTabChange: ((T) -> Unit)? = null
|
||||||
): ViewController() {
|
) : ViewController() {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Tab interface defines the requirements for tab objects that can be used with this view controller.
|
* The Tab interface defines the requirements for tab objects that can be used with this view controller.
|
||||||
*
|
*
|
||||||
* This is an interface to allow for tab objects to carry additional data. A simple implementation is provided.
|
* This is an interface to allow for tab objects to carry additional data. A simple implementation is provided.
|
||||||
* @see SimpleTab
|
* @see SimpleTab
|
||||||
*/
|
*/
|
||||||
interface Tab {
|
interface Tab {
|
||||||
/**
|
/**
|
||||||
* The view displayed on the button for this tab.
|
* The view displayed on the button for this tab.
|
||||||
*/
|
*/
|
||||||
val tabView: View
|
val tabView: View
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tooltip displayed when the button for this tab is hovered. `null` if no tooltip should be shown.
|
* The tooltip displayed when the button for this tab is hovered. `null` if no tooltip should be shown.
|
||||||
*/
|
*/
|
||||||
val tooltip: Text?
|
val tooltip: Text?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The view controller used as content when this tab is active. When switching tabs, the returned content VC
|
* The view controller used as content when this tab is active. When switching tabs, the returned content VC
|
||||||
* may be reused or created from scratch each time.
|
* may be reused or created from scratch each time.
|
||||||
*/
|
*/
|
||||||
val controller: ViewController
|
val controller: ViewController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by the tab view controller to determine whether the button for this tab should be displayed.
|
* Used by the tab view controller to determine whether the button for this tab should be displayed.
|
||||||
* If the conditions that control this change, call [TabViewController.visibleTabsChanged].
|
* If the conditions that control this change, call [TabViewController.visibleTabsChanged].
|
||||||
*/
|
*/
|
||||||
val isVisible: Boolean
|
val isVisible: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple [Tab] implementation that provides the minimum necessary information.
|
* A simple [Tab] implementation that provides the minimum necessary information.
|
||||||
* @param tabView The view to display on the tab's button.
|
* @param tabView The view to display on the tab's button.
|
||||||
* @param tooltip The tooltip to display when the tab's button is hovered (or `null`, if none).
|
* @param tooltip The tooltip to display when the tab's button is hovered (or `null`, if none).
|
||||||
* @param controller The content view controller for this tab.
|
* @param controller The content view controller for this tab.
|
||||||
* @param visible A function that determines if the tab should currently be visible.
|
* @param visible A function that determines if the tab should currently be visible.
|
||||||
*/
|
*/
|
||||||
class SimpleTab(
|
class SimpleTab(
|
||||||
override val tabView: View,
|
override val tabView: View,
|
||||||
override val tooltip: Text? = null,
|
override val tooltip: Text? = null,
|
||||||
override val controller: ViewController,
|
override val controller: ViewController,
|
||||||
private val visible: (() -> Boolean)? = null
|
private val visible: (() -> Boolean)? = null
|
||||||
): Tab {
|
) : Tab {
|
||||||
override val isVisible: Boolean
|
override val isVisible: Boolean
|
||||||
get() = visible?.invoke() ?: true
|
get() = visible?.invoke() ?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently selected tab.
|
* The currently selected tab.
|
||||||
*/
|
*/
|
||||||
var currentTab: T = initialTab
|
var currentTab: T = initialTab
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private lateinit var tabButtons: List<TabButton<T>>
|
private lateinit var tabButtons: List<TabButton<T>>
|
||||||
|
|
||||||
private lateinit var outerStack: StackView
|
private lateinit var outerStack: StackView
|
||||||
private lateinit var tabStack: StackView
|
private lateinit var tabStack: StackView
|
||||||
private lateinit var currentTabController: ViewController
|
private lateinit var currentTabController: ViewController
|
||||||
// todo: this shouldn't be public, use layout guides
|
|
||||||
lateinit var tabVCContainer: View
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun viewDidLoad() {
|
// todo: this shouldn't be public, use layout guides
|
||||||
super.viewDidLoad()
|
lateinit var tabVCContainer: View
|
||||||
|
private set
|
||||||
|
|
||||||
// todo: might be simpler to just not use a stack view
|
override fun viewDidLoad() {
|
||||||
// padding is -4 so tab button texture overlaps with panel BG as expected
|
super.viewDidLoad()
|
||||||
outerStack = StackView(Axis.VERTICAL, StackView.Distribution.FILL, -4.0)
|
|
||||||
view.addSubview(outerStack)
|
|
||||||
|
|
||||||
tabStack = StackView(Axis.HORIZONTAL, StackView.Distribution.FILL)
|
// todo: might be simpler to just not use a stack view
|
||||||
tabStack.zIndex = 1.0
|
// padding is -4 so tab button texture overlaps with panel BG as expected
|
||||||
outerStack.addArrangedSubview(tabStack)
|
outerStack = StackView(Axis.VERTICAL, StackView.Distribution.FILL, -4.0)
|
||||||
updateTabButtons()
|
view.addSubview(outerStack)
|
||||||
|
|
||||||
val background = NinePatchView(NinePatchTexture.PANEL_BG)
|
tabStack = StackView(Axis.HORIZONTAL, StackView.Distribution.FILL)
|
||||||
outerStack.addArrangedSubview(background)
|
tabStack.zIndex = 1.0
|
||||||
|
outerStack.addArrangedSubview(tabStack)
|
||||||
|
updateTabButtons()
|
||||||
|
|
||||||
tabVCContainer = View()
|
val background = NinePatchView(NinePatchTexture.PANEL_BG)
|
||||||
tabVCContainer.zIndex = 1.0
|
outerStack.addArrangedSubview(background)
|
||||||
view.addSubview(tabVCContainer)
|
|
||||||
|
|
||||||
currentTabController = currentTab.controller
|
tabVCContainer = View()
|
||||||
currentTabController.willMoveTo(this)
|
tabVCContainer.zIndex = 1.0
|
||||||
embedChild(currentTabController, tabVCContainer)
|
view.addSubview(tabVCContainer)
|
||||||
currentTabController.didMoveTo(this)
|
|
||||||
// will/did appear events for the initial VC are provided by this class' implementations of those
|
|
||||||
|
|
||||||
view.solver.dsl {
|
currentTabController = currentTab.controller
|
||||||
outerStack.leftAnchor equalTo view.leftAnchor
|
currentTabController.willMoveTo(this)
|
||||||
outerStack.rightAnchor equalTo view.rightAnchor
|
embedChild(currentTabController, tabVCContainer)
|
||||||
outerStack.topAnchor equalTo view.topAnchor
|
currentTabController.didMoveTo(this)
|
||||||
outerStack.bottomAnchor equalTo view.bottomAnchor
|
// will/did appear events for the initial VC are provided by this class' implementations of those
|
||||||
|
|
||||||
tabVCContainer.leftAnchor equalTo (background.leftAnchor + 6)
|
view.solver.dsl {
|
||||||
tabVCContainer.rightAnchor equalTo (background.rightAnchor - 6)
|
outerStack.leftAnchor equalTo view.leftAnchor
|
||||||
tabVCContainer.topAnchor equalTo (background.topAnchor + 6)
|
outerStack.rightAnchor equalTo view.rightAnchor
|
||||||
tabVCContainer.bottomAnchor equalTo (background.bottomAnchor - 6)
|
outerStack.topAnchor equalTo view.topAnchor
|
||||||
}
|
outerStack.bottomAnchor equalTo view.bottomAnchor
|
||||||
}
|
|
||||||
|
|
||||||
override fun viewWillAppear() {
|
tabVCContainer.leftAnchor equalTo (background.leftAnchor + 6)
|
||||||
super.viewWillAppear()
|
tabVCContainer.rightAnchor equalTo (background.rightAnchor - 6)
|
||||||
currentTabController.viewWillAppear()
|
tabVCContainer.topAnchor equalTo (background.topAnchor + 6)
|
||||||
}
|
tabVCContainer.bottomAnchor equalTo (background.bottomAnchor - 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun viewWillDisappear() {
|
override fun viewWillAppear() {
|
||||||
super.viewWillDisappear()
|
super.viewWillAppear()
|
||||||
currentTabController.viewWillDisappear()
|
currentTabController.viewWillAppear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun viewDidDisappear() {
|
override fun viewWillDisappear() {
|
||||||
super.viewDidDisappear()
|
super.viewWillDisappear()
|
||||||
currentTabController.viewDidDisappear()
|
currentTabController.viewWillDisappear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateTabButtons() {
|
override fun viewDidDisappear() {
|
||||||
while (tabStack.arrangedSubviews.isNotEmpty()) tabStack.removeArrangedSubview(tabStack.arrangedSubviews.first())
|
super.viewDidDisappear()
|
||||||
|
currentTabController.viewDidDisappear()
|
||||||
|
}
|
||||||
|
|
||||||
tabButtons = tabs.mapNotNull { tab ->
|
private fun updateTabButtons() {
|
||||||
if (!tab.isVisible) {
|
while (tabStack.arrangedSubviews.isNotEmpty()) tabStack.removeArrangedSubview(tabStack.arrangedSubviews.first())
|
||||||
return@mapNotNull null
|
|
||||||
}
|
|
||||||
|
|
||||||
val btn = TabButton(tab)
|
tabButtons = tabs.mapNotNull { tab ->
|
||||||
btn.handler = { selectTab(it.tab) }
|
if (!tab.isVisible) {
|
||||||
if (tab == currentTab) {
|
return@mapNotNull null
|
||||||
btn.setSelected(true)
|
}
|
||||||
}
|
|
||||||
btn
|
|
||||||
}
|
|
||||||
// todo: batch calls to addArrangedSubview
|
|
||||||
tabButtons.forEach(tabStack::addArrangedSubview)
|
|
||||||
|
|
||||||
// spacer
|
val btn = TabButton(tab)
|
||||||
tabStack.addArrangedSubview(View())
|
btn.handler = { selectTab(it.tab) }
|
||||||
|
if (tab == currentTab) {
|
||||||
|
btn.setSelected(true)
|
||||||
|
}
|
||||||
|
btn
|
||||||
|
}
|
||||||
|
// todo: batch calls to addArrangedSubview
|
||||||
|
tabButtons.forEach(tabStack::addArrangedSubview)
|
||||||
|
|
||||||
window!!.layout()
|
// spacer
|
||||||
}
|
tabStack.addArrangedSubview(View())
|
||||||
|
|
||||||
/**
|
window!!.layout()
|
||||||
* Call this method when the conditions that make the configured tabs visible change.
|
}
|
||||||
*/
|
|
||||||
fun visibleTabsChanged() {
|
|
||||||
updateTabButtons()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the provided tab as the currently active tab for this controller. Updates the state of tab bar buttons and
|
* Call this method when the conditions that make the configured tabs visible change.
|
||||||
* swaps the content view controller.
|
*/
|
||||||
*
|
fun visibleTabsChanged() {
|
||||||
* After the tab and the content are changed, [onTabChange] is invoked.
|
updateTabButtons()
|
||||||
*
|
}
|
||||||
* @throws RuntimeException If the provided tab was not passed in as part of the [tabs] list.
|
|
||||||
*/
|
|
||||||
fun selectTab(tab: T) {
|
|
||||||
if (!tabs.contains(tab)) {
|
|
||||||
throw RuntimeException("Cannot activate tab not in TabViewController.tabs")
|
|
||||||
}
|
|
||||||
|
|
||||||
val oldTab = currentTab
|
/**
|
||||||
currentTab = tab
|
* Sets the provided tab as the currently active tab for this controller. Updates the state of tab bar buttons and
|
||||||
|
* swaps the content view controller.
|
||||||
|
*
|
||||||
|
* After the tab and the content are changed, [onTabChange] is invoked.
|
||||||
|
*
|
||||||
|
* @throws RuntimeException If the provided tab was not passed in as part of the [tabs] list.
|
||||||
|
*/
|
||||||
|
fun selectTab(tab: T) {
|
||||||
|
if (!tabs.contains(tab)) {
|
||||||
|
throw RuntimeException("Cannot activate tab not in TabViewController.tabs")
|
||||||
|
}
|
||||||
|
|
||||||
tabButtons.forEach {
|
val oldTab = currentTab
|
||||||
it.setSelected(it.tab === tab)
|
currentTab = tab
|
||||||
}
|
|
||||||
currentTabController.viewWillDisappear()
|
|
||||||
currentTabController.view.removeFromSuperview()
|
|
||||||
currentTabController.viewDidDisappear()
|
|
||||||
currentTabController.willMoveTo(null)
|
|
||||||
currentTabController.removeFromParent()
|
|
||||||
currentTabController.didMoveTo(null)
|
|
||||||
|
|
||||||
currentTabController = currentTab.controller
|
tabButtons.forEach {
|
||||||
|
it.setSelected(it.tab === tab)
|
||||||
|
}
|
||||||
|
currentTabController.viewWillDisappear()
|
||||||
|
currentTabController.view.removeFromSuperview()
|
||||||
|
currentTabController.viewDidDisappear()
|
||||||
|
currentTabController.willMoveTo(null)
|
||||||
|
currentTabController.removeFromParent()
|
||||||
|
currentTabController.didMoveTo(null)
|
||||||
|
|
||||||
currentTabController.willMoveTo(this)
|
currentTabController = currentTab.controller
|
||||||
embedChild(currentTabController, tabVCContainer)
|
|
||||||
currentTabController.didMoveTo(this)
|
|
||||||
currentTabController.viewWillAppear()
|
|
||||||
|
|
||||||
onTabChange?.invoke(currentTab)
|
currentTabController.willMoveTo(this)
|
||||||
|
embedChild(currentTabController, tabVCContainer)
|
||||||
|
currentTabController.didMoveTo(this)
|
||||||
|
currentTabController.viewWillAppear()
|
||||||
|
|
||||||
// todo: setNeedsLayout
|
onTabChange?.invoke(currentTab)
|
||||||
window!!.layout()
|
|
||||||
}
|
|
||||||
|
|
||||||
private class TabButton<T: Tab>(
|
// todo: setNeedsLayout
|
||||||
val tab: T,
|
window!!.layout()
|
||||||
): AbstractButton<TabButton<T>>(
|
}
|
||||||
tab.tabView,
|
|
||||||
padding = 2.0
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
val BACKGROUND = Identifier("phycon:textures/gui/tabs.png")
|
|
||||||
}
|
|
||||||
|
|
||||||
private var selected = false
|
private class TabButton<T : Tab>(
|
||||||
private var backgroundView = TextureView(Texture(BACKGROUND, 0, 0))
|
val tab: T,
|
||||||
|
) : AbstractButton<TabButton<T>>(
|
||||||
|
tab.tabView,
|
||||||
|
padding = 2.0
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val BACKGROUND = Identifier("phycon:textures/gui/tabs.png")
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
private var selected = false
|
||||||
intrinsicContentSize = Size(28.0, 32.0)
|
private var backgroundView = TextureView(Texture(BACKGROUND, 0, 0))
|
||||||
background = null
|
|
||||||
hoveredBackground = null
|
|
||||||
disabledBackground = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun wasAdded() {
|
init {
|
||||||
super.wasAdded()
|
intrinsicContentSize = Size(28.0, 32.0)
|
||||||
backgroundView.usesConstraintBasedLayout = false
|
background = null
|
||||||
backgroundView.frame = Rect(0.0, 0.0, 28.0, 32.0)
|
hoveredBackground = null
|
||||||
backgroundView.zIndex = -1.0
|
disabledBackground = null
|
||||||
addSubview(backgroundView)
|
}
|
||||||
solver.dsl {
|
|
||||||
content.bottomAnchor lessThanOrEqualTo (bottomAnchor - 4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun didLayout() {
|
override fun wasAdded() {
|
||||||
super.didLayout()
|
super.wasAdded()
|
||||||
updateBackgroundTexture()
|
backgroundView.usesConstraintBasedLayout = false
|
||||||
}
|
backgroundView.frame = Rect(0.0, 0.0, 28.0, 32.0)
|
||||||
|
backgroundView.zIndex = -1.0
|
||||||
|
addSubview(backgroundView)
|
||||||
|
solver.dsl {
|
||||||
|
content.bottomAnchor lessThanOrEqualTo (bottomAnchor - 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setSelected(selected: Boolean) {
|
override fun didLayout() {
|
||||||
this.selected = selected
|
super.didLayout()
|
||||||
updateBackgroundTexture()
|
updateBackgroundTexture()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCurrentBackground(mouse: Point) = backgroundView
|
fun setSelected(selected: Boolean) {
|
||||||
|
this.selected = selected
|
||||||
|
updateBackgroundTexture()
|
||||||
|
}
|
||||||
|
|
||||||
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
override fun getCurrentBackground(mouse: Point) = backgroundView
|
||||||
super.draw(matrixStack, mouse, delta)
|
|
||||||
|
|
||||||
if (mouse in bounds && tab.tooltip != null) {
|
override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
window!!.drawTooltip(listOf(tab.tooltip!!))
|
super.draw(matrixStack, mouse, delta)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
if (mouse in bounds && tab.tooltip != null) {
|
||||||
if (selected) return false
|
window!!.drawTooltip(listOf(tab.tooltip!!))
|
||||||
else return super.mouseClicked(point, mouseButton)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateBackgroundTexture() {
|
override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
val v = if (selected) 32 else 0
|
if (selected) return false
|
||||||
val u = when {
|
else return super.mouseClicked(point, mouseButton)
|
||||||
superview == null -> 0
|
}
|
||||||
frame.left == 0.0 -> 0
|
|
||||||
frame.right == superview!!.bounds.right -> 56
|
private fun updateBackgroundTexture() {
|
||||||
else -> 28
|
val v = if (selected) 32 else 0
|
||||||
}
|
val u = when {
|
||||||
backgroundView.texture = Texture(BACKGROUND, u, v)
|
superview == null -> 0
|
||||||
backgroundView.frame = Rect(0.0, 0.0, 28.0, if (selected) 32.0 else 28.0)
|
frame.left == 0.0 -> 0
|
||||||
}
|
frame.right == superview!!.bounds.right -> 56
|
||||||
}
|
else -> 28
|
||||||
|
}
|
||||||
|
backgroundView.texture = Texture(BACKGROUND, u, v)
|
||||||
|
backgroundView.frame = Rect(0.0, 0.0, 28.0, if (selected) 32.0 else 28.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,111 +16,112 @@ import java.util.*
|
||||||
*/
|
*/
|
||||||
abstract class ViewController {
|
abstract class ViewController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The window that contains this view controller.
|
* The window that contains this view controller.
|
||||||
* This property is not set until either:
|
* This property is not set until either:
|
||||||
* a) a [Window] is initialized with this VC as it's root view controller or
|
* a) a [Window] is initialized with this VC as it's root view controller or
|
||||||
* b) this VC is added as a child of another view controller.
|
* b) this VC is added as a child of another view controller.
|
||||||
*/
|
*/
|
||||||
var window: Window? = null
|
var window: Window? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
for (vc in children) {
|
for (vc in children) {
|
||||||
vc.window = value
|
vc.window = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function for creating layout constraints in the domain of this VC's window.
|
* Helper function for creating layout constraints in the domain of this VC's window.
|
||||||
* This function is not usable until [window] is initialized.
|
* This function is not usable until [window] is initialized.
|
||||||
*/
|
*/
|
||||||
val createConstraints
|
val createConstraints
|
||||||
get() = window!!.solver::dsl
|
get() = window!!.solver::dsl
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The view that this View Controller has.
|
* The view that this View Controller has.
|
||||||
* This property is created by [loadView] and is not initialized before that method has been called.
|
* This property is created by [loadView] and is not initialized before that method has been called.
|
||||||
*
|
*
|
||||||
* @see loadView
|
* @see loadView
|
||||||
*/
|
*/
|
||||||
lateinit var view: View
|
lateinit var view: View
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This VC's parent view controller. If `null`, this VC is the root view controller of its [window].
|
* This VC's parent view controller. If `null`, this VC is the root view controller of its [window].
|
||||||
*/
|
*/
|
||||||
var parent: ViewController? = null
|
var parent: ViewController? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
willMoveTo(value)
|
willMoveTo(value)
|
||||||
field = value
|
field = value
|
||||||
didMoveTo(value)
|
didMoveTo(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// _children is the internal, mutable object since we only want it to be mutated by the embed/removeChild methods
|
// _children is the internal, mutable object since we only want it to be mutated by the embed/removeChild methods
|
||||||
private var _children = LinkedList<ViewController>()
|
private var _children = LinkedList<ViewController>()
|
||||||
/**
|
|
||||||
* The list of all the child VCs of this view controller.
|
|
||||||
* This list should never be mutated directly, only by the [embedChild]/[removeChild] methods.
|
|
||||||
*/
|
|
||||||
val children: List<ViewController> = _children
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method somehow loads a [View] and sets this VC's [view] property to it.
|
* The list of all the child VCs of this view controller.
|
||||||
*
|
* This list should never be mutated directly, only by the [embedChild]/[removeChild] methods.
|
||||||
* This method should only be called by the framework. After the [view] property is set, the framework is
|
*/
|
||||||
* responsible for initializing its [View.window]/[View.solver] properties and calling [View.wasAdded].
|
val children: List<ViewController> = _children
|
||||||
*
|
|
||||||
* The default implementation simply creates a [View] and does nothing else with it.
|
|
||||||
*/
|
|
||||||
open fun loadView() {
|
|
||||||
view = View()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the view for this controller has already been loaded.
|
* This method somehow loads a [View] and sets this VC's [view] property to it.
|
||||||
*/
|
*
|
||||||
val isViewLoaded: Boolean
|
* This method should only be called by the framework. After the [view] property is set, the framework is
|
||||||
get() = ::view.isInitialized
|
* responsible for initializing its [View.window]/[View.solver] properties and calling [View.wasAdded].
|
||||||
|
*
|
||||||
|
* The default implementation simply creates a [View] and does nothing else with it.
|
||||||
|
*/
|
||||||
|
open fun loadView() {
|
||||||
|
view = View()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls [loadView] to load this controller's view only if it has not already been loaded.
|
* If the view for this controller has already been loaded.
|
||||||
*/
|
*/
|
||||||
fun loadViewIfNeeded() {
|
val isViewLoaded: Boolean
|
||||||
if (!isViewLoaded) {
|
get() = ::view.isInitialized
|
||||||
loadView()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called after the view is loaded, it's properties are initialized, and [View.wasAdded] has been
|
* Calls [loadView] to load this controller's view only if it has not already been loaded.
|
||||||
* called.
|
*/
|
||||||
*/
|
fun loadViewIfNeeded() {
|
||||||
open fun viewDidLoad() {}
|
if (!isViewLoaded) {
|
||||||
|
loadView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called immediately before the [Window.solver] is going to solve constraints and update variables.
|
* This method is called after the view is loaded, it's properties are initialized, and [View.wasAdded] has been
|
||||||
* If overridden, the superclass method must be called.
|
* called.
|
||||||
*/
|
*/
|
||||||
open fun viewWillLayoutSubviews() {
|
open fun viewDidLoad() {}
|
||||||
children.forEach(ViewController::viewWillLayoutSubviews)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is called immediately after the [Window.solver] has solved constraints and variables have been updated.
|
* This method is called immediately before the [Window.solver] is going to solve constraints and update variables.
|
||||||
* This method is responsible for invoking the VC's [View.didLayout] method.
|
* If overridden, the superclass method must be called.
|
||||||
* If overridden, the superclass method must be called.
|
*/
|
||||||
*/
|
open fun viewWillLayoutSubviews() {
|
||||||
open fun viewDidLayoutSubviews() {
|
children.forEach(ViewController::viewWillLayoutSubviews)
|
||||||
view.didLayout()
|
}
|
||||||
children.forEach(ViewController::viewDidLayoutSubviews)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the VC's view has been added to the screen and is about to be displayed.
|
* This method is called immediately after the [Window.solver] has solved constraints and variables have been updated.
|
||||||
*/
|
* This method is responsible for invoking the VC's [View.didLayout] method.
|
||||||
open fun viewWillAppear() {
|
* If overridden, the superclass method must be called.
|
||||||
children.forEach(ViewController::viewWillAppear)
|
*/
|
||||||
}
|
open fun viewDidLayoutSubviews() {
|
||||||
|
view.didLayout()
|
||||||
|
children.forEach(ViewController::viewDidLayoutSubviews)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the VC's view has been added to the screen and is about to be displayed.
|
||||||
|
*/
|
||||||
|
open fun viewWillAppear() {
|
||||||
|
children.forEach(ViewController::viewWillAppear)
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * Called immediately after the VC's view has first been displayed on screen.
|
// * Called immediately after the VC's view has first been displayed on screen.
|
||||||
|
@ -129,87 +130,87 @@ abstract class ViewController {
|
||||||
// children.forEach(ViewController::viewDidAppear)
|
// children.forEach(ViewController::viewDidAppear)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before the view will disappear from the screen, either because the VC has been removed from it's parent/screen
|
* Called before the view will disappear from the screen, either because the VC has been removed from it's parent/screen
|
||||||
* or because the [net.shadowfacts.cacao.CacaoScreen] has been closed.
|
* or because the [net.shadowfacts.cacao.CacaoScreen] has been closed.
|
||||||
*/
|
*/
|
||||||
open fun viewWillDisappear() {
|
open fun viewWillDisappear() {
|
||||||
children.forEach(ViewController::viewWillDisappear)
|
children.forEach(ViewController::viewWillDisappear)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the view has disappeared from the screen.
|
* Called after the view has disappeared from the screen.
|
||||||
*/
|
*/
|
||||||
open fun viewDidDisappear() {
|
open fun viewDidDisappear() {
|
||||||
children.forEach(ViewController::viewDidDisappear)
|
children.forEach(ViewController::viewDidDisappear)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before the view controller's parent changes to the given new value.
|
* Called before the view controller's parent changes to the given new value.
|
||||||
*
|
*
|
||||||
* @param parent The new parent view controller.
|
* @param parent The new parent view controller.
|
||||||
*/
|
*/
|
||||||
open fun willMoveTo(parent: ViewController?) {}
|
open fun willMoveTo(parent: ViewController?) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the view controller's parent has changed to the given new value.
|
* Called after the view controller's parent has changed to the given new value.
|
||||||
*
|
*
|
||||||
* @param parent The new parent view controller.
|
* @param parent The new parent view controller.
|
||||||
*/
|
*/
|
||||||
open fun didMoveTo(parent: ViewController?) {}
|
open fun didMoveTo(parent: ViewController?) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Embeds a child view controller in this VC.
|
* Embeds a child view controller in this VC.
|
||||||
*
|
*
|
||||||
* @param viewController The new child VC.
|
* @param viewController The new child VC.
|
||||||
* @param container The view that will be used as the superview for the child VC's view. Defaults to this VC's [view].
|
* @param container The view that will be used as the superview for the child VC's view. Defaults to this VC's [view].
|
||||||
* @param pinEdges Whether the edges of the child VC will be pinned (constrained to be equal to) the container's edges.
|
* @param pinEdges Whether the edges of the child VC will be pinned (constrained to be equal to) the container's edges.
|
||||||
* Defaults to `true`.
|
* Defaults to `true`.
|
||||||
*/
|
*/
|
||||||
fun embedChild(viewController: ViewController, container: View = this.view, pinEdges: Boolean = true) {
|
fun embedChild(viewController: ViewController, container: View = this.view, pinEdges: Boolean = true) {
|
||||||
viewController.parent = this
|
viewController.parent = this
|
||||||
viewController.window = window
|
viewController.window = window
|
||||||
_children.add(viewController)
|
_children.add(viewController)
|
||||||
val wasViewLoaded = viewController.isViewLoaded
|
val wasViewLoaded = viewController.isViewLoaded
|
||||||
viewController.loadViewIfNeeded()
|
viewController.loadViewIfNeeded()
|
||||||
|
|
||||||
container.addSubview(viewController.view)
|
container.addSubview(viewController.view)
|
||||||
|
|
||||||
if (pinEdges) {
|
if (pinEdges) {
|
||||||
createConstraints {
|
createConstraints {
|
||||||
viewController.view.leftAnchor equalTo container.leftAnchor
|
viewController.view.leftAnchor equalTo container.leftAnchor
|
||||||
viewController.view.rightAnchor equalTo container.rightAnchor
|
viewController.view.rightAnchor equalTo container.rightAnchor
|
||||||
viewController.view.topAnchor equalTo container.topAnchor
|
viewController.view.topAnchor equalTo container.topAnchor
|
||||||
viewController.view.bottomAnchor equalTo container.bottomAnchor
|
viewController.view.bottomAnchor equalTo container.bottomAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasViewLoaded) {
|
if (!wasViewLoaded) {
|
||||||
viewController.viewDidLoad()
|
viewController.viewDidLoad()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given view controller
|
* Removes the given view controller
|
||||||
*
|
*
|
||||||
* @param viewController The child VC to remove from this view controller.
|
* @param viewController The child VC to remove from this view controller.
|
||||||
* @throws RuntimeException If the given [viewController] is not a child of this VC.
|
* @throws RuntimeException If the given [viewController] is not a child of this VC.
|
||||||
*/
|
*/
|
||||||
fun removeChild(viewController: ViewController) {
|
fun removeChild(viewController: ViewController) {
|
||||||
if (viewController.parent != this) {
|
if (viewController.parent != this) {
|
||||||
throw RuntimeException("Cannot remove child view controller whose parent is not this view controller")
|
throw RuntimeException("Cannot remove child view controller whose parent is not this view controller")
|
||||||
}
|
}
|
||||||
|
|
||||||
viewController.parent = null
|
viewController.parent = null
|
||||||
_children.remove(viewController)
|
_children.remove(viewController)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes this view controller from its parent, if it has one.
|
* Removes this view controller from its parent, if it has one.
|
||||||
*/
|
*/
|
||||||
fun removeFromParent() {
|
fun removeFromParent() {
|
||||||
parent?.removeChild(this)
|
parent?.removeChild(this)
|
||||||
view.removeFromSuperview()
|
view.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ import net.shadowfacts.cacao.viewcontroller.ViewController
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class ScreenHandlerWindow(
|
class ScreenHandlerWindow(
|
||||||
val screenHandler: ScreenHandler,
|
val screenHandler: ScreenHandler,
|
||||||
viewController: ViewController
|
viewController: ViewController
|
||||||
): Window(viewController) {
|
) : Window(viewController) {
|
||||||
|
|
||||||
}
|
}
|
|
@ -27,249 +27,256 @@ import java.lang.RuntimeException
|
||||||
* @param viewController The root view controller for this window.
|
* @param viewController The root view controller for this window.
|
||||||
*/
|
*/
|
||||||
open class Window(
|
open class Window(
|
||||||
/**
|
/**
|
||||||
* The root view controller for this window.
|
* The root view controller for this window.
|
||||||
*/
|
*/
|
||||||
val viewController: ViewController
|
val viewController: ViewController
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The screen that this window belongs to.
|
* The screen that this window belongs to.
|
||||||
* Not initialized until this window is added to a screen, using it before that point will throw a runtime exception.
|
* Not initialized until this window is added to a screen, using it before that point will throw a runtime exception.
|
||||||
*/
|
*/
|
||||||
lateinit var screen: AbstractCacaoScreen
|
lateinit var screen: AbstractCacaoScreen
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The constraint solver used by this window and all its views and subviews.
|
* The constraint solver used by this window and all its views and subviews.
|
||||||
*/
|
*/
|
||||||
var solver = Solver()
|
var solver = Solver()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout anchor for the left edge of this view in the window's coordinate system.
|
* Layout anchor for the left edge of this view in the window's coordinate system.
|
||||||
*/
|
*/
|
||||||
val leftAnchor = Variable("left")
|
val leftAnchor = Variable("left")
|
||||||
/**
|
|
||||||
* Layout anchor for the right edge of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val rightAnchor = Variable("right")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the top edge of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val topAnchor = Variable("top")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the bottom edge of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val bottomAnchor = Variable("bottom")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the width of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val widthAnchor = Variable("width")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the height of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val heightAnchor = Variable("height")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the center X position of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val centerXAnchor = Variable("centerX")
|
|
||||||
/**
|
|
||||||
* Layout anchor for the center Y position of this view in the window's coordinate system.
|
|
||||||
*/
|
|
||||||
val centerYAnchor = Variable("centerY")
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The first responder of the a window is the first object that receives indirect events (e.g., keypresses).
|
* Layout anchor for the right edge of this view in the window's coordinate system.
|
||||||
*
|
*/
|
||||||
* When an indirect event is received by the window, it is given to the first responder. If the first responder does
|
val rightAnchor = Variable("right")
|
||||||
* not accept it (i.e. returns `false` from the appropriate method), the event will be passed to that responder's
|
|
||||||
* [Responder.nextResponder], and so on.
|
|
||||||
*
|
|
||||||
* The following is the order of events when setting this property:
|
|
||||||
* 1. The old first responder (if any) has [Responder.didResignFirstResponder] invoked.
|
|
||||||
* 2. The value of the field is updated.
|
|
||||||
* 3. The new value (if any) has [Responder.didBecomeFirstResponder] invoked.
|
|
||||||
*/
|
|
||||||
var firstResponder: Responder? = null
|
|
||||||
set(value) {
|
|
||||||
field?.didResignFirstResponder()
|
|
||||||
field = value
|
|
||||||
field?.didBecomeFirstResponder()
|
|
||||||
}
|
|
||||||
|
|
||||||
// internal constraints that specify the window size based on the MC screen size
|
/**
|
||||||
// stored so that they can be removed when the screen is resized
|
* Layout anchor for the top edge of this view in the window's coordinate system.
|
||||||
private var widthConstraint: Constraint? = null
|
*/
|
||||||
private var heightConstraint: Constraint? = null
|
val topAnchor = Variable("top")
|
||||||
|
|
||||||
private var currentDragReceiver: View? = null
|
/**
|
||||||
|
* Layout anchor for the bottom edge of this view in the window's coordinate system.
|
||||||
|
*/
|
||||||
|
val bottomAnchor = Variable("bottom")
|
||||||
|
|
||||||
private var currentDeferredTooltip: List<Text>? = null
|
/**
|
||||||
|
* Layout anchor for the width of this view in the window's coordinate system.
|
||||||
|
*/
|
||||||
|
val widthAnchor = Variable("width")
|
||||||
|
|
||||||
init {
|
/**
|
||||||
createInternalConstraints()
|
* Layout anchor for the height of this view in the window's coordinate system.
|
||||||
}
|
*/
|
||||||
|
val heightAnchor = Variable("height")
|
||||||
|
|
||||||
fun wasAdded() {
|
/**
|
||||||
viewController.window = this
|
* Layout anchor for the center X position of this view in the window's coordinate system.
|
||||||
viewController.loadViewIfNeeded()
|
*/
|
||||||
|
val centerXAnchor = Variable("centerX")
|
||||||
|
|
||||||
viewController.view.window = this
|
/**
|
||||||
viewController.view.solver = solver
|
* Layout anchor for the center Y position of this view in the window's coordinate system.
|
||||||
viewController.view.wasAdded()
|
*/
|
||||||
viewController.createConstraints {
|
val centerYAnchor = Variable("centerY")
|
||||||
viewController.view.leftAnchor equalTo leftAnchor
|
|
||||||
viewController.view.rightAnchor equalTo rightAnchor
|
|
||||||
viewController.view.topAnchor equalTo topAnchor
|
|
||||||
viewController.view.bottomAnchor equalTo bottomAnchor
|
|
||||||
}
|
|
||||||
|
|
||||||
viewController.viewDidLoad()
|
/**
|
||||||
|
* The first responder of the a window is the first object that receives indirect events (e.g., keypresses).
|
||||||
|
*
|
||||||
|
* When an indirect event is received by the window, it is given to the first responder. If the first responder does
|
||||||
|
* not accept it (i.e. returns `false` from the appropriate method), the event will be passed to that responder's
|
||||||
|
* [Responder.nextResponder], and so on.
|
||||||
|
*
|
||||||
|
* The following is the order of events when setting this property:
|
||||||
|
* 1. The old first responder (if any) has [Responder.didResignFirstResponder] invoked.
|
||||||
|
* 2. The value of the field is updated.
|
||||||
|
* 3. The new value (if any) has [Responder.didBecomeFirstResponder] invoked.
|
||||||
|
*/
|
||||||
|
var firstResponder: Responder? = null
|
||||||
|
set(value) {
|
||||||
|
field?.didResignFirstResponder()
|
||||||
|
field = value
|
||||||
|
field?.didBecomeFirstResponder()
|
||||||
|
}
|
||||||
|
|
||||||
layout()
|
// internal constraints that specify the window size based on the MC screen size
|
||||||
}
|
// stored so that they can be removed when the screen is resized
|
||||||
|
private var widthConstraint: Constraint? = null
|
||||||
|
private var heightConstraint: Constraint? = null
|
||||||
|
|
||||||
/**
|
private var currentDragReceiver: View? = null
|
||||||
* Creates the internal constraints used by the window.
|
|
||||||
* If overridden, the super-class method must be called.
|
|
||||||
*/
|
|
||||||
protected fun createInternalConstraints() {
|
|
||||||
solver.dsl {
|
|
||||||
leftAnchor equalTo 0
|
|
||||||
topAnchor equalTo 0
|
|
||||||
|
|
||||||
rightAnchor equalTo (leftAnchor + widthAnchor)
|
private var currentDeferredTooltip: List<Text>? = null
|
||||||
bottomAnchor equalTo (topAnchor + heightAnchor)
|
|
||||||
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
|
||||||
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
init {
|
||||||
* Called by the window's [screen] when the Minecraft screen is resized.
|
createInternalConstraints()
|
||||||
* Used to update the window's width and height constraints and re-layout views.
|
}
|
||||||
*/
|
|
||||||
internal fun resize(width: Int, height: Int) {
|
|
||||||
if (widthConstraint != null) solver.removeConstraint(widthConstraint)
|
|
||||||
if (heightConstraint != null) solver.removeConstraint(heightConstraint)
|
|
||||||
solver.dsl {
|
|
||||||
widthConstraint = (widthAnchor equalTo width)
|
|
||||||
heightConstraint = (heightAnchor equalTo height)
|
|
||||||
}
|
|
||||||
layout()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
fun wasAdded() {
|
||||||
* Convenience method that removes this window from its [screen].
|
viewController.window = this
|
||||||
*/
|
viewController.loadViewIfNeeded()
|
||||||
fun removeFromScreen() {
|
|
||||||
viewController.viewWillDisappear()
|
|
||||||
screen.removeWindow(this)
|
|
||||||
viewController.viewDidDisappear()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
viewController.view.window = this
|
||||||
* Instructs the solver to solve all of the provided constraints.
|
viewController.view.solver = solver
|
||||||
* Should be called after the view hierarchy is setup.
|
viewController.view.wasAdded()
|
||||||
*/
|
viewController.createConstraints {
|
||||||
fun layout() {
|
viewController.view.leftAnchor equalTo leftAnchor
|
||||||
viewController.viewWillLayoutSubviews()
|
viewController.view.rightAnchor equalTo rightAnchor
|
||||||
solver.updateVariables()
|
viewController.view.topAnchor equalTo topAnchor
|
||||||
viewController.viewDidLayoutSubviews()
|
viewController.view.bottomAnchor equalTo bottomAnchor
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
viewController.viewDidLoad()
|
||||||
* Draws this window and all of its views.
|
|
||||||
* This method is called by [CacaoScreen] and generally shouldn't be called directly.
|
|
||||||
*
|
|
||||||
* @param mouse The point in the coordinate system of the window.
|
|
||||||
* @param delta The time elapsed since the last frame.
|
|
||||||
*/
|
|
||||||
open fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
|
||||||
currentDeferredTooltip = null
|
|
||||||
|
|
||||||
val mouseInView = Point(mouse.x - viewController.view.frame.left, mouse.y - viewController.view.frame.top)
|
layout()
|
||||||
viewController.view.draw(matrixStack, mouseInView, delta)
|
}
|
||||||
|
|
||||||
if (currentDeferredTooltip != null) {
|
/**
|
||||||
RenderHelper.drawTooltip(matrixStack, currentDeferredTooltip!!, mouse)
|
* Creates the internal constraints used by the window.
|
||||||
}
|
* If overridden, the super-class method must be called.
|
||||||
}
|
*/
|
||||||
|
protected fun createInternalConstraints() {
|
||||||
|
solver.dsl {
|
||||||
|
leftAnchor equalTo 0
|
||||||
|
topAnchor equalTo 0
|
||||||
|
|
||||||
/**
|
rightAnchor equalTo (leftAnchor + widthAnchor)
|
||||||
* Draw a tooltip containing the given lines at the mouse pointer location.
|
bottomAnchor equalTo (topAnchor + heightAnchor)
|
||||||
*
|
centerXAnchor equalTo (leftAnchor + widthAnchor / 2)
|
||||||
* Implementation note: the tooltip is not drawn immediately, it is done after the window is done drawing all of its
|
centerYAnchor equalTo (topAnchor + heightAnchor / 2)
|
||||||
* views. This is done to prevent other views from being drawn in front of the tooltip. Additionally, more than one
|
}
|
||||||
* tooltip cannot be drawn in a frame as they would appear at the same position.
|
}
|
||||||
*/
|
|
||||||
fun drawTooltip(text: List<Text>) {
|
|
||||||
if (currentDeferredTooltip != null) {
|
|
||||||
throw RuntimeException("Deferred tooltip already registered for current frame")
|
|
||||||
}
|
|
||||||
currentDeferredTooltip = text
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a mouse button is clicked and this is the active window.
|
* Called by the window's [screen] when the Minecraft screen is resized.
|
||||||
* This method is called by [CacaoScreen] and generally shouldn't be called directly.
|
* Used to update the window's width and height constraints and re-layout views.
|
||||||
*
|
*/
|
||||||
* @param point The point in the window of the click.
|
internal fun resize(width: Int, height: Int) {
|
||||||
* @param mouseButton The mouse button that was used to click.
|
if (widthConstraint != null) solver.removeConstraint(widthConstraint)
|
||||||
* @return Whether the mouse click was handled by a view.
|
if (heightConstraint != null) solver.removeConstraint(heightConstraint)
|
||||||
*/
|
solver.dsl {
|
||||||
fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
widthConstraint = (widthAnchor equalTo width)
|
||||||
// todo: isn't this always true?
|
heightConstraint = (heightAnchor equalTo height)
|
||||||
if (point in viewController.view.frame) {
|
}
|
||||||
val mouseInView = Point(point.x - viewController.view.frame.left, point.y - viewController.view.frame.top)
|
layout()
|
||||||
return viewController.view.mouseClicked(mouseInView, mouseButton)
|
}
|
||||||
} else {
|
|
||||||
// remove the window from the screen when the mouse clicks outside the window and this is not the primary window
|
|
||||||
if (screen.windows.size > 1) {
|
|
||||||
removeFromScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean {
|
/**
|
||||||
val currentlyDraggedView = this.currentDragReceiver
|
* Convenience method that removes this window from its [screen].
|
||||||
if (currentlyDraggedView != null) {
|
*/
|
||||||
val pointInView = viewController.view.convert(startPoint, to = currentlyDraggedView)
|
fun removeFromScreen() {
|
||||||
return currentlyDraggedView.mouseDragged(pointInView, delta, mouseButton)
|
viewController.viewWillDisappear()
|
||||||
} else if (startPoint in viewController.view.frame) {
|
screen.removeWindow(this)
|
||||||
val startInView =
|
viewController.viewDidDisappear()
|
||||||
Point(startPoint.x - viewController.view.frame.left, startPoint.y - viewController.view.frame.top)
|
}
|
||||||
var prevView: View? = null
|
|
||||||
var view = viewController.view.subviewsAtPoint(startInView).maxByOrNull(View::zIndex)
|
|
||||||
while (view != null && !view.respondsToDragging) {
|
|
||||||
prevView = view
|
|
||||||
val pointInView = viewController.view.convert(startInView, to = view)
|
|
||||||
view = view.subviewsAtPoint(pointInView).maxByOrNull(View::zIndex)
|
|
||||||
}
|
|
||||||
this.currentDragReceiver = view ?: prevView
|
|
||||||
return if (this.currentDragReceiver != null) {
|
|
||||||
val pointInView = viewController.view.convert(startPoint, to = this.currentDragReceiver!!)
|
|
||||||
this.currentDragReceiver!!.mouseDragged(pointInView, delta, mouseButton)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mouseReleased(point: Point, mouseButton: MouseButton): Boolean {
|
/**
|
||||||
val currentlyDraggedView = this.currentDragReceiver
|
* Instructs the solver to solve all of the provided constraints.
|
||||||
if (currentlyDraggedView != null) {
|
* Should be called after the view hierarchy is setup.
|
||||||
val pointInView = viewController.view.convert(point, to = currentlyDraggedView)
|
*/
|
||||||
currentlyDraggedView.mouseDragEnded(pointInView, mouseButton)
|
fun layout() {
|
||||||
this.currentDragReceiver = null
|
viewController.viewWillLayoutSubviews()
|
||||||
return true
|
solver.updateVariables()
|
||||||
}
|
viewController.viewDidLayoutSubviews()
|
||||||
return false
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun mouseScrolled(point: Point, amount: Double): Boolean {
|
/**
|
||||||
return viewController.view.mouseScrolled(point, amount)
|
* Draws this window and all of its views.
|
||||||
}
|
* This method is called by [CacaoScreen] and generally shouldn't be called directly.
|
||||||
|
*
|
||||||
|
* @param mouse The point in the coordinate system of the window.
|
||||||
|
* @param delta The time elapsed since the last frame.
|
||||||
|
*/
|
||||||
|
open fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
|
currentDeferredTooltip = null
|
||||||
|
|
||||||
|
val mouseInView = Point(mouse.x - viewController.view.frame.left, mouse.y - viewController.view.frame.top)
|
||||||
|
viewController.view.draw(matrixStack, mouseInView, delta)
|
||||||
|
|
||||||
|
if (currentDeferredTooltip != null) {
|
||||||
|
RenderHelper.drawTooltip(matrixStack, currentDeferredTooltip!!, mouse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw a tooltip containing the given lines at the mouse pointer location.
|
||||||
|
*
|
||||||
|
* Implementation note: the tooltip is not drawn immediately, it is done after the window is done drawing all of its
|
||||||
|
* views. This is done to prevent other views from being drawn in front of the tooltip. Additionally, more than one
|
||||||
|
* tooltip cannot be drawn in a frame as they would appear at the same position.
|
||||||
|
*/
|
||||||
|
fun drawTooltip(text: List<Text>) {
|
||||||
|
if (currentDeferredTooltip != null) {
|
||||||
|
throw RuntimeException("Deferred tooltip already registered for current frame")
|
||||||
|
}
|
||||||
|
currentDeferredTooltip = text
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a mouse button is clicked and this is the active window.
|
||||||
|
* This method is called by [CacaoScreen] and generally shouldn't be called directly.
|
||||||
|
*
|
||||||
|
* @param point The point in the window of the click.
|
||||||
|
* @param mouseButton The mouse button that was used to click.
|
||||||
|
* @return Whether the mouse click was handled by a view.
|
||||||
|
*/
|
||||||
|
fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
|
// todo: isn't this always true?
|
||||||
|
if (point in viewController.view.frame) {
|
||||||
|
val mouseInView = Point(point.x - viewController.view.frame.left, point.y - viewController.view.frame.top)
|
||||||
|
return viewController.view.mouseClicked(mouseInView, mouseButton)
|
||||||
|
} else {
|
||||||
|
// remove the window from the screen when the mouse clicks outside the window and this is not the primary window
|
||||||
|
if (screen.windows.size > 1) {
|
||||||
|
removeFromScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean {
|
||||||
|
val currentlyDraggedView = this.currentDragReceiver
|
||||||
|
if (currentlyDraggedView != null) {
|
||||||
|
val pointInView = viewController.view.convert(startPoint, to = currentlyDraggedView)
|
||||||
|
return currentlyDraggedView.mouseDragged(pointInView, delta, mouseButton)
|
||||||
|
} else if (startPoint in viewController.view.frame) {
|
||||||
|
val startInView =
|
||||||
|
Point(startPoint.x - viewController.view.frame.left, startPoint.y - viewController.view.frame.top)
|
||||||
|
var prevView: View? = null
|
||||||
|
var view = viewController.view.subviewsAtPoint(startInView).maxByOrNull(View::zIndex)
|
||||||
|
while (view != null && !view.respondsToDragging) {
|
||||||
|
prevView = view
|
||||||
|
val pointInView = viewController.view.convert(startInView, to = view)
|
||||||
|
view = view.subviewsAtPoint(pointInView).maxByOrNull(View::zIndex)
|
||||||
|
}
|
||||||
|
this.currentDragReceiver = view ?: prevView
|
||||||
|
return if (this.currentDragReceiver != null) {
|
||||||
|
val pointInView = viewController.view.convert(startPoint, to = this.currentDragReceiver!!)
|
||||||
|
this.currentDragReceiver!!.mouseDragged(pointInView, delta, mouseButton)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mouseReleased(point: Point, mouseButton: MouseButton): Boolean {
|
||||||
|
val currentlyDraggedView = this.currentDragReceiver
|
||||||
|
if (currentlyDraggedView != null) {
|
||||||
|
val pointInView = viewController.view.convert(point, to = currentlyDraggedView)
|
||||||
|
currentlyDraggedView.mouseDragEnded(pointInView, mouseButton)
|
||||||
|
this.currentDragReceiver = null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mouseScrolled(point: Point, amount: Double): Boolean {
|
||||||
|
return viewController.view.mouseScrolled(point, amount)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,98 +6,104 @@ import no.birkett.kiwi.*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class KiwiContext(val solver: Solver) {
|
class KiwiContext(val solver: Solver) {
|
||||||
val REQUIRED = Strength.REQUIRED
|
val REQUIRED = Strength.REQUIRED
|
||||||
val STRONG = Strength.STRONG
|
val STRONG = Strength.STRONG
|
||||||
val MEDIUM = Strength.MEDIUM
|
val MEDIUM = Strength.MEDIUM
|
||||||
val WEAK = Strength.WEAK
|
val WEAK = Strength.WEAK
|
||||||
|
|
||||||
// Constraints
|
// Constraints
|
||||||
infix fun ExpressionConvertible.equalTo(other: ExpressionConvertible): Constraint {
|
infix fun ExpressionConvertible.equalTo(other: ExpressionConvertible): Constraint {
|
||||||
return Symbolics.equals(this.toExpression(), other.toExpression()).apply(solver::addConstraint)
|
return Symbolics.equals(this.toExpression(), other.toExpression()).apply(solver::addConstraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExpressionConvertible.equalTo(other: ExpressionConvertible, strength: Double): Constraint {
|
fun ExpressionConvertible.equalTo(other: ExpressionConvertible, strength: Double): Constraint {
|
||||||
return Symbolics.equals(this.toExpression(), other.toExpression()).setStrength(strength).apply(solver::addConstraint)
|
return Symbolics.equals(this.toExpression(), other.toExpression()).setStrength(strength)
|
||||||
}
|
.apply(solver::addConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
infix fun ExpressionConvertible.equalTo(constant: Number): Constraint {
|
infix fun ExpressionConvertible.equalTo(constant: Number): Constraint {
|
||||||
return Symbolics.equals(this.toExpression(), constant.toDouble()).apply(solver::addConstraint)
|
return Symbolics.equals(this.toExpression(), constant.toDouble()).apply(solver::addConstraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExpressionConvertible.equalTo(constant: Number, strength: Double): Constraint {
|
fun ExpressionConvertible.equalTo(constant: Number, strength: Double): Constraint {
|
||||||
return Symbolics.equals(this.toExpression(), constant.toDouble()).setStrength(strength).apply(solver::addConstraint)
|
return Symbolics.equals(this.toExpression(), constant.toDouble()).setStrength(strength)
|
||||||
}
|
.apply(solver::addConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
infix fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible): Constraint {
|
infix fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible): Constraint {
|
||||||
return Symbolics.lessThanOrEqualTo(this.toExpression(), other.toExpression()).apply(solver::addConstraint)
|
return Symbolics.lessThanOrEqualTo(this.toExpression(), other.toExpression()).apply(solver::addConstraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint {
|
fun ExpressionConvertible.lessThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint {
|
||||||
return Symbolics.lessThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength).apply(solver::addConstraint)
|
return Symbolics.lessThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength)
|
||||||
}
|
.apply(solver::addConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
infix fun ExpressionConvertible.lessThanOrEqualTo(constant: Number): Constraint {
|
infix fun ExpressionConvertible.lessThanOrEqualTo(constant: Number): Constraint {
|
||||||
return Symbolics.lessThanOrEqualTo(this.toExpression(), constant.toDouble()).apply(solver::addConstraint)
|
return Symbolics.lessThanOrEqualTo(this.toExpression(), constant.toDouble()).apply(solver::addConstraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExpressionConvertible.lessThanOrEqualTo(constant: Number, strength: Double): Constraint {
|
fun ExpressionConvertible.lessThanOrEqualTo(constant: Number, strength: Double): Constraint {
|
||||||
return Symbolics.lessThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength).apply(solver::addConstraint)
|
return Symbolics.lessThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength)
|
||||||
}
|
.apply(solver::addConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
infix fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible): Constraint {
|
infix fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible): Constraint {
|
||||||
return Symbolics.greaterThanOrEqualTo(this.toExpression(), other.toExpression()).apply(solver::addConstraint)
|
return Symbolics.greaterThanOrEqualTo(this.toExpression(), other.toExpression()).apply(solver::addConstraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint {
|
fun ExpressionConvertible.greaterThanOrEqualTo(other: ExpressionConvertible, strength: Double): Constraint {
|
||||||
return Symbolics.greaterThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength).apply(solver::addConstraint)
|
return Symbolics.greaterThanOrEqualTo(this.toExpression(), other.toExpression()).setStrength(strength)
|
||||||
}
|
.apply(solver::addConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
infix fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number): Constraint {
|
infix fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number): Constraint {
|
||||||
return Symbolics.greaterThanOrEqualTo(this.toExpression(), constant.toDouble()).apply(solver::addConstraint)
|
return Symbolics.greaterThanOrEqualTo(this.toExpression(), constant.toDouble()).apply(solver::addConstraint)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number, strength: Double): Constraint {
|
fun ExpressionConvertible.greaterThanOrEqualTo(constant: Number, strength: Double): Constraint {
|
||||||
return Symbolics.greaterThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength).apply(solver::addConstraint)
|
return Symbolics.greaterThanOrEqualTo(this.toExpression(), constant.toDouble()).setStrength(strength)
|
||||||
}
|
.apply(solver::addConstraint)
|
||||||
|
}
|
||||||
|
|
||||||
// Addition
|
// Addition
|
||||||
operator fun ExpressionConvertible.plus(other: ExpressionConvertible): Expression {
|
operator fun ExpressionConvertible.plus(other: ExpressionConvertible): Expression {
|
||||||
return Symbolics.add(this.toExpression(), other.toExpression())
|
return Symbolics.add(this.toExpression(), other.toExpression())
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun ExpressionConvertible.plus(constant: Number): Expression {
|
operator fun ExpressionConvertible.plus(constant: Number): Expression {
|
||||||
return Symbolics.add(this.toExpression(), constant.toDouble())
|
return Symbolics.add(this.toExpression(), constant.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtraction
|
// Subtraction
|
||||||
operator fun ExpressionConvertible.minus(other: ExpressionConvertible): Expression {
|
operator fun ExpressionConvertible.minus(other: ExpressionConvertible): Expression {
|
||||||
return Symbolics.subtract(this.toExpression(), other.toExpression())
|
return Symbolics.subtract(this.toExpression(), other.toExpression())
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun ExpressionConvertible.minus(constant: Number): Expression {
|
operator fun ExpressionConvertible.minus(constant: Number): Expression {
|
||||||
return Symbolics.subtract(this.toExpression(), constant.toDouble())
|
return Symbolics.subtract(this.toExpression(), constant.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multiplication
|
// Multiplication
|
||||||
operator fun ExpressionConvertible.times(other: ExpressionConvertible): Expression {
|
operator fun ExpressionConvertible.times(other: ExpressionConvertible): Expression {
|
||||||
return Symbolics.multiply(this.toExpression(), other.toExpression())
|
return Symbolics.multiply(this.toExpression(), other.toExpression())
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun ExpressionConvertible.times(constant: Number): Expression {
|
operator fun ExpressionConvertible.times(constant: Number): Expression {
|
||||||
return Symbolics.multiply(this.toExpression(), constant.toDouble())
|
return Symbolics.multiply(this.toExpression(), constant.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Division
|
// Division
|
||||||
operator fun ExpressionConvertible.div(other: ExpressionConvertible): Expression {
|
operator fun ExpressionConvertible.div(other: ExpressionConvertible): Expression {
|
||||||
return Symbolics.divide(this.toExpression(), other.toExpression())
|
return Symbolics.divide(this.toExpression(), other.toExpression())
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun ExpressionConvertible.div(constant: Number): Expression {
|
operator fun ExpressionConvertible.div(constant: Number): Expression {
|
||||||
return Symbolics.divide(this.toExpression(), constant.toDouble())
|
return Symbolics.divide(this.toExpression(), constant.toDouble())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Solver.dsl(init: KiwiContext.() -> Unit): Solver {
|
fun Solver.dsl(init: KiwiContext.() -> Unit): Solver {
|
||||||
KiwiContext(this).init()
|
KiwiContext(this).init()
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,15 @@ import net.shadowfacts.phycon.util.SortMode
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object DefaultPlugin: PhyConPlugin {
|
object DefaultPlugin : PhyConPlugin {
|
||||||
|
|
||||||
lateinit var SORT_MODE: TerminalSettingKey<SortMode>
|
lateinit var SORT_MODE: TerminalSettingKey<SortMode>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun initializePhyCon(api: PhyConAPI) {
|
override fun initializePhyCon(api: PhyConAPI) {
|
||||||
SORT_MODE = api.registerTerminalSetting(Identifier(PhysicalConnectivity.MODID, "sort"), SortMode.COUNT_HIGH_FIRST)
|
SORT_MODE =
|
||||||
SORT_MODE.setPriority(Int.MAX_VALUE)
|
api.registerTerminalSetting(Identifier(PhysicalConnectivity.MODID, "sort"), SortMode.COUNT_HIGH_FIRST)
|
||||||
}
|
SORT_MODE.setPriority(Int.MAX_VALUE)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,13 @@ import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object PhyConAPIImpl: PhyConAPI {
|
object PhyConAPIImpl : PhyConAPI {
|
||||||
|
|
||||||
override fun <E> registerTerminalSetting(id: Identifier, defaultValue: E): TerminalSettingKey<E> where E: Enum<E>, E: TerminalSetting? {
|
override fun <E> registerTerminalSetting(
|
||||||
return TerminalSettings.register(id, defaultValue)
|
id: Identifier,
|
||||||
}
|
defaultValue: E
|
||||||
|
): TerminalSettingKey<E> where E : Enum<E>, E : TerminalSetting? {
|
||||||
|
return TerminalSettings.register(id, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,32 +16,35 @@ import org.apache.logging.log4j.LogManager
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object PhysicalConnectivity: ModInitializer {
|
object PhysicalConnectivity : ModInitializer {
|
||||||
|
|
||||||
val MODID = "phycon"
|
val MODID = "phycon"
|
||||||
|
|
||||||
val NETWORK_LOGGER = LogManager.getLogger("PhyNet")
|
val NETWORK_LOGGER = LogManager.getLogger("PhyNet")
|
||||||
|
|
||||||
override fun onInitialize() {
|
override fun onInitialize() {
|
||||||
PhyBlocks.init()
|
PhyBlocks.init()
|
||||||
PhyBlockEntities.init()
|
PhyBlockEntities.init()
|
||||||
PhyItems.init()
|
PhyItems.init()
|
||||||
PhyScreens.init()
|
PhyScreens.init()
|
||||||
|
|
||||||
registerGlobalReceiver(C2SConfigureDevice)
|
registerGlobalReceiver(C2SConfigureDevice)
|
||||||
registerGlobalReceiver(C2STerminalCraftingButton)
|
registerGlobalReceiver(C2STerminalCraftingButton)
|
||||||
registerGlobalReceiver(C2STerminalRequestItem)
|
registerGlobalReceiver(C2STerminalRequestItem)
|
||||||
registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
|
registerGlobalReceiver(C2STerminalUpdateDisplayedItems)
|
||||||
|
|
||||||
ItemStorage.SIDED.registerForBlockEntity(P2PReceiverBlockEntity::provideItemStorage, PhyBlockEntities.P2P_RECEIVER)
|
ItemStorage.SIDED.registerForBlockEntity(
|
||||||
|
P2PReceiverBlockEntity::provideItemStorage,
|
||||||
|
PhyBlockEntities.P2P_RECEIVER
|
||||||
|
)
|
||||||
|
|
||||||
for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) {
|
for (it in FabricLoader.getInstance().getEntrypoints("phycon", PhyConPlugin::class.java)) {
|
||||||
it.initializePhyCon(PhyConAPIImpl)
|
it.initializePhyCon(PhyConAPIImpl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerGlobalReceiver(receiver: ServerReceiver) {
|
private fun registerGlobalReceiver(receiver: ServerReceiver) {
|
||||||
ServerPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
|
ServerPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import net.fabricmc.fabric.api.client.screenhandler.v1.ScreenRegistry
|
||||||
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
|
import net.fabricmc.fabric.api.renderer.v1.RendererAccess
|
||||||
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial
|
import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial
|
||||||
import net.shadowfacts.phycon.block.inserter.InserterScreen
|
import net.shadowfacts.phycon.block.inserter.InserterScreen
|
||||||
|
import net.shadowfacts.phycon.block.netswitch.SwitchConsoleScreen
|
||||||
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen
|
import net.shadowfacts.phycon.block.redstone_emitter.RedstoneEmitterScreen
|
||||||
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen
|
import net.shadowfacts.phycon.block.terminal.CraftingTerminalScreen
|
||||||
import net.shadowfacts.phycon.init.PhyScreens
|
import net.shadowfacts.phycon.init.PhyScreens
|
||||||
|
@ -20,36 +21,37 @@ import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
object PhysicalConnectivityClient: ClientModInitializer {
|
object PhysicalConnectivityClient : ClientModInitializer {
|
||||||
|
|
||||||
val terminalSettings = TerminalSettings()
|
val terminalSettings = TerminalSettings()
|
||||||
|
|
||||||
var screenMaterial: RenderMaterial? = null
|
var screenMaterial: RenderMaterial? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onInitializeClient() {
|
override fun onInitializeClient() {
|
||||||
ModelLoadingRegistry.INSTANCE.registerResourceProvider(::PhyModelProvider)
|
ModelLoadingRegistry.INSTANCE.registerResourceProvider(::PhyModelProvider)
|
||||||
|
|
||||||
RendererAccess.INSTANCE.renderer?.also { renderer ->
|
RendererAccess.INSTANCE.renderer?.also { renderer ->
|
||||||
screenMaterial = renderer.materialFinder()
|
screenMaterial = renderer.materialFinder()
|
||||||
.emissive(0, true)
|
.emissive(0, true)
|
||||||
.disableAo(0, true)
|
.disableAo(0, true)
|
||||||
.disableDiffuse(0, true)
|
.disableDiffuse(0, true)
|
||||||
.find()
|
.find()
|
||||||
|
|
||||||
ModelLoadingRegistry.INSTANCE.registerResourceProvider(::PhyExtendedModelProvider)
|
ModelLoadingRegistry.INSTANCE.registerResourceProvider(::PhyExtendedModelProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen)
|
ScreenRegistry.register(PhyScreens.TERMINAL, ::TerminalScreen)
|
||||||
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
|
ScreenRegistry.register(PhyScreens.CRAFTING_TERMINAL, ::CraftingTerminalScreen)
|
||||||
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
|
ScreenRegistry.register(PhyScreens.INSERTER, ::InserterScreen)
|
||||||
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
|
ScreenRegistry.register(PhyScreens.REDSTONE_EMITTER, ::RedstoneEmitterScreen)
|
||||||
|
ScreenRegistry.register(PhyScreens.SWITCH_CONSOLE, ::SwitchConsoleScreen)
|
||||||
|
|
||||||
registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
|
registerGlobalReceiver(S2CTerminalUpdateDisplayedItems)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerGlobalReceiver(receiver: ClientReceiver) {
|
private fun registerGlobalReceiver(receiver: ClientReceiver) {
|
||||||
ClientPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
|
ClientPlayNetworking.registerGlobalReceiver(receiver.CHANNEL, receiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,15 @@ import net.minecraft.world.World
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class BlockWithEntity<T: BlockEntity>(settings: Settings): Block(settings), BlockEntityProvider {
|
abstract class BlockWithEntity<T : BlockEntity>(settings: Settings) : Block(settings), BlockEntityProvider {
|
||||||
abstract override fun createBlockEntity(pos: BlockPos, state: BlockState): T?
|
abstract override fun createBlockEntity(pos: BlockPos, state: BlockState): T?
|
||||||
|
|
||||||
fun getBlockEntity(world: BlockView, pos: BlockPos): T? {
|
fun getBlockEntity(world: BlockView, pos: BlockPos): T? {
|
||||||
val entity = world.getBlockEntity(pos)
|
val entity = world.getBlockEntity(pos)
|
||||||
return if (entity != null) {
|
return if (entity != null) {
|
||||||
entity as? T
|
entity as? T
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,41 @@ import net.shadowfacts.phycon.api.NetworkComponentBlock
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class DeviceBlock<T: DeviceBlockEntity>(settings: Settings): BlockWithEntity<T>(settings), NetworkComponentBlock {
|
abstract class DeviceBlock<T : DeviceBlockEntity>(settings: Settings) : BlockWithEntity<T>(settings),
|
||||||
|
NetworkComponentBlock {
|
||||||
|
|
||||||
abstract override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction>
|
abstract override fun getNetworkConnectedSides(
|
||||||
|
state: BlockState,
|
||||||
|
world: WorldAccess,
|
||||||
|
pos: BlockPos
|
||||||
|
): Collection<Direction>
|
||||||
|
|
||||||
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
|
override fun getNetworkInterfaceForSide(
|
||||||
return getBlockEntity(world, pos)!!
|
side: Direction,
|
||||||
}
|
state: BlockState,
|
||||||
|
world: WorldAccess,
|
||||||
|
pos: BlockPos
|
||||||
|
): Interface? {
|
||||||
|
return getBlockEntity(world, pos)!!
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
|
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
|
||||||
super.onBreak(world, pos, state, player)
|
super.onBreak(world, pos, state, player)
|
||||||
getBlockEntity(world, pos)!!.onBreak()
|
getBlockEntity(world, pos)!!.onBreak()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun <T: BlockEntity> getTicker(world: World, state: BlockState, type: BlockEntityType<T>): BlockEntityTicker<T>? {
|
override fun <T : BlockEntity> getTicker(
|
||||||
return if (world.isClient) {
|
world: World,
|
||||||
null
|
state: BlockState,
|
||||||
} else {
|
type: BlockEntityType<T>
|
||||||
BlockEntityTicker { world, blockPos, blockState, blockEntity ->
|
): BlockEntityTicker<T>? {
|
||||||
(blockEntity as DeviceBlockEntity).tick()
|
return if (world.isClient) {
|
||||||
}
|
null
|
||||||
}
|
} else {
|
||||||
}
|
BlockEntityTicker { world, blockPos, blockState, blockEntity ->
|
||||||
|
(blockEntity as DeviceBlockEntity).tick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,205 +28,232 @@ import java.util.*
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): BlockEntity(type, pos, state),
|
abstract class DeviceBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState) :
|
||||||
PacketSink,
|
BlockEntity(type, pos, state),
|
||||||
PacketSource,
|
PacketSink,
|
||||||
Interface {
|
PacketSource,
|
||||||
|
Interface {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ARP_RETRY_TIMEOUT = 200
|
private const val ARP_RETRY_TIMEOUT = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
var macAddress: MACAddress = MACAddress.random()
|
var macAddress: MACAddress = MACAddress.random()
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
var ipAddress: IPAddress = IPAddress.random()
|
var ipAddress: IPAddress = IPAddress.random()
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
private val arpTable = mutableMapOf<IPAddress, MACAddress>()
|
private val arpTable = mutableMapOf<IPAddress, MACAddress>()
|
||||||
private val packetQueue = LinkedList<PendingPacket>()
|
private val packetQueue = LinkedList<PendingPacket>()
|
||||||
private var cachedDestination: WeakReference<Interface>? = null
|
private var cachedDestination: WeakReference<Interface>? = null
|
||||||
|
|
||||||
var counter: Long = 0
|
var counter: Long = 0
|
||||||
|
|
||||||
override fun getIPAddress() = ipAddress
|
override fun getIPAddress() = ipAddress
|
||||||
override fun getMACAddress() = macAddress
|
override fun getMACAddress() = macAddress
|
||||||
|
|
||||||
abstract override fun handle(packet: Packet)
|
abstract override fun handle(packet: Packet)
|
||||||
|
|
||||||
private fun doHandlePacket(packet: Packet) {
|
private fun doHandlePacket(packet: Packet) {
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) received packet: {}", this, ipAddress, packet)
|
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) received packet: {}", this, ipAddress, packet)
|
||||||
when (packet) {
|
when (packet) {
|
||||||
is DeviceRemovedPacket -> {
|
is DeviceRemovedPacket -> {
|
||||||
arpTable.remove(packet.source)
|
arpTable.remove(packet.source)
|
||||||
}
|
}
|
||||||
is PingPacket -> {
|
|
||||||
sendPacket(PongPacket(ipAddress, packet.source))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
handle(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun send(frame: EthernetFrame) {
|
is PingPacket -> {
|
||||||
findDestination()?.receive(frame)
|
sendPacket(PongPacket(ipAddress, packet.source))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
handle(packet)
|
||||||
|
}
|
||||||
|
|
||||||
override fun receive(frame: EthernetFrame) {
|
override fun send(frame: EthernetFrame) {
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) received frame from {}: {}", this, ipAddress, macAddress, frame.source, frame)
|
findDestination()?.receive(frame)
|
||||||
when (frame) {
|
}
|
||||||
is ARPQueryFrame -> handleARPQuery(frame)
|
|
||||||
is ARPResponseFrame -> handleARPResponse(frame)
|
|
||||||
is NetworkSplitFrame -> handleNetworkSplit()
|
|
||||||
is PacketFrame -> {
|
|
||||||
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
|
|
||||||
doHandlePacket(frame.packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleARPQuery(frame: ARPQueryFrame) {
|
override fun receive(frame: EthernetFrame) {
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP)
|
PhysicalConnectivity.NETWORK_LOGGER.debug(
|
||||||
arpTable[frame.sourceIP] = frame.source
|
"{} ({}, {}) received frame from {}: {}",
|
||||||
if (frame.queryIP == ipAddress) {
|
this,
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP response to {} with {}", this, ipAddress, frame.sourceIP, macAddress)
|
ipAddress,
|
||||||
send(ARPResponseFrame(ipAddress, macAddress, frame.source))
|
macAddress,
|
||||||
}
|
frame.source,
|
||||||
}
|
frame
|
||||||
|
)
|
||||||
|
when (frame) {
|
||||||
|
is ARPQueryFrame -> handleARPQuery(frame)
|
||||||
|
is ARPResponseFrame -> handleARPResponse(frame)
|
||||||
|
is NetworkSplitFrame -> handleNetworkSplit()
|
||||||
|
is PacketFrame -> {
|
||||||
|
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
|
||||||
|
doHandlePacket(frame.packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleARPResponse(frame: ARPResponseFrame) {
|
private fun handleARPQuery(frame: ARPQueryFrame) {
|
||||||
arpTable[frame.query] = frame.source
|
PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}), received ARP query for {}", this, ipAddress, frame.queryIP)
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{}, ({}) received ARP response for {} with {}", this, ipAddress, frame.query, frame.source)
|
arpTable[frame.sourceIP] = frame.source
|
||||||
|
if (frame.queryIP == ipAddress) {
|
||||||
|
PhysicalConnectivity.NETWORK_LOGGER.debug(
|
||||||
|
"{} ({}) sending ARP response to {} with {}",
|
||||||
|
this,
|
||||||
|
ipAddress,
|
||||||
|
frame.sourceIP,
|
||||||
|
macAddress
|
||||||
|
)
|
||||||
|
send(ARPResponseFrame(ipAddress, macAddress, frame.source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val toRemove = packetQueue.filter { (packet, _) ->
|
private fun handleARPResponse(frame: ARPResponseFrame) {
|
||||||
if (packet.destination == frame.query) {
|
arpTable[frame.query] = frame.source
|
||||||
send(BasePacketFrame(packet, macAddress, frame.source))
|
PhysicalConnectivity.NETWORK_LOGGER.debug(
|
||||||
true
|
"{}, ({}) received ARP response for {} with {}",
|
||||||
} else {
|
this,
|
||||||
false
|
ipAddress,
|
||||||
}
|
frame.query,
|
||||||
}
|
frame.source
|
||||||
packetQueue.removeAll(toRemove)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun handleNetworkSplit() {
|
val toRemove = packetQueue.filter { (packet, _) ->
|
||||||
arpTable.clear()
|
if (packet.destination == frame.query) {
|
||||||
cachedDestination = null
|
send(BasePacketFrame(packet, macAddress, frame.source))
|
||||||
}
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packetQueue.removeAll(toRemove)
|
||||||
|
}
|
||||||
|
|
||||||
override fun sendPacket(packet: Packet) {
|
protected open fun handleNetworkSplit() {
|
||||||
if (packet.destination.isBroadcast) {
|
arpTable.clear()
|
||||||
send(BasePacketFrame(packet, macAddress, MACAddress.BROADCAST))
|
cachedDestination = null
|
||||||
} else if (arpTable.containsKey(packet.destination)) {
|
}
|
||||||
send(BasePacketFrame(packet, macAddress, arpTable[packet.destination]!!))
|
|
||||||
} else {
|
|
||||||
packetQueue.add(PendingPacket(packet, counter))
|
|
||||||
|
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}) sending ARP query for {}", this, ipAddress, packet.destination)
|
override fun sendPacket(packet: Packet) {
|
||||||
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
|
if (packet.destination.isBroadcast) {
|
||||||
}
|
send(BasePacketFrame(packet, macAddress, MACAddress.BROADCAST))
|
||||||
}
|
} else if (arpTable.containsKey(packet.destination)) {
|
||||||
|
send(BasePacketFrame(packet, macAddress, arpTable[packet.destination]!!))
|
||||||
|
} else {
|
||||||
|
packetQueue.add(PendingPacket(packet, counter))
|
||||||
|
|
||||||
open fun findDestination(): Interface? {
|
PhysicalConnectivity.NETWORK_LOGGER.debug(
|
||||||
val cachedDestination = this.cachedDestination?.get()
|
"{} ({}) sending ARP query for {}",
|
||||||
if (cachedDestination != null) {
|
this,
|
||||||
return cachedDestination
|
ipAddress,
|
||||||
}
|
packet.destination
|
||||||
|
)
|
||||||
|
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val sides = (cachedState.block as NetworkComponentBlock).getNetworkConnectedSides(cachedState, world!!, pos)
|
open fun findDestination(): Interface? {
|
||||||
return when (sides.size) {
|
val cachedDestination = this.cachedDestination?.get()
|
||||||
0 -> null
|
if (cachedDestination != null) {
|
||||||
1 -> {
|
return cachedDestination
|
||||||
NetworkUtil.findConnectedInterface(world!!, pos, sides.first())?.also {
|
}
|
||||||
this.cachedDestination = WeakReference(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cableDisconnected() {
|
val sides = (cachedState.block as NetworkComponentBlock).getNetworkConnectedSides(cachedState, world!!, pos)
|
||||||
cachedDestination = null
|
return when (sides.size) {
|
||||||
handleNetworkSplit()
|
0 -> null
|
||||||
}
|
1 -> {
|
||||||
|
NetworkUtil.findConnectedInterface(world!!, pos, sides.first())?.also {
|
||||||
|
this.cachedDestination = WeakReference(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open fun tick() {
|
else -> throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
|
||||||
counter++
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!world!!.isClient) {
|
override fun cableDisconnected() {
|
||||||
val toRemove = packetQueue.filter { entry ->
|
cachedDestination = null
|
||||||
val (packet, timestamp) = entry
|
handleNetworkSplit()
|
||||||
if (arpTable.containsKey(packet.destination)) {
|
}
|
||||||
send(BasePacketFrame(packet, macAddress, arpTable[packet.destination]!!))
|
|
||||||
true
|
|
||||||
} else if (counter - timestamp >= ARP_RETRY_TIMEOUT) {
|
|
||||||
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
|
|
||||||
entry.timestamp = counter
|
|
||||||
// todo: should there be a retry counter?
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
packetQueue.removeAll(toRemove)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun toCommonTag(tag: NbtCompound) {
|
open fun tick() {
|
||||||
tag.putInt("IPAddress", ipAddress.address)
|
counter++
|
||||||
tag.putLong("MACAddress", macAddress.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun fromCommonTag(tag: NbtCompound) {
|
if (!world!!.isClient) {
|
||||||
ipAddress = IPAddress(tag.getInt("IPAddress"))
|
val toRemove = packetQueue.filter { entry ->
|
||||||
macAddress = MACAddress(tag.getLong("MACAddress"))
|
val (packet, timestamp) = entry
|
||||||
}
|
if (arpTable.containsKey(packet.destination)) {
|
||||||
|
send(BasePacketFrame(packet, macAddress, arpTable[packet.destination]!!))
|
||||||
|
true
|
||||||
|
} else if (counter - timestamp >= ARP_RETRY_TIMEOUT) {
|
||||||
|
send(ARPQueryFrame(packet.destination, ipAddress, macAddress))
|
||||||
|
entry.timestamp = counter
|
||||||
|
// todo: should there be a retry counter?
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
packetQueue.removeAll(toRemove)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun writeNbt(tag: NbtCompound) {
|
protected open fun toCommonTag(tag: NbtCompound) {
|
||||||
super.writeNbt(tag)
|
tag.putInt("IPAddress", ipAddress.address)
|
||||||
toCommonTag(tag)
|
tag.putLong("MACAddress", macAddress.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun readNbt(tag: NbtCompound) {
|
protected open fun fromCommonTag(tag: NbtCompound) {
|
||||||
super.readNbt(tag)
|
ipAddress = IPAddress(tag.getInt("IPAddress"))
|
||||||
fromCommonTag(tag)
|
macAddress = MACAddress(tag.getLong("MACAddress"))
|
||||||
if (tag.getBoolean("_SyncPacket")) {
|
}
|
||||||
fromClientTag(tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toUpdatePacket(): BlockEntityUpdateS2CPacket {
|
override fun writeNbt(tag: NbtCompound) {
|
||||||
return BlockEntityUpdateS2CPacket.create(this)
|
super.writeNbt(tag)
|
||||||
}
|
toCommonTag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
override fun toInitialChunkDataNbt(): NbtCompound {
|
override fun readNbt(tag: NbtCompound) {
|
||||||
val tag = NbtCompound()
|
super.readNbt(tag)
|
||||||
tag.putBoolean("_SyncPacket", true)
|
fromCommonTag(tag)
|
||||||
return toClientTag(tag)
|
if (tag.getBoolean("_SyncPacket")) {
|
||||||
}
|
fromClientTag(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open fun toClientTag(tag: NbtCompound): NbtCompound {
|
override fun toUpdatePacket(): BlockEntityUpdateS2CPacket {
|
||||||
toCommonTag(tag)
|
return BlockEntityUpdateS2CPacket.create(this)
|
||||||
return tag
|
}
|
||||||
}
|
|
||||||
|
|
||||||
open fun fromClientTag(tag: NbtCompound) {
|
override fun toInitialChunkDataNbt(): NbtCompound {
|
||||||
}
|
val tag = NbtCompound()
|
||||||
|
tag.putBoolean("_SyncPacket", true)
|
||||||
|
return toClientTag(tag)
|
||||||
|
}
|
||||||
|
|
||||||
fun markUpdate() {
|
open fun toClientTag(tag: NbtCompound): NbtCompound {
|
||||||
markDirty()
|
toCommonTag(tag)
|
||||||
world!!.updateListeners(pos, cachedState, cachedState, 3)
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBreak() {
|
open fun fromClientTag(tag: NbtCompound) {
|
||||||
if (!world!!.isClient) {
|
}
|
||||||
sendPacket(DeviceRemovedPacket(this))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class PendingPacket(
|
fun markUpdate() {
|
||||||
val packet: Packet,
|
markDirty()
|
||||||
var timestamp: Long,
|
world!!.updateListeners(pos, cachedState, cachedState, 3)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
fun onBreak() {
|
||||||
|
if (!world!!.isClient) {
|
||||||
|
sendPacket(DeviceRemovedPacket(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class PendingPacket(
|
||||||
|
val packet: Packet,
|
||||||
|
var timestamp: Long,
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,146 +27,181 @@ import java.util.*
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class FaceDeviceBlock<T: DeviceBlockEntity>(settings: Settings): DeviceBlock<T>(settings) {
|
abstract class FaceDeviceBlock<T : DeviceBlockEntity>(settings: Settings) : DeviceBlock<T>(settings) {
|
||||||
companion object {
|
companion object {
|
||||||
val FACING = Properties.FACING
|
val FACING = Properties.FACING
|
||||||
val CABLE_CONNECTION = EnumProperty.of("cable_connection", FaceCableConnection::class.java)
|
val CABLE_CONNECTION = EnumProperty.of("cable_connection", FaceCableConnection::class.java)
|
||||||
val COLOR = EnumProperty.of("color", DyeColor::class.java)
|
val COLOR = EnumProperty.of("color", DyeColor::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FaceCableConnection : StringIdentifiable {
|
enum class FaceCableConnection : StringIdentifiable {
|
||||||
NONE, DOWN, UP, NORTH, SOUTH, WEST, EAST;
|
NONE, DOWN, UP, NORTH, SOUTH, WEST, EAST;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun from(dir: Direction?) = when (dir) {
|
fun from(dir: Direction?) = when (dir) {
|
||||||
null -> NONE
|
null -> NONE
|
||||||
Direction.DOWN -> DOWN
|
Direction.DOWN -> DOWN
|
||||||
Direction.UP -> UP
|
Direction.UP -> UP
|
||||||
Direction.NORTH -> NORTH
|
Direction.NORTH -> NORTH
|
||||||
Direction.SOUTH -> SOUTH
|
Direction.SOUTH -> SOUTH
|
||||||
Direction.WEST -> WEST
|
Direction.WEST -> WEST
|
||||||
Direction.EAST -> EAST
|
Direction.EAST -> EAST
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val direction: Direction?
|
val direction: Direction?
|
||||||
get() = when (this) {
|
get() = when (this) {
|
||||||
NONE -> null
|
NONE -> null
|
||||||
DOWN -> Direction.DOWN
|
DOWN -> Direction.DOWN
|
||||||
UP -> Direction.UP
|
UP -> Direction.UP
|
||||||
NORTH -> Direction.NORTH
|
NORTH -> Direction.NORTH
|
||||||
SOUTH -> Direction.SOUTH
|
SOUTH -> Direction.SOUTH
|
||||||
WEST -> Direction.WEST
|
WEST -> Direction.WEST
|
||||||
EAST -> Direction.EAST
|
EAST -> Direction.EAST
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun asString() = name.toLowerCase()
|
override fun asString() = name.toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract val faceThickness: Double
|
protected abstract val faceThickness: Double
|
||||||
abstract val faceShapes: Map<Direction, VoxelShape>
|
abstract val faceShapes: Map<Direction, VoxelShape>
|
||||||
private val centerShapes: Map<Direction, VoxelShape> by lazy {
|
private val centerShapes: Map<Direction, VoxelShape> by lazy {
|
||||||
mapOf(
|
mapOf(
|
||||||
Direction.DOWN to createCuboidShape(6.0, faceThickness, 6.0, 10.0, 10.0, 10.0),
|
Direction.DOWN to createCuboidShape(6.0, faceThickness, 6.0, 10.0, 10.0, 10.0),
|
||||||
Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 16.0 - faceThickness, 10.0),
|
Direction.UP to createCuboidShape(6.0, 6.0, 6.0, 10.0, 16.0 - faceThickness, 10.0),
|
||||||
Direction.NORTH to createCuboidShape(6.0, 6.0, faceThickness, 10.0, 10.0, 10.0),
|
Direction.NORTH to createCuboidShape(6.0, 6.0, faceThickness, 10.0, 10.0, 10.0),
|
||||||
Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 16.0 - faceThickness),
|
Direction.SOUTH to createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 16.0 - faceThickness),
|
||||||
Direction.WEST to createCuboidShape(faceThickness, 6.0, 6.0, 10.0, 10.0, 10.0),
|
Direction.WEST to createCuboidShape(faceThickness, 6.0, 6.0, 10.0, 10.0, 10.0),
|
||||||
Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 16.0 - faceThickness, 10.0, 10.0)
|
Direction.EAST to createCuboidShape(6.0, 6.0, 6.0, 16.0 - faceThickness, 10.0, 10.0)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private val shapeCache = mutableMapOf<Pair<Direction, FaceCableConnection>, VoxelShape>()
|
private val shapeCache = mutableMapOf<Pair<Direction, FaceCableConnection>, VoxelShape>()
|
||||||
|
|
||||||
private fun getShape(facing: Direction, cableConnection: FaceCableConnection): VoxelShape {
|
private fun getShape(facing: Direction, cableConnection: FaceCableConnection): VoxelShape {
|
||||||
return shapeCache.getOrPut(facing to cableConnection) {
|
return shapeCache.getOrPut(facing to cableConnection) {
|
||||||
if (cableConnection == FaceCableConnection.NONE) {
|
if (cableConnection == FaceCableConnection.NONE) {
|
||||||
VoxelShapes.union(faceShapes[facing], centerShapes[facing])
|
VoxelShapes.union(faceShapes[facing], centerShapes[facing])
|
||||||
} else {
|
} else {
|
||||||
VoxelShapes.union(faceShapes[facing], centerShapes[facing], CableBlock.SIDE_SHAPES[cableConnection.direction])
|
VoxelShapes.union(
|
||||||
}
|
faceShapes[facing],
|
||||||
}
|
centerShapes[facing],
|
||||||
}
|
CableBlock.SIDE_SHAPES[cableConnection.direction]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||||
val direction = state[CABLE_CONNECTION].direction
|
val direction = state[CABLE_CONNECTION].direction
|
||||||
return if (direction != null) EnumSet.of(direction) else setOf()
|
return if (direction != null) EnumSet.of(direction) else setOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
|
override fun getNetworkInterfaceForSide(
|
||||||
return if (side == state[FACING]) {
|
side: Direction,
|
||||||
null
|
state: BlockState,
|
||||||
} else {
|
world: WorldAccess,
|
||||||
getBlockEntity(world, pos)
|
pos: BlockPos
|
||||||
}
|
): Interface? {
|
||||||
}
|
return if (side == state[FACING]) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
getBlockEntity(world, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||||
super.appendProperties(builder)
|
super.appendProperties(builder)
|
||||||
builder.add(FACING)
|
builder.add(FACING)
|
||||||
builder.add(CABLE_CONNECTION)
|
builder.add(CABLE_CONNECTION)
|
||||||
builder.add(COLOR)
|
builder.add(COLOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||||
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
|
val facing =
|
||||||
// todo: this should never be called
|
if (context.player?.isSneaking == true) context.side.opposite else context.playerLookDirection.opposite
|
||||||
val cableConnection = FaceCableConnection.from(getCableConnectedSide(context.world, context.blockPos, facing, DyeColor.BLUE))
|
// todo: this should never be called
|
||||||
return defaultState
|
val cableConnection =
|
||||||
.with(FACING, facing)
|
FaceCableConnection.from(getCableConnectedSide(context.world, context.blockPos, facing, DyeColor.BLUE))
|
||||||
.with(CABLE_CONNECTION, cableConnection)
|
return defaultState
|
||||||
}
|
.with(FACING, facing)
|
||||||
|
.with(CABLE_CONNECTION, cableConnection)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getCableConnectedSide(world: WorldAccess, pos: BlockPos, facing: Direction, color: DyeColor): Direction? {
|
private fun getCableConnectedSide(
|
||||||
for (side in Direction.values()) {
|
world: WorldAccess,
|
||||||
if (side == facing) {
|
pos: BlockPos,
|
||||||
continue
|
facing: Direction,
|
||||||
}
|
color: DyeColor
|
||||||
val offsetPos = pos.offset(side)
|
): Direction? {
|
||||||
val state = world.getBlockState(offsetPos)
|
for (side in Direction.values()) {
|
||||||
if (canConnectTo(world, side, state, offsetPos, color)) {
|
if (side == facing) {
|
||||||
return side
|
continue
|
||||||
}
|
}
|
||||||
}
|
val offsetPos = pos.offset(side)
|
||||||
return null
|
val state = world.getBlockState(offsetPos)
|
||||||
}
|
if (canConnectTo(world, side, state, offsetPos, color)) {
|
||||||
|
return side
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private fun canConnectTo(world: WorldAccess, side: Direction, candidateState: BlockState, candidatePos: BlockPos, myColor: DyeColor): Boolean {
|
private fun canConnectTo(
|
||||||
val block = candidateState.block
|
world: WorldAccess,
|
||||||
return if (block is FaceDeviceBlock<*> && candidateState[COLOR] == myColor) {
|
side: Direction,
|
||||||
true
|
candidateState: BlockState,
|
||||||
} else if (block is CableBlock && block.color == myColor) {
|
candidatePos: BlockPos,
|
||||||
true
|
myColor: DyeColor
|
||||||
} else {
|
): Boolean {
|
||||||
block is NetworkComponentBlock && block.getNetworkConnectedSides(candidateState, world, candidatePos).contains(side.opposite)
|
val block = candidateState.block
|
||||||
}
|
return if (block is FaceDeviceBlock<*> && candidateState[COLOR] == myColor) {
|
||||||
}
|
true
|
||||||
|
} else if (block is CableBlock && block.color == myColor) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
block is NetworkComponentBlock && block.getNetworkConnectedSides(candidateState, world, candidatePos)
|
||||||
|
.contains(side.opposite)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, pos: BlockPos, neighborPos: BlockPos): BlockState {
|
override fun getStateForNeighborUpdate(
|
||||||
val current = state[CABLE_CONNECTION]
|
state: BlockState,
|
||||||
var newConnection = current
|
side: Direction,
|
||||||
|
neighborState: BlockState,
|
||||||
|
world: WorldAccess,
|
||||||
|
pos: BlockPos,
|
||||||
|
neighborPos: BlockPos
|
||||||
|
): BlockState {
|
||||||
|
val current = state[CABLE_CONNECTION]
|
||||||
|
var newConnection = current
|
||||||
|
|
||||||
if (current == FaceCableConnection.NONE) {
|
if (current == FaceCableConnection.NONE) {
|
||||||
if (canConnectTo(world, side, neighborState, neighborPos, state[COLOR])) {
|
if (canConnectTo(world, side, neighborState, neighborPos, state[COLOR])) {
|
||||||
newConnection = FaceCableConnection.from(side)
|
newConnection = FaceCableConnection.from(side)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val currentConnectedPos = pos.offset(current.direction)
|
val currentConnectedPos = pos.offset(current.direction)
|
||||||
if (neighborPos == currentConnectedPos && neighborState.block !is NetworkComponentBlock) {
|
if (neighborPos == currentConnectedPos && neighborState.block !is NetworkComponentBlock) {
|
||||||
// the old cable connection is no longer correct, try to find another
|
// the old cable connection is no longer correct, try to find another
|
||||||
newConnection = FaceCableConnection.from(getCableConnectedSide(world, pos, state[FACING], state[COLOR]))
|
newConnection = FaceCableConnection.from(getCableConnectedSide(world, pos, state[FACING], state[COLOR]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state.with(CABLE_CONNECTION, newConnection)
|
return state.with(CABLE_CONNECTION, newConnection)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
|
override fun getOutlineShape(
|
||||||
return getShape(state[FACING], state[CABLE_CONNECTION])
|
state: BlockState,
|
||||||
}
|
world: BlockView,
|
||||||
|
pos: BlockPos,
|
||||||
|
context: ShapeContext
|
||||||
|
): VoxelShape {
|
||||||
|
return getShape(state[FACING], state[CABLE_CONNECTION])
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStacksDropped(state: BlockState, world: ServerWorld, pos: BlockPos, stack: ItemStack) {
|
override fun onStacksDropped(state: BlockState, world: ServerWorld, pos: BlockPos, stack: ItemStack) {
|
||||||
super.onStacksDropped(state, world, pos, stack)
|
super.onStacksDropped(state, world, pos, stack)
|
||||||
|
|
||||||
val cableStack = ItemStack(PhyItems.CABLES[state[COLOR]])
|
val cableStack = ItemStack(PhyItems.CABLES[state[COLOR]])
|
||||||
dropStack(world, pos, cableStack)
|
dropStack(world, pos, cableStack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,180 +37,209 @@ import java.util.*
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class CableBlock(
|
class CableBlock(
|
||||||
val color: DyeColor,
|
val color: DyeColor,
|
||||||
): Block(
|
) : Block(
|
||||||
FabricBlockSettings.of(CABLE_MATERIAL)
|
FabricBlockSettings.of(CABLE_MATERIAL)
|
||||||
.strength(0.3f)
|
.strength(0.3f)
|
||||||
.nonOpaque()
|
.nonOpaque()
|
||||||
), NetworkCableBlock {
|
), NetworkCableBlock {
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "cable")
|
val ID = Identifier(PhysicalConnectivity.MODID, "cable")
|
||||||
val CABLE_MATERIAL = Material.Builder(MapColor.BLUE).build()
|
val CABLE_MATERIAL = Material.Builder(MapColor.BLUE).build()
|
||||||
val CENTER_SHAPE = createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 10.0)
|
val CENTER_SHAPE = createCuboidShape(6.0, 6.0, 6.0, 10.0, 10.0, 10.0)
|
||||||
val SIDE_SHAPES = mapOf<Direction, VoxelShape>(
|
val SIDE_SHAPES = mapOf<Direction, VoxelShape>(
|
||||||
Direction.DOWN to createCuboidShape(6.0, 0.0, 6.0, 10.0, 6.0, 10.0),
|
Direction.DOWN to createCuboidShape(6.0, 0.0, 6.0, 10.0, 6.0, 10.0),
|
||||||
Direction.UP to createCuboidShape(6.0, 10.0, 6.0, 10.0, 16.0, 10.0),
|
Direction.UP to createCuboidShape(6.0, 10.0, 6.0, 10.0, 16.0, 10.0),
|
||||||
Direction.NORTH to createCuboidShape(6.0, 6.0, 0.0, 10.0, 10.0, 6.0),
|
Direction.NORTH to createCuboidShape(6.0, 6.0, 0.0, 10.0, 10.0, 6.0),
|
||||||
Direction.SOUTH to createCuboidShape(6.0, 6.0, 10.0, 10.0, 10.0, 16.0),
|
Direction.SOUTH to createCuboidShape(6.0, 6.0, 10.0, 10.0, 10.0, 16.0),
|
||||||
Direction.WEST to createCuboidShape(0.0, 6.0, 6.0, 6.0, 10.0, 10.0),
|
Direction.WEST to createCuboidShape(0.0, 6.0, 6.0, 6.0, 10.0, 10.0),
|
||||||
Direction.EAST to createCuboidShape(10.0, 6.0, 6.0, 16.0, 10.0, 10.0)
|
Direction.EAST to createCuboidShape(10.0, 6.0, 6.0, 16.0, 10.0, 10.0)
|
||||||
)
|
)
|
||||||
private val SHAPE_CACHE = Array<VoxelShape>(64) { key ->
|
private val SHAPE_CACHE = Array<VoxelShape>(64) { key ->
|
||||||
val connectedSides = Direction.values().filterIndexed { index, _ ->
|
val connectedSides = Direction.values().filterIndexed { index, _ ->
|
||||||
((key shr index) and 1) == 1
|
((key shr index) and 1) == 1
|
||||||
}
|
}
|
||||||
connectedSides.fold(CENTER_SHAPE) { acc, side ->
|
connectedSides.fold(CENTER_SHAPE) { acc, side ->
|
||||||
VoxelShapes.union(acc, SIDE_SHAPES[side])
|
VoxelShapes.union(acc, SIDE_SHAPES[side])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val CONNECTIONS: Map<Direction, EnumProperty<CableConnection>> = Direction.values().associate { it to EnumProperty.of(it.name.toLowerCase(), CableConnection::class.java) }
|
val CONNECTIONS: Map<Direction, EnumProperty<CableConnection>> =
|
||||||
|
Direction.values().associate { it to EnumProperty.of(it.name.toLowerCase(), CableConnection::class.java) }
|
||||||
|
|
||||||
fun getShape(state: BlockState): VoxelShape {
|
fun getShape(state: BlockState): VoxelShape {
|
||||||
val key = Direction.values().foldIndexed(0) { i, acc, dir ->
|
val key = Direction.values().foldIndexed(0) { i, acc, dir ->
|
||||||
if (state[CONNECTIONS[dir]] == CableConnection.ON) {
|
if (state[CONNECTIONS[dir]] == CableConnection.ON) {
|
||||||
acc or (1 shl i)
|
acc or (1 shl i)
|
||||||
} else {
|
} else {
|
||||||
acc
|
acc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return SHAPE_CACHE[key]
|
return SHAPE_CACHE[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
defaultState = CONNECTIONS.values.fold(stateManager.defaultState) { acc, prop ->
|
defaultState = CONNECTIONS.values.fold(stateManager.defaultState) { acc, prop ->
|
||||||
acc.with(prop, CableConnection.OFF)
|
acc.with(prop, CableConnection.OFF)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||||
val set = EnumSet.noneOf(Direction::class.java)
|
val set = EnumSet.noneOf(Direction::class.java)
|
||||||
for ((side, prop) in CONNECTIONS) {
|
for ((side, prop) in CONNECTIONS) {
|
||||||
if (state[prop] == CableConnection.ON) {
|
if (state[prop] == CableConnection.ON) {
|
||||||
set.add(side)
|
set.add(side)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||||
super.appendProperties(builder)
|
super.appendProperties(builder)
|
||||||
CONNECTIONS.values.forEach {
|
CONNECTIONS.values.forEach {
|
||||||
builder.add(it)
|
builder.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInitialState(world: World, pos: BlockPos): BlockState {
|
fun getInitialState(world: World, pos: BlockPos): BlockState {
|
||||||
return CONNECTIONS.entries.fold(defaultState, { acc, (dir, prop) ->
|
return CONNECTIONS.entries.fold(defaultState, { acc, (dir, prop) ->
|
||||||
acc.with(prop, getConnectionStateInDirection(world, pos, dir))
|
acc.with(prop, getConnectionStateInDirection(world, pos, dir))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||||
return getInitialState(context.world, context.blockPos)
|
return getInitialState(context.world, context.blockPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStateForNeighborUpdate(state: BlockState, side: Direction, neighborState: BlockState, world: WorldAccess, blockPos_1: BlockPos, blockPos_2: BlockPos): BlockState {
|
override fun getStateForNeighborUpdate(
|
||||||
val prop = CONNECTIONS[side]
|
state: BlockState,
|
||||||
val current = state[prop]
|
side: Direction,
|
||||||
return when (current) {
|
neighborState: BlockState,
|
||||||
CableConnection.DISABLED -> state
|
world: WorldAccess,
|
||||||
else -> state.with(prop, getConnectionStateInDirection(world, blockPos_1, side))
|
blockPos_1: BlockPos,
|
||||||
}
|
blockPos_2: BlockPos
|
||||||
}
|
): BlockState {
|
||||||
|
val prop = CONNECTIONS[side]
|
||||||
|
val current = state[prop]
|
||||||
|
return when (current) {
|
||||||
|
CableConnection.DISABLED -> state
|
||||||
|
else -> state.with(prop, getConnectionStateInDirection(world, blockPos_1, side))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getConnectionStateInDirection(world: WorldAccess, pos: BlockPos, direction: Direction): CableConnection {
|
private fun getConnectionStateInDirection(
|
||||||
val offsetPos = pos.offset(direction)
|
world: WorldAccess,
|
||||||
val state = world.getBlockState(offsetPos)
|
pos: BlockPos,
|
||||||
val block = state.block
|
direction: Direction
|
||||||
return if (block == this) {
|
): CableConnection {
|
||||||
val prop = CONNECTIONS[direction.opposite]
|
val offsetPos = pos.offset(direction)
|
||||||
when (state[prop]) {
|
val state = world.getBlockState(offsetPos)
|
||||||
CableConnection.DISABLED -> CableConnection.DISABLED
|
val block = state.block
|
||||||
else -> CableConnection.ON
|
return if (block == this) {
|
||||||
}
|
val prop = CONNECTIONS[direction.opposite]
|
||||||
} else if (block is NetworkComponentBlock && block !is CableBlock) {
|
when (state[prop]) {
|
||||||
if (block.getNetworkConnectedSides(state, world, offsetPos).contains(direction.opposite)) {
|
CableConnection.DISABLED -> CableConnection.DISABLED
|
||||||
CableConnection.ON
|
else -> CableConnection.ON
|
||||||
} else {
|
}
|
||||||
CableConnection.OFF
|
} else if (block is NetworkComponentBlock && block !is CableBlock) {
|
||||||
}
|
if (block.getNetworkConnectedSides(state, world, offsetPos).contains(direction.opposite)) {
|
||||||
} else {
|
CableConnection.ON
|
||||||
CableConnection.OFF
|
} else {
|
||||||
}
|
CableConnection.OFF
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
CableConnection.OFF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
|
override fun getNetworkInterfaceForSide(
|
||||||
// cables don't have network interfaces
|
side: Direction,
|
||||||
return null
|
state: BlockState,
|
||||||
}
|
world: WorldAccess,
|
||||||
|
pos: BlockPos
|
||||||
|
): Interface? {
|
||||||
|
// cables don't have network interfaces
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
override fun onUse(
|
override fun onUse(
|
||||||
state: BlockState,
|
state: BlockState,
|
||||||
world: World,
|
world: World,
|
||||||
pos: BlockPos,
|
pos: BlockPos,
|
||||||
player: PlayerEntity,
|
player: PlayerEntity,
|
||||||
hand: Hand,
|
hand: Hand,
|
||||||
hitResult: BlockHitResult
|
hitResult: BlockHitResult
|
||||||
): ActionResult {
|
): ActionResult {
|
||||||
if (player.getStackInHand(hand).item == PhyItems.SCREWDRIVER) {
|
if (player.getStackInHand(hand).item == PhyItems.SCREWDRIVER) {
|
||||||
val hitPos = Vec3d(hitResult.pos.x - pos.x, hitResult.pos.y - pos.y, hitResult.pos.z - pos.z)
|
val hitPos = Vec3d(hitResult.pos.x - pos.x, hitResult.pos.y - pos.y, hitResult.pos.z - pos.z)
|
||||||
val hitConnection = SIDE_SHAPES.entries.firstOrNull { (_, shape) ->
|
val hitConnection = SIDE_SHAPES.entries.firstOrNull { (_, shape) ->
|
||||||
shape.boundingBox.containsInclusive(hitPos)
|
shape.boundingBox.containsInclusive(hitPos)
|
||||||
}
|
}
|
||||||
if (hitConnection != null) {
|
if (hitConnection != null) {
|
||||||
val side = hitConnection.key
|
val side = hitConnection.key
|
||||||
val prop = CONNECTIONS[side]
|
val prop = CONNECTIONS[side]
|
||||||
val newState = when (state[prop]) {
|
val newState = when (state[prop]) {
|
||||||
CableConnection.DISABLED -> {
|
CableConnection.DISABLED -> {
|
||||||
// if the block this cable is connecting to on the side that will be re-enabled is a cable that
|
// if the block this cable is connecting to on the side that will be re-enabled is a cable that
|
||||||
// is disabled on the side that connects it to us, we also re-enable that cable to make sure both
|
// is disabled on the side that connects it to us, we also re-enable that cable to make sure both
|
||||||
// "halves" of the connection are in the same state
|
// "halves" of the connection are in the same state
|
||||||
val connectedToPos = pos.offset(side)
|
val connectedToPos = pos.offset(side)
|
||||||
val connectedTo = world.getBlockState(connectedToPos)
|
val connectedTo = world.getBlockState(connectedToPos)
|
||||||
if (connectedTo.block == this && connectedTo[CONNECTIONS[side.opposite]] == CableConnection.DISABLED) {
|
if (connectedTo.block == this && connectedTo[CONNECTIONS[side.opposite]] == CableConnection.DISABLED) {
|
||||||
world.setBlockState(connectedToPos, connectedTo.with(CONNECTIONS[side.opposite], CableConnection.ON))
|
world.setBlockState(
|
||||||
}
|
connectedToPos,
|
||||||
|
connectedTo.with(CONNECTIONS[side.opposite], CableConnection.ON)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
state.with(prop, if (connectedTo.block is NetworkComponentBlock) CableConnection.ON else CableConnection.OFF)
|
state.with(
|
||||||
}
|
prop,
|
||||||
else -> state.with(prop, CableConnection.DISABLED)
|
if (connectedTo.block is NetworkComponentBlock) CableConnection.ON else CableConnection.OFF
|
||||||
}
|
)
|
||||||
world.setBlockState(pos, newState)
|
}
|
||||||
}
|
|
||||||
return ActionResult.SUCCESS
|
|
||||||
}
|
|
||||||
return ActionResult.PASS
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun canReplace(state: BlockState, context: ItemPlacementContext): Boolean {
|
else -> state.with(prop, CableConnection.DISABLED)
|
||||||
return context.stack.item is FaceDeviceBlockItem
|
}
|
||||||
}
|
world.setBlockState(pos, newState)
|
||||||
|
}
|
||||||
|
return ActionResult.SUCCESS
|
||||||
|
}
|
||||||
|
return ActionResult.PASS
|
||||||
|
}
|
||||||
|
|
||||||
override fun isTranslucent(blockState_1: BlockState?, blockView_1: BlockView?, blockPos_1: BlockPos?): Boolean {
|
override fun canReplace(state: BlockState, context: ItemPlacementContext): Boolean {
|
||||||
return true
|
return context.stack.item is FaceDeviceBlockItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun isTranslucent(blockState_1: BlockState?, blockView_1: BlockView?, blockPos_1: BlockPos?): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// override fun isSimpleFullBlock(blockState_1: BlockState?, blockView_1: BlockView?, blockPos_1: BlockPos?): Boolean {
|
// override fun isSimpleFullBlock(blockState_1: BlockState?, blockView_1: BlockView?, blockPos_1: BlockPos?): Boolean {
|
||||||
// return false
|
// return false
|
||||||
// }
|
// }
|
||||||
|
|
||||||
override fun getOutlineShape(state: BlockState, world: BlockView, pos: BlockPos, context: ShapeContext): VoxelShape {
|
override fun getOutlineShape(
|
||||||
return getShape(state)
|
state: BlockState,
|
||||||
}
|
world: BlockView,
|
||||||
|
pos: BlockPos,
|
||||||
|
context: ShapeContext
|
||||||
|
): VoxelShape {
|
||||||
|
return getShape(state)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
|
override fun onBreak(world: World, pos: BlockPos, state: BlockState, player: PlayerEntity) {
|
||||||
super.onBreak(world, pos, state, player)
|
super.onBreak(world, pos, state, player)
|
||||||
if (!world.isClient) {
|
if (!world.isClient) {
|
||||||
world.server?.execute {
|
world.server?.execute {
|
||||||
// notify devices on either end that the connection was broken (i.e., unset the cached receivers)
|
// notify devices on either end that the connection was broken (i.e., unset the cached receivers)
|
||||||
val connectedSides = getNetworkConnectedSides(state, world, pos)
|
val connectedSides = getNetworkConnectedSides(state, world, pos)
|
||||||
for (side in connectedSides) {
|
for (side in connectedSides) {
|
||||||
val dest = NetworkUtil.findConnectedInterface(world, pos, side)
|
val dest = NetworkUtil.findConnectedInterface(world, pos, side)
|
||||||
dest?.cableDisconnected()
|
dest?.cableDisconnected()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,66 +21,61 @@ import kotlin.math.min
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class ExtractorBlock: FaceDeviceBlock<ExtractorBlockEntity>(
|
class ExtractorBlock : FaceDeviceBlock<ExtractorBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "extractor")
|
val ID = Identifier(PhysicalConnectivity.MODID, "extractor")
|
||||||
private val EXTRACTOR_SHAPES = mutableMapOf<Direction, VoxelShape>()
|
private val EXTRACTOR_SHAPES = mutableMapOf<Direction, VoxelShape>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val components = arrayOf(
|
val components = arrayOf(
|
||||||
doubleArrayOf(0.0, 0.0, 0.0, 16.0, 2.0, 16.0),
|
doubleArrayOf(0.0, 0.0, 0.0, 16.0, 2.0, 16.0),
|
||||||
doubleArrayOf(2.0, 2.0, 2.0, 14.0, 4.0, 14.0),
|
doubleArrayOf(2.0, 2.0, 2.0, 14.0, 4.0, 14.0),
|
||||||
doubleArrayOf(4.0, 4.0, 4.0, 12.0, 6.0, 12.0),
|
doubleArrayOf(4.0, 4.0, 4.0, 12.0, 6.0, 12.0),
|
||||||
)
|
)
|
||||||
val directions = arrayOf(
|
val directions = arrayOf(
|
||||||
Triple(Direction.DOWN, null, false),
|
Triple(Direction.DOWN, null, false),
|
||||||
Triple(Direction.UP, null, true),
|
Triple(Direction.UP, null, true),
|
||||||
Triple(Direction.NORTH, 2, false),
|
Triple(Direction.NORTH, 2, false),
|
||||||
Triple(Direction.SOUTH, 2, true),
|
Triple(Direction.SOUTH, 2, true),
|
||||||
Triple(Direction.WEST, 1, false),
|
Triple(Direction.WEST, 1, false),
|
||||||
Triple(Direction.EAST, 1, true),
|
Triple(Direction.EAST, 1, true),
|
||||||
)
|
)
|
||||||
for ((dir, rotate, flip) in directions) {
|
for ((dir, rotate, flip) in directions) {
|
||||||
val shapes = components.map { it ->
|
val shapes = components.map { it ->
|
||||||
val arr = it.copyOf()
|
val arr = it.copyOf()
|
||||||
if (rotate != null) {
|
if (rotate != null) {
|
||||||
for (i in 0 until 3) {
|
for (i in 0 until 3) {
|
||||||
arr[i] = it[(i + rotate) % 3]
|
arr[i] = it[(i + rotate) % 3]
|
||||||
arr[3 + i] = it[3 + ((i + rotate) % 3)]
|
arr[3 + i] = it[3 + ((i + rotate) % 3)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flip) {
|
if (flip) {
|
||||||
for (i in arr.indices) {
|
for (i in arr.indices) {
|
||||||
arr[i] = 16.0 - arr[i]
|
arr[i] = 16.0 - arr[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createCuboidShape(min(arr[0], arr[3]), min(arr[1], arr[4]), min(arr[2], arr[5]), max(arr[0], arr[3]), max(arr[1], arr[4]), max(arr[2], arr[5]))
|
createCuboidShape(
|
||||||
}
|
min(arr[0], arr[3]),
|
||||||
EXTRACTOR_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) }
|
min(arr[1], arr[4]),
|
||||||
}
|
min(arr[2], arr[5]),
|
||||||
}
|
max(arr[0], arr[3]),
|
||||||
}
|
max(arr[1], arr[4]),
|
||||||
|
max(arr[2], arr[5])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
EXTRACTOR_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val faceThickness = 6.0
|
override val faceThickness = 6.0
|
||||||
override val faceShapes: Map<Direction, VoxelShape> = EXTRACTOR_SHAPES
|
override val faceShapes: Map<Direction, VoxelShape> = EXTRACTOR_SHAPES
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = ExtractorBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = ExtractorBlockEntity(pos, state)
|
||||||
|
|
||||||
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, stack: ItemStack) {
|
|
||||||
if (!world.isClient) {
|
|
||||||
getBlockEntity(world, pos)!!.updateInventory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighbor: Block, neighborPos: BlockPos, bl: Boolean) {
|
|
||||||
if (!world.isClient) {
|
|
||||||
getBlockEntity(world, pos)!!.updateInventory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package net.shadowfacts.phycon.block.extractor
|
package net.shadowfacts.phycon.block.extractor
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.SearchOptions
|
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
|
||||||
import alexiil.mc.lib.attributes.Simulation
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
|
||||||
import alexiil.mc.lib.attributes.item.FixedItemInv
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
import alexiil.mc.lib.attributes.item.ItemAttributes
|
import net.fabricmc.fabric.api.transfer.v1.storage.Storage
|
||||||
import alexiil.mc.lib.attributes.item.filter.ExactItemStackFilter
|
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.nbt.NbtCompound
|
import net.minecraft.nbt.NbtCompound
|
||||||
|
import net.minecraft.server.world.ServerWorld
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.Direction
|
import net.minecraft.util.math.Direction
|
||||||
import net.shadowfacts.phycon.api.packet.Packet
|
import net.shadowfacts.phycon.api.packet.Packet
|
||||||
|
@ -23,111 +24,120 @@ import net.shadowfacts.phycon.packet.ItemStackPacket
|
||||||
import net.shadowfacts.phycon.packet.RemoteActivationPacket
|
import net.shadowfacts.phycon.packet.RemoteActivationPacket
|
||||||
import net.shadowfacts.phycon.util.ActivationMode
|
import net.shadowfacts.phycon.util.ActivationMode
|
||||||
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
||||||
|
import net.shadowfacts.phycon.util.copyWithCount
|
||||||
|
import kotlin.math.min
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class ExtractorBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.EXTRACTOR, pos, state),
|
class ExtractorBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>,
|
DeviceBlockEntity(PhyBlockEntities.EXTRACTOR, pos, state),
|
||||||
ActivationController.ActivatableDevice,
|
NetworkStackDispatcher<ExtractorBlockEntity.PendingInsertion>,
|
||||||
ClientConfigurableDevice {
|
ActivationController.ActivatableDevice,
|
||||||
|
ClientConfigurableDevice {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SLEEP_TIME = 40L
|
val SLEEP_TIME = 40L
|
||||||
}
|
}
|
||||||
|
|
||||||
private val facing: Direction
|
private val facing: Direction
|
||||||
get() = cachedState[FaceDeviceBlock.FACING]
|
get() = cachedState[FaceDeviceBlock.FACING]
|
||||||
|
|
||||||
private var inventory: FixedItemInv? = null
|
private var inventory: Pair<BlockState, BlockApiCache<Storage<ItemVariant>, Direction>>? = null
|
||||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||||
override val dispatchStackTimeout = 1L
|
override val dispatchStackTimeout = 1L
|
||||||
override val controller = ActivationController(SLEEP_TIME, this)
|
override val controller = ActivationController(SLEEP_TIME, this)
|
||||||
|
|
||||||
fun updateInventory() {
|
private fun getInventory(): Storage<ItemVariant>? {
|
||||||
val offsetPos = pos.offset(facing)
|
if (inventory == null) {
|
||||||
val option = SearchOptions.inDirection(facing)
|
val offsetPos = pos.offset(facing)
|
||||||
inventory = ItemAttributes.FIXED_INV.getFirstOrNull(world, offsetPos, option)
|
val cachedFacedBlock = world!!.getBlockState(offsetPos)
|
||||||
}
|
val inventory = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, offsetPos)
|
||||||
|
this.inventory = Pair(cachedFacedBlock, inventory)
|
||||||
|
}
|
||||||
|
return inventory!!.second.find(inventory!!.first, facing.opposite)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getInventory(): FixedItemInv? {
|
override fun handle(packet: Packet) {
|
||||||
if (inventory == null) updateInventory()
|
when (packet) {
|
||||||
return inventory
|
is CapacityPacket -> handleCapacity(packet)
|
||||||
}
|
is ItemStackPacket -> handleItemStack(packet)
|
||||||
|
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
when (packet) {
|
// we can't insert things back into the inventory, so just let them spawn
|
||||||
is CapacityPacket -> handleCapacity(packet)
|
return packet.stack
|
||||||
is ItemStackPacket -> handleItemStack(packet)
|
}
|
||||||
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
||||||
// we can't insert things back into the inventory, so just let them spawn
|
|
||||||
return packet.stack
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
||||||
|
val inventory = getInventory() ?: return insertion.stack
|
||||||
|
// if the inventory has changed, the old slot index is meaningless
|
||||||
|
if (inventory !== insertion.inventory) return insertion.stack
|
||||||
|
val transaction = Transaction.openOuter()
|
||||||
|
val extractedAmount = inventory.extract(ItemVariant.of(insertion.stack), insertion.stack.count.toLong(), transaction).toInt()
|
||||||
|
transaction.commit()
|
||||||
|
if (extractedAmount == 0) {
|
||||||
|
return insertion.stack
|
||||||
|
} else if (extractedAmount != insertion.stack.count) {
|
||||||
|
// if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have
|
||||||
|
insertion.stack = insertion.stack.copyWithCount(extractedAmount)
|
||||||
|
}
|
||||||
|
return super.finishInsertion(insertion)
|
||||||
|
}
|
||||||
|
|
||||||
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
override fun tick() {
|
||||||
val inventory = getInventory() ?: return insertion.stack
|
super.tick()
|
||||||
// if the inventory has changed, the old slot index is meaningless
|
|
||||||
if (inventory !== insertion.inventory) return insertion.stack
|
|
||||||
val extracted = inventory.extractStack(insertion.inventorySlot, ExactItemStackFilter(insertion.stack), ItemStack.EMPTY, insertion.totalCapacity, Simulation.ACTION)
|
|
||||||
if (extracted.isEmpty) return insertion.stack
|
|
||||||
// if we extracted less than expected, make sure super.finishInsertion doesn't send more than we actually have
|
|
||||||
insertion.stack = extracted
|
|
||||||
return super.finishInsertion(insertion)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tick() {
|
if (!world!!.isClient) {
|
||||||
super.tick()
|
controller.tick()
|
||||||
|
|
||||||
if (!world!!.isClient) {
|
finishTimedOutPendingInsertions()
|
||||||
controller.tick()
|
}
|
||||||
|
}
|
||||||
|
|
||||||
finishTimedOutPendingInsertions()
|
override fun activate(): Boolean {
|
||||||
}
|
val inventory = getInventory() ?: return false
|
||||||
}
|
for (view in inventory.iterator(null)) {
|
||||||
|
if (view.amount <= 0) continue
|
||||||
|
val transaction = Transaction.openOuter()
|
||||||
|
var extractableAmount = inventory.simulateExtract(view.resource, view.amount, transaction).toInt()
|
||||||
|
transaction.close()
|
||||||
|
if (extractableAmount <= 0) continue
|
||||||
|
extractableAmount = min(extractableAmount, view.resource.item.maxCount)
|
||||||
|
dispatchItemStack(view.resource.toStack(extractableAmount)) { insertion ->
|
||||||
|
insertion.inventory = inventory
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
override fun activate(): Boolean {
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
val inventory = getInventory() ?: return false
|
super.toCommonTag(tag)
|
||||||
for (slot in 0 until inventory.slotCount) {
|
writeDeviceConfiguration(tag)
|
||||||
val slotStack = inventory.getInvStack(slot)
|
}
|
||||||
if (slotStack.isEmpty) continue
|
|
||||||
val extractable = inventory.extractStack(slot, ExactItemStackFilter(slotStack), ItemStack.EMPTY, slotStack.count, Simulation.SIMULATE)
|
|
||||||
if (extractable.isEmpty) continue
|
|
||||||
dispatchItemStack(extractable) { insertion ->
|
|
||||||
insertion.inventory = inventory
|
|
||||||
insertion.inventorySlot = slot
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
super.toCommonTag(tag)
|
super.fromCommonTag(tag)
|
||||||
writeDeviceConfiguration(tag)
|
loadDeviceConfiguration(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
tag.putString("ActivationMode", controller.activationMode.name)
|
||||||
loadDeviceConfiguration(tag)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
||||||
tag.putString("ActivationMode", controller.activationMode.name)
|
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
class PendingInsertion(stack: ItemStack, timestamp: Long) :
|
||||||
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
|
NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
||||||
}
|
lateinit var inventory: Storage<ItemVariant>
|
||||||
|
var inventorySlot by Delegates.notNull<Int>()
|
||||||
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
}
|
||||||
lateinit var inventory: FixedItemInv
|
|
||||||
var inventorySlot by Delegates.notNull<Int>()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,87 +31,89 @@ import kotlin.math.min
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class InserterBlock: FaceDeviceBlock<InserterBlockEntity>(
|
class InserterBlock : FaceDeviceBlock<InserterBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "inserter")
|
val ID = Identifier(PhysicalConnectivity.MODID, "inserter")
|
||||||
private val INSERTER_SHAPES = mutableMapOf<Direction, VoxelShape>()
|
private val INSERTER_SHAPES = mutableMapOf<Direction, VoxelShape>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val components = arrayOf(
|
val components = arrayOf(
|
||||||
doubleArrayOf(4.0, 0.0, 4.0, 12.0, 2.0, 12.0),
|
doubleArrayOf(4.0, 0.0, 4.0, 12.0, 2.0, 12.0),
|
||||||
doubleArrayOf(2.0, 2.0, 2.0, 14.0, 4.0, 14.0),
|
doubleArrayOf(2.0, 2.0, 2.0, 14.0, 4.0, 14.0),
|
||||||
doubleArrayOf(0.0, 4.0, 0.0, 16.0, 6.0, 16.0),
|
doubleArrayOf(0.0, 4.0, 0.0, 16.0, 6.0, 16.0),
|
||||||
)
|
)
|
||||||
val directions = arrayOf(
|
val directions = arrayOf(
|
||||||
Triple(Direction.DOWN, null, false),
|
Triple(Direction.DOWN, null, false),
|
||||||
Triple(Direction.UP, null, true),
|
Triple(Direction.UP, null, true),
|
||||||
Triple(Direction.NORTH, 2, false),
|
Triple(Direction.NORTH, 2, false),
|
||||||
Triple(Direction.SOUTH, 2, true),
|
Triple(Direction.SOUTH, 2, true),
|
||||||
Triple(Direction.WEST, 1, false),
|
Triple(Direction.WEST, 1, false),
|
||||||
Triple(Direction.EAST, 1, true),
|
Triple(Direction.EAST, 1, true),
|
||||||
)
|
)
|
||||||
for ((dir, rotate, flip) in directions) {
|
for ((dir, rotate, flip) in directions) {
|
||||||
val shapes = components.map { it ->
|
val shapes = components.map { it ->
|
||||||
val arr = it.copyOf()
|
val arr = it.copyOf()
|
||||||
if (rotate != null) {
|
if (rotate != null) {
|
||||||
for (i in 0 until 3) {
|
for (i in 0 until 3) {
|
||||||
arr[i] = it[(i + rotate) % 3]
|
arr[i] = it[(i + rotate) % 3]
|
||||||
arr[3 + i] = it[3 + ((i + rotate) % 3)]
|
arr[3 + i] = it[3 + ((i + rotate) % 3)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flip) {
|
if (flip) {
|
||||||
for (i in arr.indices) {
|
for (i in arr.indices) {
|
||||||
arr[i] = 16.0 - arr[i]
|
arr[i] = 16.0 - arr[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
createCuboidShape(min(arr[0], arr[3]), min(arr[1], arr[4]), min(arr[2], arr[5]), max(arr[0], arr[3]), max(arr[1], arr[4]), max(arr[2], arr[5]))
|
createCuboidShape(
|
||||||
}
|
min(arr[0], arr[3]),
|
||||||
INSERTER_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) }
|
min(arr[1], arr[4]),
|
||||||
}
|
min(arr[2], arr[5]),
|
||||||
}
|
max(arr[0], arr[3]),
|
||||||
}
|
max(arr[1], arr[4]),
|
||||||
|
max(arr[2], arr[5])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
INSERTER_SHAPES[dir] = shapes.reduce { a, b -> VoxelShapes.union(a, b) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override val faceThickness = 6.0
|
override val faceThickness = 6.0
|
||||||
override val faceShapes: Map<Direction, VoxelShape> = INSERTER_SHAPES
|
override val faceShapes: Map<Direction, VoxelShape> = INSERTER_SHAPES
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = InserterBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = InserterBlockEntity(pos, state)
|
||||||
|
|
||||||
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, stack: ItemStack) {
|
override fun onUse(
|
||||||
if (!world.isClient) {
|
state: BlockState,
|
||||||
getBlockEntity(world, pos)!!.updateInventory()
|
world: World,
|
||||||
}
|
pos: BlockPos,
|
||||||
}
|
player: PlayerEntity,
|
||||||
|
hand: Hand,
|
||||||
|
hitResult: BlockHitResult
|
||||||
|
): ActionResult {
|
||||||
|
if (!world.isClient) {
|
||||||
|
val be = getBlockEntity(world, pos)!!
|
||||||
|
|
||||||
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) {
|
be.markUpdate()
|
||||||
if (!world.isClient) {
|
|
||||||
getBlockEntity(world, pos)!!.updateInventory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
|
val factory = object : ExtendedScreenHandlerFactory {
|
||||||
if (!world.isClient) {
|
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
|
||||||
val be = getBlockEntity(world, pos)!!
|
return InserterScreenHandler(syncId, playerInv, be)
|
||||||
|
}
|
||||||
|
|
||||||
be.markUpdate()
|
override fun getDisplayName() = this@InserterBlock.name
|
||||||
|
|
||||||
val factory = object: ExtendedScreenHandlerFactory {
|
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
|
||||||
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
|
buf.writeBlockPos(be.pos)
|
||||||
return InserterScreenHandler(syncId, playerInv, be)
|
}
|
||||||
}
|
}
|
||||||
|
player.openHandledScreen(factory)
|
||||||
override fun getDisplayName() = this@InserterBlock.name
|
}
|
||||||
|
return ActionResult.SUCCESS
|
||||||
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
|
}
|
||||||
buf.writeBlockPos(be.pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
player.openHandledScreen(factory)
|
|
||||||
}
|
|
||||||
return ActionResult.SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package net.shadowfacts.phycon.block.inserter
|
package net.shadowfacts.phycon.block.inserter
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.SearchOptions
|
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
|
||||||
import alexiil.mc.lib.attributes.Simulation
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
|
||||||
import alexiil.mc.lib.attributes.item.ItemAttributes
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
import alexiil.mc.lib.attributes.item.ItemInsertable
|
import net.fabricmc.fabric.api.transfer.v1.storage.Storage
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.nbt.NbtCompound
|
import net.minecraft.nbt.NbtCompound
|
||||||
|
import net.minecraft.server.world.ServerWorld
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.Direction
|
import net.minecraft.util.math.Direction
|
||||||
import net.shadowfacts.phycon.api.packet.Packet
|
import net.shadowfacts.phycon.api.packet.Packet
|
||||||
|
@ -19,153 +20,161 @@ import net.shadowfacts.phycon.component.NetworkStackProvider
|
||||||
import net.shadowfacts.phycon.component.handleItemStack
|
import net.shadowfacts.phycon.component.handleItemStack
|
||||||
import net.shadowfacts.phycon.init.PhyBlockEntities
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
import net.shadowfacts.phycon.packet.*
|
import net.shadowfacts.phycon.packet.*
|
||||||
import net.shadowfacts.phycon.util.ActivationMode
|
import net.shadowfacts.phycon.util.*
|
||||||
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
|
||||||
import net.shadowfacts.phycon.util.GhostInv
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class InserterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.INSERTER, pos, state),
|
class InserterBlockEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity(PhyBlockEntities.INSERTER, pos, state),
|
||||||
ItemStackPacketHandler,
|
ItemStackPacketHandler,
|
||||||
ActivationController.ActivatableDevice,
|
ActivationController.ActivatableDevice,
|
||||||
ClientConfigurableDevice,
|
ClientConfigurableDevice,
|
||||||
GhostInv {
|
GhostInv {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SLEEP_TIME = 40L
|
val SLEEP_TIME = 40L
|
||||||
val REQUEST_TIMEOUT = 40
|
val REQUEST_TIMEOUT = 40
|
||||||
}
|
}
|
||||||
|
|
||||||
private val facing: Direction
|
private val facing: Direction
|
||||||
get() = cachedState[FaceDeviceBlock.FACING]
|
get() = cachedState[FaceDeviceBlock.FACING]
|
||||||
|
|
||||||
private var inventory: ItemInsertable? = null
|
private var inventory: Pair<BlockState, BlockApiCache<Storage<ItemVariant>, Direction>>? = null
|
||||||
private var currentRequest: PendingExtractRequest? = null
|
private var currentRequest: PendingExtractRequest? = null
|
||||||
var stackToExtract: ItemStack = ItemStack.EMPTY
|
var stackToExtract: ItemStack = ItemStack.EMPTY
|
||||||
override var ghostSlotStack: ItemStack
|
override var ghostSlotStack: ItemStack
|
||||||
get() = stackToExtract
|
get() = stackToExtract
|
||||||
set(value) { stackToExtract = value }
|
set(value) {
|
||||||
var amountToExtract = 1
|
stackToExtract = value
|
||||||
override val controller = ActivationController(SLEEP_TIME, this)
|
}
|
||||||
|
var amountToExtract = 1
|
||||||
|
override val controller = ActivationController(SLEEP_TIME, this)
|
||||||
|
|
||||||
fun updateInventory() {
|
private fun getInventory(): Storage<ItemVariant>? {
|
||||||
val offsetPos = pos.offset(facing)
|
if (inventory == null) {
|
||||||
val option = SearchOptions.inDirection(facing)
|
val offsetPos = pos.offset(facing)
|
||||||
inventory = ItemAttributes.INSERTABLE.getFirstOrNull(world, offsetPos, option)
|
val cachedFacedBlock = world!!.getBlockState(offsetPos)
|
||||||
}
|
val inventory = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, offsetPos)
|
||||||
|
this.inventory = Pair(cachedFacedBlock, inventory)
|
||||||
|
}
|
||||||
|
return inventory!!.second.find(inventory!!.first, facing.opposite)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getInventory(): ItemInsertable? {
|
override fun handle(packet: Packet) {
|
||||||
if (inventory == null) updateInventory()
|
when (packet) {
|
||||||
return inventory
|
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
|
||||||
}
|
is StackLocationPacket -> handleStackLocation(packet)
|
||||||
|
is ItemStackPacket -> handleItemStack(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
when (packet) {
|
val inventory = getInventory()
|
||||||
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
|
if (inventory != null) {
|
||||||
is StackLocationPacket -> handleStackLocation(packet)
|
val transaction = Transaction.openOuter()
|
||||||
is ItemStackPacket -> handleItemStack(packet)
|
val inserted = inventory.insert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction).toInt()
|
||||||
}
|
transaction.commit()
|
||||||
}
|
return if (inserted == 0) {
|
||||||
|
packet.stack
|
||||||
|
} else if (inserted == packet.stack.count) {
|
||||||
|
ItemStack.EMPTY
|
||||||
|
} else {
|
||||||
|
packet.stack.copyWithCount(packet.stack.count - inserted)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// no inventory, entire stack remains
|
||||||
|
return packet.stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
private fun handleStackLocation(packet: StackLocationPacket) {
|
||||||
val inventory = getInventory()
|
val request = currentRequest
|
||||||
return if (inventory != null) {
|
if (request != null && request.stack.equalsIgnoringAmount(packet.stack)) {
|
||||||
inventory.attemptInsertion(packet.stack, Simulation.ACTION)
|
request.results.add(packet.amount to packet.stackProvider)
|
||||||
} else {
|
if (request.isFinishable(counter)) {
|
||||||
// no inventory, entire stack remains
|
finishRequest()
|
||||||
packet.stack
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStackLocation(packet: StackLocationPacket) {
|
override fun tick() {
|
||||||
val request = currentRequest
|
super.tick()
|
||||||
if (request != null && ItemStackUtil.areEqualIgnoreAmounts(request.stack, packet.stack)) {
|
|
||||||
request.results.add(packet.amount to packet.stackProvider)
|
|
||||||
if (request.isFinishable(counter)) {
|
|
||||||
finishRequest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tick() {
|
if (!world!!.isClient) {
|
||||||
super.tick()
|
controller.tick()
|
||||||
|
|
||||||
if (!world!!.isClient) {
|
val request = currentRequest
|
||||||
controller.tick()
|
if (request != null) {
|
||||||
|
if (request.isFinishable(counter)) {
|
||||||
|
finishRequest()
|
||||||
|
} else if (counter - request.timestamp >= REQUEST_TIMEOUT && request.totalAmount == 0) {
|
||||||
|
currentRequest = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val request = currentRequest
|
override fun activate(): Boolean {
|
||||||
if (request != null) {
|
if (currentRequest != null || stackToExtract.isEmpty) {
|
||||||
if (request.isFinishable(counter)) {
|
return false
|
||||||
finishRequest()
|
}
|
||||||
} else if (counter - request.timestamp >= REQUEST_TIMEOUT && request.totalAmount == 0) {
|
|
||||||
currentRequest = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun activate(): Boolean {
|
// todo: configure me
|
||||||
if (currentRequest != null || stackToExtract.isEmpty) {
|
currentRequest = PendingExtractRequest(stackToExtract, counter)
|
||||||
return false
|
sendPacket(LocateStackPacket(stackToExtract, ipAddress))
|
||||||
}
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// todo: configure me
|
private fun finishRequest() {
|
||||||
currentRequest = PendingExtractRequest(stackToExtract, counter)
|
val request = currentRequest ?: return
|
||||||
sendPacket(LocateStackPacket(stackToExtract, ipAddress))
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun finishRequest() {
|
// todo: dedup with TerminalBlockEntity.stackLocateRequestCompleted
|
||||||
val request = currentRequest ?: return
|
val actualAmount = min(min(request.stack.maxCount, request.totalAmount), amountToExtract)
|
||||||
|
val sortedResults = request.results.sortedByDescending { it.first }.toMutableList()
|
||||||
|
var amountRequested = 0
|
||||||
|
while (amountRequested < actualAmount && sortedResults.isNotEmpty()) {
|
||||||
|
val (sourceAmount, source) = sortedResults.removeAt(0)
|
||||||
|
val amountToRequest = min(sourceAmount, actualAmount - amountRequested)
|
||||||
|
amountRequested += amountToRequest
|
||||||
|
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, source.ipAddress))
|
||||||
|
}
|
||||||
|
|
||||||
// todo: dedup with TerminalBlockEntity.stackLocateRequestCompleted
|
currentRequest = null
|
||||||
val actualAmount = min(min(request.stack.maxCount, request.totalAmount), amountToExtract)
|
}
|
||||||
val sortedResults = request.results.sortedByDescending { it.first }.toMutableList()
|
|
||||||
var amountRequested = 0
|
|
||||||
while (amountRequested < actualAmount && sortedResults.isNotEmpty()) {
|
|
||||||
val (sourceAmount, source) = sortedResults.removeAt(0)
|
|
||||||
val amountToRequest = min(sourceAmount, actualAmount - amountRequested)
|
|
||||||
amountRequested += amountToRequest
|
|
||||||
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, source.ipAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
currentRequest = null
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
}
|
super.toCommonTag(tag)
|
||||||
|
writeDeviceConfiguration(tag)
|
||||||
|
tag.put("StackToExtract", stackToExtract.writeNbt(NbtCompound()))
|
||||||
|
}
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
super.toCommonTag(tag)
|
super.fromCommonTag(tag)
|
||||||
writeDeviceConfiguration(tag)
|
loadDeviceConfiguration(tag)
|
||||||
tag.put("StackToExtract", stackToExtract.writeNbt(NbtCompound()))
|
stackToExtract = ItemStack.fromNbt(tag.getCompound("StackToExtract"))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
tag.putString("ActivationMode", controller.activationMode.name)
|
||||||
loadDeviceConfiguration(tag)
|
tag.putInt("AmountToExtract", amountToExtract)
|
||||||
stackToExtract = ItemStack.fromNbt(tag.getCompound("StackToExtract"))
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
||||||
tag.putString("ActivationMode", controller.activationMode.name)
|
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
|
||||||
tag.putInt("AmountToExtract", amountToExtract)
|
amountToExtract = tag.getInt("AmountToExtract")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
class PendingExtractRequest(
|
||||||
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
|
val stack: ItemStack,
|
||||||
amountToExtract = tag.getInt("AmountToExtract")
|
val timestamp: Long,
|
||||||
}
|
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
|
||||||
|
) {
|
||||||
|
val totalAmount: Int
|
||||||
|
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
|
||||||
|
|
||||||
class PendingExtractRequest(
|
fun isFinishable(currentTimestamp: Long): Boolean {
|
||||||
val stack: ItemStack,
|
return totalAmount >= stack.maxCount || (currentTimestamp - timestamp >= REQUEST_TIMEOUT && totalAmount > 0)
|
||||||
val timestamp: Long,
|
}
|
||||||
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
|
}
|
||||||
) {
|
|
||||||
val totalAmount: Int
|
|
||||||
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
|
|
||||||
|
|
||||||
fun isFinishable(currentTimestamp: Long): Boolean {
|
|
||||||
return totalAmount >= stack.maxCount || (currentTimestamp - timestamp >= REQUEST_TIMEOUT && totalAmount > 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,107 +19,107 @@ import java.lang.NumberFormatException
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class InserterScreen(
|
class InserterScreen(
|
||||||
handler: InserterScreenHandler,
|
handler: InserterScreenHandler,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
title: Text,
|
title: Text,
|
||||||
): HandledScreen<InserterScreenHandler>(
|
) : HandledScreen<InserterScreenHandler>(
|
||||||
handler,
|
handler,
|
||||||
playerInv,
|
playerInv,
|
||||||
title
|
title
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/inserter.png")
|
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/inserter.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var amountField: TextFieldWidget
|
private lateinit var amountField: TextFieldWidget
|
||||||
|
|
||||||
init {
|
init {
|
||||||
backgroundWidth = 176
|
backgroundWidth = 176
|
||||||
backgroundHeight = 133
|
backgroundHeight = 133
|
||||||
playerInventoryTitleY = backgroundHeight - 94
|
playerInventoryTitleY = backgroundHeight - 94
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
amountField = TextFieldWidget(textRenderer, x + 57, y + 24, 80, 9, LiteralText("Amount"))
|
amountField = TextFieldWidget(textRenderer, x + 57, y + 24, 80, 9, LiteralText("Amount"))
|
||||||
amountField.text = handler.inserter.amountToExtract.toString()
|
amountField.text = handler.inserter.amountToExtract.toString()
|
||||||
amountField.setDrawsBackground(false)
|
amountField.setDrawsBackground(false)
|
||||||
amountField.isVisible = true
|
amountField.isVisible = true
|
||||||
amountField.setTextFieldFocused(true)
|
amountField.setTextFieldFocused(true)
|
||||||
amountField.setEditableColor(0xffffff)
|
amountField.setEditableColor(0xffffff)
|
||||||
amountField.setTextPredicate {
|
amountField.setTextPredicate {
|
||||||
if (it.isEmpty()) {
|
if (it.isEmpty()) {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
val value = Integer.parseInt(it)
|
val value = Integer.parseInt(it)
|
||||||
value in 1..64
|
value in 1..64
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addDrawableChild(amountField)
|
addDrawableChild(amountField)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun amountUpdated() {
|
fun amountUpdated() {
|
||||||
if (amountField.text.isNotEmpty()) {
|
if (amountField.text.isNotEmpty()) {
|
||||||
handler.inserter.amountToExtract = Integer.parseInt(amountField.text)
|
handler.inserter.amountToExtract = Integer.parseInt(amountField.text)
|
||||||
client!!.player!!.networkHandler.sendPacket(C2SConfigureDevice(handler.inserter))
|
client!!.player!!.networkHandler.sendPacket(C2SConfigureDevice(handler.inserter))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handledScreenTick() {
|
override fun handledScreenTick() {
|
||||||
super.handledScreenTick()
|
super.handledScreenTick()
|
||||||
amountField.tick()
|
amountField.tick()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||||
renderBackground(matrixStack)
|
renderBackground(matrixStack)
|
||||||
|
|
||||||
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
||||||
RenderSystem.setShaderTexture(0, BACKGROUND)
|
RenderSystem.setShaderTexture(0, BACKGROUND)
|
||||||
val x = (width - backgroundWidth) / 2
|
val x = (width - backgroundWidth) / 2
|
||||||
val y = (height - backgroundHeight) / 2
|
val y = (height - backgroundHeight) / 2
|
||||||
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) {
|
||||||
super.render(matrixStack, mouseX, mouseY, delta)
|
super.render(matrixStack, mouseX, mouseY, delta)
|
||||||
|
|
||||||
amountField.render(matrixStack, mouseX, mouseY, delta)
|
amountField.render(matrixStack, mouseX, mouseY, delta)
|
||||||
|
|
||||||
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
|
drawMouseoverTooltip(matrixStack, mouseX, mouseY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, slotActionType: SlotActionType?) {
|
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, slotActionType: SlotActionType?) {
|
||||||
super.onMouseClick(slot, invSlot, clickData, slotActionType)
|
super.onMouseClick(slot, invSlot, clickData, slotActionType)
|
||||||
|
|
||||||
amountField.setTextFieldFocused(true)
|
amountField.setTextFieldFocused(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun charTyped(c: Char, i: Int): Boolean {
|
override fun charTyped(c: Char, i: Int): Boolean {
|
||||||
val oldText = amountField.text
|
val oldText = amountField.text
|
||||||
if (amountField.charTyped(c, i)) {
|
if (amountField.charTyped(c, i)) {
|
||||||
if (oldText != amountField.text) {
|
if (oldText != amountField.text) {
|
||||||
amountUpdated()
|
amountUpdated()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.charTyped(c, i)
|
return super.charTyped(c, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun keyPressed(i: Int, j: Int, k: Int): Boolean {
|
override fun keyPressed(i: Int, j: Int, k: Int): Boolean {
|
||||||
val oldText = amountField.text
|
val oldText = amountField.text
|
||||||
if (amountField.keyPressed(i, j, k)) {
|
if (amountField.keyPressed(i, j, k)) {
|
||||||
if (oldText != amountField.text) {
|
if (oldText != amountField.text) {
|
||||||
amountUpdated()
|
amountUpdated()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.keyPressed(i, j, k)
|
return super.keyPressed(i, j, k)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,65 +19,65 @@ import kotlin.math.min
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class InserterScreenHandler(
|
class InserterScreenHandler(
|
||||||
syncId: Int,
|
syncId: Int,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
val inserter: InserterBlockEntity,
|
val inserter: InserterBlockEntity,
|
||||||
): ScreenHandler(PhyScreens.INSERTER, syncId) {
|
) : ScreenHandler(PhyScreens.INSERTER, syncId) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "inserter")
|
val ID = Identifier(PhysicalConnectivity.MODID, "inserter")
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
|
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
|
||||||
this(
|
this(
|
||||||
syncId,
|
syncId,
|
||||||
playerInv,
|
playerInv,
|
||||||
PhyBlocks.INSERTER.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
PhyBlocks.INSERTER.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// fake slot
|
// fake slot
|
||||||
addSlot(GhostSlot(inserter, 31, 20))
|
addSlot(GhostSlot(inserter, 31, 20))
|
||||||
|
|
||||||
// player inv
|
// player inv
|
||||||
for (y in 0 until 3) {
|
for (y in 0 until 3) {
|
||||||
for (x in 0 until 9) {
|
for (x in 0 until 9) {
|
||||||
addSlot(Slot(playerInv, x + y * 9 + 9, 8 + x * 18, 51 + y * 18))
|
addSlot(Slot(playerInv, x + y * 9 + 9, 8 + x * 18, 51 + y * 18))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hotbar
|
// hotbar
|
||||||
for (x in 0 until 9) {
|
for (x in 0 until 9) {
|
||||||
addSlot(Slot(playerInv, x, 8 + x * 18, 109))
|
addSlot(Slot(playerInv, x, 8 + x * 18, 109))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stackToExtractChanged() {
|
private fun stackToExtractChanged() {
|
||||||
inserter.amountToExtract = min(inserter.stackToExtract.maxCount, inserter.amountToExtract)
|
inserter.amountToExtract = min(inserter.stackToExtract.maxCount, inserter.amountToExtract)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canUse(player: PlayerEntity): Boolean {
|
override fun canUse(player: PlayerEntity): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity) {
|
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity) {
|
||||||
// fake slot
|
// fake slot
|
||||||
if (slotId == 0) {
|
if (slotId == 0) {
|
||||||
if (cursorStack.isEmpty) {
|
if (cursorStack.isEmpty) {
|
||||||
inserter.stackToExtract = ItemStack.EMPTY
|
inserter.stackToExtract = ItemStack.EMPTY
|
||||||
} else {
|
} else {
|
||||||
inserter.stackToExtract = cursorStack.copyWithCount(1)
|
inserter.stackToExtract = cursorStack.copyWithCount(1)
|
||||||
}
|
}
|
||||||
stackToExtractChanged()
|
stackToExtractChanged()
|
||||||
}
|
}
|
||||||
super.onSlotClick(slotId, clickData, actionType, player)
|
super.onSlotClick(slotId, clickData, actionType, player)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
||||||
val slot = slots[slotId]
|
val slot = slots[slotId]
|
||||||
inserter.stackToExtract = slot.stack.copyWithCount(1)
|
inserter.stackToExtract = slot.stack.copyWithCount(1)
|
||||||
stackToExtractChanged()
|
stackToExtractChanged()
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,51 +23,63 @@ import java.util.*
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class MinerBlock: DeviceBlock<MinerBlockEntity>(
|
class MinerBlock : DeviceBlock<MinerBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "miner")
|
val ID = Identifier(PhysicalConnectivity.MODID, "miner")
|
||||||
val FACING = Properties.FACING
|
val FACING = Properties.FACING
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||||
return EnumSet.of(state[FACING].opposite)
|
return EnumSet.of(state[FACING].opposite)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
|
override fun getNetworkInterfaceForSide(
|
||||||
return if (side == state[FACING]) {
|
side: Direction,
|
||||||
null
|
state: BlockState,
|
||||||
} else {
|
world: WorldAccess,
|
||||||
getBlockEntity(world, pos)
|
pos: BlockPos
|
||||||
}
|
): Interface? {
|
||||||
}
|
return if (side == state[FACING]) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
getBlockEntity(world, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||||
super.appendProperties(builder)
|
super.appendProperties(builder)
|
||||||
builder.add(FACING)
|
builder.add(FACING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = MinerBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = MinerBlockEntity(pos, state)
|
||||||
|
|
||||||
override fun getPlacementState(context: ItemPlacementContext): BlockState? {
|
override fun getPlacementState(context: ItemPlacementContext): BlockState? {
|
||||||
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
|
val facing = if (context.player?.isSneaking == true) context.side.opposite else context.playerFacing.opposite
|
||||||
return defaultState.with(FACING, facing)
|
return defaultState.with(FACING, facing)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, itemStack: ItemStack) {
|
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, entity: LivingEntity?, itemStack: ItemStack) {
|
||||||
if (!world.isClient) {
|
if (!world.isClient) {
|
||||||
// getBlockEntity(world, pos)!!.updateBlockToMine()
|
// getBlockEntity(world, pos)!!.updateBlockToMine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighbor: Block, neighborPos: BlockPos, bl: Boolean) {
|
override fun neighborUpdate(
|
||||||
if (!world.isClient) {
|
state: BlockState,
|
||||||
|
world: World,
|
||||||
|
pos: BlockPos,
|
||||||
|
neighbor: Block,
|
||||||
|
neighborPos: BlockPos,
|
||||||
|
bl: Boolean
|
||||||
|
) {
|
||||||
|
if (!world.isClient) {
|
||||||
// getBlockEntity(world, pos)!!.updateBlockToMine()
|
// getBlockEntity(world, pos)!!.updateBlockToMine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package net.shadowfacts.phycon.block.miner
|
package net.shadowfacts.phycon.block.miner
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.item.GroupedItemInvView
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView
|
||||||
import alexiil.mc.lib.attributes.item.filter.ItemFilter
|
import net.fabricmc.fabric.api.transfer.v1.storage.base.ExtractionOnlyStorage
|
||||||
|
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext
|
||||||
|
import net.fabricmc.fabric.api.transfer.v1.transaction.base.SnapshotParticipant
|
||||||
import net.minecraft.block.Block
|
import net.minecraft.block.Block
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
|
@ -22,219 +24,262 @@ import net.shadowfacts.phycon.packet.*
|
||||||
import net.shadowfacts.phycon.util.ActivationMode
|
import net.shadowfacts.phycon.util.ActivationMode
|
||||||
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
||||||
import net.shadowfacts.phycon.util.copyWithCount
|
import net.shadowfacts.phycon.util.copyWithCount
|
||||||
|
import net.shadowfacts.phycon.util.equalsIgnoringAmount
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class MinerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.MINER, pos, state),
|
class MinerBlockEntity(pos: BlockPos, state: BlockState) : DeviceBlockEntity(PhyBlockEntities.MINER, pos, state),
|
||||||
NetworkStackProvider,
|
NetworkStackProvider,
|
||||||
NetworkStackDispatcher<MinerBlockEntity.PendingInsertion>,
|
NetworkStackDispatcher<MinerBlockEntity.PendingInsertion>,
|
||||||
ActivationController.ActivatableDevice,
|
ActivationController.ActivatableDevice,
|
||||||
ClientConfigurableDevice {
|
ClientConfigurableDevice {
|
||||||
|
|
||||||
private val facing: Direction
|
private val facing: Direction
|
||||||
get() = cachedState[MinerBlock.FACING]
|
get() = cachedState[MinerBlock.FACING]
|
||||||
|
|
||||||
private val invProxy = MinerInvProxy(this)
|
private val invProxy = MinerStorageProxy(this)
|
||||||
|
|
||||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||||
override val dispatchStackTimeout = AbstractTerminalBlockEntity.INSERTION_TIMEOUT
|
override val dispatchStackTimeout = AbstractTerminalBlockEntity.INSERTION_TIMEOUT
|
||||||
override val controller = ActivationController(40L, this)
|
override val controller = ActivationController(40L, this)
|
||||||
override var providerPriority = 0
|
override var providerPriority = 0
|
||||||
|
|
||||||
var minerMode = MinerMode.ON_DEMAND
|
var minerMode = MinerMode.ON_DEMAND
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
override fun handle(packet: Packet) {
|
||||||
when (packet) {
|
when (packet) {
|
||||||
is RequestInventoryPacket -> handleRequestInventory(packet)
|
is RequestInventoryPacket -> handleRequestInventory(packet)
|
||||||
is LocateStackPacket -> handleLocateStack(packet)
|
is LocateStackPacket -> handleLocateStack(packet)
|
||||||
is ExtractStackPacket -> handleExtractStack(packet)
|
is ExtractStackPacket -> handleExtractStack(packet)
|
||||||
is CapacityPacket -> handleCapacity(packet)
|
is CapacityPacket -> handleCapacity(packet)
|
||||||
is ItemStackPacket -> handleItemStack(packet)
|
is ItemStackPacket -> handleItemStack(packet)
|
||||||
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
|
is RemoteActivationPacket -> controller.handleRemoteActivation(packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleRequestInventory(packet: RequestInventoryPacket) {
|
private fun handleRequestInventory(packet: RequestInventoryPacket) {
|
||||||
if (minerMode != MinerMode.ON_DEMAND || packet.kind != RequestInventoryPacket.Kind.GROUPED) {
|
if (minerMode != MinerMode.ON_DEMAND || packet.kind != RequestInventoryPacket.Kind.GROUPED) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sendPacket(ReadGroupedInventoryPacket(invProxy, ipAddress, packet.source))
|
sendPacket(ReadItemStoragePacket(invProxy, ipAddress, packet.source))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLocateStack(packet: LocateStackPacket) {
|
private fun handleLocateStack(packet: LocateStackPacket) {
|
||||||
if (minerMode != MinerMode.ON_DEMAND) {
|
if (minerMode != MinerMode.ON_DEMAND) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val amount = invProxy.getAmount(packet.stack)
|
val amount = invProxy.simulateExtract(ItemVariant.of(packet.stack), Long.MAX_VALUE, null)
|
||||||
if (amount > 0) {
|
if (amount > 0) {
|
||||||
sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source))
|
sendPacket(StackLocationPacket(packet.stack, amount.toInt(), this, ipAddress, packet.source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleExtractStack(packet: ExtractStackPacket) {
|
private fun handleExtractStack(packet: ExtractStackPacket) {
|
||||||
if (minerMode != MinerMode.ON_DEMAND) {
|
if (minerMode != MinerMode.ON_DEMAND) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// always recalculate immediately before breaking
|
// always recalculate immediately before breaking
|
||||||
val drops = invProxy.getDrops(recalculate = true)
|
val drops = invProxy.getDrops(recalculate = true)
|
||||||
if (invProxy.getAmount(packet.stack) > 0) {
|
val amount = invProxy.simulateExtract(ItemVariant.of(packet.stack), packet.amount.toLong(), null)
|
||||||
world!!.breakBlock(pos.offset(facing), false)
|
if (amount > 0) {
|
||||||
|
world!!.breakBlock(pos.offset(facing), false)
|
||||||
|
|
||||||
// send the requested amount back to the requester
|
// send the requested amount back to the requester
|
||||||
var remaining = packet.amount
|
var remaining = packet.amount
|
||||||
for (droppedStack in drops) {
|
for (droppedStack in drops) {
|
||||||
if (remaining <= 0) {
|
if (remaining <= 0) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (!ItemStackUtil.areEqualIgnoreAmounts(droppedStack, packet.stack)) {
|
if (!droppedStack.equalsIgnoringAmount(packet.stack)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val toDecr = min(droppedStack.count, remaining)
|
val toDecr = min(droppedStack.count, remaining)
|
||||||
val copy = droppedStack.copyWithCount(toDecr)
|
val copy = droppedStack.copyWithCount(toDecr)
|
||||||
droppedStack.decrement(toDecr)
|
droppedStack.decrement(toDecr)
|
||||||
remaining -= toDecr
|
remaining -= toDecr
|
||||||
|
|
||||||
// todo: should this try to combine stacks and send as few packets as possible?
|
// todo: should this try to combine stacks and send as few packets as possible?
|
||||||
sendPacket(ItemStackPacket(copy, ipAddress, packet.source))
|
sendPacket(ItemStackPacket(copy, ipAddress, packet.source))
|
||||||
}
|
}
|
||||||
|
|
||||||
// dump any remaining drops into the network
|
// dump any remaining drops into the network
|
||||||
for (droppedStack in drops) {
|
for (droppedStack in drops) {
|
||||||
if (droppedStack.isEmpty) continue
|
if (droppedStack.isEmpty) continue
|
||||||
dispatchItemStack(droppedStack)
|
dispatchItemStack(droppedStack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
// miner can't receive stacks, so remaining is the entire packet stack
|
// miner can't receive stacks, so remaining is the entire packet stack
|
||||||
return packet.stack
|
return packet.stack
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick() {
|
||||||
super.tick()
|
super.tick()
|
||||||
|
|
||||||
if (!world!!.isClient) {
|
if (!world!!.isClient) {
|
||||||
if (minerMode == MinerMode.AUTOMATIC) {
|
if (minerMode == MinerMode.AUTOMATIC) {
|
||||||
controller.tick()
|
controller.tick()
|
||||||
}
|
}
|
||||||
finishTimedOutPendingInsertions()
|
finishTimedOutPendingInsertions()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun activate(): Boolean {
|
override fun activate(): Boolean {
|
||||||
if (minerMode == MinerMode.ON_DEMAND) {
|
if (minerMode == MinerMode.ON_DEMAND) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
val drops = invProxy.getDrops(recalculate = true)
|
val drops = invProxy.getDrops(recalculate = true)
|
||||||
if (!world!!.getBlockState(pos.offset(facing)).isAir) {
|
if (!world!!.getBlockState(pos.offset(facing)).isAir) {
|
||||||
world!!.breakBlock(pos.offset(facing), false)
|
world!!.breakBlock(pos.offset(facing), false)
|
||||||
|
|
||||||
for (stack in drops) {
|
for (stack in drops) {
|
||||||
if (stack.isEmpty) continue
|
if (stack.isEmpty) continue
|
||||||
dispatchItemStack(stack)
|
dispatchItemStack(stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canConfigureActivationController(): Boolean {
|
override fun canConfigureActivationController(): Boolean {
|
||||||
return minerMode == MinerMode.AUTOMATIC
|
return minerMode == MinerMode.AUTOMATIC
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canConfigureProviderPriority(): Boolean {
|
override fun canConfigureProviderPriority(): Boolean {
|
||||||
return minerMode == MinerMode.ON_DEMAND
|
return minerMode == MinerMode.ON_DEMAND
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
super.toCommonTag(tag)
|
super.toCommonTag(tag)
|
||||||
writeDeviceConfiguration(tag)
|
writeDeviceConfiguration(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
super.fromCommonTag(tag)
|
||||||
loadDeviceConfiguration(tag)
|
loadDeviceConfiguration(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
||||||
tag.putString("MinerMode", minerMode.name)
|
tag.putString("MinerMode", minerMode.name)
|
||||||
tag.putString("ActivationMode", controller.activationMode.name)
|
tag.putString("ActivationMode", controller.activationMode.name)
|
||||||
tag.putInt("ProviderPriority", providerPriority)
|
tag.putInt("ProviderPriority", providerPriority)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
||||||
minerMode = MinerMode.valueOf(tag.getString("MinerMode"))
|
minerMode = MinerMode.valueOf(tag.getString("MinerMode"))
|
||||||
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
|
controller.activationMode = ActivationMode.valueOf(tag.getString("ActivationMode"))
|
||||||
providerPriority = tag.getInt("ProviderPriority")
|
providerPriority = tag.getInt("ProviderPriority")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MinerMode {
|
enum class MinerMode {
|
||||||
ON_DEMAND, AUTOMATIC;
|
ON_DEMAND, AUTOMATIC;
|
||||||
|
|
||||||
val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.lowercase()}")
|
val friendlyName = TranslatableText("gui.phycon.miner_mode.${name.lowercase()}")
|
||||||
}
|
}
|
||||||
|
|
||||||
class MinerInvProxy(val miner: MinerBlockEntity): GroupedItemInvView {
|
class MinerStorageProxy(val miner: MinerBlockEntity) :
|
||||||
companion object {
|
SnapshotParticipant<Pair<BlockState, List<ItemStack>>?>(),
|
||||||
val TOOL = ItemStack(Items.DIAMOND_PICKAXE)
|
ExtractionOnlyStorage<ItemVariant>
|
||||||
}
|
{
|
||||||
|
companion object {
|
||||||
|
val TOOL = ItemStack(Items.DIAMOND_PICKAXE)
|
||||||
|
}
|
||||||
|
|
||||||
private var cachedState: BlockState? = null
|
private var cache: Pair<BlockState, List<ItemStack>>? = null
|
||||||
private var cachedDrops: List<ItemStack>? = null
|
|
||||||
|
|
||||||
private val world: World
|
private val world: World
|
||||||
get() = miner.world!!
|
get() = miner.world!!
|
||||||
private val pos: BlockPos
|
private val pos: BlockPos
|
||||||
get() = miner.pos!!
|
get() = miner.pos!!
|
||||||
private val facing: Direction
|
private val facing: Direction
|
||||||
get() = miner.facing
|
get() = miner.facing
|
||||||
|
|
||||||
fun getDrops(recalculate: Boolean = false): List<ItemStack> {
|
fun getDrops(recalculate: Boolean = false): List<ItemStack> {
|
||||||
val targetPos = pos.offset(facing)
|
val targetPos = pos.offset(facing)
|
||||||
val realState = world.getBlockState(targetPos)
|
val realState = world.getBlockState(targetPos)
|
||||||
// todo: does BlockState.equals actually work or is reference equality fine for BlockStates?
|
val cache = this.cache
|
||||||
if (cachedDrops == null || realState != cachedState || recalculate) {
|
// todo: does BlockState.equals actually work or is reference equality fine for BlockStates?
|
||||||
cachedState = realState
|
if (cache == null || realState != cache.first || recalculate) {
|
||||||
|
val be = if (realState.hasBlockEntity()) world.getBlockEntity(targetPos) else null
|
||||||
|
val drops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be, null, TOOL)
|
||||||
|
this.cache = Pair(realState, drops)
|
||||||
|
return drops
|
||||||
|
}
|
||||||
|
return cache.second
|
||||||
|
}
|
||||||
|
|
||||||
val be = if (realState.hasBlockEntity()) world.getBlockEntity(targetPos) else null
|
override fun createSnapshot(): Pair<BlockState, List<ItemStack>>? {
|
||||||
cachedDrops = Block.getDroppedStacks(realState, world as ServerWorld, targetPos, be, null, TOOL)
|
return cache
|
||||||
}
|
}
|
||||||
return cachedDrops!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getStoredStacks(): Set<ItemStack> {
|
override fun readSnapshot(snapshot: Pair<BlockState, List<ItemStack>>?) {
|
||||||
if (miner.minerMode != MinerMode.ON_DEMAND) {
|
cache = snapshot
|
||||||
return setOf()
|
}
|
||||||
}
|
|
||||||
return getDrops().toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTotalCapacity(): Int {
|
override fun extract(resource: ItemVariant, maxAmount: Long, transaction: TransactionContext): Long {
|
||||||
return Int.MAX_VALUE
|
val drops = getDrops()
|
||||||
}
|
val (matched, unmatched) = drops.partition { resource.matches(it) }
|
||||||
|
if (matched.isEmpty()) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val matchedCount = matched.sumOf { it.count }.toLong()
|
||||||
|
val extracted = min(maxAmount, matchedCount)
|
||||||
|
transaction.addCloseCallback { context, result ->
|
||||||
|
if (result.wasCommitted()) {
|
||||||
|
world.breakBlock(pos.offset(facing), false)
|
||||||
|
// send any un-extracted drops to the network
|
||||||
|
if (matchedCount > extracted) {
|
||||||
|
miner.dispatchItemStack(resource.toStack().copyWithCount((matchedCount - extracted).toInt()))
|
||||||
|
}
|
||||||
|
for (stack in unmatched) {
|
||||||
|
miner.dispatchItemStack(stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extracted
|
||||||
|
}
|
||||||
|
|
||||||
override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic {
|
override fun iterator(transaction: TransactionContext): Iterator<StorageView<ItemVariant>> {
|
||||||
var totalCount = 0
|
return getDrops().map { View(it, this) }.iterator()
|
||||||
for (s in storedStacks) {
|
}
|
||||||
if (filter.matches(s)) {
|
|
||||||
totalCount += s.count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return GroupedItemInvView.ItemInvStatistic(filter, totalCount, 0, Int.MAX_VALUE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
class View(val stack: ItemStack, val proxy: MinerStorageProxy) : StorageView<ItemVariant> {
|
||||||
}
|
override fun extract(resource: ItemVariant, maxAmount: Long, transaction: TransactionContext): Long {
|
||||||
|
return proxy.extract(resource, maxAmount, transaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isResourceBlank(): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getResource(): ItemVariant {
|
||||||
|
return ItemVariant.of(stack)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getAmount(): Long {
|
||||||
|
return stack.count.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCapacity(): Long {
|
||||||
|
// TODO: ehh? we have stuff stored, but nothing can be inserted, so...
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PendingInsertion(stack: ItemStack, timestamp: Long) :
|
||||||
|
NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package net.shadowfacts.phycon.block.netinterface
|
package net.shadowfacts.phycon.block.netinterface
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.AttributeList
|
|
||||||
import alexiil.mc.lib.attributes.AttributeProvider
|
|
||||||
import net.minecraft.block.*
|
import net.minecraft.block.*
|
||||||
import net.minecraft.entity.LivingEntity
|
import net.minecraft.entity.LivingEntity
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
|
@ -18,44 +16,27 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class InterfaceBlock: FaceDeviceBlock<InterfaceBlockEntity>(
|
class InterfaceBlock : FaceDeviceBlock<InterfaceBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
),
|
),
|
||||||
NetworkComponentBlock,
|
NetworkComponentBlock {
|
||||||
AttributeProvider {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
|
val ID = Identifier(PhysicalConnectivity.MODID, "network_interface")
|
||||||
}
|
}
|
||||||
|
|
||||||
override val faceThickness = 2.0
|
override val faceThickness = 2.0
|
||||||
override val faceShapes = mapOf(
|
override val faceShapes = mapOf(
|
||||||
Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
|
Direction.DOWN to createCuboidShape(2.0, 0.0, 2.0, 14.0, 2.0, 14.0),
|
||||||
Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
|
Direction.UP to createCuboidShape(2.0, 14.0, 2.0, 14.0, 16.0, 14.0),
|
||||||
Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
|
Direction.NORTH to createCuboidShape(2.0, 2.0, 0.0, 14.0, 14.0, 2.0),
|
||||||
Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
|
Direction.SOUTH to createCuboidShape(2.0, 2.0, 14.0, 14.0, 14.0, 16.0),
|
||||||
Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
|
Direction.WEST to createCuboidShape(0.0, 2.0, 2.0, 2.0, 14.0, 14.0),
|
||||||
Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
|
Direction.EAST to createCuboidShape(14.0, 2.0, 2.0, 16.0, 14.0, 14.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = InterfaceBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = InterfaceBlockEntity(pos, state)
|
||||||
|
|
||||||
override fun onPlaced(world: World, pos: BlockPos, state: BlockState, placer: LivingEntity?, stack: ItemStack) {
|
|
||||||
if (!world.isClient) {
|
|
||||||
getBlockEntity(world, pos)!!.updateInventory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, boolean_1: Boolean) {
|
|
||||||
if (!world.isClient) {
|
|
||||||
getBlockEntity(world, pos)!!.updateInventory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
|
|
||||||
to.offer(getBlockEntity(world, pos))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
package net.shadowfacts.phycon.block.netinterface
|
package net.shadowfacts.phycon.block.netinterface
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.SearchOptions
|
import net.fabricmc.fabric.api.lookup.v1.block.BlockApiCache
|
||||||
import alexiil.mc.lib.attributes.Simulation
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage
|
||||||
import alexiil.mc.lib.attributes.item.GroupedItemInv
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
import alexiil.mc.lib.attributes.item.ItemAttributes
|
import net.fabricmc.fabric.api.transfer.v1.storage.Storage
|
||||||
|
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.nbt.NbtCompound
|
import net.minecraft.nbt.NbtCompound
|
||||||
|
import net.minecraft.server.world.ServerWorld
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.Direction
|
import net.minecraft.util.math.Direction
|
||||||
import net.shadowfacts.phycon.api.packet.Packet
|
import net.shadowfacts.phycon.api.packet.Packet
|
||||||
|
@ -19,122 +21,123 @@ import net.shadowfacts.phycon.component.NetworkStackReceiver
|
||||||
import net.shadowfacts.phycon.component.handleItemStack
|
import net.shadowfacts.phycon.component.handleItemStack
|
||||||
import net.shadowfacts.phycon.packet.*
|
import net.shadowfacts.phycon.packet.*
|
||||||
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
||||||
import java.lang.ref.WeakReference
|
import net.shadowfacts.phycon.util.copyWithCount
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class InterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.INTERFACE, pos, state),
|
class InterfaceBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
ItemStackPacketHandler,
|
DeviceBlockEntity(PhyBlockEntities.INTERFACE, pos, state),
|
||||||
NetworkStackProvider,
|
ItemStackPacketHandler,
|
||||||
NetworkStackReceiver,
|
NetworkStackProvider,
|
||||||
ClientConfigurableDevice {
|
NetworkStackReceiver,
|
||||||
|
ClientConfigurableDevice {
|
||||||
|
|
||||||
private val facing: Direction
|
private val facing: Direction
|
||||||
get() = cachedState[FaceDeviceBlock.FACING]
|
get() = cachedState[FaceDeviceBlock.FACING]
|
||||||
|
|
||||||
override var providerPriority = 0
|
override var providerPriority = 0
|
||||||
override var receiverPriority = 0
|
override var receiverPriority = 0
|
||||||
var syncPriorities = true
|
var syncPriorities = true
|
||||||
|
|
||||||
private var inventory: WeakReference<GroupedItemInv>? = null
|
private var cachedFacedBlock: BlockState? = null
|
||||||
|
private var inventoryCache: BlockApiCache<Storage<ItemVariant>, Direction>? = null
|
||||||
|
|
||||||
fun updateInventory() {
|
private fun getInventory(): Storage<ItemVariant>? {
|
||||||
val offsetPos = pos.offset(facing)
|
if (cachedFacedBlock == null) {
|
||||||
val option = SearchOptions.inDirection(facing)
|
cachedFacedBlock = world!!.getBlockState(pos.offset(facing))
|
||||||
inventory = ItemAttributes.GROUPED_INV.getFirstOrNull(world, offsetPos, option).let {
|
}
|
||||||
WeakReference(it)
|
if (inventoryCache == null) {
|
||||||
}
|
inventoryCache = BlockApiCache.create(ItemStorage.SIDED, world!! as ServerWorld, pos.offset(facing))
|
||||||
}
|
}
|
||||||
|
return inventoryCache!!.find(cachedFacedBlock!!, facing.opposite)
|
||||||
|
}
|
||||||
|
|
||||||
private fun getInventory(): GroupedItemInv? {
|
override fun handle(packet: Packet) {
|
||||||
// if we don't have an inventory, try to get one
|
when (packet) {
|
||||||
// this happens when readAll is called before a neighbor state changes, such as immediately after world load
|
is RequestInventoryPacket -> handleRequestInventory(packet)
|
||||||
if (inventory?.get() == null) updateInventory()
|
is LocateStackPacket -> handleLocateStack(packet)
|
||||||
return inventory?.get()
|
is ExtractStackPacket -> handleExtractStack(packet)
|
||||||
}
|
is CheckCapacityPacket -> handleCheckCapacity(packet)
|
||||||
|
is ItemStackPacket -> handleItemStack(packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
private fun handleRequestInventory(packet: RequestInventoryPacket) {
|
||||||
when (packet) {
|
if (packet.kind != RequestInventoryPacket.Kind.GROUPED) {
|
||||||
is RequestInventoryPacket -> handleRequestInventory(packet)
|
return
|
||||||
is LocateStackPacket -> handleLocateStack(packet)
|
}
|
||||||
is ExtractStackPacket -> handleExtractStack(packet)
|
getInventory()?.also { inv ->
|
||||||
is CheckCapacityPacket -> handleCheckCapacity(packet)
|
sendPacket(ReadItemStoragePacket(inv, ipAddress, packet.source))
|
||||||
is ItemStackPacket -> handleItemStack(packet)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleRequestInventory(packet: RequestInventoryPacket) {
|
private fun handleLocateStack(packet: LocateStackPacket) {
|
||||||
if (packet.kind != RequestInventoryPacket.Kind.GROUPED) {
|
getInventory()?.also { inv ->
|
||||||
return
|
val transaction = Transaction.openOuter()
|
||||||
}
|
val amount = inv.simulateExtract(ItemVariant.of(packet.stack), Long.MAX_VALUE, transaction);
|
||||||
getInventory()?.also { inv ->
|
transaction.close()
|
||||||
sendPacket(ReadGroupedInventoryPacket(inv, ipAddress, packet.source))
|
sendPacket(StackLocationPacket(packet.stack, amount.toInt(), this, ipAddress, packet.source))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLocateStack(packet: LocateStackPacket) {
|
private fun handleExtractStack(packet: ExtractStackPacket) {
|
||||||
getInventory()?.also { inv ->
|
getInventory()?.also { inv ->
|
||||||
val amount = inv.getAmount(packet.stack)
|
var remaining = packet.amount
|
||||||
sendPacket(StackLocationPacket(packet.stack, amount, this, ipAddress, packet.source))
|
while (remaining > 0) {
|
||||||
}
|
val toExtract = min(remaining, packet.stack.maxCount)
|
||||||
}
|
val transaction = Transaction.openOuter()
|
||||||
|
val extracted = inv.extract(ItemVariant.of(packet.stack), toExtract.toLong(), transaction)
|
||||||
|
transaction.commit()
|
||||||
|
remaining -= extracted.toInt()
|
||||||
|
sendPacket(ItemStackPacket(packet.stack.copyWithCount(extracted.toInt()), ipAddress, packet.source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleExtractStack(packet: ExtractStackPacket) {
|
private fun handleCheckCapacity(packet: CheckCapacityPacket) {
|
||||||
getInventory()?.also { inv ->
|
getInventory()?.also { inv ->
|
||||||
var amount = packet.amount
|
val transaction = Transaction.openOuter()
|
||||||
while (amount > 0) {
|
val inserted = inv.simulateInsert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction)
|
||||||
val extracted = inv.extract(packet.stack, min(amount, packet.stack.maxCount))
|
transaction.close()
|
||||||
if (extracted.isEmpty) {
|
sendPacket(CapacityPacket(packet.stack, inserted.toInt(), this, ipAddress, packet.source))
|
||||||
break
|
}
|
||||||
} else {
|
}
|
||||||
amount -= extracted.count
|
|
||||||
sendPacket(ItemStackPacket(extracted, ipAddress, packet.source))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleCheckCapacity(packet: CheckCapacityPacket) {
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
getInventory()?.also { inv ->
|
val inventory = getInventory()
|
||||||
val remaining = inv.attemptInsertion(packet.stack, Simulation.SIMULATE)
|
if (inventory != null) {
|
||||||
val couldAccept = packet.stack.count - remaining.count
|
val transaction = Transaction.openOuter()
|
||||||
sendPacket(CapacityPacket(packet.stack, couldAccept, this, ipAddress, packet.source))
|
val inserted = inventory.insert(ItemVariant.of(packet.stack), packet.stack.count.toLong(), transaction)
|
||||||
}
|
transaction.commit()
|
||||||
}
|
val remaining = packet.stack.count - inserted.toInt()
|
||||||
|
return packet.stack.copyWithCount(remaining)
|
||||||
|
} else {
|
||||||
|
return packet.stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
val inventory = getInventory()
|
super.toCommonTag(tag)
|
||||||
if (inventory != null) {
|
writeDeviceConfiguration(tag)
|
||||||
val remaining = inventory.insert(packet.stack)
|
}
|
||||||
// whatever could not be inserted will be sent back to the packet's source
|
|
||||||
return remaining
|
|
||||||
} else {
|
|
||||||
return packet.stack
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
super.toCommonTag(tag)
|
super.fromCommonTag(tag)
|
||||||
writeDeviceConfiguration(tag)
|
loadDeviceConfiguration(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
tag.putInt("ProviderPriority", providerPriority)
|
||||||
loadDeviceConfiguration(tag)
|
tag.putInt("ReceiverPriority", receiverPriority)
|
||||||
}
|
tag.putBoolean("SyncPriorities", syncPriorities)
|
||||||
|
}
|
||||||
|
|
||||||
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
||||||
tag.putInt("ProviderPriority", providerPriority)
|
providerPriority = tag.getInt("ProviderPriority")
|
||||||
tag.putInt("ReceiverPriority", receiverPriority)
|
receiverPriority = tag.getInt("ReceiverPriority")
|
||||||
tag.putBoolean("SyncPriorities", syncPriorities)
|
syncPriorities = tag.getBoolean("SyncPriorities")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
|
||||||
providerPriority = tag.getInt("ProviderPriority")
|
|
||||||
receiverPriority = tag.getInt("ReceiverPriority")
|
|
||||||
syncPriorities = tag.getBoolean("SyncPriorities")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package net.shadowfacts.phycon.block.netswitch
|
package net.shadowfacts.phycon.block.netswitch
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.AttributeList
|
|
||||||
import alexiil.mc.lib.attributes.AttributeProvider
|
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.Material
|
import net.minecraft.block.Material
|
||||||
import net.minecraft.block.entity.BlockEntity
|
import net.minecraft.block.entity.BlockEntity
|
||||||
|
@ -11,7 +9,6 @@ import net.minecraft.sound.BlockSoundGroup
|
||||||
import net.minecraft.util.Identifier
|
import net.minecraft.util.Identifier
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.Direction
|
import net.minecraft.util.math.Direction
|
||||||
import net.minecraft.world.BlockView
|
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import net.minecraft.world.WorldAccess
|
import net.minecraft.world.WorldAccess
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
@ -23,39 +20,43 @@ import java.util.*
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class SwitchBlock: BlockWithEntity<SwitchBlockEntity>(
|
class SwitchBlock : BlockWithEntity<SwitchBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
),
|
),
|
||||||
NetworkComponentBlock,
|
NetworkComponentBlock {
|
||||||
AttributeProvider {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "switch")
|
val ID = Identifier(PhysicalConnectivity.MODID, "switch")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||||
return EnumSet.allOf(Direction::class.java)
|
return EnumSet.allOf(Direction::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNetworkInterfaceForSide(side: Direction, state: BlockState, world: WorldAccess, pos: BlockPos): Interface? {
|
override fun getNetworkInterfaceForSide(
|
||||||
return getBlockEntity(world, pos)?.interfaces?.find { it.side == side }
|
side: Direction,
|
||||||
}
|
state: BlockState,
|
||||||
|
world: WorldAccess,
|
||||||
|
pos: BlockPos
|
||||||
|
): Interface? {
|
||||||
|
return getBlockEntity(world, pos)?.interfaces?.find { it.side == side }
|
||||||
|
}
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = SwitchBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = SwitchBlockEntity(pos, state)
|
||||||
|
|
||||||
override fun <T: BlockEntity> getTicker(world: World, state: BlockState, type: BlockEntityType<T>): BlockEntityTicker<T>? {
|
override fun <T : BlockEntity> getTicker(
|
||||||
return if (world.isClient) {
|
world: World,
|
||||||
null
|
state: BlockState,
|
||||||
} else {
|
type: BlockEntityType<T>
|
||||||
BlockEntityTicker { world, blockPos, blockState, blockEntity ->
|
): BlockEntityTicker<T>? {
|
||||||
(blockEntity as SwitchBlockEntity).tick()
|
return if (world.isClient) {
|
||||||
}
|
null
|
||||||
}
|
} else {
|
||||||
}
|
BlockEntityTicker { world, blockPos, blockState, blockEntity ->
|
||||||
|
(blockEntity as SwitchBlockEntity).tick()
|
||||||
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
|
}
|
||||||
to.offer(getBlockEntity(world, pos))
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import net.shadowfacts.phycon.frame.BasePacketFrame
|
||||||
import net.shadowfacts.phycon.frame.NetworkSplitFrame
|
import net.shadowfacts.phycon.frame.NetworkSplitFrame
|
||||||
import net.shadowfacts.phycon.init.PhyBlockEntities
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
import net.shadowfacts.phycon.packet.ItemStackPacket
|
import net.shadowfacts.phycon.packet.ItemStackPacket
|
||||||
|
import net.shadowfacts.phycon.util.IntRingBuffer
|
||||||
import net.shadowfacts.phycon.util.NetworkUtil
|
import net.shadowfacts.phycon.util.NetworkUtil
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.Deque
|
import java.util.Deque
|
||||||
|
@ -28,155 +29,188 @@ import java.util.LinkedList
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class SwitchBlockEntity(pos: BlockPos, state: BlockState): BlockEntity(PhyBlockEntities.SWITCH, pos, state) {
|
class SwitchBlockEntity(pos: BlockPos, state: BlockState) : BlockEntity(PhyBlockEntities.SWITCH, pos, state) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var SWITCHING_CAPACITY = 256 // 256 packets/tick
|
var SWITCHING_CAPACITY = 256 // 256 packets/tick
|
||||||
}
|
}
|
||||||
|
|
||||||
val interfaces = Direction.values().map { SwitchInterface(it, WeakReference(this), MACAddress.random()) }
|
val interfaces = Direction.values().map { SwitchInterface(it, WeakReference(this), MACAddress.random()) }
|
||||||
|
|
||||||
private val macTable = mutableMapOf<MACAddress, Direction>()
|
private val macTable = mutableMapOf<MACAddress, Direction>()
|
||||||
private val destinationCache = Array<WeakReference<Interface>?>(6) { null }
|
private val destinationCache = Array<WeakReference<Interface>?>(6) { null }
|
||||||
private var packetsHandledThisTick = 0
|
val packetStatistics = IntRingBuffer(60) // 1 minute's worth
|
||||||
private var delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList()
|
private val currentSecondPacketStatistics = IntRingBuffer(20)
|
||||||
|
private var packetsHandledThisTick = 0
|
||||||
|
private var delayedPackets: Deque<Pair<PacketFrame, SwitchInterface>> = LinkedList()
|
||||||
|
|
||||||
fun interfaceForSide(side: Direction): SwitchInterface {
|
var statisticsObserver: (() -> Unit)? = null
|
||||||
return interfaces.find { it.side == side }!!
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handle(frame: EthernetFrame, fromItf: SwitchInterface) {
|
fun interfaceForSide(side: Direction): SwitchInterface {
|
||||||
macTable[frame.source] = fromItf.side
|
return interfaces.find { it.side == side }!!
|
||||||
|
}
|
||||||
|
|
||||||
if (frame is PacketFrame) {
|
private fun handle(frame: EthernetFrame, fromItf: SwitchInterface) {
|
||||||
if (packetsHandledThisTick > SWITCHING_CAPACITY) {
|
macTable[frame.source] = fromItf.side
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{} reached capacity, delaying forwarding {}", this, frame)
|
|
||||||
delayedPackets.addLast(frame to fromItf)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
packetsHandledThisTick++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resend(frame, fromItf)
|
if (frame is PacketFrame) {
|
||||||
}
|
if (packetsHandledThisTick > SWITCHING_CAPACITY) {
|
||||||
|
PhysicalConnectivity.NETWORK_LOGGER.debug("{} reached capacity, delaying forwarding {}", this, frame)
|
||||||
|
delayedPackets.addLast(frame to fromItf)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
packetsHandledThisTick++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun resend(frame: EthernetFrame, fromItf: SwitchInterface) {
|
resend(frame, fromItf)
|
||||||
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
|
}
|
||||||
val dir = macTable[frame.destination]!!
|
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) forwarding {} to side {}", this, fromItf.side, fromItf.macAddress, frame, dir)
|
|
||||||
interfaceForSide(dir).send(frame)
|
|
||||||
} else {
|
|
||||||
flood(frame, fromItf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun flood(frame: EthernetFrame, source: SwitchInterface) {
|
private fun resend(frame: EthernetFrame, fromItf: SwitchInterface) {
|
||||||
PhysicalConnectivity.NETWORK_LOGGER.debug("{} ({}, {}) flooding {}", this, source.side, source.macAddress, frame)
|
if (frame.destination.type != MACAddress.Type.BROADCAST && macTable.containsKey(frame.destination)) {
|
||||||
for (itf in interfaces) {
|
val dir = macTable[frame.destination]!!
|
||||||
if (source == itf) continue
|
PhysicalConnectivity.NETWORK_LOGGER.debug(
|
||||||
itf.send(frame)
|
"{} ({}, {}) forwarding {} to side {}",
|
||||||
}
|
this,
|
||||||
}
|
fromItf.side,
|
||||||
|
fromItf.macAddress,
|
||||||
|
frame,
|
||||||
|
dir
|
||||||
|
)
|
||||||
|
interfaceForSide(dir).send(frame)
|
||||||
|
} else {
|
||||||
|
flood(frame, fromItf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun findDestination(fromItf: Interface): Interface? {
|
private fun flood(frame: EthernetFrame, source: SwitchInterface) {
|
||||||
val side = (fromItf as SwitchInterface).side
|
PhysicalConnectivity.NETWORK_LOGGER.debug(
|
||||||
return destinationCache[side.ordinal]?.get()
|
"{} ({}, {}) flooding {}",
|
||||||
?: NetworkUtil.findConnectedInterface(world!!, pos, side)?.also {
|
this,
|
||||||
destinationCache[side.ordinal] = WeakReference(it)
|
source.side,
|
||||||
}
|
source.macAddress,
|
||||||
}
|
frame
|
||||||
|
)
|
||||||
|
for (itf in interfaces) {
|
||||||
|
if (source == itf) continue
|
||||||
|
itf.send(frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun cableDisconnected(itf: SwitchInterface) {
|
private fun findDestination(fromItf: Interface): Interface? {
|
||||||
macTable.entries.filter {
|
val side = (fromItf as SwitchInterface).side
|
||||||
it.value == itf.side
|
return destinationCache[side.ordinal]?.get()
|
||||||
}.forEach {
|
?: NetworkUtil.findConnectedInterface(world!!, pos, side)?.also {
|
||||||
macTable.remove(it.key)
|
destinationCache[side.ordinal] = WeakReference(it)
|
||||||
}
|
}
|
||||||
destinationCache[itf.side.ordinal] = null
|
}
|
||||||
flood(NetworkSplitFrame(itf.macAddress), itf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun tick() {
|
private fun cableDisconnected(itf: SwitchInterface) {
|
||||||
packetsHandledThisTick = 0
|
macTable.entries.filter {
|
||||||
|
it.value == itf.side
|
||||||
|
}.forEach {
|
||||||
|
macTable.remove(it.key)
|
||||||
|
}
|
||||||
|
destinationCache[itf.side.ordinal] = null
|
||||||
|
flood(NetworkSplitFrame(itf.macAddress), itf)
|
||||||
|
}
|
||||||
|
|
||||||
while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) {
|
fun tick() {
|
||||||
val (frame, fromItf) = delayedPackets.pop()
|
if (statisticsObserver != null) {
|
||||||
resend(frame, fromItf)
|
if (currentSecondPacketStatistics.size == 20) {
|
||||||
}
|
packetStatistics.add(currentSecondPacketStatistics.sum())
|
||||||
}
|
currentSecondPacketStatistics.clear()
|
||||||
|
statisticsObserver?.invoke()
|
||||||
|
} else {
|
||||||
|
currentSecondPacketStatistics.add(packetsHandledThisTick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun writeNbt(tag: NbtCompound) {
|
packetsHandledThisTick = 0
|
||||||
super.writeNbt(tag)
|
|
||||||
|
|
||||||
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
|
while (delayedPackets.isNotEmpty() && packetsHandledThisTick <= SWITCHING_CAPACITY) {
|
||||||
val list = NbtList()
|
val (frame, fromItf) = delayedPackets.pop()
|
||||||
for ((frame, fromItf) in delayedPackets) {
|
resend(frame, fromItf)
|
||||||
val packet = frame.packet
|
}
|
||||||
if (packet !is ItemStackPacket) continue
|
}
|
||||||
val compound = NbtCompound()
|
|
||||||
compound.putInt("FromItfSide", fromItf.side.ordinal)
|
|
||||||
compound.putInt("SourceIP", packet.source.address)
|
|
||||||
compound.putInt("DestinationIP", packet.destination.address)
|
|
||||||
compound.putLong("SourceMAC", frame.source.address)
|
|
||||||
compound.putLong("DestinationMAC", frame.destination.address)
|
|
||||||
compound.put("Stack", packet.stack.writeNbt(NbtCompound()))
|
|
||||||
list.add(compound)
|
|
||||||
}
|
|
||||||
tag.put("DelayedStackPackets", list)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun readNbt(tag: NbtCompound) {
|
override fun writeNbt(tag: NbtCompound) {
|
||||||
super.readNbt(tag)
|
super.writeNbt(tag)
|
||||||
|
|
||||||
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
|
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
|
||||||
interfaces[i].macAddress = MACAddress(l)
|
val list = NbtList()
|
||||||
}
|
for ((frame, fromItf) in delayedPackets) {
|
||||||
tag.getList("DelayedStackPackets", 10).forEach { it ->
|
val packet = frame.packet
|
||||||
val compound = it as NbtCompound
|
if (packet !is ItemStackPacket) continue
|
||||||
val fromItfSide = Direction.values()[compound.getInt("FromItfSide")]
|
val compound = NbtCompound()
|
||||||
val fromItf = interfaces.find { it.side == fromItfSide }!!
|
compound.putInt("FromItfSide", fromItf.side.ordinal)
|
||||||
val sourceIP = IPAddress(compound.getInt("SourceIP"))
|
compound.putInt("SourceIP", packet.source.address)
|
||||||
val destinationIP = IPAddress(compound.getInt("DestinationIP"))
|
compound.putInt("DestinationIP", packet.destination.address)
|
||||||
val sourceMAC = MACAddress(compound.getLong("SourceMAC"))
|
compound.putLong("SourceMAC", frame.source.address)
|
||||||
val destinationMAC = MACAddress(compound.getLong("DestinationMAC"))
|
compound.putLong("DestinationMAC", frame.destination.address)
|
||||||
val stack = ItemStack.fromNbt(compound.getCompound("Stack"))
|
compound.put("Stack", packet.stack.writeNbt(NbtCompound()))
|
||||||
if (!stack.isEmpty) {
|
list.add(compound)
|
||||||
val packet = ItemStackPacket(stack, sourceIP, destinationIP)
|
}
|
||||||
val frame = BasePacketFrame(packet, sourceMAC, destinationMAC)
|
tag.put("DelayedStackPackets", list)
|
||||||
delayedPackets.addLast(frame to fromItf)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
|
override fun readNbt(tag: NbtCompound) {
|
||||||
return BlockEntityUpdateS2CPacket.create(this)
|
super.readNbt(tag)
|
||||||
}
|
|
||||||
|
|
||||||
override fun toInitialChunkDataNbt(): NbtCompound {
|
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
|
||||||
val tag = NbtCompound()
|
interfaces[i].macAddress = MACAddress(l)
|
||||||
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
|
}
|
||||||
return tag
|
tag.getList("DelayedStackPackets", 10).forEach { it ->
|
||||||
}
|
val compound = it as NbtCompound
|
||||||
|
val fromItfSide = Direction.values()[compound.getInt("FromItfSide")]
|
||||||
|
val fromItf = interfaces.find { it.side == fromItfSide }!!
|
||||||
|
val sourceIP = IPAddress(compound.getInt("SourceIP"))
|
||||||
|
val destinationIP = IPAddress(compound.getInt("DestinationIP"))
|
||||||
|
val sourceMAC = MACAddress(compound.getLong("SourceMAC"))
|
||||||
|
val destinationMAC = MACAddress(compound.getLong("DestinationMAC"))
|
||||||
|
val stack = ItemStack.fromNbt(compound.getCompound("Stack"))
|
||||||
|
if (!stack.isEmpty) {
|
||||||
|
val packet = ItemStackPacket(stack, sourceIP, destinationIP)
|
||||||
|
val frame = BasePacketFrame(packet, sourceMAC, destinationMAC)
|
||||||
|
delayedPackets.addLast(frame to fromItf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tag.getIntArray("PacketStatistics")?.also { statistics ->
|
||||||
|
if (statistics.isNotEmpty()) {
|
||||||
|
packetStatistics.replace(statistics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class SwitchInterface(
|
override fun toUpdatePacket(): Packet<ClientPlayPacketListener>? {
|
||||||
val side: Direction,
|
return BlockEntityUpdateS2CPacket.create(this)
|
||||||
val switch: WeakReference<SwitchBlockEntity>,
|
}
|
||||||
@JvmField var macAddress: MACAddress,
|
|
||||||
): Interface {
|
|
||||||
override fun getMACAddress() = macAddress
|
|
||||||
|
|
||||||
override fun receive(frame: EthernetFrame) {
|
override fun toInitialChunkDataNbt(): NbtCompound {
|
||||||
switch.get()?.handle(frame, this)
|
val tag = NbtCompound()
|
||||||
}
|
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
|
||||||
|
tag.putIntArray("PacketStatistics", packetStatistics.asContiguousArray())
|
||||||
|
return tag
|
||||||
|
}
|
||||||
|
|
||||||
override fun send(frame: EthernetFrame) {
|
class SwitchInterface(
|
||||||
switch.get()?.findDestination(this)?.receive(frame)
|
val side: Direction,
|
||||||
}
|
val switch: WeakReference<SwitchBlockEntity>,
|
||||||
|
@JvmField var macAddress: MACAddress,
|
||||||
|
) : Interface {
|
||||||
|
override fun getMACAddress() = macAddress
|
||||||
|
|
||||||
override fun cableDisconnected() {
|
override fun receive(frame: EthernetFrame) {
|
||||||
switch.get()?.cableDisconnected(this)
|
switch.get()?.handle(frame, this)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
override fun send(frame: EthernetFrame) {
|
||||||
|
switch.get()?.findDestination(this)?.receive(frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cableDisconnected() {
|
||||||
|
switch.get()?.cableDisconnected(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
package net.shadowfacts.phycon.block.netswitch
|
||||||
|
|
||||||
|
import net.minecraft.client.util.math.MatrixStack
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.text.LiteralText
|
||||||
|
import net.minecraft.text.Text
|
||||||
|
import net.shadowfacts.cacao.CacaoHandledScreen
|
||||||
|
import net.shadowfacts.cacao.geometry.Point
|
||||||
|
import net.shadowfacts.cacao.geometry.Rect
|
||||||
|
import net.shadowfacts.cacao.geometry.Size
|
||||||
|
import net.shadowfacts.cacao.util.Color
|
||||||
|
import net.shadowfacts.cacao.util.RenderHelper
|
||||||
|
import net.shadowfacts.cacao.view.Label
|
||||||
|
import net.shadowfacts.cacao.view.View
|
||||||
|
import net.shadowfacts.cacao.viewcontroller.ViewController
|
||||||
|
import net.shadowfacts.cacao.window.ScreenHandlerWindow
|
||||||
|
import net.shadowfacts.kiwidsl.dsl
|
||||||
|
import org.lwjgl.glfw.GLFW
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class SwitchConsoleScreen(
|
||||||
|
handler: SwitchConsoleScreenHandler,
|
||||||
|
playerInventory: PlayerInventory,
|
||||||
|
title: Text,
|
||||||
|
) : CacaoHandledScreen<SwitchConsoleScreenHandler>(
|
||||||
|
handler,
|
||||||
|
playerInventory,
|
||||||
|
title,
|
||||||
|
) {
|
||||||
|
|
||||||
|
val root = SwitchConsoleViewController(handler.switch)
|
||||||
|
|
||||||
|
init {
|
||||||
|
addWindow(ScreenHandlerWindow(handler, root))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldPause() = false
|
||||||
|
|
||||||
|
override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean {
|
||||||
|
if (keyCode == GLFW.GLFW_KEY_E) {
|
||||||
|
close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return super.keyPressed(keyCode, scanCode, modifiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwitchConsoleViewController(val switch: SwitchBlockEntity) : ViewController() {
|
||||||
|
override fun viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
val stats = SwitchPacketStatisticsView(switch)
|
||||||
|
view.addSubview(stats)
|
||||||
|
view.solver.dsl {
|
||||||
|
stats.centerXAnchor equalTo (view.centerXAnchor + 50)
|
||||||
|
stats.centerYAnchor equalTo (view.centerYAnchor + 50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwitchPacketStatisticsView(val switch: SwitchBlockEntity) : View() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
intrinsicContentSize = Size(180.0, 90.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {
|
||||||
|
RenderHelper.fill(matrixStack, bounds, Color.BLACK)
|
||||||
|
if (switch.packetStatistics.size == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: drawLine isn't working for some reason
|
||||||
|
RenderHelper.drawLine(
|
||||||
|
Point(bounds.left, bounds.top),
|
||||||
|
Point(bounds.right, bounds.bottom),
|
||||||
|
1.0,
|
||||||
|
2f,
|
||||||
|
Color.MAGENTA
|
||||||
|
)
|
||||||
|
return
|
||||||
|
val maxPackets = switch.packetStatistics.maxOf { it }
|
||||||
|
val maxDataPointsCount = 60
|
||||||
|
var lastPoint: Point? = null
|
||||||
|
val size = Size(3.0, 3.0)
|
||||||
|
for ((index, packets) in switch.packetStatistics.withIndex()) {
|
||||||
|
val x = (1 - (switch.packetStatistics.size - index).toDouble() / maxDataPointsCount) * bounds.width
|
||||||
|
val y = (1 - (packets.toDouble() / maxPackets)) * (bounds.height)
|
||||||
|
val point = Point(x, y)
|
||||||
|
if (lastPoint != null) {
|
||||||
|
// RenderHelper.fill(matrixStack, Rect(lastPoint, 3.0, 3.0), Color.RED)
|
||||||
|
RenderHelper.drawLine(lastPoint, point, 1.0, 2f, Color.RED)
|
||||||
|
}
|
||||||
|
lastPoint = point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package net.shadowfacts.phycon.block.netswitch
|
||||||
|
|
||||||
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
import net.minecraft.network.PacketByteBuf
|
||||||
|
import net.minecraft.screen.ScreenHandler
|
||||||
|
import net.minecraft.util.Identifier
|
||||||
|
import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
|
import net.shadowfacts.phycon.init.PhyBlocks
|
||||||
|
import net.shadowfacts.phycon.init.PhyScreens
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author shadowfacts
|
||||||
|
*/
|
||||||
|
class SwitchConsoleScreenHandler(
|
||||||
|
syncId: Int,
|
||||||
|
val switch: SwitchBlockEntity,
|
||||||
|
) : ScreenHandler(PhyScreens.SWITCH_CONSOLE, syncId) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val ID = Identifier(PhysicalConnectivity.MODID, "switch_console")
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
|
||||||
|
this(
|
||||||
|
syncId,
|
||||||
|
PhyBlocks.SWITCH.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
||||||
|
)
|
||||||
|
|
||||||
|
init {
|
||||||
|
switch.statisticsObserver = {
|
||||||
|
switch.world!!.updateListeners(switch.pos, switch.cachedState, switch.cachedState, 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun canUse(player: PlayerEntity): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close(player: PlayerEntity) {
|
||||||
|
super.close(player)
|
||||||
|
|
||||||
|
switch.statisticsObserver = null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -12,10 +12,10 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class P2PInterfaceBlock: FaceDeviceBlock<P2PInterfaceBlockEntity>(
|
class P2PInterfaceBlock : FaceDeviceBlock<P2PInterfaceBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -24,12 +24,12 @@ class P2PInterfaceBlock: FaceDeviceBlock<P2PInterfaceBlockEntity>(
|
||||||
|
|
||||||
override val faceThickness = 4.0
|
override val faceThickness = 4.0
|
||||||
override val faceShapes = mapOf(
|
override val faceShapes = mapOf(
|
||||||
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 4.0, 16.0),
|
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 4.0, 16.0),
|
||||||
Direction.UP to createCuboidShape(0.0, 12.0, 0.0, 16.0, 16.0, 16.0),
|
Direction.UP to createCuboidShape(0.0, 12.0, 0.0, 16.0, 16.0, 16.0),
|
||||||
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 4.0),
|
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 4.0),
|
||||||
Direction.SOUTH to createCuboidShape(0.0, 0.0, 12.0, 16.0, 16.0, 16.0),
|
Direction.SOUTH to createCuboidShape(0.0, 0.0, 12.0, 16.0, 16.0, 16.0),
|
||||||
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 4.0, 16.0, 16.0),
|
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 4.0, 16.0, 16.0),
|
||||||
Direction.EAST to createCuboidShape(12.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
Direction.EAST to createCuboidShape(12.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = P2PInterfaceBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = P2PInterfaceBlockEntity(pos, state)
|
||||||
|
|
|
@ -15,7 +15,8 @@ import net.shadowfacts.phycon.packet.RequestInventoryPacket
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class P2PInterfaceBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.P2P_INTERFACE, pos, state) {
|
class P2PInterfaceBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
|
DeviceBlockEntity(PhyBlockEntities.P2P_INTERFACE, pos, state) {
|
||||||
|
|
||||||
private var inventory: Storage<ItemVariant>? = null
|
private var inventory: Storage<ItemVariant>? = null
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,10 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class P2PReceiverBlock: FaceDeviceBlock<P2PReceiverBlockEntity>(
|
class P2PReceiverBlock : FaceDeviceBlock<P2PReceiverBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -28,12 +28,12 @@ class P2PReceiverBlock: FaceDeviceBlock<P2PReceiverBlockEntity>(
|
||||||
|
|
||||||
override val faceThickness = 4.0
|
override val faceThickness = 4.0
|
||||||
override val faceShapes = mapOf(
|
override val faceShapes = mapOf(
|
||||||
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 4.0, 16.0),
|
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 4.0, 16.0),
|
||||||
Direction.UP to createCuboidShape(0.0, 12.0, 0.0, 16.0, 16.0, 16.0),
|
Direction.UP to createCuboidShape(0.0, 12.0, 0.0, 16.0, 16.0, 16.0),
|
||||||
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 4.0),
|
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 4.0),
|
||||||
Direction.SOUTH to createCuboidShape(0.0, 0.0, 12.0, 16.0, 16.0, 16.0),
|
Direction.SOUTH to createCuboidShape(0.0, 0.0, 12.0, 16.0, 16.0, 16.0),
|
||||||
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 4.0, 16.0, 16.0),
|
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 4.0, 16.0, 16.0),
|
||||||
Direction.EAST to createCuboidShape(12.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
Direction.EAST to createCuboidShape(12.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = P2PReceiverBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = P2PReceiverBlockEntity(pos, state)
|
||||||
|
|
|
@ -21,7 +21,8 @@ import java.lang.ref.WeakReference
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class P2PReceiverBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.P2P_RECEIVER, pos, state),
|
class P2PReceiverBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
|
DeviceBlockEntity(PhyBlockEntities.P2P_RECEIVER, pos, state),
|
||||||
ClientConfigurableDevice {
|
ClientConfigurableDevice {
|
||||||
|
|
||||||
enum class Status {
|
enum class Status {
|
||||||
|
|
|
@ -18,58 +18,65 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class RedstoneControllerBlock: FaceDeviceBlock<RedstoneControllerBlockEntity>(
|
class RedstoneControllerBlock : FaceDeviceBlock<RedstoneControllerBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_controller")
|
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_controller")
|
||||||
val POWERED = Properties.POWERED
|
val POWERED = Properties.POWERED
|
||||||
}
|
}
|
||||||
|
|
||||||
override val faceThickness = 3.0
|
override val faceThickness = 3.0
|
||||||
override val faceShapes = mapOf(
|
override val faceShapes = mapOf(
|
||||||
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 3.0, 16.0),
|
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 3.0, 16.0),
|
||||||
Direction.UP to createCuboidShape(0.0, 13.0, 0.0, 16.0, 16.0, 16.0),
|
Direction.UP to createCuboidShape(0.0, 13.0, 0.0, 16.0, 16.0, 16.0),
|
||||||
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 3.0),
|
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 3.0),
|
||||||
Direction.SOUTH to createCuboidShape(0.0, 0.0, 13.0, 16.0, 16.0, 16.0),
|
Direction.SOUTH to createCuboidShape(0.0, 0.0, 13.0, 16.0, 16.0, 16.0),
|
||||||
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 3.0, 16.0, 16.0),
|
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 3.0, 16.0, 16.0),
|
||||||
Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||||
super.appendProperties(builder)
|
super.appendProperties(builder)
|
||||||
builder.add(POWERED)
|
builder.add(POWERED)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = RedstoneControllerBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = RedstoneControllerBlockEntity(pos, state)
|
||||||
|
|
||||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||||
val state = super.getPlacementState(context)
|
val state = super.getPlacementState(context)
|
||||||
return state.with(POWERED, isPowered(context.world, context.blockPos, state[FACING]))
|
return state.with(POWERED, isPowered(context.world, context.blockPos, state[FACING]))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun neighborUpdate(state: BlockState, world: World, pos: BlockPos, neighborBlock: Block, neighborPos: BlockPos, bl: Boolean) {
|
override fun neighborUpdate(
|
||||||
// this can't be done in getStateForNeighborUpdate because getEmittedRedstonePower is defined in World not WorldAccess
|
state: BlockState,
|
||||||
if (!world.isClient) {
|
world: World,
|
||||||
val wasLit = state[POWERED]
|
pos: BlockPos,
|
||||||
val isLit = isPowered(world, pos, state[FACING])
|
neighborBlock: Block,
|
||||||
if (wasLit != isLit) {
|
neighborPos: BlockPos,
|
||||||
toggleLit(state, world, pos)
|
bl: Boolean
|
||||||
}
|
) {
|
||||||
}
|
// this can't be done in getStateForNeighborUpdate because getEmittedRedstonePower is defined in World not WorldAccess
|
||||||
}
|
if (!world.isClient) {
|
||||||
|
val wasLit = state[POWERED]
|
||||||
|
val isLit = isPowered(world, pos, state[FACING])
|
||||||
|
if (wasLit != isLit) {
|
||||||
|
toggleLit(state, world, pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun isPowered(world: World, pos: BlockPos, facing: Direction): Boolean {
|
private fun isPowered(world: World, pos: BlockPos, facing: Direction): Boolean {
|
||||||
val offset = pos.offset(facing)
|
val offset = pos.offset(facing)
|
||||||
return world.getEmittedRedstonePower(offset, facing) > 0
|
return world.getEmittedRedstonePower(offset, facing) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleLit(state: BlockState, world: World, pos: BlockPos) {
|
private fun toggleLit(state: BlockState, world: World, pos: BlockPos) {
|
||||||
world.setBlockState(pos, state.cycle(POWERED), 2)
|
world.setBlockState(pos, state.cycle(POWERED), 2)
|
||||||
getBlockEntity(world, pos)!!.redstoneStateChanged()
|
getBlockEntity(world, pos)!!.redstoneStateChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,66 +14,67 @@ import net.shadowfacts.phycon.util.RedstoneMode
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class RedstoneControllerBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER, pos, state),
|
class RedstoneControllerBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
ClientConfigurableDevice {
|
DeviceBlockEntity(PhyBlockEntities.REDSTONE_CONTROLLER, pos, state),
|
||||||
|
ClientConfigurableDevice {
|
||||||
|
|
||||||
var managedDevices = Array<IPAddress?>(5) { null }
|
var managedDevices = Array<IPAddress?>(5) { null }
|
||||||
var redstoneMode = RedstoneMode.HIGH
|
var redstoneMode = RedstoneMode.HIGH
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
redstoneStateChanged()
|
redstoneStateChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var redstonePowered = false
|
private var redstonePowered = false
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
override fun handle(packet: Packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun redstoneStateChanged() {
|
fun redstoneStateChanged() {
|
||||||
if (world == null || world!!.isClient) return
|
if (world == null || world!!.isClient) return
|
||||||
|
|
||||||
val oldPowered = redstonePowered
|
val oldPowered = redstonePowered
|
||||||
redstonePowered = cachedState[RedstoneControllerBlock.POWERED]
|
redstonePowered = cachedState[RedstoneControllerBlock.POWERED]
|
||||||
|
|
||||||
val mode: RemoteActivationPacket.Mode? = when (redstoneMode) {
|
val mode: RemoteActivationPacket.Mode? = when (redstoneMode) {
|
||||||
RedstoneMode.TOGGLE -> if (oldPowered != redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
|
RedstoneMode.TOGGLE -> if (oldPowered != redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
|
||||||
RedstoneMode.RISING_EDGE -> if (!oldPowered && redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
|
RedstoneMode.RISING_EDGE -> if (!oldPowered && redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
|
||||||
RedstoneMode.FALLING_EDGE -> if (oldPowered && !redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
|
RedstoneMode.FALLING_EDGE -> if (oldPowered && !redstonePowered) RemoteActivationPacket.Mode.SINGLE else null
|
||||||
RedstoneMode.HIGH -> if (redstonePowered) RemoteActivationPacket.Mode.ENABLE else RemoteActivationPacket.Mode.DISABLE
|
RedstoneMode.HIGH -> if (redstonePowered) RemoteActivationPacket.Mode.ENABLE else RemoteActivationPacket.Mode.DISABLE
|
||||||
RedstoneMode.LOW -> if (redstonePowered) RemoteActivationPacket.Mode.DISABLE else RemoteActivationPacket.Mode.ENABLE
|
RedstoneMode.LOW -> if (redstonePowered) RemoteActivationPacket.Mode.DISABLE else RemoteActivationPacket.Mode.ENABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode != null) {
|
if (mode != null) {
|
||||||
sendActivatePacket(mode)
|
sendActivatePacket(mode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendActivatePacket(mode: RemoteActivationPacket.Mode) {
|
private fun sendActivatePacket(mode: RemoteActivationPacket.Mode) {
|
||||||
for (ip in managedDevices) {
|
for (ip in managedDevices) {
|
||||||
if (ip == null) continue
|
if (ip == null) continue
|
||||||
sendPacket(RemoteActivationPacket(mode, ipAddress, ip))
|
sendPacket(RemoteActivationPacket(mode, ipAddress, ip))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
super.toCommonTag(tag)
|
super.toCommonTag(tag)
|
||||||
writeDeviceConfiguration(tag)
|
writeDeviceConfiguration(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
super.fromCommonTag(tag)
|
||||||
loadDeviceConfiguration(tag)
|
loadDeviceConfiguration(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
||||||
tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address })
|
tag.putIntArray("ManagedDevices", managedDevices.mapNotNull { it?.address })
|
||||||
tag.putString("RedstoneMode", redstoneMode.name)
|
tag.putString("RedstoneMode", redstoneMode.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
||||||
val addresses = tag.getIntArray("ManagedDevices")
|
val addresses = tag.getIntArray("ManagedDevices")
|
||||||
managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray()
|
managedDevices = (0..4).map { if (it >= addresses.size) null else IPAddress(addresses[it]) }.toTypedArray()
|
||||||
redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode"))
|
redstoneMode = RedstoneMode.valueOf(tag.getString("RedstoneMode"))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,65 +24,82 @@ import net.shadowfacts.phycon.block.FaceDeviceBlock
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class RedstoneEmitterBlock: FaceDeviceBlock<RedstoneEmitterBlockEntity>(
|
class RedstoneEmitterBlock : FaceDeviceBlock<RedstoneEmitterBlockEntity>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter")
|
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter")
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: don't just copy the redstone controller
|
// todo: don't just copy the redstone controller
|
||||||
override val faceThickness = 3.0
|
override val faceThickness = 3.0
|
||||||
override val faceShapes = mapOf(
|
override val faceShapes = mapOf(
|
||||||
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 3.0, 16.0),
|
Direction.DOWN to createCuboidShape(0.0, 0.0, 0.0, 16.0, 3.0, 16.0),
|
||||||
Direction.UP to createCuboidShape(0.0, 13.0, 0.0, 16.0, 16.0, 16.0),
|
Direction.UP to createCuboidShape(0.0, 13.0, 0.0, 16.0, 16.0, 16.0),
|
||||||
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 3.0),
|
Direction.NORTH to createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 3.0),
|
||||||
Direction.SOUTH to createCuboidShape(0.0, 0.0, 13.0, 16.0, 16.0, 16.0),
|
Direction.SOUTH to createCuboidShape(0.0, 0.0, 13.0, 16.0, 16.0, 16.0),
|
||||||
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 3.0, 16.0, 16.0),
|
Direction.WEST to createCuboidShape(0.0, 0.0, 0.0, 3.0, 16.0, 16.0),
|
||||||
Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
Direction.EAST to createCuboidShape(13.0, 0.0, 0.0, 16.0, 16.0, 16.0)
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = RedstoneEmitterBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = RedstoneEmitterBlockEntity(pos, state)
|
||||||
|
|
||||||
override fun emitsRedstonePower(state: BlockState): Boolean {
|
override fun emitsRedstonePower(state: BlockState): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStrongRedstonePower(state: BlockState, world: BlockView, pos: BlockPos, receivingSide: Direction): Int {
|
override fun getStrongRedstonePower(
|
||||||
return if (receivingSide.opposite == state[FACING]) {
|
state: BlockState,
|
||||||
getBlockEntity(world, pos)!!.cachedEmittedPower
|
world: BlockView,
|
||||||
} else {
|
pos: BlockPos,
|
||||||
0
|
receivingSide: Direction
|
||||||
}
|
): Int {
|
||||||
}
|
return if (receivingSide.opposite == state[FACING]) {
|
||||||
|
getBlockEntity(world, pos)!!.cachedEmittedPower
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getWeakRedstonePower(state: BlockState, world: BlockView, pos: BlockPos, receivingSide: Direction): Int {
|
override fun getWeakRedstonePower(
|
||||||
return getStrongRedstonePower(state, world, pos, receivingSide)
|
state: BlockState,
|
||||||
}
|
world: BlockView,
|
||||||
|
pos: BlockPos,
|
||||||
|
receivingSide: Direction
|
||||||
|
): Int {
|
||||||
|
return getStrongRedstonePower(state, world, pos, receivingSide)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, result: BlockHitResult): ActionResult {
|
override fun onUse(
|
||||||
if (!world.isClient) {
|
state: BlockState,
|
||||||
val be = getBlockEntity(world, pos)!!
|
world: World,
|
||||||
|
pos: BlockPos,
|
||||||
|
player: PlayerEntity,
|
||||||
|
hand: Hand,
|
||||||
|
result: BlockHitResult
|
||||||
|
): ActionResult {
|
||||||
|
if (!world.isClient) {
|
||||||
|
val be = getBlockEntity(world, pos)!!
|
||||||
|
|
||||||
be.markUpdate()
|
be.markUpdate()
|
||||||
|
|
||||||
val factory = object: ExtendedScreenHandlerFactory {
|
val factory = object : ExtendedScreenHandlerFactory {
|
||||||
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
|
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler {
|
||||||
return RedstoneEmitterScreenHandler(syncId, playerInv, be)
|
return RedstoneEmitterScreenHandler(syncId, playerInv, be)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDisplayName() = this@RedstoneEmitterBlock.name
|
override fun getDisplayName() = this@RedstoneEmitterBlock.name
|
||||||
|
|
||||||
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
|
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
|
||||||
buf.writeBlockPos(be.pos)
|
buf.writeBlockPos(be.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.openHandledScreen(factory)
|
player.openHandledScreen(factory)
|
||||||
}
|
}
|
||||||
return ActionResult.SUCCESS
|
return ActionResult.SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package net.shadowfacts.phycon.block.redstone_emitter
|
package net.shadowfacts.phycon.block.redstone_emitter
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.item.GroupedItemInvView
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
|
import net.fabricmc.fabric.api.transfer.v1.storage.Storage
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.nbt.NbtCompound
|
import net.minecraft.nbt.NbtCompound
|
||||||
|
@ -12,124 +13,127 @@ import net.shadowfacts.phycon.block.DeviceBlockEntity
|
||||||
import net.shadowfacts.phycon.block.FaceDeviceBlock
|
import net.shadowfacts.phycon.block.FaceDeviceBlock
|
||||||
import net.shadowfacts.phycon.init.PhyBlockEntities
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
import net.shadowfacts.phycon.packet.DeviceRemovedPacket
|
import net.shadowfacts.phycon.packet.DeviceRemovedPacket
|
||||||
import net.shadowfacts.phycon.packet.ReadGroupedInventoryPacket
|
import net.shadowfacts.phycon.packet.ReadItemStoragePacket
|
||||||
import net.shadowfacts.phycon.packet.RequestInventoryPacket
|
import net.shadowfacts.phycon.packet.RequestInventoryPacket
|
||||||
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
import net.shadowfacts.phycon.util.ClientConfigurableDevice
|
||||||
import net.shadowfacts.phycon.util.GhostInv
|
import net.shadowfacts.phycon.util.GhostInv
|
||||||
import kotlin.math.round
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState): DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER, pos, state),
|
class RedstoneEmitterBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
ClientConfigurableDevice,
|
DeviceBlockEntity(PhyBlockEntities.REDSTONE_EMITTER, pos, state),
|
||||||
GhostInv {
|
ClientConfigurableDevice,
|
||||||
|
GhostInv {
|
||||||
|
|
||||||
private val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
private val inventoryCache = mutableMapOf<IPAddress, Storage<ItemVariant>>()
|
||||||
var cachedEmittedPower: Int = 0
|
var cachedEmittedPower: Int = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var stackToMonitor: ItemStack = ItemStack.EMPTY
|
var stackToMonitor: ItemStack = ItemStack.EMPTY
|
||||||
override var ghostSlotStack: ItemStack
|
override var ghostSlotStack: ItemStack
|
||||||
get() = stackToMonitor
|
get() = stackToMonitor
|
||||||
set(value) { stackToMonitor = value }
|
set(value) {
|
||||||
var maxAmount = 64
|
stackToMonitor = value
|
||||||
var mode = Mode.ANALOG
|
}
|
||||||
set(value) {
|
var maxAmount = 64
|
||||||
field = value
|
var mode = Mode.ANALOG
|
||||||
recalculateRedstone()
|
set(value) {
|
||||||
}
|
field = value
|
||||||
|
recalculateRedstone()
|
||||||
|
}
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
override fun handle(packet: Packet) {
|
||||||
when (packet) {
|
when (packet) {
|
||||||
is ReadGroupedInventoryPacket -> handleReadInventory(packet)
|
is ReadItemStoragePacket -> handleReadItemStorage(packet)
|
||||||
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) {
|
private fun handleReadItemStorage(packet: ReadItemStoragePacket) {
|
||||||
inventoryCache[packet.source] = packet.inventory
|
inventoryCache[packet.source] = packet.inventory
|
||||||
recalculateRedstone()
|
recalculateRedstone()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
||||||
inventoryCache.remove(packet.source)
|
inventoryCache.remove(packet.source)
|
||||||
recalculateRedstone()
|
recalculateRedstone()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick() {
|
||||||
super.tick()
|
super.tick()
|
||||||
|
|
||||||
if (!world!!.isClient && counter % 20 == 0L) {
|
if (!world!!.isClient && counter % 20 == 0L) {
|
||||||
if (counter % 80 == 0L) {
|
if (counter % 80 == 0L) {
|
||||||
updateInventories()
|
updateInventories()
|
||||||
} else if (counter % 20 == 0L) {
|
}
|
||||||
recalculateRedstone()
|
recalculateRedstone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateInventories() {
|
private fun updateInventories() {
|
||||||
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.GROUPED, ipAddress))
|
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.GROUPED, ipAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recalculateRedstone() {
|
private fun recalculateRedstone() {
|
||||||
if (world == null || world!!.isClient) return
|
if (world == null || world!!.isClient) return
|
||||||
|
|
||||||
if (stackToMonitor.isEmpty) {
|
if (stackToMonitor.isEmpty) {
|
||||||
cachedEmittedPower = 0
|
cachedEmittedPower = 0
|
||||||
updateWorld()
|
updateWorld()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val networkAmount = inventoryCache.values.fold(0) { acc, inv ->
|
val variant = ItemVariant.of(stackToMonitor)
|
||||||
acc + inv.getAmount(stackToMonitor)
|
val networkAmount = inventoryCache.values.fold(0) { acc, inv ->
|
||||||
}
|
acc + inv.simulateExtract(variant, Long.MAX_VALUE, null).toInt()
|
||||||
cachedEmittedPower =
|
}
|
||||||
when (mode) {
|
cachedEmittedPower =
|
||||||
Mode.ANALOG -> if (networkAmount == 0) {
|
when (mode) {
|
||||||
0
|
Mode.ANALOG -> if (networkAmount == 0) {
|
||||||
} else {
|
0
|
||||||
1 + round(networkAmount / maxAmount.toDouble() * 14).toInt()
|
} else {
|
||||||
}
|
1 + (networkAmount / maxAmount.toDouble() * 14).toInt()
|
||||||
Mode.DIGITAL -> if (networkAmount >= maxAmount) 15 else 0
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateWorld()
|
Mode.DIGITAL -> if (networkAmount >= maxAmount) 15 else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateWorld() {
|
updateWorld()
|
||||||
world!!.updateNeighborsAlways(pos, cachedState.block)
|
}
|
||||||
world!!.updateNeighborsAlways(pos.offset(cachedState[FaceDeviceBlock.FACING]), cachedState.block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
private fun updateWorld() {
|
||||||
super.toCommonTag(tag)
|
world!!.updateNeighborsAlways(pos, cachedState.block)
|
||||||
tag.putInt("CachedEmittedPower", cachedEmittedPower)
|
world!!.updateNeighborsAlways(pos.offset(cachedState[FaceDeviceBlock.FACING]), cachedState.block)
|
||||||
tag.put("StackToMonitor", stackToMonitor.writeNbt(NbtCompound()))
|
}
|
||||||
writeDeviceConfiguration(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
super.toCommonTag(tag)
|
||||||
cachedEmittedPower = tag.getInt("CachedEmittedPower")
|
tag.putInt("CachedEmittedPower", cachedEmittedPower)
|
||||||
stackToMonitor = ItemStack.fromNbt(tag.getCompound("StackToMonitor"))
|
tag.put("StackToMonitor", stackToMonitor.writeNbt(NbtCompound()))
|
||||||
loadDeviceConfiguration(tag)
|
writeDeviceConfiguration(tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
tag.putInt("MaxAmount", maxAmount)
|
super.fromCommonTag(tag)
|
||||||
tag.putString("Mode", mode.name)
|
cachedEmittedPower = tag.getInt("CachedEmittedPower")
|
||||||
}
|
stackToMonitor = ItemStack.fromNbt(tag.getCompound("StackToMonitor"))
|
||||||
|
loadDeviceConfiguration(tag)
|
||||||
|
}
|
||||||
|
|
||||||
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
override fun writeDeviceConfiguration(tag: NbtCompound) {
|
||||||
maxAmount = tag.getInt("MaxAmount")
|
tag.putInt("MaxAmount", maxAmount)
|
||||||
mode = Mode.valueOf(tag.getString("Mode"))
|
tag.putString("Mode", mode.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Mode {
|
override fun loadDeviceConfiguration(tag: NbtCompound) {
|
||||||
ANALOG, DIGITAL;
|
maxAmount = tag.getInt("MaxAmount")
|
||||||
|
mode = Mode.valueOf(tag.getString("Mode"))
|
||||||
|
}
|
||||||
|
|
||||||
val friendlyName = TranslatableText("gui.phycon.redstone_emitter_mode.${name.lowercase()}")
|
enum class Mode {
|
||||||
}
|
ANALOG, DIGITAL;
|
||||||
|
|
||||||
|
val friendlyName = TranslatableText("gui.phycon.redstone_emitter_mode.${name.lowercase()}")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,129 +27,134 @@ import kotlin.math.ceil
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class RedstoneEmitterScreen(
|
class RedstoneEmitterScreen(
|
||||||
handler: RedstoneEmitterScreenHandler,
|
handler: RedstoneEmitterScreenHandler,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
title: Text
|
title: Text
|
||||||
): CacaoHandledScreen<RedstoneEmitterScreenHandler>(
|
) : CacaoHandledScreen<RedstoneEmitterScreenHandler>(
|
||||||
handler,
|
handler,
|
||||||
playerInv,
|
playerInv,
|
||||||
title
|
title
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/redstone_emitter.png")
|
val BACKGROUND = Identifier(PhysicalConnectivity.MODID, "textures/gui/redstone_emitter.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
backgroundWidth = 176
|
backgroundWidth = 176
|
||||||
backgroundHeight = 166
|
backgroundHeight = 166
|
||||||
|
|
||||||
addWindow(ScreenHandlerWindow(handler, ViewController(handler.emitter)))
|
addWindow(ScreenHandlerWindow(handler, ViewController(handler.emitter)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||||
super.drawBackground(matrixStack, delta, mouseX, mouseY)
|
super.drawBackground(matrixStack, delta, mouseX, mouseY)
|
||||||
|
|
||||||
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
||||||
RenderSystem.setShaderTexture(0, BACKGROUND)
|
RenderSystem.setShaderTexture(0, BACKGROUND)
|
||||||
val x = (width - backgroundWidth) / 2
|
val x = (width - backgroundWidth) / 2
|
||||||
val y = (height - backgroundHeight) / 2
|
val y = (height - backgroundHeight) / 2
|
||||||
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
class ViewController(
|
class ViewController(
|
||||||
private val emitter: RedstoneEmitterBlockEntity,
|
private val emitter: RedstoneEmitterBlockEntity,
|
||||||
): net.shadowfacts.cacao.viewcontroller.ViewController() {
|
) : net.shadowfacts.cacao.viewcontroller.ViewController() {
|
||||||
|
|
||||||
lateinit var halfLabel: Label
|
lateinit var halfLabel: Label
|
||||||
lateinit var fullLabel: Label
|
lateinit var fullLabel: Label
|
||||||
|
|
||||||
override fun viewDidLoad() {
|
override fun viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
val title = Label(PhyBlocks.REDSTONE_EMITTER.name)
|
val title = Label(PhyBlocks.REDSTONE_EMITTER.name)
|
||||||
title.textColor = Color.TEXT
|
title.textColor = Color.TEXT
|
||||||
view.addSubview(title)
|
view.addSubview(title)
|
||||||
|
|
||||||
val inv = Label(MinecraftClient.getInstance().player!!.inventory.displayName)
|
val inv = Label(MinecraftClient.getInstance().player!!.inventory.displayName)
|
||||||
inv.textColor = Color.TEXT
|
inv.textColor = Color.TEXT
|
||||||
view.addSubview(inv)
|
view.addSubview(inv)
|
||||||
|
|
||||||
val field = NumberField(emitter.maxAmount) {
|
val field = NumberField(emitter.maxAmount) {
|
||||||
if (it.number != null) {
|
if (it.number != null) {
|
||||||
emitter.maxAmount = it.number!!
|
emitter.maxAmount = it.number!!
|
||||||
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureDevice(emitter))
|
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2SConfigureDevice(emitter))
|
||||||
}
|
}
|
||||||
updateLabelTexts()
|
updateLabelTexts()
|
||||||
}
|
}
|
||||||
field.validator = { it >= 0 }
|
field.validator = { it >= 0 }
|
||||||
field.drawBackground = false
|
field.drawBackground = false
|
||||||
view.addSubview(field)
|
view.addSubview(field)
|
||||||
|
|
||||||
val hStack = StackView(Axis.HORIZONTAL, StackView.Distribution.FILL, spacing = 2.0)
|
val hStack = StackView(Axis.HORIZONTAL, StackView.Distribution.FILL, spacing = 2.0)
|
||||||
view.addSubview(hStack)
|
view.addSubview(hStack)
|
||||||
|
|
||||||
val zeroStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL))
|
val zeroStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL))
|
||||||
zeroStack.addArrangedSubview(Label(TranslatableText("gui.phycon.emitter.count", 0), textAlignment = Label.TextAlignment.CENTER)).apply {
|
zeroStack.addArrangedSubview(
|
||||||
textColor = Color.TEXT
|
Label(
|
||||||
}
|
TranslatableText("gui.phycon.emitter.count", 0),
|
||||||
zeroStack.addArrangedSubview(View())
|
textAlignment = Label.TextAlignment.CENTER
|
||||||
zeroStack.addArrangedSubview(Label("0", textAlignment = Label.TextAlignment.CENTER)).apply {
|
)
|
||||||
textColor = Color.RED
|
).apply {
|
||||||
}
|
textColor = Color.TEXT
|
||||||
|
}
|
||||||
|
zeroStack.addArrangedSubview(View())
|
||||||
|
zeroStack.addArrangedSubview(Label("0", textAlignment = Label.TextAlignment.CENTER)).apply {
|
||||||
|
textColor = Color.RED
|
||||||
|
}
|
||||||
|
|
||||||
val halfStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL))
|
val halfStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL))
|
||||||
halfLabel = halfStack.addArrangedSubview(Label("half", textAlignment = Label.TextAlignment.CENTER)).apply {
|
halfLabel = halfStack.addArrangedSubview(Label("half", textAlignment = Label.TextAlignment.CENTER)).apply {
|
||||||
textColor = Color.TEXT
|
textColor = Color.TEXT
|
||||||
}
|
}
|
||||||
halfStack.addArrangedSubview(View())
|
halfStack.addArrangedSubview(View())
|
||||||
halfStack.addArrangedSubview(Label("8", textAlignment = Label.TextAlignment.CENTER)).apply {
|
halfStack.addArrangedSubview(Label("8", textAlignment = Label.TextAlignment.CENTER)).apply {
|
||||||
textColor = Color.RED
|
textColor = Color.RED
|
||||||
}
|
}
|
||||||
|
|
||||||
val fullStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL))
|
val fullStack = hStack.addArrangedSubview(StackView(Axis.VERTICAL))
|
||||||
fullLabel = fullStack.addArrangedSubview(Label("full", textAlignment = Label.TextAlignment.CENTER)).apply {
|
fullLabel = fullStack.addArrangedSubview(Label("full", textAlignment = Label.TextAlignment.CENTER)).apply {
|
||||||
textColor = Color.TEXT
|
textColor = Color.TEXT
|
||||||
}
|
}
|
||||||
fullStack.addArrangedSubview(View())
|
fullStack.addArrangedSubview(View())
|
||||||
fullStack.addArrangedSubview(Label("15", textAlignment = Label.TextAlignment.CENTER)).apply {
|
fullStack.addArrangedSubview(Label("15", textAlignment = Label.TextAlignment.CENTER)).apply {
|
||||||
textColor = Color.RED
|
textColor = Color.RED
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLabelTexts()
|
updateLabelTexts()
|
||||||
|
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
val minX = Variable("minX")
|
val minX = Variable("minX")
|
||||||
val minY = Variable("minY")
|
val minY = Variable("minY")
|
||||||
minX equalTo ((view.widthAnchor - 176) / 2)
|
minX equalTo ((view.widthAnchor - 176) / 2)
|
||||||
minY equalTo ((view.heightAnchor - 166) / 2)
|
minY equalTo ((view.heightAnchor - 166) / 2)
|
||||||
|
|
||||||
title.leftAnchor equalTo (minX + 8)
|
title.leftAnchor equalTo (minX + 8)
|
||||||
title.topAnchor equalTo (minY + 6)
|
title.topAnchor equalTo (minY + 6)
|
||||||
|
|
||||||
inv.leftAnchor equalTo (minX + 8)
|
inv.leftAnchor equalTo (minX + 8)
|
||||||
inv.topAnchor equalTo (minY + 72)
|
inv.topAnchor equalTo (minY + 72)
|
||||||
|
|
||||||
field.widthAnchor equalTo 82
|
field.widthAnchor equalTo 82
|
||||||
field.heightAnchor equalTo 11
|
field.heightAnchor equalTo 11
|
||||||
field.leftAnchor equalTo (minX + 57)
|
field.leftAnchor equalTo (minX + 57)
|
||||||
field.topAnchor equalTo (minY + 23)
|
field.topAnchor equalTo (minY + 23)
|
||||||
|
|
||||||
hStack.centerXAnchor equalTo view.centerXAnchor
|
hStack.centerXAnchor equalTo view.centerXAnchor
|
||||||
hStack.widthAnchor equalTo (176 - 4)
|
hStack.widthAnchor equalTo (176 - 4)
|
||||||
hStack.topAnchor equalTo (field.bottomAnchor + 8)
|
hStack.topAnchor equalTo (field.bottomAnchor + 8)
|
||||||
hStack.bottomAnchor equalTo inv.topAnchor
|
hStack.bottomAnchor equalTo inv.topAnchor
|
||||||
|
|
||||||
zeroStack.widthAnchor equalTo halfStack.widthAnchor
|
zeroStack.widthAnchor equalTo halfStack.widthAnchor
|
||||||
halfStack.widthAnchor equalTo fullStack.widthAnchor
|
halfStack.widthAnchor equalTo fullStack.widthAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateLabelTexts() {
|
private fun updateLabelTexts() {
|
||||||
halfLabel.text = TranslatableText("gui.phycon.emitter.count", ceil(emitter.maxAmount / 2.0).toInt())
|
halfLabel.text = TranslatableText("gui.phycon.emitter.count", ceil(emitter.maxAmount / 2.0).toInt())
|
||||||
fullLabel.text = TranslatableText("gui.phycon.emitter.count", emitter.maxAmount)
|
fullLabel.text = TranslatableText("gui.phycon.emitter.count", emitter.maxAmount)
|
||||||
window!!.layout()
|
window!!.layout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,59 +18,59 @@ import net.shadowfacts.phycon.util.copyWithCount
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class RedstoneEmitterScreenHandler(
|
class RedstoneEmitterScreenHandler(
|
||||||
syncId: Int,
|
syncId: Int,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
val emitter: RedstoneEmitterBlockEntity,
|
val emitter: RedstoneEmitterBlockEntity,
|
||||||
): ScreenHandler(PhyScreens.REDSTONE_EMITTER, syncId) {
|
) : ScreenHandler(PhyScreens.REDSTONE_EMITTER, syncId) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter")
|
val ID = Identifier(PhysicalConnectivity.MODID, "redstone_emitter")
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
|
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
|
||||||
this(
|
this(
|
||||||
syncId,
|
syncId,
|
||||||
playerInv,
|
playerInv,
|
||||||
PhyBlocks.REDSTONE_EMITTER.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
PhyBlocks.REDSTONE_EMITTER.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// fake slot
|
// fake slot
|
||||||
addSlot(GhostSlot(emitter, 31, 20))
|
addSlot(GhostSlot(emitter, 31, 20))
|
||||||
|
|
||||||
// player inv
|
// player inv
|
||||||
for (y in 0 until 3) {
|
for (y in 0 until 3) {
|
||||||
for (x in 0 until 9) {
|
for (x in 0 until 9) {
|
||||||
addSlot(Slot(playerInv, x + y * 9 + 9, 8 + x * 18, 84 + y * 18))
|
addSlot(Slot(playerInv, x + y * 9 + 9, 8 + x * 18, 84 + y * 18))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hotbar
|
// hotbar
|
||||||
for (x in 0 until 9) {
|
for (x in 0 until 9) {
|
||||||
addSlot(Slot(playerInv, x, 8 + x * 18, 142))
|
addSlot(Slot(playerInv, x, 8 + x * 18, 142))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canUse(player: PlayerEntity): Boolean {
|
override fun canUse(player: PlayerEntity): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSlotClick(slotId: Int, clickData: Int, slotActionType: SlotActionType, player: PlayerEntity) {
|
override fun onSlotClick(slotId: Int, clickData: Int, slotActionType: SlotActionType, player: PlayerEntity) {
|
||||||
// fake slot
|
// fake slot
|
||||||
if (slotId == 0) {
|
if (slotId == 0) {
|
||||||
if (cursorStack.isEmpty) {
|
if (cursorStack.isEmpty) {
|
||||||
emitter.stackToMonitor = ItemStack.EMPTY
|
emitter.stackToMonitor = ItemStack.EMPTY
|
||||||
} else {
|
} else {
|
||||||
emitter.stackToMonitor = cursorStack.copyWithCount(1)
|
emitter.stackToMonitor = cursorStack.copyWithCount(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.onSlotClick(slotId, clickData, slotActionType, player)
|
super.onSlotClick(slotId, clickData, slotActionType, player)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
||||||
val slot = slots[slotId]
|
val slot = slots[slotId]
|
||||||
emitter.stackToMonitor = slot.stack.copyWithCount(1)
|
emitter.stackToMonitor = slot.stack.copyWithCount(1)
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.AttributeList
|
|
||||||
import alexiil.mc.lib.attributes.AttributeProvider
|
|
||||||
import net.minecraft.block.Block
|
import net.minecraft.block.Block
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.Material
|
import net.minecraft.block.Material
|
||||||
|
@ -12,15 +10,11 @@ import net.minecraft.state.StateManager
|
||||||
import net.minecraft.state.property.Properties
|
import net.minecraft.state.property.Properties
|
||||||
import net.minecraft.util.ActionResult
|
import net.minecraft.util.ActionResult
|
||||||
import net.minecraft.util.Hand
|
import net.minecraft.util.Hand
|
||||||
import net.minecraft.util.Identifier
|
|
||||||
import net.minecraft.util.ItemScatterer
|
|
||||||
import net.minecraft.util.hit.BlockHitResult
|
import net.minecraft.util.hit.BlockHitResult
|
||||||
import net.minecraft.util.math.BlockPos
|
import net.minecraft.util.math.BlockPos
|
||||||
import net.minecraft.util.math.Direction
|
import net.minecraft.util.math.Direction
|
||||||
import net.minecraft.world.BlockView
|
|
||||||
import net.minecraft.world.World
|
import net.minecraft.world.World
|
||||||
import net.minecraft.world.WorldAccess
|
import net.minecraft.world.WorldAccess
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
|
||||||
import net.shadowfacts.phycon.api.NetworkComponentBlock
|
import net.shadowfacts.phycon.api.NetworkComponentBlock
|
||||||
import net.shadowfacts.phycon.block.DeviceBlock
|
import net.shadowfacts.phycon.block.DeviceBlock
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
@ -28,48 +22,50 @@ import java.util.EnumSet
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class AbstractTerminalBlock<T: AbstractTerminalBlockEntity>: DeviceBlock<T>(
|
abstract class AbstractTerminalBlock<T : AbstractTerminalBlockEntity> : DeviceBlock<T>(
|
||||||
Settings.of(Material.METAL)
|
Settings.of(Material.METAL)
|
||||||
.strength(1.5f)
|
.strength(1.5f)
|
||||||
.sounds(BlockSoundGroup.METAL)
|
.sounds(BlockSoundGroup.METAL)
|
||||||
),
|
),
|
||||||
NetworkComponentBlock,
|
NetworkComponentBlock {
|
||||||
AttributeProvider {
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val FACING = Properties.FACING
|
val FACING = Properties.FACING
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
override fun getNetworkConnectedSides(state: BlockState, world: WorldAccess, pos: BlockPos): Collection<Direction> {
|
||||||
val set = EnumSet.allOf(Direction::class.java)
|
val set = EnumSet.allOf(Direction::class.java)
|
||||||
set.remove(state[FACING])
|
set.remove(state[FACING])
|
||||||
return set
|
return set
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
override fun appendProperties(builder: StateManager.Builder<Block, BlockState>) {
|
||||||
super.appendProperties(builder)
|
super.appendProperties(builder)
|
||||||
builder.add(FACING)
|
builder.add(FACING)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
override fun getPlacementState(context: ItemPlacementContext): BlockState {
|
||||||
return defaultState.with(FACING, context.playerLookDirection.opposite)
|
return defaultState.with(FACING, context.playerLookDirection.opposite)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUse(state: BlockState, world: World, pos: BlockPos, player: PlayerEntity, hand: Hand, hitResult: BlockHitResult): ActionResult {
|
override fun onUse(
|
||||||
getBlockEntity(world, pos)!!.onActivate(player)
|
state: BlockState,
|
||||||
return ActionResult.SUCCESS
|
world: World,
|
||||||
}
|
pos: BlockPos,
|
||||||
|
player: PlayerEntity,
|
||||||
|
hand: Hand,
|
||||||
|
hitResult: BlockHitResult
|
||||||
|
): ActionResult {
|
||||||
|
getBlockEntity(world, pos)!!.onActivate(player)
|
||||||
|
return ActionResult.SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos, newState: BlockState, moved: Boolean) {
|
override fun onStateReplaced(state: BlockState, world: World, pos: BlockPos, newState: BlockState, moved: Boolean) {
|
||||||
if (!state.isOf(newState.block)) {
|
if (!state.isOf(newState.block)) {
|
||||||
val be = getBlockEntity(world, pos)!!
|
val be = getBlockEntity(world, pos)!!
|
||||||
be.dropItems()
|
be.dropItems()
|
||||||
|
|
||||||
super.onStateReplaced(state, world, pos, newState, moved)
|
super.onStateReplaced(state, world, pos, newState, moved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addAllAttributes(world: World, pos: BlockPos, state: BlockState, to: AttributeList<*>) {
|
|
||||||
to.offer(getBlockEntity(world, pos))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.item.GroupedItemInvView
|
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
import net.fabricmc.fabric.api.transfer.v1.storage.Storage
|
||||||
|
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.block.entity.BlockEntityType
|
import net.minecraft.block.entity.BlockEntityType
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
|
@ -18,9 +19,9 @@ import net.shadowfacts.phycon.api.packet.Packet
|
||||||
import net.shadowfacts.phycon.api.util.IPAddress
|
import net.shadowfacts.phycon.api.util.IPAddress
|
||||||
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
import net.shadowfacts.phycon.block.DeviceBlockEntity
|
||||||
import net.shadowfacts.phycon.component.*
|
import net.shadowfacts.phycon.component.*
|
||||||
import net.shadowfacts.phycon.frame.NetworkSplitFrame
|
|
||||||
import net.shadowfacts.phycon.packet.*
|
import net.shadowfacts.phycon.packet.*
|
||||||
import net.shadowfacts.phycon.util.NetworkUtil
|
import net.shadowfacts.phycon.util.NetworkUtil
|
||||||
|
import net.shadowfacts.phycon.util.equalsIgnoringAmount
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.IntBinaryOperator
|
import java.util.function.IntBinaryOperator
|
||||||
|
@ -30,254 +31,258 @@ import kotlin.properties.Delegates
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState): DeviceBlockEntity(type, pos, state),
|
abstract class AbstractTerminalBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state: BlockState) :
|
||||||
InventoryChangedListener,
|
DeviceBlockEntity(type, pos, state),
|
||||||
ItemStackPacketHandler,
|
InventoryChangedListener,
|
||||||
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
|
ItemStackPacketHandler,
|
||||||
|
NetworkStackDispatcher<AbstractTerminalBlockEntity.PendingInsertion> {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
// the locate/insertion timeouts are only 1 tick because that's long enough to hear from every device on the network
|
// the locate/insertion timeouts are only 1 tick because that's long enough to hear from every device on the network
|
||||||
// in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable
|
// in a degraded state (when there's latency in the network), not handling interface priorities correctly is acceptable
|
||||||
val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks
|
val LOCATE_REQUEST_TIMEOUT: Long = 1 // ticks
|
||||||
val INSERTION_TIMEOUT: Long = 1 // ticks
|
val INSERTION_TIMEOUT: Long = 1 // ticks
|
||||||
val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks
|
val REQUEST_INVENTORY_TIMEOUT: Long = 1 // ticks
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val inventoryCache = mutableMapOf<IPAddress, GroupedItemInvView>()
|
protected val inventoryCache = mutableMapOf<IPAddress, Storage<ItemVariant>>()
|
||||||
val internalBuffer = TerminalBufferInventory(18)
|
val internalBuffer = TerminalBufferInventory(18)
|
||||||
|
|
||||||
protected val pendingRequests = LinkedList<StackLocateRequest>()
|
protected val pendingRequests = LinkedList<StackLocateRequest>()
|
||||||
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
override val pendingInsertions = mutableListOf<PendingInsertion>()
|
||||||
override val dispatchStackTimeout = INSERTION_TIMEOUT
|
override val dispatchStackTimeout = INSERTION_TIMEOUT
|
||||||
|
|
||||||
val cachedNetItems = ItemStackCollections.intMap()
|
val cachedNetItems = Object2IntOpenHashMap<ItemVariant>()
|
||||||
private var requestInventoryTimestamp: Long? = null
|
private var requestInventoryTimestamp: Long? = null
|
||||||
|
|
||||||
// todo: multiple players could have the terminal open simultaneously
|
// todo: multiple players could have the terminal open simultaneously
|
||||||
var netItemObserver: WeakReference<NetItemObserver>? = null
|
var netItemObserver: WeakReference<NetItemObserver>? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
internalBuffer.addListener(this)
|
internalBuffer.addListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findDestination(): Interface? {
|
override fun findDestination(): Interface? {
|
||||||
for (dir in Direction.values()) {
|
for (dir in Direction.values()) {
|
||||||
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
|
val itf = NetworkUtil.findConnectedInterface(world!!, pos, dir)
|
||||||
if (itf != null) {
|
if (itf != null) {
|
||||||
return itf
|
return itf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handleNetworkSplit() {
|
override fun handleNetworkSplit() {
|
||||||
super.handleNetworkSplit()
|
super.handleNetworkSplit()
|
||||||
inventoryCache.clear()
|
inventoryCache.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handle(packet: Packet) {
|
override fun handle(packet: Packet) {
|
||||||
when (packet) {
|
when (packet) {
|
||||||
is ReadGroupedInventoryPacket -> handleReadInventory(packet)
|
is ReadItemStoragePacket -> handleReadItemStorage(packet)
|
||||||
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
is DeviceRemovedPacket -> handleDeviceRemoved(packet)
|
||||||
is StackLocationPacket -> handleStackLocation(packet)
|
is StackLocationPacket -> handleStackLocation(packet)
|
||||||
is ItemStackPacket -> handleItemStack(packet)
|
is ItemStackPacket -> handleItemStack(packet)
|
||||||
is CapacityPacket -> handleCapacity(packet)
|
is CapacityPacket -> handleCapacity(packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReadInventory(packet: ReadGroupedInventoryPacket) {
|
private fun handleReadItemStorage(packet: ReadItemStoragePacket) {
|
||||||
inventoryCache[packet.source] = packet.inventory
|
inventoryCache[packet.source] = packet.inventory
|
||||||
updateAndSync()
|
updateAndSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
private fun handleDeviceRemoved(packet: DeviceRemovedPacket) {
|
||||||
inventoryCache.remove(packet.source)
|
inventoryCache.remove(packet.source)
|
||||||
updateAndSync()
|
updateAndSync()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStackLocation(packet: StackLocationPacket) {
|
private fun handleStackLocation(packet: StackLocationPacket) {
|
||||||
val request = pendingRequests.firstOrNull {
|
val request = pendingRequests.firstOrNull {
|
||||||
ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack)
|
it.stack.equalsIgnoringAmount(packet.stack)
|
||||||
}
|
}
|
||||||
if (request != null) {
|
if (request != null) {
|
||||||
request.results.add(packet.amount to packet.stackProvider)
|
request.results.add(packet.amount to packet.stackProvider)
|
||||||
if (request.isFinishable(counter)) {
|
if (request.isFinishable(counter)) {
|
||||||
stackLocateRequestCompleted(request)
|
stackLocateRequestCompleted(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
val mode =
|
val mode =
|
||||||
if (packet.bounceCount > 0) {
|
if (packet.bounceCount > 0) {
|
||||||
// if this stack bounced from an inventory, that means we previously tried to send it to the network, so retry
|
// if this stack bounced from an inventory, that means we previously tried to send it to the network, so retry
|
||||||
TerminalBufferInventory.Mode.TO_NETWORK
|
TerminalBufferInventory.Mode.TO_NETWORK
|
||||||
} else {
|
} else {
|
||||||
TerminalBufferInventory.Mode.FROM_NETWORK
|
TerminalBufferInventory.Mode.FROM_NETWORK
|
||||||
}
|
}
|
||||||
|
|
||||||
val remaining = internalBuffer.insert(packet.stack, mode)
|
val remaining = internalBuffer.insert(packet.stack, mode)
|
||||||
|
|
||||||
// this happens outside the normal update loop because by receiving the item stack packet
|
// this happens outside the normal update loop because by receiving the item stack packet
|
||||||
// we "know" how much the count in the source inventory has changed
|
// we "know" how much the count in the source inventory has changed
|
||||||
updateAndSync()
|
updateAndSync()
|
||||||
|
|
||||||
return remaining
|
return remaining
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun updateAndSync() {
|
protected fun updateAndSync() {
|
||||||
updateNetItems()
|
updateNetItems()
|
||||||
// syncs the internal buffer to the client
|
// syncs the internal buffer to the client
|
||||||
markUpdate()
|
markUpdate()
|
||||||
// syncs the open container (if any) to the client
|
// syncs the open container (if any) to the client
|
||||||
netItemObserver?.get()?.netItemsChanged()
|
netItemObserver?.get()?.netItemsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateNetItems() {
|
private fun updateNetItems() {
|
||||||
cachedNetItems.clear()
|
cachedNetItems.clear()
|
||||||
for (inventory in inventoryCache.values) {
|
for (inventory in inventoryCache.values) {
|
||||||
for (stack in inventory.storedStacks) {
|
val transaction = Transaction.openOuter()
|
||||||
val amount = inventory.getAmount(stack)
|
for (view in inventory.iterator(transaction)) {
|
||||||
cachedNetItems.mergeInt(stack, amount, IntBinaryOperator { a, b -> a + b })
|
val amount = view.amount.toInt()
|
||||||
}
|
cachedNetItems.mergeInt(view.resource, amount, IntBinaryOperator { a, b -> a + b })
|
||||||
}
|
}
|
||||||
}
|
transaction.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun beginInsertions() {
|
private fun beginInsertions() {
|
||||||
if (world!!.isClient) return
|
if (world!!.isClient) return
|
||||||
|
|
||||||
for (slot in 0 until internalBuffer.size()) {
|
for (slot in 0 until internalBuffer.size()) {
|
||||||
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
|
if (internalBuffer.getMode(slot) != TerminalBufferInventory.Mode.TO_NETWORK) continue
|
||||||
if (pendingInsertions.any { it.bufferSlot == slot }) continue
|
if (pendingInsertions.any { it.bufferSlot == slot }) continue
|
||||||
val stack = internalBuffer.getStack(slot)
|
val stack = internalBuffer.getStack(slot)
|
||||||
dispatchItemStack(stack) { insertion ->
|
dispatchItemStack(stack) { insertion ->
|
||||||
insertion.bufferSlot = slot
|
insertion.bufferSlot = slot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun finishPendingRequests() {
|
private fun finishPendingRequests() {
|
||||||
if (world!!.isClient) return
|
if (world!!.isClient) return
|
||||||
if (pendingRequests.isEmpty()) return
|
if (pendingRequests.isEmpty()) return
|
||||||
|
|
||||||
val finishable = pendingRequests.filter { it.isFinishable(counter) }
|
val finishable = pendingRequests.filter { it.isFinishable(counter) }
|
||||||
// stackLocateRequestCompleted removes the object from pendingRequests
|
// stackLocateRequestCompleted removes the object from pendingRequests
|
||||||
finishable.forEach(::stackLocateRequestCompleted)
|
finishable.forEach(::stackLocateRequestCompleted)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun tick() {
|
override fun tick() {
|
||||||
super.tick()
|
super.tick()
|
||||||
|
|
||||||
if (!world!!.isClient) {
|
if (!world!!.isClient) {
|
||||||
finishPendingRequests()
|
finishPendingRequests()
|
||||||
finishTimedOutPendingInsertions()
|
finishTimedOutPendingInsertions()
|
||||||
|
|
||||||
if (counter % 20 == 0L) {
|
if (counter % 20 == 0L) {
|
||||||
beginInsertions()
|
beginInsertions()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestInventoryTimestamp != null && (counter - requestInventoryTimestamp!!) >= REQUEST_INVENTORY_TIMEOUT) {
|
if (requestInventoryTimestamp != null && (counter - requestInventoryTimestamp!!) >= REQUEST_INVENTORY_TIMEOUT) {
|
||||||
updateAndSync()
|
updateAndSync()
|
||||||
requestInventoryTimestamp = null
|
requestInventoryTimestamp = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun onActivate(player: PlayerEntity) {
|
open fun onActivate(player: PlayerEntity) {
|
||||||
if (!world!!.isClient) {
|
if (!world!!.isClient) {
|
||||||
updateAndSync()
|
updateAndSync()
|
||||||
|
|
||||||
inventoryCache.clear()
|
inventoryCache.clear()
|
||||||
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.GROUPED, ipAddress))
|
sendPacket(RequestInventoryPacket(RequestInventoryPacket.Kind.GROUPED, ipAddress))
|
||||||
requestInventoryTimestamp = counter
|
requestInventoryTimestamp = counter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
|
fun requestItem(stack: ItemStack, amount: Int = stack.count) {
|
||||||
val request = StackLocateRequest(stack, amount, counter)
|
val request = StackLocateRequest(stack, amount, counter)
|
||||||
pendingRequests.add(request)
|
pendingRequests.add(request)
|
||||||
// locate packets are sent immediately instead of being added to a queue
|
// locate packets are sent immediately instead of being added to a queue
|
||||||
// otherwise the terminal UI feels sluggish and unresponsive
|
// otherwise the terminal UI feels sluggish and unresponsive
|
||||||
sendPacket(LocateStackPacket(stack, ipAddress))
|
sendPacket(LocateStackPacket(stack, ipAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
protected open fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
||||||
pendingRequests.remove(request)
|
pendingRequests.remove(request)
|
||||||
|
|
||||||
val sortedResults = request.results.toMutableList()
|
val sortedResults = request.results.toMutableList()
|
||||||
sortedResults.sortWith { a, b ->
|
sortedResults.sortWith { a, b ->
|
||||||
// sort results first by provider priority, and then by the count that it can provide
|
// sort results first by provider priority, and then by the count that it can provide
|
||||||
if (a.second.providerPriority == b.second.providerPriority) {
|
if (a.second.providerPriority == b.second.providerPriority) {
|
||||||
b.first - a.first
|
b.first - a.first
|
||||||
} else {
|
} else {
|
||||||
b.second.providerPriority - a.second.providerPriority
|
b.second.providerPriority - a.second.providerPriority
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var amountRequested = 0
|
var amountRequested = 0
|
||||||
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
|
while (amountRequested < request.amount && sortedResults.isNotEmpty()) {
|
||||||
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
|
val (sourceAmount, sourceInterface) = sortedResults.removeAt(0)
|
||||||
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
|
val amountToRequest = min(sourceAmount, request.amount - amountRequested)
|
||||||
amountRequested += amountToRequest
|
amountRequested += amountToRequest
|
||||||
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
|
sendPacket(ExtractStackPacket(request.stack, amountToRequest, ipAddress, sourceInterface.ipAddress))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
override fun createPendingInsertion(stack: ItemStack) = PendingInsertion(stack, counter)
|
||||||
|
|
||||||
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
override fun finishInsertion(insertion: PendingInsertion): ItemStack {
|
||||||
val remaining = super.finishInsertion(insertion)
|
val remaining = super.finishInsertion(insertion)
|
||||||
internalBuffer.setStack(insertion.bufferSlot, remaining)
|
internalBuffer.setStack(insertion.bufferSlot, remaining)
|
||||||
|
|
||||||
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
|
// as with extracting, we "know" the new amounts and so can update instantly without actually sending out packets
|
||||||
updateAndSync()
|
updateAndSync()
|
||||||
|
|
||||||
// don't start a second insertion, since remaining will be dispatched at the next beginInsertions
|
// don't start a second insertion, since remaining will be dispatched at the next beginInsertions
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onInventoryChanged(inv: Inventory) {
|
override fun onInventoryChanged(inv: Inventory) {
|
||||||
if (inv == internalBuffer && world != null && !world!!.isClient) {
|
if (inv == internalBuffer && world != null && !world!!.isClient) {
|
||||||
markUpdate()
|
markUpdate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun dropItems() {
|
open fun dropItems() {
|
||||||
ItemScatterer.spawn(world, pos, internalBuffer)
|
ItemScatterer.spawn(world, pos, internalBuffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
super.toCommonTag(tag)
|
super.toCommonTag(tag)
|
||||||
tag.put("InternalBuffer", internalBuffer.toTag())
|
tag.put("InternalBuffer", internalBuffer.toTag())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
super.fromCommonTag(tag)
|
||||||
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
|
internalBuffer.fromTag(tag.getCompound("InternalBuffer"))
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NetItemObserver {
|
interface NetItemObserver {
|
||||||
fun netItemsChanged()
|
fun netItemsChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
class PendingInsertion(stack: ItemStack, timestamp: Long): NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
class PendingInsertion(stack: ItemStack, timestamp: Long) :
|
||||||
var bufferSlot by Delegates.notNull<Int>()
|
NetworkStackDispatcher.PendingInsertion<PendingInsertion>(stack, timestamp) {
|
||||||
}
|
var bufferSlot by Delegates.notNull<Int>()
|
||||||
|
}
|
||||||
|
|
||||||
open class StackLocateRequest(
|
open class StackLocateRequest(
|
||||||
val stack: ItemStack,
|
val stack: ItemStack,
|
||||||
val amount: Int,
|
val amount: Int,
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
) {
|
) {
|
||||||
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
|
var results: MutableSet<Pair<Int, NetworkStackProvider>> = mutableSetOf()
|
||||||
|
|
||||||
val totalResultAmount: Int
|
val totalResultAmount: Int
|
||||||
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
|
get() = results.fold(0) { acc, (amount, _) -> acc + amount }
|
||||||
|
|
||||||
fun isFinishable(currentTimestamp: Long): Boolean {
|
fun isFinishable(currentTimestamp: Long): Boolean {
|
||||||
// we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to
|
// we can't check totalResultAmount >= amount because we need to hear back from all network stack providers to
|
||||||
// correctly sort by priority
|
// correctly sort by priority
|
||||||
return currentTimestamp - timestamp >= LOCATE_REQUEST_TIMEOUT
|
return currentTimestamp - timestamp >= LOCATE_REQUEST_TIMEOUT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,178 +27,197 @@ import kotlin.math.min
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class AbstractTerminalScreen<BE: AbstractTerminalBlockEntity, T: AbstractTerminalScreenHandler<BE>>(
|
abstract class AbstractTerminalScreen<BE : AbstractTerminalBlockEntity, T : AbstractTerminalScreenHandler<BE>>(
|
||||||
handler: T,
|
handler: T,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
title: Text,
|
title: Text,
|
||||||
val terminalBackgroundWidth: Int,
|
val terminalBackgroundWidth: Int,
|
||||||
val terminalBackgroundHeight: Int,
|
val terminalBackgroundHeight: Int,
|
||||||
): CacaoHandledScreen<T>(handler, playerInv, title) {
|
) : CacaoHandledScreen<T>(handler, playerInv, title) {
|
||||||
|
|
||||||
interface SearchQueryListener {
|
interface SearchQueryListener {
|
||||||
fun terminalSearchQueryChanged(newValue: String)
|
fun terminalSearchQueryChanged(newValue: String)
|
||||||
fun requestTerminalSearchFieldUpdate(): String?
|
fun requestTerminalSearchFieldUpdate(): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var searchQueryListener: SearchQueryListener? = null
|
var searchQueryListener: SearchQueryListener? = null
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract val backgroundTexture: Identifier
|
abstract val backgroundTexture: Identifier
|
||||||
|
|
||||||
val terminalVC: AbstractTerminalViewController<*, *, *>
|
val terminalVC: AbstractTerminalViewController<*, *, *>
|
||||||
var amountVC: TerminalRequestAmountViewController? = null
|
var amountVC: TerminalRequestAmountViewController? = null
|
||||||
|
|
||||||
private var prevSearchQuery = ""
|
private var prevSearchQuery = ""
|
||||||
var searchQuery = ""
|
var searchQuery = ""
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
if (prevSearchQuery != value) {
|
if (prevSearchQuery != value) {
|
||||||
searchQueryListener?.terminalSearchQueryChanged(value)
|
searchQueryListener?.terminalSearchQueryChanged(value)
|
||||||
}
|
}
|
||||||
prevSearchQuery = value
|
prevSearchQuery = value
|
||||||
}
|
}
|
||||||
var scrollPosition = 0.0
|
var scrollPosition = 0.0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
backgroundWidth = terminalBackgroundWidth
|
backgroundWidth = terminalBackgroundWidth
|
||||||
backgroundHeight = terminalBackgroundHeight
|
backgroundHeight = terminalBackgroundHeight
|
||||||
|
|
||||||
terminalVC = createViewController()
|
terminalVC = createViewController()
|
||||||
addWindow(ScreenHandlerWindow(handler, terminalVC))
|
addWindow(ScreenHandlerWindow(handler, terminalVC))
|
||||||
|
|
||||||
requestUpdatedItems()
|
requestUpdatedItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun createViewController(): AbstractTerminalViewController<*, *, *>
|
abstract fun createViewController(): AbstractTerminalViewController<*, *, *>
|
||||||
|
|
||||||
fun requestItem(stack: ItemStack, amount: Int) {
|
fun requestItem(stack: ItemStack, amount: Int) {
|
||||||
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
|
val netHandler = MinecraftClient.getInstance().player!!.networkHandler
|
||||||
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
|
val packet = C2STerminalRequestItem(handler.terminal, stack, amount)
|
||||||
netHandler.sendPacket(packet)
|
netHandler.sendPacket(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestUpdatedItems() {
|
fun requestUpdatedItems() {
|
||||||
val player = MinecraftClient.getInstance().player!!
|
val player = MinecraftClient.getInstance().player!!
|
||||||
player.networkHandler.sendPacket(C2STerminalUpdateDisplayedItems(handler.terminal, searchQuery, scrollPosition.toFloat()))
|
player.networkHandler.sendPacket(
|
||||||
}
|
C2STerminalUpdateDisplayedItems(
|
||||||
|
handler.terminal,
|
||||||
|
searchQuery,
|
||||||
|
scrollPosition.toFloat()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showRequestAmountDialog(stack: ItemStack) {
|
private fun showRequestAmountDialog(stack: ItemStack) {
|
||||||
val vc = TerminalRequestAmountViewController(this, stack)
|
val vc = TerminalRequestAmountViewController(this, stack)
|
||||||
addWindow(Window(vc))
|
addWindow(Window(vc))
|
||||||
amountVC = vc
|
amountVC = vc
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
@ExperimentalUnsignedTypes
|
||||||
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
|
fun drawSlotUnderlay(matrixStack: MatrixStack, slot: Slot) {
|
||||||
if (!handler.isBufferSlot(slot.id)) {
|
if (!handler.isBufferSlot(slot.id)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
|
val mode = handler.terminal.internalBuffer.getMode(slot.id - handler.bufferSlotsStart)
|
||||||
val color: UInt = when (mode) {
|
val color: UInt = when (mode) {
|
||||||
TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
|
TerminalBufferInventory.Mode.TO_NETWORK -> 0xFFFF0000u
|
||||||
TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
|
TerminalBufferInventory.Mode.FROM_NETWORK -> 0xFF00FF00u
|
||||||
else -> return
|
else -> return
|
||||||
}
|
}
|
||||||
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
|
DrawableHelper.fill(matrixStack, slot.x, slot.y, slot.x + 16, slot.y + 16, color.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
private val DECIMAL_FORMAT = DecimalFormat("#.#").apply { roundingMode = RoundingMode.HALF_UP }
|
private val DECIMAL_FORMAT = DecimalFormat("#.#").apply { roundingMode = RoundingMode.HALF_UP }
|
||||||
private val FORMAT = DecimalFormat("##").apply { roundingMode = RoundingMode.HALF_UP }
|
private val FORMAT = DecimalFormat("##").apply { roundingMode = RoundingMode.HALF_UP }
|
||||||
|
|
||||||
fun drawNetworkSlotAmount(stack: ItemStack, x: Int, y: Int) {
|
fun drawNetworkSlotAmount(stack: ItemStack, x: Int, y: Int) {
|
||||||
val amount = stack.count
|
val amount = stack.count
|
||||||
val s = when {
|
val s = when {
|
||||||
amount < 1_000 -> amount.toString()
|
amount < 1_000 -> amount.toString()
|
||||||
amount < 1_000_000 -> {
|
amount < 1_000_000 -> {
|
||||||
val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT
|
val format = if (amount < 10_000) DECIMAL_FORMAT else FORMAT
|
||||||
format.format(amount / 1_000.0) + "K"
|
format.format(amount / 1_000.0) + "K"
|
||||||
}
|
}
|
||||||
amount < 1_000_000_000 -> {
|
|
||||||
val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT
|
|
||||||
format.format(amount / 1_000_000.0) + "M"
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
DECIMAL_FORMAT.format(amount / 1000000000.0).toString() + "B"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw damage bar
|
amount < 1_000_000_000 -> {
|
||||||
// empty string for label because vanilla renders the count behind the damage bar
|
val format = if (amount < 10_000_000) DECIMAL_FORMAT else FORMAT
|
||||||
itemRenderer.renderGuiItemOverlay(textRenderer, stack, x, y, "")
|
format.format(amount / 1_000_000.0) + "M"
|
||||||
|
}
|
||||||
|
|
||||||
// ItemRenderer.renderGuiItemOverlay creates a new MatrixStack specifically for drawing the overlay
|
else -> {
|
||||||
val matrixStack = MatrixStack()
|
DECIMAL_FORMAT.format(amount / 1000000000.0).toString() + "B"
|
||||||
matrixStack.translate(x.toDouble(), y.toDouble(), itemRenderer.zOffset + 200.0)
|
}
|
||||||
val scale = 2 / 3f
|
}
|
||||||
matrixStack.scale(scale, scale, 1.0f)
|
|
||||||
val immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().buffer)
|
|
||||||
val textX = (1 / scale * 18) - textRenderer.getWidth(s).toFloat() - 3
|
|
||||||
val textY = (1 / scale * 18) - 11
|
|
||||||
textRenderer.draw(s, textX, textY, 0xffffff, true, matrixStack.peek().positionMatrix, immediate, false, 0, 0xF000F0)
|
|
||||||
RenderSystem.enableDepthTest()
|
|
||||||
immediate.draw()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
// draw damage bar
|
||||||
super.drawBackground(matrixStack, delta, mouseX, mouseY)
|
// empty string for label because vanilla renders the count behind the damage bar
|
||||||
|
itemRenderer.renderGuiItemOverlay(textRenderer, stack, x, y, "")
|
||||||
|
|
||||||
drawBackgroundTexture(matrixStack)
|
// ItemRenderer.renderGuiItemOverlay creates a new MatrixStack specifically for drawing the overlay
|
||||||
}
|
val matrixStack = MatrixStack()
|
||||||
|
matrixStack.translate(x.toDouble(), y.toDouble(), itemRenderer.zOffset + 200.0)
|
||||||
|
val scale = 2 / 3f
|
||||||
|
matrixStack.scale(scale, scale, 1.0f)
|
||||||
|
val immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().buffer)
|
||||||
|
val textX = (1 / scale * 18) - textRenderer.getWidth(s).toFloat() - 3
|
||||||
|
val textY = (1 / scale * 18) - 11
|
||||||
|
textRenderer.draw(
|
||||||
|
s,
|
||||||
|
textX,
|
||||||
|
textY,
|
||||||
|
0xffffff,
|
||||||
|
true,
|
||||||
|
matrixStack.peek().positionMatrix,
|
||||||
|
immediate,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
0xF000F0
|
||||||
|
)
|
||||||
|
RenderSystem.enableDepthTest()
|
||||||
|
immediate.draw()
|
||||||
|
}
|
||||||
|
|
||||||
open fun drawBackgroundTexture(matrixStack: MatrixStack) {
|
override fun drawBackground(matrixStack: MatrixStack, delta: Float, mouseX: Int, mouseY: Int) {
|
||||||
RenderSystem.setShader(GameRenderer::getPositionTexColorShader)
|
super.drawBackground(matrixStack, delta, mouseX, mouseY)
|
||||||
RenderSystem.setShaderTexture(0, backgroundTexture)
|
|
||||||
RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
|
|
||||||
val x = (width - backgroundWidth) / 2
|
|
||||||
val y = (height - backgroundHeight) / 2
|
|
||||||
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handledScreenTick() {
|
drawBackgroundTexture(matrixStack)
|
||||||
super.handledScreenTick()
|
}
|
||||||
|
|
||||||
if (amountVC != null) {
|
open fun drawBackgroundTexture(matrixStack: MatrixStack) {
|
||||||
amountVC!!.field.tick()
|
RenderSystem.setShader(GameRenderer::getPositionTexColorShader)
|
||||||
} else {
|
RenderSystem.setShaderTexture(0, backgroundTexture)
|
||||||
terminalVC.searchField.tick()
|
RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
|
||||||
}
|
val x = (width - backgroundWidth) / 2
|
||||||
|
val y = (height - backgroundHeight) / 2
|
||||||
|
drawTexture(matrixStack, x, y, 0, 0, backgroundWidth, backgroundHeight)
|
||||||
|
}
|
||||||
|
|
||||||
val newSearchQuery = searchQueryListener?.requestTerminalSearchFieldUpdate()
|
override fun handledScreenTick() {
|
||||||
if (newSearchQuery != null && searchQuery != newSearchQuery) {
|
super.handledScreenTick()
|
||||||
searchQuery = newSearchQuery
|
|
||||||
terminalVC.searchField.text = newSearchQuery
|
|
||||||
requestUpdatedItems()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
|
if (amountVC != null) {
|
||||||
super.onMouseClick(slot, invSlot, clickData, type)
|
amountVC!!.field.tick()
|
||||||
|
} else {
|
||||||
|
terminalVC.searchField.tick()
|
||||||
|
}
|
||||||
|
|
||||||
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id) && handler.cursorStack.isEmpty) {
|
val newSearchQuery = searchQueryListener?.requestTerminalSearchFieldUpdate()
|
||||||
val stack = slot.stack
|
if (newSearchQuery != null && searchQuery != newSearchQuery) {
|
||||||
|
searchQuery = newSearchQuery
|
||||||
|
terminalVC.searchField.text = newSearchQuery
|
||||||
|
requestUpdatedItems()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (type == SlotActionType.QUICK_MOVE) {
|
override fun onMouseClick(slot: Slot?, invSlot: Int, clickData: Int, type: SlotActionType?) {
|
||||||
// shift click, request full stack
|
super.onMouseClick(slot, invSlot, clickData, type)
|
||||||
requestItem(stack, min(stack.count, stack.maxCount))
|
|
||||||
} else if (type == SlotActionType.PICKUP) {
|
|
||||||
if (clickData == 1) {
|
|
||||||
// right click, request half stack
|
|
||||||
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
|
|
||||||
} else {
|
|
||||||
showRequestAmountDialog(stack)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setFocused(element: Element?) {
|
if (slot != null && !slot.stack.isEmpty && handler.isNetworkSlot(slot.id) && handler.cursorStack.isEmpty) {
|
||||||
super.setFocused(element)
|
val stack = slot.stack
|
||||||
// so that when something else (e.g., REI) steals focus and calls setFocused(null) on us, any first responder resigns
|
|
||||||
if (element == null) {
|
if (type == SlotActionType.QUICK_MOVE) {
|
||||||
windows.last().firstResponder?.resignFirstResponder()
|
// shift click, request full stack
|
||||||
}
|
requestItem(stack, min(stack.count, stack.maxCount))
|
||||||
}
|
} else if (type == SlotActionType.PICKUP) {
|
||||||
|
if (clickData == 1) {
|
||||||
|
// right click, request half stack
|
||||||
|
requestItem(stack, ceil(min(stack.count, stack.maxCount) / 2f).toInt())
|
||||||
|
} else {
|
||||||
|
showRequestAmountDialog(stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setFocused(element: Element?) {
|
||||||
|
super.setFocused(element)
|
||||||
|
// so that when something else (e.g., REI) steals focus and calls setFocused(null) on us, any first responder resigns
|
||||||
|
if (element == null) {
|
||||||
|
windows.last().firstResponder?.resignFirstResponder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,24 +1,20 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
import net.minecraft.screen.slot.Slot
|
import net.minecraft.screen.slot.Slot
|
||||||
import net.minecraft.screen.slot.SlotActionType
|
import net.minecraft.screen.slot.SlotActionType
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
import net.minecraft.entity.player.PlayerInventory
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
import net.minecraft.item.ItemStack
|
import net.minecraft.item.ItemStack
|
||||||
import net.minecraft.network.PacketByteBuf
|
|
||||||
import net.minecraft.screen.ScreenHandler
|
import net.minecraft.screen.ScreenHandler
|
||||||
import net.minecraft.screen.ScreenHandlerType
|
import net.minecraft.screen.ScreenHandlerType
|
||||||
import net.minecraft.server.network.ServerPlayerEntity
|
import net.minecraft.server.network.ServerPlayerEntity
|
||||||
import net.minecraft.util.Identifier
|
|
||||||
import net.minecraft.util.registry.Registry
|
import net.minecraft.util.registry.Registry
|
||||||
import net.shadowfacts.phycon.DefaultPlugin
|
import net.shadowfacts.phycon.DefaultPlugin
|
||||||
import net.shadowfacts.phycon.PhysicalConnectivity
|
|
||||||
import net.shadowfacts.phycon.init.PhyBlocks
|
|
||||||
import net.shadowfacts.phycon.init.PhyScreens
|
|
||||||
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
|
import net.shadowfacts.phycon.networking.S2CTerminalUpdateDisplayedItems
|
||||||
import net.shadowfacts.phycon.util.SortMode
|
import net.shadowfacts.phycon.util.SortMode
|
||||||
import net.shadowfacts.phycon.util.TerminalSettings
|
import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
import net.shadowfacts.phycon.util.copyWithCount
|
import net.shadowfacts.phycon.util.name
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
@ -28,227 +24,252 @@ import kotlin.math.roundToInt
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class AbstractTerminalScreenHandler<T: AbstractTerminalBlockEntity>(
|
abstract class AbstractTerminalScreenHandler<T : AbstractTerminalBlockEntity>(
|
||||||
handlerType: ScreenHandlerType<*>,
|
handlerType: ScreenHandlerType<*>,
|
||||||
syncId: Int,
|
syncId: Int,
|
||||||
val playerInv: PlayerInventory,
|
val playerInv: PlayerInventory,
|
||||||
val terminal: T,
|
val terminal: T,
|
||||||
): ScreenHandler(handlerType, syncId),
|
) : ScreenHandler(handlerType, syncId),
|
||||||
AbstractTerminalBlockEntity.NetItemObserver {
|
AbstractTerminalBlockEntity.NetItemObserver {
|
||||||
|
|
||||||
private val rowsDisplayed = 6
|
private val rowsDisplayed = 6
|
||||||
|
|
||||||
private val fakeInv = FakeInventory(this)
|
private val fakeInv = FakeInventory(this)
|
||||||
private var searchQuery: String = ""
|
private var searchQuery: String = ""
|
||||||
private var settings = TerminalSettings()
|
private var settings = TerminalSettings()
|
||||||
var totalEntries = 0
|
var totalEntries = 0
|
||||||
private set
|
private set
|
||||||
var scrollPosition = 0f
|
var scrollPosition = 0f
|
||||||
private var itemEntries = listOf<Entry>()
|
private var itemEntries = listOf<Entry>()
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
if (terminal.world!!.isClient) {
|
if (terminal.world!!.isClient) {
|
||||||
itemsForDisplay = value.map {
|
itemsForDisplay = value.map {
|
||||||
it.stack.copyWithCount(it.amount)
|
it.variant.toStack(it.amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var itemsForDisplay = listOf<ItemStack>()
|
var itemsForDisplay = listOf<ItemStack>()
|
||||||
private set
|
private set
|
||||||
|
|
||||||
open val xOffset: Int = 0
|
open val xOffset: Int = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (!terminal.world!!.isClient) {
|
if (!terminal.world!!.isClient) {
|
||||||
assert(terminal.netItemObserver?.get() === null)
|
assert(terminal.netItemObserver?.get() === null)
|
||||||
terminal.netItemObserver = WeakReference(this)
|
terminal.netItemObserver = WeakReference(this)
|
||||||
// intentionally don't call netItemsChanged immediately, we need to wait for the client to send us its settings
|
// intentionally don't call netItemsChanged immediately, we need to wait for the client to send us its settings
|
||||||
}
|
}
|
||||||
|
|
||||||
val xOffset = xOffset
|
val xOffset = xOffset
|
||||||
|
|
||||||
// network
|
// network
|
||||||
for (y in 0 until 6) {
|
for (y in 0 until 6) {
|
||||||
for (x in 0 until 9) {
|
for (x in 0 until 9) {
|
||||||
addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, xOffset + 66 + x * 18, 18 + y * 18))
|
addSlot(TerminalFakeSlot(fakeInv, y * 9 + x, xOffset + 66 + x * 18, 18 + y * 18))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal buffer
|
// internal buffer
|
||||||
for (y in 0 until 6) {
|
for (y in 0 until 6) {
|
||||||
for (x in 0 until 3) {
|
for (x in 0 until 3) {
|
||||||
addSlot(Slot(terminal.internalBuffer, y * 3 + x, xOffset + 8 + x * 18, 18 + y * 18))
|
addSlot(Slot(terminal.internalBuffer, y * 3 + x, xOffset + 8 + x * 18, 18 + y * 18))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// player inv
|
// player inv
|
||||||
for (y in 0 until 3) {
|
for (y in 0 until 3) {
|
||||||
for (x in 0 until 9) {
|
for (x in 0 until 9) {
|
||||||
addSlot(Slot(playerInv, x + y * 9 + 9, xOffset + 66 + x * 18, 140 + y * 18))
|
addSlot(Slot(playerInv, x + y * 9 + 9, xOffset + 66 + x * 18, 140 + y * 18))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// hotbar
|
// hotbar
|
||||||
for (x in 0 until 9) {
|
for (x in 0 until 9) {
|
||||||
addSlot(Slot(playerInv, x, xOffset + 66 + x * 18, 198))
|
addSlot(Slot(playerInv, x, xOffset + 66 + x * 18, 198))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun netItemsChanged() {
|
override fun netItemsChanged() {
|
||||||
val player = playerInv.player
|
val player = playerInv.player
|
||||||
assert(player is ServerPlayerEntity)
|
assert(player is ServerPlayerEntity)
|
||||||
|
|
||||||
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
|
val filtered = terminal.cachedNetItems.object2IntEntrySet().filter {
|
||||||
if (searchQuery.isBlank()) return@filter true
|
if (searchQuery.isBlank()) return@filter true
|
||||||
if (searchQuery.startsWith('@')) {
|
if (searchQuery.startsWith('@')) {
|
||||||
val unprefixed = searchQuery.drop(1)
|
val unprefixed = searchQuery.drop(1)
|
||||||
val key = Registry.ITEM.getKey(it.key.item)
|
val key = Registry.ITEM.getKey(it.key.item)
|
||||||
if (key.isPresent && key.get().value.namespace.startsWith(unprefixed, true)) {
|
if (key.isPresent && key.get().value.namespace.startsWith(unprefixed, true)) {
|
||||||
return@filter true
|
return@filter true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
it.key.name.string.contains(searchQuery, true)
|
// TODO: this is happening on the logical server, won't work with localization
|
||||||
}
|
// should filtering happen on the client?
|
||||||
|
it.key.name.string.contains(searchQuery, true)
|
||||||
|
}
|
||||||
|
|
||||||
totalEntries = filtered.size
|
totalEntries = filtered.size
|
||||||
|
|
||||||
val sorted =
|
val sorted =
|
||||||
when (settings[DefaultPlugin.SORT_MODE]) {
|
when (settings[DefaultPlugin.SORT_MODE]) {
|
||||||
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
|
SortMode.COUNT_HIGH_FIRST -> filtered.sortedByDescending { it.intValue }
|
||||||
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
|
SortMode.COUNT_LOW_FIRST -> filtered.sortedBy { it.intValue }
|
||||||
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
|
SortMode.ALPHABETICAL -> filtered.sortedBy { it.key.name.string }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val offsetInItems = currentScrollOffsetInItems()
|
val offsetInItems = currentScrollOffsetInItems()
|
||||||
val end = min(offsetInItems + rowsDisplayed * 9, sorted.size)
|
val end = min(offsetInItems + rowsDisplayed * 9, sorted.size)
|
||||||
itemEntries = sorted.subList(offsetInItems, end).map { Entry(it.key, it.intValue) }
|
itemEntries = sorted.subList(offsetInItems, end).map { Entry(it.key, it.intValue) }
|
||||||
|
|
||||||
// itemEntries = sorted.map { Entry(it.key, it.intValue) }
|
// itemEntries = sorted.map { Entry(it.key, it.intValue) }
|
||||||
|
|
||||||
(player as ServerPlayerEntity).networkHandler.sendPacket(S2CTerminalUpdateDisplayedItems(terminal, itemEntries, searchQuery, settings, scrollPosition, totalEntries))
|
(player as ServerPlayerEntity).networkHandler.sendPacket(
|
||||||
}
|
S2CTerminalUpdateDisplayedItems(
|
||||||
|
terminal,
|
||||||
|
itemEntries,
|
||||||
|
searchQuery,
|
||||||
|
settings,
|
||||||
|
scrollPosition,
|
||||||
|
totalEntries
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun totalRows(): Int {
|
fun totalRows(): Int {
|
||||||
return ceil(totalEntries / 9f).toInt()
|
return ceil(totalEntries / 9f).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun maxScrollOffsetInRows(): Int {
|
fun maxScrollOffsetInRows(): Int {
|
||||||
return totalRows() - rowsDisplayed
|
return totalRows() - rowsDisplayed
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentScrollOffsetInRows(): Int {
|
fun currentScrollOffsetInRows(): Int {
|
||||||
return max(0, (scrollPosition * maxScrollOffsetInRows()).roundToInt())
|
return max(0, (scrollPosition * maxScrollOffsetInRows()).roundToInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun currentScrollOffsetInItems(): Int {
|
fun currentScrollOffsetInItems(): Int {
|
||||||
return currentScrollOffsetInRows() * 9
|
return currentScrollOffsetInRows() * 9
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendUpdatedItemsToClient(player: ServerPlayerEntity, query: String, settings: TerminalSettings, scrollPosition: Float) {
|
fun sendUpdatedItemsToClient(
|
||||||
this.searchQuery = query
|
player: ServerPlayerEntity,
|
||||||
this.settings = settings
|
query: String,
|
||||||
this.scrollPosition = scrollPosition
|
settings: TerminalSettings,
|
||||||
netItemsChanged()
|
scrollPosition: Float
|
||||||
}
|
) {
|
||||||
|
this.searchQuery = query
|
||||||
|
this.settings = settings
|
||||||
|
this.scrollPosition = scrollPosition
|
||||||
|
netItemsChanged()
|
||||||
|
}
|
||||||
|
|
||||||
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, scrollPosition: Float, totalEntries: Int) {
|
fun receivedUpdatedItemsFromServer(entries: List<Entry>, query: String, scrollPosition: Float, totalEntries: Int) {
|
||||||
assert(playerInv.player.world.isClient)
|
assert(playerInv.player.world.isClient)
|
||||||
|
|
||||||
this.searchQuery = query
|
this.searchQuery = query
|
||||||
this.scrollPosition = scrollPosition
|
this.scrollPosition = scrollPosition
|
||||||
this.totalEntries = totalEntries
|
this.totalEntries = totalEntries
|
||||||
itemEntries = entries
|
itemEntries = entries
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun canUse(player: PlayerEntity): Boolean {
|
override fun canUse(player: PlayerEntity): Boolean {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close(player: PlayerEntity) {
|
override fun close(player: PlayerEntity) {
|
||||||
super.close(player)
|
super.close(player)
|
||||||
|
|
||||||
terminal.netItemObserver = null
|
terminal.netItemObserver = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity) {
|
override fun onSlotClick(slotId: Int, clickData: Int, actionType: SlotActionType, player: PlayerEntity) {
|
||||||
if (isBufferSlot(slotId)) {
|
if (isBufferSlot(slotId)) {
|
||||||
// todo: why does this think it's quick_craft sometimes?
|
// todo: why does this think it's quick_craft sometimes?
|
||||||
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !cursorStack.isEmpty) {
|
if ((actionType == SlotActionType.PICKUP || actionType == SlotActionType.QUICK_CRAFT) && !cursorStack.isEmpty) {
|
||||||
// placing cursor stack into buffer
|
// placing cursor stack into buffer
|
||||||
val bufferSlot = slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
|
val bufferSlot =
|
||||||
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
|
slotId - bufferSlotsStart // subtract 54 to convert the handler slot ID to a valid buffer index
|
||||||
}
|
terminal.internalBuffer.markSlot(bufferSlot, TerminalBufferInventory.Mode.TO_NETWORK)
|
||||||
}
|
}
|
||||||
super.onSlotClick(slotId, clickData, actionType, player)
|
}
|
||||||
}
|
super.onSlotClick(slotId, clickData, actionType, player)
|
||||||
|
}
|
||||||
|
|
||||||
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
||||||
if (isNetworkSlot(slotId)) {
|
if (isNetworkSlot(slotId)) {
|
||||||
return ItemStack.EMPTY;
|
return ItemStack.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
val slot = slots[slotId]
|
val slot = slots[slotId]
|
||||||
if (!slot.hasStack()) {
|
if (!slot.hasStack()) {
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = slot.stack.copy()
|
val result = slot.stack.copy()
|
||||||
|
|
||||||
if (isBufferSlot(slotId)) {
|
if (isBufferSlot(slotId)) {
|
||||||
// last boolean param is fromLast
|
// last boolean param is fromLast
|
||||||
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
|
if (!insertItem(slot.stack, playerSlotsStart, playerSlotsEnd, true)) {
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
if (slot.stack.isEmpty) {
|
if (slot.stack.isEmpty) {
|
||||||
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
|
terminal.internalBuffer.markSlot(slotId - bufferSlotsStart, TerminalBufferInventory.Mode.UNASSIGNED)
|
||||||
}
|
}
|
||||||
} else if (isPlayerSlot(slotId)) {
|
} else if (isPlayerSlot(slotId)) {
|
||||||
val slotsInsertedInto = tryInsertItem(slot.stack, bufferSlotsStart until playerSlotsStart) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
|
val slotsInsertedInto = tryInsertItem(
|
||||||
slotsInsertedInto.forEach { terminal.internalBuffer.markSlot(it - bufferSlotsStart, TerminalBufferInventory.Mode.TO_NETWORK) }
|
slot.stack,
|
||||||
if (slotsInsertedInto.isEmpty()) {
|
bufferSlotsStart until playerSlotsStart
|
||||||
return ItemStack.EMPTY
|
) { terminal.internalBuffer.getMode(it - bufferSlotsStart) != TerminalBufferInventory.Mode.FROM_NETWORK }
|
||||||
}
|
slotsInsertedInto.forEach {
|
||||||
}
|
terminal.internalBuffer.markSlot(
|
||||||
|
it - bufferSlotsStart,
|
||||||
|
TerminalBufferInventory.Mode.TO_NETWORK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (slotsInsertedInto.isEmpty()) {
|
||||||
|
return ItemStack.EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
|
private fun tryInsertItem(stack: ItemStack, slots: IntRange, slotPredicate: (Int) -> Boolean): Collection<Int> {
|
||||||
val slotsInsertedInto = mutableListOf<Int>()
|
val slotsInsertedInto = mutableListOf<Int>()
|
||||||
for (index in slots) {
|
for (index in slots) {
|
||||||
if (stack.isEmpty) break
|
if (stack.isEmpty) break
|
||||||
if (!slotPredicate(index)) continue
|
if (!slotPredicate(index)) continue
|
||||||
|
|
||||||
val slot = this.slots[index]
|
val slot = this.slots[index]
|
||||||
val slotStack = slot.stack
|
val slotStack = slot.stack
|
||||||
if (slotStack.isEmpty) {
|
if (slotStack.isEmpty) {
|
||||||
slot.stack = stack.copy()
|
slot.stack = stack.copy()
|
||||||
stack.count = 0
|
stack.count = 0
|
||||||
|
|
||||||
slot.markDirty()
|
slot.markDirty()
|
||||||
slotsInsertedInto.add(index)
|
slotsInsertedInto.add(index)
|
||||||
} else if (ItemStack.canCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
|
} else if (ItemStack.canCombine(slotStack, stack) && slotStack.count < slotStack.maxCount) {
|
||||||
val maxToMove = slotStack.maxCount - slotStack.count
|
val maxToMove = slotStack.maxCount - slotStack.count
|
||||||
val toMove = min(maxToMove, stack.count)
|
val toMove = min(maxToMove, stack.count)
|
||||||
slotStack.increment(toMove)
|
slotStack.increment(toMove)
|
||||||
stack.decrement(toMove)
|
stack.decrement(toMove)
|
||||||
|
|
||||||
slot.markDirty()
|
slot.markDirty()
|
||||||
slotsInsertedInto.add(index)
|
slotsInsertedInto.add(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return slotsInsertedInto
|
return slotsInsertedInto
|
||||||
}
|
}
|
||||||
|
|
||||||
val networkSlotsStart = 0
|
val networkSlotsStart = 0
|
||||||
val networkSlotsEnd = 54
|
val networkSlotsEnd = 54
|
||||||
val bufferSlotsStart = 54
|
val bufferSlotsStart = 54
|
||||||
val bufferSlotsEnd = 72
|
val bufferSlotsEnd = 72
|
||||||
val playerSlotsStart = 72
|
val playerSlotsStart = 72
|
||||||
val playerSlotsEnd = 72 + 36
|
val playerSlotsEnd = 72 + 36
|
||||||
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
|
fun isNetworkSlot(id: Int) = id in 0 until bufferSlotsStart
|
||||||
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
|
fun isBufferSlot(id: Int) = id in bufferSlotsStart until playerSlotsStart
|
||||||
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
|
fun isPlayerSlot(id: Int) = id >= playerSlotsStart
|
||||||
|
|
||||||
data class Entry(val stack: ItemStack, val amount: Int)
|
data class Entry(val variant: ItemVariant, val amount: Int)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,168 +19,168 @@ import net.shadowfacts.phycon.util.TerminalSettings
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
abstract class AbstractTerminalViewController<BE: AbstractTerminalBlockEntity, S: AbstractTerminalScreen<BE, H>, H: AbstractTerminalScreenHandler<BE>>(
|
abstract class AbstractTerminalViewController<BE : AbstractTerminalBlockEntity, S : AbstractTerminalScreen<BE, H>, H : AbstractTerminalScreenHandler<BE>>(
|
||||||
val screen: S,
|
val screen: S,
|
||||||
val handler: H,
|
val handler: H,
|
||||||
val terminal: BE = handler.terminal,
|
val terminal: BE = handler.terminal,
|
||||||
): ViewController() {
|
) : ViewController() {
|
||||||
|
|
||||||
private lateinit var scrollTrack: ScrollTrackView
|
private lateinit var scrollTrack: ScrollTrackView
|
||||||
lateinit var settingsView: View
|
lateinit var settingsView: View
|
||||||
private set
|
private set
|
||||||
lateinit var searchField: TextField
|
lateinit var searchField: TextField
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var pane: LayoutGuide
|
lateinit var pane: LayoutGuide
|
||||||
private set
|
private set
|
||||||
lateinit var buffer: LayoutGuide
|
lateinit var buffer: LayoutGuide
|
||||||
private set
|
private set
|
||||||
lateinit var network: LayoutGuide
|
lateinit var network: LayoutGuide
|
||||||
private set
|
private set
|
||||||
lateinit var playerInv: LayoutGuide
|
lateinit var playerInv: LayoutGuide
|
||||||
private set
|
private set
|
||||||
|
|
||||||
lateinit var networkLabel: View
|
lateinit var networkLabel: View
|
||||||
private set
|
private set
|
||||||
lateinit var playerInvLabel: View
|
lateinit var playerInvLabel: View
|
||||||
private set
|
private set
|
||||||
lateinit var bufferLabel: View
|
lateinit var bufferLabel: View
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun loadView() {
|
override fun loadView() {
|
||||||
view = ScrollHandlingView(this)
|
view = ScrollHandlingView(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun viewDidLoad() {
|
override fun viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
pane = view.addLayoutGuide()
|
pane = view.addLayoutGuide()
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
pane.centerXAnchor equalTo view.centerXAnchor
|
pane.centerXAnchor equalTo view.centerXAnchor
|
||||||
pane.centerYAnchor equalTo view.centerYAnchor
|
pane.centerYAnchor equalTo view.centerYAnchor
|
||||||
pane.widthAnchor equalTo screen.terminalBackgroundWidth
|
pane.widthAnchor equalTo screen.terminalBackgroundWidth
|
||||||
pane.heightAnchor equalTo screen.terminalBackgroundHeight
|
pane.heightAnchor equalTo screen.terminalBackgroundHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = view.addLayoutGuide()
|
buffer = view.addLayoutGuide()
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
buffer.leftAnchor equalTo (pane.leftAnchor + 7 + handler.xOffset)
|
buffer.leftAnchor equalTo (pane.leftAnchor + 7 + handler.xOffset)
|
||||||
buffer.topAnchor equalTo (pane.topAnchor + 17)
|
buffer.topAnchor equalTo (pane.topAnchor + 17)
|
||||||
buffer.widthAnchor equalTo (18 * 3)
|
buffer.widthAnchor equalTo (18 * 3)
|
||||||
buffer.heightAnchor equalTo (18 * 6)
|
buffer.heightAnchor equalTo (18 * 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
network = view.addLayoutGuide()
|
network = view.addLayoutGuide()
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
network.leftAnchor equalTo (pane.leftAnchor + 65 + handler.xOffset)
|
network.leftAnchor equalTo (pane.leftAnchor + 65 + handler.xOffset)
|
||||||
network.topAnchor equalTo buffer.topAnchor
|
network.topAnchor equalTo buffer.topAnchor
|
||||||
network.widthAnchor equalTo (18 * 9)
|
network.widthAnchor equalTo (18 * 9)
|
||||||
network.heightAnchor equalTo (18 * 6)
|
network.heightAnchor equalTo (18 * 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
playerInv = view.addLayoutGuide()
|
playerInv = view.addLayoutGuide()
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
playerInv.leftAnchor equalTo network.leftAnchor
|
playerInv.leftAnchor equalTo network.leftAnchor
|
||||||
playerInv.topAnchor equalTo (pane.topAnchor + 139)
|
playerInv.topAnchor equalTo (pane.topAnchor + 139)
|
||||||
playerInv.widthAnchor equalTo (18 * 9)
|
playerInv.widthAnchor equalTo (18 * 9)
|
||||||
playerInv.heightAnchor equalTo 76
|
playerInv.heightAnchor equalTo 76
|
||||||
}
|
}
|
||||||
|
|
||||||
networkLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_network"))).apply {
|
networkLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_network"))).apply {
|
||||||
textColor = Color.TEXT
|
textColor = Color.TEXT
|
||||||
}
|
}
|
||||||
playerInvLabel = view.addSubview(Label(handler.playerInv.displayName)).apply {
|
playerInvLabel = view.addSubview(Label(handler.playerInv.displayName)).apply {
|
||||||
textColor = Color.TEXT
|
textColor = Color.TEXT
|
||||||
}
|
}
|
||||||
bufferLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_buffer"))).apply {
|
bufferLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_buffer"))).apply {
|
||||||
textColor = Color.TEXT
|
textColor = Color.TEXT
|
||||||
}
|
}
|
||||||
|
|
||||||
searchField = view.addSubview(TerminalSearchField()).apply {
|
searchField = view.addSubview(TerminalSearchField()).apply {
|
||||||
handler = ::searchFieldChanged
|
handler = ::searchFieldChanged
|
||||||
drawBackground = false
|
drawBackground = false
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTrack = view.addSubview(ScrollTrackView(::scrollPositionChanged))
|
scrollTrack = view.addSubview(ScrollTrackView(::scrollPositionChanged))
|
||||||
|
|
||||||
val settingsStack = view.addSubview(StackView(Axis.VERTICAL, spacing = 2.0))
|
val settingsStack = view.addSubview(StackView(Axis.VERTICAL, spacing = 2.0))
|
||||||
settingsView = settingsStack
|
settingsView = settingsStack
|
||||||
TerminalSettings.allKeys.sortedByDescending { it.priority }.forEach { key ->
|
TerminalSettings.allKeys.sortedByDescending { it.priority }.forEach { key ->
|
||||||
val button = SettingButton(key)
|
val button = SettingButton(key)
|
||||||
button.handler = { settingsChanged() }
|
button.handler = { settingsChanged() }
|
||||||
settingsStack.addArrangedSubview(button)
|
settingsStack.addArrangedSubview(button)
|
||||||
}
|
}
|
||||||
|
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
networkLabel.leftAnchor equalTo network.leftAnchor
|
networkLabel.leftAnchor equalTo network.leftAnchor
|
||||||
networkLabel.topAnchor equalTo (pane.topAnchor + 6)
|
networkLabel.topAnchor equalTo (pane.topAnchor + 6)
|
||||||
|
|
||||||
bufferLabel.leftAnchor equalTo buffer.leftAnchor
|
bufferLabel.leftAnchor equalTo buffer.leftAnchor
|
||||||
bufferLabel.topAnchor equalTo networkLabel.topAnchor
|
bufferLabel.topAnchor equalTo networkLabel.topAnchor
|
||||||
|
|
||||||
playerInvLabel.leftAnchor equalTo playerInv.leftAnchor
|
playerInvLabel.leftAnchor equalTo playerInv.leftAnchor
|
||||||
playerInvLabel.topAnchor equalTo (pane.topAnchor + 128)
|
playerInvLabel.topAnchor equalTo (pane.topAnchor + 128)
|
||||||
|
|
||||||
searchField.leftAnchor equalTo (pane.leftAnchor + 138 + handler.xOffset)
|
searchField.leftAnchor equalTo (pane.leftAnchor + 138 + handler.xOffset)
|
||||||
searchField.topAnchor equalTo (pane.topAnchor + 5)
|
searchField.topAnchor equalTo (pane.topAnchor + 5)
|
||||||
searchField.widthAnchor equalTo 80
|
searchField.widthAnchor equalTo 80
|
||||||
searchField.heightAnchor equalTo 9
|
searchField.heightAnchor equalTo 9
|
||||||
|
|
||||||
scrollTrack.leftAnchor equalTo (pane.leftAnchor + 232 + handler.xOffset)
|
scrollTrack.leftAnchor equalTo (pane.leftAnchor + 232 + handler.xOffset)
|
||||||
scrollTrack.topAnchor equalTo (network.topAnchor + 1)
|
scrollTrack.topAnchor equalTo (network.topAnchor + 1)
|
||||||
scrollTrack.bottomAnchor equalTo (network.bottomAnchor - 1)
|
scrollTrack.bottomAnchor equalTo (network.bottomAnchor - 1)
|
||||||
scrollTrack.widthAnchor equalTo 12
|
scrollTrack.widthAnchor equalTo 12
|
||||||
|
|
||||||
settingsStack.leftAnchor equalTo (pane.rightAnchor + 4)
|
settingsStack.leftAnchor equalTo (pane.rightAnchor + 4)
|
||||||
settingsStack.topAnchor equalTo pane.topAnchor
|
settingsStack.topAnchor equalTo pane.topAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun viewWillAppear() {
|
override fun viewWillAppear() {
|
||||||
super.viewWillAppear()
|
super.viewWillAppear()
|
||||||
|
|
||||||
searchField.becomeFirstResponder()
|
searchField.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchFieldChanged(field: TextField) {
|
private fun searchFieldChanged(field: TextField) {
|
||||||
screen.searchQuery = field.text
|
screen.searchQuery = field.text
|
||||||
screen.requestUpdatedItems()
|
screen.requestUpdatedItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scrollPositionChanged(track: ScrollTrackView) {
|
private fun scrollPositionChanged(track: ScrollTrackView) {
|
||||||
val oldOffset = handler.currentScrollOffsetInRows()
|
val oldOffset = handler.currentScrollOffsetInRows()
|
||||||
|
|
||||||
handler.scrollPosition = track.scrollPosition.toFloat()
|
handler.scrollPosition = track.scrollPosition.toFloat()
|
||||||
screen.scrollPosition = track.scrollPosition
|
screen.scrollPosition = track.scrollPosition
|
||||||
|
|
||||||
if (handler.currentScrollOffsetInRows() != oldOffset) {
|
if (handler.currentScrollOffsetInRows() != oldOffset) {
|
||||||
screen.requestUpdatedItems()
|
screen.requestUpdatedItems()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun settingsChanged() {
|
private fun settingsChanged() {
|
||||||
screen.requestUpdatedItems()
|
screen.requestUpdatedItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
class TerminalSearchField: TextField("") {
|
class TerminalSearchField : TextField("") {
|
||||||
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
override fun mouseClickedOutside(point: Point, mouseButton: MouseButton) {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScrollHandlingView(val vc: AbstractTerminalViewController<*, *, *>): View() {
|
class ScrollHandlingView(val vc: AbstractTerminalViewController<*, *, *>) : View() {
|
||||||
override fun mouseScrolled(point: Point, amount: Double): Boolean {
|
override fun mouseScrolled(point: Point, amount: Double): Boolean {
|
||||||
var newOffsetInRows = vc.handler.currentScrollOffsetInRows() - amount.toInt()
|
var newOffsetInRows = vc.handler.currentScrollOffsetInRows() - amount.toInt()
|
||||||
newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, vc.handler.maxScrollOffsetInRows())
|
newOffsetInRows = MathHelper.clamp(newOffsetInRows, 0, vc.handler.maxScrollOffsetInRows())
|
||||||
if (newOffsetInRows != vc.handler.currentScrollOffsetInRows()) {
|
if (newOffsetInRows != vc.handler.currentScrollOffsetInRows()) {
|
||||||
val newScrollPosition = newOffsetInRows / vc.handler.maxScrollOffsetInRows().toDouble()
|
val newScrollPosition = newOffsetInRows / vc.handler.maxScrollOffsetInRows().toDouble()
|
||||||
vc.screen.scrollPosition = newScrollPosition
|
vc.screen.scrollPosition = newScrollPosition
|
||||||
vc.scrollTrack.scrollPosition = newScrollPosition
|
vc.scrollTrack.scrollPosition = newScrollPosition
|
||||||
vc.screen.requestUpdatedItems()
|
vc.screen.requestUpdatedItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,12 @@ import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class CraftingTerminalBlock: AbstractTerminalBlock<CraftingTerminalBlockEntity>() {
|
class CraftingTerminalBlock : AbstractTerminalBlock<CraftingTerminalBlockEntity>() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ID = Identifier(PhysicalConnectivity.MODID, "crafting_terminal")
|
val ID = Identifier(PhysicalConnectivity.MODID, "crafting_terminal")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createBlockEntity(pos: BlockPos, state: BlockState) = CraftingTerminalBlockEntity(pos, state)
|
override fun createBlockEntity(pos: BlockPos, state: BlockState) = CraftingTerminalBlockEntity(pos, state)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package net.shadowfacts.phycon.block.terminal
|
package net.shadowfacts.phycon.block.terminal
|
||||||
|
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackCollections
|
import it.unimi.dsi.fastutil.ints.IntBinaryOperator
|
||||||
import alexiil.mc.lib.attributes.item.ItemStackUtil
|
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
|
||||||
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
|
import net.fabricmc.fabric.api.screenhandler.v1.ExtendedScreenHandlerFactory
|
||||||
|
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant
|
||||||
import net.minecraft.block.BlockState
|
import net.minecraft.block.BlockState
|
||||||
import net.minecraft.entity.player.PlayerEntity
|
import net.minecraft.entity.player.PlayerEntity
|
||||||
import net.minecraft.entity.player.PlayerInventory
|
import net.minecraft.entity.player.PlayerInventory
|
||||||
|
@ -17,122 +18,130 @@ import net.minecraft.util.math.BlockPos
|
||||||
import net.shadowfacts.phycon.init.PhyBlockEntities
|
import net.shadowfacts.phycon.init.PhyBlockEntities
|
||||||
import net.shadowfacts.phycon.packet.ItemStackPacket
|
import net.shadowfacts.phycon.packet.ItemStackPacket
|
||||||
import net.shadowfacts.phycon.packet.LocateStackPacket
|
import net.shadowfacts.phycon.packet.LocateStackPacket
|
||||||
import net.shadowfacts.phycon.packet.RequestInventoryPacket
|
import net.shadowfacts.phycon.util.equalsIgnoringAmount
|
||||||
import net.shadowfacts.phycon.util.fromTag
|
import net.shadowfacts.phycon.util.fromTag
|
||||||
import net.shadowfacts.phycon.util.toTag
|
import net.shadowfacts.phycon.util.toTag
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState): AbstractTerminalBlockEntity(PhyBlockEntities.CRAFTING_TERMINAL, pos, state) {
|
class CraftingTerminalBlockEntity(pos: BlockPos, state: BlockState) :
|
||||||
|
AbstractTerminalBlockEntity(PhyBlockEntities.CRAFTING_TERMINAL, pos, state) {
|
||||||
|
|
||||||
val craftingInv = SimpleInventory(9)
|
val craftingInv = SimpleInventory(9)
|
||||||
|
|
||||||
private val completedCraftingStackRequests = LinkedList<CraftingStackLocateRequest>()
|
private val completedCraftingStackRequests = LinkedList<CraftingStackLocateRequest>()
|
||||||
|
|
||||||
override fun onActivate(player: PlayerEntity) {
|
override fun onActivate(player: PlayerEntity) {
|
||||||
super.onActivate(player)
|
super.onActivate(player)
|
||||||
|
|
||||||
if (!world!!.isClient) {
|
if (!world!!.isClient) {
|
||||||
val factory = object: ExtendedScreenHandlerFactory {
|
val factory = object : ExtendedScreenHandlerFactory {
|
||||||
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
|
override fun createMenu(syncId: Int, playerInv: PlayerInventory, player: PlayerEntity): ScreenHandler? {
|
||||||
return CraftingTerminalScreenHandler(syncId, playerInv, this@CraftingTerminalBlockEntity)
|
return CraftingTerminalScreenHandler(syncId, playerInv, this@CraftingTerminalBlockEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getDisplayName() = TranslatableText("block.phycon.crafting_terminal")
|
override fun getDisplayName() = TranslatableText("block.phycon.crafting_terminal")
|
||||||
|
|
||||||
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
|
override fun writeScreenOpeningData(player: ServerPlayerEntity, buf: PacketByteBuf) {
|
||||||
buf.writeBlockPos(this@CraftingTerminalBlockEntity.pos)
|
buf.writeBlockPos(this@CraftingTerminalBlockEntity.pos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.openHandledScreen(factory)
|
player.openHandledScreen(factory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestItemsForCrafting(maxAmount: Int) {
|
fun requestItemsForCrafting(maxAmount: Int) {
|
||||||
val amounts = ItemStackCollections.map<IntArray>()
|
// use an array map because we have at most 9 items
|
||||||
|
// values are bitfields of which slots contain the item
|
||||||
|
val stackToSlotsMap = Object2IntArrayMap<ItemVariant>()
|
||||||
|
|
||||||
for (i in 0 until craftingInv.size()) {
|
val or = IntBinaryOperator { a, b -> a or b }
|
||||||
val craftingInvStack = craftingInv.getStack(i)
|
for (i in 0 until craftingInv.size()) {
|
||||||
if (craftingInvStack.isEmpty) continue
|
val craftingInvStack = craftingInv.getStack(i)
|
||||||
if (craftingInvStack.count >= craftingInvStack.maxCount) continue
|
if (craftingInvStack.isEmpty) continue
|
||||||
|
if (craftingInvStack.count >= craftingInvStack.maxCount) continue
|
||||||
|
|
||||||
if (craftingInvStack !in amounts) amounts[craftingInvStack] = IntArray(9) { 0 }
|
stackToSlotsMap.mergeInt(ItemVariant.of(craftingInvStack), 1 shl i, or)
|
||||||
amounts[craftingInvStack]!![i] = min(maxAmount, craftingInvStack.maxCount - craftingInvStack.count)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for ((stack, amountPerSlot) in amounts) {
|
for ((variant, slots) in stackToSlotsMap) {
|
||||||
val total = amountPerSlot.sum()
|
val total = slots.countOneBits()
|
||||||
val request = CraftingStackLocateRequest(stack, total, counter, amountPerSlot)
|
val stack = variant.toStack()
|
||||||
pendingRequests.add(request)
|
val request = CraftingStackLocateRequest(stack, total, counter, slots)
|
||||||
sendPacket(LocateStackPacket(stack, ipAddress))
|
pendingRequests.add(request)
|
||||||
}
|
sendPacket(LocateStackPacket(stack, ipAddress))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
override fun stackLocateRequestCompleted(request: StackLocateRequest) {
|
||||||
if (request is CraftingStackLocateRequest) {
|
if (request is CraftingStackLocateRequest) {
|
||||||
completedCraftingStackRequests.add(request)
|
completedCraftingStackRequests.add(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
super.stackLocateRequestCompleted(request)
|
super.stackLocateRequestCompleted(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
override fun doHandleItemStack(packet: ItemStackPacket): ItemStack {
|
||||||
val craftingReq = completedCraftingStackRequests.find { ItemStackUtil.areEqualIgnoreAmounts(it.stack, packet.stack) }
|
val craftingReq =
|
||||||
if (craftingReq != null) {
|
completedCraftingStackRequests.find { it.stack.equalsIgnoringAmount(packet.stack) }
|
||||||
var remaining = packet.stack.copy()
|
if (craftingReq != null) {
|
||||||
|
var remaining = packet.stack.copy()
|
||||||
|
|
||||||
for (i in 0 until craftingInv.size()) {
|
for (i in 0 until craftingInv.size()) {
|
||||||
val currentStack = craftingInv.getStack(i)
|
val currentStack = craftingInv.getStack(i)
|
||||||
if (currentStack.count >= currentStack.maxCount) continue
|
if (currentStack.count >= currentStack.maxCount) continue
|
||||||
if (!ItemStackUtil.areEqualIgnoreAmounts(currentStack, remaining)) continue
|
if (!currentStack.equalsIgnoringAmount(remaining)) continue
|
||||||
|
|
||||||
val toInsert = minOf(remaining.count, currentStack.maxCount - currentStack.count, craftingReq.amountPerSlot[i])
|
currentStack.count += 1
|
||||||
currentStack.count += toInsert
|
remaining.count -= 1
|
||||||
remaining.count -= toInsert
|
|
||||||
craftingReq.amountPerSlot[i] -= toInsert
|
|
||||||
craftingReq.received += toInsert
|
|
||||||
|
|
||||||
if (remaining.isEmpty) {
|
craftingReq.slots = craftingReq.slots and (1 shl i).inv()
|
||||||
break
|
craftingReq.received += 1
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (craftingReq.amountPerSlot.sum() == 0 || craftingReq.received >= craftingReq.totalResultAmount) {
|
if (remaining.isEmpty) {
|
||||||
completedCraftingStackRequests.remove(craftingReq)
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!remaining.isEmpty) {
|
// if slots == 0, there are no more slots needing items for this request
|
||||||
remaining = internalBuffer.insert(remaining, TerminalBufferInventory.Mode.FROM_NETWORK)
|
if (craftingReq.slots == 0 || craftingReq.received >= craftingReq.totalResultAmount) {
|
||||||
}
|
completedCraftingStackRequests.remove(craftingReq)
|
||||||
|
}
|
||||||
|
|
||||||
updateAndSync()
|
if (!remaining.isEmpty) {
|
||||||
|
remaining = internalBuffer.insert(remaining, TerminalBufferInventory.Mode.FROM_NETWORK)
|
||||||
|
}
|
||||||
|
|
||||||
return remaining
|
updateAndSync()
|
||||||
} else {
|
|
||||||
return super.doHandleItemStack(packet)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toCommonTag(tag: NbtCompound) {
|
return remaining
|
||||||
super.toCommonTag(tag)
|
} else {
|
||||||
tag.put("CraftingInv", craftingInv.toTag())
|
return super.doHandleItemStack(packet)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun fromCommonTag(tag: NbtCompound) {
|
override fun toCommonTag(tag: NbtCompound) {
|
||||||
super.fromCommonTag(tag)
|
super.toCommonTag(tag)
|
||||||
craftingInv.fromTag(tag.getList("CraftingInv", 10))
|
tag.put("CraftingInv", craftingInv.toTag())
|
||||||
}
|
}
|
||||||
|
|
||||||
class CraftingStackLocateRequest(
|
override fun fromCommonTag(tag: NbtCompound) {
|
||||||
stack: ItemStack,
|
super.fromCommonTag(tag)
|
||||||
amount: Int,
|
craftingInv.fromTag(tag.getList("CraftingInv", 10))
|
||||||
timestamp: Long,
|
}
|
||||||
val amountPerSlot: IntArray,
|
|
||||||
): StackLocateRequest(stack, amount, timestamp) {
|
class CraftingStackLocateRequest(
|
||||||
var received: Int = 0
|
stack: ItemStack,
|
||||||
}
|
amount: Int,
|
||||||
|
timestamp: Long,
|
||||||
|
/**
|
||||||
|
* Values are bitfields of which slots in the crafting inventory this request is for.
|
||||||
|
*/
|
||||||
|
var slots: Int,
|
||||||
|
) : StackLocateRequest(stack, amount, timestamp) {
|
||||||
|
var received: Int = 0
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,38 +12,38 @@ import net.shadowfacts.phycon.PhysicalConnectivity
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class CraftingTerminalScreen(
|
class CraftingTerminalScreen(
|
||||||
handler: CraftingTerminalScreenHandler,
|
handler: CraftingTerminalScreenHandler,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
title: Text,
|
title: Text,
|
||||||
): AbstractTerminalScreen<CraftingTerminalBlockEntity, CraftingTerminalScreenHandler>(
|
) : AbstractTerminalScreen<CraftingTerminalBlockEntity, CraftingTerminalScreenHandler>(
|
||||||
handler,
|
handler,
|
||||||
playerInv,
|
playerInv,
|
||||||
title,
|
title,
|
||||||
259,
|
259,
|
||||||
252,
|
252,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val BACKGROUND_1 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_1.png")
|
private val BACKGROUND_1 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_1.png")
|
||||||
private val BACKGROUND_2 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_2.png")
|
private val BACKGROUND_2 = Identifier(PhysicalConnectivity.MODID, "textures/gui/crafting_terminal_2.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
override val backgroundTexture = BACKGROUND_1
|
override val backgroundTexture = BACKGROUND_1
|
||||||
|
|
||||||
override fun createViewController(): AbstractTerminalViewController<*, *, *> {
|
override fun createViewController(): AbstractTerminalViewController<*, *, *> {
|
||||||
return CraftingTerminalViewController(this, handler)
|
return CraftingTerminalViewController(this, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun drawBackgroundTexture(matrixStack: MatrixStack) {
|
override fun drawBackgroundTexture(matrixStack: MatrixStack) {
|
||||||
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
RenderSystem.setShader(GameRenderer::getPositionTexShader)
|
||||||
RenderSystem.setShaderTexture(0, BACKGROUND_1)
|
RenderSystem.setShaderTexture(0, BACKGROUND_1)
|
||||||
val x = (width - backgroundWidth) / 2
|
val x = (width - backgroundWidth) / 2
|
||||||
val y = (height - backgroundHeight) / 2
|
val y = (height - backgroundHeight) / 2
|
||||||
drawTexture(matrixStack, x, y, 0, 0, 256, 252)
|
drawTexture(matrixStack, x, y, 0, 0, 256, 252)
|
||||||
|
|
||||||
RenderSystem.setShaderTexture(0, BACKGROUND_2)
|
RenderSystem.setShaderTexture(0, BACKGROUND_2)
|
||||||
drawTexture(matrixStack, x + 256, y, 0, 0, 3, 252)
|
drawTexture(matrixStack, x + 256, y, 0, 0, 3, 252)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,146 +20,158 @@ import net.shadowfacts.phycon.init.PhyScreens
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class CraftingTerminalScreenHandler(
|
class CraftingTerminalScreenHandler(
|
||||||
syncId: Int,
|
syncId: Int,
|
||||||
playerInv: PlayerInventory,
|
playerInv: PlayerInventory,
|
||||||
terminal: CraftingTerminalBlockEntity,
|
terminal: CraftingTerminalBlockEntity,
|
||||||
): AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(PhyScreens.CRAFTING_TERMINAL, syncId, playerInv, terminal) {
|
) : AbstractTerminalScreenHandler<CraftingTerminalBlockEntity>(
|
||||||
|
PhyScreens.CRAFTING_TERMINAL,
|
||||||
|
syncId,
|
||||||
|
playerInv,
|
||||||
|
terminal
|
||||||
|
) {
|
||||||
|
|
||||||
val craftingInv = CraftingInv(this)
|
val craftingInv = CraftingInv(this)
|
||||||
val result = CraftingResultInventory()
|
val result = CraftingResultInventory()
|
||||||
val resultSlot: CraftingResultSlot
|
val resultSlot: CraftingResultSlot
|
||||||
|
|
||||||
val craftingSlotsStart: Int
|
val craftingSlotsStart: Int
|
||||||
val craftingSlotsEnd: Int
|
val craftingSlotsEnd: Int
|
||||||
get() = craftingSlotsStart + 9
|
get() = craftingSlotsStart + 9
|
||||||
|
|
||||||
override val xOffset: Int
|
override val xOffset: Int
|
||||||
get() = 5
|
get() = 5
|
||||||
|
|
||||||
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf):
|
constructor(syncId: Int, playerInv: PlayerInventory, buf: PacketByteBuf) :
|
||||||
this(
|
this(
|
||||||
syncId,
|
syncId,
|
||||||
playerInv,
|
playerInv,
|
||||||
PhyBlocks.CRAFTING_TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
PhyBlocks.CRAFTING_TERMINAL.getBlockEntity(playerInv.player.world, buf.readBlockPos())!!
|
||||||
)
|
)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
craftingSlotsStart = slots.size
|
craftingSlotsStart = slots.size
|
||||||
for (y in 0 until 3) {
|
for (y in 0 until 3) {
|
||||||
for (x in 0 until 3) {
|
for (x in 0 until 3) {
|
||||||
this.addSlot(Slot(craftingInv, x + y * 3, 13 + x * 18, 140 + y * 18))
|
this.addSlot(Slot(craftingInv, x + y * 3, 13 + x * 18, 140 + y * 18))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resultSlot = CraftingResultSlot(playerInv.player, craftingInv, result, 0, 31, 224)
|
resultSlot = CraftingResultSlot(playerInv.player, craftingInv, result, 0, 31, 224)
|
||||||
addSlot(resultSlot)
|
addSlot(resultSlot)
|
||||||
|
|
||||||
updateCraftingResult()
|
updateCraftingResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onContentChanged(inventory: Inventory?) {
|
override fun onContentChanged(inventory: Inventory?) {
|
||||||
updateCraftingResult()
|
updateCraftingResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCraftingResult() {
|
private fun updateCraftingResult() {
|
||||||
val world = playerInv.player.world
|
val world = playerInv.player.world
|
||||||
if (!world.isClient) {
|
if (!world.isClient) {
|
||||||
val player = playerInv.player as ServerPlayerEntity
|
val player = playerInv.player as ServerPlayerEntity
|
||||||
val recipe = world.server!!.recipeManager.getFirstMatch(RecipeType.CRAFTING, craftingInv, world)
|
val recipe = world.server!!.recipeManager.getFirstMatch(RecipeType.CRAFTING, craftingInv, world)
|
||||||
val resultStack =
|
val resultStack =
|
||||||
if (recipe.isPresent && result.shouldCraftRecipe(world, player, recipe.get())) {
|
if (recipe.isPresent && result.shouldCraftRecipe(world, player, recipe.get())) {
|
||||||
recipe.get().craft(craftingInv)
|
recipe.get().craft(craftingInv)
|
||||||
} else {
|
} else {
|
||||||
ItemStack.EMPTY
|
ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
result.setStack(0, resultStack)
|
result.setStack(0, resultStack)
|
||||||
player.networkHandler.sendPacket(ScreenHandlerSlotUpdateS2CPacket(syncId, nextRevision(), resultSlot.id, resultStack))
|
player.networkHandler.sendPacket(
|
||||||
}
|
ScreenHandlerSlotUpdateS2CPacket(
|
||||||
}
|
syncId,
|
||||||
|
nextRevision(),
|
||||||
|
resultSlot.id,
|
||||||
|
resultStack
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun clearCraftingGrid() {
|
fun clearCraftingGrid() {
|
||||||
assert(!playerInv.player.world.isClient)
|
assert(!playerInv.player.world.isClient)
|
||||||
for (i in 0 until terminal.craftingInv.size()) {
|
for (i in 0 until terminal.craftingInv.size()) {
|
||||||
val craftingInvStack = terminal.craftingInv.getStack(i)
|
val craftingInvStack = terminal.craftingInv.getStack(i)
|
||||||
if (craftingInvStack.isEmpty) continue
|
if (craftingInvStack.isEmpty) continue
|
||||||
val remainder = terminal.internalBuffer.insert(craftingInvStack, TerminalBufferInventory.Mode.TO_NETWORK)
|
val remainder = terminal.internalBuffer.insert(craftingInvStack, TerminalBufferInventory.Mode.TO_NETWORK)
|
||||||
terminal.craftingInv.setStack(i, remainder)
|
terminal.craftingInv.setStack(i, remainder)
|
||||||
}
|
}
|
||||||
updateCraftingResult()
|
updateCraftingResult()
|
||||||
sendContentUpdates()
|
sendContentUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestMoreCraftingIngredients(maxAmount: Int) {
|
fun requestMoreCraftingIngredients(maxAmount: Int) {
|
||||||
assert(!playerInv.player.world.isClient)
|
assert(!playerInv.player.world.isClient)
|
||||||
terminal.requestItemsForCrafting(maxAmount)
|
terminal.requestItemsForCrafting(maxAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
override fun transferSlot(player: PlayerEntity, slotId: Int): ItemStack {
|
||||||
if (slotId == resultSlot.id && resultSlot.hasStack()) {
|
if (slotId == resultSlot.id && resultSlot.hasStack()) {
|
||||||
val craftingResult = resultSlot.stack
|
val craftingResult = resultSlot.stack
|
||||||
val originalResult = craftingResult.copy()
|
val originalResult = craftingResult.copy()
|
||||||
|
|
||||||
// todo: CraftingScreenHandler calls onCraft, but I don't think that's necessary because onStackChanged should handle it
|
// todo: CraftingScreenHandler calls onCraft, but I don't think that's necessary because onStackChanged should handle it
|
||||||
craftingResult.item.onCraft(craftingResult, player.world, player)
|
craftingResult.item.onCraft(craftingResult, player.world, player)
|
||||||
|
|
||||||
if (!insertItem(craftingResult, playerSlotsStart, playerSlotsEnd, true)) {
|
if (!insertItem(craftingResult, playerSlotsStart, playerSlotsEnd, true)) {
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
resultSlot.onQuickTransfer(craftingResult, originalResult)
|
resultSlot.onQuickTransfer(craftingResult, originalResult)
|
||||||
|
|
||||||
if (craftingResult.isEmpty) {
|
if (craftingResult.isEmpty) {
|
||||||
resultSlot.stack = ItemStack.EMPTY
|
resultSlot.stack = ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
if (craftingResult.count == originalResult.count) {
|
if (craftingResult.count == originalResult.count) {
|
||||||
return ItemStack.EMPTY
|
return ItemStack.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
resultSlot.onTakeItem(player, craftingResult)
|
resultSlot.onTakeItem(player, craftingResult)
|
||||||
player.dropItem(craftingResult, false)
|
player.dropItem(craftingResult, false)
|
||||||
|
|
||||||
return originalResult
|
return originalResult
|
||||||
} else {
|
} else {
|
||||||
return super.transferSlot(player, slotId)
|
return super.transferSlot(player, slotId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecipeType.CRAFTING wants a CraftingInventory, but we can't store a CraftingInventory on the BE without a screen handler, so...
|
// RecipeType.CRAFTING wants a CraftingInventory, but we can't store a CraftingInventory on the BE without a screen handler, so...
|
||||||
class CraftingInv(val handler: CraftingTerminalScreenHandler): CraftingInventory(handler, 3, 3) {
|
class CraftingInv(val handler: CraftingTerminalScreenHandler) : CraftingInventory(handler, 3, 3) {
|
||||||
private val backing = handler.terminal.craftingInv
|
private val backing = handler.terminal.craftingInv
|
||||||
|
|
||||||
override fun isEmpty(): Boolean {
|
override fun isEmpty(): Boolean {
|
||||||
return backing.isEmpty
|
return backing.isEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStack(i: Int): ItemStack {
|
override fun getStack(i: Int): ItemStack {
|
||||||
return backing.getStack(i)
|
return backing.getStack(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeStack(i: Int): ItemStack {
|
override fun removeStack(i: Int): ItemStack {
|
||||||
return backing.removeStack(i)
|
return backing.removeStack(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeStack(i: Int, j: Int): ItemStack {
|
override fun removeStack(i: Int, j: Int): ItemStack {
|
||||||
val res = backing.removeStack(i, j)
|
val res = backing.removeStack(i, j)
|
||||||
if (!res.isEmpty) {
|
if (!res.isEmpty) {
|
||||||
handler.onContentChanged(this)
|
handler.onContentChanged(this)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setStack(i: Int, itemStack: ItemStack?) {
|
override fun setStack(i: Int, itemStack: ItemStack?) {
|
||||||
backing.setStack(i, itemStack)
|
backing.setStack(i, itemStack)
|
||||||
handler.onContentChanged(this)
|
handler.onContentChanged(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
backing.clear()
|
backing.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun provideRecipeInputs(finder: RecipeMatcher) {
|
override fun provideRecipeInputs(finder: RecipeMatcher) {
|
||||||
TODO()
|
TODO()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,81 +21,86 @@ import org.lwjgl.glfw.GLFW
|
||||||
* @author shadowfacts
|
* @author shadowfacts
|
||||||
*/
|
*/
|
||||||
class CraftingTerminalViewController(
|
class CraftingTerminalViewController(
|
||||||
screen: CraftingTerminalScreen,
|
screen: CraftingTerminalScreen,
|
||||||
handler: CraftingTerminalScreenHandler,
|
handler: CraftingTerminalScreenHandler,
|
||||||
): AbstractTerminalViewController<CraftingTerminalBlockEntity, CraftingTerminalScreen, CraftingTerminalScreenHandler>(
|
) : AbstractTerminalViewController<CraftingTerminalBlockEntity, CraftingTerminalScreen, CraftingTerminalScreenHandler>(
|
||||||
screen,
|
screen,
|
||||||
handler,
|
handler,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SMALL_BUTTON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 0, 48)
|
val SMALL_BUTTON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 0, 48)
|
||||||
val SMALL_BUTTON_HOVERED = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 16, 48)
|
val SMALL_BUTTON_HOVERED = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 16, 48)
|
||||||
val CLEAR_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 32, 48)
|
val CLEAR_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 32, 48)
|
||||||
val PLUS_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 48, 48)
|
val PLUS_ICON = Texture(Identifier(PhysicalConnectivity.MODID, "textures/gui/icons.png"), 48, 48)
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var craftingInv: LayoutGuide
|
lateinit var craftingInv: LayoutGuide
|
||||||
|
|
||||||
override fun viewDidLoad() {
|
override fun viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
craftingInv = view.addLayoutGuide()
|
craftingInv = view.addLayoutGuide()
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
craftingInv.leftAnchor equalTo buffer.leftAnchor
|
craftingInv.leftAnchor equalTo buffer.leftAnchor
|
||||||
craftingInv.topAnchor equalTo playerInv.topAnchor
|
craftingInv.topAnchor equalTo playerInv.topAnchor
|
||||||
craftingInv.widthAnchor equalTo buffer.widthAnchor
|
craftingInv.widthAnchor equalTo buffer.widthAnchor
|
||||||
craftingInv.heightAnchor equalTo 54
|
craftingInv.heightAnchor equalTo 54
|
||||||
}
|
}
|
||||||
|
|
||||||
val craftingLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_crafting"))).apply {
|
val craftingLabel = view.addSubview(Label(TranslatableText("gui.phycon.terminal_crafting"))).apply {
|
||||||
textColor = Color.TEXT
|
textColor = Color.TEXT
|
||||||
}
|
}
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
craftingLabel.leftAnchor equalTo craftingInv.leftAnchor
|
craftingLabel.leftAnchor equalTo craftingInv.leftAnchor
|
||||||
craftingLabel.topAnchor equalTo playerInvLabel.topAnchor
|
craftingLabel.topAnchor equalTo playerInvLabel.topAnchor
|
||||||
}
|
}
|
||||||
|
|
||||||
val clearIcon = TextureView(CLEAR_ICON).apply {
|
val clearIcon = TextureView(CLEAR_ICON).apply {
|
||||||
intrinsicContentSize = Size(3.0,3.0)
|
intrinsicContentSize = Size(3.0, 3.0)
|
||||||
}
|
}
|
||||||
val clearButton = view.addSubview(Button(clearIcon, padding = 2.0, handler = ::clearPressed)).apply {
|
val clearButton = view.addSubview(Button(clearIcon, padding = 2.0, handler = ::clearPressed)).apply {
|
||||||
background = TextureView(SMALL_BUTTON)
|
background = TextureView(SMALL_BUTTON)
|
||||||
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
|
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
|
||||||
tooltip = TranslatableText("gui.phycon.terminal.clear_crafting")
|
tooltip = TranslatableText("gui.phycon.terminal.clear_crafting")
|
||||||
}
|
}
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
clearButton.topAnchor equalTo craftingInv.topAnchor
|
clearButton.topAnchor equalTo craftingInv.topAnchor
|
||||||
clearButton.leftAnchor equalTo (pane.leftAnchor + 4)
|
clearButton.leftAnchor equalTo (pane.leftAnchor + 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
val plusIcon = TextureView(PLUS_ICON).apply {
|
val plusIcon = TextureView(PLUS_ICON).apply {
|
||||||
intrinsicContentSize = Size(3.0, 3.0)
|
intrinsicContentSize = Size(3.0, 3.0)
|
||||||
}
|
}
|
||||||
val plusButton = view.addSubview(Button(plusIcon, padding = 2.0, handler = ::plusPressed)).apply {
|
val plusButton = view.addSubview(Button(plusIcon, padding = 2.0, handler = ::plusPressed)).apply {
|
||||||
background= TextureView(SMALL_BUTTON)
|
background = TextureView(SMALL_BUTTON)
|
||||||
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
|
hoveredBackground = TextureView(SMALL_BUTTON_HOVERED)
|
||||||
tooltip = TranslatableText("gui.phycon.terminal.more_crafting")
|
tooltip = TranslatableText("gui.phycon.terminal.more_crafting")
|
||||||
}
|
}
|
||||||
view.solver.dsl {
|
view.solver.dsl {
|
||||||
plusButton.topAnchor equalTo (clearButton.bottomAnchor + 2)
|
plusButton.topAnchor equalTo (clearButton.bottomAnchor + 2)
|
||||||
plusButton.leftAnchor equalTo clearButton.leftAnchor
|
plusButton.leftAnchor equalTo clearButton.leftAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun clearPressed(button: Button) {
|
private fun clearPressed(button: Button) {
|
||||||
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, C2STerminalCraftingButton.Action.CLEAR_GRID))
|
MinecraftClient.getInstance().player!!.networkHandler.sendPacket(
|
||||||
}
|
C2STerminalCraftingButton(
|
||||||
|
terminal,
|
||||||
|
C2STerminalCraftingButton.Action.CLEAR_GRID
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun plusPressed(button: Button) {
|
private fun plusPressed(button: Button) {
|
||||||
val client = MinecraftClient.getInstance()
|
val client = MinecraftClient.getInstance()
|
||||||
val action =
|
val action =
|
||||||
if (Screen.hasShiftDown()) {
|
if (Screen.hasShiftDown()) {
|
||||||
C2STerminalCraftingButton.Action.REQUEST_MAX_MORE
|
C2STerminalCraftingButton.Action.REQUEST_MAX_MORE
|
||||||
} else {
|
} else {
|
||||||
C2STerminalCraftingButton.Action.REQUEST_ONE_MORE
|
C2STerminalCraftingButton.Action.REQUEST_ONE_MORE
|
||||||
}
|
}
|
||||||
client.player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, action))
|
client.player!!.networkHandler.sendPacket(C2STerminalCraftingButton(terminal, action))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue