Split up config interface, main and actual frame
This commit is contained in:
@ -1,266 +1,18 @@
|
||||
package de.oshgnacknak.gruphi;
|
||||
|
||||
import h07.graph.DirectedGraph;
|
||||
import h07.graph.DirectedGraphFactory;
|
||||
|
||||
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;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
class Gruphi extends JFrame {
|
||||
public interface Gruphi {
|
||||
|
||||
private static final int FRAME_DELAY = 1000 / 60;
|
||||
private static final double VEL = 5;
|
||||
private static final double NEIGHBOUR_DISTANCE = 50;
|
||||
long getFrameDelay();
|
||||
|
||||
private final DirectedGraph<Node, Double> graph;
|
||||
double getVelocity();
|
||||
|
||||
private final MazeGenerator<Node> mazeGenerator;
|
||||
double getGridSpacing();
|
||||
|
||||
private Node selected = null;
|
||||
private final Vector vel = new Vector(0, 0);
|
||||
private boolean running = true;
|
||||
BiPredicate<Node, Node> getNeighbourPredicate();
|
||||
|
||||
Gruphi(DirectedGraphFactory<Node, Double> factory) {
|
||||
super("Gruphi - The Graph GUI - By Osh");
|
||||
|
||||
this.graph = factory.createDirectedGraph();
|
||||
this. mazeGenerator = new MazeGenerator<>(graph, (a, b) ->
|
||||
a.pos.dist(b.pos) <= NEIGHBOUR_DISTANCE);
|
||||
|
||||
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);
|
||||
|
||||
addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_K:
|
||||
case KeyEvent.VK_W:
|
||||
case KeyEvent.VK_UP: {
|
||||
vel.y = -VEL;
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_J:
|
||||
case KeyEvent.VK_S:
|
||||
case KeyEvent.VK_DOWN: {
|
||||
vel.y = VEL;
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_H:
|
||||
case KeyEvent.VK_A:
|
||||
case KeyEvent.VK_LEFT: {
|
||||
vel.x = -VEL;
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_L:
|
||||
case KeyEvent.VK_D:
|
||||
case KeyEvent.VK_RIGHT: {
|
||||
vel.x = VEL;
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_X:
|
||||
case KeyEvent.VK_DELETE:
|
||||
case KeyEvent.VK_BACK_SPACE: {
|
||||
if (selected != null) {
|
||||
graph.removeNode(selected);
|
||||
selected = null;
|
||||
}
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_Q:
|
||||
case KeyEvent.VK_ESCAPE: {
|
||||
running = false;
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_C: {
|
||||
clearGraph();
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_M: {
|
||||
if (selected != null) {
|
||||
mazeGenerator.generate(selected);
|
||||
}
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_G: {
|
||||
generateGrid();
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
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;
|
||||
} 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;
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void generateGrid() {
|
||||
clearGraph();
|
||||
|
||||
var dist = NEIGHBOUR_DISTANCE;
|
||||
|
||||
var rows = getHeight() / dist - 1;
|
||||
var cols = getWidth() / dist - 1;
|
||||
|
||||
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);
|
||||
graph.addNode(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearGraph() {
|
||||
selected = null;
|
||||
for (var node : graph.getAllNodes()) {
|
||||
graph.removeNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Node> findClickedNode(MouseEvent e) {
|
||||
return graph.getAllNodes()
|
||||
.stream()
|
||||
.filter(n ->
|
||||
n.inside(e.getX(), e.getY()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private void draw(Drawable d) {
|
||||
d.fill(Color.BLACK);
|
||||
d.rect(0, 0, getWidth(), getHeight());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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 updateLoop() {
|
||||
var last = System.currentTimeMillis();
|
||||
var acc = 0;
|
||||
|
||||
while (running) {
|
||||
while (acc > FRAME_DELAY) {
|
||||
update();
|
||||
acc -= FRAME_DELAY;
|
||||
}
|
||||
repaint();
|
||||
|
||||
var current = System.currentTimeMillis();
|
||||
acc += current - last;
|
||||
last = current;
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (selected != null) {
|
||||
selected.pos.add(vel);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
// TODO:
|
||||
// var gruphi = new Gruphi(new SomeFactory<>());
|
||||
// gruphi.setVisible(true);
|
||||
// gruphi.updateLoop();
|
||||
// System.exit(0);
|
||||
}
|
||||
DirectedGraphFactory<Node, Double> getDirectedGraphFactory();
|
||||
}
|
||||
|
254
src/main/java/de/oshgnacknak/gruphi/GruphiFrame.java
Normal file
254
src/main/java/de/oshgnacknak/gruphi/GruphiFrame.java
Normal file
@ -0,0 +1,254 @@
|
||||
package de.oshgnacknak.gruphi;
|
||||
|
||||
import h07.graph.DirectedGraph;
|
||||
|
||||
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;
|
||||
|
||||
class GruphiFrame extends JFrame {
|
||||
|
||||
private final DirectedGraph<Node, Double> graph;
|
||||
|
||||
private final MazeGenerator<Node> mazeGenerator;
|
||||
private final Gruphi gruphi;
|
||||
|
||||
private Node selected = null;
|
||||
private final Vector vel = new Vector(0, 0);
|
||||
private boolean running = true;
|
||||
|
||||
GruphiFrame(Gruphi gruphi) {
|
||||
super("Gruphi - The Graph GUI - By Osh");
|
||||
|
||||
this.gruphi = gruphi;
|
||||
this.graph = gruphi.getDirectedGraphFactory().createDirectedGraph();
|
||||
this.mazeGenerator = new MazeGenerator<>(graph, gruphi.getNeighbourPredicate());
|
||||
|
||||
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);
|
||||
|
||||
addKeyListener(new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
case KeyEvent.VK_K:
|
||||
case KeyEvent.VK_W:
|
||||
case KeyEvent.VK_UP: {
|
||||
vel.y = -gruphi.getVelocity();
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_J:
|
||||
case KeyEvent.VK_S:
|
||||
case KeyEvent.VK_DOWN: {
|
||||
vel.y = gruphi.getVelocity();
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_H:
|
||||
case KeyEvent.VK_A:
|
||||
case KeyEvent.VK_LEFT: {
|
||||
vel.x = -gruphi.getVelocity();
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_L:
|
||||
case KeyEvent.VK_D:
|
||||
case KeyEvent.VK_RIGHT: {
|
||||
vel.x = gruphi.getVelocity();
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_X:
|
||||
case KeyEvent.VK_DELETE:
|
||||
case KeyEvent.VK_BACK_SPACE: {
|
||||
if (selected != null) {
|
||||
graph.removeNode(selected);
|
||||
selected = null;
|
||||
}
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_Q:
|
||||
case KeyEvent.VK_ESCAPE: {
|
||||
running = false;
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_C: {
|
||||
clearGraph();
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_M: {
|
||||
if (selected != null) {
|
||||
mazeGenerator.generate(selected);
|
||||
}
|
||||
} break;
|
||||
|
||||
case KeyEvent.VK_G: {
|
||||
generateGrid();
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
switch (e.getKeyCode()) {
|
||||
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;
|
||||
} 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;
|
||||
} break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void generateGrid() {
|
||||
clearGraph();
|
||||
|
||||
var dist = gruphi.getGridSpacing();
|
||||
|
||||
var rows = getHeight() / dist - 1;
|
||||
var cols = getWidth() / dist - 1;
|
||||
|
||||
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);
|
||||
graph.addNode(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clearGraph() {
|
||||
selected = null;
|
||||
for (var node : graph.getAllNodes()) {
|
||||
graph.removeNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<Node> findClickedNode(MouseEvent e) {
|
||||
return graph.getAllNodes()
|
||||
.stream()
|
||||
.filter(n ->
|
||||
n.inside(e.getX(), e.getY()))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private void draw(Drawable d) {
|
||||
d.fill(Color.BLACK);
|
||||
d.rect(0, 0, getWidth(), getHeight());
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
var acc = 0;
|
||||
|
||||
while (running) {
|
||||
while (acc > gruphi.getFrameDelay()) {
|
||||
update();
|
||||
acc -= gruphi.getFrameDelay();
|
||||
}
|
||||
repaint();
|
||||
|
||||
var current = System.currentTimeMillis();
|
||||
acc += current - last;
|
||||
last = current;
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
if (selected != null) {
|
||||
selected.pos.add(vel);
|
||||
}
|
||||
}
|
||||
}
|
46
src/main/java/de/oshgnacknak/gruphi/GruphiMain.java
Normal file
46
src/main/java/de/oshgnacknak/gruphi/GruphiMain.java
Normal file
@ -0,0 +1,46 @@
|
||||
package de.oshgnacknak.gruphi;
|
||||
|
||||
import h07.graph.DirectedGraphFactory;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public class GruphiMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
var spacing = 50;
|
||||
|
||||
var gruphi = new Gruphi() {
|
||||
|
||||
@Override
|
||||
public long getFrameDelay() {
|
||||
return 1000 / 60;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getVelocity() {
|
||||
return 5;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getGridSpacing() {
|
||||
return spacing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiPredicate<Node, Node> getNeighbourPredicate() {
|
||||
return (a, b) ->
|
||||
a.pos.dist(b.pos) <= spacing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DirectedGraphFactory<Node, Double> getDirectedGraphFactory() {
|
||||
return DirectedGraphFactory.defaultFactory();
|
||||
}
|
||||
};
|
||||
|
||||
var frame = new GruphiFrame(gruphi);
|
||||
frame.setVisible(true);
|
||||
frame.updateLoop();
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
@ -11,12 +11,12 @@ public class MazeGenerator<V> {
|
||||
private final Random random;
|
||||
private final DirectedGraph<V, Double> graph;
|
||||
private final Set<V> visited;
|
||||
private final BiPredicate<V, V> areNeighbours;
|
||||
private final BiPredicate<V, V> neighbourPredicate;
|
||||
|
||||
public MazeGenerator(DirectedGraph<V, Double> graph, BiPredicate<V, V> areNeighbours) {
|
||||
public MazeGenerator(DirectedGraph<V, Double> graph, BiPredicate<V, V> neighbourPredicate) {
|
||||
this.random = new Random();
|
||||
this.graph = graph;
|
||||
this.areNeighbours = areNeighbours;
|
||||
this.neighbourPredicate = neighbourPredicate;
|
||||
this.visited = new HashSet<>();
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ public class MazeGenerator<V> {
|
||||
.stream()
|
||||
.filter(n ->
|
||||
n != node
|
||||
&& areNeighbours.test(node, n)
|
||||
&& neighbourPredicate.test(node, n)
|
||||
&& !visited.contains(n))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
Reference in New Issue
Block a user