Fixes #4730 and also introduces repeat-pageup/pagedown when holding the mouse on the area above or below the scrollbar handle.
This commit is contained in:
@@ -229,7 +229,7 @@ public abstract class AEBaseScreen<T extends AEBaseContainer> extends ContainerS
|
||||
RenderSystem.color4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
|
||||
if (this.getScrollBar() != null) {
|
||||
this.getScrollBar().draw(this);
|
||||
this.getScrollBar().draw(matrixStack, this);
|
||||
}
|
||||
|
||||
this.drawFG(matrixStack, ox, oy, x, y);
|
||||
@@ -291,22 +291,36 @@ public abstract class AEBaseScreen<T extends AEBaseContainer> extends ContainerS
|
||||
}
|
||||
}
|
||||
|
||||
if (this.getScrollBar() != null) {
|
||||
this.getScrollBar().click(xCoord - this.guiLeft, yCoord - this.guiTop);
|
||||
// Forward left mouse button down events to the scrollbar
|
||||
if (btn == 0 && this.getScrollBar() != null) {
|
||||
if (this.getScrollBar().mouseDown(xCoord - this.guiLeft, yCoord - this.guiTop)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.mouseClicked(xCoord, yCoord, btn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseDragged(double mouseX, double mouseY, int mouseButton, double dragX, double dragY) {
|
||||
public boolean mouseReleased(double mouseX, double mouseY, int button) {
|
||||
// Forward left mouse button up events to the scrollbar
|
||||
if (button == 0 && this.getScrollBar() != null) {
|
||||
if (this.getScrollBar().mouseUp(mouseX - this.guiLeft, mouseY - this.guiTop)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.mouseReleased(mouseX, mouseY, button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mouseDragged(double mouseX, double mouseY, int mouseButton, double dragX, double dragY) {
|
||||
final Slot slot = this.getSlot((int) mouseX, (int) mouseY);
|
||||
final ItemStack itemstack = getPlayer().inventory.getItemStack();
|
||||
|
||||
if (this.getScrollBar() != null) {
|
||||
// FIXME: Coordinate system of mouseX/mouseY is unclear
|
||||
this.getScrollBar().click((int) mouseX - this.guiLeft, (int) mouseY - this.guiTop);
|
||||
this.getScrollBar().mouseDragged((int) mouseX - this.guiLeft, (int) mouseY - this.guiTop);
|
||||
}
|
||||
|
||||
if (slot instanceof FakeSlot && !itemstack.isEmpty()) {
|
||||
@@ -581,7 +595,7 @@ public abstract class AEBaseScreen<T extends AEBaseContainer> extends ContainerS
|
||||
if (slot instanceof SlotME) {
|
||||
final IAEItemStack item = ((SlotME) slot).getAEStack();
|
||||
if (item != null) {
|
||||
((AEBaseContainer) this.container).setTargetStack(item);
|
||||
this.container.setTargetStack(item);
|
||||
final InventoryAction direction = wheel > 0 ? InventoryAction.ROLL_DOWN : InventoryAction.ROLL_UP;
|
||||
final int times = (int) Math.abs(wheel);
|
||||
final int inventorySize = this.getInventorySlots().size();
|
||||
@@ -796,6 +810,11 @@ public abstract class AEBaseScreen<T extends AEBaseContainer> extends ContainerS
|
||||
|
||||
public void tick() {
|
||||
super.tick();
|
||||
|
||||
if (this.getScrollBar() != null) {
|
||||
this.getScrollBar().tick();
|
||||
}
|
||||
|
||||
for (IGuiEventListener child : children) {
|
||||
if (child instanceof ITickingWidget) {
|
||||
((ITickingWidget) child).tick();
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* This file is part of Applied Energistics 2.
|
||||
* Copyright (c) 2013 - 2014, AlgorithmX2, All rights reserved.
|
||||
*
|
||||
* Applied Energistics 2 is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Applied Energistics 2 is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with Applied Energistics 2. If not, see <http://www.gnu.org/licenses/lgpl>.
|
||||
*/
|
||||
|
||||
package appeng.client.gui.widgets;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* This class can be used to implement repeating events such as holding down a button to fire
|
||||
* an event repeatedly while the button is still being held, or repeatedly scrolling down
|
||||
* a page, while the mouse is held down on the scrollbar.
|
||||
*/
|
||||
public class EventRepeater {
|
||||
|
||||
/**
|
||||
* -1 if no repeat event is scheduled.
|
||||
* Otherwise contains the {@link System#nanoTime()} at which the next event should occur.
|
||||
*/
|
||||
private long nextEventTime = -1;
|
||||
|
||||
private EventCallback eventCallback = null;
|
||||
|
||||
private final long eventDelay; // In nanoseconds
|
||||
|
||||
private final long eventInterval; // In nanoseconds
|
||||
|
||||
public EventRepeater(Duration delay, Duration interval) {
|
||||
this.eventDelay = delay.toNanos();
|
||||
this.eventInterval = interval.toNanos();
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (this.eventCallback == null) {
|
||||
return; // No event scheduled
|
||||
}
|
||||
|
||||
// Use nanoTime here because it is monotonically increasing, while System.currentTimeMillis is not
|
||||
long nanoTime = System.nanoTime();
|
||||
if (nanoTime < this.nextEventTime) {
|
||||
return; // Event time not reached
|
||||
}
|
||||
|
||||
// Before triggering, recompute the next event, since
|
||||
// the event callback itself may reschedule/cancel, and
|
||||
// we should not overwrite that
|
||||
this.nextEventTime = nanoTime + this.eventInterval;
|
||||
this.eventCallback.trigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the given callback to be called after a given initial delay, and then
|
||||
* after the given interval repeatedly.
|
||||
*
|
||||
* <p>Replaces any previously queued callback.
|
||||
*/
|
||||
public void repeat(EventCallback callback) {
|
||||
long time = System.nanoTime();
|
||||
this.eventCallback = callback;
|
||||
this.nextEventTime = time + eventDelay;
|
||||
}
|
||||
|
||||
public boolean isRepeating() {
|
||||
return this.eventCallback != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop repeating the event.
|
||||
*/
|
||||
public void stop() {
|
||||
this.eventCallback = null;
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface EventCallback {
|
||||
void trigger();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,17 +18,62 @@
|
||||
|
||||
package appeng.client.gui.widgets;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
|
||||
import net.minecraftforge.fml.client.gui.GuiUtils;
|
||||
|
||||
import appeng.client.gui.AEBaseScreen;
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import net.minecraft.client.gui.AbstractGui;
|
||||
import net.minecraft.client.renderer.Rectangle2d;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
|
||||
public class Scrollbar implements IScrollSource {
|
||||
import java.time.Duration;
|
||||
|
||||
public class Scrollbar extends AbstractGui implements IScrollSource {
|
||||
|
||||
/**
|
||||
* Width of the scrollbar handle sprite in the source texture.
|
||||
*/
|
||||
private static final int HANDLE_WIDTH = 12;
|
||||
|
||||
/**
|
||||
* Height of the scrollbar handle sprite in the source texture.
|
||||
*/
|
||||
private static final int HANDLE_HEIGHT = 15;
|
||||
|
||||
/**
|
||||
* Texture containing the scrollbar handle sprites.
|
||||
*/
|
||||
private static final ResourceLocation TEXTURE = new ResourceLocation("minecraft",
|
||||
"textures/gui/container/creative_inventory/tabs.png");
|
||||
|
||||
/**
|
||||
* Rectangle in the source texture that contains the sprite for an enabled
|
||||
* handle.
|
||||
*/
|
||||
private static final Rectangle2d ENABLED = new Rectangle2d(232, 0, HANDLE_WIDTH, HANDLE_HEIGHT);
|
||||
|
||||
/**
|
||||
* Rectangle in the source texture that contains the sprite for a disabled
|
||||
* handle.
|
||||
*/
|
||||
private static final Rectangle2d DISABLED = new Rectangle2d(232 + HANDLE_WIDTH, 0, HANDLE_WIDTH, HANDLE_HEIGHT);
|
||||
|
||||
/**
|
||||
* The screen x-coordinate of the scrollbar's inner track.
|
||||
*/
|
||||
private int displayX = 0;
|
||||
|
||||
/**
|
||||
* The screen y-coordinate of the scrollbar's inner track.
|
||||
*/
|
||||
private int displayY = 0;
|
||||
private int width = 12;
|
||||
|
||||
/**
|
||||
* The inner width of the scrollbar track.
|
||||
*/
|
||||
private int width = HANDLE_WIDTH;
|
||||
|
||||
/**
|
||||
* The inner height of the scrollbar track.
|
||||
*/
|
||||
private int height = 16;
|
||||
private int pageSize = 1;
|
||||
|
||||
@@ -36,18 +81,51 @@ public class Scrollbar implements IScrollSource {
|
||||
private int minScroll = 0;
|
||||
private int currentScroll = 0;
|
||||
|
||||
public void draw(final AEBaseScreen<?> g) {
|
||||
g.bindTexture("minecraft", "gui/container/creative_inventory/tabs.png");
|
||||
RenderSystem.color4f(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
/**
|
||||
* True if the scrollbar's handle is currently being dragged.
|
||||
*/
|
||||
private boolean dragging;
|
||||
|
||||
private final EventRepeater eventRepeater = new EventRepeater(
|
||||
Duration.ofMillis(250),
|
||||
Duration.ofMillis(150)
|
||||
);
|
||||
|
||||
/**
|
||||
* Draws the handle of the scrollbar.
|
||||
* <p>
|
||||
* The GUI is assumed to already contain a prebaked scrollbar track in its
|
||||
* background.
|
||||
*/
|
||||
public void draw(MatrixStack matrices, final AEBaseScreen<?> g) {
|
||||
setBlitOffset(g.getBlitOffset());
|
||||
|
||||
// Draw the track (nice for debugging)
|
||||
// fill(matrices, displayX, displayY, this.displayX + width, this.displayY + height, 0xffff0000);
|
||||
|
||||
g.bindTexture(TEXTURE);
|
||||
|
||||
int yOffset;
|
||||
Rectangle2d sourceRect;
|
||||
if (this.getRange() == 0) {
|
||||
GuiUtils.drawTexturedModalRect(this.displayX, this.displayY, 232 + this.width, 0, this.width, 15,
|
||||
g.getBlitOffset());
|
||||
yOffset = 0;
|
||||
sourceRect = DISABLED;
|
||||
} else {
|
||||
final int offset = (this.currentScroll - this.minScroll) * (this.height - 15) / this.getRange();
|
||||
GuiUtils.drawTexturedModalRect(this.displayX, offset + this.displayY, 232, 0, this.width, 15,
|
||||
g.getBlitOffset());
|
||||
yOffset = getHandleYOffset();
|
||||
sourceRect = ENABLED;
|
||||
}
|
||||
|
||||
blit(matrices, this.displayX, this.displayY + yOffset, sourceRect.getX(), sourceRect.getY(),
|
||||
sourceRect.getWidth(), sourceRect.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the y-position of the scrollbar handle in relation to the upper edge
|
||||
* of the scrollbar's track.
|
||||
*/
|
||||
private int getHandleYOffset() {
|
||||
int availableHeight = this.height - HANDLE_HEIGHT;
|
||||
return (this.currentScroll - this.minScroll) * availableHeight / this.getRange();
|
||||
}
|
||||
|
||||
private int getRange() {
|
||||
@@ -111,18 +189,55 @@ public class Scrollbar implements IScrollSource {
|
||||
return this.currentScroll;
|
||||
}
|
||||
|
||||
public void click(final double x, final double y) {
|
||||
if (this.getRange() == 0) {
|
||||
public boolean mouseDown(double x, double y) {
|
||||
this.dragging = false;
|
||||
|
||||
// Clicks to the left or right of the scrollbar don't do anything
|
||||
if (x < displayX || x >= displayX + width) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clicks to the top or bottom don't do anything either
|
||||
int relY = (int) Math.round(y - displayY);
|
||||
if (relY < 0 || relY >= height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int handleYOffset = getHandleYOffset();
|
||||
|
||||
if (relY < handleYOffset) {
|
||||
// Clicks above the handle will page up, repeatedly
|
||||
repeatPageUp();
|
||||
eventRepeater.repeat(this::repeatPageUp);
|
||||
|
||||
} else if (relY < handleYOffset + HANDLE_HEIGHT) {
|
||||
// Clicks on the handle will initiate dragging it
|
||||
this.dragging = true;
|
||||
} else {
|
||||
// Clicks below the handle will page down, repeatedly
|
||||
repeatPageDown();
|
||||
eventRepeater.repeat(this::repeatPageDown);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean mouseUp(double x, double y) {
|
||||
this.dragging = false;
|
||||
this.eventRepeater.stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
public void mouseDragged(double x, double y) {
|
||||
if (this.getRange() == 0 || !this.dragging || this.eventRepeater.isRepeating()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (x > this.displayX && x <= this.displayX + this.width) {
|
||||
if (y > this.displayY && y <= this.displayY + this.height) {
|
||||
this.currentScroll = (int) (y - this.displayY);
|
||||
this.currentScroll = this.minScroll + ((this.currentScroll * 2 * this.getRange() / this.height));
|
||||
this.currentScroll = (this.currentScroll + 1) >> 1;
|
||||
this.applyRange();
|
||||
}
|
||||
if (y > this.displayY && y <= this.displayY + this.height) {
|
||||
this.currentScroll = (int) (y - this.displayY);
|
||||
this.currentScroll = this.minScroll + ((this.currentScroll * 2 * this.getRange() / this.height));
|
||||
this.currentScroll = (this.currentScroll + 1) >> 1;
|
||||
this.applyRange();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,4 +246,25 @@ public class Scrollbar implements IScrollSource {
|
||||
this.currentScroll += delta * this.pageSize;
|
||||
this.applyRange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ticks the scrollbar for the purposes of input-repeats (since mouse-downs are not repeat-triggered),
|
||||
* used to repeatedly page-up or page-down when the mouse is held in the area above or below the scrollbar handle.
|
||||
*/
|
||||
public void tick() {
|
||||
this.eventRepeater.tick();
|
||||
}
|
||||
|
||||
private void repeatPageUp() {
|
||||
System.out.println("REPEAT PAGE UP");
|
||||
this.currentScroll -= this.pageSize;
|
||||
this.applyRange();
|
||||
}
|
||||
|
||||
private void repeatPageDown() {
|
||||
System.out.println("REPEAT PAGE DOWN");
|
||||
this.currentScroll += this.pageSize;
|
||||
this.applyRange();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user