Make a line of Swing components of variable length inside another line - swing

I'm trying to make a web inspector interface in Swing.
I managed to make the tags render correctly, but I have a problem with attributes.
I have a JPanel with FlowLayout, there are some JLabels inside it, and a JPanel in the middle, which has FlowLayout set on it too. That JPanel can contain any number of inner JPanels for attributes, each of them has 2 TextFields and 3 JLabels, inputs have zero width by default and are not visible, JLabels can have arbitrary length.
The problem is I cannot make my lines have the correct length. I add my attributes JPanels after the main container is laid out and made visible, maybe that is the problem. I also tried setting all the widths manually in the code (and increase the width of my top-level line too), but the output is still far from correct. My elements are either not visible except the first child JPanel (but I have a lot of empty space on the right).
I tried calling validate() and doLayout(), and I don't see any difference.
What is the right way to do this layout? First add everything, and then call setVisible() on my JFrame? Or it's not necessary?
Also, how can I make sure that the elements will always stay in one line?
I tried BoxLayout too, but that's not suitable, as it gives all the available parent width to my attributes panel (and I need it have zero pixel width when it is empty). If I add glue to the end, then free space is distributed equally between the glue and my JPanel.
public class WebInspectorTest {
private static Node prepareTree() {
Node root = new Node(1);
root.tagName = "body";
root.attributes.put("onload", "init()");
Node p = new Node(root, 1);
p.tagName = "p";
Node text1 = new Node(p, 3);
text1.nodeValue = "This is a ";
Node i = new Node(p, 1);
i.tagName = "i";
i.attributes.put("style", "background-color: rgb(223, 216, 45); color: #8848a7; cursor: pointer");
Node text2 = new Node(i, 3);
text2.nodeValue = "paragraph";
Node text3 = new Node(p, 3);
text3.nodeValue = ".";
Node img = new Node(p, 1);
img.tagName = "img";
img.attributes.put("src", "smiley.gif");
return root;
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {}
final Node root = prepareTree();
if (root == null) return;
final JFrame frame = new JFrame("Document Inspector");
JPanel cp = new JPanel();
cp.setBorder(BorderFactory.createEmptyBorder(9, 10, 9, 10));
frame.setContentPane(cp);
cp.setLayout(new BorderLayout());
final JPanel contentpane = new JPanel();
contentpane.setBackground(Color.WHITE);
contentpane.setOpaque(true);
//contentpane.setBounds(0, 0, 490, 380);
final int width = 490, height = 418;
final JScrollPane scrollpane = new JScrollPane(contentpane);
scrollpane.setOpaque(false);
scrollpane.getInsets();
cp.add(scrollpane);
scrollpane.setBackground(Color.WHITE);
scrollpane.setOpaque(true);
scrollpane.setPreferredSize(new Dimension(width, height));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
TagLibrary.init();
final Entry rootEntry = new Entry(root);
contentpane.add(rootEntry);
final JScrollPane sp = scrollpane;
int width = sp.getVerticalScrollBar().isVisible() ? sp.getWidth() - sp.getVerticalScrollBar().getPreferredSize().width - 12 : sp.getWidth() + sp.getVerticalScrollBar().getPreferredSize().width;
rootEntry.inflate(width);
contentpane.addComponentListener(new java.awt.event.ComponentAdapter() {
#Override
public void componentMoved(java.awt.event.ComponentEvent evt) {}
#Override
public void componentResized(java.awt.event.ComponentEvent evt) {
int width = sp.getVerticalScrollBar().isVisible() ? sp.getWidth() - sp.getVerticalScrollBar().getPreferredSize().width - 12 : sp.getWidth() - 12;
rootEntry.setWidth(width);
}
});
}
});
}
}
public class Entry extends javax.swing.JPanel {
public Entry() {
initComponents();
}
public Entry(Node node) {
this.node = node;
initComponents();
initEvents();
}
private void addAttributes() {
final Entry entry = this;
Set<String> keys = node.attributes.keySet();
attributes.setPreferredSize(new Dimension(0, line_height));
int index = 0;
for (String key: keys) {
Attribute attr = new Attribute(this, key, node.attributes.get(key));
attributes.add(attr);
Dimension dim = attributes.getPreferredSize();
attributes.setPreferredSize(new Dimension(dim.width + attr.getWidth(), dim.height));
}
int max_width = Math.max(attributes.getPreferredSize().width + headerTag.getWidth() + headerTag2.getWidth() + 38, getPreferredSize().width);
header.setMinimumSize(new Dimension(max_width, line_height));
footer.setMinimumSize(new Dimension(max_width, line_height));
setMinimumSize(new Dimension(max_width, line_height));
}
private void initEvents() {
marker.addMouseListener(new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {
if (!opened) open();
else close();
}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
});
addMouseListener(listener);
}
public void addChild(Entry child, int pos) {
content.add(child, pos);
//content.validate();
}
public void inflate(int width) {
if (node == null) return;
if (node.nodeType == 1) {
boolean isPaired = !TagLibrary.tags.containsKey(node.tagName.toLowerCase()) ||
TagLibrary.tags.get(node.tagName.toLowerCase());
if (!isPaired) {
headerTag.setText("<" + node.tagName.toLowerCase());
headerTag2.setText(" />");
threeDots.setText("");
headerTag3.setText("");
content.setVisible(false);
footer.setVisible(false);
marker.setVisible(false);
} else {
headerTag.setText("<" + node.tagName.toLowerCase());
headerTag2.setText(">");
headerTag3.setText("</" + node.tagName.toLowerCase() + ">");
footerTag.setText("</" + node.tagName.toLowerCase() + ">");
}
addAttributes();
int w = Math.max(Math.max(header.getMinimumSize().width, min_width), width - margin);
content.removeAll();
//System.out.println(getWidth());
for (int i = 0; i < node.children.size(); i++) {
Entry e = new Entry(node.children.get(i));
content.add(e);
e.inflate(w);
//content.setSize(e.getSize());
}
content.doLayout();
if (node.children.size() > 0) {
open();
} else {
close();
}
} else if (node.nodeType == 3 && !node.nodeValue.matches("\\s*")) {
content.removeAll();
header.setVisible(false);
footer.setVisible(false);
JTextArea textarea = new JTextArea();
textarea.setText(node.nodeValue);
textarea.setEditable(false);
textarea.setOpaque(false);
textarea.setBackground(new Color(255, 255, 255, 0));
textarea.setColumns(180);
textarea.setFont(new Font("Tahoma", Font.PLAIN, 16));
int rows = node.nodeValue.split("\n").length;
textarea.setRows(rows);
textarea.addMouseListener(listener);
int height = getFontMetrics(textarea.getFont()).getHeight() * rows;
content.add(textarea);
content.setOpaque(false);
int w = Math.max(Math.max(header.getMinimumSize().width, min_width), width - margin);
header.setMinimumSize(new Dimension(w, line_height));
footer.setMinimumSize(new Dimension(w, line_height));
content.setPreferredSize(new Dimension(w, content.getPreferredSize().height));
((JPanel)getParent()).setMinimumSize(new Dimension(w, line_height * 2 + content.getPreferredSize().height));
opened = true;
content.validate();
} else {
setVisible(false);
content.removeAll();
opened = false;
return;
}
int w = Math.max(Math.max(Math.max(content.getMinimumSize().width, header.getMinimumSize().width), min_width), width - margin);
header.setMinimumSize(new Dimension(w, line_height));
footer.setMinimumSize(new Dimension(w, line_height));
content.setPreferredSize(new Dimension(w, content.getPreferredSize().height));
int height = line_height * 2 + content.getPreferredSize().height;
if (opened) {
setSize(w, height);
}
updateWidth(w);
}
public void setWidth(int width) {
int w = Math.max(Math.max(header.getMinimumSize().width, min_width), width - margin);
setPreferredSize(new Dimension(w, getPreferredSize().height));
header.setMinimumSize(new Dimension(w, line_height));
footer.setMinimumSize(new Dimension(w, line_height));
content.setPreferredSize(new Dimension(w, content.getPreferredSize().height));
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry)c[i]).setWidth(w);
} else {
c[i].setSize(w, c[i].getMaximumSize().height);
c[i].setMaximumSize(new Dimension(w, c[i].getMaximumSize().height));
}
}
}
private void updateWidth(int width) {
int w = Math.max(Math.max(header.getMinimumSize().width, min_width), width);
if (content.getPreferredSize().width > w) {
w = content.getPreferredSize().width;
}
if (getSize().width > w) return;
setPreferredSize(new Dimension(w, getPreferredSize().height));
Entry last = this;
Component c = getParent();
while (c != null && c.getParent() != null && c.getParent() instanceof Entry) {
//c.setPreferredSize(new Dimension(w, c.getPreferredSize().height));
Component[] children = ((JPanel)c).getComponents();
for (int i = 0; i < children.length; i++) {
if (children[i] instanceof Entry && children[i] != last && children[i].getSize().width < w) {
((Entry)children[i]).setWidth(w);
} else if (!(children[i] instanceof Entry) && children[i].getParent() != last && children[i].getSize().width < w) {
children[i].setSize(w, children[i].getMaximumSize().height);
children[i].setMaximumSize(new Dimension(w, children[i].getMaximumSize().height));
}
}
w += margin;
w = Math.max(c.getParent().getSize().width, w);
if (((Entry)c.getParent()).node.tagName.equals("body")) System.err.println("Root entry width: " + w);
int h = line_height * 2 + ((Entry)c.getParent()).content.getPreferredSize().height;
c.getParent().setSize(w, h);
((Entry)c.getParent()).header.setMinimumSize(new Dimension(w, line_height));
((Entry)c.getParent()).footer.setMinimumSize(new Dimension(w, line_height));
//((Entry)c.getParent()).content.setPreferredSize(new Dimension(w, ((Entry)c.getParent()).getSize().height - line_height * 2));
last = (Entry)c.getParent();
c = c.getParent().getParent();
}
if (c != null) c.validate();
content.validate();
}
ActionListener callback;
public static final int min_width = 280;
public static final int line_height = 26;
public static final int margin = 30;
MouseListener listener = new MouseListener() {
#Override
public void mouseClicked(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {
//System.out.println("Entered");
updateChildren(true);
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
//System.out.println("Exited");
updateChildren(false);
repaint();
}
};
private void updateChildren(boolean value) {
hovered = value;
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry)c[i]).updateChildren(value);
}
}
}
#Override
public void paintComponent(Graphics g) {
if (hovered) {
g.clearRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(190, 230, 255, 93));
g.fillRect(0, 0, getWidth(), getHeight());
} else {
g.clearRect(0, 0, getWidth(), getHeight());
g.setColor(new Color(255, 255, 255));
g.fillRect(0, 0, getWidth(), getHeight());
}
//super.paintComponent(g);
}
private boolean hovered = false;
public void open() {
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle.png")));
threeDots.setVisible(false);
headerTag3.setVisible(false);
content.setVisible(true);
footer.setVisible(true);
opened = true;
}
public void close() {
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle2.png")));
content.setVisible(false);
footer.setVisible(false);
boolean has_children = node.children.size() > 0;
threeDots.setVisible(has_children);
marker.setVisible(has_children);
headerTag3.setVisible(true);
opened = false;
}
public void openAll() {
open();
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry) c[i]).openAll();
}
}
}
public void closeAll() {
close();
Component[] c = content.getComponents();
for (int i = 0; i < c.length; i++) {
if (c[i] instanceof Entry) {
((Entry) c[i]).closeAll();
}
}
}
public boolean opened = false;
public Node node;
#SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
header = new javax.swing.JPanel();
headerMargin = new javax.swing.JPanel();
marker = new javax.swing.JLabel();
headerTag = new javax.swing.JLabel();
attributes = new javax.swing.JPanel();
headerTag2 = new javax.swing.JLabel();
threeDots = new javax.swing.JLabel();
headerTag3 = new javax.swing.JLabel();
content = new javax.swing.JPanel();
footer = new javax.swing.JPanel();
footerMargin = new javax.swing.JPanel();
footerTag = new javax.swing.JLabel();
setBackground(new java.awt.Color(255, 255, 255));
setLayout(new javax.swing.BoxLayout(this, javax.swing.BoxLayout.PAGE_AXIS));
header.setBackground(new java.awt.Color(255, 255, 255));
header.setAlignmentX(0.0F);
header.setMaximumSize(new java.awt.Dimension(32767, 26));
header.setMinimumSize(new java.awt.Dimension(280, 26));
header.setOpaque(false);
header.setPreferredSize(new java.awt.Dimension(280, 26));
header.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEADING, 0, 2));
headerMargin.setBorder(javax.swing.BorderFactory.createEmptyBorder(2, 0, 0, 5));
headerMargin.setMaximumSize(new java.awt.Dimension(30, 26));
headerMargin.setOpaque(false);
headerMargin.setPreferredSize(new java.awt.Dimension(30, 26));
marker.setHorizontalAlignment(javax.swing.SwingConstants.TRAILING);
marker.setIcon(new javax.swing.ImageIcon(getClass().getResource("/resources/triangle.png"))); // NOI18N
marker.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
marker.setPreferredSize(new java.awt.Dimension(22, 22));
javax.swing.GroupLayout headerMarginLayout = new javax.swing.GroupLayout(headerMargin);
headerMargin.setLayout(headerMarginLayout);
headerMarginLayout.setHorizontalGroup(
headerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(headerMarginLayout.createSequentialGroup()
.addComponent(marker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
headerMarginLayout.setVerticalGroup(
headerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(headerMarginLayout.createSequentialGroup()
.addComponent(marker, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
header.add(headerMargin);
headerTag.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
headerTag.setForeground(new java.awt.Color(102, 0, 153));
headerTag.setText("<body");
header.add(headerTag);
attributes.setMaximumSize(new java.awt.Dimension(32767, 26));
attributes.setOpaque(false);
attributes.setPreferredSize(new java.awt.Dimension(0, 26));
attributes.setLayout(new javax.swing.BoxLayout(attributes, javax.swing.BoxLayout.LINE_AXIS));
header.add(attributes);
headerTag2.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
headerTag2.setForeground(new java.awt.Color(102, 0, 153));
headerTag2.setText(">");
header.add(headerTag2);
threeDots.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
threeDots.setText("...");
threeDots.setPreferredSize(new java.awt.Dimension(19, 20));
header.add(threeDots);
headerTag3.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
headerTag3.setForeground(new java.awt.Color(102, 0, 153));
headerTag3.setText("</body>");
header.add(headerTag3);
add(header);
content.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 30, 0, 0));
content.setAlignmentX(0.0F);
content.setOpaque(false);
content.setLayout(new javax.swing.BoxLayout(content, javax.swing.BoxLayout.PAGE_AXIS));
add(content);
footer.setBackground(new java.awt.Color(255, 255, 255));
footer.setAlignmentX(0.0F);
footer.setMaximumSize(new java.awt.Dimension(32767, 26));
footer.setOpaque(false);
footer.setPreferredSize(new java.awt.Dimension(91, 26));
footer.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEADING, 0, 2));
footerMargin.setOpaque(false);
footerMargin.setPreferredSize(new java.awt.Dimension(30, 26));
javax.swing.GroupLayout footerMarginLayout = new javax.swing.GroupLayout(footerMargin);
footerMargin.setLayout(footerMarginLayout);
footerMarginLayout.setHorizontalGroup(
footerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 30, Short.MAX_VALUE)
);
footerMarginLayout.setVerticalGroup(
footerMarginLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 26, Short.MAX_VALUE)
);
footer.add(footerMargin);
footerTag.setFont(new java.awt.Font("Arial", 1, 16)); // NOI18N
footerTag.setForeground(new java.awt.Color(102, 0, 153));
footerTag.setText("</body>");
footer.add(footerTag);
add(footer);
}// </editor-fold>
// Variables declaration - do not modify
private javax.swing.JPanel attributes;
private javax.swing.JPanel content;
private javax.swing.JPanel footer;
private javax.swing.JPanel footerMargin;
private javax.swing.JLabel footerTag;
private javax.swing.JPanel header;
private javax.swing.JPanel headerMargin;
private javax.swing.JLabel headerTag;
private javax.swing.JLabel headerTag2;
private javax.swing.JLabel headerTag3;
private javax.swing.JLabel marker;
private javax.swing.JLabel threeDots;
// End of variables declaration
}
public class Attribute extends javax.swing.JPanel {
public Attribute(Entry entry, String name, String value) {
initComponents();
this.entry = entry;
name_field.setText(name);
if (value == null || value.isEmpty()) {
value_field.setText("");
eq.setVisible(false);
quote1.setVisible(false);
quote2.setVisible(false);
value_field.setVisible(false);
} else {
value_field.setText(value);
}
//setOpaque(true);
//setBackground(Color.RED);
setSize(name_field.getPreferredSize().width + value_field.getPreferredSize().width + 36, Entry.line_height);
originalName = name;
}
public String getEditorText() {
return full_editor.getText();
}
public String getNameField() {
return name_field.getText();
}
public String getValueField() {
return value_field.getText();
}
public String getOriginalName() {
return originalName;
}
#SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
full_editor = new javax.swing.JTextField();
name_field = new javax.swing.JLabel();
eq = new javax.swing.JLabel();
quote1 = new javax.swing.JLabel();
value_editor = new javax.swing.JTextField();
value_field = new javax.swing.JLabel();
quote2 = new javax.swing.JLabel();
setMaximumSize(new java.awt.Dimension(32767, 24));
setOpaque(false);
setPreferredSize(new java.awt.Dimension(400, 22));
setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT, 0, 0));
full_editor.setBorder(null);
full_editor.setMinimumSize(new java.awt.Dimension(0, 22));
full_editor.setOpaque(false);
full_editor.setPreferredSize(new java.awt.Dimension(6, 22));
add(full_editor);
name_field.setForeground(new java.awt.Color(102, 0, 102));
name_field.setText("class");
name_field.setBorder(javax.swing.BorderFactory.createEmptyBorder(0, 2, 0, 0));
add(name_field);
eq.setForeground(new java.awt.Color(102, 0, 102));
eq.setText("=");
add(eq);
quote1.setForeground(new java.awt.Color(153, 153, 153));
quote1.setText("\"");
add(quote1);
value_editor.setBorder(null);
value_editor.setMinimumSize(new java.awt.Dimension(0, 22));
value_editor.setOpaque(false);
value_editor.setPreferredSize(new java.awt.Dimension(0, 22));
add(value_editor);
value_field.setForeground(new java.awt.Color(0, 51, 204));
value_field.setText("link");
add(value_field);
quote2.setForeground(new java.awt.Color(153, 153, 153));
quote2.setText("\"");
add(quote2);
}// </editor-fold>
private final int full_editor_width = 8;
private boolean is_new = false;
private String originalName;
private Entry entry;
private ActionListener listener;
// Variables declaration - do not modify
private javax.swing.JLabel eq;
private javax.swing.JTextField full_editor;
private javax.swing.JLabel name_field;
private javax.swing.JLabel quote1;
private javax.swing.JLabel quote2;
private javax.swing.JTextField value_editor;
private javax.swing.JLabel value_field;
// End of variables declaration
}
public class Node {
public Node() {}
public Node(Node parent_node) {
if (parent_node.nodeType == 1) {
parent = parent_node;
parent_node.addChild(this);
}
}
public Node(int node_type) {
nodeType = node_type;
}
public Node(Node parent_node, int node_type) {
if (parent_node.nodeType == 1) {
parent = parent_node;
parent_node.addChild(this);
}
nodeType = node_type;
}
public boolean addChild(Node node) {
if (nodeType == 1) {
children.add(node);
return true;
}
return false;
}
public Node parent;
public Vector<Node> children = new Vector<Node>();
public LinkedHashMap<String, String> attributes = new LinkedHashMap<String, String>();
public Node previousSibling;
public Node nextSibling;
public String tagName = "";
public int nodeType = 3;
public String nodeValue = "";
}
public class TagLibrary {
public static void init() {
if (init) return;
tags.put("br", false);
tags.put("hr", false);
tags.put("link", false);
tags.put("img", false);
tags.put("a", true);
tags.put("span", true);
tags.put("div", true);
tags.put("p", true);
tags.put("sub", true);
tags.put("sup", true);
tags.put("b", true);
tags.put("i", true);
tags.put("u", true);
tags.put("s", true);
tags.put("strong", true);
tags.put("em", true);
tags.put("quote", true);
tags.put("cite", true);
tags.put("table", true);
tags.put("thead", true);
tags.put("tbody", true);
tags.put("cite", true);
tags.put("head", true);
tags.put("body", true);
leaves.add("style");
leaves.add("script");
init = true;
}
private static boolean init = false;
public static Hashtable<String, Boolean> tags = new Hashtable<String, Boolean>();
public static Vector<String> leaves = new Vector<String>();
}
Here is the screeshot of the GUI:
Output
And here is the same when I make a viewport wider by dragging:
Output
This is close to what I need, despite the fact that the whole attributes are hidden when the parent line container is way too narrow.

