Skip to content

Commit

Permalink
Fix bug with mousewheel scrolling
Browse files Browse the repository at this point in the history
This issue caused unexpected behavior when scrolling via mouse wheel and locked the camera to the boundaries of the map without the possibility to scroll to a different place via mousewheel. This was mainly caused due to a missing flag which caused the implementation to reset the focus multiple times during an update operation.
With this change, the scrolling logic has also been moved to a dedicated class with an abstraction layer to the actual Swing components. This allows us to write tests for this part of the editor and makes the code better readable.
  • Loading branch information
steffen-wilke committed Dec 28, 2020
1 parent 97b736f commit 66b4051
Show file tree
Hide file tree
Showing 4 changed files with 176 additions and 83 deletions.
21 changes: 5 additions & 16 deletions utiliti/src/de/gurkenlabs/utiliti/components/MapComponent.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import de.gurkenlabs.litiengine.util.io.ImageSerializer;
import de.gurkenlabs.utiliti.Cursors;
import de.gurkenlabs.utiliti.UndoManager;
import de.gurkenlabs.utiliti.handlers.Scroll;
import de.gurkenlabs.utiliti.handlers.Snap;
import de.gurkenlabs.utiliti.handlers.Transform;
import de.gurkenlabs.utiliti.handlers.Transform.TransformType;
Expand All @@ -83,8 +84,6 @@ public class MapComponent extends GuiComponent {
public static final int EDITMODE_MOVE = 2;
private static final Logger log = Logger.getLogger(MapComponent.class.getName());

private static final int BASE_SCROLL_SPEED = 50;

private final List<IntConsumer> editModeChangedConsumer;
private final List<Consumer<IMapObject>> focusChangedConsumer;
private final List<Consumer<List<IMapObject>>> selectionChangedConsumer;
Expand All @@ -101,8 +100,6 @@ public class MapComponent extends GuiComponent {

private final List<TmxMap> maps;

private double scrollSpeed = BASE_SCROLL_SPEED;

private Point2D startPoint;
private Blueprint copiedBlueprint;

Expand Down Expand Up @@ -276,7 +273,6 @@ public boolean isLoading() {
public void prepare() {
Game.world().camera().onZoom(event -> {
Transform.updateAnchors();
this.scrollSpeed = BASE_SCROLL_SPEED / event.getZoom();
});

Zoom.applyPreference();
Expand Down Expand Up @@ -1018,16 +1014,12 @@ private void handleMouseWheelScrolled(ComponentMouseWheelEvent e) {
return;
}

final Point2D currentFocus = Game.world().camera().getFocus();
// horizontal scrolling
if (Input.keyboard().isPressed(KeyEvent.VK_CONTROL)) {
if (e.getEvent().getWheelRotation() < 0) {

Point2D newFocus = new Point2D.Double(currentFocus.getX() - this.scrollSpeed, currentFocus.getY());
Game.world().camera().setFocus(newFocus);
Scroll.left();
} else {
Point2D newFocus = new Point2D.Double(currentFocus.getX() + this.scrollSpeed, currentFocus.getY());
Game.world().camera().setFocus(newFocus);
Scroll.right();
}

return;
Expand All @@ -1044,12 +1036,9 @@ private void handleMouseWheelScrolled(ComponentMouseWheelEvent e) {
}

if (e.getEvent().getWheelRotation() < 0) {
Point2D newFocus = new Point2D.Double(currentFocus.getX(), currentFocus.getY() - this.scrollSpeed);
Game.world().camera().setFocus(newFocus);

Scroll.up();
} else {
Point2D newFocus = new Point2D.Double(currentFocus.getX(), currentFocus.getY() + this.scrollSpeed);
Game.world().camera().setFocus(newFocus);
Scroll.down();
}
}

Expand Down
137 changes: 137 additions & 0 deletions utiliti/src/de/gurkenlabs/utiliti/handlers/Scroll.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package de.gurkenlabs.utiliti.handlers;

import de.gurkenlabs.litiengine.Game;
import de.gurkenlabs.utiliti.components.Editor;

import java.awt.geom.Point2D;
import java.util.EventListener;

public final class Scroll {
private static final int SCROLL_MAX = 100;
private static final int SCROLL_SPEED = 50;

private static ScrollHandler verticalHandler;
private static ScrollHandler horizontalHandler;

private static float currentScrollSize;
private static float currentScrollSpeed;

private static boolean updating;

private Scroll() {
}

public static void up() {
final Point2D currentFocus = Game.world().camera().getFocus();
scroll(currentFocus.getX(), currentFocus.getY() - currentScrollSpeed);
}

public static void down() {
final Point2D currentFocus = Game.world().camera().getFocus();
scroll(currentFocus.getX(), currentFocus.getY() + currentScrollSpeed);
}

public static void left() {
final Point2D currentFocus = Game.world().camera().getFocus();
scroll(currentFocus.getX() - currentScrollSpeed, currentFocus.getY());
}

public static void right() {
final Point2D currentFocus = Game.world().camera().getFocus();
scroll(currentFocus.getX() + currentScrollSpeed, currentFocus.getY());
}

public static void scroll(double x, double y) {
if (Editor.instance().getMapComponent().isLoading()) {
return;
}

Game.world().camera().setFocus(x, y);
}

public static ScrollHandler getVerticalHandler() {
return verticalHandler;
}

public static ScrollHandler getHorizontalHandler() {
return horizontalHandler;
}

public static void init(ScrollHandler vertical, ScrollHandler horizontal) {
verticalHandler = vertical;
horizontalHandler = horizontal;

getVerticalHandler().onScrolled((handler) -> {
final double y = getScrollValue(handler, Game.world().environment().getMap().getSizeInPixels().height);

scroll(Game.world().camera().getFocus().getX(), y);
});

getHorizontalHandler().onScrolled((handler) -> {
final double x = getScrollValue(handler, Game.world().environment().getMap().getSizeInPixels().width);

scroll(x, Game.world().camera().getFocus().getY());
});

Game.world().camera().onZoom(e -> updateScrollHandlers());

Game.world().camera().onFocus(e -> updateScrollHandlers());

Game.world().onLoaded(e -> updateScrollHandlers());
}

private static void updateScrollHandlers() {
if (Game.world().environment() == null || updating) {
return;
}

updating = true;

try {
double relativeX = Game.world().camera().getFocus().getX() / Game.world().environment().getMap().getSizeInPixels().width;
double relativeY = Game.world().camera().getFocus().getY() / Game.world().environment().getMap().getSizeInPixels().height;

// decouple the scrollbar from the environment
currentScrollSize = Math.round(SCROLL_MAX * Math.sqrt(Game.world().camera().getRenderScale()));
currentScrollSpeed = SCROLL_SPEED / Game.world().camera().getZoom();

getHorizontalHandler().setMinimum(0);
getHorizontalHandler().setMaximum((int) currentScrollSize);
getVerticalHandler().setMinimum(0);
getVerticalHandler().setMaximum((int) currentScrollSize);

int valueX = (int) (relativeX * currentScrollSize);
int valueY = (int) (relativeY * currentScrollSize);

getHorizontalHandler().setValue(valueX);
getVerticalHandler().setValue(valueY);
} finally {
updating = false;
}
}

private static double getScrollValue(ScrollHandler scrollHandler, double actualSize) {
double currentValue = scrollHandler.getValue() / currentScrollSize;
return currentValue * actualSize;
}

public interface ScrollHandler {
int getMinimum();

int getMaximum();

int getValue();

void setMinimum(int min);

void setMaximum(int max);

void setValue(int value);

void onScrolled(ScrollHandlerEventListener listener);
}

public interface ScrollHandlerEventListener extends EventListener {
void scrolled(ScrollHandler handler);
}
}
29 changes: 29 additions & 0 deletions utiliti/src/de/gurkenlabs/utiliti/swing/ScrollHandlerBar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package de.gurkenlabs.utiliti.swing;

import de.gurkenlabs.utiliti.handlers.Scroll;

import javax.swing.*;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class ScrollHandlerBar extends JScrollBar implements Scroll.ScrollHandler {
private final List<Scroll.ScrollHandlerEventListener> listeners;

public ScrollHandlerBar(int orientation) {
super(orientation);

this.listeners = new CopyOnWriteArrayList<>();

this.setDoubleBuffered(true);
this.addAdjustmentListener(e -> {
for (Scroll.ScrollHandlerEventListener listener : listeners) {
listener.scrolled(this);
}
});
}

@Override
public void onScrolled(Scroll.ScrollHandlerEventListener listener) {
this.listeners.add(listener);
}
}
72 changes: 5 additions & 67 deletions utiliti/src/de/gurkenlabs/utiliti/swing/UI.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
Expand All @@ -20,7 +19,6 @@
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollBar;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
Expand All @@ -44,6 +42,7 @@
import de.gurkenlabs.utiliti.components.MapComponent;
import de.gurkenlabs.utiliti.components.MapController;
import de.gurkenlabs.utiliti.components.PropertyInspector;
import de.gurkenlabs.utiliti.handlers.Scroll;
import de.gurkenlabs.utiliti.swing.controllers.AssetList;
import de.gurkenlabs.utiliti.swing.controllers.EntityList;
import de.gurkenlabs.utiliti.swing.controllers.LayerList;
Expand All @@ -54,9 +53,6 @@

public final class UI {
private static final List<JComponent> orphanComponents = new CopyOnWriteArrayList<>();
private static final int SCROLL_MAX = 100;
private static JScrollBar horizontalScroll;
private static JScrollBar verticalScroll;
private static JPopupMenu canvasPopup;
private static AssetList assetComponent;

Expand All @@ -67,8 +63,6 @@ public final class UI {

private static boolean initialized;

private static float currentScrollSize;

private static volatile boolean loadingTheme;

private UI() {
Expand Down Expand Up @@ -158,70 +152,14 @@ public static MapController getMapController() {
return mapSelectionPanel;
}

private static void updateScrollBars() {
if (Game.world().environment() == null) {
return;
}

double relativeX = Game.world().camera().getFocus().getX() / Game.world().environment().getMap().getSizeInPixels().width;
double relativeY = Game.world().camera().getFocus().getY() / Game.world().environment().getMap().getSizeInPixels().height;

// decouple the scrollbar from the environment
currentScrollSize = Math.round(SCROLL_MAX * Math.sqrt(Game.world().camera().getRenderScale()));

horizontalScroll.setMinimum(0);
horizontalScroll.setMaximum((int) currentScrollSize);
verticalScroll.setMinimum(0);
verticalScroll.setMaximum((int) currentScrollSize);

int valueX = (int) (relativeX * currentScrollSize);
int valueY = (int) (relativeY * currentScrollSize);

horizontalScroll.setValue(valueX);
verticalScroll.setValue(valueY);
}

private static double getScrollValue(JScrollBar scrollbar, double actualSize) {
double currentValue = scrollbar.getValue() / currentScrollSize;
return currentValue * actualSize;
}

private static void initScrollBars(JPanel renderPane) {
horizontalScroll = new JScrollBar(java.awt.Adjustable.HORIZONTAL);
ScrollHandlerBar horizontalScroll = new ScrollHandlerBar(java.awt.Adjustable.HORIZONTAL);
ScrollHandlerBar verticalScroll = new ScrollHandlerBar(java.awt.Adjustable.VERTICAL);

renderPane.add(horizontalScroll, BorderLayout.SOUTH);
verticalScroll = new JScrollBar(java.awt.Adjustable.VERTICAL);
renderPane.add(verticalScroll, BorderLayout.EAST);

horizontalScroll.setDoubleBuffered(true);
verticalScroll.setDoubleBuffered(true);

horizontalScroll.addAdjustmentListener(e -> {
if (Editor.instance().getMapComponent().isLoading()) {
return;
}

final double x = getScrollValue(horizontalScroll, Game.world().environment().getMap().getSizeInPixels().width);

Point2D newFocus = new Point2D.Double(x, Game.world().camera().getFocus().getY());
Game.world().camera().setFocus(newFocus);
});

verticalScroll.addAdjustmentListener(e -> {
if (Editor.instance().getMapComponent().isLoading()) {
return;
}

final double y = getScrollValue(verticalScroll, Game.world().environment().getMap().getSizeInPixels().height);

Point2D newFocus = new Point2D.Double(Game.world().camera().getFocus().getX(), y);
Game.world().camera().setFocus(newFocus);
});

Game.world().camera().onZoom(e -> updateScrollBars());

Game.world().camera().onFocus(e -> updateScrollBars());

Game.world().onLoaded(e -> updateScrollBars());
Scroll.init(verticalScroll, horizontalScroll);
}

private static void setupInterface() {
Expand Down

0 comments on commit 66b4051

Please sign in to comment.