diff --git a/build.gradle b/build.gradle index 5bc74f6c..0106c581 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,10 @@ plugins { //alias(libs.plugins.shadow) } +loom { + accessWidenerPath = file("src/main/resources/civmodern.accesswidener") +} + dependencies { minecraft(libs.minecraft) mappings(loom.layered() { diff --git a/src/main/java/sh/okx/civmodern/common/CivMapConfig.java b/src/main/java/sh/okx/civmodern/common/CivMapConfig.java index c9bc9fe5..ac228e3c 100644 --- a/src/main/java/sh/okx/civmodern/common/CivMapConfig.java +++ b/src/main/java/sh/okx/civmodern/common/CivMapConfig.java @@ -6,6 +6,7 @@ import java.util.Properties; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import sh.okx.civmodern.common.features.CompactedItem; import sh.okx.civmodern.common.gui.Alignment; public class CivMapConfig { @@ -16,7 +17,6 @@ public class CivMapConfig { public static final int DEFAULT_CHEVRON_COLOUR = 0xFF0000; public static final int DEFAULT_BORDER_COLOUR = 0x7a7a7a; private final File file; - private int compactedColour; private int radarCircles; private int radarSize; private float iconSize; @@ -47,7 +47,6 @@ public class CivMapConfig { private int minimapSize; private boolean playerWaypointsEnabled; private float minimapZoom; - private boolean cratesAreCompacted; private boolean showRepairCost; private boolean radarLogarithm; private boolean showMinimapCoords; @@ -55,7 +54,7 @@ public class CivMapConfig { public CivMapConfig(File file, Properties properties) { this.file = file; - this.compactedColour = Integer.parseInt(properties.getProperty("compacted_colour", "16777048")); + CompactedItem.COMPACTED.setRBG(Integer.parseInt(properties.getProperty("compacted_colour", Integer.toString(CompactedItem.COMPACTED.defaultColour)))); this.radarCircles = Integer.parseInt(properties.getProperty("radar_circles", "4")); this.radarSize = Integer.parseInt(properties.getProperty("radar_size", "80")); this.alignment = Alignment.valueOf(properties.getProperty("alignment", "top_left").toUpperCase()); @@ -86,17 +85,17 @@ public CivMapConfig(File file, Properties properties) { this.minimapSize = Integer.parseInt(properties.getProperty("minimap_size", "100")); this.playerWaypointsEnabled = Boolean.parseBoolean(properties.getProperty("player_waypoints_enabled", "true")); this.minimapZoom = Float.parseFloat(properties.getProperty("minimap_zoom", "4")); - this.cratesAreCompacted = Boolean.parseBoolean(properties.getProperty("crates_are_compacted", "true")); this.showRepairCost = Boolean.parseBoolean(properties.getProperty("show_repair_cost", "true")); this.radarLogarithm = Boolean.parseBoolean(properties.getProperty("radar_logarithm", "false")); this.showMinimapCoords = Boolean.parseBoolean(properties.getProperty("show_minimap_coords", "true")); this.borderColour = Integer.parseInt(properties.getProperty("border_colour", Integer.toString(DEFAULT_BORDER_COLOUR))); + CompactedItem.CRATE.setRBG(Integer.parseInt(properties.getProperty("crate_colour", Integer.toString(CompactedItem.CRATE.defaultColour)))); } public void save() { try { Properties properties = new Properties(); - properties.setProperty("compacted_colour", Integer.toString(compactedColour)); + properties.setProperty("compacted_colour", Integer.toString(CompactedItem.COMPACTED.getRBG())); properties.setProperty("radar_circles", Integer.toString(radarCircles)); properties.setProperty("radar_size", Integer.toString(radarSize)); properties.setProperty("alignment", alignment.name().toLowerCase()); @@ -126,11 +125,11 @@ public void save() { properties.setProperty("minimap_size", Integer.toString(minimapSize)); properties.setProperty("player_waypoints_enabled", Boolean.toString(playerWaypointsEnabled)); properties.setProperty("minimap_zoom", Float.toString(minimapZoom)); - properties.setProperty("crates_are_compacted", Boolean.toString(cratesAreCompacted)); properties.setProperty("show_repair_cost", Boolean.toString(showRepairCost)); properties.setProperty("radar_logarithm", Boolean.toString(radarLogarithm)); properties.setProperty("show_minimap_coords", Boolean.toString(showMinimapCoords)); properties.setProperty("border_colour", Integer.toString(borderColour)); + properties.setProperty("crate_colour", Integer.toString(CompactedItem.CRATE.getRBG())); try (FileOutputStream output = new FileOutputStream(file)) { properties.store(output, null); @@ -149,14 +148,6 @@ public void setShowItems(boolean showItems) { this.showItems = showItems; } - public int getColour() { - return compactedColour; - } - - public void setColour(int compactedColour) { - this.compactedColour = compactedColour; - } - public void setRadarCircles(int radarCircles) { this.radarCircles = radarCircles; } @@ -389,14 +380,6 @@ public void setMinimapZoom(float minimapZoom) { this.minimapZoom = minimapZoom; } - public boolean isCratesAreCompacted() { - return cratesAreCompacted; - } - - public void setCratesAreCompacted(boolean cratesAreCompacted) { - this.cratesAreCompacted = cratesAreCompacted; - } - public boolean isShowRepairCost() { return showRepairCost; } diff --git a/src/main/java/sh/okx/civmodern/common/ColourProvider.java b/src/main/java/sh/okx/civmodern/common/ColourProvider.java index 93d52887..351a3ceb 100644 --- a/src/main/java/sh/okx/civmodern/common/ColourProvider.java +++ b/src/main/java/sh/okx/civmodern/common/ColourProvider.java @@ -7,7 +7,6 @@ public class ColourProvider { private final CivMapConfig config; private Integer radarFg; private Integer radarBg; - private Integer compacted; private Integer chevron; private Integer border; @@ -15,10 +14,6 @@ public ColourProvider(CivMapConfig config) { this.config = config; } - public int getCompactedColour() { - return Objects.requireNonNullElseGet(compacted, config::getColour); - } - public int getForegroundColour() { return Objects.requireNonNullElseGet(radarFg, config::getRadarColour); } @@ -43,10 +38,6 @@ public void setTemporaryRadarBackgroundColour(Integer colour) { radarBg = colour; } - public void setTemporaryCompactedColour(Integer colour) { - compacted = colour; - } - public void setTemporaryChevronColour(Integer colour) { chevron = colour; } diff --git a/src/main/java/sh/okx/civmodern/common/features/CompactedItem.java b/src/main/java/sh/okx/civmodern/common/features/CompactedItem.java new file mode 100644 index 00000000..db6ff76b --- /dev/null +++ b/src/main/java/sh/okx/civmodern/common/features/CompactedItem.java @@ -0,0 +1,124 @@ +package sh.okx.civmodern.common.features; + +import java.awt.Color; +import java.util.List; +import java.util.NoSuchElementException; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.component.DataComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.component.ItemLore; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public enum CompactedItem { + CRATE(0xFF_41_41), + COMPACTED(0xFF_FF_58), + ; + + public final int defaultColour; + public final Color defaultAwtColor; + private volatile int colour; + + CompactedItem( + final int defaultColour + ) { + this.defaultColour = defaultColour; + this.defaultAwtColor = new Color(defaultColour); + this.colour = defaultColour; + } + + public int getRBG() { + return 0xFF_00_00_00 | this.colour; + } + + public void setRBG( + final int colour + ) { + this.colour = colour; + } + + private static final String CRATE_LORE = "Crate"; + private static final String COMPACTED_LORE = "Compacted Item"; + + public static @Nullable CompactedItem determineCompactedItemType( + final @NotNull ItemStack item + ) { + if (CompactedItem.isCrate(item)) { + return CompactedItem.CRATE; + } + else if (CompactedItem.isCompacted(item)) { + return CompactedItem.COMPACTED; + } + else { + return null; + } + } + + private static boolean isCrate( + final @NotNull ItemStack item + ) { + return item.getItem() == Items.CHEST + && hasLastPlainLoreLine(item, CRATE_LORE, true); + } + + private static boolean isCompacted( + final @NotNull ItemStack item + ) { + return hasLastPlainLoreLine(item, COMPACTED_LORE, true); + } + + private static boolean hasLastPlainLoreLine( + final @NotNull ItemStack item, + final @NotNull String expected, + final boolean caseSensitive + ) { + final ItemLore lore = item.getComponents().get(DataComponents.LORE); + if (lore == null) { + return false; + } + final Component line; + try { + line = lore.lines().getLast(); + } + catch (final NoSuchElementException ignored) { + return false; + } + final var content = new StringBuilder(); + for (final Component child : line.toFlatList()) { + if (!Style.EMPTY.equals(child.getStyle())) { + return false; + } + content.append(child.getString()); + } + return caseSensitive + ? expected.contentEquals(content) + : expected.equalsIgnoreCase(content.toString()); + } + + public static @NotNull ItemStack createExampleCrate() { + return new ItemStack( + Items.CHEST.builtInRegistryHolder(), + 64, + DataComponentPatch.builder() + .set(DataComponents.LORE, new ItemLore(List.of( + Component.literal(CompactedItem.CRATE_LORE) + ))) + .build() + ); + } + + public static @NotNull ItemStack createExampleCompacted() { + return new ItemStack( + Items.STONE.builtInRegistryHolder(), + 64, + DataComponentPatch.builder() + .set(DataComponents.LORE, new ItemLore(List.of( + Component.literal(CompactedItem.COMPACTED_LORE) + ))) + .build() + ); + } +} diff --git a/src/main/java/sh/okx/civmodern/common/features/ExtendedItemStack.java b/src/main/java/sh/okx/civmodern/common/features/ExtendedItemStack.java deleted file mode 100644 index eeff819a..00000000 --- a/src/main/java/sh/okx/civmodern/common/features/ExtendedItemStack.java +++ /dev/null @@ -1,6 +0,0 @@ -package sh.okx.civmodern.common.features; - -public interface ExtendedItemStack { - String COMPACTED_ITEM_LORE = "Compacted Item"; - boolean isMarkedAsCompacted(); -} diff --git a/src/main/java/sh/okx/civmodern/common/gui/screen/ItemsConfigScreen.java b/src/main/java/sh/okx/civmodern/common/gui/screen/ItemsConfigScreen.java index 91dc1d47..0c82fae9 100644 --- a/src/main/java/sh/okx/civmodern/common/gui/screen/ItemsConfigScreen.java +++ b/src/main/java/sh/okx/civmodern/common/gui/screen/ItemsConfigScreen.java @@ -1,219 +1,180 @@ package sh.okx.civmodern.common.gui.screen; -import java.util.List; +import com.mojang.blaze3d.platform.InputConstants; +import io.wispforest.owo.ui.base.BaseOwoScreen; +import io.wispforest.owo.ui.component.Components; +import io.wispforest.owo.ui.component.ItemComponent; +import io.wispforest.owo.ui.container.Containers; +import io.wispforest.owo.ui.container.FlowLayout; +import io.wispforest.owo.ui.core.Color; +import io.wispforest.owo.ui.core.HorizontalAlignment; +import io.wispforest.owo.ui.core.Insets; +import io.wispforest.owo.ui.core.OwoUIAdapter; +import io.wispforest.owo.ui.core.Sizing; +import io.wispforest.owo.ui.core.Surface; +import io.wispforest.owo.ui.core.VerticalAlignment; import java.util.Objects; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.Button; +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.core.component.DataComponentMap; -import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.component.ItemLore; import org.jetbrains.annotations.NotNull; -import org.lwjgl.glfw.GLFW; import sh.okx.civmodern.common.CivMapConfig; -import sh.okx.civmodern.common.ColourProvider; -import sh.okx.civmodern.common.features.ExtendedItemStack; -import sh.okx.civmodern.common.gui.Alignment; +import sh.okx.civmodern.common.features.CompactedItem; import sh.okx.civmodern.common.gui.widget.ColourTextEditBox; -import sh.okx.civmodern.common.gui.widget.HsbColourPicker; -import sh.okx.civmodern.common.gui.widget.ImageButton; -import sh.okx.civmodern.common.gui.widget.TextRenderable; -import sh.okx.civmodern.common.gui.widget.ToggleButton; +import sh.okx.civmodern.common.gui.widget.OwoButton; +import sh.okx.civmodern.common.gui.widget.OwoColourPicker; -final class ItemsConfigScreen extends AbstractConfigScreen { - private static final ItemStack ITEM; static { - ITEM = new ItemStack(Items.STONE, 64); - ITEM.applyComponents( - DataComponentMap.builder() - .set(DataComponents.LORE, new ItemLore( - List.of(Component.literal(ExtendedItemStack.COMPACTED_ITEM_LORE)) - )) - .build() - ); - } - - private final ColourProvider colourProvider; - - private HsbColourPicker colourPicker; - private int itemX; - private int itemY; +final class ItemsConfigScreen extends BaseOwoScreen { + private final CivMapConfig config; + private final Screen previousScreen; ItemsConfigScreen( final @NotNull CivMapConfig config, - final @NotNull ColourProvider colourProvider, - final @NotNull MainConfigScreen parent + final @NotNull Screen previousScreen ) { - super( - config, - Objects.requireNonNull(parent), - Component.translatable("civmodern.screen.items.title") - ); - this.colourProvider = Objects.requireNonNull(colourProvider); + this.config = Objects.requireNonNull(config); + this.previousScreen = previousScreen; } + /// Prevent Minecraft from pausing various parts of Minecraft while this screen is open @Override - protected void init() { - super.init(); - - addRenderableOnly(new TextRenderable.CentreAligned( - this.font, - this.centreX, - getHeaderY(), - this.title - )); + public boolean isPauseScreen() { + return false; + } - int offsetY = getBodyY(); + @Override + protected @NotNull OwoUIAdapter createAdapter() { + return OwoUIAdapter.create(this, Containers::verticalFlow); + } - this.itemX = centreX - 8; // Items have a render size of 16x16 - this.itemY = offsetY; - offsetY += 16 + 10; + @Override + protected void build( + final @NotNull FlowLayout rootComponent + ) { + rootComponent.surface(Surface.VANILLA_TRANSLUCENT); + rootComponent.verticalAlignment(VerticalAlignment.TOP); + + final var picker = new OwoColourPicker(rootComponent); + + final var body = Containers.verticalFlow(Sizing.content(), Sizing.content()); + body.horizontalAlignment(HorizontalAlignment.CENTER); + body.gap(10); + body.padding(Insets.of(10, 10, 0, 0)); + rootComponent.child(body); + + // Title + body.child( + Components.label(Component.translatable("civmodern.screen.items.title")) + .color(Color.ofRgb(0xFF_FF_FF)) + .shadow(true) + ); - final var compactedColourEditBox = addRenderableWidget(new ColourTextEditBox( - this.font, - this.centreX - 30, - offsetY, - 60, // width - 20, // height - this.config::getColour, - this.config::setColour - )); - addRenderableWidget(this.colourPicker = new HsbColourPicker( - this.centreX - (compactedColourEditBox.getWidth() / 2) - 5 - 20, - offsetY, - 20, - 20, - this.config.getColour(), - (colour) -> { - compactedColourEditBox.setValue("#" + "%06X".formatted(colour)); - this.config.setColour(colour); - }, - this.colourProvider::setTemporaryCompactedColour, - () -> {} - )); - addRenderableWidget(new ImageButton( - this.centreX + (compactedColourEditBox.getWidth() / 2) + 5, - offsetY, - 20, - 20, - ResourceLocation.tryBuild("civmodern", "gui/rollback.png"), - (button) -> { - final int colour = 0xffff58; - compactedColourEditBox.setValue("#FFFF58"); - this.config.setColour(colour); - this.colourPicker.close(); - } + // Compacted item colours + body.child(colourPickerRow( + picker, + 0xFF_FF_58, + CompactedItem.COMPACTED::getRBG, + CompactedItem.COMPACTED::setRBG, + CompactedItem.createExampleCompacted() )); - offsetY += Button.DEFAULT_HEIGHT + 10; - addRenderableWidget(new ToggleButton( - this.centreX - (Button.DEFAULT_WIDTH / 2), - offsetY, - ToggleButton.DEFAULT_BUTTON_WIDTH, - Component.translatable("civmodern.screen.items.crates"), - this.config::isCratesAreCompacted, - this.config::setCratesAreCompacted, - Tooltip.create(Component.translatable("civmodern.screen.items.crates.tooltip")), - ToggleButton.DEFAULT_NARRATION + // Crate colours + body.child(colourPickerRow( + picker, + 0xFF_41_41, + CompactedItem.CRATE::getRBG, + CompactedItem.CRATE::setRBG, + CompactedItem.createExampleCrate() )); - offsetY += Button.DEFAULT_HEIGHT + 4; - addRenderableWidget(new ToggleButton( - this.centreX - (Button.DEFAULT_WIDTH / 2), - offsetY, - ToggleButton.DEFAULT_BUTTON_WIDTH, + // Show repair cost + body.child(OwoButton.toggleButton( Component.translatable("civmodern.screen.items.repair"), this.config::isShowRepairCost, this.config::setShowRepairCost, - Tooltip.create(Component.translatable("civmodern.screen.items.repair.tooltip")), - ToggleButton.DEFAULT_NARRATION + Tooltip.create(Component.translatable("civmodern.screen.items.repair.tooltip")) )); - addRenderableWidget( - Button - .builder( - CommonComponents.GUI_DONE, - (button) -> { - this.config.save(); - this.minecraft.setScreen(this.parent); - } - ) - .width(150) - .pos( - this.centreX - 75, - getFooterY(offsetY) - ) - .build() - ); - } - - @Override - public void render( - final @NotNull GuiGraphics guiGraphics, - final int mouseX, - final int mouseY, - final float delta - ) { - super.render(guiGraphics, mouseX, mouseY, delta); - - guiGraphics.renderItem(ITEM, this.itemX, this.itemY); - guiGraphics.renderItemDecorations(this.font, ITEM, this.itemX, this.itemY); - - if (isCursorOverItem(mouseX, mouseY)) { -// guiGraphics.renderTooltip(this.font, ITEM, mouseX, mouseY); - } - } - - @Override - public boolean mouseClicked( - final double mouseX, - final double mouseY, - final int button - ) { - if (!super.mouseClicked(mouseX, mouseY, button)) { - if (this.minecraft != null) { - final LocalPlayer player = this.minecraft.player; - if (player != null && isCursorOverItem((int) mouseX, (int) mouseY) && button == GLFW.GLFW_MOUSE_BUTTON_1 && player.isCreative()) { - player.addItem(ITEM.copy()); - return true; - } + body.child(Components.spacer()); + body.child(new OwoButton( + CommonComponents.GUI_DONE, + (button) -> { + this.config.save(); + this.minecraft.setScreen(null); // .onClose() will redirect to the .previousScreen } - return false; - } else { - return true; - } - } - - private boolean isCursorOverItem( - final int mouseX, - final int mouseY - ) { - return mouseX >= this.itemX - 1 - && mouseX < this.itemX + 17 - && mouseY > this.itemY - 1 - && mouseY < this.itemY + 17; + )); } - @Override - public void mouseMoved( - final double mouseX, - final double mouseY + private @NotNull FlowLayout colourPickerRow( + final @NotNull OwoColourPicker picker, + final int defaultColour, + final @NotNull IntSupplier colourGetter, + final @NotNull IntConsumer colourSetter, + final @NotNull ItemStack exampleItem ) { - super.mouseMoved(mouseX, mouseY); - if (this.colourPicker != null) { - this.colourPicker.mouseMoved(mouseX, mouseY); - } + final var container = Containers.horizontalFlow( + Sizing.content(), + Sizing.content() + ); + container.allowOverflow(true); + container.gap(2); + final var colourField = new ColourTextEditBox( + Sizing.fixed(60), // width + colourGetter, + colourSetter + ); + colourField.margins(Insets.top(-1)); // For some reason, fields are offset vertically by 1px + return container + .child(OwoButton.imageButton( + ResourceLocation.fromNamespaceAndPath("civmodern", "gui/colour.png"), + (button) -> picker.showPopup(button, colourGetter, colourSetter) + )) + .child(colourField) + .child(OwoButton.imageButton( + ResourceLocation.fromNamespaceAndPath("civmodern", "gui/rollback.png"), + (button) -> { + colourSetter.accept(defaultColour); + colourField.setColourText(defaultColour); + } + )) + .child(Components.item(exampleItem).configure((ItemComponent component) -> { + component.sizing(Sizing.fixed(16), Sizing.fixed(16)); + component.margins(Insets.both(1, 1)); + component.showOverlay(true); // show decorations (count, damage, etc) + component.setTooltipFromStack(true); + component.mouseDown().subscribe((mouseX, mouseY, button) -> { + if (button != InputConstants.MOUSE_BUTTON_MIDDLE) { + return false; + } + final Minecraft minecraft = this.minecraft; + if (minecraft == null) { + return true; // Do nothing, avoid NPE + } + final LocalPlayer player = minecraft.player; + if (player == null) { + return true; // Do nothing, avoid NPE + } + if (!player.isCreative()) { + return true; // Do nothing, not allowed + } + if (player.addItem(exampleItem.copy())) { + player.inventoryMenu.broadcastChanges(); + } + return true; + }); + })); } @Override public void onClose() { - super.onClose(); - this.colourProvider.setTemporaryCompactedColour(null); + this.minecraft.setScreen(this.previousScreen); this.config.save(); } } diff --git a/src/main/java/sh/okx/civmodern/common/gui/screen/MainConfigScreen.java b/src/main/java/sh/okx/civmodern/common/gui/screen/MainConfigScreen.java index 5b519385..c1d3a2ff 100644 --- a/src/main/java/sh/okx/civmodern/common/gui/screen/MainConfigScreen.java +++ b/src/main/java/sh/okx/civmodern/common/gui/screen/MainConfigScreen.java @@ -48,7 +48,7 @@ protected void init() { Button .builder( Component.translatable("civmodern.screen.main.items"), - (button) -> this.minecraft.setScreen(new ItemsConfigScreen(this.config, this.colourProvider, this)) + (button) -> this.minecraft.setScreen(new ItemsConfigScreen(this.config, this)) ) .pos(offsetX, offsetY) .build() diff --git a/src/main/java/sh/okx/civmodern/common/gui/screen/RadarConfigScreen.java b/src/main/java/sh/okx/civmodern/common/gui/screen/RadarConfigScreen.java index 6006baab..509e9d36 100644 --- a/src/main/java/sh/okx/civmodern/common/gui/screen/RadarConfigScreen.java +++ b/src/main/java/sh/okx/civmodern/common/gui/screen/RadarConfigScreen.java @@ -392,7 +392,6 @@ public void set(final double value) { offsetY += this.font.lineHeight + 2; final var colourEditBox = addRenderableWidget(new ColourTextEditBox( - this.font, innerCenterX - 30, offsetY, 60, @@ -408,7 +407,7 @@ public void set(final double value) { 20, colourGetter.getAsInt(), (colour) -> { - colourEditBox.setColourFromInt(colour); + colourEditBox.setColourText(colour); colourSetter.accept(colour); }, preview, @@ -422,7 +421,7 @@ public void set(final double value) { 20, ROLLBACK_ICON, (button) -> { - colourEditBox.setColourFromInt(defaultColour); + colourEditBox.setColourText(defaultColour); colourSetter.accept(defaultColour); hsb.close(); } diff --git a/src/main/java/sh/okx/civmodern/common/gui/widget/ColourTextEditBox.java b/src/main/java/sh/okx/civmodern/common/gui/widget/ColourTextEditBox.java index 688d18fe..e3348b46 100644 --- a/src/main/java/sh/okx/civmodern/common/gui/widget/ColourTextEditBox.java +++ b/src/main/java/sh/okx/civmodern/common/gui/widget/ColourTextEditBox.java @@ -1,51 +1,47 @@ package sh.okx.civmodern.common.gui.widget; +import io.wispforest.owo.ui.component.TextBoxComponent; +import io.wispforest.owo.ui.core.Positioning; +import io.wispforest.owo.ui.core.Sizing; +import java.util.Objects; import java.util.function.IntConsumer; import java.util.function.IntSupplier; import java.util.regex.Pattern; - -import io.wispforest.owo.mixin.ui.access.TextFieldWidgetAccessor; -import io.wispforest.owo.ui.core.CursorStyle; -import io.wispforest.owo.ui.core.Sizing; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.components.EditBox; -import net.minecraft.network.chat.Component; import org.jetbrains.annotations.NotNull; -public class ColourTextEditBox extends EditBox { - private static final Pattern HEX_COLOUR_REGEX = Pattern.compile("^(#[0-9A-F]{0,6})?$", Pattern.CASE_INSENSITIVE); +public class ColourTextEditBox extends TextBoxComponent { + private static final Pattern HEX_COLOUR_REGEX = Pattern.compile("^#([0-9A-F]{0,6})?$", Pattern.CASE_INSENSITIVE); + + private final IntConsumer colourSetter; public ColourTextEditBox( - Sizing horizontalSizing, + final @NotNull Sizing horizontalSizing, final @NotNull IntSupplier colourGetter, final @NotNull IntConsumer colourSetter ) { - this(Minecraft.getInstance().font, 0, 0, 0, 0, colourGetter, colourSetter); - - this.sizing(horizontalSizing, Sizing.content()); - setColourFromInt(colourGetter.getAsInt()); + super(horizontalSizing); + this.colourSetter = Objects.requireNonNull(colourSetter); + this.setMaxLength(7); + this.setFilter((value) -> HEX_COLOUR_REGEX.matcher(value).matches()); + this.onChanged().subscribe((value) -> { + // Support 3-digit hex codes + // https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color + if (value.length() == 3) { + final var chars = new char[6]; + chars[0] = chars[1] = value.charAt(0); // r + chars[2] = chars[3] = value.charAt(1); // g + chars[4] = chars[5] = value.charAt(2); // b + value = new String(chars); + } + if (value.length() == 6) { + colourSetter.accept(Integer.parseInt(value, 16)); + } + }); this.moveCursorToStart(false); - } - - @Override - public void updateX(int x) { - super.updateX(x); - ((TextFieldWidgetAccessor) this).owo$updateTextPosition(); - } - - @Override - public void updateY(int y) { - super.updateY(y); - ((TextFieldWidgetAccessor) this).owo$updateTextPosition(); - } - - protected CursorStyle owo$preferredCursorStyle() { - return CursorStyle.TEXT; + this.setColourText(colourGetter.getAsInt()); } public ColourTextEditBox( - final @NotNull Font font, final int x, final int y, final int width, @@ -53,33 +49,26 @@ public ColourTextEditBox( final @NotNull IntSupplier colourGetter, final @NotNull IntConsumer colourSetter ) { - super(font, x, y, width, height, Component.empty()); - setColourFromInt(colourGetter.getAsInt()); - setMaxLength(7); - setFilter((string) -> HEX_COLOUR_REGEX.matcher(string).matches()); - setResponder((val) -> { - if (val.length() <= 1) { - return; - } - val = val.substring(1); - // Support 3-digit hex codes - // https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color - if (val.length() == 3) { - final var chars = new char[6]; - chars[0] = chars[1] = val.charAt(0); // r - chars[2] = chars[3] = val.charAt(1); // g - chars[4] = chars[5] = val.charAt(2); // b - val = new String(chars); - } - if (val.length() == 6) { - colourSetter.accept(Integer.parseInt(val, 16)); - } - }); + this( + Sizing.fixed(width), + colourGetter, + colourSetter + ); + this.verticalSizing(Sizing.fixed(height)); + this.positioning(Positioning.absolute(x, y)); + } + + public void setColour( + final int colour + ) { + this.colourSetter.accept(colour); + this.setColourText(colour); } - public void setColourFromInt( + /// This differs from [#setColour(int)] in that it ONLY updates the field's text. + public void setColourText( final int colour ) { - setValue("#" + "%06X".formatted(colour)); + this.value = "#" + "%06X".formatted(0xFF_FF_FF & colour).toUpperCase(); } } diff --git a/src/main/java/sh/okx/civmodern/common/gui/widget/OwoButton.java b/src/main/java/sh/okx/civmodern/common/gui/widget/OwoButton.java new file mode 100644 index 00000000..d15e5217 --- /dev/null +++ b/src/main/java/sh/okx/civmodern/common/gui/widget/OwoButton.java @@ -0,0 +1,82 @@ +package sh.okx.civmodern.common.gui.widget; + +import io.wispforest.owo.ui.component.ButtonComponent; +import io.wispforest.owo.ui.core.Sizing; +import java.util.Objects; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.function.BooleanConsumer; +import org.jetbrains.annotations.NotNull; + +public class OwoButton extends ButtonComponent { + public static final Consumer ON_PRESS_NO_OP = (button) -> {}; + + public OwoButton( + final @NotNull Component message, + final @NotNull Consumer onPress + ) { + super( + message, + onPress + ); + this.sizing( + Sizing.fixed(Button.DEFAULT_WIDTH), + Sizing.fixed(Button.DEFAULT_HEIGHT) + ); + } + + public static @NotNull OwoButton toggleButton( + final @NotNull Component label, + final @NotNull BooleanSupplier valueGetter, + final @NotNull BooleanConsumer valueSetter, + final Tooltip tooltip + ) { + Objects.requireNonNull(label); + Objects.requireNonNull(valueGetter); + Objects.requireNonNull(valueSetter); + final var button = new OwoButton( + valueGetter.getAsBoolean() + ? Component.translatable("civmodern.button.toggle.on", label) + : Component.translatable("civmodern.button.toggle.off", label), + (cb_button) -> { + final boolean next = !valueGetter.getAsBoolean(); + valueSetter.accept(next); + cb_button.setMessage(next + ? Component.translatable("civmodern.button.toggle.on", label) + : Component.translatable("civmodern.button.toggle.off", label) + ); + } + ); + button.setTooltip(tooltip); + return button; + } + + public static final int IMAGE_BUTTON_SIZE = 20; + public static final int IMAGE_BUTTON_TEXTURE_WIDTH = 20; + public static final int IMAGE_BUTTON_TEXTURE_HEIGHT = 40; + public static @NotNull OwoButton imageButton( + final @NotNull ResourceLocation texture, + final @NotNull Consumer onPress + ) { + final var button = new OwoButton( + Component.empty(), + Objects.requireNonNull(onPress) + ); + button.sizing( + Sizing.fixed(IMAGE_BUTTON_SIZE), + Sizing.fixed(IMAGE_BUTTON_SIZE) + ); + button.renderer(Renderer.texture( + Objects.requireNonNull(texture), + 0, + 0, + IMAGE_BUTTON_TEXTURE_WIDTH, + IMAGE_BUTTON_TEXTURE_HEIGHT + )); + return button; + } +} diff --git a/src/main/java/sh/okx/civmodern/common/gui/widget/OwoColourPicker.java b/src/main/java/sh/okx/civmodern/common/gui/widget/OwoColourPicker.java new file mode 100644 index 00000000..a2d3cd64 --- /dev/null +++ b/src/main/java/sh/okx/civmodern/common/gui/widget/OwoColourPicker.java @@ -0,0 +1,86 @@ +package sh.okx.civmodern.common.gui.widget; + +import io.wispforest.owo.ui.component.ColorPickerComponent; +import io.wispforest.owo.ui.container.Containers; +import io.wispforest.owo.ui.container.FlowLayout; +import io.wispforest.owo.ui.core.Color; +import io.wispforest.owo.ui.core.Positioning; +import io.wispforest.owo.ui.core.Sizing; +import io.wispforest.owo.ui.core.Surface; +import java.util.Objects; +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; +import net.minecraft.client.gui.components.AbstractWidget; +import org.jetbrains.annotations.NotNull; + +public class OwoColourPicker extends ColorPickerComponent { + private static final int POPUP_WIDTH = 100; + private static final int POPUP_HEIGHT = 50; + + protected final FlowLayout rootComponent; + protected IntConsumer colourSetter; + protected AbstractWidget anchor; + + public OwoColourPicker( + final @NotNull FlowLayout rootComponent + ) { + this.rootComponent = Objects.requireNonNull(rootComponent); + this.positioning(Positioning.absolute(0, 0)); + this.sizing( + Sizing.fixed(POPUP_WIDTH), + Sizing.fixed(POPUP_HEIGHT) + ); + this.selectorWidth(10); + this.selectorPadding(2); + this.onChanged().subscribe((colour) -> { + final IntConsumer colourSetter = this.colourSetter; + if (colourSetter != null) { + colourSetter.accept(colour.rgb()); + } + }); + } + + @Override + public void update( + final float delta, + final int mouseX, + final int mouseY + ) { + super.update( + delta, + mouseX, + mouseY + ); + final AbstractWidget anchor = this.anchor; + if (anchor != null) { + final int anchorX = anchor.x(); + this.updateX(anchorX); + final int anchorY = anchor.y(); + final int anchorBottomY = anchorY + anchor.height(); + if (this.rootComponent.isInBoundingBox(anchorX, anchorBottomY + POPUP_HEIGHT)) { + this.updateY(anchorBottomY + 1); + } + else if (this.rootComponent.isInBoundingBox(anchorX, anchorY - POPUP_HEIGHT)) { + this.updateY(anchorY - POPUP_HEIGHT - 1); + } + } + } + + public void showPopup( + final @NotNull AbstractWidget anchor, + final @NotNull IntSupplier colourGetter, + final @NotNull IntConsumer colourSetter + ) { + this.anchor = Objects.requireNonNull(anchor); + this.colourSetter = Objects.requireNonNull(colourSetter); + this.selectedColor(Color.ofRgb(colourGetter.getAsInt())); + final var popup = Containers.overlay(this); + popup.zIndex(Short.MAX_VALUE); + popup.surface(Surface.BLANK); + this.rootComponent.child(popup); + this.rootComponent.focusHandler().focus( + this, + FocusSource.MOUSE_CLICK + ); + } +} diff --git a/src/main/java/sh/okx/civmodern/common/gui/widget/ToggleButton.java b/src/main/java/sh/okx/civmodern/common/gui/widget/ToggleButton.java index 243461f4..0c345538 100644 --- a/src/main/java/sh/okx/civmodern/common/gui/widget/ToggleButton.java +++ b/src/main/java/sh/okx/civmodern/common/gui/widget/ToggleButton.java @@ -35,13 +35,9 @@ public ToggleButton( } protected final @NotNull Component generateLabel() { - return Component.translatable( - "civmodern.button.toggle", - this.label, - this.valueGetter.getAsBoolean() - ? Component.translatable("civmodern.button.toggle.on") - : Component.translatable("civmodern.button.toggle.off") - ); + return this.valueGetter.getAsBoolean() + ? Component.translatable("civmodern.button.toggle.on", this.label) + : Component.translatable("civmodern.button.toggle.off", this.label); } @Override diff --git a/src/main/java/sh/okx/civmodern/common/map/screen/EditWaypointModal.java b/src/main/java/sh/okx/civmodern/common/map/screen/EditWaypointModal.java index 4a13fb37..a1c55958 100644 --- a/src/main/java/sh/okx/civmodern/common/map/screen/EditWaypointModal.java +++ b/src/main/java/sh/okx/civmodern/common/map/screen/EditWaypointModal.java @@ -96,7 +96,7 @@ public void setWaypoint(Waypoint waypoint) { 20, waypoint.colour(), (colour) -> { - colourBox.setColourFromInt(colour); + colourBox.setColour(colour); this.colour = colour; this.previewColour = colour; }, diff --git a/src/main/java/sh/okx/civmodern/common/map/screen/NewWaypointModal.java b/src/main/java/sh/okx/civmodern/common/map/screen/NewWaypointModal.java index 333ac3ae..09a771aa 100644 --- a/src/main/java/sh/okx/civmodern/common/map/screen/NewWaypointModal.java +++ b/src/main/java/sh/okx/civmodern/common/map/screen/NewWaypointModal.java @@ -86,7 +86,7 @@ public void open(String name, int x, int y, int z) { 20, this.colour, (colour) -> { - colourBox.setColourFromInt(colour); + colourBox.setColour(colour); this.colour = colour; this.previewColour = colour; }, diff --git a/src/main/java/sh/okx/civmodern/common/mixins/GuiGraphicsMixin.java b/src/main/java/sh/okx/civmodern/common/mixins/GuiGraphicsMixin.java index f4a25d67..63ee80ae 100644 --- a/src/main/java/sh/okx/civmodern/common/mixins/GuiGraphicsMixin.java +++ b/src/main/java/sh/okx/civmodern/common/mixins/GuiGraphicsMixin.java @@ -5,32 +5,46 @@ import net.minecraft.world.item.ItemStack; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.ModifyVariable; -import sh.okx.civmodern.common.AbstractCivModernMod; -import sh.okx.civmodern.common.features.ExtendedItemStack; +import sh.okx.civmodern.common.features.CompactedItem; @Mixin(GuiGraphics.class) public abstract class GuiGraphicsMixin { - @ModifyVariable(method = "renderItemCount", at = @At("HEAD"), argsOnly = true) - protected @Nullable String civmodern$showCompactedItem(String value, @Local(argsOnly = true) ItemStack item) { + @Unique + private CompactedItem compactedItemType; + + @ModifyVariable( + method = "renderItemCount", + at = @At("HEAD"), + argsOnly = true + ) + protected @Nullable String civmodern$alwaysShowItemAmountIfCompacted( + final String value, + final @Local(argsOnly = true) ItemStack item + ) { if (value != null) { return value; - } else if (((ExtendedItemStack) (Object) item).isMarkedAsCompacted()) { - return String.valueOf(item.getCount()); - } else { - return null; } + return switch (this.compactedItemType = CompactedItem.determineCompactedItemType(item)) { + case COMPACTED, CRATE -> String.valueOf(item.getCount()); + case null -> null; + }; } - @ModifyConstant(method = "renderItemCount", constant = @Constant(intValue = -1)) - protected int civmodern$colourCompactedItem(int itemColour, @Local(argsOnly = true) ItemStack item) { - if (((ExtendedItemStack) (Object) item).isMarkedAsCompacted()) { - return 0xff000000 | AbstractCivModernMod.getInstance().getColourProvider().getCompactedColour(); - } else { - return itemColour; - } + @ModifyConstant( + method = "renderItemCount", + constant = @Constant(intValue = -1) + ) + protected int civmodern$colourItemDecorationIfCompacted( + final int colour + ) { + return switch (this.compactedItemType) { + case final CompactedItem type -> type.getRBG(); + case null -> colour; + }; } } diff --git a/src/main/java/sh/okx/civmodern/common/mixins/ItemStackMixin.java b/src/main/java/sh/okx/civmodern/common/mixins/ItemStackMixin.java deleted file mode 100644 index c06b8b33..00000000 --- a/src/main/java/sh/okx/civmodern/common/mixins/ItemStackMixin.java +++ /dev/null @@ -1,55 +0,0 @@ -package sh.okx.civmodern.common.mixins; - -import net.minecraft.core.component.DataComponentMap; -import net.minecraft.core.component.DataComponents; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.Style; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.component.ItemLore; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import sh.okx.civmodern.common.AbstractCivModernMod; -import sh.okx.civmodern.common.features.ExtendedItemStack; - -@Mixin(ItemStack.class) -public abstract class ItemStackMixin implements ExtendedItemStack { - @Shadow - public abstract DataComponentMap getComponents(); - - @Unique - private Boolean civmodern$isCompacted = null; - - @Unique - @Override - public boolean isMarkedAsCompacted() { - if (this.civmodern$isCompacted == null) { - this.civmodern$isCompacted = civmodern$isCompacted(); - } - return this.civmodern$isCompacted; - } - - @Unique - private boolean civmodern$isCompacted() { - final ItemLore lore = getComponents().get(DataComponents.LORE); - if (lore == null) { - return false; - } - for (final Component line : lore.lines()) { - if (line == null) { - continue; - } - final var content = new StringBuilder(); - for (final Component child : line.toFlatList()) { - if (!Style.EMPTY.equals(child.getStyle())) { - return false; - } - content.append(child.getString()); - } - if (ExtendedItemStack.COMPACTED_ITEM_LORE.contentEquals(content) || (AbstractCivModernMod.getInstance().getConfig().isCratesAreCompacted() && "Crate".contentEquals(content))) { - return true; - } - } - return false; - } -} diff --git a/src/main/resources/assets/civmodern/lang/en_us.json b/src/main/resources/assets/civmodern/lang/en_us.json index e8fba95c..3d5e5def 100644 --- a/src/main/resources/assets/civmodern/lang/en_us.json +++ b/src/main/resources/assets/civmodern/lang/en_us.json @@ -41,8 +41,6 @@ "civmodern.screen.mapping": "Rendering", "civmodern.screen.radar.background_transparency": "BG Transparency: %s", "civmodern.screen.items.title": "Civ Modern Config (Items)", - "civmodern.screen.items.crates": "Include crates", - "civmodern.screen.items.crates.tooltip": "If enabled, crates will have the compacted item colour", "civmodern.screen.items.repair": "Show repair cost", "civmodern.screen.items.repair.tooltip": "If enabled, will show the repair cost of items in their lore", "civmodern.screen.ice.title": "Civ Modern Config (Ice Road Macro)", @@ -62,8 +60,7 @@ "civmodern.radar.enter" : "§r%s §eappeared at §b[%s§b]", "civmodern.radar.hover" : "Click to highlight position\nControl click to add waypoint", "civmodern.radar.leave" : "§r%s §edisappeared at §b[%s§b]", - "civmodern.button.toggle": "%s: %s", - "civmodern.button.toggle.on": "ON", - "civmodern.button.toggle.off": "OFF", + "civmodern.button.toggle.on": "%s: ON", + "civmodern.button.toggle.off": "%s: OFF", "civmodern.repaircost": "%d levels to repair" } diff --git a/src/main/resources/civmodern.accesswidener b/src/main/resources/civmodern.accesswidener new file mode 100644 index 00000000..1d6e5b30 --- /dev/null +++ b/src/main/resources/civmodern.accesswidener @@ -0,0 +1,9 @@ +accessWidener v2 named + +# https://fabricmc.net/wiki/tutorial:accesswideners +# https://linkie.shedaniel.dev/mappings?namespace=mojang&version=1.21.8 + +# Use './gradlew :civianmod-clickdest:genSources' after any change to this file. +# You'll probably need to reload your Gradle project too. + +accessible field net/minecraft/client/gui/components/EditBox value Ljava/lang/String; diff --git a/src/main/resources/civmodern.common.mixins.json b/src/main/resources/civmodern.mixins.json similarity index 95% rename from src/main/resources/civmodern.common.mixins.json rename to src/main/resources/civmodern.mixins.json index 11b1d86c..f31d24fe 100644 --- a/src/main/resources/civmodern.common.mixins.json +++ b/src/main/resources/civmodern.mixins.json @@ -10,7 +10,6 @@ "ClientPacketListenerMixin", "GuiGraphicsMixin", "InventoryMixin", - "ItemStackMixin", "KeyMappingAccessor", "RespawnMixin", "ScreenAccessor", diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2224c272..29d12240 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -26,6 +26,7 @@ "fabric": "*" }, "mixins": [ - "civmodern.common.mixins.json" - ] + "civmodern.mixins.json" + ], + "accessWidener": "civmodern.accesswidener" }