Related

Why Doesn't My Timer Delay My Code? I am attemping to delay my graph from sorting to create my iteration of a sorting visualizer

I am trying to create a timer that will delay my GUI from sorting the bars representing random integers. This will make the sorting visible and in a style similar to those seen on YouTube. I read online that Swing Timers are recommended for GUI's so I went with one. If you can't already tell, this is my second project so if my code looks bad, please refrain from sending me death threats.
Anyways, when running my code and pressing the button that sorts the graph, the graph is immediately sorted despite having a timer programmed to delay frame.repaint() calls each time. Again, please go easy on me. This is my second time asking questions on StackOverflow, and I've seen vicious lashes at people who forgot a semicolon. Let me know if I programmed my Swing timer wrong, or if it's something else entirely
Thank you
Main Program
import java.util.Random;
import java.util.Scanner;
public class SortVisualizerShell {
public static int[] unsortedArray = arrayGenerator();
public static String requestedSort;
public static int[] arrayGenerator() {
int $N = 500;
int[] array = new int[$N];
Random rand = new Random();
for (int i = 0; i < $N; i++) {
int random_num = rand.nextInt($N);
array[i] = random_num;
}
return array;
}
public static void selectionSort(int[] array, int n) {
for (int i = 0; i < (n - 1); i++) {
int min = i;
for (int j = (i + 1); j < n; j++) {
if (array[min] > array[j]) {
min = j;
}
}
int key = array[min];
while (min > i) {
array[min] = array[min - 1];
min = min - 1;
}
array[i] = key;
}
}
public static void bubbleSort(int[] array, int n) {
boolean swapped;
int i, j, temp;
for (i = 0; i < (n - 1); i++) {
swapped = false;
for (j = 0; j < ((n - 1) - 1); j++) {
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
swapped = true;
}
}
if (swapped == false) {
break;
}
}
}
public static void insertionSort(int[] array) {
int n = array.length;
for (int i = 1; i < n; i++) {
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j = j - 1;
}
array[j + 1] = key;
}
}
public static void quickSort(int[] array, int left, int right) {
if (left < right) {
int pivot = partition(array, left, right);
quickSort(array, left, pivot - 1);
quickSort(array, pivot + 1, right);
}
}
public static int partition(int[] array, int low, int high) {
int pivot = array[high];
int i = (low - 1);
for (int j = low; j <= high - 1; j++) {
if (array[j] < pivot) {
i++;
swap(array, i, j);
}
}
swap(array, i + 1, high);
return (i + 1);
}
public static void swap(int[] array, int i, int j) {
int temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void mergeSort(int[] array, int len) {
if (len < 2) {
return;
}
int middle = len / 2;
int[] left_arr = new int[middle];
int[] right_arr = new int[len - middle];
int k = 0;
for (int i = 0; i < len; i++) {
if (i < middle) {
left_arr[i] = array[i];
}
else {
right_arr[k] = array[i];
k = k + 1;
}
}
mergeSort(left_arr, middle);
mergeSort(right_arr, len - middle);
merge(left_arr, right_arr, array, middle, (len - middle));
}
public static void merge(int[] left_arr, int[] right_arr, int[] array, int left_side,
int right_side) {
int i = 0;
int left = 0;
int right = 0;
while (left < left_side && right < right_side) {
if (left_arr[left] < right_arr[right]) {
array[i++] = left_arr[left++];
}
else {
array[i++] = right_arr[right++];
}
}
while (left < left_side) {
array[i++] = left_arr[left++];
}
while (right < right_side) {
array[i++] = right_arr[right++];
}
}
public static void userInputFrame(String requestedSort, int[] array) {
if (requestedSort.equals("merge sort")) {
mergeSort(array, array.length);
}
else if (requestedSort.equals("quick sort")) {
quickSort(array, 0, array.length - 1);
}
else if (requestedSort.equals("insertion sort")) {
insertionSort(array);
}
else if (requestedSort.equals("bubble sort")) {
bubbleSort(array, array.length);
}
else if (requestedSort.equals("selection sort")) {
selectionSort(array, array.length);
}
}
public static void activateSort(String requestedSort, int[] unsortedArray) {
userInputFrame(requestedSort, unsortedArray);
}
public static int[] unsort(int[] sortedArray) {
int[] unsortedArray = arrayGenerator();
return unsortedArray;
}
public static void main(String[] args) {
Scanner myScanner = new Scanner(System.in);
System.out.println("Welcome to my sort visualizer");
System.out.println("");
System.out.println("Which sort would you like to see in action? ");
requestedSort = myScanner.nextLine();
requestedSort = requestedSort.toLowerCase();
Drawer.createGUI();
myScanner.close();
}
}
GUI Program
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Drawer extends JPanel {
public static SortVisualizerShell sort;
public static int[] y = SortVisualizerShell.unsortedArray;
public static String requestedSort;
public static Timer timer;
public static int delay = 1000;
public static void createGUI() {
JFrame frame = new JFrame();
JButton sortButton = new JButton("Sort");
JButton unsortButton = new JButton("Randomize Array");
JPanel panel = new JPanel();
Drawer draw = new Drawer();
requestedSort = SortVisualizerShell.requestedSort;
panel.setOpaque(false);
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.add(Box.createRigidArea(new Dimension(0, 5)));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(draw);
panel.add(sortButton);
panel.add(unsortButton);
frame.add(panel);
frame.getContentPane().setBackground(Color.BLACK);
frame.setVisible(true);
frame.pack();
frame.setSize(550, 650);
sortButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SortVisualizerShell.activateSort(requestedSort, y);
frame.repaint();
}
});
unsortButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
y = SortVisualizerShell.unsort(y);
frame.repaint();
}
});
Timer timer = new Timer(1000, sortButton.getAction());
timer.setInitialDelay(1000);
timer.setDelay(1000);
timer.setRepeats(true);
timer.start();
}
public void paintComponent(Graphics g) {
for (int i = 0; i < y.length; i++) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawRect(i, 0, 5, y[i]);
g2d.setColor(Color.WHITE);
g2d.fillRect(i, 0, 5, y[i]);
}
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createGUI();
}
});
}
}
First, some points to note regarding the code in your question.
You shouldn't mix console application with GUI application.
I would add a JComboBox for choosing the desired sort method.
In the below line of your code, sortButton.getAction() returns null.
Timer timer = new Timer(1000, sortButton.getAction());
Hence your timer essentially does nothing.
Usually the first line of an overridden paintComponent method should be
super.paintComponent(g);
Refer to Performing Custom Painting lesson of Creating a GUI With Swing trail in Oracle's Java tutorials.
Now I believe that you want to perform a repaint after each step of the chosen sort algorithm in order to manifest the visualization of the sort algorithm. In the below code, each sort algorithm is a separate class and each class implements a sortStep method which is declared in interface IntsSort.
I defined an enum
for the different sorting algorithms.
After clicking on the Sort button, a [Swing] timer is launched that invokes the sortStep method of the selected sorting algorithm every second. The timer is stopped when the sortStep method returns true indicating that the sort has completed.
As an added visual aid, I set the cursor to a wait cursor while the sort is
executing and display a JOptionPane informing the user that the sort has terminated.
Note that the below code is missing the classes that implement merge sort and
quick sort. Hopefully you will be able to write those yourself based on the
below code.
The IntsSort interface:
/**
* Sorts array of {#code int}.
*/
public interface IntsSort {
/**
* Performs a single, sorting step.
*
* #return <tt>true</tt> if there are no more sorting steps required indicating that the sort
* has completed.
*/
public boolean sortStep();
}
The bubble sort implementation of IntsSort:
public class BublSort implements IntsSort {
private int[] array;
private int i;
public BublSort(int[] arr) {
array = arr;
i = -1;
}
#Override
public boolean sortStep() {
boolean swapped = false;
i++;
if (i < array.length - 1) {
int temp;
for (int j = 0; j < ((array.length - 1) - 1); j++) {
if (array[j] > array[j + 1]) {
temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
swapped = true;
}
}
}
return !swapped;
}
}
The insertion sort implementation of IntsSort:
public class InsrtSrt implements IntsSort {
private int[] array;
private int i;
public InsrtSrt(int[] arr) {
array = arr;
i = -1;
}
public boolean sortStep() {
boolean stopSorting;
i++;
int n = array.length;
if (i < n) {
stopSorting = false;
int key = array[i];
int j = i - 1;
while (j >= 0 && array[j] > key) {
array[j + 1] = array[j];
j = j - 1;
}
array[j + 1] = key;
}
else {
stopSorting = true;
}
return stopSorting;
}
}
The selection sort implementation of interface IntsSort:
public class SlctnSrt implements IntsSort {
private int[] array;
private int i;
public SlctnSrt(int[] arr) {
array = arr;
i = -1;
}
#Override
public boolean sortStep() {
boolean stopSorting;
i++;
if (i < array.length - 1) {
stopSorting = false;
int min = i;
for (int j = (i + 1); j < array.length; j++) {
if (array[min] > array[j]) {
min = j;
}
}
int key = array[min];
while (min > i) {
array[min] = array[min - 1];
min = min - 1;
}
array[i] = key;
}
else {
stopSorting = true;
}
return stopSorting;
}
}
The Swing GUI application:
Note that there is no ActionListener for randomizeButton since I am under the impression that you already know how to do that part.
Also, the ActionListener for sortButton uses a method reference.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.Timer;
public class SortDraw extends JPanel implements Runnable {
private JComboBox<SortExec.SortMethod> sortAlgorithmsCombo;
private JFrame frame;
private Timer timer;
public SortDraw() {
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
setBackground(Color.darkGray);
setOpaque(false);
}
#Override
public void run() {
createAndDisplayGui();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int[] y = SortExec.getArray();
if (y != null) {
for (int i = 0; i < y.length; i++) {
Graphics2D g2d = (Graphics2D) g;
g2d.drawRect(i, 0, 5, y[i]);
g2d.setColor(Color.WHITE);
g2d.fillRect(i, 0, 5, y[i]);
}
}
}
private void createAndDisplayGui() {
frame = new JFrame("Algos");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createTopPanel(), BorderLayout.PAGE_START);
frame.add(this, BorderLayout.CENTER);
frame.add(createButtonsPanel(), BorderLayout.PAGE_END);
frame.setSize(550, 650);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createButtonsPanel() {
JPanel buttonsPanel = new JPanel();
JButton sortButton = new JButton("Sort");
sortButton.setMnemonic(KeyEvent.VK_S);
sortButton.addActionListener(this::launchSort);
buttonsPanel.add(sortButton);
JButton randomizeButton = new JButton("Randomize");
buttonsPanel.add(randomizeButton);
return buttonsPanel;
}
private JPanel createTopPanel() {
JPanel topPanel = new JPanel();
JLabel label = new JLabel("Sort Algorithm");
topPanel.add(label);
sortAlgorithmsCombo = new JComboBox<>(SortExec.SortMethod.values());
topPanel.add(sortAlgorithmsCombo);
return topPanel;
}
private void launchSort(ActionEvent event) {
SortExec.SortMethod requestedSort = (SortExec.SortMethod) sortAlgorithmsCombo.getSelectedItem();
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
SortExec.initSort(requestedSort);
}
catch (RuntimeException xRuntime) {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
throw xRuntime;
}
timer = new Timer(1000, this::performSort);
timer.setInitialDelay(0);
timer.start();
}
private void performSort(ActionEvent event) {
boolean stopSorting = SortExec.performSort();
repaint();
if (stopSorting) {
timer.stop();
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
JOptionPane.showMessageDialog(frame,
"Sorting completed.",
"Complete",
JOptionPane.INFORMATION_MESSAGE);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new SortDraw());
}
}
And finally, the SortExec utility class (including the sort algorithms enum):
import java.util.Random;
public class SortExec {
public enum SortMethod {
BUBBLE, INSERTION, MERGE, QUICK, SELECTION;
#Override
public String toString() {
String str;
switch (this) {
case BUBBLE:
str = "bubble sort";
break;
case INSERTION:
str = "insertion sort";
break;
case MERGE:
str = "merge sort";
break;
case QUICK:
str = "quick sort";
break;
case SELECTION:
str = "selection sort";
break;
default:
str = "unknown: " + this.ordinal();
}
return str;
}
}
private static int[] arr;
private static IntsSort sorter;
public static int[] getArray() {
return arr;
}
public static void initSort(SortMethod sortMethod) {
arr = arrayGenerator();
sorter = null;
switch (sortMethod) {
case BUBBLE:
sorter = new BublSort(arr);
break;
case INSERTION:
sorter = new InsrtSrt(arr);
break;
case SELECTION:
sorter = new SlctnSrt(arr);
break;
default:
throw new RuntimeException(sortMethod.toString());
}
}
public static boolean performSort() {
return sorter.sortStep();
}
private static int[] arrayGenerator() {
int $N = 500;
int[] array = new int[$N];
Random rand = new Random();
for (int i = 0; i < $N; i++) {
int random_num = rand.nextInt($N);
array[i] = random_num;
}
return array;
}
}

