diff options
Diffstat (limited to 'gui')
| -rw-r--r-- | gui/build.gradle.kts | 30 | ||||
| -rw-r--r-- | gui/src/main/java/dev/figboot/launcher/gui/GUIMain.java | 106 | ||||
| -rw-r--r-- | gui/src/main/java/dev/figboot/launcher/gui/InstanceListPanel.java | 212 | ||||
| -rw-r--r-- | gui/src/main/java/dev/figboot/launcher/gui/WrapLayout.java | 204 | ||||
| -rw-r--r-- | gui/src/main/resources/icons/play.svg | 137 | ||||
| -rw-r--r-- | gui/src/main/resources/icons/play_24.png | bin | 0 -> 1027 bytes |
6 files changed, 689 insertions, 0 deletions
diff --git a/gui/build.gradle.kts b/gui/build.gradle.kts new file mode 100644 index 0000000..16b7bf3 --- /dev/null +++ b/gui/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + application +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(8) + } +} + +group = "dev.figboot.launcher" +version = "1.0-SNAPSHOT" + +repositories { + mavenCentral() +} + +dependencies { + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") +} + +tasks.test { + useJUnitPlatform() +} + +application { + mainClass = "dev.figboot.launcher.gui.GUIMain" + applicationDefaultJvmArgs = listOf("-Dswing.aatext=true", "-Dawt.useSystemAAFontSettings=on") +} 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; + } +} diff --git a/gui/src/main/resources/icons/play.svg b/gui/src/main/resources/icons/play.svg new file mode 100644 index 0000000..799ea9c --- /dev/null +++ b/gui/src/main/resources/icons/play.svg @@ -0,0 +1,137 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + width="48" + height="48" + viewBox="0 0 48 48" + version="1.1" + id="svg1" + inkscape:export-filename="play_24.png" + inkscape:export-xdpi="48" + inkscape:export-ydpi="48" + inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" + sodipodi:docname="drawing.svg" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <sodipodi:namedview + id="namedview1" + pagecolor="#ffffff" + bordercolor="#000000" + borderopacity="0.25" + inkscape:showpageshadow="2" + inkscape:pageopacity="0.0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:document-units="px" + inkscape:zoom="7.2773073" + inkscape:cx="-6.3210193" + inkscape:cy="40.743092" + inkscape:window-width="1916" + inkscape:window-height="1026" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="layer1" /> + <defs + id="defs1"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 24 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="48 : 24 : 1" + inkscape:persp3d-origin="24 : 16 : 1" + id="perspective1" /> + <filter + inkscape:label="Button" + inkscape:menu="Bevels" + inkscape:menu-tooltip="Soft bevel, slightly depressed middle" + style="color-interpolation-filters:sRGB;" + id="filter21" + x="-0.4555" + y="-0.4555" + width="1.911" + height="1.911"> + <feGaussianBlur + stdDeviation="2.3" + in="SourceAlpha" + result="result0" + id="feGaussianBlur18" /> + <feMorphology + in="SourceAlpha" + radius="6.5" + result="result1" + id="feMorphology18" /> + <feGaussianBlur + stdDeviation="7" + in="result1" + id="feGaussianBlur19" /> + <feColorMatrix + values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.3 0" + result="result91" + id="feColorMatrix19" /> + <feComposite + in="result0" + operator="out" + result="result2" + in2="result91" + id="feComposite19" /> + <feDiffuseLighting + surfaceScale="10" + id="feDiffuseLighting19"> + <feDistantLight + azimuth="225" + elevation="45" + id="feDistantLight19" /> + </feDiffuseLighting> + <feBlend + in2="SourceGraphic" + mode="multiply" + id="feBlend19" /> + <feComposite + in2="SourceAlpha" + operator="in" + result="result3" + id="feComposite20" /> + <feGaussianBlur + stdDeviation="1" + in="result3" + result="result4" + id="feGaussianBlur20" /> + <feComposite + in2="result3" + operator="atop" + id="feComposite21" /> + </filter> + </defs> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <circle + style="fill:#00ff00;filter:url(#filter21)" + id="path1" + r="20" + cy="24" + cx="24" /> + <path + sodipodi:type="star" + style="fill:#ffffff;stroke-width:1;stroke-dasharray:none;fill-opacity:1" + id="path7" + inkscape:flatsided="true" + sodipodi:sides="3" + sodipodi:cx="19.031765" + sodipodi:cy="16.146082" + sodipodi:r1="9.0851498" + sodipodi:r2="4.5425749" + sodipodi:arg1="2.0943951" + sodipodi:arg2="3.1415927" + inkscape:rounded="0" + inkscape:randomized="0" + d="m 14.48919,24.014052 0,-15.7359406 13.627725,7.8679706 z" + inkscape:transform-center-x="-2.2712875" + transform="translate(2.6969476,7.8539181)" /> + </g> +</svg> diff --git a/gui/src/main/resources/icons/play_24.png b/gui/src/main/resources/icons/play_24.png Binary files differnew file mode 100644 index 0000000..11bd88e --- /dev/null +++ b/gui/src/main/resources/icons/play_24.png |
