sclass JG22Labels extends MetaWithChangeListeners { settableWithVar bool showCount = true; settableWithVar bool allowCreation = true; settableWithVar L labels; transient JLabel lblInfo = jlabel(); transient JPanel line = jline(); transient ReliableSingleThread rstUpdate = new(r _update); transient settableWithVar JG22Label creating; cachedVisualize { varLabels().onChangeAndNow(rstUpdate); varCreating().onChangeAndNow(rstUpdate); ret borderless(jscrollHorizontal(line)); } void _update { var labels = labels(); removeAllComponents(line); if (showCount) { setText(lblInfo, nLabels(labels) + (empty(labels) ? "" : ":")); line.add(lblInfo); } fOr (label : labels) line.add(makeLabelComponent(label)); if (creating != null) line.add(creating); else if (allowCreation) line.add(jimageButtonScaledToWidth(16, #1103069, "Add a label", r _addALabel)); revalidate(line); } JG22Label makeLabelComponent(G22Label label) { var lbl = new JG22Label(label); componentPopupMenuItem(lbl.jLabel, "Remove label", rThread { removeLabel(label) }); ret lbl; } bool containsLabel(S text) { ret containsPred(labels, lbl -> lbl.textIs(text)); } swappable void addLabel(S text) {} swappable void removeLabel(G22Label label) {} void _addALabel { new JG22Label lbl; lbl.onDoneEditing(text -> { if (!containsLabel(text)) { print("Adding label: " + text); addLabel(text); } else infoBox("Label already contained"); creating(null); }); focusOnFirstShowVerbose(lbl.focusablePart()); creating(lbl); } }