LWJGL glDrawArrays doesn't draw

I'm kinda new to the LWJGL library and im trying to draw a simple triangle. Only my code seems okay with it won't draw my triangle. It also gives no errors or anything else. I searched everywhere but found no solution at all. Anyone with a solution?
Code:
public class Cube {
private int width, height;
private int ID, bufferID;
private int count;
private float[] vertices;
public Cube() {
vertices = new float[] {
0, 0, 0,
1, 0, 0,
0, 1, 0,
};
count = vertices.length / 3;
FloatBuffer buffer = BufferUtils.createFloatBuffer(vertices.length);
buffer.put(vertices);
buffer.flip();
ID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(ID);
bufferID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
GL30.glBindVertexArray(0);
GL20.glDisableVertexAttribArray(0);
}
public void render() {
GL30.glBindVertexArray(ID);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, count);
GL30.glBindVertexArray(0);
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
public class Launcher {
public static void main(String[] args) {
Window window = new Window("Cube Wave", 800, 600);
window.setBackground(1, 0, 1);
window.setup();
}
}
public class Window {
private String title;
private int width, height;
private Vector3f color;
private long window;
private ArrayList<Cube> cubes = new ArrayList<Cube>();
public Window(String title, int width, int height) {
this.title = title;
this.width = width;
this.height = height;
setBackground(0, 0, 0);
}
public void setup() {
if (!glfwInit()) {
return;
}
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
window = glfwCreateWindow(width, height, title, NULL, NULL);
if (window == NULL) {
return;
}
GLFWVidMode monitor = glfwGetVideoMode(glfwGetPrimaryMonitor());
glfwSetWindowPos(window, (monitor.width() - width) / 2, (monitor.height() - height) / 2);
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
GL.createCapabilities();
glfwShowWindow(window);
loop();
}
public long getWindow() {
return window;
}
public void loop() {
init();
glClearColor(color.x, color.y, color.z, 1.0f);
while(!closed()) {
tick();
render();
}
}
public void render() {
for(Cube cube : cubes) {
cube.render();
}
glfwSwapBuffers(window);
}
public void tick() {
glfwPollEvents();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
public void init() {
cubes.add(new Cube());
}
public boolean closed() {
return glfwWindowShouldClose(window);
}
public void setBackground(float r, float g, float b) {
color = new Vector3f(r, g, b);
}
}
It seems that you have a mistake in your render function.
Below is an example for how to use glDrawArrays in a render function.
public void render() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
// bind the VBO and enable the attribute
GL30.glBindVertexArray(ID);
GL20.glEnableVertexAttribArray(0);
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, count);
// disable the VBO and disable the attribute
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);
}
Also it is considered best practice to unbind buffers when you are done with them. So your initialization function should look like this.
public Cube() {
// ... vertex stuff
ID = GL30.glGenVertexArrays();
GL30.glBindVertexArray(ID);
bufferID = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, bufferID);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, buffer, GL15.GL_STATIC_DRAW);
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
// unbind both the Array Buffer and the Vertex Array
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
}
You can find a more in depth discussion and example of this from the lwjgl wiki

