diff --git a/src/generated/resources/.cache/cache b/src/generated/resources/.cache/cache index 3250272bdb..840b6279b0 100644 --- a/src/generated/resources/.cache/cache +++ b/src/generated/resources/.cache/cache @@ -566,8 +566,8 @@ bf2b0310500213ff853c748c236eb5d01f61658e assets/create/blockstates/yellow_toolbo 5616dda664dd106d576848124fc0fc1de18d0fd3 assets/create/blockstates/yellow_valve_handle.json 7f39521b211441f5c3e06d60c5978cebe16cacfb assets/create/blockstates/zinc_block.json b7181bcd8182b2f17088e5aa881f374c9c65470c assets/create/blockstates/zinc_ore.json -f1bedeb51c35e70a2247178634e61ea637a6622e assets/create/lang/en_ud.json -59fd557ef593efa3c7b783195ec5dc789eae1834 assets/create/lang/en_us.json +0f4e5a2fc58580df5b156fdac438ddeb6b57e386 assets/create/lang/en_ud.json +85d790bedbdc65bb6e6377edcc63e7b00455e879 assets/create/lang/en_us.json 487a511a01b2a4531fb672f917922312db78f958 assets/create/models/block/acacia_window.json b48060cba1a382f373a05bf0039054053eccf076 assets/create/models/block/acacia_window_pane_noside.json 3066db1bf03cffa1a9c7fbacf47ae586632f4eb3 assets/create/models/block/acacia_window_pane_noside_alt.json @@ -1663,6 +1663,10 @@ f7aca6aff65e1de269a99cf2a280d9841b7a0076 assets/create/models/item/brass_sheet.j 87637b39c3a5a386457d52b37eb65f1c4bcabaf0 assets/create/models/item/chocolate_glazed_berries.json fe67c3f380d17735a9436a4579a8be1a02b8e4a0 assets/create/models/item/chute.json 6680a68526576ded5dac2aa3bc9fb9de3e744146 assets/create/models/item/cinder_flour.json +0f79260d962011817a41af8c46996f426669b089 assets/create/models/item/clipboard.json +1bd8ad56fe261f3ff2f7bb2ac12f691a76ae5c94 assets/create/models/item/clipboard_0.json +7a4d58451e051796e21138faf3428f3d3c14ef85 assets/create/models/item/clipboard_1.json +376e88de90f33fd32f3f2ffe0062ae5b3bdc1370 assets/create/models/item/clipboard_2.json c1da21be9f1af4f7a2ef4ec9cd92195d65ada316 assets/create/models/item/clockwork_bearing.json 0a2a0f0aafeab0088172f77afd40c1fa2cc1f2b8 assets/create/models/item/clutch.json dcb09deae110077bcddf090996b51cc66e9a7de3 assets/create/models/item/cogwheel.json diff --git a/src/generated/resources/assets/create/lang/en_ud.json b/src/generated/resources/assets/create/lang/en_ud.json index ebd1117181..f04109d00c 100644 --- a/src/generated/resources/assets/create/lang/en_ud.json +++ b/src/generated/resources/assets/create/lang/en_ud.json @@ -595,6 +595,7 @@ "item.create.chocolate_glazed_berries": "s\u01DD\u0131\u0279\u0279\u01DD\u15FA p\u01DDz\u0250\u05DF\u2141 \u01DD\u0287\u0250\u05DFo\u0254o\u0265\u0186", "item.create.chromatic_compound": "punod\u026Fo\u0186 \u0254\u0131\u0287\u0250\u026Fo\u0279\u0265\u0186", "item.create.cinder_flour": "\u0279no\u05DF\u2132 \u0279\u01DDpu\u0131\u0186", + "item.create.clipboard": "p\u0279\u0250oqd\u0131\u05DF\u0186", "item.create.copper_backtank": "\u029Eu\u0250\u0287\u029E\u0254\u0250\u15FA \u0279\u01DDddo\u0186", "item.create.copper_backtank_placeable": "\u01DD\u05DFq\u0250\u01DD\u0254\u0250\u05DF\u0500 \u029Eu\u0250\u0287\u029E\u0254\u0250\u15FA \u0279\u01DDddo\u0186", "item.create.copper_diving_boots": "s\u0287oo\u15FA bu\u0131\u028C\u0131\u15E1 \u0279\u01DDddo\u0186", diff --git a/src/generated/resources/assets/create/lang/en_us.json b/src/generated/resources/assets/create/lang/en_us.json index af41fc01de..05c6ce5ab5 100644 --- a/src/generated/resources/assets/create/lang/en_us.json +++ b/src/generated/resources/assets/create/lang/en_us.json @@ -602,6 +602,7 @@ "item.create.chocolate_glazed_berries": "Chocolate Glazed Berries", "item.create.chromatic_compound": "Chromatic Compound", "item.create.cinder_flour": "Cinder Flour", + "item.create.clipboard": "Clipboard", "item.create.copper_backtank": "Copper Backtank", "item.create.copper_backtank_placeable": "Copper Backtank Placeable", "item.create.copper_diving_boots": "Copper Diving Boots", @@ -1130,6 +1131,7 @@ "create.gui.sequenced_gearshift.speed.forward_fast": "Double speed, Forwards", "create.gui.sequenced_gearshift.speed.back": "Input speed, Reversed", "create.gui.sequenced_gearshift.speed.back_fast": "Double speed, Reversed", + "create.gui.clipboard.erase_checked": "Erase checked items", "create.schematicAndQuill.dimensions": "Schematic Size: %1$sx%2$sx%3$s", "create.schematicAndQuill.firstPos": "First position set.", diff --git a/src/generated/resources/assets/create/models/item/clipboard.json b/src/generated/resources/assets/create/models/item/clipboard.json new file mode 100644 index 0000000000..07fd628205 --- /dev/null +++ b/src/generated/resources/assets/create/models/item/clipboard.json @@ -0,0 +1,26 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "create:item/clipboard" + }, + "overrides": [ + { + "predicate": { + "create:clipboard_type": 0.0 + }, + "model": "create:item/clipboard_0" + }, + { + "predicate": { + "create:clipboard_type": 1.0 + }, + "model": "create:item/clipboard_1" + }, + { + "predicate": { + "create:clipboard_type": 2.0 + }, + "model": "create:item/clipboard_2" + } + ] +} \ No newline at end of file diff --git a/src/generated/resources/assets/create/models/item/clipboard_0.json b/src/generated/resources/assets/create/models/item/clipboard_0.json new file mode 100644 index 0000000000..46f7226fa4 --- /dev/null +++ b/src/generated/resources/assets/create/models/item/clipboard_0.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "create:item/empty_clipboard" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/create/models/item/clipboard_1.json b/src/generated/resources/assets/create/models/item/clipboard_1.json new file mode 100644 index 0000000000..ee59b74f7b --- /dev/null +++ b/src/generated/resources/assets/create/models/item/clipboard_1.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "create:item/clipboard" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/create/models/item/clipboard_2.json b/src/generated/resources/assets/create/models/item/clipboard_2.json new file mode 100644 index 0000000000..a51972bbcc --- /dev/null +++ b/src/generated/resources/assets/create/models/item/clipboard_2.json @@ -0,0 +1,6 @@ +{ + "parent": "minecraft:item/generated", + "textures": { + "layer0": "create:item/clipboard_and_quill" + } +} \ No newline at end of file diff --git a/src/main/java/com/simibubi/create/AllItems.java b/src/main/java/com/simibubi/create/AllItems.java index 3b92b62739..66717e1d02 100644 --- a/src/main/java/com/simibubi/create/AllItems.java +++ b/src/main/java/com/simibubi/create/AllItems.java @@ -39,6 +39,8 @@ import com.simibubi.create.content.curiosities.armor.BacktankItem; import com.simibubi.create.content.curiosities.armor.BacktankItem.BacktankBlockItem; import com.simibubi.create.content.curiosities.armor.DivingBootsItem; import com.simibubi.create.content.curiosities.armor.DivingHelmetItem; +import com.simibubi.create.content.curiosities.clipboard.ClipboardItem; +import com.simibubi.create.content.curiosities.clipboard.ClipboardOverrides; import com.simibubi.create.content.curiosities.symmetry.SymmetryWandItem; import com.simibubi.create.content.curiosities.tools.BlueprintItem; import com.simibubi.create.content.curiosities.tools.ExtendoGripItem; @@ -72,7 +74,10 @@ public class AllItems { REGISTRATE.creativeModeTab(() -> AllCreativeModeTabs.BASE_CREATIVE_TAB); } - // Materials + public static final ItemEntry CLIPBOARD = REGISTRATE.item("clipboard", ClipboardItem::new) + .onRegister(ClipboardItem::registerModelOverrides) + .model((c, p) -> ClipboardOverrides.addOverrideModels(c, p)) + .register(); public static final ItemEntry WHEAT_FLOUR = taggedIngredient("wheat_flour", forgeItemTag("flour/wheat"), forgeItemTag("flour")), diff --git a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardEditPacket.java b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardEditPacket.java new file mode 100644 index 0000000000..713201792d --- /dev/null +++ b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardEditPacket.java @@ -0,0 +1,44 @@ +package com.simibubi.create.content.curiosities.clipboard; + +import com.simibubi.create.AllItems; +import com.simibubi.create.foundation.networking.SimplePacketBase; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.network.NetworkEvent.Context; + +public class ClipboardEditPacket extends SimplePacketBase { + + private int hotbarSlot; + private CompoundTag data; + + public ClipboardEditPacket(int hotbarSlot, CompoundTag data) { + this.hotbarSlot = hotbarSlot; + this.data = data; + } + + public ClipboardEditPacket(FriendlyByteBuf buffer) { + hotbarSlot = buffer.readVarInt(); + data = buffer.readNbt(); + } + + @Override + public void write(FriendlyByteBuf buffer) { + buffer.writeVarInt(hotbarSlot); + buffer.writeNbt(data); + } + + @Override + public boolean handle(Context context) { + ServerPlayer sender = context.getSender(); + ItemStack itemStack = sender.getInventory() + .getItem(hotbarSlot); + if (!AllItems.CLIPBOARD.isIn(itemStack)) + return true; + itemStack.setTag(data.isEmpty() ? null : data); + return true; + } + +} diff --git a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardEntry.java b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardEntry.java new file mode 100644 index 0000000000..570546412a --- /dev/null +++ b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardEntry.java @@ -0,0 +1,52 @@ +package com.simibubi.create.content.curiosities.clipboard; + +import java.util.ArrayList; +import java.util.List; + +import com.simibubi.create.foundation.utility.NBTHelper; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.item.ItemStack; + +public class ClipboardEntry { + + boolean checked; + MutableComponent text; + + public ClipboardEntry(boolean checked, MutableComponent text) { + this.checked = checked; + this.text = text; + } + + public static List> readAll(ItemStack clipboardItem) { + CompoundTag tag = clipboardItem.getTag(); + if (tag == null) + return new ArrayList<>(); + return NBTHelper.readCompoundList(tag.getList("Pages", Tag.TAG_COMPOUND), pageTag -> NBTHelper + .readCompoundList(pageTag.getList("Entries", Tag.TAG_COMPOUND), ClipboardEntry::readNBT)); + } + + public static void saveAll(List> entries, ItemStack clipboardItem) { + CompoundTag tag = clipboardItem.getOrCreateTag(); + tag.put("Pages", NBTHelper.writeCompoundList(entries, list -> { + CompoundTag pageTag = new CompoundTag(); + pageTag.put("Entries", NBTHelper.writeCompoundList(list, ClipboardEntry::writeNBT)); + return pageTag; + })); + } + + public CompoundTag writeNBT() { + CompoundTag nbt = new CompoundTag(); + nbt.putBoolean("Checked", checked); + nbt.putString("Text", Component.Serializer.toJson(text)); + return nbt; + } + + public static ClipboardEntry readNBT(CompoundTag tag) { + return new ClipboardEntry(tag.getBoolean("Checked"), Component.Serializer.fromJson(tag.getString("Text"))); + } + +} diff --git a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardItem.java b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardItem.java new file mode 100644 index 0000000000..478fa1f4ed --- /dev/null +++ b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardItem.java @@ -0,0 +1,62 @@ +package com.simibubi.create.content.curiosities.clipboard; + +import javax.annotation.Nonnull; + +import com.simibubi.create.foundation.gui.ScreenOpener; + +import net.minecraft.client.Minecraft; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.InteractionResultHolder; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.level.Level; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; + +public class ClipboardItem extends Item { + + public ClipboardItem(Properties pProperties) { + super(pProperties); + } + + @Nonnull + @Override + public InteractionResult useOn(UseOnContext context) { + if (context.getPlayer() == null) + return InteractionResult.PASS; + return use(context.getLevel(), context.getPlayer(), context.getHand()).getResult(); + } + + @Override + public InteractionResultHolder use(Level world, Player player, InteractionHand hand) { + ItemStack heldItem = player.getItemInHand(hand); + if (hand == InteractionHand.OFF_HAND) + return InteractionResultHolder.pass(heldItem); + + player.getCooldowns() + .addCooldown(heldItem.getItem(), 10); + if (world.isClientSide) + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> openScreen(player, heldItem)); + CompoundTag tag = heldItem.getOrCreateTag(); + tag.putInt("Type", ClipboardOverrides.ClipboardType.EDITING.ordinal()); + heldItem.setTag(tag); + + return InteractionResultHolder.success(heldItem); + } + + @OnlyIn(Dist.CLIENT) + private void openScreen(Player player, ItemStack stack) { + if (Minecraft.getInstance().player == player) + ScreenOpener.open(new ClipboardScreen(player.getInventory().selected, stack)); + } + + public void registerModelOverrides() { + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> ClipboardOverrides.registerModelOverridesClient(this)); + } + +} diff --git a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardOverrides.java b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardOverrides.java new file mode 100644 index 0000000000..2845b9d9fe --- /dev/null +++ b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardOverrides.java @@ -0,0 +1,57 @@ +package com.simibubi.create.content.curiosities.clipboard; + +import com.simibubi.create.Create; +import com.tterrag.registrate.providers.DataGenContext; +import com.tterrag.registrate.providers.RegistrateItemModelProvider; + +import net.minecraft.client.renderer.item.ItemProperties; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.client.model.generators.ItemModelBuilder; +import net.minecraftforge.client.model.generators.ModelFile.UncheckedModelFile; + +public class ClipboardOverrides { + + public enum ClipboardType { + EMPTY("empty_clipboard"), WRITTEN("clipboard"), EDITING("clipboard_and_quill"); + + public String file; + public static ResourceLocation ID = Create.asResource("clipboard_type"); + + private ClipboardType(String file) { + this.file = file; + } + } + + public static void switchTo(ClipboardType type, ItemStack clipboardItem) { + CompoundTag tag = clipboardItem.getOrCreateTag(); + tag.putInt("Type", type.ordinal()); + } + + @OnlyIn(Dist.CLIENT) + public static void registerModelOverridesClient(ClipboardItem item) { + ItemProperties.register(item, ClipboardType.ID, (pStack, pLevel, pEntity, pSeed) -> { + CompoundTag tag = pStack.getTag(); + return tag == null ? 0 : tag.getInt("Type"); + }); + } + + public static ItemModelBuilder addOverrideModels(DataGenContext c, + RegistrateItemModelProvider p) { + ItemModelBuilder builder = p.generated(() -> c.get()); + for (int i = 0; i < ClipboardType.values().length; i++) { + builder.override() + .predicate(ClipboardType.ID, i) + .model(p.getBuilder(c.getName() + "_" + i) + .parent(new UncheckedModelFile("item/generated")) + .texture("layer0", Create.asResource("item/" + ClipboardType.values()[i].file))) + .end(); + } + return builder; + } + +} diff --git a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreen.java b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreen.java new file mode 100644 index 0000000000..f9cf685813 --- /dev/null +++ b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreen.java @@ -0,0 +1,775 @@ +package com.simibubi.create.content.curiosities.clipboard; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.mutable.MutableBoolean; +import org.apache.commons.lang3.mutable.MutableInt; + +import com.google.common.collect.Lists; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.simibubi.create.content.curiosities.clipboard.ClipboardOverrides.ClipboardType; +import com.simibubi.create.foundation.gui.AbstractSimiScreen; +import com.simibubi.create.foundation.gui.AllGuiTextures; +import com.simibubi.create.foundation.gui.AllIcons; +import com.simibubi.create.foundation.gui.widget.IconButton; +import com.simibubi.create.foundation.networking.AllPackets; +import com.simibubi.create.foundation.utility.Components; +import com.simibubi.create.foundation.utility.Lang; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.SharedConstants; +import net.minecraft.Util; +import net.minecraft.client.StringSplitter; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.gui.font.TextFieldHelper; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.PageButton; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.Rect2i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; +import net.minecraft.network.chat.TextComponent; +import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Mth; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +public class ClipboardScreen extends AbstractSimiScreen { + + private ItemStack item; + + List> pages; + List currentEntries; + int editingIndex; + int frameTick; + PageButton forward; + PageButton backward; + int currentPage; + long lastClickTime; + int lastIndex = -1; + + int hoveredEntry; + boolean hoveredCheck; + + DisplayCache displayCache = DisplayCache.EMPTY; + TextFieldHelper editContext; + + IconButton closeBtn; + IconButton clearBtn; + + private int targetSlot; + + public ClipboardScreen(int targetSlot, ItemStack item) { + this.targetSlot = targetSlot; + this.item = item; + pages = ClipboardEntry.readAll(item); + if (pages.isEmpty()) + pages.add(new ArrayList<>()); + currentPage = item.getTag() == null ? 0 + : item.getTag() + .getInt("PreviouslyOpenedPage"); + currentPage = Mth.clamp(currentPage, 0, pages.size() - 1); + currentEntries = pages.get(currentPage); + boolean startEmpty = currentEntries.isEmpty(); + if (startEmpty) + currentEntries.add(new ClipboardEntry(false, Components.empty())); + editingIndex = 0; + editContext = new TextFieldHelper(this::getCurrentEntryText, this::setCurrentEntryText, this::getClipboard, + this::setClipboard, this::validateTextForEntry); + editingIndex = startEmpty ? 0 : -1; + } + + @Override + protected void init() { + setWindowSize(256, 256); + super.init(); + minecraft.keyboardHandler.setSendRepeatsToGui(true); + clearDisplayCache(); + + int x = guiLeft; + int y = guiTop - 8; + + clearWidgets(); + clearBtn = new IconButton(x + 234, y + 153, AllIcons.I_CLEAR_CHECKED).withCallback(() -> { + editingIndex = -1; + currentEntries.removeIf(ce -> ce.checked); + if (currentEntries.isEmpty()) + currentEntries.add(new ClipboardEntry(false, Components.empty())); + }); + clearBtn.setToolTip(Lang.translateDirect("gui.clipboard.erase_checked")); + closeBtn = new IconButton(x + 234, y + 175, AllIcons.I_PRIORITY_VERY_LOW) + .withCallback(() -> minecraft.setScreen(null)); + closeBtn.setToolTip(Lang.translateDirect("station.close")); + addRenderableWidget(closeBtn); + addRenderableWidget(clearBtn); + + forward = new PageButton(x + 176, y + 229, true, $ -> changePage(true), true); + backward = new PageButton(x + 53, y + 229, false, $ -> changePage(false), true); + addRenderableWidget(forward); + addRenderableWidget(backward); + + forward.visible = currentPage < 50; + backward.visible = currentPage > 0; + } + + private int getNumPages() { + return pages.size(); + } + + public void tick() { + super.tick(); + frameTick++; + + int mx = (int) (this.minecraft.mouseHandler.xpos() * (double) this.minecraft.getWindow() + .getGuiScaledWidth() / (double) this.minecraft.getWindow() + .getScreenWidth()); + int my = (int) (this.minecraft.mouseHandler.ypos() * (double) this.minecraft.getWindow() + .getGuiScaledHeight() / (double) this.minecraft.getWindow() + .getScreenHeight()); + + mx -= guiLeft + 35; + my -= guiTop + 41; + + hoveredCheck = false; + hoveredEntry = -1; + + if (mx > 0 && mx < 183 && my > 0 && my < 190) { + hoveredCheck = mx < 20; + int totalHeight = 0; + for (int i = 0; i < currentEntries.size(); i++) { + ClipboardEntry clipboardEntry = currentEntries.get(i); + String text = clipboardEntry.text.getString(); + totalHeight += Math.max(12, font.split(Components.literal(text), 150) + .size() * 9 + 3); + + if (totalHeight > my) { + hoveredEntry = i; + return; + } + } + hoveredEntry = currentEntries.size(); + } + } + + private String getCurrentEntryText() { + return currentEntries.get(editingIndex).text.getString(); + } + + private void setCurrentEntryText(String text) { + currentEntries.get(editingIndex).text = Components.literal(text); + } + + private void setClipboard(String p_98148_) { + if (minecraft != null) + TextFieldHelper.setClipboardContents(minecraft, p_98148_); + } + + private String getClipboard() { + return minecraft != null ? TextFieldHelper.getClipboardContents(minecraft) : ""; + } + + private boolean validateTextForEntry(String newText) { + int totalHeight = 0; + for (int i = 0; i < currentEntries.size(); i++) { + ClipboardEntry clipboardEntry = currentEntries.get(i); + String text = i == editingIndex ? newText : clipboardEntry.text.getString(); + totalHeight += Math.max(12, font.split(Components.literal(text), 150) + .size() * 9 + 3); + } + return totalHeight < 185; + } + + private int yOffsetOfEditingEntry() { + int totalHeight = 0; + for (int i = 0; i < currentEntries.size(); i++) { + if (i == editingIndex) + break; + ClipboardEntry clipboardEntry = currentEntries.get(i); + totalHeight += Math.max(12, font.split(clipboardEntry.text, 150) + .size() * 9 + 3); + } + return totalHeight; + } + + private void changePage(boolean next) { + int previously = currentPage; + currentPage = Mth.clamp(currentPage + (next ? 1 : -1), 0, 50); + if (currentPage == previously) + return; + editingIndex = -1; + if (pages.size() <= currentPage) + pages.add(new ArrayList<>()); + currentEntries = pages.get(currentPage); + if (currentEntries.isEmpty()) { + currentEntries.add(new ClipboardEntry(false, Components.empty())); + editingIndex = 0; + editContext.setCursorToEnd(); + clearDisplayCacheAfterChange(); + } + + forward.visible = currentPage < 50; + backward.visible = currentPage > 0; + + if (next) + return; + if (pages.get(currentPage + 1) + .stream() + .allMatch(ce -> ce.text.getString() + .isBlank())) + pages.remove(currentPage + 1); + } + + @Override + protected void renderWindow(PoseStack ms, int mouseX, int mouseY, float partialTicks) { + int x = guiLeft; + int y = guiTop - 8; + + AllGuiTextures.CLIPBOARD.render(ms, x, y); + font.draw(ms, new TranslatableComponent("book.pageIndicator", currentPage + 1, getNumPages()), x + 150, y + 9, + 0x43ffffff); + + for (int i = 0; i < currentEntries.size(); i++) { + ClipboardEntry clipboardEntry = currentEntries.get(i); + boolean checked = clipboardEntry.checked; + + font.draw(ms, "\u25A1", x + 45, y + 51, checked ? 0x668D7F6B : 0xff8D7F6B); + if (checked) + font.draw(ms, "\u2714", x + 45, y + 50, 0x31B25D); + + List split = font.split(clipboardEntry.text, 150); + if (split.isEmpty()) { + y += 12; + continue; + } + + for (FormattedCharSequence sequence : split) { + if (i != editingIndex) + font.draw(ms, sequence, x + 58, y + 50, checked ? 0x31B25D : 0x311A00); + y += 9; + } + y += 3; + } + + if (editingIndex == -1) + return; + + boolean checked = currentEntries.get(editingIndex).checked; + + setFocused(null); + DisplayCache cache = getDisplayCache(); + + for (LineInfo line : cache.lines) + font.draw(ms, line.asComponent, line.x, line.y, checked ? 0x31B25D : 0x311A00); + + renderHighlight(cache.selection); + renderCursor(ms, cache.cursor, cache.cursorAtEnd); + } + + @Override + public void removed() { + minecraft.keyboardHandler.setSendRepeatsToGui(false); + pages.forEach(list -> list.removeIf(ce -> ce.text.getString() + .isBlank())); + pages.removeIf(List::isEmpty); + + ClipboardEntry.saveAll(pages, item); + ClipboardOverrides.switchTo(ClipboardType.WRITTEN, item); + + for (int i = 0; i < pages.size(); i++) + if (pages.get(i) == currentEntries) + item.getTag() + .putInt("PreviouslyOpenedPage", i); + + if (pages.isEmpty()) + item.setTag(new CompoundTag()); + AllPackets.getChannel() + .sendToServer(new ClipboardEditPacket(targetSlot, item.getOrCreateTag())); + super.removed(); + } + + @Override + public boolean isPauseScreen() { + return false; + } + + @Override + public boolean keyPressed(int pKeyCode, int pScanCode, int pModifiers) { + if (pKeyCode == 266) { + backward.onPress(); + return true; + } + if (pKeyCode == 267) { + forward.onPress(); + return true; + } + if (editingIndex != -1 && pKeyCode != 256) { + keyPressedWhileEditing(pKeyCode, pScanCode, pModifiers); + clearDisplayCache(); + return true; + } + if (super.keyPressed(pKeyCode, pScanCode, pModifiers)) + return true; + return true; + } + + @Override + public boolean charTyped(char pCodePoint, int pModifiers) { + if (super.charTyped(pCodePoint, pModifiers)) + return true; + if (!SharedConstants.isAllowedChatCharacter(pCodePoint)) + return false; + if (editingIndex == -1) + return false; + editContext.insertText(Character.toString(pCodePoint)); + clearDisplayCache(); + return true; + } + + private boolean keyPressedWhileEditing(int pKeyCode, int pScanCode, int pModifiers) { + if (Screen.isSelectAll(pKeyCode)) { + editContext.selectAll(); + return true; + } else if (Screen.isCopy(pKeyCode)) { + editContext.copy(); + return true; + } else if (Screen.isPaste(pKeyCode)) { + editContext.paste(); + return true; + } else if (Screen.isCut(pKeyCode)) { + editContext.cut(); + return true; + } else { + switch (pKeyCode) { + case 257: + case 335: + if (hasShiftDown()) { + editContext.insertText("\n"); + return true; + } else if (!hasControlDown()) { + if (currentEntries.size() <= editingIndex + 1 + || !currentEntries.get(editingIndex + 1).text.getString() + .isEmpty()) + currentEntries.add(editingIndex + 1, new ClipboardEntry(false, Components.empty())); + editingIndex += 1; + editContext.setCursorToEnd(); + if (validateTextForEntry(" ")) + return true; + currentEntries.remove(editingIndex); + editingIndex -= 1; + editContext.setCursorToEnd(); + return true; + } + editingIndex = -1; + return true; + case 259: + if (currentEntries.get(editingIndex).text.getString() + .isEmpty() && currentEntries.size() > 1) { + currentEntries.remove(editingIndex); + editingIndex -= 1; + editContext.setCursorToEnd(); + return true; + } else if (hasControlDown()) { + int prevPos = editContext.getCursorPos(); + editContext.moveByWords(-1); + if (prevPos != editContext.getCursorPos()) + editContext.removeCharsFromCursor(prevPos - editContext.getCursorPos()); + return true; + } + editContext.removeCharsFromCursor(-1); + return true; + case 261: + if (hasControlDown()) { + int prevPos = editContext.getCursorPos(); + editContext.moveByWords(1); + if (prevPos != editContext.getCursorPos()) + editContext.removeCharsFromCursor(prevPos - editContext.getCursorPos()); + return true; + } + editContext.removeCharsFromCursor(1); + return true; + case 262: + if (hasControlDown()) { + editContext.moveByWords(1, Screen.hasShiftDown()); + return true; + } + editContext.moveByChars(1, Screen.hasShiftDown()); + return true; + case 263: + if (hasControlDown()) { + editContext.moveByWords(-1, Screen.hasShiftDown()); + return true; + } + editContext.moveByChars(-1, Screen.hasShiftDown()); + return true; + case 264: + keyDown(); + return true; + case 265: + keyUp(); + return true; + case 268: + keyHome(); + return true; + case 269: + keyEnd(); + return true; + default: + return false; + } + } + } + + private void keyUp() { + changeLine(-1); + } + + private void keyDown() { + changeLine(1); + } + + private void changeLine(int pYChange) { + int i = editContext.getCursorPos(); + int j = getDisplayCache().changeLine(i, pYChange); + editContext.setCursorPos(j, Screen.hasShiftDown()); + } + + private void keyHome() { + int i = editContext.getCursorPos(); + int j = getDisplayCache().findLineStart(i); + editContext.setCursorPos(j, Screen.hasShiftDown()); + } + + private void keyEnd() { + DisplayCache cache = getDisplayCache(); + int i = editContext.getCursorPos(); + int j = cache.findLineEnd(i); + editContext.setCursorPos(j, Screen.hasShiftDown()); + } + + private void renderCursor(PoseStack pPoseStack, Pos2i pCursorPos, boolean pIsEndOfText) { + if (frameTick / 6 % 2 != 0) + return; + pCursorPos = convertLocalToScreen(pCursorPos); + if (!pIsEndOfText) { + GuiComponent.fill(pPoseStack, pCursorPos.x, pCursorPos.y - 1, pCursorPos.x + 1, pCursorPos.y + 9, + -16777216); + } else { + font.draw(pPoseStack, "_", (float) pCursorPos.x, (float) pCursorPos.y, 0); + } + } + + private void renderHighlight(Rect2i[] pSelected) { + Tesselator tesselator = Tesselator.getInstance(); + BufferBuilder bufferbuilder = tesselator.getBuilder(); + RenderSystem.setShader(GameRenderer::getPositionShader); + RenderSystem.setShaderColor(0.0F, 0.0F, 255.0F, 255.0F); + RenderSystem.disableTexture(); + RenderSystem.enableColorLogicOp(); + RenderSystem.logicOp(GlStateManager.LogicOp.OR_REVERSE); + bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION); + + for (Rect2i rect2i : pSelected) { + int i = rect2i.getX(); + int j = rect2i.getY(); + int k = i + rect2i.getWidth(); + int l = j + rect2i.getHeight(); + bufferbuilder.vertex((double) i, (double) l, 0.0D) + .endVertex(); + bufferbuilder.vertex((double) k, (double) l, 0.0D) + .endVertex(); + bufferbuilder.vertex((double) k, (double) j, 0.0D) + .endVertex(); + bufferbuilder.vertex((double) i, (double) j, 0.0D) + .endVertex(); + } + + tesselator.end(); + RenderSystem.disableColorLogicOp(); + RenderSystem.enableTexture(); + } + + private Pos2i convertScreenToLocal(Pos2i pScreenPos) { + return new Pos2i(pScreenPos.x - (width - 192) / 2 - 36 + 10, pScreenPos.y - 32 - 24 - yOffsetOfEditingEntry()); + } + + private Pos2i convertLocalToScreen(Pos2i pLocalScreenPos) { + return new Pos2i(pLocalScreenPos.x + (width - 192) / 2 + 36 - 10, + pLocalScreenPos.y + 32 + 24 + yOffsetOfEditingEntry()); + } + + public boolean mouseClicked(double pMouseX, double pMouseY, int pButton) { + if (super.mouseClicked(pMouseX, pMouseY, pButton)) + return true; + if (pButton != 0) + return true; + + if (hoveredEntry != -1) { + if (hoveredCheck) { + editingIndex = -1; + if (hoveredEntry < currentEntries.size()) + currentEntries.get(hoveredEntry).checked ^= true; + return true; + } + + if (hoveredEntry != editingIndex) { + editingIndex = hoveredEntry; + if (hoveredEntry >= currentEntries.size()) { + currentEntries.add(new ClipboardEntry(false, Components.empty())); + if (!validateTextForEntry(" ")) { + currentEntries.remove(hoveredEntry); + editingIndex = -1; + return true; + } + } + clearDisplayCacheAfterChange(); + } + } + + if (editingIndex == -1) + return false; + + long i = Util.getMillis(); + DisplayCache cache = getDisplayCache(); + int j = cache.getIndexAtPosition(font, convertScreenToLocal(new Pos2i((int) pMouseX, (int) pMouseY))); + if (j >= 0) { + if (j == lastIndex && i - lastClickTime < 250L) { + if (!editContext.isSelecting()) { + selectWord(j); + } else { + editContext.selectAll(); + } + } else { + editContext.setCursorPos(j, Screen.hasShiftDown()); + } + + clearDisplayCache(); + } + + lastIndex = j; + lastClickTime = i; + return true; + } + + private void selectWord(int pIndex) { + String s = getCurrentEntryText(); + editContext.setSelectionRange(StringSplitter.getWordPosition(s, -1, pIndex, false), + StringSplitter.getWordPosition(s, 1, pIndex, false)); + } + + public boolean mouseDragged(double pMouseX, double pMouseY, int pButton, double pDragX, double pDragY) { + if (super.mouseDragged(pMouseX, pMouseY, pButton, pDragX, pDragY)) + return true; + if (pButton != 0) + return true; + if (editingIndex == -1) + return false; + + DisplayCache cache = getDisplayCache(); + int i = cache.getIndexAtPosition(font, convertScreenToLocal(new Pos2i((int) pMouseX, (int) pMouseY))); + editContext.setCursorPos(i, true); + clearDisplayCache(); + return true; + } + + private DisplayCache getDisplayCache() { + if (displayCache == null) + displayCache = rebuildDisplayCache(); + return displayCache; + } + + private void clearDisplayCache() { + displayCache = null; + } + + private void clearDisplayCacheAfterChange() { + editContext.setCursorToEnd(); + clearDisplayCache(); + } + + private DisplayCache rebuildDisplayCache() { + String s = getCurrentEntryText(); + if (s.isEmpty()) + return DisplayCache.EMPTY; + + int i = editContext.getCursorPos(); + int j = editContext.getSelectionPos(); + IntList intlist = new IntArrayList(); + List list = Lists.newArrayList(); + MutableInt mutableint = new MutableInt(); + MutableBoolean mutableboolean = new MutableBoolean(); + StringSplitter stringsplitter = font.getSplitter(); + stringsplitter.splitLines(s, 150, Style.EMPTY, true, (p_98132_, p_98133_, p_98134_) -> { + int k3 = mutableint.getAndIncrement(); + String s2 = s.substring(p_98133_, p_98134_); + mutableboolean.setValue(s2.endsWith("\n")); + String s3 = StringUtils.stripEnd(s2, " \n"); + int l3 = k3 * 9; + Pos2i pos1 = convertLocalToScreen(new Pos2i(0, l3)); + intlist.add(p_98133_); + list.add(new LineInfo(p_98132_, s3, pos1.x, pos1.y)); + }); + + int[] aint = intlist.toIntArray(); + boolean flag = i == s.length(); + Pos2i pos; + if (flag && mutableboolean.isTrue()) { + pos = new Pos2i(0, list.size() * 9); + } else { + int k = findLineFromPos(aint, i); + int l = font.width(s.substring(aint[k], i)); + pos = new Pos2i(l, k * 9); + } + + List list1 = Lists.newArrayList(); + if (i != j) { + int l2 = Math.min(i, j); + int i1 = Math.max(i, j); + int j1 = findLineFromPos(aint, l2); + int k1 = findLineFromPos(aint, i1); + if (j1 == k1) { + int l1 = j1 * 9; + int i2 = aint[j1]; + list1.add(createPartialLineSelection(s, stringsplitter, l2, i1, l1, i2)); + } else { + int i3 = j1 + 1 > aint.length ? s.length() : aint[j1 + 1]; + list1.add(createPartialLineSelection(s, stringsplitter, l2, i3, j1 * 9, aint[j1])); + + for (int j3 = j1 + 1; j3 < k1; ++j3) { + int j2 = j3 * 9; + String s1 = s.substring(aint[j3], aint[j3 + 1]); + int k2 = (int) stringsplitter.stringWidth(s1); + list1.add(createSelection(new Pos2i(0, j2), new Pos2i(k2, j2 + 9))); + } + + list1.add(createPartialLineSelection(s, stringsplitter, aint[k1], i1, k1 * 9, aint[k1])); + } + } + + return new DisplayCache(s, pos, flag, aint, list.toArray(new LineInfo[0]), list1.toArray(new Rect2i[0])); + } + + static int findLineFromPos(int[] pLineStarts, int pFind) { + int i = Arrays.binarySearch(pLineStarts, pFind); + return i < 0 ? -(i + 2) : i; + } + + private Rect2i createPartialLineSelection(String pInput, StringSplitter pSplitter, int p_98122_, int p_98123_, + int p_98124_, int p_98125_) { + String s = pInput.substring(p_98125_, p_98122_); + String s1 = pInput.substring(p_98125_, p_98123_); + Pos2i firstPos = new Pos2i((int) pSplitter.stringWidth(s), p_98124_); + Pos2i secondPos = new Pos2i((int) pSplitter.stringWidth(s1), p_98124_ + 9); + return createSelection(firstPos, secondPos); + } + + private Rect2i createSelection(Pos2i pCorner1, Pos2i pCorner2) { + Pos2i firstPos = convertLocalToScreen(pCorner1); + Pos2i secondPos = convertLocalToScreen(pCorner2); + int i = Math.min(firstPos.x, secondPos.x); + int j = Math.max(firstPos.x, secondPos.x); + int k = Math.min(firstPos.y, secondPos.y); + int l = Math.max(firstPos.y, secondPos.y); + return new Rect2i(i, k, j - i, l - k); + } + + @OnlyIn(Dist.CLIENT) + static class DisplayCache { + static final DisplayCache EMPTY = new DisplayCache("", new Pos2i(0, 0), true, new int[] { 0 }, + new LineInfo[] { new LineInfo(Style.EMPTY, "", 0, 0) }, new Rect2i[0]); + private final String fullText; + final Pos2i cursor; + final boolean cursorAtEnd; + private final int[] lineStarts; + final LineInfo[] lines; + final Rect2i[] selection; + + public DisplayCache(String pFullText, Pos2i pCursor, boolean pCursorAtEnd, int[] pLineStarts, LineInfo[] pLines, + Rect2i[] pSelection) { + fullText = pFullText; + cursor = pCursor; + cursorAtEnd = pCursorAtEnd; + lineStarts = pLineStarts; + lines = pLines; + selection = pSelection; + } + + public int getIndexAtPosition(Font pFont, Pos2i pCursorPosition) { + int i = pCursorPosition.y / 9; + if (i < 0) + return 0; + if (i >= lines.length) + return fullText.length(); + LineInfo line = lines[i]; + return lineStarts[i] + pFont.getSplitter() + .plainIndexAtWidth(line.contents, pCursorPosition.x, line.style); + } + + public int changeLine(int pXChange, int pYChange) { + int i = findLineFromPos(lineStarts, pXChange); + int j = i + pYChange; + int k; + if (0 <= j && j < lineStarts.length) { + int l = pXChange - lineStarts[i]; + int i1 = lines[j].contents.length(); + k = lineStarts[j] + Math.min(l, i1); + } else { + k = pXChange; + } + + return k; + } + + public int findLineStart(int pLine) { + int i = findLineFromPos(lineStarts, pLine); + return lineStarts[i]; + } + + public int findLineEnd(int pLine) { + int i = findLineFromPos(lineStarts, pLine); + return lineStarts[i] + lines[i].contents.length(); + } + } + + @OnlyIn(Dist.CLIENT) + static class LineInfo { + final Style style; + final String contents; + final Component asComponent; + final int x; + final int y; + + public LineInfo(Style pStyle, String pContents, int pX, int pY) { + style = pStyle; + contents = pContents; + x = pX; + y = pY; + asComponent = (new TextComponent(pContents)).setStyle(pStyle); + } + } + + @OnlyIn(Dist.CLIENT) + static class Pos2i { + public final int x; + public final int y; + + Pos2i(int pX, int pY) { + x = pX; + y = pY; + } + } + +} diff --git a/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreenHelper.java b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreenHelper.java new file mode 100644 index 0000000000..0e53a7075d --- /dev/null +++ b/src/main/java/com/simibubi/create/content/curiosities/clipboard/ClipboardScreenHelper.java @@ -0,0 +1,11 @@ +package com.simibubi.create.content.curiosities.clipboard; + +import net.minecraft.SharedConstants; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.BookEditScreen; + +public class ClipboardScreenHelper { + + + +} diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java index bcb832da5e..52797d1da8 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllGuiTextures.java @@ -76,6 +76,8 @@ public enum AllGuiTextures implements ScreenElement { LINKED_CONTROLLER("curiosities_2", 179, 109), BLUEPRINT("curiosities_2", 0, 109, 179, 109), + + CLIPBOARD("clipboard", 0, 0, 256, 256), PROJECTOR("projector", 235, 185), PROJECTOR_FILTER_STRENGTH("projector", 0, 14, 162, 22), diff --git a/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java b/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java index b0bb6a60e1..d52de789a7 100644 --- a/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java +++ b/src/main/java/com/simibubi/create/foundation/gui/AllIcons.java @@ -127,6 +127,8 @@ public class AllIcons implements ScreenElement { I_PATTERN_CHANCE_75 = next(), I_FOLLOW_DIAGONAL = next(), I_FOLLOW_MATERIAL = next(), + + I_CLEAR_CHECKED = next(), I_SCHEMATIC = newRow(), I_SEQ_REPEAT = next(), diff --git a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java index 4ba7f21f4a..6220cffa9c 100644 --- a/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java +++ b/src/main/java/com/simibubi/create/foundation/networking/AllPackets.java @@ -37,6 +37,7 @@ import com.simibubi.create.content.contraptions.relays.advanced.sequencer.Config import com.simibubi.create.content.contraptions.relays.gauge.GaugeObservedPacket; import com.simibubi.create.content.curiosities.armor.NetheriteDivingHandler; import com.simibubi.create.content.curiosities.bell.SoulPulseEffectPacket; +import com.simibubi.create.content.curiosities.clipboard.ClipboardEditPacket; import com.simibubi.create.content.curiosities.symmetry.ConfigureSymmetryWandPacket; import com.simibubi.create.content.curiosities.symmetry.SymmetryEffectPacket; import com.simibubi.create.content.curiosities.toolbox.ToolboxDisposeAllPacket; @@ -157,6 +158,7 @@ public enum AllPackets { REQUEST_FLOOR_LIST(ElevatorFloorListPacket.RequestFloorList.class, ElevatorFloorListPacket.RequestFloorList::new, PLAY_TO_SERVER), ELEVATOR_SET_FLOOR(ElevatorTargetFloorPacket.class, ElevatorTargetFloorPacket::new, PLAY_TO_SERVER), + CLIPBOARD_EDIT(ClipboardEditPacket.class, ClipboardEditPacket::new, PLAY_TO_SERVER), // Server to Client SYMMETRY_EFFECT(SymmetryEffectPacket.class, SymmetryEffectPacket::new, PLAY_TO_CLIENT), diff --git a/src/main/resources/assets/create/lang/default/interface.json b/src/main/resources/assets/create/lang/default/interface.json index dd6433bd8b..e3fba6afb6 100644 --- a/src/main/resources/assets/create/lang/default/interface.json +++ b/src/main/resources/assets/create/lang/default/interface.json @@ -272,6 +272,8 @@ "create.gui.sequenced_gearshift.speed.back": "Input speed, Reversed", "create.gui.sequenced_gearshift.speed.back_fast": "Double speed, Reversed", + "create.gui.clipboard.erase_checked": "Erase checked items", + "create.schematicAndQuill.dimensions": "Schematic Size: %1$sx%2$sx%3$s", "create.schematicAndQuill.firstPos": "First position set.", "create.schematicAndQuill.secondPos": "Second position set.", diff --git a/src/main/resources/assets/create/textures/gui/clipboard.pdn b/src/main/resources/assets/create/textures/gui/clipboard.pdn new file mode 100644 index 0000000000..a44cea80b7 Binary files /dev/null and b/src/main/resources/assets/create/textures/gui/clipboard.pdn differ diff --git a/src/main/resources/assets/create/textures/gui/clipboard.png b/src/main/resources/assets/create/textures/gui/clipboard.png new file mode 100644 index 0000000000..53e099083d Binary files /dev/null and b/src/main/resources/assets/create/textures/gui/clipboard.png differ diff --git a/src/main/resources/assets/create/textures/gui/icons.png b/src/main/resources/assets/create/textures/gui/icons.png index ef0384d390..6a7a7d5cc2 100644 Binary files a/src/main/resources/assets/create/textures/gui/icons.png and b/src/main/resources/assets/create/textures/gui/icons.png differ diff --git a/src/main/resources/assets/create/textures/item/clipboard.png b/src/main/resources/assets/create/textures/item/clipboard.png new file mode 100644 index 0000000000..00ff940e12 Binary files /dev/null and b/src/main/resources/assets/create/textures/item/clipboard.png differ diff --git a/src/main/resources/assets/create/textures/item/clipboard_and_quill.png b/src/main/resources/assets/create/textures/item/clipboard_and_quill.png new file mode 100644 index 0000000000..7740e0dabc Binary files /dev/null and b/src/main/resources/assets/create/textures/item/clipboard_and_quill.png differ diff --git a/src/main/resources/assets/create/textures/item/empty_clipboard.png b/src/main/resources/assets/create/textures/item/empty_clipboard.png new file mode 100644 index 0000000000..497a850ef4 Binary files /dev/null and b/src/main/resources/assets/create/textures/item/empty_clipboard.png differ