Split up config interface, main and actual frame

This commit is contained in:
2021-07-01 20:51:22 +02:00
parent bc4f95a0a0
commit bddaa19ca4
4 changed files with 311 additions and 259 deletions

View File

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

View 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);
}
}
}

View 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);
}
}

View File

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