diff --git a/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginClient.kt b/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginClient.kt index 25c700b..cb83c75 100644 --- a/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginClient.kt +++ b/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginClient.kt @@ -17,71 +17,72 @@ import java.lang.invoke.MethodHandles /** * @author shadowfacts */ -object PhyConPluginClient: ClientModInitializer, REIClientPlugin, AbstractTerminalScreen.SearchQueryListener { +object PhyConPluginClient : ClientModInitializer, REIClientPlugin, AbstractTerminalScreen.SearchQueryListener { - private val logger = LogManager.getLogger() - private var isHighlightingHandle: MethodHandle? = null + private val logger = LogManager.getLogger() + private var isHighlightingHandle: MethodHandle? = null - override fun onInitializeClient() { - ClientScreenInputEvent.MOUSE_RELEASED_PRE.register { client, screen, mouseX, mouseY, button -> - if (screen is AbstractTerminalScreen<*, *>) { - REIRuntime.getInstance().searchTextField?.also { - if (it.isFocused) { - screen.terminalVC.searchField.resignFirstResponder() - } else { - screen.terminalVC.searchField.becomeFirstResponder() - } - } - } - EventResult.pass() - } + override fun onInitializeClient() { + ClientScreenInputEvent.MOUSE_RELEASED_PRE.register { client, screen, mouseX, mouseY, button -> + if (screen is AbstractTerminalScreen<*, *>) { + REIRuntime.getInstance().searchTextField?.also { + if (it.isFocused) { + screen.terminalVC.searchField.resignFirstResponder() + } else { + screen.terminalVC.searchField.becomeFirstResponder() + } + } + } + EventResult.pass() + } - AbstractTerminalScreen.searchQueryListener = this - try { - val clazz = Class.forName("me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField") - isHighlightingHandle = MethodHandles.publicLookup().findStaticGetter(clazz, "isHighlighting", Boolean::class.java) - } catch (e: ReflectiveOperationException) { - logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e) - } - } + AbstractTerminalScreen.searchQueryListener = this + try { + val clazz = Class.forName("me.shedaniel.rei.impl.client.gui.widget.search.OverlaySearchField") + isHighlightingHandle = + MethodHandles.publicLookup().findStaticGetter(clazz, "isHighlighting", Boolean::class.java) + } catch (e: ReflectiveOperationException) { + logger.warn("Unable to find OverlaySearchField.isHighlighting, highlight sync will be disabled", e) + } + } - override fun registerScreens(registry: ScreenRegistry) { - registry.exclusionZones().register(AbstractTerminalScreen::class.java) { - val screen = MinecraftClient.getInstance().currentScreen as AbstractTerminalScreen<*, *> - val view = screen.terminalVC.settingsView - val rect = view.convert(view.bounds, to = null) - listOf( - Rectangle(rect.left.toInt(), rect.top.toInt(), view.bounds.width.toInt(), view.bounds.height.toInt()) - ) - } - } + override fun registerScreens(registry: ScreenRegistry) { + registry.exclusionZones().register(AbstractTerminalScreen::class.java) { + val screen = MinecraftClient.getInstance().currentScreen as AbstractTerminalScreen<*, *> + val view = screen.terminalVC.settingsView + val rect = view.convert(view.bounds, to = null) + listOf( + Rectangle(rect.left.toInt(), rect.top.toInt(), view.bounds.width.toInt(), view.bounds.height.toInt()) + ) + } + } - override fun terminalSearchQueryChanged(newValue: String) { - if (shouldSync()) { - REIRuntime.getInstance().searchTextField?.text = newValue - } - } + override fun terminalSearchQueryChanged(newValue: String) { + if (shouldSync()) { + REIRuntime.getInstance().searchTextField?.text = newValue + } + } - override fun requestTerminalSearchFieldUpdate(): String? { - return if (shouldSync()) { - REIRuntime.getInstance().searchTextField?.text - } else { - null - } - } + override fun requestTerminalSearchFieldUpdate(): String? { + return if (shouldSync()) { + REIRuntime.getInstance().searchTextField?.text + } else { + null + } + } - private fun shouldSync(): Boolean { - return when (PhysicalConnectivityClient.terminalSettings[PhyConPluginCommon.REI_SYNC_KEY]) { - REISyncMode.OFF -> false - REISyncMode.ON -> true - REISyncMode.HIGHLIGHT_ONLY -> { - if (isHighlightingHandle != null) { - isHighlightingHandle!!.invoke() as Boolean - } else { - false - } - } - } - } + private fun shouldSync(): Boolean { + return when (PhysicalConnectivityClient.terminalSettings[PhyConPluginCommon.REI_SYNC_KEY]) { + REISyncMode.OFF -> false + REISyncMode.ON -> true + REISyncMode.HIGHLIGHT_ONLY -> { + if (isHighlightingHandle != null) { + isHighlightingHandle!!.invoke() as Boolean + } else { + false + } + } + } + } } diff --git a/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginCommon.kt b/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginCommon.kt index 961d277..da0ea33 100644 --- a/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginCommon.kt +++ b/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/PhyConPluginCommon.kt @@ -18,51 +18,55 @@ import java.util.stream.IntStream /** * @author shadowfacts */ -object PhyConPluginCommon: REIServerPlugin, PhyConPlugin { - const val MODID = "phycon_rei" +object PhyConPluginCommon : REIServerPlugin, PhyConPlugin { + const val MODID = "phycon_rei" - lateinit var REI_SYNC_KEY: TerminalSettingKey - private set + lateinit var REI_SYNC_KEY: TerminalSettingKey + private set - override fun registerMenuInfo(registry: MenuInfoRegistry) { - registry.register(CategoryIdentifier.of("minecraft", "plugins/crafting"), CraftingTerminalScreenHandler::class.java, SimpleMenuInfoProvider.of(::TerminalInfo)) - } + override fun registerMenuInfo(registry: MenuInfoRegistry) { + registry.register( + CategoryIdentifier.of("minecraft", "plugins/crafting"), + CraftingTerminalScreenHandler::class.java, + SimpleMenuInfoProvider.of(::TerminalInfo) + ) + } - override fun initializePhyCon(api: PhyConAPI) { - REI_SYNC_KEY = api.registerTerminalSetting(Identifier(MODID, "rei_sync"), REISyncMode.OFF) - } + override fun initializePhyCon(api: PhyConAPI) { + REI_SYNC_KEY = api.registerTerminalSetting(Identifier(MODID, "rei_sync"), REISyncMode.OFF) + } - class TerminalInfo( - private val display: D, - ): SimpleGridMenuInfo { + class TerminalInfo( + private val display: D, + ) : SimpleGridMenuInfo { - override fun getCraftingResultSlotIndex(menu: CraftingTerminalScreenHandler): Int { - return menu.resultSlot.id - } + override fun getCraftingResultSlotIndex(menu: CraftingTerminalScreenHandler): Int { + return menu.resultSlot.id + } - override fun getInputStackSlotIds(context: MenuInfoContext): IntStream { - return IntStream.range(context.menu.craftingSlotsStart, context.menu.craftingSlotsEnd) - } + override fun getInputStackSlotIds(context: MenuInfoContext): IntStream { + return IntStream.range(context.menu.craftingSlotsStart, context.menu.craftingSlotsEnd) + } - override fun getInventorySlots(context: MenuInfoContext): Iterable { - val slots = super.getInventorySlots(context).toMutableList() - for (i in (context.menu.bufferSlotsStart until context.menu.bufferSlotsEnd)) { - slots.add(SlotAccessor.fromSlot(context.menu.getSlot(i))) - } - return slots - } + override fun getInventorySlots(context: MenuInfoContext): Iterable { + val slots = super.getInventorySlots(context).toMutableList() + for (i in (context.menu.bufferSlotsStart until context.menu.bufferSlotsEnd)) { + slots.add(SlotAccessor.fromSlot(context.menu.getSlot(i))) + } + return slots + } - override fun getCraftingWidth(menu: CraftingTerminalScreenHandler): Int { - return 3 - } + override fun getCraftingWidth(menu: CraftingTerminalScreenHandler): Int { + return 3 + } - override fun getCraftingHeight(menu: CraftingTerminalScreenHandler): Int { - return 3 - } + override fun getCraftingHeight(menu: CraftingTerminalScreenHandler): Int { + return 3 + } - override fun getDisplay(): D { - return display - } - } + override fun getDisplay(): D { + return display + } + } } diff --git a/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/REISyncMode.kt b/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/REISyncMode.kt index 819e8c0..a226016 100644 --- a/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/REISyncMode.kt +++ b/plugin/rei/src/main/kotlin/net/shadowfacts/phycon/plugin/rei/REISyncMode.kt @@ -8,7 +8,7 @@ import net.shadowfacts.phycon.api.TerminalSetting /** * @author shadowfacts */ -enum class REISyncMode: TerminalSetting { +enum class REISyncMode : TerminalSetting { OFF, ON, HIGHLIGHT_ONLY; diff --git a/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/PhyConTR.kt b/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/PhyConTR.kt index 5958a5f..5338035 100644 --- a/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/PhyConTR.kt +++ b/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/PhyConTR.kt @@ -14,18 +14,27 @@ import techreborn.init.TRContent /** * @author shadowfacts */ -object PhyConTR: ModInitializer { +object PhyConTR : ModInitializer { - override fun onInitialize() { - TRContent.StorageUnit.values().forEach { - ItemAttributes.GROUPED_INV.setBlockAdder(AttributeSourceType.COMPAT_WRAPPER, it.block, ::addStorageUnitGroupedInv) - } - } + override fun onInitialize() { + TRContent.StorageUnit.values().forEach { + ItemAttributes.GROUPED_INV.setBlockAdder( + AttributeSourceType.COMPAT_WRAPPER, + it.block, + ::addStorageUnitGroupedInv + ) + } + } - private fun addStorageUnitGroupedInv(world: World, pos: BlockPos, state: BlockState, to: AttributeList) { - (world.getBlockEntity(pos) as? StorageUnitBaseBlockEntity)?.also { su -> - to.offer(StorageUnitWrapper(su)) - } - } + private fun addStorageUnitGroupedInv( + world: World, + pos: BlockPos, + state: BlockState, + to: AttributeList + ) { + (world.getBlockEntity(pos) as? StorageUnitBaseBlockEntity)?.also { su -> + to.offer(StorageUnitWrapper(su)) + } + } } diff --git a/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/StorageUnitWrapper.kt b/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/StorageUnitWrapper.kt index 84ad1d4..2b3cd05 100644 --- a/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/StorageUnitWrapper.kt +++ b/plugin/techreborn/src/main/kotlin/net/shadowfacts/phycon/plugin/techreborn/StorageUnitWrapper.kt @@ -15,67 +15,67 @@ import kotlin.math.min * @author shadowfacts */ class StorageUnitWrapper( - val be: StorageUnitBaseBlockEntity, -): GroupedItemInv { + val be: StorageUnitBaseBlockEntity, +) : GroupedItemInv { - override fun getStoredStacks(): Set { - val set = ItemStackCollections.set() - if (!be.storedStack.isEmpty) { - set.add(be.storedStack) - } - return set - } + override fun getStoredStacks(): Set { + val set = ItemStackCollections.set() + if (!be.storedStack.isEmpty) { + set.add(be.storedStack) + } + return set + } - override fun getTotalCapacity(): Int { - return be.maxCapacity - } + override fun getTotalCapacity(): Int { + return be.maxCapacity + } - override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic { - // todo: should spaceAddable really be zero? that's what SimpleGroupedItemInv does - return if (be.storedStack.isEmpty) { - GroupedItemInvView.ItemInvStatistic(filter, 0, 0, totalCapacity) - } else if (filter.matches(be.storedStack)) { - // don't use the storedAmount field, it's only used on the client for rendering - val amount = be.getStoredAmount() - GroupedItemInvView.ItemInvStatistic(filter, amount, 0, totalCapacity - amount) - } else { - GroupedItemInvView.ItemInvStatistic(filter, 0, 0, 0) - } - } + override fun getStatistics(filter: ItemFilter): GroupedItemInvView.ItemInvStatistic { + // todo: should spaceAddable really be zero? that's what SimpleGroupedItemInv does + return if (be.storedStack.isEmpty) { + GroupedItemInvView.ItemInvStatistic(filter, 0, 0, totalCapacity) + } else if (filter.matches(be.storedStack)) { + // don't use the storedAmount field, it's only used on the client for rendering + val amount = be.getStoredAmount() + GroupedItemInvView.ItemInvStatistic(filter, amount, 0, totalCapacity - amount) + } else { + GroupedItemInvView.ItemInvStatistic(filter, 0, 0, 0) + } + } - override fun attemptInsertion(filter: ItemStack, simulation: Simulation): ItemStack { - if (simulation.isAction) { - return be.processInput(filter) - } + override fun attemptInsertion(filter: ItemStack, simulation: Simulation): ItemStack { + if (simulation.isAction) { + return be.processInput(filter) + } - if (be.storedStack.isEmpty) { - return ItemStack.EMPTY - } + if (be.storedStack.isEmpty) { + return ItemStack.EMPTY + } - if (!ItemStackUtil.areEqualIgnoreAmounts(be.storedStack, filter)) { - return filter - } + if (!ItemStackUtil.areEqualIgnoreAmounts(be.storedStack, filter)) { + return filter + } - val availableCapacity = totalCapacity - be.getStoredAmount() - return if (availableCapacity >= filter.count) { - ItemStack.EMPTY - } else { - filter.copyWithCount(filter.count - availableCapacity) - } - } + val availableCapacity = totalCapacity - be.getStoredAmount() + return if (availableCapacity >= filter.count) { + ItemStack.EMPTY + } else { + filter.copyWithCount(filter.count - availableCapacity) + } + } - override fun attemptExtraction(filter: ItemFilter, maxAmount: Int, simulation: Simulation): ItemStack { - if (be.storedStack.isEmpty || !filter.matches(be.storedStack)) { - return ItemStack.EMPTY - } + override fun attemptExtraction(filter: ItemFilter, maxAmount: Int, simulation: Simulation): ItemStack { + if (be.storedStack.isEmpty || !filter.matches(be.storedStack)) { + return ItemStack.EMPTY + } - val extracted = min(maxAmount, be.getStoredAmount()) + val extracted = min(maxAmount, be.getStoredAmount()) - if (simulation.isAction) { - be.storedStack.decrement(extracted) - } + if (simulation.isAction) { + be.storedStack.decrement(extracted) + } - return be.storedStack.copyWithCount(extracted) - } + return be.storedStack.copyWithCount(extracted) + } } diff --git a/src/main/java/net/shadowfacts/phycon/api/Interface.java b/src/main/java/net/shadowfacts/phycon/api/Interface.java index 528596f..c593045 100644 --- a/src/main/java/net/shadowfacts/phycon/api/Interface.java +++ b/src/main/java/net/shadowfacts/phycon/api/Interface.java @@ -16,6 +16,7 @@ public interface Interface { void send(@NotNull EthernetFrame frame); - default void cableDisconnected() {} + default void cableDisconnected() { + } } diff --git a/src/main/java/net/shadowfacts/phycon/api/NetworkDevice.java b/src/main/java/net/shadowfacts/phycon/api/NetworkDevice.java index 07eaad2..08c5a98 100644 --- a/src/main/java/net/shadowfacts/phycon/api/NetworkDevice.java +++ b/src/main/java/net/shadowfacts/phycon/api/NetworkDevice.java @@ -10,9 +10,9 @@ public interface NetworkDevice { /** * The IP address of this device. - * + *

* If a device has not been assigned an address by a DHCP server, it may self-assign a randomly generated one. - * + *

* The address of a network device should never be the broadcast address. * * @return The IP address of this device. diff --git a/src/main/java/net/shadowfacts/phycon/api/TerminalSetting.java b/src/main/java/net/shadowfacts/phycon/api/TerminalSetting.java index fbeb93c..1d49746 100644 --- a/src/main/java/net/shadowfacts/phycon/api/TerminalSetting.java +++ b/src/main/java/net/shadowfacts/phycon/api/TerminalSetting.java @@ -13,6 +13,7 @@ public interface TerminalSetting { int[] getUV(); - @Nullable Text getTooltip(); + @Nullable + Text getTooltip(); } diff --git a/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java b/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java index 63e6a9b..3fc6bcf 100644 --- a/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java +++ b/src/main/java/net/shadowfacts/phycon/api/util/IPAddress.java @@ -23,12 +23,14 @@ public final class IPAddress { } private static final Random ipAddressRandom = new Random(); + @NotNull public static IPAddress random() { return random(ipAddressRandom); } private static final Pattern IP_PATTERN = Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"); + @Nullable public static IPAddress parse(String s) { Matcher matcher = IP_PATTERN.matcher(s); diff --git a/src/main/java/net/shadowfacts/phycon/api/util/MACAddress.java b/src/main/java/net/shadowfacts/phycon/api/util/MACAddress.java index 92096b7..38a0f6d 100644 --- a/src/main/java/net/shadowfacts/phycon/api/util/MACAddress.java +++ b/src/main/java/net/shadowfacts/phycon/api/util/MACAddress.java @@ -22,6 +22,7 @@ public final class MACAddress { } private static final Random macAddressRandom = new Random(); + @NotNull public static MACAddress random() { return random(macAddressRandom); @@ -61,7 +62,7 @@ public final class MACAddress { public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - MACAddress that = (MACAddress)o; + MACAddress that = (MACAddress) o; return address == that.address; } diff --git a/src/main/java/net/shadowfacts/phycon/mixin/client/MixinHandledScreen.java b/src/main/java/net/shadowfacts/phycon/mixin/client/MixinHandledScreen.java index 8bba4df..51317be 100644 --- a/src/main/java/net/shadowfacts/phycon/mixin/client/MixinHandledScreen.java +++ b/src/main/java/net/shadowfacts/phycon/mixin/client/MixinHandledScreen.java @@ -26,7 +26,7 @@ public class MixinHandledScreen { at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/systems/RenderSystem;enableDepthTest()V") ) 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); } } @@ -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") ) 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(); if (slot.id < handler.getNetworkSlotsEnd() && stack.getCount() > 1) { self.drawNetworkSlotAmount(stack, x, y); diff --git a/src/main/kotlin/net/shadowfacts/cacao/AbstractCacaoScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/AbstractCacaoScreen.kt index 2a36d90..ef41645 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/AbstractCacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/AbstractCacaoScreen.kt @@ -7,13 +7,13 @@ import net.shadowfacts.cacao.window.Window */ interface AbstractCacaoScreen { - val windows: List + val windows: List - fun addWindow(window: T, index: Int): T - fun addWindow(window: T): T + fun addWindow(window: T, index: Int): T + fun addWindow(window: T): T - fun removeWindow(window: Window) + fun removeWindow(window: Window) - fun screenWillAppear() + fun screenWillAppear() } diff --git a/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt index 37769f6..1e7aa90 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/CacaoHandledScreen.kt @@ -20,177 +20,177 @@ import java.util.* /** * @author shadowfacts */ -open class CacaoHandledScreen( - handler: Handler, - playerInv: PlayerInventory, - title: Text, -): HandledScreen(handler, playerInv, title), AbstractCacaoScreen { +open class CacaoHandledScreen( + handler: Handler, + playerInv: PlayerInventory, + title: Text, +) : HandledScreen(handler, playerInv, title), AbstractCacaoScreen { - private val _windows = LinkedList() + private val _windows = LinkedList() - override val windows: List = _windows + override val windows: List = _windows - private var hasAppeared = false + private var hasAppeared = false - override fun addWindow(window: T, index: Int): T { - if (window is ScreenHandlerWindow && window.screenHandler != handler) { - throw RuntimeException("Adding ScreenHandlerWindow to CacaoHandledScreen with different screen handler is not supported") - } - if (hasAppeared) { - window.viewController.viewWillAppear() - } + override fun addWindow(window: T, index: Int): T { + if (window is ScreenHandlerWindow && window.screenHandler != handler) { + throw RuntimeException("Adding ScreenHandlerWindow to CacaoHandledScreen with different screen handler is not supported") + } + if (hasAppeared) { + window.viewController.viewWillAppear() + } - _windows.add(index, window) + _windows.add(index, window) - window.screen = this - window.wasAdded() - window.resize(width, height) + window.screen = this + window.wasAdded() + window.resize(width, height) - return window - } + return window + } - override fun addWindow(window: T): T { - return addWindow(window, _windows.size) - } + override fun addWindow(window: T): T { + return addWindow(window, _windows.size) + } - override fun removeWindow(window: Window) { - _windows.remove(window) - if (windows.isEmpty()) { - close() - } - } + override fun removeWindow(window: Window) { + _windows.remove(window) + if (windows.isEmpty()) { + close() + } + } - override fun screenWillAppear() { - windows.forEach { - it.viewController.viewWillAppear() - } - } + override fun screenWillAppear() { + windows.forEach { + it.viewController.viewWillAppear() + } + } - override fun init() { - super.init() + override fun init() { + super.init() - windows.forEach { - it.resize(width, height) - } - } + windows.forEach { + it.resize(width, height) + } + } - override fun close() { - super.close() + override fun close() { + super.close() - windows.forEach { - it.viewController.viewWillDisappear() - it.viewController.viewDidDisappear() + windows.forEach { + it.viewController.viewWillDisappear() + 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) { - // no-op - } + override fun drawForeground(matrixStack: MatrixStack, mouseX: Int, mouseY: Int) { + // no-op + } - override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { - val mouse = Point(mouseX, mouseY) + override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { + val mouse = Point(mouseX, mouseY) - matrixStack.push() - matrixStack.translate(0.0, 0.0, -350.0) + matrixStack.push() + matrixStack.translate(0.0, 0.0, -350.0) - for (i in windows.indices) { - val it = windows[i] + for (i in windows.indices) { + val it = windows[i] - if (i == windows.size - 1) { - renderBackground(matrixStack) - } + if (i == windows.size - 1) { + renderBackground(matrixStack) + } - if (it is ScreenHandlerWindow) { - if (i == windows.size - 1) { - super.render(matrixStack, mouseX, mouseY, delta) - } else { - // 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) - } + if (it is ScreenHandlerWindow) { + if (i == windows.size - 1) { + super.render(matrixStack, mouseX, mouseY, delta) + } else { + // 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) + } - 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 { - val window = windows.lastOrNull() - val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) - return if (result == true) { - RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) - true - } else if (window is ScreenHandlerWindow) { - super.mouseClicked(mouseX, mouseY, button) - } else { - false - } - } + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + val window = windows.lastOrNull() + val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) + return if (result == true) { + RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) + true + } else if (window is ScreenHandlerWindow) { + super.mouseClicked(mouseX, mouseY, button) + } else { + false + } + } - override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { - val window = windows.lastOrNull() - val startPoint = Point(mouseX, mouseY) - val delta = Point(deltaX, deltaY) - val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button)) - return if (result == true) { - true - } else if (window is ScreenHandlerWindow) { - return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) - } else { - false - } - } + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { + val window = windows.lastOrNull() + val startPoint = Point(mouseX, mouseY) + val delta = Point(deltaX, deltaY) + val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button)) + return if (result == true) { + true + } else if (window is ScreenHandlerWindow) { + return super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY) + } else { + false + } + } - override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { - val window = windows.lastOrNull() - val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button)) - return if (result == true) { - true - } else if (window is ScreenHandlerWindow) { - super.mouseReleased(mouseX, mouseY, button) - } else { - false - } - } + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + val window = windows.lastOrNull() + val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button)) + return if (result == true) { + true + } else if (window is ScreenHandlerWindow) { + super.mouseReleased(mouseX, mouseY, button) + } else { + false + } + } - override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean { - val window = windows.lastOrNull() - val result = window?.mouseScrolled(Point(mouseX, mouseY), amount) - return result == true - } + override fun mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean { + val window = windows.lastOrNull() + val result = window?.mouseScrolled(Point(mouseX, mouseY), amount) + return result == true + } - override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - 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 keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + 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 { - val modifiersSet by lazy { KeyModifiers(modifiers) } - if (findResponder { it.charTyped(char, modifiersSet) }) { - return true - } - return super.charTyped(char, modifiers) - } + override fun charTyped(char: Char, modifiers: Int): Boolean { + 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 - } + override fun shouldCloseOnEsc(): Boolean { + return false + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt index b906a3d..e8b6cef 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/CacaoScreen.kt @@ -20,166 +20,167 @@ import java.util.* * * @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. - private val _windows = LinkedList() - /** - * 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 = _windows + // _windows is the internal, mutable object, since we only want it to by mutated by the add/removeWindow methods. + private val _windows = LinkedList() - 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 = _windows - /** - * 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 addWindow(window: T, index: Int): T { - if (hasAppeared) { - window.viewController.viewWillAppear() - } + private var hasAppeared = false - _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 addWindow(window: T, index: Int): T { + if (hasAppeared) { + window.viewController.viewWillAppear() + } - window.screen = this - window.wasAdded() - window.resize(width, height) + _windows.add(index, window) - return window - } + window.screen = this + window.wasAdded() + window.resize(width, height) - /** - * Adds the given window to the end of this screen's window list, making it the active window. - */ - override fun addWindow(window: T): T { - return addWindow(window, _windows.size) - } + return window + } - /** - * Removes the given window from this screen's window list. - */ - override fun removeWindow(window: Window) { - _windows.remove(window) - if (windows.isEmpty()) { - close() - } - } + /** + * Adds the given window to the end of this screen's window list, making it the active window. + */ + override fun addWindow(window: T): T { + return addWindow(window, _windows.size) + } - override fun screenWillAppear() { - windows.forEach { - it.viewController.viewWillAppear() - } - } + /** + * Removes the given window from this screen's window list. + */ + override fun removeWindow(window: Window) { + _windows.remove(window) + if (windows.isEmpty()) { + close() + } + } - override fun init() { - super.init() + override fun screenWillAppear() { + windows.forEach { + it.viewController.viewWillAppear() + } + } - windows.forEach { - it.resize(width, height) - } - } + override fun init() { + super.init() - override fun close() { - super.close() + windows.forEach { + it.resize(width, height) + } + } - windows.forEach { - it.viewController.viewWillDisappear() - it.viewController.viewDidDisappear() + override fun close() { + super.close() - // resign the current first responder (if any) - it.firstResponder = null - } - } + windows.forEach { + it.viewController.viewWillDisappear() + it.viewController.viewDidDisappear() - override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { - if (client != null) { - // workaround this.minecraft sometimes being null causing a crash - renderBackground(matrixStack) - } + // resign the current first responder (if any) + it.firstResponder = null + } + } - val mouse = Point(mouseX, mouseY) - windows.forEach { - it.draw(matrixStack, mouse, delta) - } - } + override fun render(matrixStack: MatrixStack, mouseX: Int, mouseY: Int, delta: Float) { + if (client != null) { + // workaround this.minecraft sometimes being null causing a crash + renderBackground(matrixStack) + } - override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { - val window = windows.lastOrNull() - val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) - return if (result == true) { - RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) - true - } else { - false - } - } + val mouse = Point(mouseX, mouseY) + windows.forEach { + it.draw(matrixStack, mouse, delta) + } + } - override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { - val window = windows.lastOrNull() - val startPoint = Point(mouseX, mouseY) - val delta = Point(deltaX, deltaY) - val result = window?.mouseDragged(startPoint, delta, MouseButton.fromMC(button)) - return result == true - } + override fun mouseClicked(mouseX: Double, mouseY: Double, button: Int): Boolean { + val window = windows.lastOrNull() + val result = window?.mouseClicked(Point(mouseX, mouseY), MouseButton.fromMC(button)) + return if (result == true) { + RenderHelper.playSound(SoundEvents.UI_BUTTON_CLICK) + true + } else { + false + } + } - override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { - val window = windows.lastOrNull() - val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button)) - return result == true - } + override fun mouseDragged(mouseX: Double, mouseY: Double, button: Int, deltaX: Double, deltaY: Double): Boolean { + val window = windows.lastOrNull() + val startPoint = Point(mouseX, mouseY) + 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 { - val window = windows.lastOrNull() - val result = window?.mouseScrolled(Point(mouseX, mouseY), amount) - return result == true - } + override fun mouseReleased(mouseX: Double, mouseY: Double, button: Int): Boolean { + val window = windows.lastOrNull() + val result = window?.mouseReleased(Point(mouseX, mouseY), MouseButton.fromMC(button)) + return result == true + } - override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { - 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 mouseScrolled(mouseX: Double, mouseY: Double, amount: Double): Boolean { + val window = windows.lastOrNull() + val result = window?.mouseScrolled(Point(mouseX, mouseY), amount) + return result == true + } - override fun keyReleased(i: Int, j: Int, k: Int): Boolean { - return super.keyReleased(i, j, k) - } + override fun keyPressed(keyCode: Int, scanCode: Int, modifiers: Int): Boolean { + 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 { - val modifiersSet by lazy { KeyModifiers(modifiers) } - if (findResponder { it.charTyped(char, modifiersSet) }) { - return true - } - return super.charTyped(char, modifiers) - } + override fun keyReleased(i: Int, j: Int, k: Int): Boolean { + return super.keyReleased(i, j, k) + } - override fun shouldCloseOnEsc(): Boolean { - return false - } + override fun charTyped(char: Char, modifiers: Int): Boolean { + 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 { - var responder = windows.lastOrNull()?.firstResponder - while (responder != null) { - if (fn(responder)) { - return true - } - responder = responder.nextResponder - } - return false + var responder = windows.lastOrNull()?.firstResponder + while (responder != null) { + if (fn(responder)) { + return true + } + responder = responder.nextResponder + } + return false } diff --git a/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt b/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt index 3499002..8dbc81e 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/LayoutVariable.kt @@ -11,22 +11,22 @@ import no.birkett.kiwi.Variable * @author shadowfacts */ class LayoutVariable( - val view: View?, - val layoutGuide: LayoutGuide?, - val property: String, -): Variable("LayoutVariable") { + val view: View?, + val layoutGuide: LayoutGuide?, + val property: String, +) : Variable("LayoutVariable") { - constructor(view: View, property: String): this(view, null, property) - constructor(layoutGuide: LayoutGuide, property: String): this(null, layoutGuide, property) + constructor(view: View, property: String) : this(view, null, property) + constructor(layoutGuide: LayoutGuide, property: String) : this(null, layoutGuide, property) - init { - if ((view == null) == (layoutGuide == null)) { - throw RuntimeException("LayoutVariable must be constructed with either a view or layout guide") - } - } + init { + if ((view == null) == (layoutGuide == null)) { + 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)" } diff --git a/src/main/kotlin/net/shadowfacts/cacao/README.md b/src/main/kotlin/net/shadowfacts/cacao/README.md index c873a79..33b0f3b 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/README.md +++ b/src/main/kotlin/net/shadowfacts/cacao/README.md @@ -1,9 +1,12 @@ # Cacao + Cacao is a UI framework for Fabric/Minecraft mods based on Apple's [Cocoa](https://en.wikipedia.org/wiki/Cocoa_(API) UI toolkit. ## Architecture + ### Screen + 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 @@ -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 ### Window + 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. @@ -21,6 +25,7 @@ view hierarchy. [Window]: https://git.shadowfacts.net/minecraft/ASMR/src/branch/master/src/main/kotlin/net/shadowfacts/cacao/Window.kt ### View Controller + 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. @@ -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 ### 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 diff --git a/src/main/kotlin/net/shadowfacts/cacao/Responder.kt b/src/main/kotlin/net/shadowfacts/cacao/Responder.kt index e52c0d8..1a7f96a 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/Responder.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/Responder.kt @@ -12,89 +12,89 @@ import net.shadowfacts.cacao.window.Window */ interface Responder { - /** - * The window that this responder is part of. - */ - val window: Window? + /** + * The window that this responder is part of. + */ + val window: Window? - /** - * Whether this responder is the first responder of its window. - * - * `false` if [window] is null. - * - * @see Window.firstResponder - */ - val isFirstResponder: Boolean - get() = window?.firstResponder === this + /** + * Whether this responder is the first responder of its window. + * + * `false` if [window] is null. + * + * @see Window.firstResponder + */ + val isFirstResponder: Boolean + get() = window?.firstResponder === this - /** - * The next responder in the chain after this. The next responder will receive an event if this responder did not - * accept it. - * - * The next responder may be `null` if this responder is at the end of the chain. - */ - val nextResponder: Responder? + /** + * The next responder in the chain after this. The next responder will receive an event if this responder did not + * accept it. + * + * The next responder may be `null` if this responder is at the end of the chain. + */ + val nextResponder: Responder? - /** - * Makes this responder become the window's first responder. - * @throws RuntimeException if [window] is null - * @see Window.firstResponder - */ - fun becomeFirstResponder() { - if (window == null) { - throw RuntimeException("Cannot become first responder while not in Window") - } - window!!.firstResponder = this - } + /** + * Makes this responder become the window's first responder. + * @throws RuntimeException if [window] is null + * @see Window.firstResponder + */ + fun becomeFirstResponder() { + if (window == null) { + throw RuntimeException("Cannot become first responder while not in Window") + } + window!!.firstResponder = this + } - /** - * Called immediately after this responder has become the window's first responder. - * @see Window.firstResponder - */ - fun didBecomeFirstResponder() {} + /** + * Called immediately after this responder has become the window's first responder. + * @see Window.firstResponder + */ + fun didBecomeFirstResponder() {} - /** - * Removes this object as the window's first responder. - * @throws RuntimeException if [window] is null - * @see Window.firstResponder - */ - fun resignFirstResponder() { - if (window == null) { - throw RuntimeException("Cannot resign first responder while not in Window") - } - window!!.firstResponder = null - } + /** + * Removes this object as the window's first responder. + * @throws RuntimeException if [window] is null + * @see Window.firstResponder + */ + fun resignFirstResponder() { + if (window == null) { + throw RuntimeException("Cannot resign first responder while not in Window") + } + window!!.firstResponder = null + } - /** - * Called immediately before this object is removed as the window's first responder. - * @see Window.firstResponder - */ - fun didResignFirstResponder() {} + /** + * Called immediately before this object is removed as the window's first responder. + * @see Window.firstResponder + */ + fun didResignFirstResponder() {} - /** - * Called when a character has been typed. - * - * @param char The character that 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. - */ - fun charTyped(char: Char, modifiers: KeyModifiers): Boolean { - return false - } + /** + * Called when a character has been typed. + * + * @param char The character that 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. + */ + fun charTyped(char: Char, modifiers: KeyModifiers): Boolean { + return false + } - /** - * 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 - * invoked is undefined and should not be relied upon. - * - * @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. - * @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 - */ - fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean { - return false - } + /** + * 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 + * invoked is undefined and should not be relied upon. + * + * @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. + * @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 + */ + fun keyPressed(keyCode: Int, modifiers: KeyModifiers): Boolean { + return false + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt index b77a31f..1113a12 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Axis.kt @@ -6,14 +6,14 @@ package net.shadowfacts.cacao.geometry * @author shadowfacts */ enum class Axis { - HORIZONTAL, VERTICAL; + HORIZONTAL, VERTICAL; - /** - * Gets the axis that is perpendicular to this one. - */ - val perpendicular: Axis - get() = when (this) { - HORIZONTAL -> VERTICAL - VERTICAL -> HORIZONTAL - } -} \ No newline at end of file + /** + * Gets the axis that is perpendicular to this one. + */ + val perpendicular: Axis + get() = when (this) { + HORIZONTAL -> VERTICAL + VERTICAL -> HORIZONTAL + } +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt index 5d3a7f5..76a5511 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/AxisPosition.kt @@ -6,16 +6,18 @@ package net.shadowfacts.cacao.geometry * @author shadowfacts */ enum class AxisPosition { - /** - * Top for vertical, left for horizontal. - */ - LEADING, - /** - * Center X/Y. - */ - CENTER, - /** - * Bottom for vertical, right for horizontal. - */ - TRAILING; -} \ No newline at end of file + /** + * Top for vertical, left for horizontal. + */ + LEADING, + + /** + * Center X/Y. + */ + CENTER, + + /** + * Bottom for vertical, right for horizontal. + */ + TRAILING; +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/BezierCurve.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/BezierCurve.kt index 60a4453..99df721 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/BezierCurve.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/BezierCurve.kt @@ -10,40 +10,40 @@ import kotlin.math.pow */ data class BezierCurve(private val points: Array) { - init { - if (points.size != 4) { - throw RuntimeException("Cubic bezier curve must have exactly four points") - } - } + init { + if (points.size != 4) { + throw RuntimeException("Cubic bezier curve must have exactly four points") + } + } - fun point(time: Double): Point { - val x = coordinate(time, Axis.HORIZONTAL) - val y = coordinate(time, Axis.VERTICAL) - return Point(x, y) - } + fun point(time: Double): Point { + val x = coordinate(time, Axis.HORIZONTAL) + val y = coordinate(time, Axis.VERTICAL) + return Point(x, y) + } - 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 - val p0 = points[0][axis] - val p1 = points[1][axis] - val p2 = points[2][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) - } + 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 + val p0 = points[0][axis] + val p1 = points[1][axis] + val p2 = points[2][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) + } - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false + override fun equals(other: Any?): Boolean { + if (this === other) return true + 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 { - return points.contentHashCode() - } + override fun hashCode(): Int { + return points.contentHashCode() + } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt index 81406d4..c8bf865 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Point.kt @@ -7,25 +7,25 @@ package net.shadowfacts.cacao.geometry */ 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 { - val ORIGIN = Point(0.0, 0.0) - } + companion object { + val ORIGIN = Point(0.0, 0.0) + } - operator fun plus(other: Point): Point { - return Point(x + other.x, y + other.y) - } + operator fun plus(other: Point): Point { + return Point(x + other.x, y + other.y) + } - operator fun minus(other: Point): Point { - return Point(x - other.x, y - other.y) - } + operator fun minus(other: Point): Point { + return Point(x - other.x, y - other.y) + } - operator fun get(axis: Axis): Double { - return when (axis) { - Axis.HORIZONTAL -> x - Axis.VERTICAL -> y - } - } + operator fun get(axis: Axis): Double { + return when (axis) { + Axis.HORIZONTAL -> x + Axis.VERTICAL -> y + } + } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt b/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt index 04ff1ec..5c4208f 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/geometry/Rect.kt @@ -7,35 +7,35 @@ package net.shadowfacts.cacao.geometry */ 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 { - left + width - } - val bottom: Double by lazy { - top + height - } + val right: Double by lazy { + left + width + } + val bottom: Double by lazy { + top + height + } - val midX: Double by lazy { - left + width / 2 - } - val midY: Double by lazy { - top + height / 2 - } + val midX: Double by lazy { + left + width / 2 + } + val midY: Double by lazy { + top + height / 2 + } - val origin: Point by lazy { - Point(left, top) - } - val center: Point by lazy { - Point(midX, midY) - } + val origin: Point by lazy { + Point(left, top) + } + val center: Point by lazy { + Point(midX, midY) + } - val size: Size by lazy { - Size(width, height) - } + val size: Size by lazy { + Size(width, height) + } - operator fun contains(point: Point): Boolean { - return point.x >= left && point.x < right && point.y >= top && point.y < bottom - } + operator fun contains(point: Point): Boolean { + return point.x >= left && point.x < right && point.y >= top && point.y < bottom + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt b/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt index f9fdc45..da88b06 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/Color.kt @@ -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) { - /** - * 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) + /** + * 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) - /** - * The ARGB packed representation of this color. - */ - 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) + /** + * The ARGB packed representation of this color. + */ + 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) - companion object { - val CLEAR = Color(0, alpha = 0) - val WHITE = Color(0xffffff) - val BLACK = Color(0) - val RED = Color(0xff0000) - val GREEN = Color(0x00ff00) - val BLUE = Color(0x0000ff) - val MAGENTA = Color(0xfc46e4) + companion object { + val CLEAR = Color(0, alpha = 0) + val WHITE = Color(0xffffff) + val BLACK = Color(0) + val RED = Color(0xff0000) + val GREEN = Color(0x00ff00) + val BLUE = Color(0x0000ff) + val MAGENTA = Color(0xfc46e4) - val TEXT = Color(0x404040) - } + val TEXT = Color(0x404040) + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/EnumHelper.kt b/src/main/kotlin/net/shadowfacts/cacao/util/EnumHelper.kt index 3ed19eb..b7ecd35 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/EnumHelper.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/EnumHelper.kt @@ -5,16 +5,16 @@ package net.shadowfacts.cacao.util */ object EnumHelper { - fun > next(value: E): E { - val constants = value.declaringClass.enumConstants - val index = constants.indexOf(value) + 1 - return if (index < constants.size) constants[index] else constants.first() - } + fun > next(value: E): E { + val constants = value.declaringClass.enumConstants + val index = constants.indexOf(value) + 1 + return if (index < constants.size) constants[index] else constants.first() + } - fun > previous(value: E): E { - val constants = value.declaringClass.enumConstants - val index = constants.indexOf(value) - 1 - return if (index >= 0) constants[index] else constants.last() - } + fun > previous(value: E): E { + val constants = value.declaringClass.enumConstants + val index = constants.indexOf(value) - 1 + return if (index >= 0) constants[index] else constants.last() + } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt b/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt index fb5cf9e..cb7afbb 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/KeyModifiers.kt @@ -10,26 +10,26 @@ import org.lwjgl.glfw.GLFW */ class KeyModifiers(val value: Int) { - val shift: Boolean - get() = this[GLFW.GLFW_MOD_SHIFT] + val shift: Boolean + get() = this[GLFW.GLFW_MOD_SHIFT] - val control: Boolean - get() = this[GLFW.GLFW_MOD_CONTROL] + val control: Boolean + get() = this[GLFW.GLFW_MOD_CONTROL] - val alt: Boolean - get() = this[GLFW.GLFW_MOD_ALT] + val alt: Boolean + get() = this[GLFW.GLFW_MOD_ALT] - val command: Boolean - get() = this[GLFW.GLFW_MOD_SUPER] + val command: Boolean + get() = this[GLFW.GLFW_MOD_SUPER] - val capsLock: Boolean - get() = this[GLFW.GLFW_MOD_CAPS_LOCK] + val capsLock: Boolean + get() = this[GLFW.GLFW_MOD_CAPS_LOCK] - val numLock: Boolean - get() = this[GLFW.GLFW_MOD_NUM_LOCK] + val numLock: Boolean + get() = this[GLFW.GLFW_MOD_NUM_LOCK] - private operator fun get(mod: Int): Boolean { - return (value and mod) == mod - } + private operator fun get(mod: Int): Boolean { + return (value and mod) == mod + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/LayoutGuide.kt b/src/main/kotlin/net/shadowfacts/cacao/util/LayoutGuide.kt index b4a6642..98c7c29 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/LayoutGuide.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/LayoutGuide.kt @@ -15,19 +15,24 @@ import net.shadowfacts.cacao.view.View * @author shadowfacts */ class LayoutGuide( - val owningView: View, + val owningView: View, ) { - val leftAnchor: LayoutVariable = LayoutVariable(this, "left") - val rightAnchor: LayoutVariable = LayoutVariable(this, "right") - val topAnchor: LayoutVariable = LayoutVariable(this, "top") - val bottomAnchor: LayoutVariable = LayoutVariable(this, "bottom") - val widthAnchor: LayoutVariable = LayoutVariable(this, "width") - val heightAnchor: LayoutVariable = LayoutVariable(this, "height") - val centerXAnchor: LayoutVariable = LayoutVariable(this, "centerX") - val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY") + val leftAnchor: LayoutVariable = LayoutVariable(this, "left") + val rightAnchor: LayoutVariable = LayoutVariable(this, "right") + val topAnchor: LayoutVariable = LayoutVariable(this, "top") + val bottomAnchor: LayoutVariable = LayoutVariable(this, "bottom") + val widthAnchor: LayoutVariable = LayoutVariable(this, "width") + val heightAnchor: LayoutVariable = LayoutVariable(this, "height") + val centerXAnchor: LayoutVariable = LayoutVariable(this, "centerX") + val centerYAnchor: LayoutVariable = LayoutVariable(this, "centerY") - val frame: Rect - get() = Rect(leftAnchor.value - owningView.leftAnchor.value, topAnchor.value - owningView.topAnchor.value, widthAnchor.value, heightAnchor.value) + val frame: Rect + get() = Rect( + leftAnchor.value - owningView.leftAnchor.value, + topAnchor.value - owningView.topAnchor.value, + widthAnchor.value, + heightAnchor.value + ) } diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/LowestCommonAncestor.kt b/src/main/kotlin/net/shadowfacts/cacao/util/LowestCommonAncestor.kt index 6ee8951..4b2088f 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/LowestCommonAncestor.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/LowestCommonAncestor.kt @@ -14,43 +14,43 @@ import kotlin.NoSuchElementException */ object LowestCommonAncestor { - fun find(node1: Node, node2: Node, parent: Node.() -> Node?): Node? { - @Suppress("NAME_SHADOWING") var node1: Node? = node1 - @Suppress("NAME_SHADOWING") var node2: Node? = node2 + fun find(node1: Node, node2: Node, parent: Node.() -> Node?): Node? { + @Suppress("NAME_SHADOWING") var node1: Node? = node1 + @Suppress("NAME_SHADOWING") var node2: Node? = node2 - val parent1 = LinkedList() - while (node1 != null) { - parent1.push(node1) - node1 = node1.parent() - } + val parent1 = LinkedList() + while (node1 != null) { + parent1.push(node1) + node1 = node1.parent() + } - val parent2 = LinkedList() - while (node2 != null) { - parent2.push(node2) - node2 = node2.parent() - } + val parent2 = LinkedList() + while (node2 != null) { + parent2.push(node2) + node2 = node2.parent() + } - // paths don't converge on the same root element - if (parent1.first != parent2.first) { - return null - } + // paths don't converge on the same root element + if (parent1.first != parent2.first) { + return null + } - var oldNode: Node? = null - while (node1 == node2 && parent1.isNotEmpty() && parent2.isNotEmpty()) { - oldNode = node1 - node1 = parent1.popOrNull() - node2 = parent2.popOrNull() - } - return if (node1 == node2) node1!! - else oldNode!! - } + var oldNode: Node? = null + while (node1 == node2 && parent1.isNotEmpty() && parent2.isNotEmpty()) { + oldNode = node1 + node1 = parent1.popOrNull() + node2 = parent2.popOrNull() + } + return if (node1 == node2) node1!! + else oldNode!! + } } private fun LinkedList.popOrNull(): T? { - return try { - pop() - } catch (e: NoSuchElementException) { - null - } -} \ No newline at end of file + return try { + pop() + } catch (e: NoSuchElementException) { + null + } +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/MouseButton.kt b/src/main/kotlin/net/shadowfacts/cacao/util/MouseButton.kt index 52c54cc..ad74db9 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/MouseButton.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/MouseButton.kt @@ -4,16 +4,16 @@ package net.shadowfacts.cacao.util * @author shadowfacts */ enum class MouseButton { - LEFT, RIGHT, MIDDLE, UNKNOWN; + LEFT, RIGHT, MIDDLE, UNKNOWN; - companion object { - fun fromMC(button: Int): MouseButton { - return when (button) { - 0 -> LEFT - 1 -> RIGHT - 2 -> MIDDLE - else -> UNKNOWN - } - } - } -} \ No newline at end of file + companion object { + fun fromMC(button: Int): MouseButton { + return when (button) { + 0 -> LEFT + 1 -> RIGHT + 2 -> MIDDLE + else -> UNKNOWN + } + } + } +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt b/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt index 79971a5..2941251 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/RenderHelper.kt @@ -23,111 +23,153 @@ import kotlin.math.roundToInt * * @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 - fun playSound(event: SoundEvent) { - if (disabled) return - MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(event, 1f)) - } + // TODO: find a better place for this + fun playSound(event: SoundEvent) { + if (disabled) return + MinecraftClient.getInstance().soundManager.play(PositionedSoundInstance.master(event, 1f)) + } - /** - * Draws a solid [rect] filled with the given [color]. - */ - fun fill(matrixStack: MatrixStack, rect: Rect, color: Color) { - if (disabled) return - fill(matrixStack, rect.left.toInt(), rect.top.toInt(), rect.right.toInt(), rect.bottom.toInt(), color.argb) - } + /** + * Draws a solid [rect] filled with the given [color]. + */ + fun fill(matrixStack: MatrixStack, rect: Rect, color: Color) { + if (disabled) return + 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]. - */ - fun draw(matrixStack: MatrixStack, rect: Rect, texture: Texture) { - if (disabled) return - RenderSystem.setShader(GameRenderer::getPositionTexShader) - RenderSystem.setShaderTexture(0, texture.location) - draw(matrixStack, rect.left, rect.top, texture.u, texture.v, rect.width, rect.height, texture.width, texture.height) - } + /** + * Binds and draws the given [texture] filling the [rect]. + */ + fun draw(matrixStack: MatrixStack, rect: Rect, texture: Texture) { + if (disabled) return + RenderSystem.setShader(GameRenderer::getPositionTexShader) + RenderSystem.setShaderTexture(0, texture.location) + 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) { - if (disabled) return + fun drawLine(start: Point, end: Point, z: Double, width: Float, color: Color) { + if (disabled) return - RenderSystem.lineWidth(width) - RenderSystem.enableBlend() - RenderSystem.disableTexture() - RenderSystem.defaultBlendFunc() - RenderSystem.setShader(GameRenderer::getPositionColorShader) - val tessellator = Tessellator.getInstance() - 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() - } + RenderSystem.lineWidth(width) + RenderSystem.enableBlend() + RenderSystem.disableTexture() + RenderSystem.defaultBlendFunc() + RenderSystem.setShader(GameRenderer::getPositionColorShader) + val tessellator = Tessellator.getInstance() + 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. - */ - fun draw(matrixStack: MatrixStack, x: Double, y: Double, u: Int, v: Int, 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) - } + /** + * 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 + ) { + 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 - // 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) { - 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) - } + // Copied from net.minecraft.client.gui.DrawableHelper + // 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 + ) { + 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) { - drawTooltip(matrixStack, listOf(text.asOrderedText()), mouse) - } + fun drawTooltip(matrixStack: MatrixStack, text: Text, mouse: Point) { + drawTooltip(matrixStack, listOf(text.asOrderedText()), mouse) + } - fun drawTooltip(matrixStack: MatrixStack, texts: List, mouse: Point) { - drawTooltip(matrixStack, texts.map(Text::asOrderedText), mouse) - } + fun drawTooltip(matrixStack: MatrixStack, texts: List, mouse: Point) { + drawTooltip(matrixStack, texts.map(Text::asOrderedText), mouse) + } - private val dummyScreen = object: Screen(LiteralText("")) { - init { - textRenderer = MinecraftClient.getInstance().textRenderer - itemRenderer = MinecraftClient.getInstance().itemRenderer - } - } + private val dummyScreen = object : Screen(LiteralText("")) { + init { + textRenderer = MinecraftClient.getInstance().textRenderer + itemRenderer = MinecraftClient.getInstance().itemRenderer + } + } - @JvmName("drawOrderedTooltip") - fun drawTooltip(matrixStack: MatrixStack, texts: List, mouse: Point) { - if (disabled) return - if (texts.isEmpty()) return + @JvmName("drawOrderedTooltip") + fun drawTooltip(matrixStack: MatrixStack, texts: List, mouse: Point) { + if (disabled) return + if (texts.isEmpty()) return - val client = MinecraftClient.getInstance() - dummyScreen.width = client.window.scaledWidth - dummyScreen.height = client.window.scaledHeight - dummyScreen.renderOrderedTooltip(matrixStack, texts, mouse.x.roundToInt(), mouse.y.roundToInt()) - } + val client = MinecraftClient.getInstance() + dummyScreen.width = client.window.scaledWidth + dummyScreen.height = client.window.scaledHeight + dummyScreen.renderOrderedTooltip(matrixStack, texts, mouse.x.roundToInt(), mouse.y.roundToInt()) + } - /** - * @see org.lwjgl.opengl.GL11.glColor4f - */ - fun color(r: Float, g: Float, b: Float, alpha: Float) { - if (disabled) return - RenderSystem.setShaderColor(r, g, b, alpha) - } + /** + * @see org.lwjgl.opengl.GL11.glColor4f + */ + fun color(r: Float, g: Float, b: Float, alpha: Float) { + if (disabled) return + RenderSystem.setShaderColor(r, g, b, alpha) + } - private fun VertexConsumer.color(color: Color): VertexConsumer { - return color(color.red, color.green, color.blue, color.alpha) - } + private fun VertexConsumer.color(color: Color): VertexConsumer { + return color(color.red, color.green, color.blue, color.alpha) + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/constraints.kt b/src/main/kotlin/net/shadowfacts/cacao/util/constraints.kt index 3b74884..db4fa2c 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/constraints.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/constraints.kt @@ -10,5 +10,5 @@ import no.birkett.kiwi.Variable * @author shadowfacts */ fun Constraint.getVariables(): List { - return expression.terms.map(Term::getVariable) -} \ No newline at end of file + return expression.terms.map(Term::getVariable) +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt index 21e2176..1f2e9bc 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLateInitProperty.kt @@ -5,20 +5,20 @@ import kotlin.reflect.KProperty /** * @author shadowfacts */ -class ObservableLateInitProperty(val observer: (T) -> Unit) { +class ObservableLateInitProperty(val observer: (T) -> Unit) { - lateinit var storage: T + lateinit var storage: T - val isInitialized: Boolean - get() = this::storage.isInitialized + val isInitialized: Boolean + get() = this::storage.isInitialized - operator fun getValue(thisRef: Any, property: KProperty<*>): T { - return storage - } + operator fun getValue(thisRef: Any, property: KProperty<*>): T { + return storage + } - operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) { - storage = value - observer(value) - } + operator fun setValue(thisRef: Any, property: KProperty<*>, value: T) { + storage = value + observer(value) + } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLazyProperty.kt b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLazyProperty.kt index a912b59..88615ca 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLazyProperty.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ObservableLazyProperty.kt @@ -7,14 +7,14 @@ import kotlin.reflect.KProperty */ class ObservableLazyProperty(val create: () -> Value, val onCreate: () -> Unit) { - var storage: Value? = null + var storage: Value? = null - operator fun getValue(thisRef: Any, property: KProperty<*>): Value { - if (storage == null) { - storage = create() - onCreate() - } - return storage!! - } + operator fun getValue(thisRef: Any, property: KProperty<*>): Value { + if (storage == null) { + storage = create() + onCreate() + } + return storage!! + } -} \ No newline at end of file +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt index 753dab2..25ad9d9 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/properties/ResettableLazyProperty.kt @@ -6,19 +6,19 @@ import kotlin.reflect.KProperty * @author shadowfacts */ class ResettableLazyProperty(val initializer: () -> Value) { - var value: Value? = null + var value: Value? = null - val isInitialized: Boolean - get() = value != null + val isInitialized: Boolean + get() = value != null - operator fun getValue(thisRef: Any, property: KProperty<*>): Value { - if (value == null) { - value = initializer() - } - return value!! - } + operator fun getValue(thisRef: Any, property: KProperty<*>): Value { + if (value == null) { + value = initializer() + } + return value!! + } - fun reset() { - value = null - } -} \ No newline at end of file + fun reset() { + value = null + } +} diff --git a/src/main/kotlin/net/shadowfacts/cacao/util/texture/NinePatchTexture.kt b/src/main/kotlin/net/shadowfacts/cacao/util/texture/NinePatchTexture.kt index d922c24..48a1786 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/util/texture/NinePatchTexture.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/util/texture/NinePatchTexture.kt @@ -15,47 +15,53 @@ import net.minecraft.util.Identifier * @param centerWidth The width 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 { - val PANEL_BG = NinePatchTexture(Texture(Identifier("textures/gui/demo_background.png"), 0, 0), 5, 5, 238, 156) + companion object { + 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_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_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_DISABLED_BG = NinePatchTexture(Texture(Identifier("textures/gui/widgets.png"), 0, 46), 3, 3, 194, 14) + } - // Corners - val topLeft by lazy { - texture - } - val topRight by lazy { - Texture(texture.location, texture.u + cornerWidth + centerWidth, texture.v, texture.width, texture.height) - } - val bottomLeft by lazy { - Texture(texture.location, texture.u, texture.v + cornerHeight + centerHeight, texture.width, texture.height) - } - val bottomRight by lazy { - Texture(texture.location, topRight.u, bottomLeft.v, texture.width, texture.height) - } + // Corners + val topLeft by lazy { + texture + } + val topRight by lazy { + Texture(texture.location, texture.u + cornerWidth + centerWidth, texture.v, texture.width, texture.height) + } + val bottomLeft by lazy { + Texture(texture.location, texture.u, texture.v + cornerHeight + centerHeight, texture.width, texture.height) + } + val bottomRight by lazy { + Texture(texture.location, topRight.u, bottomLeft.v, texture.width, texture.height) + } - // Edges - val topMiddle by lazy { - Texture(texture.location, texture.u + cornerWidth, texture.v, texture.width, texture.height) - } - val bottomMiddle by lazy { - Texture(texture.location, topMiddle.u, bottomLeft.v, texture.width, texture.height) - } - val leftMiddle by lazy { - Texture(texture.location, texture.u, texture.v + cornerHeight, texture.width, texture.height) - } - val rightMiddle by lazy { - Texture(texture.location, topRight.u, leftMiddle.v, texture.width, texture.height) - } + // Edges + val topMiddle by lazy { + Texture(texture.location, texture.u + cornerWidth, texture.v, texture.width, texture.height) + } + val bottomMiddle by lazy { + Texture(texture.location, topMiddle.u, bottomLeft.v, texture.width, texture.height) + } + val leftMiddle by lazy { + Texture(texture.location, texture.u, texture.v + cornerHeight, texture.width, texture.height) + } + val rightMiddle by lazy { + Texture(texture.location, topRight.u, leftMiddle.v, texture.width, texture.height) + } - // Center - val center by lazy { - Texture(texture.location, texture.u + cornerWidth, texture.v + cornerHeight, texture.width, texture.height) - } + // Center + val center by lazy { + Texture(texture.location, texture.u + cornerWidth, texture.v + cornerHeight, texture.width, texture.height) + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/BezierCurveView.kt b/src/main/kotlin/net/shadowfacts/cacao/view/BezierCurveView.kt index 1f40e29..7905378 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/BezierCurveView.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/BezierCurveView.kt @@ -9,30 +9,30 @@ import net.shadowfacts.cacao.util.RenderHelper /** * @author shadowfacts */ -class BezierCurveView(val curve: BezierCurve): View() { +class BezierCurveView(val curve: BezierCurve) : View() { - private val points by lazy { - val step = 0.05 - var t = 0.0 - val points = mutableListOf() - while (t <= 1) { - points.add(curve.point(t)) - t += step - } - points - } + private val points by lazy { + val step = 0.05 + var t = 0.0 + val points = mutableListOf() + while (t <= 1) { + points.add(curve.point(t)) + t += step + } + points + } - var lineWidth = 3f - var lineColor = Color.BLACK + var lineWidth = 3f + var lineColor = Color.BLACK - override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { - matrixStack.push() - matrixStack.scale(bounds.width.toFloat(), bounds.height.toFloat(), 1f) - for ((index, point) in points.withIndex()) { - val next = points.getOrNull(index + 1) ?: break - RenderHelper.drawLine(point, next, zIndex, lineWidth, lineColor) - } - matrixStack.pop() - } + override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { + matrixStack.push() + matrixStack.scale(bounds.width.toFloat(), bounds.height.toFloat(), 1f) + for ((index, point) in points.withIndex()) { + val next = points.getOrNull(index + 1) ?: break + RenderHelper.drawLine(point, next, zIndex, lineWidth, lineColor) + } + matrixStack.pop() + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/DialogView.kt b/src/main/kotlin/net/shadowfacts/cacao/view/DialogView.kt index 278a2c9..1c23e6a 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/DialogView.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/DialogView.kt @@ -14,103 +14,103 @@ import net.shadowfacts.kiwidsl.dsl * @author shadowfacts */ class DialogView( - val title: Text, - val message: Text, - val buttonTypes: Array, - val iconTexture: Texture?, - val buttonCallback: (ButtonType, Window) -> Unit -): View() { + val title: Text, + val message: Text, + val buttonTypes: Array, + val iconTexture: Texture?, + val buttonCallback: (ButtonType, Window) -> Unit +) : View() { - interface ButtonType { - val localizedName: Text - } + interface ButtonType { + val localizedName: Text + } - enum class DefaultButtonType: ButtonType { - CANCEL, CONFIRM, OK, CLOSE; + enum class DefaultButtonType : ButtonType { + CANCEL, CONFIRM, OK, CLOSE; - override val localizedName: Text - get() = LiteralText(name.lowercase().replaceFirstChar(Char::titlecase)) // todo: actually localize me - } + override val localizedName: Text + get() = LiteralText(name.lowercase().replaceFirstChar(Char::titlecase)) // todo: actually localize me + } - private lateinit var background: NinePatchView - private lateinit var hStack: StackView - private var iconView: TextureView? = null - private lateinit var vStack: StackView - private lateinit var messageLabel: Label - private var buttonContainer: View? = null - private var buttonStack: StackView? = null + private lateinit var background: NinePatchView + private lateinit var hStack: StackView + private var iconView: TextureView? = null + private lateinit var vStack: StackView + private lateinit var messageLabel: Label + private var buttonContainer: View? = null + private var buttonStack: StackView? = null - override fun wasAdded() { - background = addSubview(NinePatchView(NinePatchTexture.PANEL_BG).apply { zIndex = -1.0 }) + override fun wasAdded() { + 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) { - iconView = hStack.addArrangedSubview(TextureView(iconTexture)) - } + if (iconTexture != null) { + 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 { - textColor = Color(0x404040) - }) - messageLabel = vStack.addArrangedSubview(Label(message, shadow = false).apply { - textColor = Color(0x404040) - }) + vStack.addArrangedSubview(Label(title, shadow = false).apply { + textColor = Color(0x404040) + }) + messageLabel = vStack.addArrangedSubview(Label(message, shadow = false).apply { + textColor = Color(0x404040) + }) - if (buttonTypes.isNotEmpty()) { - buttonContainer = vStack.addArrangedSubview(View()) - buttonStack = buttonContainer!!.addSubview(StackView(Axis.HORIZONTAL)) - for (type in buttonTypes) { - buttonStack!!.addArrangedSubview(Button(Label(type.localizedName)).apply { - handler = { - this@DialogView.buttonCallback(type, this@DialogView.window!!) - } - }) - } - } + if (buttonTypes.isNotEmpty()) { + buttonContainer = vStack.addArrangedSubview(View()) + buttonStack = buttonContainer!!.addSubview(StackView(Axis.HORIZONTAL)) + for (type in buttonTypes) { + buttonStack!!.addArrangedSubview(Button(Label(type.localizedName)).apply { + handler = { + this@DialogView.buttonCallback(type, this@DialogView.window!!) + } + }) + } + } - super.wasAdded() - } + super.wasAdded() + } - override fun createInternalConstraints() { - super.createInternalConstraints() + override fun createInternalConstraints() { + super.createInternalConstraints() - solver.dsl { - centerXAnchor equalTo window!!.centerXAnchor - centerYAnchor equalTo window!!.centerYAnchor + solver.dsl { + centerXAnchor equalTo window!!.centerXAnchor + centerYAnchor equalTo window!!.centerYAnchor - widthAnchor greaterThanOrEqualTo 175 + widthAnchor greaterThanOrEqualTo 175 - background.leftAnchor equalTo leftAnchor - 8 - background.rightAnchor equalTo rightAnchor + 8 - background.topAnchor equalTo topAnchor - 8 - background.bottomAnchor equalTo bottomAnchor + 8 + background.leftAnchor equalTo leftAnchor - 8 + background.rightAnchor equalTo rightAnchor + 8 + background.topAnchor equalTo topAnchor - 8 + background.bottomAnchor equalTo bottomAnchor + 8 - hStack.leftAnchor equalTo leftAnchor - hStack.rightAnchor equalTo rightAnchor - hStack.topAnchor equalTo topAnchor - hStack.bottomAnchor equalTo bottomAnchor + hStack.leftAnchor equalTo leftAnchor + hStack.rightAnchor equalTo rightAnchor + hStack.topAnchor equalTo topAnchor + hStack.bottomAnchor equalTo bottomAnchor - if (iconView != null) { - hStack.bottomAnchor greaterThanOrEqualTo iconView!!.bottomAnchor - } - hStack.bottomAnchor greaterThanOrEqualTo vStack.bottomAnchor + if (iconView != null) { + hStack.bottomAnchor greaterThanOrEqualTo iconView!!.bottomAnchor + } + hStack.bottomAnchor greaterThanOrEqualTo vStack.bottomAnchor - if (iconView != null) { - iconView!!.widthAnchor equalTo 30 - iconView!!.heightAnchor equalTo 30 - } + if (iconView != null) { + iconView!!.widthAnchor equalTo 30 + iconView!!.heightAnchor equalTo 30 + } - messageLabel.heightAnchor greaterThanOrEqualTo 50 + messageLabel.heightAnchor greaterThanOrEqualTo 50 - if (buttonContainer != null) { - buttonStack!!.heightAnchor equalTo buttonContainer!!.heightAnchor - buttonStack!!.centerYAnchor equalTo buttonContainer!!.centerYAnchor + if (buttonContainer != null) { + buttonStack!!.heightAnchor equalTo buttonContainer!!.heightAnchor + buttonStack!!.centerYAnchor equalTo buttonContainer!!.centerYAnchor - buttonStack!!.rightAnchor equalTo buttonContainer!!.rightAnchor - } - } - } + buttonStack!!.rightAnchor equalTo buttonContainer!!.rightAnchor + } + } + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt index 02e9aef..2a83aab 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/Label.kt @@ -26,117 +26,117 @@ import kotlin.math.min * wrapping. */ class Label( - text: Text, - var shadow: Boolean = false, - val maxLines: Int = 0, - val wrappingMode: WrappingMode = WrappingMode.WRAP, - var textAlignment: TextAlignment = TextAlignment.LEFT -): View() { + text: Text, + var shadow: Boolean = false, + val maxLines: Int = 0, + val wrappingMode: WrappingMode = WrappingMode.WRAP, + var textAlignment: TextAlignment = TextAlignment.LEFT +) : View() { - companion object { - private val textRenderer: TextRenderer - get() = MinecraftClient.getInstance().textRenderer - } + companion object { + private val textRenderer: TextRenderer + get() = MinecraftClient.getInstance().textRenderer + } - enum class WrappingMode { - WRAP, NO_WRAP - } + enum class WrappingMode { + WRAP, NO_WRAP + } - enum class TextAlignment { - LEFT, CENTER, RIGHT - } + enum class TextAlignment { + LEFT, CENTER, RIGHT + } - constructor( - text: String, - shadow: Boolean = false, - maxLines: Int = 0, - wrappingMode: WrappingMode = WrappingMode.WRAP, - textAlignment: TextAlignment = TextAlignment.LEFT, - ): this(LiteralText(text), shadow, maxLines, wrappingMode, textAlignment) + constructor( + text: String, + shadow: Boolean = false, + maxLines: Int = 0, + wrappingMode: WrappingMode = WrappingMode.WRAP, + textAlignment: TextAlignment = TextAlignment.LEFT, + ) : 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. - */ - var text: Text = text - set(value) { - field = value - // todo: uhhhh - updateIntrinsicContentSize(true) - // todo: setNeedsLayout instead of force unwrapping window - window!!.layout() - } - private lateinit var lines: List + /** + * The text of this label. Mutating this field will update the intrinsic content size and trigger a layout. + */ + var text: Text = text + set(value) { + field = value + // todo: uhhhh + updateIntrinsicContentSize(true) + // todo: setNeedsLayout instead of force unwrapping window + window!!.layout() + } + private lateinit var lines: List - var textColor = Color.WHITE - set(value) { - field = value - textColorARGB = value.argb - } - private var textColorARGB: Int = textColor.argb + var textColor = Color.WHITE + set(value) { + field = value + textColorARGB = value.argb + } + private var textColorARGB: Int = textColor.argb - override fun wasAdded() { - super.wasAdded() + override fun wasAdded() { + super.wasAdded() - updateIntrinsicContentSize(false) - } + updateIntrinsicContentSize(false) + } - private fun updateIntrinsicContentSize(canWrap: Boolean, isFromDidLayout: Boolean = false): Boolean { - if (RenderHelper.disabled) return false + private fun updateIntrinsicContentSize(canWrap: Boolean, isFromDidLayout: Boolean = false): Boolean { + if (RenderHelper.disabled) return false - val oldSize = intrinsicContentSize - // 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) { - val lines = textRenderer.wrapLines(text, bounds.width.toInt()) - val height = (if (maxLines == 0) lines.size else min(lines.size, maxLines)) * textRenderer.fontHeight - intrinsicContentSize = Size(bounds.width, height.toDouble()) - } else { - val width = textRenderer.getWidth(text) - val height = textRenderer.fontHeight - intrinsicContentSize = Size(width.toDouble(), height.toDouble()) - } - return 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 + if (wrappingMode == WrappingMode.WRAP && canWrap && hasSolver && isFromDidLayout) { + val lines = textRenderer.wrapLines(text, bounds.width.toInt()) + val height = (if (maxLines == 0) lines.size else min(lines.size, maxLines)) * textRenderer.fontHeight + intrinsicContentSize = Size(bounds.width, height.toDouble()) + } else { + val width = textRenderer.getWidth(text) + val height = textRenderer.fontHeight + intrinsicContentSize = Size(width.toDouble(), height.toDouble()) + } + return oldSize != intrinsicContentSize + } - override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { - if (!this::lines.isInitialized) { - computeLines() - } + override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { + if (!this::lines.isInitialized) { + computeLines() + } - for (i in 0 until lines.size) { - val x = when (textAlignment) { - TextAlignment.LEFT -> 0.0 - TextAlignment.CENTER -> (bounds.width - textRenderer.getWidth(lines[i])) / 2 - TextAlignment.RIGHT -> bounds.width - textRenderer.getWidth(lines[i]) - } - val y = i * textRenderer.fontHeight - if (shadow) { - textRenderer.drawWithShadow(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB) - } else { - textRenderer.draw(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB) - } - } - } + for (i in 0 until lines.size) { + val x = when (textAlignment) { + TextAlignment.LEFT -> 0.0 + TextAlignment.CENTER -> (bounds.width - textRenderer.getWidth(lines[i])) / 2 + TextAlignment.RIGHT -> bounds.width - textRenderer.getWidth(lines[i]) + } + val y = i * textRenderer.fontHeight + if (shadow) { + textRenderer.drawWithShadow(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB) + } else { + textRenderer.draw(matrixStack, lines[i], x.toFloat(), y.toFloat(), textColorARGB) + } + } + } - override fun didLayout() { - super.didLayout() + override fun didLayout() { + super.didLayout() - computeLines() - if (updateIntrinsicContentSize(true, true)) { - // if the intrinsic content size changes, relayout - window!!.layout() - } - } + computeLines() + if (updateIntrinsicContentSize(true, true)) { + // if the intrinsic content size changes, relayout + window!!.layout() + } + } - private fun computeLines() { - if (wrappingMode == WrappingMode.WRAP) { - var lines = textRenderer.wrapLines(text, bounds.width.toInt()) - if (maxLines > 0 && maxLines < lines.size) { - lines = lines.dropLast(lines.size - maxLines) - } - this.lines = lines - } else { - this.lines = listOf(text.asOrderedText()) - } - } + private fun computeLines() { + if (wrappingMode == WrappingMode.WRAP) { + var lines = textRenderer.wrapLines(text, bounds.width.toInt()) + if (maxLines > 0 && maxLines < lines.size) { + lines = lines.dropLast(lines.size - maxLines) + } + this.lines = lines + } else { + this.lines = listOf(text.asOrderedText()) + } + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt b/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt index 39ee49f..c49e5ab 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/NinePatchView.kt @@ -17,121 +17,260 @@ import kotlin.math.roundToInt * @author shadowfacts * @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 - private val topLeftDelegate = ResettableLazyProperty { - Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) - } - protected open val topLeft by topLeftDelegate + // Corners + private val topLeftDelegate = ResettableLazyProperty { + Rect(0.0, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) + } + protected open val topLeft by topLeftDelegate - private val topRightDelegate = ResettableLazyProperty { - Rect(bounds.width - ninePatch.cornerWidth, 0.0, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) - } - protected open val topRight by topRightDelegate + private val topRightDelegate = ResettableLazyProperty { + Rect( + bounds.width - ninePatch.cornerWidth, + 0.0, + ninePatch.cornerWidth.toDouble(), + ninePatch.cornerHeight.toDouble() + ) + } + protected open val topRight by topRightDelegate - private val bottomLeftDelegate = ResettableLazyProperty { - Rect(0.0, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) - } - protected open val bottomLeft by bottomLeftDelegate + private val bottomLeftDelegate = ResettableLazyProperty { + Rect( + 0.0, + bounds.height - ninePatch.cornerHeight, + ninePatch.cornerWidth.toDouble(), + ninePatch.cornerHeight.toDouble() + ) + } + protected open val bottomLeft by bottomLeftDelegate - private val bottomRightDelegate = ResettableLazyProperty { - Rect(bounds.width - ninePatch.cornerWidth, bounds.height - ninePatch.cornerHeight, ninePatch.cornerWidth.toDouble(), ninePatch.cornerHeight.toDouble()) - } - protected open val bottomRight by bottomRightDelegate + private val bottomRightDelegate = ResettableLazyProperty { + Rect( + bounds.width - ninePatch.cornerWidth, + bounds.height - ninePatch.cornerHeight, + ninePatch.cornerWidth.toDouble(), + ninePatch.cornerHeight.toDouble() + ) + } + protected open val bottomRight by bottomRightDelegate - // Edges - private val topMiddleDelegate = ResettableLazyProperty { - Rect(ninePatch.cornerWidth.toDouble(), topLeft.top, bounds.width - 2 * ninePatch.cornerWidth, ninePatch.cornerHeight.toDouble()) - } - protected open val topMiddle by topMiddleDelegate + // Edges + private val topMiddleDelegate = ResettableLazyProperty { + Rect( + ninePatch.cornerWidth.toDouble(), + topLeft.top, + bounds.width - 2 * ninePatch.cornerWidth, + ninePatch.cornerHeight.toDouble() + ) + } + protected open val topMiddle by topMiddleDelegate - private val bottomMiddleDelegate = ResettableLazyProperty { - Rect(topMiddle.left, bottomLeft.top, topMiddle.width, topMiddle.height) - } - protected open val bottomMiddle by bottomMiddleDelegate + private val bottomMiddleDelegate = ResettableLazyProperty { + Rect(topMiddle.left, bottomLeft.top, topMiddle.width, topMiddle.height) + } + protected open val bottomMiddle by bottomMiddleDelegate - private val leftMiddleDelegate = ResettableLazyProperty { - Rect(topLeft.left, ninePatch.cornerHeight.toDouble(), ninePatch.cornerWidth.toDouble(), bounds.height - 2 * ninePatch.cornerHeight) - } - protected open val leftMiddle by leftMiddleDelegate + private val leftMiddleDelegate = ResettableLazyProperty { + Rect( + topLeft.left, + ninePatch.cornerHeight.toDouble(), + ninePatch.cornerWidth.toDouble(), + bounds.height - 2 * ninePatch.cornerHeight + ) + } + protected open val leftMiddle by leftMiddleDelegate - private val rightMiddleDelegate = ResettableLazyProperty { - Rect(topRight.left, leftMiddle.top, leftMiddle.width, leftMiddle.height) - } - protected open val rightMiddle by rightMiddleDelegate + private val rightMiddleDelegate = ResettableLazyProperty { + Rect(topRight.left, leftMiddle.top, leftMiddle.width, leftMiddle.height) + } + protected open val rightMiddle by rightMiddleDelegate - // Center - private val centerDelegate = ResettableLazyProperty { - Rect(topLeft.right, topLeft.bottom, topMiddle.width, leftMiddle.height) - } - protected open val center by centerDelegate + // Center + private val centerDelegate = ResettableLazyProperty { + Rect(topLeft.right, topLeft.bottom, topMiddle.width, leftMiddle.height) + } + 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() { - super.didLayout() + override fun didLayout() { + super.didLayout() - delegates.forEach(ResettableLazyProperty::reset) - } + delegates.forEach(ResettableLazyProperty::reset) + } - override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { - drawCorners(matrixStack) - drawEdges(matrixStack) - drawCenter(matrixStack) - } + override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { + drawCorners(matrixStack) + drawEdges(matrixStack) + drawCenter(matrixStack) + } - private fun drawCorners(matrixStack: MatrixStack) { - RenderHelper.draw(matrixStack, topLeft, ninePatch.topLeft) - RenderHelper.draw(matrixStack, topRight, ninePatch.topRight) - RenderHelper.draw(matrixStack, bottomLeft, ninePatch.bottomLeft) - RenderHelper.draw(matrixStack, bottomRight, ninePatch.bottomRight) - } + private fun drawCorners(matrixStack: MatrixStack) { + RenderHelper.draw(matrixStack, topLeft, ninePatch.topLeft) + RenderHelper.draw(matrixStack, topRight, ninePatch.topRight) + RenderHelper.draw(matrixStack, bottomLeft, ninePatch.bottomLeft) + RenderHelper.draw(matrixStack, bottomRight, ninePatch.bottomRight) + } - private fun drawEdges(matrixStack: MatrixStack) { - // Horizontal - 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(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) - } + private fun drawEdges(matrixStack: MatrixStack) { + // Horizontal + 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( + 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 - 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(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) - } - } + // Vertical + 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( + 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) { - for (i in 0 until (center.height.roundToInt() / ninePatch.centerHeight)) { - drawCenterRow(matrixStack, center.top + i * ninePatch.centerHeight.toDouble(), ninePatch.centerHeight.toDouble()) - } - val remHeight = center.height.roundToInt() % ninePatch.centerHeight - if (remHeight > 0) { - drawCenterRow(matrixStack, center.bottom - remHeight, remHeight.toDouble()) - } - } + private fun drawCenter(matrixStack: MatrixStack) { + for (i in 0 until (center.height.roundToInt() / ninePatch.centerHeight)) { + drawCenterRow( + matrixStack, + center.top + i * ninePatch.centerHeight.toDouble(), + ninePatch.centerHeight.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) { - 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) - } - 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) - } - } + private fun drawCenterRow(matrixStack: MatrixStack, y: Double, height: Double) { + 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 + ) + } + 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 + ) + } + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt b/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt index 024a250..9b71af9 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/StackView.kt @@ -22,251 +22,265 @@ import java.util.* * @param spacing The distance between arranged subviews along the primary axis. */ open class StackView( - val axis: Axis, - val distribution: Distribution = Distribution.FILL, - val spacing: Double = 0.0 -): View() { + val axis: Axis, + val distribution: Distribution = Distribution.FILL, + val spacing: Double = 0.0 +) : View() { - // the internal, mutable list of arranged subviews - private val _arrangedSubviews = LinkedList() - /** - * 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 = _arrangedSubviews + // the internal, mutable list of arranged subviews + private val _arrangedSubviews = LinkedList() - private var leadingConnection: Constraint? = null - private var trailingConnection: Constraint? = null - private var arrangedSubviewConnections = mutableListOf() + /** + * 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 = _arrangedSubviews - /** - * 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 addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T { - addSubview(view) - _arrangedSubviews.add(index, view) + private var leadingConnection: Constraint? = null + private var trailingConnection: Constraint? = null + private var arrangedSubviewConnections = mutableListOf() - 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 addArrangedSubview(view: T, index: Int = arrangedSubviews.size): T { + addSubview(view) + _arrangedSubviews.add(index, view) - return view - } + addConstraintsForArrangedView(view, index) - /** - * 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") - } + return view + } - if (index == 0) { - solver.removeConstraint(leadingConnection) - val next = arrangedSubviews.getOrNull(1) - if (next != null) { - solver.dsl { - leadingConnection = anchor(LEADING) equalTo anchor(LEADING, next) - } - } 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 - } - } + /** + * 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 the removed view is in the middle - if (arrangedSubviews.size >= 3 && index > 0 && index < arrangedSubviews.size - 1) { - val prev = arrangedSubviews[index - 1] - val next = arrangedSubviews[index + 1] - solver.dsl { - solver.removeConstraint(arrangedSubviewConnections[index - 1]) - solver.removeConstraint(arrangedSubviewConnections[index]) + if (index == 0) { + solver.removeConstraint(leadingConnection) + val next = arrangedSubviews.getOrNull(1) + if (next != null) { + solver.dsl { + leadingConnection = anchor(LEADING) equalTo anchor(LEADING, next) + } + } 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 - arrangedSubviewConnections[index - 1] = anchor(TRAILING, prev) equalTo anchor(LEADING, next) - arrangedSubviewConnections.removeAt(index) - } - } + // if the removed view is in the middle + if (arrangedSubviews.size >= 3 && index > 0 && index < arrangedSubviews.size - 1) { + val prev = arrangedSubviews[index - 1] + val next = arrangedSubviews[index + 1] + solver.dsl { + solver.removeConstraint(arrangedSubviewConnections[index - 1]) + solver.removeConstraint(arrangedSubviewConnections[index]) - _arrangedSubviews.remove(view) - removeSubview(view) - } + // todo: double check me + arrangedSubviewConnections[index - 1] = anchor(TRAILING, prev) equalTo anchor(LEADING, next) + arrangedSubviewConnections.removeAt(index) + } + } - override fun removeSubview(view: View) { - if (arrangedSubviews.contains(view)) { - removeArrangedSubview(view) - } else { - super.removeSubview(view) - } - } + _arrangedSubviews.remove(view) + removeSubview(view) + } - private fun addConstraintsForArrangedView(view: View, index: Int) { - if (index == 0) { - if (leadingConnection != null) { - solver.removeConstraint(leadingConnection) - } - 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) - } - } - } + override fun removeSubview(view: View) { + if (arrangedSubviews.contains(view)) { + removeArrangedSubview(view) + } else { + super.removeSubview(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) - } + private fun addConstraintsForArrangedView(view: View, index: Int) { + if (index == 0) { + if (leadingConnection != null) { + solver.removeConstraint(leadingConnection) + } + 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) - /** - * 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 { - /** - * The leading edges of arranged subviews are pinned to the leading edge of the stack view. - * ``` - * ┌─────────────────────────────┐ - * │┌─────────────┐ │ - * ││ │ │ - * ││ │ │ - * ││ │ │ - * │└─────────────┘ │ - * │┌─────────┐ │ - * ││ │ │ - * ││ │ │ - * ││ │ │ - * │└─────────┘ │ - * │┌───────────────────────────┐│ - * ││ ││ - * ││ ││ - * ││ ││ - * │└───────────────────────────┘│ - * └─────────────────────────────┘ - * ``` - */ - LEADING, - /** - * The centers of the arranged subviews are pinned to the center of the stack view. - * ``` - * ┌─────────────────────────────┐ - * │ ┌─────────────┐ │ - * │ │ │ │ - * │ │ │ │ - * │ │ │ │ - * │ └─────────────┘ │ - * │ ┌─────────┐ │ - * │ │ │ │ - * │ │ │ │ - * │ │ │ │ - * │ └─────────┘ │ - * │┌───────────────────────────┐│ - * ││ ││ - * ││ ││ - * ││ ││ - * │└───────────────────────────┘│ - * └─────────────────────────────┘ - * ``` - */ - CENTER, - /** - * The trailing edges of arranged subviews are pinned to the leading edge of the stack view. - * ``` - * ┌─────────────────────────────┐ - * │ ┌─────────────┐│ - * │ │ ││ - * │ │ ││ - * │ │ ││ - * │ └─────────────┘│ - * │ ┌─────────┐│ - * │ │ ││ - * │ │ ││ - * │ │ ││ - * │ └─────────┘│ - * │┌───────────────────────────┐│ - * ││ ││ - * ││ ││ - * ││ ││ - * │└───────────────────────────┘│ - * └─────────────────────────────┘ - * ``` - */ - TRAILING, - /** - * The arranged subviews fill the perpendicular axis of the stack view. - * ``` - * ┌─────────────────────────────┐ - * │┌───────────────────────────┐│ - * ││ ││ - * ││ ││ - * ││ ││ - * │└───────────────────────────┘│ - * │┌───────────────────────────┐│ - * ││ ││ - * ││ ││ - * ││ ││ - * │└───────────────────────────┘│ - * │┌───────────────────────────┐│ - * ││ ││ - * ││ ││ - * ││ ││ - * │└───────────────────────────┘│ - * └─────────────────────────────┘ - * ``` - */ - FILL - } + 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 { + 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 { + /** + * The leading edges of arranged subviews are pinned to the leading edge of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │┌─────────────┐ │ + * ││ │ │ + * ││ │ │ + * ││ │ │ + * │└─────────────┘ │ + * │┌─────────┐ │ + * ││ │ │ + * ││ │ │ + * ││ │ │ + * │└─────────┘ │ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + LEADING, + + /** + * The centers of the arranged subviews are pinned to the center of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │ ┌─────────────┐ │ + * │ │ │ │ + * │ │ │ │ + * │ │ │ │ + * │ └─────────────┘ │ + * │ ┌─────────┐ │ + * │ │ │ │ + * │ │ │ │ + * │ │ │ │ + * │ └─────────┘ │ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + CENTER, + + /** + * The trailing edges of arranged subviews are pinned to the leading edge of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │ ┌─────────────┐│ + * │ │ ││ + * │ │ ││ + * │ │ ││ + * │ └─────────────┘│ + * │ ┌─────────┐│ + * │ │ ││ + * │ │ ││ + * │ │ ││ + * │ └─────────┘│ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + TRAILING, + + /** + * The arranged subviews fill the perpendicular axis of the stack view. + * ``` + * ┌─────────────────────────────┐ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * │┌───────────────────────────┐│ + * ││ ││ + * ││ ││ + * ││ ││ + * │└───────────────────────────┘│ + * └─────────────────────────────┘ + * ``` + */ + FILL + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/TextureView.kt b/src/main/kotlin/net/shadowfacts/cacao/view/TextureView.kt index 9a082c6..ea19841 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/TextureView.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/TextureView.kt @@ -11,12 +11,12 @@ import net.shadowfacts.cacao.util.texture.Texture * * @author shadowfacts */ -class TextureView(var texture: Texture?): View() { +class TextureView(var texture: Texture?) : View() { - override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { - texture?.also { - RenderHelper.draw(matrixStack, bounds, it) - } - } + override fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) { + texture?.also { + RenderHelper.draw(matrixStack, bounds, it) + } + } } diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt index ba86ad1..010eb32 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/View.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/View.kt @@ -20,502 +20,521 @@ import kotlin.math.floor * * @author shadowfacts */ -open class View(): Responder { +open class View() : Responder { - /** - * The window whose view hierarchy this view belongs to. - * Not initialized until the root view in this hierarchy has been added to a hierarchy, - * using it before that will throw a runtime exception. - */ - override var window: Window? = null + /** + * The window whose view hierarchy this view belongs to. + * Not initialized until the root view in this hierarchy has been added to a hierarchy, + * using it before that will throw a runtime exception. + */ + override var window: Window? = null - /** - * The next responder after this one. - * For views, the next responder is the view's superview. - */ - override val nextResponder: Responder? - // todo: should the view controller be a Responder? - get() = superview + /** + * The next responder after this one. + * For views, the next responder is the view's superview. + */ + override val nextResponder: Responder? + // todo: should the view controller be a Responder? + get() = superview - private val solverDelegate = ObservableLateInitProperty { - for (v in subviews) { - 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 + private val solverDelegate = ObservableLateInitProperty { + for (v in subviews) { + v.solver = it + } + } - 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 - /** - * Layout anchor for the left edge of this view in the window's coordinate system. - */ - 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") + val hasSolver: Boolean + get() = solverDelegate.isInitialized - private val _layoutGuides = LinkedList() + /** + * 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. - * - * To add a layout guide, call [addLayoutGuide]. - * - * @see LayoutGuide - */ - val layoutGuides: List = _layoutGuides + /** + * Layout anchor for the right edge of this view in the window's coordinate system. + */ + val rightAnchor = LayoutVariable(this, "right") - /** - * Whether this view uses constraint-based layout. - * 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. - * - * Default is `true`. - */ - var usesConstraintBasedLayout = true + /** + * Layout anchor for the top edge of this view in the window's coordinate system. + */ + val topAnchor = LayoutVariable(this, "top") - /** - * The rectangle for this view in the coordinate system of its superview view (or the window, if there is no superview). - * 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) - } + /** + * Layout anchor for the bottom edge of this view in the window's coordinate system. + */ + val bottomAnchor = LayoutVariable(this, "bottom") - /** - * The rectangle for this view in its own coordinate system. - * If using constraint based layout, this property has zero dimensions until [didLayout] called. - * Otherwise, this will be initialized when [frame] is set. - */ - var bounds = Rect(0.0, 0.0, 0.0, 0.0) + /** + * Layout anchor for the width of this view in the window's coordinate system. + */ + val widthAnchor = LayoutVariable(this, "width") - /** - * 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. - */ - var zIndex: Double = 0.0 + /** + * Layout anchor for the height of this view in the window's coordinate system. + */ + val heightAnchor = LayoutVariable(this, "height") - /** - * The intrinsic size of this view's content. May be null if the view doesn't have any content or there is no - * intrinsic size. - * - * 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 + /** + * Layout anchor for the center X position of this view in the window's coordinate system. + */ + val centerXAnchor = LayoutVariable(this, "centerX") - /** - * The background color of this view. - */ - var backgroundColor = Color.CLEAR + /** + * Layout anchor for the center Y position of this view in the window's coordinate system. + */ + val centerYAnchor = LayoutVariable(this, "centerY") - var respondsToDragging = false + private val _layoutGuides = LinkedList() - /** - * This view's parent view. If `null`, this view is a top-level view in the [Window]. - */ - var superview: View? = null - // _subviews is the internal, mutable object since we only want it to be mutated by the add/removeSubview methods - private val _subviews = LinkedList() - private var subviewsSortedByZIndex: List = listOf() + /** + * All the layout guides attached to this view. + * + * To add a layout guide, call [addLayoutGuide]. + * + * @see LayoutGuide + */ + val layoutGuides: List = _layoutGuides - /** - * 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 = _subviews + /** + * Whether this view uses constraint-based layout. + * 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. + * + * Default is `true`. + */ + var usesConstraintBasedLayout = true - constructor(frame: Rect): this() { - this.usesConstraintBasedLayout = false - this.frame = frame - } + /** + * The rectangle for this view in the coordinate system of its superview view (or the window, if there is no superview). + * 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. - */ - fun getAnchor(axis: Axis, position: AxisPosition): LayoutVariable { - return when (axis) { - Axis.HORIZONTAL -> - when (position) { - AxisPosition.LEADING -> leftAnchor - AxisPosition.CENTER -> centerXAnchor - AxisPosition.TRAILING -> rightAnchor - } - Axis.VERTICAL -> - when (position) { - AxisPosition.LEADING -> topAnchor - AxisPosition.CENTER -> centerYAnchor - AxisPosition.TRAILING -> bottomAnchor - } - } - } + /** + * The rectangle for this view in its own coordinate system. + * If using constraint based layout, this property has zero dimensions until [didLayout] called. + * Otherwise, this will be initialized when [frame] is set. + */ + var bounds = Rect(0.0, 0.0, 0.0, 0.0) - /** - * 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 addSubview(view: T): T { - _subviews.add(view) - subviewsSortedByZIndex = subviews.sortedBy(View::zIndex) + /** + * 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. + */ + var zIndex: Double = 0.0 - view.superview = this - if (hasSolver) { - view.solver = solver - } - view.window = window + /** + * The intrinsic size of this view's content. May be null if the view doesn't have any content or there is no + * intrinsic size. + * + * 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 - * 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") - } + /** + * This view's parent view. If `null`, this view is a top-level view in the [Window]. + */ + var superview: View? = null - _subviews.remove(view) - subviewsSortedByZIndex = subviews.sortedBy(View::zIndex) + // _subviews is the internal, mutable object since we only want it to be mutated by the add/removeSubview methods + private val _subviews = LinkedList() + private var subviewsSortedByZIndex: List = 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 = _subviews - // 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 } + constructor(frame: Rect) : this() { + this.usesConstraintBasedLayout = false + this.frame = frame + } - 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) + /** + * Helper method for retrieve the anchor for a specific position on the given axis. + */ + fun getAnchor(axis: Axis, position: AxisPosition): LayoutVariable { + return when (axis) { + Axis.HORIZONTAL -> + when (position) { + AxisPosition.LEADING -> leftAnchor + AxisPosition.CENTER -> centerXAnchor + AxisPosition.TRAILING -> rightAnchor + } - // 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 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.window = null + view.window = null - // todo: is this necessary? + // todo: is this necessary? // view.wasRemoved() - } + } - /** - * Creates and returns a new layout guide with this view as its owner. - */ - fun addLayoutGuide(): LayoutGuide { - val guide = LayoutGuide(this) - _layoutGuides.add(guide) - if (hasSolver) { - guide.attachTo(solver) - } - return guide - } + /** + * Creates and returns a new layout guide with this view as its owner. + */ + fun addLayoutGuide(): LayoutGuide { + val guide = LayoutGuide(this) + _layoutGuides.add(guide) + if (hasSolver) { + guide.attachTo(solver) + } + return guide + } - /** - * Removes this view from its superview, if it has one. - */ - fun removeFromSuperview() { - superview?.removeSubview(this) - } + /** + * Removes this view from its superview, if it has one. + */ + fun removeFromSuperview() { + superview?.removeSubview(this) + } - /** - * Finds all subviews that contain the given point. - * - * @param point The point to find subviews for, in the coordinate system of this view. - * @return All views that contain the given point. - */ - fun subviewsAtPoint(point: Point): List { - return subviews.filter { point in it.frame } - } + /** + * Finds all subviews that contain the given point. + * + * @param point The point to find subviews for, in the coordinate system of this view. + * @return All views that contain the given point. + */ + fun subviewsAtPoint(point: Point): List { + return subviews.filter { point in it.frame } + } - /** - * Attempts to find a subview which contains the given point. - * 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]. - * - * @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. - */ - fun subviewAtPoint(point: Point): View? { - return subviews.firstOrNull { point in it.frame } - } + /** + * Attempts to find a subview which contains the given point. + * 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]. + * + * @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. + */ + fun subviewAtPoint(point: Point): View? { + return subviews.firstOrNull { point in it.frame } + } - /** - * Called when this view was added to a view hierarchy. - * If overridden, the super-class method must be called. - */ - open fun wasAdded() { - createInternalConstraints() - updateIntrinsicContentSizeConstraints(null, intrinsicContentSize) + /** + * Called when this view was added to a view hierarchy. + * If overridden, the super-class method must be called. + */ + open fun wasAdded() { + createInternalConstraints() + updateIntrinsicContentSizeConstraints(null, intrinsicContentSize) - layoutGuides.forEach { - it.attachTo(solver) - } - } + layoutGuides.forEach { + it.attachTo(solver) + } + } - /** - * 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. - */ - protected open fun createInternalConstraints() { - if (!usesConstraintBasedLayout) return + /** + * 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. + */ + protected open fun createInternalConstraints() { + if (!usesConstraintBasedLayout) return - solver.dsl { - rightAnchor equalTo (leftAnchor + widthAnchor) - bottomAnchor equalTo (topAnchor + heightAnchor) - centerXAnchor equalTo (leftAnchor + widthAnchor / 2) - centerYAnchor equalTo (topAnchor + heightAnchor / 2) - } - } + solver.dsl { + rightAnchor equalTo (leftAnchor + widthAnchor) + bottomAnchor equalTo (topAnchor + heightAnchor) + centerXAnchor equalTo (leftAnchor + widthAnchor / 2) + centerYAnchor equalTo (topAnchor + heightAnchor / 2) + } + } - private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) { - if (!usesConstraintBasedLayout || !hasSolver) return + private fun updateIntrinsicContentSizeConstraints(old: Size?, new: Size?) { + if (!usesConstraintBasedLayout || !hasSolver) return - if (old != null) { - solver.removeConstraint(intrinsicContentSizeWidthConstraint!!) - solver.removeConstraint(intrinsicContentSizeHeightConstraint!!) - } - if (new != null) { - solver.dsl { - this@View.intrinsicContentSizeWidthConstraint = (widthAnchor.equalTo(new.width, strength = MEDIUM)) - this@View.intrinsicContentSizeHeightConstraint = (heightAnchor.equalTo(new.height, strength = MEDIUM)) - } - } - } + if (old != null) { + solver.removeConstraint(intrinsicContentSizeWidthConstraint!!) + solver.removeConstraint(intrinsicContentSizeHeightConstraint!!) + } + if (new != null) { + solver.dsl { + this@View.intrinsicContentSizeWidthConstraint = (widthAnchor.equalTo(new.width, strength = MEDIUM)) + this@View.intrinsicContentSizeHeightConstraint = (heightAnchor.equalTo(new.height, strength = MEDIUM)) + } + } + } - /** - * Called after this view has been laid-out. - * If overridden, the super-class method must be called. - */ - open fun didLayout() { - if (usesConstraintBasedLayout) { - val superviewLeft = superview?.leftAnchor?.value ?: 0.0 - val superviewTop = superview?.topAnchor?.value ?: 0.0 - frame = Rect(leftAnchor.value - superviewLeft, topAnchor.value - superviewTop, widthAnchor.value, heightAnchor.value) - bounds = Rect(0.0, 0.0, widthAnchor.value, heightAnchor.value) - } + /** + * Called after this view has been laid-out. + * If overridden, the super-class method must be called. + */ + open fun didLayout() { + if (usesConstraintBasedLayout) { + val superviewLeft = superview?.leftAnchor?.value ?: 0.0 + val superviewTop = superview?.topAnchor?.value ?: 0.0 + frame = Rect( + 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. - * 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. - * Use [drawContent] to draw any custom content. - * - * @param mouse The position of the mouse in the coordinate system of this view. - * @param delta The time since the last frame. - */ - open fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) { - matrixStack.push() - matrixStack.translate(frame.left, frame.top, 0.0) + /** + * Called to draw this view. + * 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. + * Use [drawContent] to draw any custom content. + * + * @param mouse The position of the mouse in the coordinate system of this view. + * @param delta The time since the last frame. + */ + open fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) { + matrixStack.push() + 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 { - val mouseInView = convert(mouse, to = it) - it.draw(matrixStack, mouseInView, delta) - } + subviewsSortedByZIndex.forEach { + val mouseInView = convert(mouse, to = it) + it.draw(matrixStack, mouseInView, delta) + } - matrixStack.pop() - } + matrixStack.pop() + } - /** - * 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 - * 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 delta The time since the last frame. - */ - open fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {} + /** + * 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 + * 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 delta The time since the last frame. + */ + open fun drawContent(matrixStack: MatrixStack, mouse: Point, delta: Float) {} - /** - * Called when this view is clicked. - * - * 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 - * 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. - * - * 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. - * - * @param point The point in the coordinate system of this view that the mouse was clicked. - * @param mouseButton The mouse button used to click. - * @return Whether the mouse click was handled by this view or any subviews. - */ - open fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { - val (inside, outside) = subviews.partition { point in it.frame } - val view = inside.maxByOrNull(View::zIndex) - var result = false - if (view != null) { - val pointInView = convert(point, to = view) - result = view.mouseClicked(pointInView, mouseButton) - } - for (v in outside) { - val pointInV = convert(point, to = v) - v.mouseClickedOutside(pointInV, mouseButton) - } - return result - } + /** + * Called when this view is clicked. + * + * 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 + * 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. + * + * 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. + * + * @param point The point in the coordinate system of this view that the mouse was clicked. + * @param mouseButton The mouse button used to click. + * @return Whether the mouse click was handled by this view or any subviews. + */ + open fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { + val (inside, outside) = subviews.partition { point in it.frame } + val view = inside.maxByOrNull(View::zIndex) + var result = false + if (view != null) { + val pointInView = convert(point, to = view) + result = view.mouseClicked(pointInView, mouseButton) + } + for (v in outside) { + val pointInV = convert(point, to = v) + v.mouseClickedOutside(pointInV, mouseButton) + } + return result + } - /** - * 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. - * - * @param point The clicked point _in the coordinate space of this view_. - * @param mouseButton The mouse button used to click. - */ - open fun mouseClickedOutside(point: Point, mouseButton: MouseButton) { - for (view in subviews) { - val pointInView = convert(point, to = view) - view.mouseClickedOutside(pointInView, mouseButton) - } - } + /** + * 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. + * + * @param point The clicked point _in the coordinate space of this view_. + * @param mouseButton The mouse button used to click. + */ + open fun mouseClickedOutside(point: Point, mouseButton: MouseButton) { + for (view in subviews) { + val pointInView = convert(point, to = view) + view.mouseClickedOutside(pointInView, mouseButton) + } + } - open fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean { - val view = subviewsAtPoint(startPoint).maxByOrNull(View::zIndex) - if (view != null) { - val startInView = convert(startPoint, to = view) - return view.mouseDragged(startInView, delta, mouseButton) - } - return false - } + open fun mouseDragged(startPoint: Point, delta: Point, mouseButton: MouseButton): Boolean { + val view = subviewsAtPoint(startPoint).maxByOrNull(View::zIndex) + if (view != null) { + val startInView = convert(startPoint, to = view) + return view.mouseDragged(startInView, delta, mouseButton) + } + return false + } - open fun mouseDragEnded(point: Point, mouseButton: MouseButton) { - val view = subviewsAtPoint(point).maxByOrNull(View::zIndex) - if (view != null) { - val pointInView = convert(point, to = view) - return view.mouseDragEnded(pointInView, mouseButton) - } - } + open fun mouseDragEnded(point: Point, mouseButton: MouseButton) { + val view = subviewsAtPoint(point).maxByOrNull(View::zIndex) + if (view != null) { + val pointInView = convert(point, to = view) + return view.mouseDragEnded(pointInView, mouseButton) + } + } - open fun mouseScrolled(point: Point, amount: Double): Boolean { - val view = subviewsAtPoint(point).maxByOrNull(View::zIndex) - if (view != null) { - val pointInView = convert(point, to = view) - return view.mouseScrolled(pointInView, amount) - } - return false - } + open fun mouseScrolled(point: Point, amount: Double): Boolean { + val view = subviewsAtPoint(point).maxByOrNull(View::zIndex) + if (view != null) { + val pointInView = convert(point, to = view) + return view.mouseScrolled(pointInView, amount) + } + return false + } - /** - * 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 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. - */ - fun convert(point: Point, to: View?): Point { - if (to != null) { - val ancestor = LowestCommonAncestor.find(this, to, View::superview) - @Suppress("NAME_SHADOWING") var point = point + /** + * 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 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. + */ + fun convert(point: Point, to: View?): Point { + if (to != null) { + val ancestor = LowestCommonAncestor.find(this, to, View::superview) + @Suppress("NAME_SHADOWING") var point = point - // Convert up to the LCA - var view: View? = this - while (view != null && view != ancestor) { - point = Point(point.x + view.frame.left, point.y + view.frame.top) - view = view.superview - } + // Convert up to the LCA + var view: View? = this + while (view != null && view != ancestor) { + point = Point(point.x + view.frame.left, point.y + view.frame.top) + view = view.superview + } - // Convert back down to the other view - view = to - while (view != null && view != ancestor) { - point = Point(point.x - view.frame.left, point.y - view.frame.top) - view = view.superview - } + // Convert back down to the other view + view = to + while (view != null && view != ancestor) { + point = Point(point.x - view.frame.left, point.y - view.frame.top) + view = view.superview + } - return point - } else { - return Point(leftAnchor.value + point.x, topAnchor.value + point.y) - } - } + return point + } else { + 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. - * - * @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. - * @return The rectangle in the coordinate system of the [to] view. - */ - fun convert(rect: Rect, to: View?): Rect { - return Rect(convert(rect.origin, to), rect.size) - } + /** + * 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 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. + */ + fun convert(rect: Rect, to: View?): Rect { + return Rect(convert(rect.origin, to), rect.size) + } } private fun LayoutGuide.attachTo(solver: Solver) { - solver.dsl { - rightAnchor equalTo (leftAnchor + widthAnchor) - bottomAnchor equalTo (topAnchor + heightAnchor) - centerXAnchor equalTo (leftAnchor + widthAnchor / 2) - centerYAnchor equalTo (topAnchor + heightAnchor / 2) - } + solver.dsl { + rightAnchor equalTo (leftAnchor + widthAnchor) + bottomAnchor equalTo (topAnchor + heightAnchor) + centerXAnchor equalTo (leftAnchor + widthAnchor / 2) + centerYAnchor equalTo (topAnchor + heightAnchor / 2) + } } private val LayoutVariable.viewOrLayoutGuideView: View - get() = view ?: layoutGuide!!.owningView + get() = view ?: layoutGuide!!.owningView diff --git a/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt index ec6f4ea..3703f19 100644 --- a/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt +++ b/src/main/kotlin/net/shadowfacts/cacao/view/button/AbstractButton.kt @@ -22,134 +22,136 @@ import kotlin.math.floor * 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. */ -abstract class AbstractButton>(val content: View, val padding: Double = 4.0): View() { +abstract class AbstractButton>(val content: View, val padding: Double = 4.0) : View() { - /** - * The function that handles when this button is clicked. - * The parameter is the type of the concrete button implementation that was used. - */ - var handler: ((Impl) -> Unit)? = null + /** + * The function that handles when this button is clicked. + * The parameter is the type of the concrete button implementation that was used. + */ + var handler: ((Impl) -> Unit)? = null - /** - * Whether the button is disabled. - * Disabled buttons have a different background ([disabledBackground]) and do not receive click events. - */ - var disabled = false + /** + * Whether the button is disabled. + * Disabled buttons have a different background ([disabledBackground]) and do not receive click events. + */ + var disabled = false - /** - * 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. - * - * The background will fill the entire button (going beneath the content [padding]). - * 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 - * unless the background view is not fully opaque. - */ - var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG) - set(value) { - field?.removeFromSuperview() - field = value - 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 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. + * + * The background will fill the entire button (going beneath the content [padding]). + * 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 + * unless the background view is not fully opaque. + */ + var background: View? = NinePatchView(NinePatchTexture.BUTTON_BG) + set(value) { + field?.removeFromSuperview() + field = value + value?.also(::addBackground) + } - /** - * The tooltip text shown when this button is hovered. - */ - var tooltip: Text? = null + /** + * 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) + } - override fun wasAdded() { - solver.dsl { - addSubview(content) - content.centerXAnchor equalTo centerXAnchor - content.centerYAnchor equalTo centerYAnchor + /** + * 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) + } - content.leftAnchor.lessThanOrEqualTo((leftAnchor + padding), WEAK) - content.rightAnchor.greaterThanOrEqualTo(rightAnchor - padding, WEAK) - content.topAnchor.lessThanOrEqualTo(topAnchor + padding, WEAK) - content.bottomAnchor.greaterThanOrEqualTo(bottomAnchor - padding, WEAK) - } + /** + * The tooltip text shown when this button is hovered. + */ + 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) { - if (superview != null && hasSolver) { - addSubview(view) - solver.dsl { - view.leftAnchor equalTo leftAnchor - view.rightAnchor equalTo rightAnchor - view.topAnchor equalTo topAnchor - view.bottomAnchor equalTo bottomAnchor - } - } - } + listOfNotNull(background, hoveredBackground, disabledBackground).forEach(::addBackground) - override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) { - matrixStack.push() - matrixStack.translate(frame.left, frame.top, 0.0) + super.wasAdded() + } - 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 - // the edges are all pinned, so the coordinate space is the same - getCurrentBackground(mouse)?.draw(matrixStack, mouse, delta) + override fun draw(matrixStack: MatrixStack, mouse: Point, delta: Float) { + matrixStack.push() + matrixStack.translate(frame.left, frame.top, 0.0) - val mouseInContent = convert(mouse, to = content) - content.draw(matrixStack, mouseInContent, delta) + RenderHelper.fill(matrixStack, bounds, backgroundColor) - // 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) { - window!!.drawTooltip(listOf(tooltip!!)) - } - } + // don't draw subviews, otherwise all background views + content will get drawn - override fun mouseClicked(point: Point, mouseButton: MouseButton): Boolean { - if (disabled) return false + matrixStack.pop() - // We can perform an unchecked cast here because we are certain that Impl will be the concrete implementation - // of AbstractButton. - // For example, an implementing class may be defined as such: `class Button: AbstractButton