Panning and zooming

This commit is contained in:
2021-07-01 23:35:51 +02:00
parent c26c37cc82
commit ac35357259
3 changed files with 224 additions and 90 deletions

View File

@ -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<Node> 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,30 +218,47 @@ class GruphiFrame extends JFrame {
}
}
private Optional<Node> findClickedNode(MouseEvent e) {
private Optional<Node> 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);
drawConnections(d);
for (var node : graph.getAllNodes()) {
d.fill(node.color);
d.ellipse(node.pos.x, node.pos.y, node.radius * 2, node.radius * 2);
}
}
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 - r)
.setMag(v.mag() - child.radius)
.add(node.pos);
d.rotated(v.angle(), p.x, p.y, () ->
@ -221,13 +267,6 @@ class GruphiFrame extends JFrame {
p.x-r, p.y-r,
p.x-r, p.y+r));
}
}
for (var node : graph.getAllNodes()) {
d.fill(node.color);
d.ellipse(node.pos.x, node.pos.y, node.radius * 2, node.radius * 2);
}
}
public void updateLoop() {
var last = System.currentTimeMillis();
@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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) {}
}