Panning and zooming
This commit is contained in:
@ -6,7 +6,6 @@ import javax.swing.*;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.KeyAdapter;
|
import java.awt.event.KeyAdapter;
|
||||||
import java.awt.event.KeyEvent;
|
import java.awt.event.KeyEvent;
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@ -17,8 +16,12 @@ class GruphiFrame extends JFrame {
|
|||||||
private final MazeGenerator<Node> mazeGenerator;
|
private final MazeGenerator<Node> mazeGenerator;
|
||||||
private final Gruphi gruphi;
|
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 Node selected = null;
|
||||||
private final Vector vel = new Vector(0, 0);
|
|
||||||
private boolean running = true;
|
private boolean running = true;
|
||||||
|
|
||||||
GruphiFrame(Gruphi gruphi) {
|
GruphiFrame(Gruphi gruphi) {
|
||||||
@ -27,84 +30,55 @@ class GruphiFrame extends JFrame {
|
|||||||
this.gruphi = gruphi;
|
this.gruphi = gruphi;
|
||||||
this.graph = gruphi.getDirectedGraphFactory().createDirectedGraph();
|
this.graph = gruphi.getDirectedGraphFactory().createDirectedGraph();
|
||||||
this.mazeGenerator = new MazeGenerator<>(graph, gruphi.getNeighbourPredicate());
|
this.mazeGenerator = new MazeGenerator<>(graph, gruphi.getNeighbourPredicate());
|
||||||
|
this.panningAndZooming = new PanningAndZooming(this);
|
||||||
|
|
||||||
add(new Canvas(this::draw));
|
add(new Canvas(this::draw));
|
||||||
pack();
|
pack();
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||||
|
|
||||||
var mouseListener = new MouseAdapter() {
|
addMouseListener(panningAndZooming);
|
||||||
|
addMouseWheelListener(panningAndZooming);
|
||||||
@Override
|
addMouseMotionListener(panningAndZooming);
|
||||||
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);
|
|
||||||
|
|
||||||
addKeyListener(new KeyAdapter() {
|
addKeyListener(new KeyAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void keyPressed(KeyEvent e) {
|
public void keyPressed(KeyEvent e) {
|
||||||
switch (e.getKeyCode()) {
|
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_K:
|
||||||
case KeyEvent.VK_W:
|
|
||||||
case KeyEvent.VK_UP: {
|
case KeyEvent.VK_UP: {
|
||||||
vel.y = -gruphi.getVelocity();
|
cameraVel.y = gruphi.getVelocity();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case KeyEvent.VK_J:
|
case KeyEvent.VK_J:
|
||||||
case KeyEvent.VK_S:
|
|
||||||
case KeyEvent.VK_DOWN: {
|
case KeyEvent.VK_DOWN: {
|
||||||
vel.y = gruphi.getVelocity();
|
cameraVel.y = -gruphi.getVelocity();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case KeyEvent.VK_H:
|
case KeyEvent.VK_H:
|
||||||
case KeyEvent.VK_A:
|
|
||||||
case KeyEvent.VK_LEFT: {
|
case KeyEvent.VK_LEFT: {
|
||||||
vel.x = -gruphi.getVelocity();
|
cameraVel.x = gruphi.getVelocity();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case KeyEvent.VK_L:
|
case KeyEvent.VK_L:
|
||||||
case KeyEvent.VK_D:
|
|
||||||
case KeyEvent.VK_RIGHT: {
|
case KeyEvent.VK_RIGHT: {
|
||||||
vel.x = gruphi.getVelocity();
|
cameraVel.x = -gruphi.getVelocity();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case KeyEvent.VK_X:
|
case KeyEvent.VK_X:
|
||||||
@ -142,22 +116,36 @@ class GruphiFrame extends JFrame {
|
|||||||
@Override
|
@Override
|
||||||
public void keyReleased(KeyEvent e) {
|
public void keyReleased(KeyEvent e) {
|
||||||
switch (e.getKeyCode()) {
|
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_K:
|
||||||
case KeyEvent.VK_J:
|
case KeyEvent.VK_J:
|
||||||
case KeyEvent.VK_W:
|
|
||||||
case KeyEvent.VK_S:
|
|
||||||
case KeyEvent.VK_DOWN:
|
case KeyEvent.VK_DOWN:
|
||||||
case KeyEvent.VK_UP: {
|
case KeyEvent.VK_UP: {
|
||||||
vel.y = 0;
|
cameraVel.y = 0;
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case KeyEvent.VK_H:
|
case KeyEvent.VK_H:
|
||||||
case KeyEvent.VK_L:
|
case KeyEvent.VK_L:
|
||||||
case KeyEvent.VK_A:
|
|
||||||
case KeyEvent.VK_D:
|
|
||||||
case KeyEvent.VK_RIGHT:
|
case KeyEvent.VK_RIGHT:
|
||||||
case KeyEvent.VK_LEFT: {
|
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;
|
} break;
|
||||||
|
|
||||||
default: 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() {
|
private void generateGrid() {
|
||||||
clearGraph();
|
clearGraph();
|
||||||
|
|
||||||
@ -176,7 +202,10 @@ class GruphiFrame extends JFrame {
|
|||||||
|
|
||||||
for (int x = 0; x < cols; x++) {
|
for (int x = 0; x < cols; x++) {
|
||||||
for (int y = 0; y < rows; y++) {
|
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);
|
graph.addNode(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,39 +218,24 @@ class GruphiFrame extends JFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Node> findClickedNode(MouseEvent e) {
|
private Optional<Node> findClickedNode(Vector v) {
|
||||||
return graph.getAllNodes()
|
return graph.getAllNodes()
|
||||||
.stream()
|
.stream()
|
||||||
.filter(n ->
|
.filter(n -> n.inside(v))
|
||||||
n.inside(e.getX(), e.getY()))
|
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void draw(Drawable d) {
|
public void draw(Drawable d) {
|
||||||
d.fill(Color.BLACK);
|
d.fill(Color.BLACK);
|
||||||
d.rect(0, 0, getWidth(), getHeight());
|
d.rect(0, 0, getWidth(), getHeight());
|
||||||
|
panningAndZooming.draw(d, () ->
|
||||||
|
drawNodes(d));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawNodes(Drawable d) {
|
||||||
d.strokeWeight(1);
|
d.strokeWeight(1);
|
||||||
d.fill(Color.WHITE);
|
d.fill(Color.WHITE);
|
||||||
for (var node : graph.getAllNodes()) {
|
drawConnections(d);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var node : graph.getAllNodes()) {
|
for (var node : graph.getAllNodes()) {
|
||||||
d.fill(node.color);
|
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() {
|
public void updateLoop() {
|
||||||
var last = System.currentTimeMillis();
|
var last = System.currentTimeMillis();
|
||||||
var acc = 0;
|
var acc = 0;
|
||||||
@ -248,7 +287,8 @@ class GruphiFrame extends JFrame {
|
|||||||
|
|
||||||
private void update() {
|
private void update() {
|
||||||
if (selected != null) {
|
if (selected != null) {
|
||||||
selected.pos.add(vel);
|
selected.pos.add(selectedVel);
|
||||||
}
|
}
|
||||||
|
panningAndZooming.pan(cameraVel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,13 @@ class Node {
|
|||||||
|
|
||||||
Color color;
|
Color color;
|
||||||
|
|
||||||
Node(double x, double y) {
|
Node(Vector pos) {
|
||||||
this.pos = new Vector(x, y);
|
this.pos = pos;
|
||||||
this.radius = RADIUS;
|
this.radius = RADIUS;
|
||||||
this.color = COLOR;
|
this.color = COLOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean inside(double x, double y) {
|
boolean inside(Vector v) {
|
||||||
return pos.dist(x, y) <= radius;
|
return pos.dist(v) <= radius;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
94
src/main/java/de/oshgnacknak/gruphi/PanningAndZooming.java
Normal file
94
src/main/java/de/oshgnacknak/gruphi/PanningAndZooming.java
Normal 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) {}
|
||||||
|
}
|
Reference in New Issue
Block a user