ClothConfigScreen.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. package me.shedaniel.clothconfig2.gui;
  2. import com.google.common.collect.Lists;
  3. import com.google.common.collect.Maps;
  4. import com.mojang.blaze3d.systems.RenderSystem;
  5. import me.shedaniel.clothconfig2.api.*;
  6. import me.shedaniel.clothconfig2.gui.widget.DynamicElementListWidget;
  7. import me.shedaniel.math.Point;
  8. import me.shedaniel.math.Rectangle;
  9. import net.fabricmc.api.EnvType;
  10. import net.fabricmc.api.Environment;
  11. import net.minecraft.client.MinecraftClient;
  12. import net.minecraft.client.gui.screen.Screen;
  13. import net.minecraft.client.gui.widget.AbstractButtonWidget;
  14. import net.minecraft.client.gui.widget.ButtonWidget;
  15. import net.minecraft.client.render.BufferBuilder;
  16. import net.minecraft.client.render.Tessellator;
  17. import net.minecraft.client.render.VertexFormats;
  18. import net.minecraft.client.resource.language.I18n;
  19. import net.minecraft.client.util.NarratorManager;
  20. import net.minecraft.client.util.math.MatrixStack;
  21. import net.minecraft.text.Text;
  22. import net.minecraft.text.TranslatableText;
  23. import net.minecraft.util.Identifier;
  24. import net.minecraft.util.Pair;
  25. import net.minecraft.util.math.Matrix4f;
  26. import org.jetbrains.annotations.ApiStatus;
  27. import java.util.*;
  28. import java.util.stream.Collectors;
  29. @SuppressWarnings({"deprecation", "rawtypes", "DuplicatedCode"})
  30. @Environment(EnvType.CLIENT)
  31. public class ClothConfigScreen extends AbstractTabbedConfigScreen {
  32. private ScrollingContainer tabsScroller = new ScrollingContainer() {
  33. @Override
  34. public Rectangle getBounds() {
  35. return new Rectangle(0, 0, 1, ClothConfigScreen.this.width - 40); // We don't need to handle dragging
  36. }
  37. @Override
  38. public int getMaxScrollHeight() {
  39. return (int) ClothConfigScreen.this.getTabsMaximumScrolled();
  40. }
  41. @Override
  42. public void updatePosition(float delta) {
  43. super.updatePosition(delta);
  44. scrollAmount = clamp(scrollAmount, 0);
  45. }
  46. };
  47. public ListWidget<AbstractConfigEntry<AbstractConfigEntry<?>>> listWidget;
  48. private final LinkedHashMap<Text, List<AbstractConfigEntry<?>>> categorizedEntries = Maps.newLinkedHashMap();
  49. private final List<Pair<Text, Integer>> tabs;
  50. private AbstractButtonWidget quitButton, saveButton, buttonLeftTab, buttonRightTab;
  51. private Rectangle tabsBounds, tabsLeftBounds, tabsRightBounds;
  52. private double tabsMaximumScrolled = -1d;
  53. private final List<ClothConfigTabButton> tabButtons = Lists.newArrayList();
  54. @ApiStatus.Internal
  55. public ClothConfigScreen(Screen parent, Text title, Map<Text, List<Object>> entriesMap, Identifier backgroundLocation) {
  56. super(parent, title, backgroundLocation);
  57. entriesMap.forEach((categoryName, list) -> {
  58. List<AbstractConfigEntry<?>> entries = Lists.newArrayList();
  59. for (Object object : list) {
  60. AbstractConfigListEntry<?> entry;
  61. if (object instanceof Pair<?, ?>) {
  62. entry = (AbstractConfigListEntry<?>) ((Pair<?, ?>) object).getRight();
  63. } else {
  64. entry = (AbstractConfigListEntry<?>) object;
  65. }
  66. entry.setScreen(this);
  67. entries.add(entry);
  68. }
  69. categorizedEntries.put(categoryName, entries);
  70. });
  71. this.tabs = categorizedEntries.keySet().stream().map(s -> new Pair<>(s, MinecraftClient.getInstance().textRenderer.getWidth(s) + 8)).collect(Collectors.toList());
  72. }
  73. @Override
  74. public Text getSelectedCategory() {
  75. return tabs.get(selectedCategoryIndex).getLeft();
  76. }
  77. @Override
  78. public Map<Text, List<AbstractConfigEntry<?>>> getCategorizedEntries() {
  79. return categorizedEntries;
  80. }
  81. @Override
  82. public void tick() {
  83. super.tick();
  84. boolean edited = isEdited();
  85. quitButton.setMessage(edited ? new TranslatableText("text.cloth-config.cancel_discard") : new TranslatableText("gui.cancel"));
  86. saveButton.active = edited;
  87. }
  88. @Override
  89. public boolean isEdited() {
  90. return super.isEdited();
  91. }
  92. /**
  93. * Override #isEdited please
  94. */
  95. @Deprecated
  96. public void setEdited(boolean edited) {
  97. super.setEdited(edited);
  98. }
  99. /**
  100. * Override #isEdited please
  101. */
  102. @Override
  103. @Deprecated
  104. public void setEdited(boolean edited, boolean requiresRestart) {
  105. super.setEdited(edited, requiresRestart);
  106. }
  107. @Override
  108. public void saveAll(boolean openOtherScreens) {
  109. super.saveAll(openOtherScreens);
  110. }
  111. @Override
  112. protected void init() {
  113. super.init();
  114. this.tabButtons.clear();
  115. children.add(listWidget = new ListWidget(this, client, width, height, isShowingTabs() ? 70 : 30, height - 32, getBackgroundLocation()));
  116. if (categorizedEntries.size() > selectedCategoryIndex) {
  117. listWidget.children().addAll((List) Lists.newArrayList(categorizedEntries.values()).get(selectedCategoryIndex));
  118. }
  119. int buttonWidths = Math.min(200, (width - 50 - 12) / 3);
  120. addButton(quitButton = new ButtonWidget(width / 2 - buttonWidths - 3, height - 26, buttonWidths, 20, isEdited() ? new TranslatableText("text.cloth-config.cancel_discard") : new TranslatableText("gui.cancel"), widget -> quit()));
  121. addButton(saveButton = new ButtonWidget(width / 2 + 3, height - 26, buttonWidths, 20, NarratorManager.EMPTY, button -> saveAll(true)) {
  122. @Override
  123. public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
  124. boolean hasErrors = false;
  125. for (List<AbstractConfigEntry<?>> entries : Lists.newArrayList(categorizedEntries.values())) {
  126. for (AbstractConfigEntry<?> entry : entries)
  127. if (entry.getConfigError().isPresent()) {
  128. hasErrors = true;
  129. break;
  130. }
  131. if (hasErrors)
  132. break;
  133. }
  134. active = isEdited() && !hasErrors;
  135. setMessage(hasErrors ? new TranslatableText("text.cloth-config.error_cannot_save") : new TranslatableText("text.cloth-config.save_and_done"));
  136. super.render(matrices, mouseX, mouseY, delta);
  137. }
  138. });
  139. saveButton.active = isEdited();
  140. if (isShowingTabs()) {
  141. tabsBounds = new Rectangle(0, 41, width, 24);
  142. tabsLeftBounds = new Rectangle(0, 41, 18, 24);
  143. tabsRightBounds = new Rectangle(width - 18, 41, 18, 24);
  144. children.add(buttonLeftTab = new ButtonWidget(4, 44, 12, 18, NarratorManager.EMPTY, button -> tabsScroller.scrollTo(0, true)) {
  145. @Override
  146. public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) {
  147. client.getTextureManager().bindTexture(CONFIG_TEX);
  148. RenderSystem.color4f(1.0F, 1.0F, 1.0F, this.alpha);
  149. int int_3 = this.getYImage(this.isHovered());
  150. RenderSystem.enableBlend();
  151. RenderSystem.blendFuncSeparate(770, 771, 0, 1);
  152. RenderSystem.blendFunc(770, 771);
  153. this.drawTexture(matrices, x, y, 12, 18 * int_3, width, height);
  154. }
  155. });
  156. int j = 0;
  157. for (Pair<Text, Integer> tab : tabs) {
  158. tabButtons.add(new ClothConfigTabButton(this, j, -100, 43, tab.getRight(), 20, tab.getLeft()));
  159. j++;
  160. }
  161. children.addAll(tabButtons);
  162. children.add(buttonRightTab = new ButtonWidget(width - 16, 44, 12, 18, NarratorManager.EMPTY, button -> tabsScroller.scrollTo(tabsScroller.getMaxScroll(), true)) {
  163. @Override
  164. public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) {
  165. client.getTextureManager().bindTexture(CONFIG_TEX);
  166. RenderSystem.color4f(1.0F, 1.0F, 1.0F, this.alpha);
  167. int int_3 = this.getYImage(this.isHovered());
  168. RenderSystem.enableBlend();
  169. RenderSystem.blendFuncSeparate(770, 771, 0, 1);
  170. RenderSystem.blendFunc(770, 771);
  171. this.drawTexture(matrices, x, y, 0, 18 * int_3, width, height);
  172. }
  173. });
  174. } else {
  175. tabsBounds = tabsLeftBounds = tabsRightBounds = new Rectangle();
  176. }
  177. Optional.ofNullable(this.afterInitConsumer).ifPresent(consumer -> consumer.accept(this));
  178. }
  179. @Override
  180. public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
  181. if (tabsBounds.contains(mouseX, mouseY) && !tabsLeftBounds.contains(mouseX, mouseY) && !tabsRightBounds.contains(mouseX, mouseY) && amount != 0d) {
  182. tabsScroller.offset(-amount * 16, true);
  183. return true;
  184. }
  185. return super.mouseScrolled(mouseX, mouseY, amount);
  186. }
  187. public double getTabsMaximumScrolled() {
  188. if (tabsMaximumScrolled == -1d) {
  189. int[] i = {0};
  190. for (Pair<Text, Integer> pair : tabs) i[0] += pair.getRight() + 2;
  191. tabsMaximumScrolled = i[0];
  192. }
  193. return tabsMaximumScrolled + 6;
  194. }
  195. public void resetTabsMaximumScrolled() {
  196. tabsMaximumScrolled = -1d;
  197. }
  198. @Override
  199. public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
  200. if (isShowingTabs()) {
  201. tabsScroller.updatePosition(delta * 3);
  202. int xx = 24 - (int) tabsScroller.scrollAmount;
  203. for (ClothConfigTabButton tabButton : tabButtons) {
  204. tabButton.x = xx;
  205. xx += tabButton.getWidth() + 2;
  206. }
  207. buttonLeftTab.active = tabsScroller.scrollAmount > 0d;
  208. buttonRightTab.active = tabsScroller.scrollAmount < getTabsMaximumScrolled() - width + 40;
  209. }
  210. if (isTransparentBackground()) {
  211. fillGradient(matrices, 0, 0, this.width, this.height, -1072689136, -804253680);
  212. } else {
  213. renderBackgroundTexture(0);
  214. }
  215. listWidget.render(matrices, mouseX, mouseY, delta);
  216. ScissorsHandler.INSTANCE.scissor(new Rectangle(listWidget.left, listWidget.top, listWidget.width, listWidget.bottom - listWidget.top));
  217. for (AbstractConfigEntry child : listWidget.children())
  218. child.lateRender(matrices, mouseX, mouseY, delta);
  219. ScissorsHandler.INSTANCE.removeLastScissor();
  220. if (isShowingTabs()) {
  221. drawCenteredText(matrices, client.textRenderer, title, width / 2, 18, -1);
  222. Rectangle onlyInnerTabBounds = new Rectangle(tabsBounds.x + 20, tabsBounds.y, tabsBounds.width - 40, tabsBounds.height);
  223. ScissorsHandler.INSTANCE.scissor(onlyInnerTabBounds);
  224. if (isTransparentBackground())
  225. fillGradient(matrices, onlyInnerTabBounds.x, onlyInnerTabBounds.y, onlyInnerTabBounds.getMaxX(), onlyInnerTabBounds.getMaxY(), 0x68000000, 0x68000000);
  226. else
  227. overlayBackground(matrices, onlyInnerTabBounds, 32, 32, 32, 255, 255);
  228. tabButtons.forEach(widget -> widget.render(matrices, mouseX, mouseY, delta));
  229. drawTabsShades(matrices, 0, isTransparentBackground() ? 120 : 255);
  230. ScissorsHandler.INSTANCE.removeLastScissor();
  231. buttonLeftTab.render(matrices, mouseX, mouseY, delta);
  232. buttonRightTab.render(matrices, mouseX, mouseY, delta);
  233. } else
  234. drawCenteredText(matrices, client.textRenderer, title, width / 2, 12, -1);
  235. if (isEditable()) {
  236. List<Text> errors = Lists.newArrayList();
  237. for (List<AbstractConfigEntry<?>> entries : Lists.newArrayList(categorizedEntries.values()))
  238. for (AbstractConfigEntry<?> entry : entries)
  239. if (entry.getConfigError().isPresent())
  240. errors.add(entry.getConfigError().get());
  241. if (errors.size() > 0) {
  242. client.getTextureManager().bindTexture(CONFIG_TEX);
  243. RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
  244. String text = "§c" + (errors.size() == 1 ? errors.get(0).copy().getString() : I18n.translate("text.cloth-config.multi_error"));
  245. if (isTransparentBackground()) {
  246. int stringWidth = client.textRenderer.getStringWidth(text);
  247. fillGradient(matrices, 8, 9, 20 + stringWidth, 14 + client.textRenderer.fontHeight, 0x68000000, 0x68000000);
  248. }
  249. drawTexture(matrices, 10, 10, 0, 54, 3, 11);
  250. drawStringWithShadow(matrices, client.textRenderer, text, 18, 12, -1);
  251. if (errors.size() > 1) {
  252. int stringWidth = client.textRenderer.getStringWidth(text);
  253. if (mouseX >= 10 && mouseY >= 10 && mouseX <= 18 + stringWidth && mouseY <= 14 + client.textRenderer.fontHeight)
  254. addTooltip(Tooltip.of(new Point(mouseX, mouseY), errors.toArray(new Text[0])));
  255. }
  256. }
  257. } else if (!isEditable()) {
  258. client.getTextureManager().bindTexture(CONFIG_TEX);
  259. RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
  260. String text = "§c" + I18n.translate("text.cloth-config.not_editable");
  261. if (isTransparentBackground()) {
  262. int stringWidth = client.textRenderer.getStringWidth(text);
  263. fillGradient(matrices, 8, 9, 20 + stringWidth, 14 + client.textRenderer.fontHeight, 0x68000000, 0x68000000);
  264. }
  265. drawTexture(matrices, 10, 10, 0, 54, 3, 11);
  266. drawStringWithShadow(matrices, client.textRenderer, text, 18, 12, -1);
  267. }
  268. super.render(matrices, mouseX, mouseY, delta);
  269. }
  270. @ApiStatus.ScheduledForRemoval
  271. @Deprecated
  272. public void queueTooltip(QueuedTooltip queuedTooltip) {
  273. super.addTooltip(queuedTooltip);
  274. }
  275. private void drawTabsShades(MatrixStack matrices, int lightColor, int darkColor) {
  276. drawTabsShades(matrices.peek().getModel(), lightColor, darkColor);
  277. }
  278. private void drawTabsShades(Matrix4f matrix, int lightColor, int darkColor) {
  279. RenderSystem.enableBlend();
  280. RenderSystem.blendFuncSeparate(770, 771, 0, 1);
  281. RenderSystem.disableAlphaTest();
  282. RenderSystem.shadeModel(7425);
  283. RenderSystem.disableTexture();
  284. Tessellator tessellator = Tessellator.getInstance();
  285. BufferBuilder buffer = tessellator.getBuffer();
  286. buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
  287. buffer.vertex(matrix, tabsBounds.getMinX() + 20, tabsBounds.getMinY() + 4, 0.0F).texture(0, 1f).color(0, 0, 0, lightColor).next();
  288. buffer.vertex(matrix, tabsBounds.getMaxX() - 20, tabsBounds.getMinY() + 4, 0.0F).texture(1f, 1f).color(0, 0, 0, lightColor).next();
  289. buffer.vertex(matrix, tabsBounds.getMaxX() - 20, tabsBounds.getMinY(), 0.0F).texture(1f, 0).color(0, 0, 0, darkColor).next();
  290. buffer.vertex(matrix, tabsBounds.getMinX() + 20, tabsBounds.getMinY(), 0.0F).texture(0, 0).color(0, 0, 0, darkColor).next();
  291. tessellator.draw();
  292. buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
  293. buffer.vertex(matrix, tabsBounds.getMinX() + 20, tabsBounds.getMaxY(), 0.0F).texture(0, 1f).color(0, 0, 0, darkColor).next();
  294. buffer.vertex(matrix, tabsBounds.getMaxX() - 20, tabsBounds.getMaxY(), 0.0F).texture(1f, 1f).color(0, 0, 0, darkColor).next();
  295. buffer.vertex(matrix, tabsBounds.getMaxX() - 20, tabsBounds.getMaxY() - 4, 0.0F).texture(1f, 0).color(0, 0, 0, lightColor).next();
  296. buffer.vertex(matrix, tabsBounds.getMinX() + 20, tabsBounds.getMaxY() - 4, 0.0F).texture(0, 0).color(0, 0, 0, lightColor).next();
  297. tessellator.draw();
  298. RenderSystem.enableTexture();
  299. RenderSystem.shadeModel(7424);
  300. RenderSystem.enableAlphaTest();
  301. RenderSystem.disableBlend();
  302. }
  303. @Override
  304. public void save() {
  305. super.save();
  306. }
  307. @Override
  308. public boolean isEditable() {
  309. return super.isEditable();
  310. }
  311. public static class ListWidget<R extends DynamicElementListWidget.ElementEntry<R>> extends DynamicElementListWidget<R> {
  312. private AbstractConfigScreen screen;
  313. public ListWidget(AbstractConfigScreen screen, MinecraftClient client, int width, int height, int top, int bottom, Identifier backgroundLocation) {
  314. super(client, width, height, top, bottom, backgroundLocation);
  315. setRenderSelection(false);
  316. this.screen = screen;
  317. }
  318. @Override
  319. public int getItemWidth() {
  320. return width - 80;
  321. }
  322. @Override
  323. protected int getScrollbarPosition() {
  324. return left + width - 36;
  325. }
  326. @Override
  327. protected void renderItem(MatrixStack matrices, R item, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
  328. if (item instanceof AbstractConfigEntry)
  329. ((AbstractConfigEntry) item).updateSelected(getFocused() == item);
  330. super.renderItem(matrices, item, index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta);
  331. }
  332. @Override
  333. public boolean mouseClicked(double mouseX, double mouseY, int button) {
  334. this.updateScrollingState(mouseX, mouseY, button);
  335. if (!this.isMouseOver(mouseX, mouseY)) {
  336. return false;
  337. } else {
  338. for (R entry : children()) {
  339. if (entry.mouseClicked(mouseX, mouseY, button)) {
  340. this.setFocused(entry);
  341. this.setDragging(true);
  342. return true;
  343. }
  344. }
  345. if (button == 0) {
  346. this.clickedHeader((int) (mouseX - (double) (this.left + this.width / 2 - this.getItemWidth() / 2)), (int) (mouseY - (double) this.top) + (int) this.getScroll() - 4);
  347. return true;
  348. }
  349. return this.scrolling;
  350. }
  351. }
  352. @Override
  353. protected void renderBackBackground(MatrixStack matrices, BufferBuilder buffer, Tessellator tessellator) {
  354. if (!screen.isTransparentBackground())
  355. super.renderBackBackground(matrices, buffer, tessellator);
  356. else {
  357. fillGradient(matrices, left, top, right, bottom, 0x68000000, 0x68000000);
  358. }
  359. }
  360. @Override
  361. protected void renderHoleBackground(MatrixStack matrices, int y1, int y2, int alpha1, int alpha2) {
  362. if (!screen.isTransparentBackground())
  363. super.renderHoleBackground(matrices, y1, y2, alpha1, alpha2);
  364. }
  365. }
  366. }