How does LibGDX FitViewport keep aspect ratio after resizing?

I am a little bit confused why my FitViewport is not keeping the aspect ratio when resizing the window.
I thought that it should always keep the aspect ratio and then fill up the screen with black bars for areas which are not used.
However for me it is not keeping the aspect ratio and circles become ellipsis f.e. (see screenshots).
Code when creating my game renderer (32 and 18 are my world units)
viewport = new FitViewport(32, 18);
camera = viewport.getCamera();
visibleArea = new Rectangle(0, 0, viewport.getScreenWidth(), viewport.getScreenHeight());
scissors = new Rectangle();
Code when resizing the window
public void resize(int width, int height) {
Gdx.app.debug(TAG, "Resizing to " + width + " x " + height);
viewport.update(width, height);
visibleArea.set(0, 0, viewport.getScreenWidth(), viewport.getScreenHeight());
Render method
public void render(float alpha) {
viewport.calculateScissors(batch.getTransformMatrix(), visibleArea, scissors);
ScissorStack.pushScissors(scissors);
viewport.apply();
setView(camera.combined, visibleArea.x, visibleArea.y, visibleArea.width, visibleArea.height);
batch.begin();
// ...
batch.end();
ScissorStack.popScissors();
}
correct aspect ratio on startup
wrong aspect ratio on resize
Okay the problem actually was with my framebuffer light method (prepareLightFrameBuffer) which also had a call to batch.begin(); and batch.end().
It seems like this messes up the view (or resets it to something?). To solve the issue I just applied the viewport again and set the view again in the render method (Note: I also have a stage so I think that viewport.apply() has to be called here and also in the stage.render() method).
Here is the complete code of the GameRenderer if anyone is interested. I guess somehow it could be simplified but I am no OpenGL/Matrix expert so I have no idea how to do it :)
package com.lok.game;
import java.util.Comparator;
import com.badlogic.ashley.core.ComponentMapper;
import com.badlogic.ashley.core.Entity;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer.ShapeType;
import com.badlogic.gdx.maps.MapLayer;
import com.badlogic.gdx.maps.tiled.TiledMapImageLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
import com.badlogic.gdx.maps.tiled.tiles.AnimatedTiledMapTile;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.scenes.scene2d.utils.ScissorStack;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.lok.game.ecs.components.AnimationComponent;
import com.lok.game.ecs.components.CollisionComponent;
import com.lok.game.ecs.components.MapRevelationComponent;
import com.lok.game.ecs.components.SizeComponent;
import com.lok.game.map.Map;
import com.lok.game.map.Map.Portal;
import com.lok.game.map.MapManager;
public class GameRenderer extends OrthogonalTiledMapRenderer {
private final static String TAG = GameRenderer.class.getName();
private static class yPositionComparator implements Comparator<Entity> {
private final ComponentMapper<SizeComponent> sizeComponentMapper;
private yPositionComparator(ComponentMapper<SizeComponent> sizeComponentMapper) {
this.sizeComponentMapper = sizeComponentMapper;
}
#Override
public int compare(Entity o1, Entity o2) {
if (o1 == o2) {
return 0;
} else if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
}
return sizeComponentMapper.get(o1).boundingRectangle.y > sizeComponentMapper.get(o2).boundingRectangle.y ? -1 : 1;
}
}
private SizeComponent cameraLockEntitySizeComponent;
private MapRevelationComponent cameraLockEntityRevelationComponent;
private Map map;
private TiledMapTileLayer groundLayer;
private final Array<TiledMapTileLayer> backgroundLayers;
private final Array<TiledMapTileLayer> foregroundLayers;
private TiledMapImageLayer lightMapLayer;
private final yPositionComparator entityComparator;
private final ComponentMapper<SizeComponent> sizeComponentMapper;
private final ComponentMapper<AnimationComponent> animationComponentMapper;
private final Camera camera;
private final Viewport viewport;
private final Rectangle visibleArea;
private final Rectangle scissors;
private final ShapeRenderer shapeRenderer;
private FrameBuffer frameBuffer;
private final AtlasRegion lightTexture;
private final AtlasRegion shadowTexture;
public GameRenderer() {
super(null, MapManager.WORLD_UNITS_PER_PIXEL);
if (Gdx.app.getLogLevel() == Application.LOG_DEBUG) {
Gdx.app.debug(TAG, "Creating in debug mode");
shapeRenderer = new ShapeRenderer();
} else {
Gdx.app.debug(TAG, "Creating in non-debug mode");
shapeRenderer = null;
}
viewport = new FitViewport(32, 18);
camera = viewport.getCamera();
visibleArea = new Rectangle();
scissors = new Rectangle();
this.backgroundLayers = new Array<TiledMapTileLayer>();
this.foregroundLayers = new Array<TiledMapTileLayer>();
this.sizeComponentMapper = ComponentMapper.getFor(SizeComponent.class);
this.animationComponentMapper = ComponentMapper.getFor(AnimationComponent.class);
this.entityComparator = new yPositionComparator(sizeComponentMapper);
final TextureAtlas textureAtlas = AssetManager.getManager().getAsset("lights/lights.atlas", TextureAtlas.class);
lightTexture = textureAtlas.findRegion("light");
shadowTexture = textureAtlas.findRegion("shadow");
frameBuffer = null;
}
public void setMap(Map map) {
this.map = map;
super.setMap(map.getTiledMap());
this.backgroundLayers.clear();
this.foregroundLayers.clear();
this.lightMapLayer = null;
for (MapLayer mapLayer : map.getTiledMap().getLayers()) {
if (mapLayer instanceof TiledMapTileLayer) {
if ("ground".equals(mapLayer.getName())) {
groundLayer = (TiledMapTileLayer) mapLayer;
} else if (mapLayer.getName().startsWith("background")) {
backgroundLayers.add((TiledMapTileLayer) mapLayer);
} else {
foregroundLayers.add((TiledMapTileLayer) mapLayer);
}
} else if (mapLayer instanceof TiledMapImageLayer) {
lightMapLayer = (TiledMapImageLayer) mapLayer;
}
}
}
public void resize(int width, int height) {
Gdx.app.debug(TAG, "Resizing with " + width + "x" + height + " from viewport " + viewport.getScreenWidth() + "x" + viewport.getScreenHeight());
viewport.update(width, height, false);
visibleArea.set(0, 0, viewport.getWorldWidth(), viewport.getWorldHeight());
Gdx.app.debug(TAG, "To viewport " + viewport.getScreenWidth() + "x" + viewport.getScreenHeight());
if (frameBuffer != null) {
frameBuffer.dispose();
}
try {
frameBuffer = FrameBuffer.createFrameBuffer(Pixmap.Format.RGBA8888, viewport.getScreenWidth(), viewport.getScreenHeight(), false);
} catch (GdxRuntimeException e) {
frameBuffer = FrameBuffer.createFrameBuffer(Pixmap.Format.RGB565, viewport.getScreenWidth(), viewport.getScreenHeight(), false);
}
}
public void lockCameraToEntity(Entity entity) {
if (entity == null) {
cameraLockEntitySizeComponent = null;
cameraLockEntityRevelationComponent = null;
} else {
cameraLockEntityRevelationComponent = entity.getComponent(MapRevelationComponent.class);
cameraLockEntitySizeComponent = entity.getComponent(SizeComponent.class);
if (cameraLockEntitySizeComponent == null) {
throw new GdxRuntimeException("Trying to lock camera to an entity without size component: " + entity);
}
}
}
private void interpolateEntities(float alpha) {
for (Entity entity : map.getEntities()) {
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
final float invAlpha = 1.0f - alpha;
sizeComp.interpolatedPosition.x = sizeComp.interpolatedPosition.x * invAlpha + sizeComp.boundingRectangle.x * alpha;
sizeComp.interpolatedPosition.y = sizeComp.interpolatedPosition.y * invAlpha + sizeComp.boundingRectangle.y * alpha;
}
}
public void render(float alpha) {
AnimatedTiledMapTile.updateAnimationBaseTime();
interpolateEntities(alpha);
map.getEntities().sort(entityComparator);
if (cameraLockEntitySizeComponent != null) {
camera.position.set(cameraLockEntitySizeComponent.interpolatedPosition, 0);
visibleArea.setCenter(cameraLockEntitySizeComponent.interpolatedPosition);
}
prepareLightFrameBuffer();
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
viewport.apply();
setView(camera.combined, visibleArea.x, visibleArea.y, visibleArea.width, visibleArea.height);
batch.begin();
viewport.calculateScissors(batch.getTransformMatrix(), visibleArea, scissors);
ScissorStack.pushScissors(scissors);
if (groundLayer != null) {
renderTileLayer(groundLayer);
}
for (Entity entity : map.getEntities()) {
renderEntityShadow(entity);
}
for (TiledMapTileLayer layer : backgroundLayers) {
renderTileLayer(layer);
}
for (Entity entity : map.getEntities()) {
renderEntity(entity);
}
for (TiledMapTileLayer layer : foregroundLayers) {
renderTileLayer(layer);
}
batch.end();
applyLightFrameBuffer();
if (Gdx.app.getLogLevel() == Application.LOG_DEBUG) {
renderDebugInformation();
}
ScissorStack.popScissors();
}
private void renderEntityShadow(Entity entity) {
final AnimationComponent animationComp = animationComponentMapper.get(entity);
if (animationComp.animation != null) {
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
if (!viewBounds.overlaps(sizeComp.boundingRectangle)) {
return;
}
if (cameraLockEntityRevelationComponent != null && !Intersector.overlaps(cameraLockEntityRevelationComponent.revelationCircle, sizeComp.boundingRectangle)) {
return;
}
batch.draw(shadowTexture, sizeComp.interpolatedPosition.x, sizeComp.interpolatedPosition.y - sizeComp.boundingRectangle.height * 0.2f, sizeComp.boundingRectangle.width,
sizeComp.boundingRectangle.height * 0.5f);
}
}
private void renderEntity(Entity entity) {
final AnimationComponent animationComp = animationComponentMapper.get(entity);
if (animationComp.animation != null) {
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
if (!viewBounds.overlaps(sizeComp.boundingRectangle)) {
return;
}
if (cameraLockEntityRevelationComponent != null && !Intersector.overlaps(cameraLockEntityRevelationComponent.revelationCircle, sizeComp.boundingRectangle)) {
return;
}
final TextureRegion keyFrame = animationComp.animation.getKeyFrame(animationComp.animationTime, true);
batch.draw(keyFrame, sizeComp.interpolatedPosition.x, sizeComp.interpolatedPosition.y, sizeComp.boundingRectangle.width, sizeComp.boundingRectangle.height);
}
}
private void prepareLightFrameBuffer() {
if (cameraLockEntityRevelationComponent != null) {
frameBuffer.begin();
final Color mapBackgroundColor = map.getBackgroundColor();
Gdx.gl.glClearColor(mapBackgroundColor.r, mapBackgroundColor.g, mapBackgroundColor.b, mapBackgroundColor.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
setView(camera.combined, visibleArea.x, visibleArea.y, visibleArea.width, visibleArea.height);
batch.begin();
if (lightMapLayer != null) {
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
renderImageLayer(lightMapLayer);
}
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE);
final Rectangle boundingRectangle = cameraLockEntitySizeComponent.boundingRectangle;
batch.draw(lightTexture, cameraLockEntitySizeComponent.interpolatedPosition.x + boundingRectangle.width * 0.5f - cameraLockEntityRevelationComponent.revelationRadius, // x
cameraLockEntitySizeComponent.interpolatedPosition.y + boundingRectangle.height * 0.5f - cameraLockEntityRevelationComponent.revelationRadius, // y
cameraLockEntityRevelationComponent.revelationRadius * 2f, cameraLockEntityRevelationComponent.revelationRadius * 2f);
batch.end();
frameBuffer.end();
}
}
private void applyLightFrameBuffer() {
if (cameraLockEntityRevelationComponent != null) {
batch.setProjectionMatrix(batch.getProjectionMatrix().idt());
batch.setBlendFunction(GL20.GL_ZERO, GL20.GL_SRC_COLOR);
batch.begin();
batch.draw(frameBuffer.getColorBufferTexture(), -1, 1, 2, -2);
batch.end();
}
}
private void renderDebugInformation() {
shapeRenderer.setProjectionMatrix(camera.combined);
shapeRenderer.begin(ShapeType.Line);
shapeRenderer.setColor(Color.RED);
for (Rectangle rect : map.getCollisionAreas()) {
shapeRenderer.rect(rect.x, rect.y, rect.width, rect.height);
}
for (Entity entity : map.getEntities()) {
final CollisionComponent collisionComponent = entity.getComponent(CollisionComponent.class);
final SizeComponent sizeComp = sizeComponentMapper.get(entity);
if (collisionComponent != null) {
shapeRenderer.setColor(Color.RED);
shapeRenderer.rect(sizeComp.interpolatedPosition.x + collisionComponent.rectOffset.x, sizeComp.interpolatedPosition.y + collisionComponent.rectOffset.y,
collisionComponent.collisionRectangle.width, collisionComponent.collisionRectangle.height);
}
if (sizeComp != null) {
shapeRenderer.setColor(Color.BLUE);
shapeRenderer.rect(sizeComp.interpolatedPosition.x, sizeComp.interpolatedPosition.y, sizeComp.boundingRectangle.width, sizeComp.boundingRectangle.height);
}
}
shapeRenderer.setColor(Color.BLUE);
for (Portal portal : map.getPortals()) {
shapeRenderer.rect(portal.getArea().x, portal.getArea().y, portal.getArea().width, portal.getArea().height);
}
if (cameraLockEntityRevelationComponent != null) {
shapeRenderer.setColor(Color.WHITE);
shapeRenderer.circle(cameraLockEntitySizeComponent.interpolatedPosition.x + cameraLockEntitySizeComponent.boundingRectangle.width * 0.5f,
cameraLockEntitySizeComponent.interpolatedPosition.y + cameraLockEntitySizeComponent.boundingRectangle.height * 0.5f,
cameraLockEntityRevelationComponent.revelationCircle.radius, 64);
}
shapeRenderer.end();
}
#Override
public void dispose() {
Gdx.app.debug(TAG, "Disposing Gamerenderer");
super.dispose();
if (shapeRenderer != null) {
shapeRenderer.dispose();
}
if (frameBuffer != null) {
frameBuffer.dispose();
}
}
}

