package mhfc.net.client.gui; import java.util.ArrayList; import org.lwjgl.opengl.GL11; import mhfc.net.client.gui.GuiListItem.Alignment; import mhfc.net.client.quests.MHFCRegQuestVisual; import mhfc.net.client.util.gui.MHFCGuiUtil; import net.minecraft.client.Minecraft; public class ClickableGuiList<Item extends GuiListItem> extends ArrayList<Item> implements IMouseInteractable, IMHFCGuiItem { private static final float SLIDER_WIDTH = 8; private static final float BORDER_WIDTH = 2; public static class GuiListStringItem extends GuiListItem { private String string; public GuiListStringItem(String itemString) { this.string = itemString; } @Override public String getRepresentationString() { return string; } public String getInitializationString() { return string; } } /** * Constructs a list with fixed list item height. * * @param posX * The X-Position of the list * @param posY * The Y-Position of the list * @param width * The width of the list * @param height * The height of the list * @param itemHeight * The height to be used for list items */ public ClickableGuiList(int width, int height, float itemHeight) { this.width = width; this.height = height; this.itemHeight = itemHeight; } /** * Constructs a list that automatically adjusts the height of its items depending on their count * * @param posX * The X-position in the parent frame * @param posY * The Y-position in the parent frame * @param width * The width of the list * @param height * The height of the list */ public ClickableGuiList(int width, int height) { this(width, height, 0); recalculateItemHeightOnDraw = true; } protected int width, height; protected float itemHeight; protected float scrollAmount = 0; protected int selected = -1; protected float sliderWidth = SLIDER_WIDTH; protected boolean recalculateItemHeightOnDraw = false; protected boolean visible = true; private boolean drawSmallestBounds = false; private Alignment alignment = Alignment.LEFT; protected boolean isSliderDragged = false; protected float mouseClickX, mouseClickY; protected float mouseClickMoveX, mouseClickMoveY; private Minecraft mc; private static final long serialVersionUID = -7451553351083938970L; public float getItemWidth() { return itemHeight; } public void setItemWidth(int itemWidth) { this.itemHeight = itemWidth; this.recalculateItemHeightOnDraw = false; } public float getSliderWidth() { return sliderWidth; } public void setSliderWidth(float sliderWidth) { this.sliderWidth = sliderWidth; } protected void draw(int baseX, int baseY, int mouseX, int mouseY) { if (!visible) { return; } int posX = baseX; int posY = baseY; if (recalculateItemHeightOnDraw) { recalculateItemHeight(); } GL11.glEnable(GL11.GL_TEXTURE_2D); GL11.glDisable(GL11.GL_BLEND); drawBackground(posX, posY, mouseX, mouseY); drawListItems(posX, posY, mouseX, mouseY); drawListSlider(posX, posY, mouseX, mouseY); GL11.glEnable(GL11.GL_BLEND); } protected void drawListSlider(int posX, int posY, int mouseX, int mouseY) { float alpha = getSliderAlpha(mouseX, mouseY); if (alpha > 0f && !isEmpty()) { float sliderHeight = getSliderHeight(); float sliderPosY = getSliderVerticalDisplacement(); GL11.glColor4f(1, 1, 1, alpha); mc.getTextureManager().bindTexture(MHFCRegQuestVisual.CLICKABLE_LIST); MHFCGuiUtil.drawTexturedRectangle( posX + width - sliderWidth, posY + sliderPosY, sliderWidth, sliderHeight, 0.5f, 0.5f, 0.0625f, 0.5f); } } /** * Returns the size the list would have if all items were displayed in their full height */ public final float getFullHeight() { return itemHeight * size(); } /** * Returns the current height of the default slider */ public final float getSliderHeight() { return height * height / getFullHeight(); } /** * Returns the current vertical displacement of the default slider */ public final float getSliderVerticalDisplacement() { return scrollAmount * height / getFullHeight(); } /** * Gets the alpha value that should currently be used for the slider. Useful for effects, that maybe added by * subclasses. */ protected float getSliderAlpha(int mouseX, int mouseY) { if (isMouseOnSlider(mouseX, mouseY) || isSliderDragged) { return 1.0f; } return 0.0f; } /** * Checks if a slider should be present */ protected boolean isSliderRequired() { return itemHeight * size() > height; } /** * Return if the mouse actually hit the slider with its positioning */ protected boolean isMouseOnSlider(float relativeX, float relativeY) { return hoversMouseOverSlider(relativeX, relativeY) && isSliderRequired(); } /** * Tells if the mouse position (relative to parent frame, like this list, is over the area where the scroll bar * would be. */ protected boolean hoversMouseOverSlider(float relativeX, float relativeY) { if (relativeY >= 0 && relativeY <= height && relativeX > width - sliderWidth && relativeX <= width) { return true; } return false; } protected void drawBackground(int posX, int posY, int mouseX, int mouseY) { GL11.glColor4f(1, 1, 1, 1); mc.getTextureManager().bindTexture(MHFCRegQuestVisual.CLICKABLE_LIST); float texheight = height; if (drawSmallestBounds) { texheight = Math.min(size() * itemHeight, texheight); } MHFCGuiUtil.drawTexturedRectangle(posX, posY, width, texheight, 0, 0, 0.5f, 1.0f); if (selected >= 0) { float selectionYMin = selected * itemHeight - scrollAmount; float selectionYHeight = itemHeight; if (selectionYMin < BORDER_WIDTH) { float sm = BORDER_WIDTH - selectionYMin; selectionYMin += sm; selectionYHeight -= sm; } if (selectionYMin + selectionYHeight > height) { float sm = selectionYMin + selectionYHeight - height; selectionYHeight -= sm; } MHFCGuiUtil.drawTexturedRectangle( posX + width - BORDER_WIDTH, posY + selectionYMin, BORDER_WIDTH, selectionYHeight, 0.625f, 0.5f, 0.125f, 0.25f); } } protected void drawListItems(int posX, int posY, int mouseX, int mouseY) { int startIndex = (int) (scrollAmount / itemHeight + 0.5f); for (int i = startIndex; i < this.size(); i++) { GuiListItem item = this.get(i); int itemPosY = posY - (int) (scrollAmount - i * itemHeight); item.draw(posX, itemPosY, width, (int) itemHeight, mc, i == selected, alignment); if (i - startIndex >= (int) (height / itemHeight)) { break; } } } /** * Sets the item height to a number, so that all elements in the list are displayed. */ protected void recalculateItemHeight() { itemHeight = (float) height / size(); } @Override public boolean handleClick(float relativeX, float relativeY, int button) { if (!visible || relativeX < 0 || relativeX >= width || relativeY < 0 || relativeY >= height) { return false; } if (isMouseOnSlider(relativeX, relativeY)) { mouseClickX = relativeX; mouseClickY = relativeY; mouseClickMoveX = mouseClickX; mouseClickMoveY = mouseClickY; } else { int selec = (int) ((relativeY + scrollAmount) / itemHeight); if (selec >= size()) { return false; } setSelected(selec); } return true; } @Override public void handleMovementMouseDown(float mouseX, float mouseY, int button, long timeDiff) { if (isMouseOnSlider(mouseClickX, mouseClickY)) { scrollAmount += (mouseY - mouseClickMoveY) / height * getFullHeight(); scrollAmount = Math.min(scrollAmount, getFullHeight() - height); scrollAmount = Math.max(scrollAmount, 0f); isSliderDragged = true; mouseClickMoveX = mouseX; mouseClickMoveY = mouseY; } } @Override public void handleMouseUp(float mouseX, float mouseY, int id) { isSliderDragged = false; } @Override public void handleMovement(float mouseX, float mouseY) {} /** * Returns which item id is selected or -1 if none * */ public int getSelected() { return selected; } /** * Side effect, increase selection index when insertion happened before it. If you insert before 0 and catch * exceptions, the selection index might be at a different position than expected */ @Override public void add(int index, Item element) { if (index <= selected) { selected++; } super.add(index, element); } /** * See {@link ClickableGuiList#remove(int)} */ @Override public boolean remove(Object o) { int index = indexOf(o); if (index < selected) { selected--; } if (index == selected) { selected = -1; } return super.remove(o); } /** * Side effect, decreases selection index when deletion occurs before it. Selects -1 when the selected element is * removed. If you delete before 0 and catch exceptions, the selection index might be at a different position than * expected. */ @Override public Item remove(int index) { if (index < selected) { selected--; } if (index == selected) { selected = -1; } return super.remove(index); } @Override public void clear() { selected = -1; super.clear(); } public Item getSelectedItem() { if (selected >= 0 && selected < size()) { return get(selected); } return null; } /** * Sets the currently selected index */ public void setSelected(int newSelected) { selected = newSelected; } public int getWidth() { return width; } public int getHeight() { return height; } public void setWidthAndHeight(int width, int height) { this.width = width; this.height = height; } /** * Gets the currently used {@link Alignment} for list items */ public Alignment getAlignment() { return alignment; } public void setAlignment(Alignment alignment) { this.alignment = alignment; } public void setRecalculateItemHeightOnDraw(boolean recalculate) { this.recalculateItemHeightOnDraw = recalculate; } public boolean isDrawSmallestBounds() { return drawSmallestBounds; } public void setDrawSmallestBounds(boolean drawSmallestBounds) { this.drawSmallestBounds = drawSmallestBounds; } public boolean isVisible() { return visible; } public void setVisible(boolean newVisible) { this.visible = newVisible; } @Override public void draw(double mouseX, double mouseY, float partialTick) { draw(0, 0, (int) mouseX, (int) mouseY); } @Override public void initializeContext(Minecraft mc) { this.mc = mc; } }