package codechicken.nei.scroll; import java.awt.Point; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.function.Predicate; import org.lwjgl.input.Keyboard; import org.lwjgl.input.Mouse; import org.lwjgl.opengl.GL11; import codechicken.lib.gui.GuiDraw; import codechicken.lib.vec.Rectangle4i; import codechicken.nei.NEIClientUtils; import codechicken.nei.Widget; import codechicken.nei.WidgetContainer; import codechicken.nei.scroll.ScrollBar.OverflowType; import codechicken.nei.scroll.ScrollBar.ScrollType; public class ScrollContainer extends WidgetContainer { /** The mouse scroll wheel scroll speed */ protected static final int MOUSE_SCROLL_SPEED = 16; /** The keyboard arrow keys scroll speed */ protected static final int KEYBOARD_SCROLL_SPEED = 36; /** The scrollbar will fade away over this many ticks */ protected static final int FADE_TICKS = 24; protected int xScroll = 0; protected int yScroll = 0; protected int actualWidth = 0; protected int actualHeight = 0; protected int paddingInlineStart = 0; protected int paddingInlineEnd = 0; protected int paddingBlockStart = 0; protected int paddingBlockEnd = 0; protected final Map relativePosition = new HashMap<>(); protected ScrollBar horizontalScrollBar; protected ScrollBar verticalScrollBar; protected int fade = 0; public Rectangle4i boundsOutside() { int width = this.w; int height = this.h; int x = this.x; int y = this.y; if (canScrollVertical() && (this.verticalScrollBar.getOverflowType() == OverflowType.SCROLL || this.verticalScrollBar.getOverflowType() == OverflowType.AUTO)) { final Rectangle4i trackBounds = this.verticalScrollBar.trackBounds(this); x = Math.min(x, trackBounds.x); y = Math.min(y, trackBounds.y); height = Math.max(this.y + height, trackBounds.y + trackBounds.h) - y; width = Math.max(this.x + width, trackBounds.x + trackBounds.w) - x; } if (canScrollHorizontal() && (this.horizontalScrollBar.getOverflowType() == OverflowType.SCROLL || this.horizontalScrollBar.getOverflowType() == OverflowType.AUTO)) { final Rectangle4i trackBounds = this.horizontalScrollBar.trackBounds(this); x = Math.min(x, trackBounds.x); y = Math.min(y, trackBounds.y); height = Math.max(this.y + height, trackBounds.y + trackBounds.h) - y; width = Math.max(this.x + width, trackBounds.x + trackBounds.w) - x; } return new Rectangle4i(x, y, width, height); } public Rectangle4i boundsInside() { return new Rectangle4i( x + this.paddingInlineStart, y + this.paddingBlockStart, getVisibleWidth(), getVisibleHeight()); } @Override public void clear() { for (Map.Entry entry : this.relativePosition.entrySet()) { entry.getKey().x = entry.getValue().x; entry.getKey().y = entry.getValue().y; } super.clear(); this.relativePosition.clear(); } @Override public boolean removeIf(Predicate filter) { final Iterator> it = this.relativePosition.entrySet().iterator(); while (it.hasNext()) { final Map.Entry entry = it.next(); if (filter.test(entry.getKey())) { entry.getKey().x = entry.getValue().x; entry.getKey().y = entry.getValue().y; it.remove(); } } return super.removeIf(filter); } public void addWidget(Widget widget) { super.addWidget(widget); this.relativePosition.put(widget, new Point(widget.x, widget.y)); } public Point getWidgetPosition(Widget widget) { return this.relativePosition.get(widget); } public void setPaddingInline(int start, int end) { this.paddingInlineStart = start; this.paddingInlineEnd = end; } public void setPaddingBlock(int start, int end) { this.paddingBlockStart = start; this.paddingBlockEnd = end; } public int getVisibleWidth() { return this.w - this.paddingInlineStart - this.paddingInlineEnd; } public int getActualWidth() { return this.actualWidth; } public boolean setHorizontalScrollOffset(int offset) { if (!canScrollHorizontal() && getVisibleWidth() < getActualWidth()) { offset = 0; } final int scroll = this.xScroll; this.xScroll = Math.max(0, Math.min(getActualWidth() - getVisibleWidth(), offset)); if (scroll != this.xScroll) { for (Widget widget : this.widgets) { widget.x += scroll - this.xScroll; } this.fade = FADE_TICKS; return true; } return false; } public int getHorizontalScrollOffset() { return this.xScroll; } public boolean canScrollHorizontal() { return this.horizontalScrollBar != null && (this.horizontalScrollBar.getOverflowType() != OverflowType.NONE && getVisibleWidth() < getActualWidth() || this.horizontalScrollBar.getOverflowType() == OverflowType.SCROLL); } public ScrollBar getHorizontalScroll() { return this.horizontalScrollBar; } public ScrollContainer setHorizontalScroll() { return setHorizontalScroll(ScrollBar.defaultScrollBar()); } public ScrollContainer setHorizontalScroll(ScrollBar scrollBar) { this.horizontalScrollBar = scrollBar; if (this.horizontalScrollBar != null) { this.horizontalScrollBar.setScrollType(ScrollType.HORIZONTAL); } return this; } public int getVisibleHeight() { return this.h - this.paddingBlockStart - this.paddingBlockEnd; } public int getActualHeight() { return this.actualHeight; } public boolean setVerticalScrollOffset(int offset) { if (!canScrollVertical() && getVisibleHeight() < getActualHeight()) { offset = 0; } final int scroll = this.yScroll; this.yScroll = Math.max(0, Math.min(getActualHeight() - getVisibleHeight(), offset)); if (scroll != this.yScroll) { for (Widget widget : this.widgets) { widget.y += scroll - this.yScroll; } this.fade = FADE_TICKS; return true; } return false; } public int getVerticalScrollOffset() { return this.yScroll; } public boolean canScrollVertical() { return this.verticalScrollBar != null && (this.verticalScrollBar.getOverflowType() != OverflowType.NONE && getVisibleHeight() < getActualHeight() || this.verticalScrollBar.getOverflowType() == OverflowType.SCROLL); } public ScrollBar getVerticalScroll() { return this.verticalScrollBar; } public ScrollContainer setVerticalScroll() { return setVerticalScroll(ScrollBar.defaultScrollBar()); } public ScrollContainer setVerticalScroll(ScrollBar scrollBar) { this.verticalScrollBar = scrollBar; if (this.verticalScrollBar != null) { this.verticalScrollBar.setScrollType(ScrollType.VERTICAL); } return this; } @Override public void draw(int mousex, int mousey) { final boolean canScrollVertical = canScrollVertical(); final boolean canScrollHorizontal = canScrollHorizontal(); if (canScrollVertical || canScrollHorizontal) { dragScrollBars(mousex, mousey, canScrollVertical, canScrollHorizontal); GuiHelper.useScissor( this.x + this.paddingInlineStart, this.y + this.paddingBlockStart, this.w - this.paddingInlineStart - this.paddingInlineEnd, this.h - this.paddingBlockStart - this.paddingBlockEnd, () -> drawContent(mousex, mousey)); GL11.glPushAttrib(GL11.GL_ENABLE_BIT | GL11.GL_COLOR_BUFFER_BIT | GL11.GL_LIGHTING_BIT); if (canScrollVertical) { this.verticalScrollBar.draw(this, mousex, mousey, getScrollBarOpacity()); } if (canScrollHorizontal) { this.horizontalScrollBar.draw(this, mousex, mousey, getScrollBarOpacity()); } GL11.glPopAttrib(); } else { drawContent(mousex, mousey); } } private void dragScrollBars(int mousex, int mousey, boolean canScrollVertical, boolean canScrollHorizontal) { if (!Mouse.isButtonDown(0)) { return; } if (canScrollVertical) { this.verticalScrollBar.onMouseDragged(this, mousex, mousey, 0, 0); } if (canScrollHorizontal) { this.horizontalScrollBar.onMouseDragged(this, mousex, mousey, 0, 0); } } protected void drawContent(int mousex, int mousey) { GL11.glPushAttrib(GL11.GL_ALL_ATTRIB_BITS); GL11.glPushMatrix(); GL11.glColor4f(1, 1, 1, 1); super.draw(mousex, mousey); GL11.glPopMatrix(); GL11.glPopAttrib(); } private float getScrollBarOpacity() { return Math.min(2f * this.fade, FADE_TICKS) / FADE_TICKS; } @Override public void update() { this.actualHeight = 0; this.actualWidth = 0; for (Map.Entry entry : this.relativePosition.entrySet()) { entry.getKey().x = entry.getValue().x; entry.getKey().y = entry.getValue().y; } super.update(); final int x = this.x + this.paddingInlineStart - this.xScroll; final int y = this.y + this.paddingBlockStart - this.yScroll; for (Widget widget : this.widgets) { this.actualWidth = Math.max(this.actualWidth, widget.x + widget.w); this.actualHeight = Math.max(this.actualHeight, widget.y + widget.h); widget.x += x; widget.y += y; } if (this.fade > 0) { final Point mouse = GuiDraw.getMousePosition(); if (canScrollVertical() && (this.verticalScrollBar.isMouseDragged() || this.verticalScrollBar.trackBounds(this).contains(mouse.x, mouse.y))) { this.fade = FADE_TICKS; } else if (canScrollHorizontal() && (this.horizontalScrollBar.isMouseDragged() || this.horizontalScrollBar.trackBounds(this).contains(mouse.x, mouse.y))) { this.fade = FADE_TICKS; } else { this.fade--; } } setHorizontalScrollOffset(this.xScroll); setVerticalScrollOffset(this.yScroll); } @Override public boolean onMouseWheel(int direction, int mousex, int mousey) { if (super.onMouseWheel(direction, mousex, mousey)) { return true; } if (boundsOutside().contains(mousex, mousey)) { if (canScrollHorizontal() && (NEIClientUtils.shiftKey() || this.horizontalScrollBar.trackBounds(this).contains(mousex, mousey))) { this.horizontalScrollBar.setScrollOffset(this, this.xScroll - direction * MOUSE_SCROLL_SPEED); return true; } if (canScrollVertical()) { this.verticalScrollBar.setScrollOffset(this, this.yScroll - direction * MOUSE_SCROLL_SPEED); return true; } } return false; } @Override public boolean handleClick(int mousex, int mousey, int button) { if (canScrollVertical() && this.verticalScrollBar.onClick(this, mousex, mousey, button)) { return true; } if (canScrollHorizontal() && this.horizontalScrollBar.onClick(this, mousex, mousey, button)) { return true; } if (super.handleClick(mousex, mousey, button)) { return true; } return false; } @Override public void mouseDragged(int mousex, int mousey, int button, long heldTime) { if (canScrollVertical() && this.verticalScrollBar.onMouseDragged(this, mousex, mousey, button, heldTime)) { return; } if (canScrollHorizontal() && this.horizontalScrollBar.onMouseDragged(this, mousex, mousey, button, heldTime)) { return; } super.mouseDragged(mousex, mousey, button, heldTime); } @Override public void mouseUp(int mousex, int mousey, int button) { if (canScrollVertical()) { this.verticalScrollBar.onClickReleased(this, mousex, mousey, button); } if (canScrollHorizontal()) { this.horizontalScrollBar.onClickReleased(this, mousex, mousey, button); } super.mouseUp(mousex, mousey, button); } @Override public boolean handleKeyPress(int keyCode, char keyChar) { if (super.handleKeyPress(keyCode, keyChar)) { return true; } if (this.isFocused()) { switch (keyCode) { case Keyboard.KEY_UP: return canScrollVertical() && this.verticalScrollBar.setScrollOffset(this, this.yScroll - KEYBOARD_SCROLL_SPEED); case Keyboard.KEY_DOWN: return canScrollVertical() && this.verticalScrollBar.setScrollOffset(this, this.yScroll + KEYBOARD_SCROLL_SPEED); case Keyboard.KEY_LEFT: return canScrollHorizontal() && this.horizontalScrollBar.setScrollOffset(this, this.xScroll - KEYBOARD_SCROLL_SPEED); case Keyboard.KEY_RIGHT: return canScrollHorizontal() && this.horizontalScrollBar.setScrollOffset(this, this.xScroll + KEYBOARD_SCROLL_SPEED); } } return false; } }