sclass CollapsibleNorthPanel > MetaWithChangeListeners is Swingable { settableWithVar volatile S sidePanelName; JComponent sideComponent, mainComponent; JSplitPane splitPane; settableWithVar volatile bool expanded; settable double defaultSplitPoint = 0.5; SingleComponentPanel scp = singleComponentPanel(); *(bool *expanded, S *sidePanelName, JComponent *sideComponent, JComponent *mainComponent) { } void expand { setExpanded(true); } void collapse { setExpanded(false); } scaffolded void updateScp() swing { if (expanded) { splitPane().setBottomComponent(mainComponent); scp.set(splitPane()); } else { var collapsedNorthPanel = sidePanelMargins(sidePanelHeading()); scp.set(northAndCenterWithMargin( collapsedNorthPanel, mainComponent)); } } JComponent expandButton() { ret jClickableImage_instantToolTip(#1103076, "Expand " + sidePanelName, r expand); } JComponent collapseButton() { ret jClickableImage_instantToolTip(#1103104, "Collapse " + sidePanelName, r collapse); } // side component plus heading in expanded view JComponent wrappedSideComponent() { var c = northAndCenterWithMargin( sidePanelHeading(), sideComponent ); ret sidePanelMargins(c); } JComponent expandOrCollapseButton() { ret expanded ? collapseButton() : expandButton(); } JComponent sidePanelHeading() { ret rightAlignedLineWithSpacing(5, liveValueLabel(varSidePanelName()), expandOrCollapseButton()); } JSplitPane splitPane() { if (splitPane == null) { splitPane = jvsplit(defaultSplitPoint(), wrappedSideComponent(), null); varExpanded().onChangeAndNow(r updateScp); } ret splitPane; } swappable JComponent sidePanelMargins(JComponent c) { ret c; } visualize { splitPane(); ret scp; } }