From c743efc3b619f22ee7b020aaf1c26f8961ac3a45 Mon Sep 17 00:00:00 2001 From: ericek111 Date: Wed, 23 Sep 2026 23:21:52 +0000 Subject: [PATCH] Keep the window responsive while the channel tree loads Connecting to a server with ~560 channels froze the UI for over two seconds: every model event queued its own full tree rebuild, and each rebuild expanded row by row with Swing's variable-height layout cache, which measures every row on each expansion. Coalesce model-change bursts into one rebuild and switch the tree to the large-model layout (it already has a fixed row height), which only measures rows on screen. Co-Authored-By: Claude Opus 5.5 --- .../java/com/ts3client/ui/ServerTabConnectionEvents.java | 6 ++++++ .../src/main/java/com/ts3client/ui/ServerTreePanel.java | 3 +++ 2 files changed, 9 insertions(+) diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabConnectionEvents.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabConnectionEvents.java index 7de44e6..d77d70e 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabConnectionEvents.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTabConnectionEvents.java @@ -8,6 +8,7 @@ import com.ts3client.net.TeamspeakConnection; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * Marshals {@link ConnectionListener} callbacks onto the EDT and fans them out to @@ -32,6 +33,8 @@ final class ServerTabConnectionEvents implements ConnectionListener { private final ServerTab tab; private final Listener listener; + private final AtomicBoolean modelChangePending = new AtomicBoolean(); + private TeamspeakConnection conn; private ServerTreePanel treePanel; private ChatPanel chatPanel; @@ -101,7 +104,10 @@ final class ServerTabConnectionEvents implements ConnectionListener { @Override public void onModelChanged() { + // Events arrive in bursts (hundreds while connecting); one rebuild covers all of them. + if (!modelChangePending.compareAndSet(false, true)) return; SwingUtilities.invokeLater(() -> { + modelChangePending.set(false); treePanel.rebuild(); treeActions.renderInfo(); treeActions.refreshGroupsDialog(); diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java index 15644b9..710f30e 100644 --- a/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java +++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ServerTreePanel.java @@ -170,6 +170,9 @@ public final class ServerTreePanel extends JScrollPane { ui.setRightChildIndent(10); } tree.setRowHeight(UIScale.scale(20)); + // Lays out only the rows on screen. The default cache measures every row as it is + // expanded, which freezes the window for seconds on servers with hundreds of channels. + tree.setLargeModel(true); tree.setBackground(Theme.treeBg()); tree.setFont(Theme.uiFont()); ServerTreeCellRenderer renderer = new ServerTreeCellRenderer(id -> id == selfClientId, contacts, settings);