1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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;
}
}
|