DropdownBoxEntry.java 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. package me.shedaniel.clothconfig2.gui.entries;
  2. import com.google.common.collect.ImmutableList;
  3. import com.google.common.collect.Lists;
  4. import com.mojang.blaze3d.systems.RenderSystem;
  5. import me.shedaniel.clothconfig2.ClothConfigInitializer;
  6. import me.shedaniel.clothconfig2.api.ScissorsHandler;
  7. import me.shedaniel.clothconfig2.gui.widget.DynamicEntryListWidget.SmoothScrollingSettings;
  8. import me.shedaniel.clothconfig2.gui.widget.DynamicNewSmoothScrollingEntryListWidget.Interpolation;
  9. import me.shedaniel.clothconfig2.gui.widget.DynamicNewSmoothScrollingEntryListWidget.Precision;
  10. import me.shedaniel.math.api.Rectangle;
  11. import me.shedaniel.math.impl.PointHelper;
  12. import net.fabricmc.api.EnvType;
  13. import net.fabricmc.api.Environment;
  14. import net.minecraft.client.MinecraftClient;
  15. import net.minecraft.client.font.TextRenderer;
  16. import net.minecraft.client.gui.AbstractParentElement;
  17. import net.minecraft.client.gui.Drawable;
  18. import net.minecraft.client.gui.Element;
  19. import net.minecraft.client.gui.widget.ButtonWidget;
  20. import net.minecraft.client.gui.widget.TextFieldWidget;
  21. import net.minecraft.client.render.BufferBuilder;
  22. import net.minecraft.client.render.Tessellator;
  23. import net.minecraft.client.render.VertexFormats;
  24. import net.minecraft.client.resource.language.I18n;
  25. import net.minecraft.client.util.Window;
  26. import net.minecraft.util.math.MathHelper;
  27. import org.jetbrains.annotations.ApiStatus;
  28. import org.jetbrains.annotations.NotNull;
  29. import org.jetbrains.annotations.Nullable;
  30. import java.util.*;
  31. import java.util.function.Consumer;
  32. import java.util.function.Function;
  33. import java.util.function.Supplier;
  34. @SuppressWarnings("deprecation")
  35. @Environment(EnvType.CLIENT)
  36. public class DropdownBoxEntry<T> extends TooltipListEntry<T> {
  37. protected ButtonWidget resetButton;
  38. protected SelectionElement<T> selectionElement;
  39. @NotNull private Supplier<T> defaultValue;
  40. @Nullable private Consumer<T> saveConsumer;
  41. @ApiStatus.Internal
  42. @Deprecated
  43. public DropdownBoxEntry(String fieldName, @NotNull String resetButtonKey, @Nullable Supplier<Optional<String[]>> tooltipSupplier, boolean requiresRestart, @Nullable Supplier<T> defaultValue, @Nullable Consumer<T> saveConsumer, @Nullable Iterable<T> selections, @NotNull SelectionTopCellElement<T> topRenderer, @NotNull SelectionCellCreator<T> cellCreator) {
  44. super(I18n.translate(fieldName), tooltipSupplier, requiresRestart);
  45. this.defaultValue = defaultValue;
  46. this.saveConsumer = saveConsumer;
  47. this.resetButton = new ButtonWidget(0, 0, MinecraftClient.getInstance().textRenderer.getStringWidth(I18n.translate(resetButtonKey)) + 6, 20, I18n.translate(resetButtonKey), widget -> {
  48. selectionElement.topRenderer.setValue(defaultValue.get());
  49. getScreen().setEdited(true, isRequiresRestart());
  50. });
  51. this.selectionElement = new SelectionElement<>(this, new Rectangle(0, 0, 150, 20), new DefaultDropdownMenuElement<>(selections == null ? ImmutableList.of() : ImmutableList.copyOf(selections)), topRenderer, cellCreator);
  52. }
  53. @Override
  54. public void render(int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean isSelected, float delta) {
  55. super.render(index, y, x, entryWidth, entryHeight, mouseX, mouseY, isSelected, delta);
  56. Window window = MinecraftClient.getInstance().getWindow();
  57. this.resetButton.active = isEditable() && getDefaultValue().isPresent() && (!defaultValue.get().equals(getValue()) || getConfigError().isPresent());
  58. this.resetButton.y = y;
  59. this.selectionElement.active = isEditable();
  60. this.selectionElement.bounds.y = y;
  61. if (MinecraftClient.getInstance().textRenderer.isRightToLeft()) {
  62. MinecraftClient.getInstance().textRenderer.drawWithShadow(I18n.translate(getFieldName()), window.getScaledWidth() - x - MinecraftClient.getInstance().textRenderer.getStringWidth(I18n.translate(getFieldName())), y + 5, getPreferredTextColor());
  63. this.resetButton.x = x;
  64. this.selectionElement.bounds.x = x + resetButton.getWidth() + 1;
  65. } else {
  66. MinecraftClient.getInstance().textRenderer.drawWithShadow(I18n.translate(getFieldName()), x, y + 5, getPreferredTextColor());
  67. this.resetButton.x = x + entryWidth - resetButton.getWidth();
  68. this.selectionElement.bounds.x = x + entryWidth - 150 + 1;
  69. }
  70. this.selectionElement.bounds.width = 150 - resetButton.getWidth() - 4;
  71. resetButton.render(mouseX, mouseY, delta);
  72. selectionElement.render(mouseX, mouseY, delta);
  73. }
  74. @NotNull
  75. public ImmutableList<T> getSelections() {
  76. return selectionElement.menu.getSelections();
  77. }
  78. @Override
  79. public T getValue() {
  80. return selectionElement.getValue();
  81. }
  82. @Deprecated
  83. public SelectionElement<T> getSelectionElement() {
  84. return selectionElement;
  85. }
  86. @Override
  87. public Optional<T> getDefaultValue() {
  88. return defaultValue == null ? Optional.empty() : Optional.ofNullable(defaultValue.get());
  89. }
  90. @Override
  91. public void save() {
  92. if (saveConsumer != null)
  93. saveConsumer.accept(getValue());
  94. }
  95. @Override
  96. public List<? extends Element> children() {
  97. return Lists.newArrayList(selectionElement, resetButton);
  98. }
  99. @Override
  100. public Optional<String> getError() {
  101. return selectionElement.topRenderer.getError();
  102. }
  103. @Override
  104. public void lateRender(int mouseX, int mouseY, float delta) {
  105. selectionElement.lateRender(mouseX, mouseY, delta);
  106. }
  107. @Override
  108. public int getMorePossibleHeight() {
  109. return selectionElement.getMorePossibleHeight();
  110. }
  111. @Override
  112. public boolean mouseScrolled(double double_1, double double_2, double double_3) {
  113. return selectionElement.mouseScrolled(double_1, double_2, double_3);
  114. }
  115. public static class SelectionElement<R> extends AbstractParentElement implements Drawable {
  116. protected Rectangle bounds;
  117. protected boolean active;
  118. protected SelectionTopCellElement<R> topRenderer;
  119. protected DropdownBoxEntry<R> entry;
  120. protected DropdownMenuElement<R> menu;
  121. protected boolean dontReFocus = false;
  122. public SelectionElement(DropdownBoxEntry<R> entry, Rectangle bounds, DropdownMenuElement<R> menu, SelectionTopCellElement<R> topRenderer, SelectionCellCreator<R> cellCreator) {
  123. this.bounds = bounds;
  124. this.entry = entry;
  125. this.menu = Objects.requireNonNull(menu);
  126. this.menu.entry = entry;
  127. this.menu.cellCreator = Objects.requireNonNull(cellCreator);
  128. this.menu.initCells();
  129. this.topRenderer = Objects.requireNonNull(topRenderer);
  130. this.topRenderer.entry = entry;
  131. }
  132. @Override
  133. public void render(int mouseX, int mouseY, float delta) {
  134. fill(bounds.x, bounds.y, bounds.x + bounds.width, bounds.y + bounds.height, -6250336);
  135. fill(bounds.x + 1, bounds.y + 1, bounds.x + bounds.width - 1, bounds.y + bounds.height - 1, -16777216);
  136. topRenderer.render(mouseX, mouseY, bounds.x, bounds.y, bounds.width, bounds.height, delta);
  137. if (menu.isExpanded())
  138. menu.render(mouseX, mouseY, bounds, delta);
  139. }
  140. @Deprecated
  141. public SelectionTopCellElement<R> getTopRenderer() {
  142. return topRenderer;
  143. }
  144. @Override
  145. public boolean mouseScrolled(double double_1, double double_2, double double_3) {
  146. if (menu.isExpanded())
  147. return menu.mouseScrolled(double_1, double_2, double_3);
  148. return false;
  149. }
  150. public void lateRender(int mouseX, int mouseY, float delta) {
  151. if (menu.isExpanded())
  152. menu.lateRender(mouseX, mouseY, delta);
  153. }
  154. public int getMorePossibleHeight() {
  155. if (menu.isExpanded())
  156. return menu.getHeight();
  157. return -1;
  158. }
  159. public R getValue() {
  160. return topRenderer.getValue();
  161. }
  162. @Override
  163. public List<? extends Element> children() {
  164. return Lists.newArrayList(topRenderer, menu);
  165. }
  166. @Override
  167. public boolean mouseClicked(double double_1, double double_2, int int_1) {
  168. dontReFocus = false;
  169. boolean b = super.mouseClicked(double_1, double_2, int_1);
  170. if (dontReFocus) {
  171. setFocused(null);
  172. dontReFocus = false;
  173. }
  174. return b;
  175. }
  176. }
  177. public static abstract class DropdownMenuElement<R> extends AbstractParentElement {
  178. @Deprecated @NotNull private SelectionCellCreator<R> cellCreator;
  179. @Deprecated @NotNull private DropdownBoxEntry<R> entry;
  180. @NotNull
  181. public SelectionCellCreator<R> getCellCreator() {
  182. return cellCreator;
  183. }
  184. @NotNull
  185. public final DropdownBoxEntry<R> getEntry() {
  186. return entry;
  187. }
  188. @NotNull
  189. public abstract ImmutableList<R> getSelections();
  190. public abstract void initCells();
  191. public abstract void render(int mouseX, int mouseY, Rectangle rectangle, float delta);
  192. public abstract void lateRender(int mouseX, int mouseY, float delta);
  193. public abstract int getHeight();
  194. public final boolean isExpanded() {
  195. return (getEntry().selectionElement.getFocused() == getEntry().selectionElement.topRenderer || getEntry().selectionElement.getFocused() == getEntry().selectionElement.menu) && getEntry().getFocused() == getEntry().selectionElement && getEntry().getParent().getFocused() == getEntry();
  196. }
  197. @Override
  198. public abstract List<SelectionCellElement<R>> children();
  199. }
  200. public static class DefaultDropdownMenuElement<R> extends DropdownMenuElement<R> {
  201. @NotNull protected ImmutableList<R> selections;
  202. @NotNull protected List<SelectionCellElement<R>> cells;
  203. @NotNull protected List<SelectionCellElement<R>> currentElements;
  204. protected String lastSearchKeyword = "";
  205. protected Rectangle lastRectangle;
  206. protected boolean scrolling;
  207. protected double scroll, target;
  208. protected long start;
  209. protected long duration;
  210. public DefaultDropdownMenuElement(@NotNull ImmutableList<R> selections) {
  211. this.selections = selections;
  212. this.cells = Lists.newArrayList();
  213. this.currentElements = Lists.newArrayList();
  214. }
  215. public final double clamp(double v) {
  216. return MathHelper.clamp(v, -SmoothScrollingSettings.CLAMP_EXTENSION, getMaxScrollPosition() + SmoothScrollingSettings.CLAMP_EXTENSION);
  217. }
  218. public double getMaxScroll() {
  219. return getCellCreator().getCellHeight() * currentElements.size();
  220. }
  221. protected double getMaxScrollPosition() {
  222. return Math.max(0, this.getMaxScroll() - (getHeight()));
  223. }
  224. @Override
  225. @NotNull
  226. public ImmutableList<R> getSelections() {
  227. return selections;
  228. }
  229. @Override
  230. public void initCells() {
  231. for (R selection : getSelections()) {
  232. cells.add(getCellCreator().create(selection));
  233. }
  234. for (SelectionCellElement<R> cell : cells) {
  235. cell.entry = getEntry();
  236. }
  237. search();
  238. }
  239. public void search() {
  240. currentElements.clear();
  241. String keyword = this.lastSearchKeyword.toLowerCase();
  242. for (SelectionCellElement<R> cell : cells) {
  243. String key = cell.getSearchKey();
  244. if (key == null || key.toLowerCase().contains(keyword))
  245. currentElements.add(cell);
  246. }
  247. if (!keyword.isEmpty()) {
  248. Comparator<SelectionCellElement<?>> c = Comparator.comparingDouble(i -> i.getSearchKey() == null ? Double.MAX_VALUE : similarity(i.getSearchKey(), keyword));
  249. currentElements.sort(c.reversed());
  250. }
  251. scrollTo(0, false);
  252. }
  253. protected int editDistance(String s1, String s2) {
  254. s1 = s1.toLowerCase();
  255. s2 = s2.toLowerCase();
  256. int[] costs = new int[s2.length() + 1];
  257. for (int i = 0; i <= s1.length(); i++) {
  258. int lastValue = i;
  259. for (int j = 0; j <= s2.length(); j++) {
  260. if (i == 0)
  261. costs[j] = j;
  262. else {
  263. if (j > 0) {
  264. int newValue = costs[j - 1];
  265. if (s1.charAt(i - 1) != s2.charAt(j - 1))
  266. newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
  267. costs[j - 1] = lastValue;
  268. lastValue = newValue;
  269. }
  270. }
  271. }
  272. if (i > 0)
  273. costs[s2.length()] = lastValue;
  274. }
  275. return costs[s2.length()];
  276. }
  277. protected double similarity(String s1, String s2) {
  278. String longer = s1, shorter = s2;
  279. if (s1.length() < s2.length()) { // longer should always have greater length
  280. longer = s2;
  281. shorter = s1;
  282. }
  283. int longerLength = longer.length();
  284. if (longerLength == 0) {
  285. return 1.0; /* both strings are zero length */
  286. }
  287. return (longerLength - editDistance(longer, shorter)) / (double) longerLength;
  288. }
  289. @Override
  290. public void render(int mouseX, int mouseY, Rectangle rectangle, float delta) {
  291. if (!getEntry().selectionElement.topRenderer.getSearchTerm().equals(lastSearchKeyword)) {
  292. lastSearchKeyword = getEntry().selectionElement.topRenderer.getSearchTerm();
  293. search();
  294. }
  295. updatePosition(delta);
  296. lastRectangle = rectangle.clone();
  297. lastRectangle.translate(0, -1);
  298. }
  299. private void updatePosition(float delta) {
  300. target = clamp(target);
  301. if (target < 0) {
  302. target -= target * (1 - ClothConfigInitializer.getBounceBackMultiplier()) * delta / 3;
  303. } else if (target > getMaxScrollPosition()) {
  304. target = (target - getMaxScrollPosition()) * (1 - (1 - ClothConfigInitializer.getBounceBackMultiplier()) * delta / 3) + getMaxScrollPosition();
  305. }
  306. if (!Precision.almostEquals(scroll, target, Precision.FLOAT_EPSILON))
  307. scroll = (float) Interpolation.expoEase(scroll, target, Math.min((System.currentTimeMillis() - start) / ((double) duration), 1));
  308. else
  309. scroll = target;
  310. }
  311. @Override
  312. public void lateRender(int mouseX, int mouseY, float delta) {
  313. int last10Height = getHeight();
  314. int cWidth = getCellCreator().getCellWidth();
  315. fill(lastRectangle.x, lastRectangle.y + lastRectangle.height, lastRectangle.x + cWidth, lastRectangle.y + lastRectangle.height + last10Height + 1, -6250336);
  316. fill(lastRectangle.x + 1, lastRectangle.y + lastRectangle.height + 1, lastRectangle.x + cWidth - 1, lastRectangle.y + lastRectangle.height + last10Height, -16777216);
  317. RenderSystem.pushMatrix();
  318. RenderSystem.translatef(0, 0, 300f);
  319. ScissorsHandler.INSTANCE.scissor(new Rectangle(lastRectangle.x, lastRectangle.y + lastRectangle.height + 1, cWidth - 6, last10Height - 1));
  320. double yy = lastRectangle.y + lastRectangle.height - scroll;
  321. for (SelectionCellElement<R> cell : currentElements) {
  322. if (yy + getCellCreator().getCellHeight() >= lastRectangle.y + lastRectangle.height && yy <= lastRectangle.y + lastRectangle.height + last10Height + 1)
  323. cell.render(mouseX, mouseY, lastRectangle.x, (int) yy, getMaxScrollPosition() > 6 ? getCellCreator().getCellWidth() - 6 : getCellCreator().getCellWidth(), getCellCreator().getCellHeight(), delta);
  324. else
  325. cell.dontRender(delta);
  326. yy += getCellCreator().getCellHeight();
  327. }
  328. ScissorsHandler.INSTANCE.removeLastScissor();
  329. if (currentElements.isEmpty()) {
  330. TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
  331. String s = I18n.translate("text.cloth-config.dropdown.value.unknown");
  332. textRenderer.drawWithShadow(s, lastRectangle.x + getCellCreator().getCellWidth() / 2f - textRenderer.getStringWidth(s) / 2f, lastRectangle.y + lastRectangle.height + 3, -1);
  333. }
  334. if (getMaxScrollPosition() > 6) {
  335. RenderSystem.disableTexture();
  336. int scrollbarPositionMinX = lastRectangle.x + getCellCreator().getCellWidth() - 6;
  337. int scrollbarPositionMaxX = scrollbarPositionMinX + 6;
  338. int height = (int) (((last10Height) * (last10Height)) / this.getMaxScrollPosition());
  339. height = MathHelper.clamp(height, 32, last10Height - 8);
  340. height -= Math.min((scroll < 0 ? (int) -scroll : scroll > getMaxScrollPosition() ? (int) scroll - getMaxScrollPosition() : 0), height * .95);
  341. height = Math.max(10, height);
  342. int minY = (int) Math.min(Math.max((int) scroll * (last10Height - height) / getMaxScrollPosition() + (lastRectangle.y + lastRectangle.height + 1), (lastRectangle.y + lastRectangle.height + 1)), (lastRectangle.y + lastRectangle.height + 1 + last10Height) - height);
  343. int bottomc = new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height).contains(PointHelper.ofMouse()) ? 168 : 128;
  344. int topc = new Rectangle(scrollbarPositionMinX, minY, scrollbarPositionMaxX - scrollbarPositionMinX, height).contains(PointHelper.ofMouse()) ? 222 : 172;
  345. Tessellator tessellator = Tessellator.getInstance();
  346. BufferBuilder buffer = tessellator.getBuffer();
  347. // Bottom
  348. buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
  349. buffer.vertex(scrollbarPositionMinX, minY + height, 0.0D).texture(0, 1).color(bottomc, bottomc, bottomc, 255).next();
  350. buffer.vertex(scrollbarPositionMaxX, minY + height, 0.0D).texture(1, 1).color(bottomc, bottomc, bottomc, 255).next();
  351. buffer.vertex(scrollbarPositionMaxX, minY, 0.0D).texture(1, 0).color(bottomc, bottomc, bottomc, 255).next();
  352. buffer.vertex(scrollbarPositionMinX, minY, 0.0D).texture(0, 0).color(bottomc, bottomc, bottomc, 255).next();
  353. tessellator.draw();
  354. // Top
  355. buffer.begin(7, VertexFormats.POSITION_TEXTURE_COLOR);
  356. buffer.vertex(scrollbarPositionMinX, (minY + height - 1), 0.0D).texture(0, 1).color(topc, topc, topc, 255).next();
  357. buffer.vertex((scrollbarPositionMaxX - 1), (minY + height - 1), 0.0D).texture(1, 1).color(topc, topc, topc, 255).next();
  358. buffer.vertex((scrollbarPositionMaxX - 1), minY, 0.0D).texture(1, 0).color(topc, topc, topc, 255).next();
  359. buffer.vertex(scrollbarPositionMinX, minY, 0.0D).texture(0, 0).color(topc, topc, topc, 255).next();
  360. tessellator.draw();
  361. RenderSystem.enableTexture();
  362. }
  363. RenderSystem.translatef(0, 0, -300f);
  364. RenderSystem.popMatrix();
  365. }
  366. @Override
  367. public int getHeight() {
  368. return Math.max(Math.min(getCellCreator().getDropBoxMaxHeight(), (int) getMaxScroll()), 14);
  369. }
  370. @Override
  371. public boolean isMouseOver(double mouseX, double mouseY) {
  372. return isExpanded() && mouseX >= lastRectangle.x && mouseX <= lastRectangle.x + getCellCreator().getCellWidth() && mouseY >= lastRectangle.y + lastRectangle.height && mouseY <= lastRectangle.y + lastRectangle.height + getHeight() + 1;
  373. }
  374. @Override
  375. public boolean mouseDragged(double double_1, double double_2, int int_1, double double_3, double double_4) {
  376. if (!isExpanded())
  377. return false;
  378. if (int_1 == 0 && this.scrolling) {
  379. if (double_2 < (double) lastRectangle.y + lastRectangle.height) {
  380. scrollTo(0, false);
  381. } else if (double_2 > (double) lastRectangle.y + lastRectangle.height + getHeight()) {
  382. scrollTo(getMaxScrollPosition(), false);
  383. } else {
  384. double double_5 = Math.max(1, this.getMaxScrollPosition());
  385. int int_2 = getHeight();
  386. int int_3 = MathHelper.clamp((int) ((float) (int_2 * int_2) / (float) this.getMaxScrollPosition()), 32, int_2 - 8);
  387. double double_6 = Math.max(1.0D, double_5 / (double) (int_2 - int_3));
  388. this.offset(double_4 * double_6, false);
  389. }
  390. target = MathHelper.clamp(target, 0, getMaxScrollPosition());
  391. return true;
  392. }
  393. return false;
  394. }
  395. @Override
  396. public boolean mouseScrolled(double mouseX, double mouseY, double double_3) {
  397. if (isMouseOver(mouseX, mouseY)) {
  398. offset(ClothConfigInitializer.getScrollStep() * -double_3, true);
  399. return true;
  400. }
  401. return false;
  402. }
  403. protected void updateScrollingState(double double_1, double double_2, int int_1) {
  404. this.scrolling = isExpanded() && lastRectangle != null && int_1 == 0 && double_1 >= (double) lastRectangle.x + getCellCreator().getCellWidth() - 6 && double_1 < (double) (lastRectangle.x + getCellCreator().getCellWidth());
  405. }
  406. @Override
  407. public boolean mouseClicked(double double_1, double double_2, int int_1) {
  408. if (!isExpanded())
  409. return false;
  410. updateScrollingState(double_1, double_2, int_1);
  411. return super.mouseClicked(double_1, double_2, int_1) || scrolling;
  412. }
  413. public void offset(double value, boolean animated) {
  414. scrollTo(target + value, animated);
  415. }
  416. public void scrollTo(double value, boolean animated) {
  417. scrollTo(value, animated, ClothConfigInitializer.getScrollDuration());
  418. }
  419. public void scrollTo(double value, boolean animated, long duration) {
  420. target = clamp(value);
  421. if (animated) {
  422. start = System.currentTimeMillis();
  423. this.duration = duration;
  424. } else
  425. scroll = target;
  426. }
  427. @Override
  428. public List<SelectionCellElement<R>> children() {
  429. return currentElements;
  430. }
  431. }
  432. public static abstract class SelectionCellCreator<R> {
  433. public abstract SelectionCellElement<R> create(R selection);
  434. public abstract int getCellHeight();
  435. public abstract int getDropBoxMaxHeight();
  436. public int getCellWidth() {
  437. return 132;
  438. }
  439. }
  440. public static class DefaultSelectionCellCreator<R> extends SelectionCellCreator<R> {
  441. protected Function<R, String> toStringFunction;
  442. public DefaultSelectionCellCreator(Function<R, String> toStringFunction) {
  443. this.toStringFunction = toStringFunction;
  444. }
  445. public DefaultSelectionCellCreator() {
  446. this(Object::toString);
  447. }
  448. @Override
  449. public SelectionCellElement<R> create(R selection) {
  450. return new DefaultSelectionCellElement<>(selection, toStringFunction);
  451. }
  452. @Override
  453. public int getCellHeight() {
  454. return 14;
  455. }
  456. @Override
  457. public int getDropBoxMaxHeight() {
  458. return getCellHeight() * 7;
  459. }
  460. }
  461. public static abstract class SelectionCellElement<R> extends AbstractParentElement {
  462. @Deprecated @NotNull private DropdownBoxEntry<R> entry;
  463. @NotNull
  464. public final DropdownBoxEntry<R> getEntry() {
  465. return entry;
  466. }
  467. public abstract void render(int mouseX, int mouseY, int x, int y, int width, int height, float delta);
  468. public abstract void dontRender(float delta);
  469. @Nullable
  470. public abstract String getSearchKey();
  471. @Nullable
  472. public abstract R getSelection();
  473. }
  474. public static class DefaultSelectionCellElement<R> extends SelectionCellElement<R> {
  475. protected R r;
  476. protected int x;
  477. protected int y;
  478. protected int width;
  479. protected int height;
  480. protected boolean rendering;
  481. protected Function<R, String> toStringFunction;
  482. public DefaultSelectionCellElement(R r, Function<R, String> toStringFunction) {
  483. this.r = r;
  484. this.toStringFunction = toStringFunction;
  485. }
  486. @Override
  487. public void render(int mouseX, int mouseY, int x, int y, int width, int height, float delta) {
  488. rendering = true;
  489. this.x = x;
  490. this.y = y;
  491. this.width = width;
  492. this.height = height;
  493. boolean b = mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height;
  494. if (b)
  495. fill(x + 1, y + 1, x + width - 1, y + height - 1, -15132391);
  496. MinecraftClient.getInstance().textRenderer.drawWithShadow(toStringFunction.apply(r), x + 6, y + 3, b ? 16777215 : 8947848);
  497. }
  498. @Override
  499. public void dontRender(float delta) {
  500. rendering = false;
  501. }
  502. @Nullable
  503. @Override
  504. public String getSearchKey() {
  505. return toStringFunction.apply(r);
  506. }
  507. @Nullable
  508. @Override
  509. public R getSelection() {
  510. return r;
  511. }
  512. @Override
  513. public List<? extends Element> children() {
  514. return Collections.emptyList();
  515. }
  516. @Override
  517. public boolean mouseClicked(double mouseX, double mouseY, int int_1) {
  518. boolean b = rendering && mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height;
  519. if (b) {
  520. getEntry().selectionElement.topRenderer.setValue(r);
  521. getEntry().selectionElement.setFocused(null);
  522. getEntry().selectionElement.dontReFocus = true;
  523. return true;
  524. }
  525. return false;
  526. }
  527. }
  528. public static abstract class SelectionTopCellElement<R> extends AbstractParentElement {
  529. @Deprecated private DropdownBoxEntry<R> entry;
  530. public abstract R getValue();
  531. public abstract void setValue(R value);
  532. public abstract String getSearchTerm();
  533. public abstract Optional<String> getError();
  534. public final Optional<String> getConfigError() {
  535. return entry.getConfigError();
  536. }
  537. public DropdownBoxEntry<R> getParent() {
  538. return entry;
  539. }
  540. public final boolean hasConfigError() {
  541. return getConfigError().isPresent();
  542. }
  543. public final int getPreferredTextColor() {
  544. return getConfigError().isPresent() ? 16733525 : 16777215;
  545. }
  546. public void selectFirstRecommendation() {
  547. List<SelectionCellElement<R>> children = getParent().selectionElement.menu.children();
  548. for (SelectionCellElement<R> child : children) {
  549. if (child.getSelection() != null) {
  550. setValue(child.getSelection());
  551. getParent().selectionElement.setFocused(null);
  552. break;
  553. }
  554. }
  555. }
  556. public abstract void render(int mouseX, int mouseY, int x, int y, int width, int height, float delta);
  557. }
  558. public static class DefaultSelectionTopCellElement<R> extends SelectionTopCellElement<R> {
  559. protected TextFieldWidget textFieldWidget;
  560. protected Function<String, R> toObjectFunction;
  561. protected Function<R, String> toStringFunction;
  562. protected R value;
  563. public DefaultSelectionTopCellElement(R value, Function<String, R> toObjectFunction, Function<R, String> toStringFunction) {
  564. this.value = Objects.requireNonNull(value);
  565. this.toObjectFunction = Objects.requireNonNull(toObjectFunction);
  566. this.toStringFunction = Objects.requireNonNull(toStringFunction);
  567. textFieldWidget = new TextFieldWidget(MinecraftClient.getInstance().textRenderer, 0, 0, 148, 18, "") {
  568. @Override
  569. public void render(int int_1, int int_2, float float_1) {
  570. boolean f = isFocused();
  571. setFocused(DefaultSelectionTopCellElement.this.getParent().getParent().getFocused() == DefaultSelectionTopCellElement.this.getParent() && DefaultSelectionTopCellElement.this.getParent().getFocused() == DefaultSelectionTopCellElement.this.getParent().selectionElement && DefaultSelectionTopCellElement.this.getParent().selectionElement.getFocused() == DefaultSelectionTopCellElement.this && DefaultSelectionTopCellElement.this.getFocused() == this);
  572. super.render(int_1, int_2, float_1);
  573. setFocused(f);
  574. }
  575. @Override
  576. public boolean keyPressed(int int_1, int int_2, int int_3) {
  577. if (int_1 == 257 || int_1 == 335) {
  578. DefaultSelectionTopCellElement.this.selectFirstRecommendation();
  579. return true;
  580. }
  581. return super.keyPressed(int_1, int_2, int_3);
  582. }
  583. };
  584. textFieldWidget.setHasBorder(false);
  585. textFieldWidget.setMaxLength(999999);
  586. textFieldWidget.setText(toStringFunction.apply(value));
  587. textFieldWidget.setChangedListener(s -> {
  588. if (getParent() != null && getParent().getScreen() != null && !toStringFunction.apply(value).equals(s))
  589. getParent().getScreen().setEdited(true, getParent().isRequiresRestart());
  590. });
  591. }
  592. @Override
  593. public void render(int mouseX, int mouseY, int x, int y, int width, int height, float delta) {
  594. textFieldWidget.x = x + 4;
  595. textFieldWidget.y = y + 6;
  596. textFieldWidget.setWidth(width - 8);
  597. textFieldWidget.setEditable(getParent().isEditable());
  598. textFieldWidget.setEditableColor(getPreferredTextColor());
  599. textFieldWidget.render(mouseX, mouseY, delta);
  600. }
  601. @Override
  602. public R getValue() {
  603. if (hasConfigError())
  604. return value;
  605. return toObjectFunction.apply(textFieldWidget.getText());
  606. }
  607. @Override
  608. public void setValue(R value) {
  609. textFieldWidget.setText(toStringFunction.apply(value));
  610. textFieldWidget.setCursor(0);
  611. }
  612. @Override
  613. public String getSearchTerm() {
  614. return textFieldWidget.getText();
  615. }
  616. @Override
  617. public Optional<String> getError() {
  618. if (toObjectFunction.apply(textFieldWidget.getText()) != null)
  619. return Optional.empty();
  620. return Optional.of("Invalid Value!");
  621. }
  622. @Override
  623. public List<? extends Element> children() {
  624. return Collections.singletonList(textFieldWidget);
  625. }
  626. }
  627. }