summaryrefslogtreecommitdiffstats
path: root/gui/src/main/java/dev/figboot/launcher
diff options
context:
space:
mode:
authorLibravatar bigfoot547 <[email protected]>2025-08-25 22:28:24 -0500
committerLibravatar bigfoot547 <[email protected]>2025-08-25 22:28:24 -0500
commit954e102e836e19ea425253f1990fff8d76dd98b4 (patch)
tree2dc68f8a09efc5b11c88ee4154b6487db0baadbb /gui/src/main/java/dev/figboot/launcher
initial commit
Diffstat (limited to 'gui/src/main/java/dev/figboot/launcher')
-rw-r--r--gui/src/main/java/dev/figboot/launcher/gui/GUIMain.java106
-rw-r--r--gui/src/main/java/dev/figboot/launcher/gui/InstanceListPanel.java212
-rw-r--r--gui/src/main/java/dev/figboot/launcher/gui/WrapLayout.java204
3 files changed, 522 insertions, 0 deletions
diff --git a/gui/src/main/java/dev/figboot/launcher/gui/GUIMain.java b/gui/src/main/java/dev/figboot/launcher/gui/GUIMain.java
new file mode 100644
index 0000000..dc5b2ed
--- /dev/null
+++ b/gui/src/main/java/dev/figboot/launcher/gui/GUIMain.java
@@ -0,0 +1,106 @@
+package dev.figboot.launcher.gui;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.MatteBorder;
+import java.awt.*;
+
+public class GUIMain {
+ public static void main(String[] args) {
+ JFrame mainFrame = new JFrame("amogus");
+ mainFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+ mainFrame.setMinimumSize(new Dimension(480, 360));
+
+ JPanel mainPanel = new JPanel();
+ mainPanel.setLayout(new BorderLayout());
+ mainFrame.setContentPane(mainPanel);
+
+ JPanel statusPanel = new JPanel();
+ statusPanel.setBorder(new CompoundBorder(new MatteBorder(1, 0, 0, 0, Color.GRAY), new EmptyBorder(0, 4, 0, 4)));
+ //statusPanel.setBorder(new LineBorder(Color.BLACK, 1));
+
+ JProgressBar statusProgress = new JProgressBar(0, 100);
+ statusProgress.setValue(33);
+ statusPanel.add(statusProgress);
+
+ JLabel statusLabel = new JLabel("Loading online versions (+2 tasks)");
+ statusLabel.setOpaque(false);
+ statusPanel.add(statusLabel);
+
+ mainPanel.add(statusPanel, BorderLayout.PAGE_END);
+
+ JPanel launchPanel = new JPanel();
+ launchPanel.setLayout(new BorderLayout());
+ //launchPanel.setBorder(new LineBorder(Color.GREEN, 1));
+ mainPanel.add(launchPanel, BorderLayout.CENTER);
+
+ JPanel navPanel = new JPanel();
+ navPanel.setLayout(new BorderLayout());
+ //navPanel.setBorder(new LineBorder(Color.CYAN, 1));
+ launchPanel.add(navPanel, BorderLayout.LINE_END);
+
+ JPanel navItemPanel = new JPanel();
+ navItemPanel.setLayout(new GridLayout(0, 1));
+ navPanel.add(navItemPanel, BorderLayout.PAGE_START);
+
+ Font font = new Font(null, Font.BOLD, 16);
+ ButtonGroup navItems = new ButtonGroup();
+ for (String name : new String[]{"News", "Instances", "Accounts", "Explore", "Add-ons", "Settings"}) {
+ JToggleButton btnNavItem = new JToggleButton(name);
+ navItems.add(btnNavItem);
+ btnNavItem.setSelected(name.equals("Instances"));
+ btnNavItem.setFont(font);
+ btnNavItem.setPreferredSize(new Dimension(175, 40));
+ navItemPanel.add(btnNavItem);
+ }
+
+ JPanel playPanel = new JPanel();
+ playPanel.setLayout(new GridLayout(1, 0));
+ //playPanel.setBorder(new LineBorder(Color.RED, 1));
+ playPanel.setBorder(new EmptyBorder(2, 0, 2, 0));
+ launchPanel.add(playPanel, BorderLayout.PAGE_END);
+
+ JPanel accountSelectPanelOuter = new JPanel();
+ accountSelectPanelOuter.setLayout(new BoxLayout(accountSelectPanelOuter, BoxLayout.PAGE_AXIS));
+ accountSelectPanelOuter.setBorder(new EmptyBorder(4, 4, 4, 4));
+ JPanel accountSelectPanel = new JPanel();
+ accountSelectPanel.setLayout(new BorderLayout());
+ accountSelectPanel.setPreferredSize(new Dimension(200, 30));
+ accountSelectPanel.setMaximumSize(new Dimension(200, 30));
+
+ JComboBox<String> accountSelector = new JComboBox<>(new String[]{"figboot", "bootfig", "figroot", "FacePalmOS", "EXR0"});
+ accountSelectPanel.add(accountSelector, BorderLayout.CENTER);
+
+ accountSelectPanel.add(new JButton("+"), BorderLayout.LINE_END);
+
+ accountSelectPanelOuter.add(Box.createGlue());
+ accountSelectPanelOuter.add(accountSelectPanel);
+ accountSelectPanelOuter.add(Box.createGlue());
+ playPanel.add(accountSelectPanelOuter);
+
+ JButton playButton = new JButton("<html><center><p>Play<br><span style=\"font-weight: normal; font-size: 0.8em\">Latest release</span></p></center></html>");
+ playButton.setFont(font);
+ playButton.setPreferredSize(new Dimension(0, 60));
+ playPanel.add(playButton);
+
+ playPanel.add(new JPanel());
+
+ JPanel actionPanel = new JPanel();
+ CardLayout cardLayout = new CardLayout();
+ actionPanel.setLayout(cardLayout);
+ cardLayout.show(actionPanel, "Instances");
+
+ InstanceListPanel instances = new InstanceListPanel();
+ instances.setOpaque(false);
+
+ JScrollPane instanceListScroll = new JScrollPane(instances, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ //instanceListScroll.setBorder(new LineBorder(Color.ORANGE, 1));
+ instanceListScroll.getViewport().setBackground(Color.LIGHT_GRAY);
+ launchPanel.add(instanceListScroll, BorderLayout.CENTER);
+
+ mainFrame.setSize(1067, 600);
+ mainFrame.invalidate();
+ mainFrame.setVisible(true);
+ }
+}
diff --git a/gui/src/main/java/dev/figboot/launcher/gui/InstanceListPanel.java b/gui/src/main/java/dev/figboot/launcher/gui/InstanceListPanel.java
new file mode 100644
index 0000000..8ed8c5c
--- /dev/null
+++ b/gui/src/main/java/dev/figboot/launcher/gui/InstanceListPanel.java
@@ -0,0 +1,212 @@
+package dev.figboot.launcher.gui;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+public class InstanceListPanel extends JPanel implements Scrollable {
+ private Font titleFont, versionFont;
+
+ public InstanceListPanel() {
+ setLayout(new WrapLayout(FlowLayout.LEADING, 5, 5));
+
+ updateFont();
+
+ add(new Instance("Latest release", "Release 1.21.8", true));
+ add(new Instance("Latest snapshot", "Snapshot 25w19a", false));
+ add(new Instance("SevTech Ages", "Release forge_1.7.10-1059.10.aaaaaaa", false));
+ add(new Instance("Hypixel Modpack", "Release forge_1.8.9-1750.19.4", false));
+ add(new Instance("gangsta shi", "Release fabric-1.16.5+0.19.0", false));
+ }
+
+ private void updateFont() {
+ titleFont = new Font(null, Font.BOLD, getFont().getSize() + 2);
+ versionFont = new Font(null, Font.PLAIN, getFont().getSize());
+ }
+
+ private void handleSelect(Instance selected) {
+ for (int i = 0, max = getComponentCount(); i < max; ++i) {
+ Component comp = getComponent(i);
+ if (comp instanceof Instance) {
+ ((Instance)comp).setSelected(comp == selected);
+ }
+ }
+ }
+
+ private static final Color SELECTED_COLOR = new Color(0x00B0FF);
+ private static final Color ARMED_COLOR = SELECTED_COLOR.darker();
+ private static final Color ROLLOVER_COLOR = new Color(0x909090);
+
+ private class Instance extends JPanel implements MouseListener {
+ private boolean selected;
+ private boolean rollover = false;
+ private boolean armed = false;
+
+ Instance(String text, String version, boolean selected) {
+ setBorder(new CompoundBorder(new LineBorder(Color.BLACK, 1), new InstBorder()));
+
+ setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+
+ JLabel iconLabel = new JLabel(new Icon() {
+ @Override
+ public void paintIcon(Component c, Graphics _g, int x, int y) {
+ Graphics2D g = (Graphics2D)_g;
+
+ Object oldAAHint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
+ Color oldColor = g.getColor();
+ Stroke oldStroke = g.getStroke();
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setColor(Color.RED);
+ g.setStroke(new BasicStroke(5, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
+ g.drawLine(128 / 4, 128 / 4, 3 * 128 / 4, 3 * 128 / 4);
+ g.drawLine(128 / 4, 3 * 128 / 4, 3 * 128 / 4, 128 / 4);
+
+ g.setColor(Color.BLACK);
+ g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1));
+ g.drawRect(0, 0, 127, 127);
+
+ g.setStroke(oldStroke);
+ g.setColor(oldColor);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAAHint);
+ }
+
+ @Override
+ public int getIconWidth() {
+ return 128;
+ }
+
+ @Override
+ public int getIconHeight() {
+ return 128;
+ }
+ });
+ iconLabel.setAlignmentX(CENTER_ALIGNMENT);
+
+ JLabel titleLabel = new JLabel(text);
+ titleLabel.setFont(titleFont);
+ titleLabel.setAlignmentX(CENTER_ALIGNMENT);
+
+ JLabel versionLabel = new JLabel(version);
+ versionLabel.setFont(versionFont);
+ versionLabel.setAlignmentX(CENTER_ALIGNMENT);
+
+ add(titleLabel);
+ add(iconLabel);
+ add(Box.createGlue());
+ add(versionLabel);
+ add(Box.createGlue());
+
+ JPanel actionsPanel = new JPanel();
+ actionsPanel.setAlignmentX(CENTER_ALIGNMENT);
+ actionsPanel.setLayout(new BoxLayout(actionsPanel, BoxLayout.LINE_AXIS));
+ actionsPanel.add(new JButton("Play"));
+ actionsPanel.add(new JButton("Edit"));
+
+ add(actionsPanel);
+ add(Box.createVerticalStrut(2));
+
+ this.selected = selected;
+
+ Dimension prefSz = getPreferredSize();
+ setPreferredSize(new Dimension(215, prefSz.height));
+
+ addMouseListener(this);
+ }
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e)) {
+ InstanceListPanel.this.handleSelect(this);
+ }
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e)) {
+ armed = true;
+ repaint();
+ }
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e)) {
+ armed = false;
+ repaint();
+ }
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+ rollover = true;
+ repaint();
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+ rollover = false;
+ repaint();
+ }
+
+ public void setSelected(boolean sel) {
+ if (this.selected == sel) return;
+
+ this.selected = sel;
+ repaint();
+ }
+
+ class InstBorder extends LineBorder {
+ InstBorder() {
+ super(null, 3, false);
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+ if (armed) {
+ lineColor = ARMED_COLOR;
+ super.paintBorder(c, g, x, y, width, height);
+ } else if (selected) {
+ lineColor = SELECTED_COLOR;
+ super.paintBorder(c, g, x, y, width, height);
+ } else if (rollover) {
+ lineColor = ROLLOVER_COLOR;
+ super.paintBorder(c, g, x, y, width, height);
+ }
+ }
+
+ @Override
+ public boolean isBorderOpaque() {
+ return selected || rollover || armed;
+ }
+ }
+ }
+
+ @Override
+ public Dimension getPreferredScrollableViewportSize() {
+ return getPreferredSize();
+ }
+
+ @Override
+ public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
+ return 20; // TODO
+ }
+
+ @Override
+ public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
+ return 40; // TODO
+ }
+
+ @Override
+ public boolean getScrollableTracksViewportWidth() {
+ return true;
+ }
+
+ @Override
+ public boolean getScrollableTracksViewportHeight() {
+ return false;
+ }
+}
diff --git a/gui/src/main/java/dev/figboot/launcher/gui/WrapLayout.java b/gui/src/main/java/dev/figboot/launcher/gui/WrapLayout.java
new file mode 100644
index 0000000..f0b3095
--- /dev/null
+++ b/gui/src/main/java/dev/figboot/launcher/gui/WrapLayout.java
@@ -0,0 +1,204 @@
+/*
+MIT License
+
+Copyright (c) 2023 Rob Camick
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+ */
+
+package dev.figboot.launcher.gui;
+
+import java.awt.*;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+/**
+ * FlowLayout subclass that fully supports wrapping of components.
+ */
+public class WrapLayout extends FlowLayout {
+ private Dimension preferredLayoutSize;
+
+ /**
+ * Constructs a new <code>WrapLayout</code> with a left
+ * alignment and a default 5-unit horizontal and vertical gap.
+ */
+ public WrapLayout() {
+ super();
+ }
+
+ /**
+ * Constructs a new <code>FlowLayout</code> with the specified
+ * alignment and a default 5-unit horizontal and vertical gap.
+ * The value of the alignment argument must be one of
+ * <code>WrapLayout</code>, <code>WrapLayout</code>,
+ * or <code>WrapLayout</code>.
+ *
+ * @param align the alignment value
+ */
+ public WrapLayout(int align) {
+ super(align);
+ }
+
+ /**
+ * Creates a new flow layout manager with the indicated alignment
+ * and the indicated horizontal and vertical gaps.
+ * <p>
+ * The value of the alignment argument must be one of
+ * <code>WrapLayout</code>, <code>WrapLayout</code>,
+ * or <code>WrapLayout</code>.
+ *
+ * @param align the alignment value
+ * @param hgap the horizontal gap between components
+ * @param vgap the vertical gap between components
+ */
+ public WrapLayout(int align, int hgap, int vgap) {
+ super(align, hgap, vgap);
+ }
+
+ /**
+ * Returns the preferred dimensions for this layout given the
+ * <i>visible</i> components in the specified target container.
+ *
+ * @param target the component which needs to be laid out
+ * @return the preferred dimensions to lay out the
+ * subcomponents of the specified container
+ */
+ @Override
+ public Dimension preferredLayoutSize(Container target) {
+ return layoutSize(target, true);
+ }
+
+ /**
+ * Returns the minimum dimensions needed to layout the <i>visible</i>
+ * components contained in the specified target container.
+ *
+ * @param target the component which needs to be laid out
+ * @return the minimum dimensions to lay out the
+ * subcomponents of the specified container
+ */
+ @Override
+ public Dimension minimumLayoutSize(Container target) {
+ Dimension minimum = layoutSize(target, false);
+ minimum.width -= (getHgap() + 1);
+ return minimum;
+ }
+
+ /**
+ * Returns the minimum or preferred dimension needed to layout the target
+ * container.
+ *
+ * @param target target to get layout size for
+ * @param preferred should preferred size be calculated
+ * @return the dimension to layout the target container
+ */
+ private Dimension layoutSize(Container target, boolean preferred) {
+ synchronized (target.getTreeLock()) {
+ // Each row must fit with the width allocated to the containter.
+ // When the container width = 0, the preferred width of the container
+ // has not yet been calculated so lets ask for the maximum.
+
+ int targetWidth = target.getSize().width;
+ Container container = target;
+
+ while (container.getSize().width == 0 && container.getParent() != null) {
+ container = container.getParent();
+ }
+
+ targetWidth = container.getSize().width;
+
+ if (targetWidth == 0)
+ targetWidth = Integer.MAX_VALUE;
+
+ int hgap = getHgap();
+ int vgap = getVgap();
+ Insets insets = target.getInsets();
+ int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
+ int maxWidth = targetWidth - horizontalInsetsAndGap;
+
+ // Fit components into the allowed width
+
+ Dimension dim = new Dimension(0, 0);
+ int rowWidth = 0;
+ int rowHeight = 0;
+
+ int nmembers = target.getComponentCount();
+
+ for (int i = 0; i < nmembers; i++) {
+ Component m = target.getComponent(i);
+
+ if (m.isVisible()) {
+ Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
+
+ // Can't add the component to current row. Start a new row.
+
+ if (rowWidth + d.width > maxWidth) {
+ addRow(dim, rowWidth, rowHeight);
+ rowWidth = 0;
+ rowHeight = 0;
+ }
+
+ // Add a horizontal gap for all components after the first
+
+ if (rowWidth != 0) {
+ rowWidth += hgap;
+ }
+
+ rowWidth += d.width;
+ rowHeight = Math.max(rowHeight, d.height);
+ }
+ }
+
+ addRow(dim, rowWidth, rowHeight);
+
+ dim.width += horizontalInsetsAndGap;
+ dim.height += insets.top + insets.bottom + vgap * 2;
+
+ // When using a scroll pane or the DecoratedLookAndFeel we need to
+ // make sure the preferred size is less than the size of the
+ // target containter so shrinking the container size works
+ // correctly. Removing the horizontal gap is an easy way to do this.
+
+ Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
+
+ if (scrollPane != null && target.isValid()) {
+ dim.width -= (hgap + 1);
+ }
+
+ return dim;
+ }
+ }
+
+ /*
+ * A new row has been completed. Use the dimensions of this row
+ * to update the preferred size for the container.
+ *
+ * @param dim update the width and height when appropriate
+ * @param rowWidth the width of the row to add
+ * @param rowHeight the height of the row to add
+ */
+ private void addRow(Dimension dim, int rowWidth, int rowHeight) {
+ dim.width = Math.max(dim.width, rowWidth);
+
+ if (dim.height > 0) {
+ dim.height += getVgap();
+ }
+
+ dim.height += rowHeight;
+ }
+}