libgdx snake logic explained

This is the code from a book I am reading. It is just a snake game. you can paste this code into editor and change 3 texture files to what you have on computer. The code works fine and this is probably really stupid, but I am unable to make the connection between snakeBody parts and their position. How exactly does the body of the snake(not the head) knows where it should go after the head of the snake changes position? Can you elaborate on this? Thank you
// class 1/2:
public class MyGdxGame extends Game {
#Override
public void create () {
setScreen(new GameScreen());
}
}
class 2/2:
package com.mygdx.game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.ScreenAdapter;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.utils.Array;
public class GameScreen extends ScreenAdapter{
private static final float MOVE_TIME = 0.5F;
private static final int SNAKE_MOVEMENT = 32;
private static final int RIGHT = 0;
private static final int LEFT = 1;
private static final int UP = 2;
private static final int DOWN = 3;
private SpriteBatch batch;
private Texture snakeHead;
private Texture snakeBody;
private Texture apple;
private boolean appleAvailable = false;
private int appleX, appleY;
private float timer = MOVE_TIME;
private int snakeX = 0, snakeY = 0;
private int snakeXBeforeUpdate = 0, snakeYBeforeUpdate = 0;
private int snakeDirection = RIGHT;
private Array<BodyPart> bodyParts = new Array<BodyPart>();
#Override
public void show() {
super.show();
batch = new SpriteBatch();
snakeHead = new Texture(Gdx.files.internal("snakehead.png"));
snakeBody = new Texture(Gdx.files.internal("snakeBody.png"));
apple = new Texture(Gdx.files.internal("apple.png"));
}
#Override
public void render(float delta) {
super.render(delta);
queryInput();
timer -= delta;
if (timer <= 0) {
timer = MOVE_TIME;
moveSnake();
checkForOutOfBounds();
updateBodyPartsPosition();
}
checkAppleCollision();
checkAndPlaceApple();
clearScreen();
draw();
}
private void queryInput() {
boolean lPressed = Gdx.input.isKeyPressed(Input.Keys.LEFT);
boolean rPressed = Gdx.input.isKeyPressed(Input.Keys.RIGHT);
boolean uPressed = Gdx.input.isKeyPressed(Input.Keys.UP);
boolean dPressed = Gdx.input.isKeyPressed(Input.Keys.DOWN);
if (lPressed) snakeDirection = LEFT;
if (rPressed) snakeDirection = RIGHT;
if (uPressed) snakeDirection = UP;
if (dPressed) snakeDirection = DOWN;
}
private void moveSnake() {
snakeXBeforeUpdate = snakeX;
snakeYBeforeUpdate = snakeY;
switch (snakeDirection) {
case RIGHT: {
snakeX += SNAKE_MOVEMENT;
return;
}
case LEFT: {
snakeX -= SNAKE_MOVEMENT;
return;
}
case UP: {
snakeY += SNAKE_MOVEMENT;
return;
}
case DOWN: {
snakeY -= SNAKE_MOVEMENT;
return;
}
}
}
private void checkForOutOfBounds() {
if (snakeX >= Gdx.graphics.getWidth()) {
snakeX = 0;
}
if (snakeX < 0) {
snakeX = Gdx.graphics.getWidth() - SNAKE_MOVEMENT;
}
if (snakeY >= Gdx.graphics.getHeight()) {
snakeY = 0;
}
if (snakeY < 0) {
snakeY = Gdx.graphics.getHeight() - SNAKE_MOVEMENT;
}
}
private void updateBodyPartsPosition() {
if (bodyParts.size > 0) {
BodyPart bodyPart = bodyParts.removeIndex(0);
bodyPart.updateBodyPosition(snakeXBeforeUpdate, snakeYBeforeUpdate);
bodyParts.add(bodyPart);
}
}
private void checkAndPlaceApple() {
if (!appleAvailable) {
do {
appleX = MathUtils.random(Gdx.graphics.getWidth() / SNAKE_MOVEMENT - 1) * SNAKE_MOVEMENT;
appleY = MathUtils.random(Gdx.graphics.getHeight() / SNAKE_MOVEMENT - 1) * SNAKE_MOVEMENT;
appleAvailable = true;
} while (appleX == snakeX && appleY == snakeY);
}
}
private void checkAppleCollision() {
if (appleAvailable && appleX == snakeX && appleY == snakeY) {
BodyPart bodyPart = new BodyPart(snakeBody);
bodyPart.updateBodyPosition(snakeX, snakeY);
bodyParts.insert(0,bodyPart);
appleAvailable = false;
}
}
private void clearScreen() {
Gdx.gl.glClearColor(Color.BLACK.r, Color.BLACK.g, Color.BLACK.b, Color.BLACK.a);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
}
private void draw() {
batch.begin();
batch.draw(snakeHead, snakeX, snakeY);
for (BodyPart bodyPart : bodyParts) {
bodyPart.draw(batch);
}
if (appleAvailable) {
batch.draw(apple, appleX, appleY);
}
batch.end();
}
private class BodyPart {
private int x, y;
private Texture texture;
public BodyPart(Texture texture) {
this.texture = texture;
}
public void updateBodyPosition(int x, int y) {
this.x = x;
this.y = y;
}
public void draw(Batch batch) {
if (!(x == snakeX && y == snakeY)) batch.draw(texture, x, y);
}
}
}
First it moves the head in moveSnake(), making sure to keep track of where the head was before the move. Then in updateBodyPartsPosition() it puts the last piece of the body where the head was just moved from.

