From ac353572599051f654d90de1b52bcd8db443b10f Mon Sep 17 00:00:00 2001 From: Oshgnacknak Date: Thu, 1 Jul 2021 23:35:51 +0200 Subject: [PATCH] Panning and zooming --- .../de/oshgnacknak/gruphi/GruphiFrame.java | 212 +++++++++++------- src/main/java/de/oshgnacknak/gruphi/Node.java | 8 +- .../oshgnacknak/gruphi/PanningAndZooming.java | 94 ++++++++ 3 files changed, 224 insertions(+), 90 deletions(-) create mode 100644 src/main/java/de/oshgnacknak/gruphi/PanningAndZooming.java diff --git a/src/main/java/de/oshgnacknak/gruphi/GruphiFrame.java b/src/main/java/de/oshgnacknak/gruphi/GruphiFrame.java index ffe90de..420edb5 100644 --- a/src/main/java/de/oshgnacknak/gruphi/GruphiFrame.java +++ b/src/main/java/de/oshgnacknak/gruphi/GruphiFrame.java @@ -6,7 +6,6 @@ import javax.swing.*; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.Optional; @@ -17,8 +16,12 @@ class GruphiFrame extends JFrame { private final MazeGenerator mazeGenerator; private final Gruphi gruphi; + private final PanningAndZooming panningAndZooming; + + private final Vector selectedVel = new Vector(0, 0); + private final Vector cameraVel = new Vector(0, 0); + private Node selected = null; - private final Vector vel = new Vector(0, 0); private boolean running = true; GruphiFrame(Gruphi gruphi) { @@ -27,84 +30,55 @@ class GruphiFrame extends JFrame { this.gruphi = gruphi; this.graph = gruphi.getDirectedGraphFactory().createDirectedGraph(); this.mazeGenerator = new MazeGenerator<>(graph, gruphi.getNeighbourPredicate()); + this.panningAndZooming = new PanningAndZooming(this); add(new Canvas(this::draw)); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); - var mouseListener = new MouseAdapter() { - - @Override - public void mousePressed(MouseEvent e) { - switch (e.getButton()) { - case MouseEvent.BUTTON1: { - if (selected != null) { - var clicked = findClickedNode(e); - if (clicked.isPresent()) { - var n = clicked.get(); - if (graph.getChildrenForNode(selected).contains(n)) { - graph.disconnectNodes(selected, n); - } else { - graph.connectNodes(selected, 1.0, n); - } - } else { - selected.pos.x = e.getX(); - selected.pos.y = e.getY(); - } - } else { - graph.addNode(new Node(e.getX(), e.getY())); - } - } break; - - case MouseEvent.BUTTON3: { - if (selected != null) { - selected.color = Node.COLOR; - selected.radius = Node.RADIUS; - selected = null; - } - findClickedNode(e) - .ifPresent(n -> { - selected = n; - selected.radius *= 1.3; - selected.color = Color.RED; - }); - } break; - - default: break; - } - } - }; - - addMouseListener(mouseListener); - addMouseMotionListener(mouseListener); + addMouseListener(panningAndZooming); + addMouseWheelListener(panningAndZooming); + addMouseMotionListener(panningAndZooming); addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { switch (e.getKeyCode()) { + case KeyEvent.VK_W: { + selectedVel.y = -gruphi.getVelocity(); + } break; + + case KeyEvent.VK_S: { + selectedVel.y = gruphi.getVelocity(); + } break; + + case KeyEvent.VK_A: { + selectedVel.x = -gruphi.getVelocity(); + } break; + + case KeyEvent.VK_D: { + selectedVel.x = gruphi.getVelocity(); + } break; + case KeyEvent.VK_K: - case KeyEvent.VK_W: case KeyEvent.VK_UP: { - vel.y = -gruphi.getVelocity(); + cameraVel.y = gruphi.getVelocity(); } break; case KeyEvent.VK_J: - case KeyEvent.VK_S: case KeyEvent.VK_DOWN: { - vel.y = gruphi.getVelocity(); + cameraVel.y = -gruphi.getVelocity(); } break; case KeyEvent.VK_H: - case KeyEvent.VK_A: case KeyEvent.VK_LEFT: { - vel.x = -gruphi.getVelocity(); + cameraVel.x = gruphi.getVelocity(); } break; case KeyEvent.VK_L: - case KeyEvent.VK_D: case KeyEvent.VK_RIGHT: { - vel.x = gruphi.getVelocity(); + cameraVel.x = -gruphi.getVelocity(); } break; case KeyEvent.VK_X: @@ -142,22 +116,36 @@ class GruphiFrame extends JFrame { @Override public void keyReleased(KeyEvent e) { switch (e.getKeyCode()) { + case KeyEvent.VK_W: + case KeyEvent.VK_S: { + selectedVel.y = 0; + } break; + + case KeyEvent.VK_A: + case KeyEvent.VK_D: { + selectedVel.x = 0; + } break; + case KeyEvent.VK_K: case KeyEvent.VK_J: - case KeyEvent.VK_W: - case KeyEvent.VK_S: case KeyEvent.VK_DOWN: case KeyEvent.VK_UP: { - vel.y = 0; + cameraVel.y = 0; } break; case KeyEvent.VK_H: case KeyEvent.VK_L: - case KeyEvent.VK_A: - case KeyEvent.VK_D: case KeyEvent.VK_RIGHT: case KeyEvent.VK_LEFT: { - vel.x = 0; + cameraVel.x = 0; + } break; + + case KeyEvent.VK_PLUS: { + panningAndZooming.zoom(1.1); + } break; + + case KeyEvent.VK_MINUS: { + panningAndZooming.zoom(0.9); } break; default: break; @@ -166,6 +154,44 @@ class GruphiFrame extends JFrame { }); } + public void onMousePressed(int button, Vector v) { + switch (button) { + case MouseEvent.BUTTON1: { + if (selected != null) { + var clicked = findClickedNode(v); + if (clicked.isPresent() && clicked.get() != selected) { + var n = clicked.get(); + if (graph.getChildrenForNode(selected).contains(n)) { + graph.disconnectNodes(selected, n); + } else { + graph.connectNodes(selected, 1.0, n); + } + } else { + selected.pos = v; + } + } else { + graph.addNode(new Node(v)); + } + } break; + + case MouseEvent.BUTTON3: { + if (selected != null) { + selected.color = Node.COLOR; + selected.radius = Node.RADIUS; + selected = null; + } + findClickedNode(v) + .ifPresent(n -> { + selected = n; + selected.radius *= 1.3; + selected.color = Color.RED; + }); + } break; + + default: break; + } + } + private void generateGrid() { clearGraph(); @@ -176,7 +202,10 @@ class GruphiFrame extends JFrame { for (int x = 0; x < cols; x++) { for (int y = 0; y < rows; y++) { - var n = new Node((x + 0.5) * dist, (y + 0.5) * dist); + var v = new Vector(x, y) + .add(0.5, 0.5) + .mul(dist); + var n = new Node(v); graph.addNode(n); } } @@ -189,39 +218,24 @@ class GruphiFrame extends JFrame { } } - private Optional findClickedNode(MouseEvent e) { + private Optional findClickedNode(Vector v) { return graph.getAllNodes() .stream() - .filter(n -> - n.inside(e.getX(), e.getY())) + .filter(n -> n.inside(v)) .findFirst(); } - private void draw(Drawable d) { + public void draw(Drawable d) { d.fill(Color.BLACK); d.rect(0, 0, getWidth(), getHeight()); + panningAndZooming.draw(d, () -> + drawNodes(d)); + } + private void drawNodes(Drawable d) { d.strokeWeight(1); d.fill(Color.WHITE); - for (var node : graph.getAllNodes()) { - for (var child : graph.getChildrenForNode(node)) { - d.line(node.pos.x, node.pos.y, child.pos.x, child.pos.y); - - var v = child.pos - .copy() - .sub(node.pos); - var r = 4; - var p = v.copy() - .setMag(v.mag() - child.radius - r) - .add(node.pos); - - d.rotated(v.angle(), p.x, p.y, () -> - d.triangle( - p.x+r, p.y, - p.x-r, p.y-r, - p.x-r, p.y+r)); - } - } + drawConnections(d); for (var node : graph.getAllNodes()) { d.fill(node.color); @@ -229,6 +243,31 @@ class GruphiFrame extends JFrame { } } + private void drawConnections(Drawable d) { + for (var node : graph.getAllNodes()) { + for (var child : graph.getChildrenForNode(node)) { + d.line(node.pos.x, node.pos.y, child.pos.x, child.pos.y); + drawConnectionArrowHead(d, node, child); + } + } + } + + private void drawConnectionArrowHead(Drawable d, Node node, Node child) { + var v = child.pos + .copy() + .sub(node.pos); + var r = 4; + var p = v.copy() + .setMag(v.mag() - child.radius) + .add(node.pos); + + d.rotated(v.angle(), p.x, p.y, () -> + d.triangle( + p.x+r, p.y, + p.x-r, p.y-r, + p.x-r, p.y+r)); + } + public void updateLoop() { var last = System.currentTimeMillis(); var acc = 0; @@ -248,7 +287,8 @@ class GruphiFrame extends JFrame { private void update() { if (selected != null) { - selected.pos.add(vel); + selected.pos.add(selectedVel); } + panningAndZooming.pan(cameraVel); } } diff --git a/src/main/java/de/oshgnacknak/gruphi/Node.java b/src/main/java/de/oshgnacknak/gruphi/Node.java index 66b47b5..ddbf626 100644 --- a/src/main/java/de/oshgnacknak/gruphi/Node.java +++ b/src/main/java/de/oshgnacknak/gruphi/Node.java @@ -15,13 +15,13 @@ class Node { Color color; - Node(double x, double y) { - this.pos = new Vector(x, y); + Node(Vector pos) { + this.pos = pos; this.radius = RADIUS; this.color = COLOR; } - boolean inside(double x, double y) { - return pos.dist(x, y) <= radius; + boolean inside(Vector v) { + return pos.dist(v) <= radius; } } diff --git a/src/main/java/de/oshgnacknak/gruphi/PanningAndZooming.java b/src/main/java/de/oshgnacknak/gruphi/PanningAndZooming.java new file mode 100644 index 0000000..829dad9 --- /dev/null +++ b/src/main/java/de/oshgnacknak/gruphi/PanningAndZooming.java @@ -0,0 +1,94 @@ +package de.oshgnacknak.gruphi; + +import java.awt.event.*; + +public class PanningAndZooming implements MouseListener, MouseWheelListener, MouseMotionListener { + + private final Vector offset; + + private double scale; + + private final GruphiFrame frame; + + private Vector prevMouse = null; + + public PanningAndZooming(GruphiFrame frame) { + this.offset = new Vector(0, 0); + this.scale = 1; + this.frame = frame; + } + + public void pan(Vector v) { + offset.add(v.copy().div(scale)); + } + + public void zoom(double x, double y, double s) { + var before = screenToWorld(x, y); + scale *= s; + var after = screenToWorld(x, y); + + var d = before + .sub(after) + .mul(scale); + offset.sub(d); + } + + public void zoom(double s) { + zoom(frame.getWidth()/2.0, frame.getHeight()/2.0, s); + } + + public Vector screenToWorld(double x, double y) { + return new Vector(x, y) + .sub(offset) + .div(scale); + } + + public void draw(Drawable d, Runnable r) { + d.translated(offset.x, offset.y, () -> + d.scaled(scale, r)); + } + + @Override + public void mouseClicked(MouseEvent e) {} + + @Override + public void mousePressed(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON2) { + prevMouse = new Vector(e.getX(), e.getY()); + } + + var v = screenToWorld(e.getX(), e.getY()); + frame.onMousePressed(e.getButton(), v); + } + + @Override + public void mouseReleased(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON2) { + prevMouse = null; + } + } + + @Override + public void mouseEntered(MouseEvent e) {} + + @Override + public void mouseExited(MouseEvent e) {} + + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + var s = e.getWheelRotation() < 0 ? 1.1 : 0.9; + zoom(e.getX(), e.getY(), s); + } + + @Override + public void mouseDragged(MouseEvent e) { + if (prevMouse != null) { + var v = new Vector(e.getX(), e.getY()); + offset.sub(prevMouse.sub(v)); + prevMouse = v; + } + } + + @Override + public void mouseMoved(MouseEvent e) {} +}