diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/GuiEditKeyframe.java b/src/main/java/eu/crushedpixel/replaymod/gui/GuiEditKeyframe.java index e8f1b04a..1c6bdad4 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/GuiEditKeyframe.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/GuiEditKeyframe.java @@ -115,7 +115,7 @@ public abstract class GuiEditKeyframe extends GuiScreen //Real Time Input int timestamp = keyframe.getRealTimestamp(); - min = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 30, 0d, 9d, (double)TimestampUtils.timestampToWholeMinutes(timestamp), false, "", 1); + min = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 30, 0d, 29d, (double)TimestampUtils.timestampToWholeMinutes(timestamp), false, "", 1); sec = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 25, 0d, 59d, (double)TimestampUtils.getSecondsFromTimestamp(timestamp), false, "", 1); ms = new GuiDraggingNumberInput(fontRendererObj, 0, 0, 35, 0d, 999d, (double)TimestampUtils.getMillisecondsFromTimestamp(timestamp), false, "", 1); diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java index 5f7fd83e..683707ff 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/GuiScrollbar.java @@ -137,7 +137,7 @@ public class GuiScrollbar extends Gui implements GuiElement { int backgroundBodyWidth = width - BORDER_LEFT - BORDER_RIGHT; int bodyLeft = positionX + SLIDER_BORDER_LEFT; - int bodyRight = positionX + (int) Math.round(backgroundBodyWidth * size) - SLIDER_BORDER_RIGHT; + int bodyRight = positionX + Math.max(1, (int) Math.round(backgroundBodyWidth * size) - SLIDER_BORDER_RIGHT); // Left border rect(positionX, positionY, SLIDER_TEXTURE_X, SLIDER_TEXTURE_Y, SLIDER_BORDER_LEFT, SLIDER_HEIGHT); @@ -172,7 +172,7 @@ public class GuiScrollbar extends Gui implements GuiElement { public boolean isHovering(int mouseX, int mouseY) { int offsetX = getSliderOffsetX(); int minX = positionX + offsetX; - int maxX = positionX + offsetX + (int) (width * size); + int maxX = positionX + offsetX + Math.max(3, (int) (width * size)); int minY = positionY + BORDER_TOP; int maxY = minY + SLIDER_HEIGHT; return mouseX >= minX && mouseY >= minY && mouseX < maxX && mouseY < maxY; diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/elements/timelines/GuiTimeline.java b/src/main/java/eu/crushedpixel/replaymod/gui/elements/timelines/GuiTimeline.java index 1396f519..3be4dea4 100644 --- a/src/main/java/eu/crushedpixel/replaymod/gui/elements/timelines/GuiTimeline.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/elements/timelines/GuiTimeline.java @@ -2,6 +2,9 @@ package eu.crushedpixel.replaymod.gui.elements.timelines; import eu.crushedpixel.replaymod.ReplayMod; import eu.crushedpixel.replaymod.gui.elements.GuiElement; +import eu.crushedpixel.replaymod.utils.RoundUtils; +import lombok.AllArgsConstructor; +import lombok.Data; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Gui; import net.minecraft.client.renderer.GlStateManager; @@ -47,6 +50,27 @@ public class GuiTimeline extends Gui implements GuiElement { */ public double zoom = 1; + /** + * The smallest value zoom might have. + */ + public double minZoom = 0.005; + + /** + * The step size of zooming in/out + */ + public double zoomStep = 0.05; + + /** + * Where the more detailed part of the timeline begins + */ + + public double detailedZoom = 0.05; + + /** + * The step size for the more detailed part of the timeline + */ + public double smallZoomStep = 0.005; + /** * Whether to draw markers on the timeline in regular time intervals. * This draws the time at big markers above the timeline. Therefore extra space in negative y direction @@ -154,7 +178,7 @@ public class GuiTimeline extends Gui implements GuiElement { // Draw markers if (showMarkers) { int markerY = positionY + height - BORDER_BOTTOM; - MarkerType mt = MarkerType.getMarkerType(zoom, timelineLength); + Markers mt = Markers.getMarkers(zoom, timelineLength, bodyWidth); // Small markers for (int s = 0; s <= timelineLength; s += mt.smallDistance) { @@ -178,7 +202,7 @@ public class GuiTimeline extends Gui implements GuiElement { // Write time above the timeline long sec = Math.round(s / 1000.0); - String timestamp = String.format("%02d:%02ds", sec / 60, sec % 60); + String timestamp = String.format("%02d:%02d", sec / 60, sec % 60); drawCenteredString(mc.fontRendererObj, timestamp, markerX, positionY - 8, 0xffffffff); } } @@ -217,7 +241,7 @@ public class GuiTimeline extends Gui implements GuiElement { long mouseTime = getTimeAt(mouseX, mouseY); if (mouseTime != -1) { long sec = mouseTime / 1000; - String timestamp = String.format("%02d:%02ds", sec / 60, sec % 60); + String timestamp = String.format("%02d:%02d", sec / 60, sec % 60); ReplayMod.tooltipRenderer.drawTooltip(mouseX, mouseY, timestamp, null, Color.WHITE); } } @@ -262,34 +286,52 @@ public class GuiTimeline extends Gui implements GuiElement { drawModalRectWithCustomSizedTexture(x, y, u, v, width, height, TEXTURE_SIZE, TEXTURE_SIZE); } - - private enum MarkerType { - - ONE_S(1000, 100), - FIVE_S(5 * 1000, 1000), - QUARTER_M(15 * 1000, 3 * 1000), - HALF_M(30 * 1000, 5 * 1000), - ONE_M(60 * 1000, 10 * 1000), - FIVE_M(5 * 60 * 1000, 50 * 1000); - - int distance; - int smallDistance; - - MarkerType(int minimum, int smallDistance) { - this.distance = minimum; - this.smallDistance = smallDistance; + public void zoomIn() { + if(zoom-zoomStep < detailedZoom) { + zoom = Math.max(minZoom, zoom - smallZoomStep); + timeStart = Math.min(timeStart, 1f - smallZoomStep); + } else { + zoom = Math.max(minZoom, zoom - zoomStep); + timeStart = Math.min(timeStart, 1f - zoomStep); } + } - public static MarkerType getMarkerType(double scale, long totalLength) { - long seconds = Math.round(totalLength * scale); + public void zoomOut() { + if(zoom+smallZoomStep <= detailedZoom) { + zoom = Math.min(1, zoom+smallZoomStep); + } else { + zoom = Math.min(1, zoom+zoomStep); + } + } - for(MarkerType mt : values()) { - if(seconds / mt.distance <= 10) { - return mt; - } - } + @Data + @AllArgsConstructor + public static class Markers { - return FIVE_M; + static final int S = 1000; + static final int M = 60*1000; + + static final int[] snapNumbers = new int[]{S, 2*S, 5*S, 10*S, 15*S, 20*S, 30*S, M, 2*M, + 5*M, 10*M, 15*M, 30*M}; + + static final int MARKER_DISTANCE = 35; + + int smallDistance; + int distance; + + public static Markers getMarkers(double scale, long totalLength, int pixelWidth) { + + //amount of seconds visible on timeline + int visible = (int)(scale*totalLength); + + int bigMarkerCount = pixelWidth/MARKER_DISTANCE; + + int bigMarkerDistance = visible / bigMarkerCount; + int snap = RoundUtils.getClosestInt(bigMarkerDistance, snapNumbers); + + bigMarkerDistance = RoundUtils.roundToMultiple(bigMarkerDistance, snap); + + return new Markers(bigMarkerDistance/4, bigMarkerDistance); } } diff --git a/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java b/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java index 26d5612e..00de0c10 100755 --- a/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java +++ b/src/main/java/eu/crushedpixel/replaymod/gui/overlay/GuiReplayOverlay.java @@ -48,9 +48,8 @@ public class GuiReplayOverlay extends Gui { private static final Minecraft mc = Minecraft.getMinecraft(); public static final ResourceLocation replay_gui = new ResourceLocation("replaymod", "replay_gui.png"); public static final int TEXTURE_SIZE = 128; - private static final float ZOOM_STEPS = 0.05f; - public static final int KEYFRAME_TIMELINE_LENGTH = 10 * 60 * 1000; + public static final int KEYFRAME_TIMELINE_LENGTH = 30 * 60 * 1000; public static GuiTexturedButton texturedButton(int x, int y, int u, int v, int size, Runnable action, String hoverText) { return new GuiTexturedButton(0, x, y, size, size, replay_gui, u, v, TEXTURE_SIZE, TEXTURE_SIZE, action, I18n.format(hoverText)); @@ -228,7 +227,7 @@ public class GuiReplayOverlay extends Gui { private final GuiElement buttonZoomIn = texturedButton(WIDTH - 14 - 9, BOTTOM_ROW, 40, 20, 9, new Runnable() { @Override public void run() { - zoom_scale = Math.max(0.025f, zoom_scale - ZOOM_STEPS); + timelineReal.zoomIn(); } }, "replaymod.gui.ingame.menu.zoomin"); @@ -236,8 +235,7 @@ public class GuiReplayOverlay extends Gui { @Override public void run() { - zoom_scale = Math.min(1f, zoom_scale + ZOOM_STEPS); - pos_left = Math.min(pos_left, 1f - zoom_scale); + timelineReal.zoomOut(); } }, "replaymod.gui.ingame.menu.zoomout"); @@ -252,7 +250,7 @@ public class GuiReplayOverlay extends Gui { private final GuiScrollbar scrollbar = new GuiScrollbar(TIMELINE_REAL_X, BOTTOM_ROW + 22, TIMELINE_REAL_WIDTH) { @Override public void dragged() { - pos_left = scrollbar.sliderPosition; + timelineReal.timeStart = scrollbar.sliderPosition; } }; @@ -350,9 +348,6 @@ public class GuiReplayOverlay extends Gui { private final GuiElement content = new ComposedElement(buttonPlayPause, buttonExport, buttonPlace, buttonTime, buttonPlayPausePath, buttonZoomIn, buttonZoomOut, timeline, timelineReal, scrollbar, speedSlider, toolbar); - private float zoom_scale = 0.1f; //can see 1/10th of the timeline - private double pos_left = 0f; //left border of timeline is at 0% - public boolean isVisible() { return ReplayHandler.isInReplay(); } @@ -365,8 +360,8 @@ public class GuiReplayOverlay extends Gui { if(FMLClientHandler.instance().isGUIOpen(GuiMouseInput.class)) { mc.displayGuiScreen(null); } - zoom_scale = 0.1f; - pos_left = 0; + timelineReal.zoom = 0.1f; + timelineReal.timeStart = 0; if (slider) { ReplayHandler.setRealTimelineCursor(0); speedSlider.reset(); @@ -386,8 +381,8 @@ public class GuiReplayOverlay extends Gui { private void checkResize() { if (displayWidth != mc.displayWidth || displayHeight != mc.displayHeight) { GuiReplayOverlay other = new GuiReplayOverlay(); - other.zoom_scale = this.zoom_scale; - other.pos_left = this.pos_left; + other.timelineReal.zoom = this.timelineReal.zoom; + other.timelineReal.timeStart = this.timelineReal.timeStart; other.speedSlider.copyValueFrom(this.speedSlider); this.unregister(); @@ -560,15 +555,13 @@ public class GuiReplayOverlay extends Gui { } // Setup scrollbar and timelines - scrollbar.size = zoom_scale; - scrollbar.sliderPosition = pos_left; + scrollbar.size = timelineReal.zoom; + scrollbar.sliderPosition = timelineReal.timeStart; timeline.cursorPosition = ReplayMod.replaySender.currentTimeStamp(); timeline.timelineLength = ReplayMod.replaySender.replayLength(); timelineReal.cursorPosition = ReplayHandler.getRealTimelineCursor(); - timelineReal.zoom = zoom_scale; - timelineReal.timeStart = pos_left; // Draw all elements content.draw(mc, mouseX, mouseY); diff --git a/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java b/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java index 05e5a5d3..956240d6 100644 --- a/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java +++ b/src/main/java/eu/crushedpixel/replaymod/utils/RoundUtils.java @@ -4,6 +4,10 @@ import java.text.DecimalFormat; public class RoundUtils { + public static int roundToMultiple(int value, int snap) { + return Math.max(snap, (int)Math.round((float)value/snap) * snap); + } + public static double round2Decimals(double val) { return round(val, 2); } @@ -20,4 +24,20 @@ public class RoundUtils { return Double.valueOf(df.format(value)); } + public static int getClosestInt(int base, int[] snap) { + int min = Integer.MAX_VALUE; + int closest = base; + + for (int v : snap) { + final int diff = Math.abs(v - base); + + if (diff < min) { + min = diff; + closest = v; + } + } + + return closest; + } + }