ClothConfigScreen.java 19 KB

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