From 10f58848913a1086485dc4608816721c5466ead5 Mon Sep 17 00:00:00 2001 From: Czekaj Tom Date: Mon, 10 Jul 2023 15:16:04 +0200 Subject: [PATCH] :sparkles: New UI --- src/main/java/fr/xen0xys/Main.java | 2 +- src/main/java/fr/xen0xys/models/Computer.java | 4 +- .../models/algorithms/AlgorithmType.java | 16 ++- src/main/java/fr/xen0xys/ui/GameWindow.java | 131 ++++++++++++++++-- src/main/java/fr/xen0xys/ui/utils/Runner.java | 22 ++- .../fr/xen0xys/ui/views/AlgorithmPanel.java | 66 +++++++++ .../fr/xen0xys/ui/views/AlgorithmView.java | 87 ------------ .../java/fr/xen0xys/ui/views/MenuPanel.java | 121 ++++++++++++++++ .../java/fr/xen0xys/ui/views/MenuView.java | 32 ----- src/main/java/fr/xen0xys/utils/Tuple.java | 4 + 10 files changed, 344 insertions(+), 141 deletions(-) create mode 100644 src/main/java/fr/xen0xys/ui/views/AlgorithmPanel.java delete mode 100644 src/main/java/fr/xen0xys/ui/views/AlgorithmView.java create mode 100644 src/main/java/fr/xen0xys/ui/views/MenuPanel.java delete mode 100644 src/main/java/fr/xen0xys/ui/views/MenuView.java create mode 100644 src/main/java/fr/xen0xys/utils/Tuple.java diff --git a/src/main/java/fr/xen0xys/Main.java b/src/main/java/fr/xen0xys/Main.java index 2fc5ffc..27a5d88 100644 --- a/src/main/java/fr/xen0xys/Main.java +++ b/src/main/java/fr/xen0xys/Main.java @@ -4,6 +4,6 @@ public class Main { public static void main(String[] args) { - GameWindow gameWindow = new GameWindow(); + new GameWindow(); } } diff --git a/src/main/java/fr/xen0xys/models/Computer.java b/src/main/java/fr/xen0xys/models/Computer.java index 2e416ca..d680193 100644 --- a/src/main/java/fr/xen0xys/models/Computer.java +++ b/src/main/java/fr/xen0xys/models/Computer.java @@ -8,7 +8,7 @@ public class Computer { private final Runner runner; - public Computer(AlgorithmType algorithmType, List rods) { + public Computer(AlgorithmType algorithmType, List rods, int tps) { Algorithm algorithm; switch (algorithmType){ case BUBBLE_SORT -> algorithm = new BubbleSort(this, rods); @@ -16,7 +16,7 @@ public Computer(AlgorithmType algorithmType, List rods) { case INSERTION_SORT -> algorithm = new InsertionSort(this, rods); default -> throw new IllegalStateException("Unexpected value: " + algorithmType); } - this.runner = new Runner(algorithm::step, 20, true); + this.runner = new Runner(algorithm::step, tps, true); } public void start(){ diff --git a/src/main/java/fr/xen0xys/models/algorithms/AlgorithmType.java b/src/main/java/fr/xen0xys/models/algorithms/AlgorithmType.java index 5ee566c..f83a53f 100644 --- a/src/main/java/fr/xen0xys/models/algorithms/AlgorithmType.java +++ b/src/main/java/fr/xen0xys/models/algorithms/AlgorithmType.java @@ -1,7 +1,17 @@ package fr.xen0xys.models.algorithms; public enum AlgorithmType { - BUBBLE_SORT, - SELECTION_SORT, - INSERTION_SORT + BUBBLE_SORT("Bubble Sort"), + SELECTION_SORT("Selection Sort"), + INSERTION_SORT("Insertion Sort"); + + private final String displayName; + + AlgorithmType(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } } diff --git a/src/main/java/fr/xen0xys/ui/GameWindow.java b/src/main/java/fr/xen0xys/ui/GameWindow.java index 68053c6..14a1656 100644 --- a/src/main/java/fr/xen0xys/ui/GameWindow.java +++ b/src/main/java/fr/xen0xys/ui/GameWindow.java @@ -1,35 +1,142 @@ package fr.xen0xys.ui; -import fr.xen0xys.ui.views.MenuView; +import fr.xen0xys.models.Computer; +import fr.xen0xys.models.Rod; +import fr.xen0xys.models.algorithms.AlgorithmType; +import fr.xen0xys.ui.utils.Runner; +import fr.xen0xys.ui.views.AlgorithmPanel; +import fr.xen0xys.ui.views.MenuPanel; +import fr.xen0xys.utils.Tuple; import javax.swing.*; +import java.awt.*; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; -public class GameWindow extends JFrame { +public class GameWindow extends JFrame implements ComponentListener { + + private final MenuPanel menuPanel; + private final AlgorithmPanel algorithmPanel; + private final Runner graphicRunner; + private Computer computer; + private final List rods; + + public static final Font font = new Font("Roboto", Font.PLAIN, 18); public GameWindow() { super("Sorting Challenge"); + this.rods = new ArrayList<>(); + // Init panels and window + this.menuPanel = new MenuPanel(this); + this.algorithmPanel = new AlgorithmPanel(this, this.rods); + this.graphicRunner = new Runner(this.algorithmPanel::repaint, 60, true); this.initWindow(); - this.loadView(new MenuView(this)); this.setVisible(true); + this.generateRods(100); + this.graphicRunner.start(); } - private void initWindow(){ + private void initWindow() { this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - this.setSize(500, 500); -// this.setResizable(false); - this.setLocationRelativeTo(null); try { UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) { throw new RuntimeException(e); } + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int width = screenSize.width - 50; + int height = screenSize.height - 300; + this.setLocation(25, 75); + this.resizePanels(width, height); + this.setSize(width + 15, height + 39); + this.setMinimumSize(new Dimension(1400, 675)); + // Add panels to window + this.setLayout(null); + this.add(this.menuPanel); + this.add(this.algorithmPanel); + this.addComponentListener(this); + } + + public Tuple getRunnersAps() { + if (Objects.isNull(this.computer)) + return new Tuple<>(this.graphicRunner.getCurrentAps(), -1); + return new Tuple<>(this.graphicRunner.getCurrentAps(), this.computer.getRunner().getCurrentAps()); + } + + private void resizePanels(int width, int height) { + int algorithmPanelHeight = (int) (height * 0.85); + int menuPanelHeight = height - algorithmPanelHeight; + this.algorithmPanel.setSize(width, algorithmPanelHeight); + this.algorithmPanel.setLocation(0, menuPanelHeight); + this.menuPanel.setSize(width, menuPanelHeight); + this.menuPanel.setLocation(0, 0); + } + + @Override + public void componentResized(ComponentEvent e) { + this.resizePanels(this.getWidth() - 15, this.getHeight() - 39); + this.menuPanel.repaint(); + } + + @Override + public void componentMoved(ComponentEvent e) { + + } + + @Override + public void componentShown(ComponentEvent e) { + + } + + @Override + public void componentHidden(ComponentEvent e) { + + } + + private void generateRods(int rodCount) { + this.rods.clear(); + for (int i = 0; i < rodCount; i++) + this.rods.add(new Rod(rodCount - i)); + this.shuffle(); + } + + public void stopComputer() { + if(Objects.isNull(this.computer)) + return; + this.computer.stop(); + this.computer = null; + this.rods.forEach(rod -> rod.setColor(Color.WHITE)); + } + + public void startComputer(AlgorithmType algorithmType, int tps) { + if(Objects.nonNull(this.computer)) + this.stopComputer(); + this.computer = new Computer(algorithmType, this.rods, tps); + this.computer.start(); + } + + public void setFps(int fps) { + this.graphicRunner.setAps(fps); + } + + public void setTps(int tps){ + if(Objects.nonNull(this.computer)) + this.computer.getRunner().setAps(tps); + } + + public void setRodCount(int rodCount) { + if(Objects.nonNull(this.computer)) + this.stopComputer(); + this.generateRods(rodCount); } - public void loadView(JPanel panel){ - this.setContentPane(panel); - this.revalidate(); - this.setSize(panel.getPreferredSize()); - this.pack(); + public void shuffle(){ + this.stopComputer(); + Collections.shuffle(this.rods); } } diff --git a/src/main/java/fr/xen0xys/ui/utils/Runner.java b/src/main/java/fr/xen0xys/ui/utils/Runner.java index 35f9e5c..95e0573 100644 --- a/src/main/java/fr/xen0xys/ui/utils/Runner.java +++ b/src/main/java/fr/xen0xys/ui/utils/Runner.java @@ -11,13 +11,13 @@ public class Runner extends Thread{ private final Runnable action; - private final double aps; + private double aps; private final boolean stability; private boolean running = false; private boolean paused = false; private int passedActions = 0; - private final Queue apsQueue; + private Queue apsQueue; private final ReentrantLock queueLock = new ReentrantLock(); /** @@ -36,7 +36,7 @@ public Runner(@NotNull final Runnable action){ */ public Runner(@NotNull final Runnable action, @Range(from = 0, to=10000) final int aps, final boolean stability){ this.action = action; - this.aps = 1D/aps; + this.aps = 1D / aps; this.stability = stability; this.apsQueue = EvictingQueue.create(aps); } @@ -67,7 +67,12 @@ public void run() { } Thread.yield(); } - this.apsQueue.clear(); + this.queueLock.lock(); + try { + this.apsQueue.clear(); + } finally { + this.queueLock.unlock(); + } } public void setPaused(boolean paused) { @@ -76,6 +81,15 @@ public void setPaused(boolean paused) { public void stopRunner(){ this.running = false; } + public void setAps(int aps){ + this.aps = 1D / aps; + this.queueLock.lock(); + try { + this.apsQueue = EvictingQueue.create(aps); + } finally { + this.queueLock.unlock(); + } + } public boolean isRunning() { return running; diff --git a/src/main/java/fr/xen0xys/ui/views/AlgorithmPanel.java b/src/main/java/fr/xen0xys/ui/views/AlgorithmPanel.java new file mode 100644 index 0000000..1a1ce11 --- /dev/null +++ b/src/main/java/fr/xen0xys/ui/views/AlgorithmPanel.java @@ -0,0 +1,66 @@ +package fr.xen0xys.ui.views; + +import fr.xen0xys.models.Rod; +import fr.xen0xys.ui.GameWindow; +import fr.xen0xys.utils.Tuple; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.geom.Rectangle2D; +import java.util.List; + +public class AlgorithmPanel extends JPanel { + + private final GameWindow window; + private final List rods; + + private final Rectangle2D rectangle2D = new Rectangle2D.Double(0, 0, 0, 0); + + public AlgorithmPanel(GameWindow window, List rods) { + this.window = window; + this.rods = rods; + this.initPanel(); + } + + private void initPanel(){ + this.setBackground(Color.BLACK); + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + this.setSize(screenSize.width - 50, screenSize.height - 150); + this.setPreferredSize(this.getSize()); + this.setBorder(new EmptyBorder(0, 0, 0, 0)); + } + + @Override + protected void paintComponent(@NotNull final Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + g2d.setColor(Color.WHITE); + g2d.setFont(GameWindow.font); + Tuple runnersAps = this.window.getRunnersAps(); + g2d.drawString("FPS : %d TPS : %d".formatted(runnersAps.first(), runnersAps.second()), 10, 25); + this.drawRods(g2d, this.rods); + } + + private void drawRods(Graphics2D g, List rods) { + if(rods.isEmpty()) return; + double spaceWidth = 1; + double spaces = rods.size() * spaceWidth; + double rodWidth = (this.getWidth() - spaces) / rods.size(); + double maxRodHeight = this.getHeight() - 50; + for(int i = 0; i < rods.size(); i++){ + Rod rod = rods.get(i); + double x = i * spaceWidth + i * rodWidth; + double height = maxRodHeight * rod.getValue() / rods.size(); + double y = this.getHeight() - height; + g.setColor(rod.getColor()); + rectangle2D.setRect(x, y, rodWidth, height); + g.fill(rectangle2D); + } + } +} diff --git a/src/main/java/fr/xen0xys/ui/views/AlgorithmView.java b/src/main/java/fr/xen0xys/ui/views/AlgorithmView.java deleted file mode 100644 index 644a42d..0000000 --- a/src/main/java/fr/xen0xys/ui/views/AlgorithmView.java +++ /dev/null @@ -1,87 +0,0 @@ -package fr.xen0xys.ui.views; - -import fr.xen0xys.models.Computer; -import fr.xen0xys.models.Rod; -import fr.xen0xys.models.algorithms.AlgorithmType; -import fr.xen0xys.ui.GameWindow; -import fr.xen0xys.ui.utils.Runner; -import org.jetbrains.annotations.NotNull; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import java.awt.*; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class AlgorithmView extends JPanel { - - private final GameWindow window; - private final List rods; - private final Runner graphicRunner; - private final Computer computer; - - private final Font font = new Font("Roboto", Font.PLAIN, 18); - private final Rectangle2D rectangle2D = new Rectangle2D.Double(0, 0, 0, 0); - private final double spaceWidth = 1; - private final double spaces; - - public AlgorithmView(GameWindow window, int rodCount) { - this.window = window; - this.graphicRunner = new Runner(this::repaint, 300, true); - this.initPanel(); - // Init rods - this.rods = new ArrayList<>(); - this.initRods(rodCount); - this.computer = new Computer(AlgorithmType.SELECTION_SORT, this.rods); - // Init graphic values - this.spaces = this.rods.size() * spaceWidth; - // Start runners - this.graphicRunner.start(); - this.computer.start(); - } - - private void initPanel(){ - this.setBackground(Color.BLACK); - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - this.setSize(screenSize.width - 50, screenSize.height - 150); - this.setPreferredSize(this.getSize()); - this.setBorder(new EmptyBorder(0, 0, 0, 0)); - this.window.setLocation(25, 75); - } - - private void initRods(int rodCount){ - for(int i = 0; i < rodCount; i++) - this.rods.add(new Rod(rodCount - i)); - Collections.shuffle(this.rods); - } - - @Override - protected void paintComponent(@NotNull final Graphics g) { - super.paintComponent(g); - Graphics2D g2d = (Graphics2D) g; - g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, - RenderingHints.VALUE_TEXT_ANTIALIAS_ON); - g2d.setColor(Color.WHITE); - g2d.setFont(font); - g2d.drawString("FPS : %d TPS : %d".formatted(this.graphicRunner.getCurrentAps(), this.computer.getRunner().getCurrentAps()), 15, 25); - this.drawRods(g2d); - } - - private void drawRods(Graphics2D g) { - double rodWidth = (this.getWidth() - this.spaces) / this.rods.size(); - double maxRodHeight = this.getHeight() - 50; - for(int i = 0; i < this.rods.size(); i++){ - Rod rod = this.rods.get(i); - double x = i * this.spaceWidth + i * rodWidth; - double height = maxRodHeight * rod.getValue() / this.rods.size(); - double y = this.getHeight() - height; - g.setColor(rod.getColor()); - rectangle2D.setRect(x, y, rodWidth, height); - g.fill(rectangle2D); - } - } -} diff --git a/src/main/java/fr/xen0xys/ui/views/MenuPanel.java b/src/main/java/fr/xen0xys/ui/views/MenuPanel.java new file mode 100644 index 0000000..95b0c41 --- /dev/null +++ b/src/main/java/fr/xen0xys/ui/views/MenuPanel.java @@ -0,0 +1,121 @@ +package fr.xen0xys.ui.views; + +import fr.xen0xys.models.algorithms.AlgorithmType; +import fr.xen0xys.ui.GameWindow; + +import javax.swing.*; +import java.awt.*; + +public class MenuPanel extends JPanel { + + private final GameWindow frame; + + private final JLabel rodCountLabel = new JLabel("Rod count : 25"); + private final JSlider rodCountSlider = new JSlider(10, 1000, 25); + private final JLabel fpsLabel = new JLabel("FPS : 60"); + private final JSlider fpsSlider = new JSlider(10, 1000, 100); + private final JLabel tpsLabel = new JLabel("TPS : 20"); + private final JSlider tpsSlider = new JSlider(1, 10000, 20); + private final JButton stopButton = new JButton("Stop"); + private final JButton shuffleButton = new JButton("Shuffle"); + + public MenuPanel(GameWindow frame) { + this.frame = frame; + this.initPanel(); + this.initComponents(); + } + + private void initComponents() { + this.rodCountLabel.setBounds(10, 10, 150, 30); + this.rodCountLabel.setFont(GameWindow.font); + this.rodCountLabel.setBackground(Color.BLACK); + this.rodCountLabel.setForeground(Color.WHITE); + this.add(this.rodCountLabel); + this.rodCountSlider.setBounds(10, 50, 150, 50); + this.rodCountSlider.setMajorTickSpacing(500); + this.rodCountSlider.setMinorTickSpacing(100); + this.rodCountSlider.setPaintTicks(true); + this.rodCountSlider.setPaintLabels(true); + this.rodCountSlider.setFont(GameWindow.font); + this.rodCountSlider.setBackground(Color.BLACK); + this.rodCountSlider.setForeground(Color.WHITE); + this.rodCountSlider.addChangeListener(e -> { + this.rodCountLabel.setText("Rod count : " + this.rodCountSlider.getValue()); + this.frame.setRodCount(this.rodCountSlider.getValue()); + }); + this.add(this.rodCountSlider); + + this.fpsLabel.setBounds(210, 10, 150, 30); + this.fpsLabel.setFont(GameWindow.font); + this.fpsLabel.setBackground(Color.BLACK); + this.fpsLabel.setForeground(Color.WHITE); + this.add(this.fpsLabel); + this.fpsSlider.setBounds(210, 50, 150, 50); + this.fpsSlider.setMajorTickSpacing(500); + this.fpsSlider.setMinorTickSpacing(100); + this.fpsSlider.setPaintTicks(true); + this.fpsSlider.setPaintLabels(true); + this.fpsSlider.setFont(GameWindow.font); + this.fpsSlider.setBackground(Color.BLACK); + this.fpsSlider.setForeground(Color.WHITE); + this.fpsSlider.addChangeListener(e -> { + this.fpsLabel.setText("FPS : " + this.fpsSlider.getValue()); + this.frame.setFps(this.fpsSlider.getValue()); + }); + this.add(this.fpsSlider); + + this.tpsLabel.setBounds(410, 10, 150, 30); + this.tpsLabel.setFont(GameWindow.font); + this.tpsLabel.setBackground(Color.BLACK); + this.tpsLabel.setForeground(Color.WHITE); + this.add(this.tpsLabel); + this.tpsSlider.setBounds(410, 50, 150, 50); + this.tpsSlider.setMajorTickSpacing(5000); + this.tpsSlider.setMinorTickSpacing(1000); + this.tpsSlider.setPaintTicks(true); + this.tpsSlider.setPaintLabels(true); + this.tpsSlider.setFont(GameWindow.font); + this.tpsSlider.setBackground(Color.BLACK); + this.tpsSlider.setForeground(Color.WHITE); + this.tpsSlider.addChangeListener(e -> { + this.tpsLabel.setText("TPS : " + this.tpsSlider.getValue()); + this.frame.setTps(this.tpsSlider.getValue()); + }); + this.add(this.tpsSlider); + + this.stopButton.setBounds(610, 50, 150, 30); + this.stopButton.setFont(GameWindow.font); + this.stopButton.setBackground(Color.BLACK); + this.stopButton.setForeground(Color.WHITE); + this.stopButton.addActionListener(e -> { + this.frame.stopComputer(); + }); + this.add(this.stopButton); + + this.shuffleButton.setBounds(810, 50, 150, 30); + this.shuffleButton.setFont(GameWindow.font); + this.shuffleButton.setBackground(Color.BLACK); + this.shuffleButton.setForeground(Color.WHITE); + this.shuffleButton.addActionListener(e -> { + this.frame.shuffle(); + }); + this.add(this.shuffleButton); + + for(AlgorithmType algorithmType : AlgorithmType.values()){ + JButton button = new JButton(algorithmType.getDisplayName()); + button.setBounds(610 + (algorithmType.ordinal() * 200), 10, 150, 30); + button.setFont(GameWindow.font); + button.setBackground(Color.BLACK); + button.setForeground(Color.WHITE); + button.addActionListener(e -> { + this.frame.startComputer(algorithmType, this.tpsSlider.getValue()); + }); + this.add(button); + } + } + + private void initPanel(){ + this.setBackground(Color.BLACK); + this.setLayout(null); + } +} diff --git a/src/main/java/fr/xen0xys/ui/views/MenuView.java b/src/main/java/fr/xen0xys/ui/views/MenuView.java deleted file mode 100644 index 423c4ec..0000000 --- a/src/main/java/fr/xen0xys/ui/views/MenuView.java +++ /dev/null @@ -1,32 +0,0 @@ -package fr.xen0xys.ui.views; - -import fr.xen0xys.ui.GameWindow; - -import javax.swing.*; - -public class MenuView extends JPanel { - - private final GameWindow frame; - - private final JButton startButton = new JButton("Start"); - - public MenuView(GameWindow frame) { - this.frame = frame; - this.initPanel(); - this.initComponents(); - } - - private void initComponents() { - this.startButton.setBounds(50, 250, 400, 50); - this.startButton.addActionListener(e -> { - this.frame.loadView(new AlgorithmView(this.frame, 25)); - }); - this.add(this.startButton); - } - - private void initPanel(){ - this.setLayout(null); - this.setSize(500, 500); - this.setPreferredSize(this.getSize()); - } -} diff --git a/src/main/java/fr/xen0xys/utils/Tuple.java b/src/main/java/fr/xen0xys/utils/Tuple.java new file mode 100644 index 0000000..9e6e117 --- /dev/null +++ b/src/main/java/fr/xen0xys/utils/Tuple.java @@ -0,0 +1,4 @@ +package fr.xen0xys.utils; + +public record Tuple(A first, B second) { +}