How can I move balls in random directions and how can I keep them bouncing inside the frame?

Here is the code:
public class BallGame extends JPanel implements Runnable {
JPanel panel1 = new JPanel();
private int ballX = 10, ballY = 110, ...;
Thread aThread;
int toRight=5;
int toLeft= -5;
int upWard=5;
int downWard= -5;
int widthBall, heightBall;
public BallGame(){
game=true;
aThread=new Thread(this);
aThread.start();
}
public void paintComponent(Graphics g){
setOpaque(false);
super.paintComponent(g);
g.setColor(Color.RED);
g.fillOval(ballX, ballY, 7,7);
g.setColor(Color.BLUE);
g.fillOval(ballX + 15, ballY + 10, 7,7);
g.setColor(Color.GREEN);
g.fillOval(ballY - 10, ballY - 15, 7,7);
}
public void positionBall(int sx, int sy)
{
ballX = sx;
ballY = sy;
this.widthBall = this.getWidth();
this.heightBall = this.getHeight();
repaint();
}
public void run() {
boolean leftRight = false;
boolean upDown = false;
while(true){
if(game){
if (leftRight)
{
ballX += toRight;
if (ballX >= (widthBall - 5))
leftRight= false;
}
else
{
ballX += toLeft;
if ( ballX <= 0)
leftRight = true;
}
if (upDown)
{
ballY += upWard;
if (ballY >= (heightBall - 5))
upDown = false;
}
else
{
ballY += downWard;
if ( ballY <= 0)
upDown = true;
}
positionBall(ballX, ballY);
try
{
Thread.sleep(70);
}
catch(InterruptedException ex)
{
}
I don't know if the part where I drew the balls was right. The balls move in the same path. How can I move them in different directions and how can I limit them inside the frame? I need this for our case study immediately. Thank you for your time!
In order for the balls to move independently you need to treat them as 3 balls.
The reason why they always go the same direction, is that you use the same delta, just inverting the sign of delta x and delta y, thus you will always keep the same speed, and bounce at 90 degrees.
In the code below which is basically the same as you had, I keep the state of each ball in separate instances, change speed of delta x and delta y once a side is touched, and use the Swing Timer which is a better approach with respect to timing in Swing, as pointed out by Robin above.
I have updated the example, so that 4 balls start in the middle, and they move away from each other. This should give you enough information to adapt it to your requirements. The picture below is produced by only allowing 10 iterations, and setting
ballGame.setOpaque(true);
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.*;
public class BallGame extends JPanel {
private class Ball {
private int x;
private int y;
private int width;
private int height;
private Color color;
private boolean leftRight;
private boolean upDown;
private int deltaX;
private int deltaY;
Ball(Color color, int x, int y, int width, int height) {
this(color, x, y, width, height, false, false);
}
Ball(Color color, int x, int y, int width, int height, boolean leftRight, boolean upDown) {
this.color = color;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.leftRight = leftRight;
this.upDown = upDown;
updateDelta();
}
private void updateDelta() {
final int minimumMovement = 5;
final int maxExtra = 10;
deltaY = minimumMovement + (int) (Math.random() * maxExtra);
deltaX = minimumMovement + (int) (Math.random() * maxExtra);
}
public void positionBall() {
if (leftRight) {
x += deltaX;
if (x >= (BallGame.this.getWidth() - width / 2)) {
leftRight = false;
updateDelta();
}
} else {
x += -deltaX;
if (x <= 0) {
leftRight = true;
updateDelta();
}
}
if (upDown) {
y += deltaY;
upDown = !(y >= (BallGame.this.getHeight() - height / 2));
if (y >= (BallGame.this.getHeight() - height / 2)) {
upDown = false;
updateDelta();
}
} else {
y += -deltaY;
if (y <= 0) {
upDown = true;
updateDelta();
}
}
}
public Color getColor() {
return color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
}
private ArrayList<Ball> balls = new ArrayList<>(3);
public BallGame() {
createBalls();
startGame();
}
private void startGame() {
int framesPerSecond = 30;
int timeToWait = 1000 / framesPerSecond;
Timer timer = new Timer(timeToWait, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Ball ball : balls) {
ball.positionBall();
}
repaint();
}
});
timer.start();
}
private void createBalls() {
int startX = 400;
int startY = 200;
balls.add(new Ball(Color.green, startX, startY, 10, 10));
balls.add(new Ball(Color.blue, startX, startY, 15, 15, true, true));
balls.add(new Ball(Color.red, startX, startY, 20, 20, false, true));
balls.add(new Ball(Color.orange, startX, startY, 20, 20, true, false));
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
for (Ball ball : balls) {
g2.setColor(ball.getColor());
g2.fillOval(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
g2.setColor(ball.getColor().darker());
g2.drawOval(ball.getX(), ball.getY(), ball.getWidth(), ball.getHeight());
}
g2.dispose();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Ball Game");
BallGame ballGame = new BallGame();
ballGame.setOpaque(false);
frame.getContentPane().add(ballGame);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setMinimumSize(new Dimension(800, 450));
frame.setLocationRelativeTo(null); // Center
frame.pack();
frame.setVisible(true);
}
});
}
}