diff options
| -rw-r--r-- | base/src/examples/java/bjc/utils/examples/gui/panels/CollapsiblePanelDemo.java | 71 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/gui/panels/CollapsiblePanel.java | 217 |
2 files changed, 288 insertions, 0 deletions
diff --git a/base/src/examples/java/bjc/utils/examples/gui/panels/CollapsiblePanelDemo.java b/base/src/examples/java/bjc/utils/examples/gui/panels/CollapsiblePanelDemo.java new file mode 100644 index 0000000..fed128f --- /dev/null +++ b/base/src/examples/java/bjc/utils/examples/gui/panels/CollapsiblePanelDemo.java @@ -0,0 +1,71 @@ +package bjc.utils.examples.gui.panels; + +import java.awt.BorderLayout; +import java.awt.GridLayout; +import java.awt.Insets; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; + +import bjc.utils.gui.panels.CollapsiblePanel; + +public class CollapsiblePanelDemo { + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); } catch (Exception ignored) {} + + JFrame frame = new JFrame("CollapsiblePanel Demo"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setLayout(new BorderLayout()); + + JPanel content = new JPanel(); + content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS)); + + CollapsiblePanel section1 = new CollapsiblePanel("Search Options"); + // Add some content + section1.add(new JLabel("Keyword:")); + section1.add(new JTextField(20)); + section1.add(new JCheckBox("Case sensitive")); + + // Add custom header bits (e.g. a small gear button) + JButton gear = new JButton("\u2699"); // ⚙ + gear.setMargin(new Insets(0, 4, 0, 4)); + gear.setFocusable(false); + section1.addHeaderComponent(gear); + + CollapsiblePanel section2 = new CollapsiblePanel("Advanced Filters", new GridLayout(0, 2, 4, 4)); + section2.add(new JLabel("From date:")); + section2.add(new JTextField(10)); + section2.add(new JLabel("To date:")); + section2.add(new JTextField(10)); + section2.add(new JLabel("Status:")); + section2.add(new JComboBox<>(new String[]{"Any", "Open", "Closed"})); + section2.setCollapsed(true); // start collapsed + + // Example: a right-aligned "Reset" button in the header + JButton resetBtn = new JButton("Reset"); + resetBtn.setMargin(new Insets(0, 6, 0, 6)); + resetBtn.setFocusable(false); + section2.setHeaderComponent(resetBtn); + + content.add(section1); + content.add(Box.createVerticalStrut(8)); + content.add(section2); + + frame.add(new JScrollPane(content), BorderLayout.CENTER); + frame.setSize(450, 300); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + } +} diff --git a/base/src/main/java/bjc/utils/gui/panels/CollapsiblePanel.java b/base/src/main/java/bjc/utils/gui/panels/CollapsiblePanel.java new file mode 100644 index 0000000..6b4453b --- /dev/null +++ b/base/src/main/java/bjc/utils/gui/panels/CollapsiblePanel.java @@ -0,0 +1,217 @@ +package bjc.utils.gui.panels;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+/**
+ * A JPanel with a clickable header that can collapse/expand its content.
+ *
+ * Features:
+ * - Title + arrow (▶ / ▼)
+ * - Optional custom header component on the right side (e.g., buttons, filters)
+ * - Add children like a normal JPanel (they go into the content area)
+ */
+public class CollapsiblePanel extends JPanel {
+ private static final long serialVersionUID = -5941733171067755926L;
+
+ private final JPanel headerPanel;
+ private final JPanel headerClickableArea;
+ private final JPanel headerExtrasPanel;
+ private final JButton toggleButton;
+ private final JLabel titleLabel;
+ private final JPanel contentPanel;
+
+ private boolean collapsed = false;
+ private boolean internalAdd = false; // guard for addImpl
+
+ /**
+ * Create a new collapsible panel.
+ *
+ * @param title The title for the panel
+ */
+ public CollapsiblePanel(String title) {
+ this(title, new FlowLayout(FlowLayout.LEFT, 4, 4));
+ }
+
+ /**
+ * Create a new collapsible panel, using the following layout manager for the content.
+ *
+ * @param title The title for the panel
+ * @param contentLayout The layout manager for the panel
+ */
+ public CollapsiblePanel(String title, LayoutManager contentLayout) {
+ super(new BorderLayout());
+
+ // === Header ===
+ headerPanel = new JPanel(new BorderLayout(4, 0));
+ headerPanel.setBorder(new EmptyBorder(2, 4, 2, 4));
+ headerPanel.setOpaque(false);
+
+ // Left side: clickable region (arrow + title)
+ headerClickableArea = new JPanel(new BorderLayout(4, 0));
+ headerClickableArea.setOpaque(false);
+
+ toggleButton = new JButton("\u25BC"); // ▼ expanded
+ toggleButton.setMargin(new Insets(0, 4, 0, 4));
+ toggleButton.setFocusable(false);
+ toggleButton.setBorderPainted(false);
+ toggleButton.setContentAreaFilled(false);
+
+ titleLabel = new JLabel(title);
+ titleLabel.setBorder(new EmptyBorder(0, 2, 0, 0));
+ titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));
+
+ headerClickableArea.add(toggleButton, BorderLayout.WEST);
+ headerClickableArea.add(titleLabel, BorderLayout.CENTER);
+
+ // Right side: custom header component container
+ headerExtrasPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 0));
+ headerExtrasPanel.setOpaque(false);
+
+ headerPanel.add(headerClickableArea, BorderLayout.CENTER);
+ headerPanel.add(headerExtrasPanel, BorderLayout.EAST);
+
+ // === Content ===
+ contentPanel = new JPanel(contentLayout);
+
+ // === Wiring: toggle behavior ===
+ toggleButton.addActionListener(e -> setCollapsed(!isCollapsed()));
+
+ MouseAdapter headerClick = new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (SwingUtilities.isLeftMouseButton(e)) {
+ setCollapsed(!isCollapsed());
+ }
+ }
+ };
+ // Only the left side (arrow + title) toggles; extras panel is not clickable for
+ // toggle
+ headerClickableArea.addMouseListener(headerClick);
+ titleLabel.addMouseListener(headerClick);
+ toggleButton.addMouseListener(headerClick);
+
+ // Add header + content to this panel
+ internalAdd = true;
+ add(headerPanel, BorderLayout.NORTH);
+ add(contentPanel, BorderLayout.CENTER);
+ internalAdd = false;
+ }
+
+ /**
+ * Returns the content panel; you can use this if you want direct access.
+ *
+ * @return The content panel
+ */
+ public JPanel getContentPanel() {
+ return contentPanel;
+ }
+
+ /**
+ * Returns the panel that holds extra header components (right side).
+ *
+ * @return The header extras panel
+ */
+ public JPanel getHeaderExtrasPanel() {
+ return headerExtrasPanel;
+ }
+
+ /**
+ * Replace any existing header extras with a single component. Pass null to
+ * clear.
+ *
+ * @param comp The header component to use
+ */
+ public void setHeaderComponent(Component comp) {
+ headerExtrasPanel.removeAll();
+ if (comp != null) {
+ headerExtrasPanel.add(comp);
+ }
+ headerExtrasPanel.revalidate();
+ headerExtrasPanel.repaint();
+ }
+
+ /**
+ * Add an extra component to the header (right side) without clearing existing
+ * ones.
+ *
+ *
+ * @param comp The component to add
+ */
+ public void addHeaderComponent(Component comp) {
+ headerExtrasPanel.add(comp);
+ headerExtrasPanel.revalidate();
+ headerExtrasPanel.repaint();
+ }
+
+ /**
+ * Set the header title text.
+ *
+ * @param title The header title
+ */
+ public void setTitle(String title) {
+ titleLabel.setText(title);
+ }
+
+ /**
+ * Get the header title
+ *
+ * @return The header title
+ */
+ public String getTitle() {
+ return titleLabel.getText();
+ }
+
+ /**
+ * Collapse or expand the content.
+ *
+ * @param collapsed True to collapse the panel, false to expand it
+ */
+ public void setCollapsed(boolean collapsed) {
+ if (this.collapsed == collapsed)
+ return;
+ this.collapsed = collapsed;
+ contentPanel.setVisible(!collapsed);
+ toggleButton.setText(collapsed ? "\u25B6" : "\u25BC"); // ▶ / ▼
+ revalidate();
+ repaint();
+ }
+
+ /**
+ * Check if the panel is collapsed or not.
+ *
+ * @return Is the panel collapsed or not?
+ */
+ public boolean isCollapsed() {
+ return collapsed;
+ }
+
+ /**
+ * Override addImpl so that user-added components go into the content area
+ * instead of directly into the outer BorderLayout.
+ */
+ @Override
+ protected void addImpl(Component comp, Object constraints, int index) {
+ if (internalAdd) {
+ super.addImpl(comp, constraints, index);
+ } else {
+ contentPanel.add(comp, constraints, index);
+ }
+ }
+
+ /**
+ * Convenience factory for a titled, initially-collapsed panel.
+ *
+ * @param title The title for the panel
+ * @param layout The layout manager for the content
+ * @return The panel created
+ */
+ public static CollapsiblePanel collapsed(String title, LayoutManager layout) {
+ CollapsiblePanel p = new CollapsiblePanel(title, layout);
+ p.setCollapsed(true);
+ return p;
+ }
+}
|
