diff --git a/src/Launcher.java b/src/Launcher.java index b3463c3..8a89905 100644 --- a/src/Launcher.java +++ b/src/Launcher.java @@ -6,6 +6,20 @@ public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { + try { + for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { + if ("Windows".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + if ("Mac OS X".equals(info.getName())) { + UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (Exception e) { + // Style not available + } MainWindow main = new MainWindow(); main.show(); } diff --git a/src/MainWindow.java b/src/MainWindow.java index 48fea17..1284887 100644 --- a/src/MainWindow.java +++ b/src/MainWindow.java @@ -1,17 +1,73 @@ import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class MainWindow implements ActionListener, ChangeListener { + private final JFrame window; + private MenuPanel menuPanel; + private SortingPanel sortingPanel; + public static final int MAX = 48; + public static final int MIN = 2; -public class MainWindow { - private JFrame window; public MainWindow() { window = new JFrame(); - window.setTitle("Tetris"); + window.setLayout(new BorderLayout(10, 5)); + window.setTitle("Sorting Visualiser"); window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - window.setSize(800, 500); + window.setSize(1200, 800); window.setLocationRelativeTo(null); + + createMenuButtons(); + createVisualiserPanel(); + } + + private void createMenuButtons() { + menuPanel = new MenuPanel(); + menuPanel.addActionListeners(this); + menuPanel.addChangeListeners(this); + window.add(menuPanel.getNorthPanel(), BorderLayout.NORTH); + window.add(menuPanel.getSouthPanel(), BorderLayout.SOUTH); + } + + private void createVisualiserPanel() { + sortingPanel = new SortingPanel(); + window.add(sortingPanel, BorderLayout.CENTER); + } + + public JFrame getWindow() { + return window; } public void show() { window.setVisible(true); } + + @Override + public void actionPerformed(ActionEvent e) { + System.out.println(e); + if (e.getSource() instanceof JButton) { + if (e.getActionCommand().equals("generate")) { + sortingPanel.generateArray(); + } else if (e.getActionCommand().equals("start")) { + sortingPanel.start(menuPanel.getSortingMethod()); + } + } + } + + @Override + public void stateChanged(ChangeEvent e) { + if (e.getSource() instanceof JSlider) { + if (((JSlider)e.getSource()).getName().equals("length")) { + sortingPanel.generateArray(((JSlider) e.getSource()).getValue()); + System.out.println(((JSlider) e.getSource()).getValue() + ":" + e); + } else if (((JSlider)e.getSource()).getName().equals("speed")) { + sortingPanel.setSpeed(((JSlider) e.getSource()).getValue()); + System.out.println(((JSlider) e.getSource()).getValue() + ":" + e); + } + } + } } diff --git a/src/MenuPanel.java b/src/MenuPanel.java new file mode 100644 index 0000000..c6dedac --- /dev/null +++ b/src/MenuPanel.java @@ -0,0 +1,128 @@ +import javax.swing.*; +import javax.swing.event.ChangeListener; +import java.awt.*; +import java.awt.event.ActionListener; +import java.net.URL; + +public class MenuPanel { + private JPanel northPanel; + private JPanel southPanel; + private JButton generate; + private JSlider length; + private JLabel lengthLabel; + private JComboBox sortingMethod; + private JButton start; + private JButton forward; + private JButton backward; + private JSlider speed; + private JLabel speedLabel; + + private static final String[] sorts = new String[]{"Selection Sort"}; + + public MenuPanel() { + createNorthPanel(); + createSouthPanel(); + } + + private void createNorthPanel() { + northPanel = new JPanel(); + northPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); + + URL generateURL = getClass().getResource("assets/random.png"); + if (generateURL != null) { + ImageIcon generateIcon = new ImageIcon(generateURL); + generateIcon = new ImageIcon(generateIcon.getImage().getScaledInstance(20, 20, Image.SCALE_DEFAULT)); + generate = new JButton("New Array", generateIcon); + } else { + generate = new JButton("New Array"); + } + northPanel.add(generate); + + length = new JSlider(MainWindow.MIN, MainWindow.MAX); + northPanel.add(length); + length.setValue(MainWindow.MAX / 2); + + lengthLabel = new JLabel(); + northPanel.add(lengthLabel); + lengthLabel.setText("Length:" + length.getValue()); + + sortingMethod = new JComboBox<>(sorts); + northPanel.add(sortingMethod); + } + + private void createSouthPanel() { + southPanel = new JPanel(); + southPanel.setLayout(new FlowLayout(FlowLayout.CENTER)); + + URL backURL = getClass().getResource("assets/backward.png"); + if (backURL != null) { + ImageIcon backIcon = new ImageIcon(backURL); + backIcon = new ImageIcon(backIcon.getImage().getScaledInstance(20, 20, Image.SCALE_DEFAULT)); + backward = new JButton(backIcon); + } else { + backward = new JButton("Step Backward"); + } + southPanel.add(backward); + + URL startURL = getClass().getResource("assets/start.png"); + if (startURL != null) { + ImageIcon startIcon = new ImageIcon(startURL); + startIcon = new ImageIcon(startIcon.getImage().getScaledInstance(20, 20, Image.SCALE_DEFAULT)); + start = new JButton(startIcon); + } else { + start = new JButton("Start"); + } + southPanel.add(start); + + URL forwardURL = getClass().getResource("assets/forward.png"); + if (forwardURL != null) { + ImageIcon forwardIcon = new ImageIcon(forwardURL); + forwardIcon = new ImageIcon(forwardIcon.getImage().getScaledInstance(20, 20, Image.SCALE_DEFAULT)); + forward = new JButton(forwardIcon); + } else { + forward = new JButton("Step Forward"); + } + southPanel.add(forward); + + speed = new JSlider(1, 10); + southPanel.add(speed); + + speedLabel = new JLabel(); + southPanel.add(speedLabel); + speedLabel.setText("Speed:" + speed.getValue()); + } + + public JPanel getNorthPanel() { + return northPanel; + } + + public JPanel getSouthPanel() { + return southPanel; + } + + public String getSortingMethod() { + return (String) sortingMethod.getSelectedItem(); + } + + public void addChangeListeners(ChangeListener changeListener) { + length.setName("length"); + length.addChangeListener(e -> lengthLabel.setText("Length:" + length.getValue())); + length.addChangeListener(changeListener); + + speed.setName("speed"); + speed.addChangeListener(e -> speedLabel.setText("Speed:" + speed.getValue())); + speed.addChangeListener(changeListener); + } + + public void addActionListeners(ActionListener actionListener) { + generate.setActionCommand("generate"); + generate.addActionListener(actionListener); + + start.setActionCommand("start"); + start.addActionListener(actionListener); + backward.setActionCommand("backward"); + backward.addActionListener(actionListener); + forward.setActionCommand("forward"); + forward.addActionListener(actionListener); + } +} diff --git a/src/SortingPanel.java b/src/SortingPanel.java new file mode 100644 index 0000000..4a781a9 --- /dev/null +++ b/src/SortingPanel.java @@ -0,0 +1,100 @@ +import javax.swing.*; +import java.awt.*; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.stream.IntStream; + +public class SortingPanel extends JComponent { + + private Map map; + private int[] array; + private final Random r; + private int size; + private int speed; + + private static final Color CURRENT = Color.ORANGE; + private static final Color SORTED = Color.GREEN; + private static final Color MIN = Color.RED; + + public SortingPanel() { + r = new Random(System.nanoTime()); + size = MainWindow.MAX / 2; + generateArray(); + map = new HashMap<>(size); + speed = 5; + } + + public void paint(Graphics g) { + int y = getHeight() -20; + int scalar = getHeight() / MainWindow.MAX - 1; + int width = Math.min(getWidth() / size, 100); + g.setFont(new Font("Monospaced", Font.PLAIN, 16)); + for (int i = 0; i < size; i++) { + g.setColor(Color.BLACK); + g.drawString(String.valueOf(array[i]), (int) i * width + width / 4, y + 15); + g.setColor(map.get(i)); + g.fillRect(i * width, y, width - 2, -scalar * array[i]); + } + } + + public void generateArray(int size) { + this.size = size; + IntStream ints = r.ints(size, 1, MainWindow.MAX); + array = ints.toArray(); + map = new HashMap<>(size); + System.out.println(Arrays.toString(array)); + this.repaint(); + } + + public void generateArray() { + generateArray(this.size); + } + + public void start(String sortingMethod) { + System.out.println(this.getClass().getSimpleName() + "." + sortingMethod.replace(" ", "") + "()"); + selectionSort(); + } + + public void setSpeed(int speed) { + this.speed = speed; + } + + public void updateScreen() { + paintImmediately(0, 0, getWidth(), getHeight()); + try { + Thread.sleep((long) (2048 * Math.pow(2, -speed))); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void selectionSort() { + map.clear(); + int numSorted = 0; + for (int n = 0; n < size; n++) { + int minIndex = n; + map.put(minIndex, MIN); + for (int i = numSorted; i < size; i++) { + if (array[i] < array[minIndex]) { + map.remove(minIndex); + map.put(i, MIN); + minIndex = i; + } else { + map.put(i, CURRENT); + } + updateScreen(); + if (map.get(i) == CURRENT) map.remove(i); + } + int temp = array[numSorted]; + array[numSorted] = array[minIndex]; + array[minIndex] = temp; + map.remove(minIndex); + map.put(numSorted, SORTED); + numSorted++; + repaint(); + } + } +} diff --git a/src/assets/backward.png b/src/assets/backward.png new file mode 100644 index 0000000..fd580bb Binary files /dev/null and b/src/assets/backward.png differ diff --git a/src/assets/forward.png b/src/assets/forward.png new file mode 100644 index 0000000..021d173 Binary files /dev/null and b/src/assets/forward.png differ diff --git a/src/assets/random.png b/src/assets/random.png new file mode 100644 index 0000000..49d2483 Binary files /dev/null and b/src/assets/random.png differ diff --git a/src/assets/refresh.png b/src/assets/refresh.png new file mode 100644 index 0000000..8267e76 Binary files /dev/null and b/src/assets/refresh.png differ diff --git a/src/assets/start.png b/src/assets/start.png new file mode 100644 index 0000000..7019bf1 Binary files /dev/null and b/src/assets/start.png differ