ClothConfigScreen.java 18 KB

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