diff --git a/ts3-client/README.md b/ts3-client/README.md
index fba03c7..0ce1d36 100644
--- a/ts3-client/README.md
+++ b/ts3-client/README.md
@@ -169,6 +169,18 @@ frontend supplies its own UI and audio backend while reusing `core` unchanged.
- **Pick one in Options → Design**, which also shows a viewer of everything the pack
contains; switching redraws the window right away.
+### Display scaling (Linux)
+- Java2D on X11 (and so under XWayland) only scales by whole numbers, so 125 % or
+ 150 % comes out at 100 %. On Linux the client pins Java2D to 1 and hands the full
+ factor to FlatLaf instead, scaling its own sizes and icons to match.
+- The factor follows the desktop: XSETTINGS `Xft/DPI` (GNOME, Plasma), else KWin's
+ XWayland scale, else `GDK_SCALE`. For crisp text on Wayland, let X11 apps scale
+ themselves (Plasma: *Display Configuration → Legacy Applications → Apply scaling
+ themselves*; GNOME: the `xwayland-native-scaling` mutter feature), otherwise the
+ compositor upscales the window.
+- **Options → Design → Interface scale** overrides it (after a restart);
+ `-Dflatlaf.uiScale=` or `-Dsun.java2d.uiScale=` on the command line bypasses all this.
+
## Requirements
- Java 26+ (developed/tested on Temurin 26)
- The native Opus library on the system:
@@ -269,6 +281,7 @@ swing/ com.ts3client
├── HotkeyActions carries a fired hotkey out on the client
├── NotificationsPanel sound pack + per-action sound/important configuration
├── IconPackPanel icon pack chooser + icon viewer (Options → Design)
+ ├── DisplayScale interface scale on Linux (fractional, via FlatLaf)
├── ConnectDialog connect form
├── BookmarksDialog manage saved servers
├── IdentitiesDialog manage identities (new/import/export/improve)
diff --git a/ts3-client/core/src/main/java/com/ts3client/config/Settings.java b/ts3-client/core/src/main/java/com/ts3client/config/Settings.java
index f67a624..45a8dbc 100644
--- a/ts3-client/core/src/main/java/com/ts3client/config/Settings.java
+++ b/ts3-client/core/src/main/java/com/ts3client/config/Settings.java
@@ -154,6 +154,8 @@ public final class Settings {
/** Look-and-feel variant; see {@link Appearance}. */
public Appearance appearance = Appearance.LIGHT;
+ /** Interface scale factor, 1.0 being 100 %; 0 follows the desktop. Read at startup. */
+ public double uiScale = 0;
// ---- window chrome ----
/** Whether the status bar at the bottom of the window is shown. */
@@ -263,6 +265,7 @@ public final class Settings {
iconPack = props.getProperty("iconPack", iconPack);
iconPackDir = props.getProperty("iconPackDir", iconPackDir);
appearance = parseAppearance(props.getProperty("appearance"), appearance);
+ uiScale = parseD(props.getProperty("uiScale"), uiScale);
showStatusBar = parseB(props.getProperty("showStatusBar"), showStatusBar);
showMasterVolumeSlider = parseB(props.getProperty("showMasterVolumeSlider"), showMasterVolumeSlider);
infoSplitRatio = parseD(props.getProperty("infoSplitRatio"), infoSplitRatio);
@@ -311,6 +314,7 @@ public final class Settings {
props.setProperty("iconPack", iconPack);
props.setProperty("iconPackDir", iconPackDir);
props.setProperty("appearance", appearance.name());
+ props.setProperty("uiScale", Double.toString(uiScale));
props.setProperty("showStatusBar", Boolean.toString(showStatusBar));
props.setProperty("showMasterVolumeSlider", Boolean.toString(showMasterVolumeSlider));
props.setProperty("infoSplitRatio", Double.toString(infoSplitRatio));
diff --git a/ts3-client/swing/src/main/java/com/ts3client/Main.java b/ts3-client/swing/src/main/java/com/ts3client/Main.java
index 6aa28a9..687b190 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/Main.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/Main.java
@@ -1,6 +1,7 @@
package com.ts3client;
import com.ts3client.config.Settings;
+import com.ts3client.ui.DisplayScale;
import com.ts3client.ui.IconTheme;
import com.ts3client.ui.LookAndFeelManager;
import com.ts3client.ui.MainFrame;
@@ -21,6 +22,7 @@ public final class Main {
}
final Settings settings = Settings.load();
+ DisplayScale.configure(settings.uiScale);
SwingUtilities.invokeLater(() -> {
LookAndFeelManager.install(settings.appearance);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarDialog.java
index a1301cc..c588e23 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarDialog.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ClientEntry;
import com.ts3client.net.TeamspeakConnection;
import com.ts3client.net.avatar.Avatar;
@@ -39,16 +41,16 @@ final class AvatarDialog extends JDialog {
JPanel picture = new JPanel(new BorderLayout());
picture.setBorder(BorderFactory.createCompoundBorder(
- BorderFactory.createEmptyBorder(8, 8, 4, 8),
+ new ScaledEmptyBorder(8, 8, 4, 8),
BorderFactory.createLineBorder(Theme.border())));
picture.add(preview, BorderLayout.CENTER);
JLabel hint = new JLabel("Images up to " + (conn.maxAvatarBytes() / 1024)
+ " KiB may be uploaded to this server. Animated GIFs stay animated.");
hint.setFont(Theme.uiSmall());
- hint.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 8));
+ hint.setBorder(new ScaledEmptyBorder(0, 8, 0, 8));
status.setFont(Theme.uiSmall());
- status.setBorder(BorderFactory.createEmptyBorder(2, 8, 0, 8));
+ status.setBorder(new ScaledEmptyBorder(2, 8, 0, 8));
JPanel text = new JPanel();
text.setLayout(new BoxLayout(text, BoxLayout.Y_AXIS));
@@ -57,13 +59,13 @@ final class AvatarDialog extends JDialog {
JPanel buttons = new JPanel();
buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
- buttons.setBorder(BorderFactory.createEmptyBorder(6, 8, 8, 8));
+ buttons.setBorder(new ScaledEmptyBorder(6, 8, 8, 8));
choose.addActionListener(e -> chooseFile());
remove.addActionListener(e -> removeAvatar());
JButton close = new JButton("Close");
close.addActionListener(e -> dispose());
buttons.add(choose);
- buttons.add(Box.createHorizontalStrut(4));
+ buttons.add(Box.createHorizontalStrut(UIScale.scale(4)));
buttons.add(remove);
buttons.add(Box.createHorizontalGlue());
buttons.add(close);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarHtml.java b/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarHtml.java
index 9210f74..6cb76e8 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarHtml.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarHtml.java
@@ -1,5 +1,6 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.avatar.Avatar;
import javax.imageio.ImageIO;
@@ -12,7 +13,8 @@ import java.util.Iterator;
/**
* Renders an avatar as an HTML {@code } for the info views. Swing's HTML renderer
* loads images by URL and plays animated GIFs on its own, so the tag simply points at
- * the repository's on-disk copy, scaled to fit a box while keeping its aspect ratio.
+ * the repository's on-disk copy, drawn at the interface scale and fitted to a box while
+ * keeping its aspect ratio.
*/
final class AvatarHtml {
@@ -25,7 +27,8 @@ final class AvatarHtml {
/** @return the tag, or an empty string if the avatar cannot be shown */
static String imgTag(Avatar avatar, int maxSize) {
if (avatar == null || avatar.file() == null) return "";
- Dimension size = fit(dimensions(avatar.data()), maxSize);
+ Dimension natural = dimensions(avatar.data());
+ Dimension size = fit(natural == null ? null : UIScale.scale(natural), maxSize);
String dims = size == null ? "" : " width='" + size.width + "' height='" + size.height + "'";
return "";
}
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarView.java b/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarView.java
index cbf4903..dcd4960 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarView.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/AvatarView.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.UIScale;
+
import javax.swing.JComponent;
import java.awt.Dimension;
import java.awt.Graphics;
@@ -22,7 +24,7 @@ final class AvatarView extends JComponent {
AvatarView(int boxSize) {
this.boxSize = boxSize;
- setPreferredSize(new Dimension(boxSize, boxSize));
+ setPreferredSize(UIScale.scale(new Dimension(boxSize, boxSize)));
setMinimumSize(getPreferredSize());
}
@@ -47,7 +49,7 @@ final class AvatarView extends JComponent {
imageHeight = image.getHeight(this);
if (imageWidth < 0 || imageHeight < 0) return;
}
- double scale = Math.min(1.0, Math.min((double) getWidth() / imageWidth, (double) getHeight() / imageHeight));
+ double scale = Math.min(UIScale.getUserScaleFactor(), Math.min((double) getWidth() / imageWidth, (double) getHeight() / imageHeight));
int w = Math.max(1, (int) Math.round(imageWidth * scale));
int h = Math.max(1, (int) Math.round(imageHeight * scale));
Graphics2D g2 = (Graphics2D) g.create();
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/AwayMessagesDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/AwayMessagesDialog.java
index 34104c4..8e54da6 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/AwayMessagesDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/AwayMessagesDialog.java
@@ -1,8 +1,9 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.AwayMessages;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -39,13 +40,13 @@ public final class AwayMessagesDialog extends JDialog {
table.setTableHeader(null);
table.setShowGrid(false);
table.setFillsViewportHeight(true);
- table.setRowHeight(Math.max(20, table.getRowHeight()));
+ table.setRowHeight(Math.max(UIScale.scale(20), table.getRowHeight()));
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// Single click selects, double click starts editing — like a renameable list.
table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE);
JScrollPane scroll = new JScrollPane(table);
- scroll.setBorder(BorderFactory.createEmptyBorder(8, 8, 4, 8));
+ scroll.setBorder(new ScaledEmptyBorder(8, 8, 4, 8));
getContentPane().setLayout(new BorderLayout());
getContentPane().add(scroll, BorderLayout.CENTER);
@@ -53,14 +54,14 @@ public final class AwayMessagesDialog extends JDialog {
Dialogs.closeOnEscape(this, this::closeDialog);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- setSize(new Dimension(360, 300));
+ setSize(UIScale.scale(new Dimension(360, 300)));
setLocationRelativeTo(owner);
}
private JPanel buildButtons() {
JPanel buttons = new JPanel();
buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
- buttons.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
+ buttons.setBorder(new ScaledEmptyBorder(0, 8, 8, 8));
addButton(buttons, "Add", this::addEntry);
addButton(buttons, "Remove", this::removeSelected);
buttons.add(Box.createHorizontalGlue());
@@ -72,7 +73,7 @@ public final class AwayMessagesDialog extends JDialog {
JButton b = new JButton(text);
b.addActionListener(e -> action.run());
panel.add(b);
- panel.add(Box.createHorizontalStrut(4));
+ panel.add(Box.createHorizontalStrut(UIScale.scale(4)));
}
private void addEntry() {
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/BanAddDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/BanAddDialog.java
index cd6de71..5cea0f7 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/BanAddDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/BanAddDialog.java
@@ -1,11 +1,12 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.BanReasonPresets;
import com.ts3client.net.ban.BanEntry;
import com.ts3client.net.ban.BanPattern;
import com.ts3client.net.ban.BanRule;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -75,24 +76,24 @@ final class BanAddDialog extends JDialog {
JPanel form = new JPanel();
form.setLayout(new BoxLayout(form, BoxLayout.Y_AXIS));
- form.setBorder(BorderFactory.createEmptyBorder(12, 12, 8, 12));
+ form.setBorder(new ScaledEmptyBorder(12, 12, 8, 12));
form.add(ReasonDialog.label("IP:"));
form.add(ipField);
- form.add(Box.createVerticalStrut(8));
+ form.add(Box.createVerticalStrut(UIScale.scale(8)));
form.add(ReasonDialog.label("Name:"));
form.add(nameField);
- form.add(Box.createVerticalStrut(8));
+ form.add(Box.createVerticalStrut(UIScale.scale(8)));
form.add(patternRow());
- form.add(Box.createVerticalStrut(8));
+ form.add(Box.createVerticalStrut(UIScale.scale(8)));
form.add(ReasonDialog.label("Unique ID:"));
form.add(uidField);
- form.add(Box.createVerticalStrut(8));
+ form.add(Box.createVerticalStrut(UIScale.scale(8)));
form.add(ReasonDialog.label("myTeamSpeak ID:"));
form.add(mytsidField);
- form.add(Box.createVerticalStrut(12));
+ form.add(Box.createVerticalStrut(UIScale.scale(12)));
form.add(reasonRow());
form.add(reasonField);
- form.add(Box.createVerticalStrut(8));
+ form.add(Box.createVerticalStrut(UIScale.scale(8)));
form.add(ReasonDialog.label("Duration:"));
form.add(duration);
@@ -119,7 +120,7 @@ final class BanAddDialog extends JDialog {
row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
row.setAlignmentX(Component.LEFT_ALIGNMENT);
row.add(new JLabel("Interpret IP/Name as:"));
- row.add(Box.createHorizontalStrut(8));
+ row.add(Box.createHorizontalStrut(UIScale.scale(8)));
row.add(patternBox);
row.add(Box.createHorizontalGlue());
ReasonDialog.fixHeight(row);
@@ -137,13 +138,13 @@ final class BanAddDialog extends JDialog {
JPanel row = new JPanel();
row.setLayout(new BoxLayout(row, BoxLayout.X_AXIS));
row.setAlignmentX(Component.LEFT_ALIGNMENT);
- row.setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));
+ row.setBorder(new ScaledEmptyBorder(0, 0, 4, 0));
row.add(new JLabel("Reason:"));
row.add(Box.createHorizontalGlue());
row.add(new JLabel("Preset:"));
- row.add(Box.createHorizontalStrut(6));
+ row.add(Box.createHorizontalStrut(UIScale.scale(6)));
row.add(presetBox);
- row.add(Box.createHorizontalStrut(6));
+ row.add(Box.createHorizontalStrut(UIScale.scale(6)));
row.add(save);
ReasonDialog.fixHeight(row);
return row;
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/BanDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/BanDialog.java
index e776518..6b7a688 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/BanDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/BanDialog.java
@@ -1,8 +1,9 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ban.BanRule;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -32,12 +33,12 @@ final class BanDialog extends JDialog {
JPanel form = new JPanel();
form.setLayout(new BoxLayout(form, BoxLayout.Y_AXIS));
- form.setBorder(BorderFactory.createEmptyBorder(12, 12, 8, 12));
+ form.setBorder(new ScaledEmptyBorder(12, 12, 8, 12));
form.add(ReasonDialog.label("Ban " + nickname + " from the server."));
- form.add(Box.createVerticalStrut(8));
+ form.add(Box.createVerticalStrut(UIScale.scale(8)));
form.add(ReasonDialog.label("Reason:"));
form.add(reasonField);
- form.add(Box.createVerticalStrut(8));
+ form.add(Box.createVerticalStrut(UIScale.scale(8)));
form.add(ReasonDialog.label("Duration:"));
form.add(duration);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/BanDurationPicker.java b/ts3-client/swing/src/main/java/com/ts3client/ui/BanDurationPicker.java
index 1650ed2..57a5adb 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/BanDurationPicker.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/BanDurationPicker.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.UIScale;
+
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -42,9 +44,9 @@ final class BanDurationPicker extends JPanel {
max.addActionListener(e -> setSeconds(maxSeconds));
add(amount);
- add(Box.createHorizontalStrut(4));
+ add(Box.createHorizontalStrut(UIScale.scale(4)));
add(unit);
- add(Box.createHorizontalStrut(4));
+ add(Box.createHorizontalStrut(UIScale.scale(4)));
add(max);
add(Box.createHorizontalGlue());
ReasonDialog.fixHeight(this);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/BanListDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/BanListDialog.java
index 226e72d..268d737 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/BanListDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/BanListDialog.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.BanReasonPresets;
import com.ts3client.config.Settings;
import com.ts3client.net.ServerModel;
@@ -8,7 +10,6 @@ import com.ts3client.net.ban.BanEntry;
import com.ts3client.net.ban.BanRule;
import javax.swing.AbstractAction;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -122,9 +123,9 @@ final class BanListDialog extends JDialog {
this.settings = settings;
this.presets = presets;
- JPanel content = new JPanel(new BorderLayout(0, 6));
+ JPanel content = new JPanel(new BorderLayout(0, UIScale.scale(6)));
content.setBackground(Theme.windowBg());
- content.setBorder(BorderFactory.createEmptyBorder(10, 12, 10, 12));
+ content.setBorder(new ScaledEmptyBorder(10, 12, 10, 12));
content.add(buildToolbar(), BorderLayout.NORTH);
content.add(buildTable(), BorderLayout.CENTER);
content.add(buildBottom(), BorderLayout.SOUTH);
@@ -133,7 +134,7 @@ final class BanListDialog extends JDialog {
serverNameChanged();
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- setPreferredSize(new Dimension(920, 460));
+ setPreferredSize(UIScale.scale(new Dimension(920, 460)));
pack();
setLocationRelativeTo(owner);
reload();
@@ -172,18 +173,18 @@ final class BanListDialog extends JDialog {
searchField.setMaximumSize(new Dimension(Integer.MAX_VALUE, searchField.getPreferredSize().height));
bar.add(addButton);
- bar.add(Box.createHorizontalStrut(6));
+ bar.add(Box.createHorizontalStrut(UIScale.scale(6)));
bar.add(removeButton);
- bar.add(Box.createHorizontalStrut(6));
+ bar.add(Box.createHorizontalStrut(UIScale.scale(6)));
bar.add(editButton);
- bar.add(Box.createHorizontalStrut(16));
+ bar.add(Box.createHorizontalStrut(UIScale.scale(16)));
bar.add(searchField);
return bar;
}
private JScrollPane buildTable() {
table.setFont(Theme.uiFont());
- table.setRowHeight(20);
+ table.setRowHeight(UIScale.scale(20));
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setFillsViewportHeight(true);
table.setAutoCreateColumnsFromModel(false);
@@ -248,9 +249,9 @@ final class BanListDialog extends JDialog {
options.setLayout(new BoxLayout(options, BoxLayout.X_AXIS));
options.setOpaque(false);
options.add(reload);
- options.add(Box.createHorizontalStrut(12));
+ options.add(Box.createHorizontalStrut(UIScale.scale(12)));
options.add(onlyOwn);
- options.add(Box.createHorizontalStrut(12));
+ options.add(Box.createHorizontalStrut(UIScale.scale(12)));
options.add(highlightOwn);
options.add(Box.createHorizontalGlue());
@@ -260,7 +261,7 @@ final class BanListDialog extends JDialog {
closeRow.setOpaque(false);
closeRow.add(close, BorderLayout.EAST);
- JPanel bottom = new JPanel(new BorderLayout(0, 8));
+ JPanel bottom = new JPanel(new BorderLayout(0, UIScale.scale(8)));
bottom.setOpaque(false);
bottom.add(status, BorderLayout.NORTH);
bottom.add(options, BorderLayout.CENTER);
@@ -659,7 +660,7 @@ final class BanListDialog extends JDialog {
g2.setColor(Theme.chatEvent());
g2.setFont(getFont().deriveFont(Font.ITALIC));
int y = (getHeight() + g2.getFontMetrics().getAscent() - g2.getFontMetrics().getDescent()) / 2;
- g2.drawString("Search", getInsets().left + 2, y);
+ g2.drawString("Search", getInsets().left + UIScale.scale(2), y);
g2.dispose();
}
}
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/BookmarksDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/BookmarksDialog.java
index 933ef34..b76edcf 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/BookmarksDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/BookmarksDialog.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Bookmark;
import com.ts3client.config.Bookmarks;
import com.ts3client.config.IdentityStore;
@@ -7,7 +9,6 @@ import com.ts3client.config.Settings;
import com.ts3client.teamspeak.TeamSpeakImporter;
import com.ts3client.teamspeak.TeamSpeakSettingsDb;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
@@ -81,10 +82,10 @@ public final class BookmarksDialog extends JDialog {
details = buildDetails();
JScrollPane listScroll = new JScrollPane(list);
- listScroll.setPreferredSize(new Dimension(180, 320));
+ listScroll.setPreferredSize(UIScale.scale(new Dimension(180, 320)));
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, listScroll, details);
- split.setBorder(BorderFactory.createEmptyBorder(8, 8, 4, 8));
+ split.setBorder(new ScaledEmptyBorder(8, 8, 4, 8));
split.setResizeWeight(0.3);
split.setContinuousLayout(true);
@@ -105,7 +106,7 @@ public final class BookmarksDialog extends JDialog {
persist();
}
});
- setSize(new Dimension(640, 400));
+ setSize(UIScale.scale(new Dimension(640, 400)));
setLocationRelativeTo(owner);
}
@@ -113,7 +114,7 @@ public final class BookmarksDialog extends JDialog {
private JPanel buildDetails() {
JPanel panel = new JPanel(new GridBagLayout());
- panel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
+ panel.setBorder(new ScaledEmptyBorder(0, 8, 0, 0));
channel.setToolTipText("Channel path, e.g. \"Lobby/Games\", or a channel id. "
+ "Leave empty for the server's default channel.");
@@ -143,7 +144,7 @@ public final class BookmarksDialog extends JDialog {
private void addRow(JPanel panel, int row, String text, java.awt.Component field) {
GridBagConstraints c = new GridBagConstraints();
c.gridy = row;
- c.insets = new Insets(2, 0, 2, 4);
+ c.insets = UIScale.scale(new Insets(2, 0, 2, 4));
c.anchor = GridBagConstraints.WEST;
c.gridx = 0;
panel.add(new JLabel(text), c);
@@ -156,7 +157,7 @@ public final class BookmarksDialog extends JDialog {
private JPanel buildButtons() {
JPanel buttons = new JPanel();
buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
- buttons.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
+ buttons.setBorder(new ScaledEmptyBorder(0, 8, 8, 8));
addButton(buttons, "Add", this::addBookmark);
addButton(buttons, "Remove", this::removeSelected);
if (TeamSpeakSettingsDb.locate() != null) {
@@ -174,7 +175,7 @@ public final class BookmarksDialog extends JDialog {
JButton b = new JButton(text);
b.addActionListener(e -> action.run());
panel.add(b);
- panel.add(Box.createHorizontalStrut(4));
+ panel.add(Box.createHorizontalStrut(UIScale.scale(4)));
return b;
}
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAdvancedPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAdvancedPanel.java
index 5084af6..0ed4399 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAdvancedPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAdvancedPanel.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ChannelSettings;
import javax.swing.BorderFactory;
@@ -47,7 +49,7 @@ final class ChannelAdvancedPanel extends JPanel implements ChannelDialog.Tab {
ChannelAdvancedPanel() {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
- setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
phoneticName.setToolTipText("Set phonetic nickname used for this channel by text-to-speech.");
deleteDelay.setToolTipText("
Delay in seconds after temporary channels are deleted"
@@ -79,12 +81,12 @@ final class ChannelAdvancedPanel extends JPanel implements ChannelDialog.Tab {
other.setAlignmentX(0f);
fixHeight(other);
add(other);
- add(Box.createVerticalStrut(6));
+ add(Box.createVerticalStrut(UIScale.scale(6)));
JPanel limits = new JPanel();
limits.setLayout(new BoxLayout(limits, BoxLayout.X_AXIS));
limits.add(limitBox("Max Users", new JRadioButton[]{maxUsersUnlimited, maxUsersLimited}, maxUsers));
- limits.add(Box.createHorizontalStrut(8));
+ limits.add(Box.createHorizontalStrut(UIScale.scale(8)));
limits.add(limitBox("Family Max Users",
new JRadioButton[]{familyInherited, familyUnlimited, familyLimited}, familyMaxUsers));
limits.setAlignmentX(0f);
@@ -140,7 +142,7 @@ final class ChannelAdvancedPanel extends JPanel implements ChannelDialog.Tab {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createTitledBorder("Other Settings"));
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(3, 4, 3, 4);
+ c.insets = UIScale.scale(new Insets(3, 4, 3, 4));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
@@ -155,7 +157,7 @@ final class ChannelAdvancedPanel extends JPanel implements ChannelDialog.Tab {
c.gridy = 1;
c.weightx = 0;
panel.add(new JLabel("Delete delay:"), c);
- JPanel delay = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
+ JPanel delay = new JPanel(new FlowLayout(FlowLayout.LEFT, UIScale.scale(4), 0));
delay.add(deleteDelay);
delay.add(deleteDelayMax);
c.gridx = 1;
@@ -183,7 +185,7 @@ final class ChannelAdvancedPanel extends JPanel implements ChannelDialog.Tab {
box.add(radio);
continue;
}
- JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
+ JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT, UIScale.scale(6), 0));
row.setAlignmentX(0f);
row.add(radio);
row.add(spinner);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAudioPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAudioPanel.java
index d7ccf79..46479b9 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAudioPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelAudioPanel.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ChannelSettings;
import javax.swing.BorderFactory;
@@ -49,8 +51,8 @@ final class ChannelAudioPanel extends JPanel implements ChannelDialog.Tab {
private final JPanel customSettings = new JPanel(new GridBagLayout());
ChannelAudioPanel() {
- super(new BorderLayout(8, 8));
- setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ super(new BorderLayout(UIScale.scale(8), UIScale.scale(8)));
+ setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
ButtonGroup presets = new ButtonGroup();
presets.add(voiceMobile);
@@ -126,7 +128,7 @@ final class ChannelAudioPanel extends JPanel implements ChannelDialog.Tab {
private JPanel customBox() {
customSettings.setBorder(BorderFactory.createTitledBorder("Custom Settings"));
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(4, 4, 4, 4);
+ c.insets = UIScale.scale(new Insets(4, 4, 4, 4));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
@@ -158,7 +160,7 @@ final class ChannelAudioPanel extends JPanel implements ChannelDialog.Tab {
}
private JPanel bandwidthRow() {
- JPanel row = new JPanel(new BorderLayout(6, 0));
+ JPanel row = new JPanel(new BorderLayout(UIScale.scale(6), 0));
row.add(new JLabel("Bandwidth usage:"), BorderLayout.WEST);
row.add(bandwidth, BorderLayout.CENTER);
return row;
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelDialog.java
index 2f26f11..293c147 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelDialog.java
@@ -1,10 +1,11 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ChannelNode;
import com.ts3client.net.ChannelSettings;
import com.ts3client.net.TeamspeakConnection;
-import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
@@ -144,19 +145,19 @@ final class ChannelDialog extends JDialog {
// The tabs need no more than their natural height, so they sit at the bottom
// with the buttons and every spare pixel goes to the description instead.
- JPanel bottom = new JPanel(new BorderLayout(0, 6));
+ JPanel bottom = new JPanel(new BorderLayout(0, UIScale.scale(6)));
bottom.add(tabs, BorderLayout.CENTER);
bottom.add(buttons(), BorderLayout.SOUTH);
- getContentPane().setLayout(new BorderLayout(0, 6));
+ getContentPane().setLayout(new BorderLayout(0, UIScale.scale(6)));
getContentPane().add(header(), BorderLayout.CENTER);
getContentPane().add(bottom, BorderLayout.SOUTH);
setEnabledForLoading(false);
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- setSize(new Dimension(560, 620));
- setMinimumSize(new Dimension(480, 520));
+ setSize(UIScale.scale(new Dimension(560, 620)));
+ setMinimumSize(UIScale.scale(new Dimension(480, 520)));
setLocationRelativeTo(owner);
if (channel != null) load();
@@ -167,9 +168,9 @@ final class ChannelDialog extends JDialog {
private JPanel header() {
JPanel panel = new JPanel(new GridBagLayout());
- panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 0, 8));
+ panel.setBorder(new ScaledEmptyBorder(8, 8, 0, 8));
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(3, 4, 3, 4);
+ c.insets = UIScale.scale(new Insets(3, 4, 3, 4));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
@@ -248,7 +249,7 @@ final class ChannelDialog extends JDialog {
private JPanel iconField() {
iconButton.setToolTipText("Set channel icon.");
- iconButton.setPreferredSize(new Dimension(26, 24));
+ iconButton.setPreferredSize(UIScale.scale(new Dimension(26, 24)));
iconButton.setMargin(new Insets(1, 1, 1, 1));
iconButton.addActionListener(a -> chooseIcon());
iconButton.addMouseListener(new MouseAdapter() {
@@ -262,7 +263,7 @@ final class ChannelDialog extends JDialog {
if (e.isPopupTrigger()) showIconMenu(e);
}
});
- JPanel row = new JPanel(new BorderLayout(4, 0));
+ JPanel row = new JPanel(new BorderLayout(UIScale.scale(4), 0));
row.add(new JLabel("Icon:"), BorderLayout.WEST);
row.add(iconButton, BorderLayout.CENTER);
return row;
@@ -273,7 +274,7 @@ final class ChannelDialog extends JDialog {
ok.addActionListener(a -> save());
cancel.addActionListener(a -> dispose());
JPanel panel = new JPanel(new BorderLayout());
- panel.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
+ panel.setBorder(new ScaledEmptyBorder(0, 8, 8, 8));
JPanel right = new JPanel();
right.add(ok);
right.add(cancel);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelPermissionsPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelPermissionsPanel.java
index 46b8ab1..00e978b 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelPermissionsPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelPermissionsPanel.java
@@ -1,5 +1,8 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
+
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
@@ -49,18 +52,18 @@ final class ChannelPermissionsPanel extends JPanel {
ChannelPermissionsPanel() {
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
- setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
JPanel groups = new JPanel();
groups.setLayout(new BoxLayout(groups, BoxLayout.X_AXIS));
groups.add(group("Regular Needed Powers", REGULAR));
- groups.add(Box.createHorizontalStrut(8));
+ groups.add(Box.createHorizontalStrut(UIScale.scale(8)));
groups.add(group("File Transfer Needed Powers", FILE_TRANSFER));
groups.setAlignmentX(0f);
add(groups);
status.setAlignmentX(0f);
- status.setBorder(BorderFactory.createEmptyBorder(6, 2, 0, 2));
+ status.setBorder(new ScaledEmptyBorder(6, 2, 0, 2));
add(status);
add(Box.createVerticalGlue());
setEditable(false);
@@ -119,7 +122,7 @@ final class ChannelPermissionsPanel extends JPanel {
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createTitledBorder(title));
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(3, 4, 3, 4);
+ c.insets = UIScale.scale(new Insets(3, 4, 3, 4));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
int row = 0;
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelStandardPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelStandardPanel.java
index 3f4aea7..8a09d60 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelStandardPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ChannelStandardPanel.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ChannelNode;
import com.ts3client.net.ChannelSettings;
@@ -45,8 +47,8 @@ final class ChannelStandardPanel extends JPanel implements ChannelDialog.Tab {
* excluding the channel being edited
*/
ChannelStandardPanel(List siblings) {
- super(new BorderLayout(8, 8));
- setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
+ super(new BorderLayout(UIScale.scale(8), UIScale.scale(8)));
+ setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
temporary.setToolTipText("Channel will be deleted when the last user left.");
semiPermanent.setToolTipText("Channel exists until server is restarted.");
@@ -67,7 +69,7 @@ final class ChannelStandardPanel extends JPanel implements ChannelDialog.Tab {
for (ChannelNode sibling : siblings) order.addElement(new SortEntry(sibling.id, sibling.name));
sortAfter.setModel(order);
sortAfter.setToolTipText("Channel will be sorted below this channel.");
- sortAfter.setPreferredSize(new Dimension(200, sortAfter.getPreferredSize().height));
+ sortAfter.setPreferredSize(new Dimension(UIScale.scale(200), sortAfter.getPreferredSize().height));
Spinners.compact(talkPower);
add(typeBox(), BorderLayout.WEST);
@@ -139,10 +141,10 @@ final class ChannelStandardPanel extends JPanel implements ChannelDialog.Tab {
}
JSeparator line = new JSeparator();
line.setAlignmentX(0f);
- line.setMaximumSize(new Dimension(Integer.MAX_VALUE, 8));
- box.add(Box.createVerticalStrut(4));
+ line.setMaximumSize(new Dimension(Integer.MAX_VALUE, UIScale.scale(8)));
+ box.add(Box.createVerticalStrut(UIScale.scale(4)));
box.add(line);
- box.add(Box.createVerticalStrut(4));
+ box.add(Box.createVerticalStrut(UIScale.scale(4)));
defaultChannel.setAlignmentX(0f);
box.add(defaultChannel);
box.add(Box.createVerticalGlue());
@@ -154,7 +156,7 @@ final class ChannelStandardPanel extends JPanel implements ChannelDialog.Tab {
sort.setBorder(BorderFactory.createTitledBorder("Sort This Channel After:"));
sort.add(sortAfter, BorderLayout.CENTER);
- JPanel moderation = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 2));
+ JPanel moderation = new JPanel(new FlowLayout(FlowLayout.LEFT, UIScale.scale(6), UIScale.scale(2)));
moderation.setBorder(BorderFactory.createTitledBorder("Moderation"));
JLabel label = new JLabel("Needed Talk Power:");
label.setToolTipText("Talk Power required to speak in this channel.");
@@ -168,7 +170,7 @@ final class ChannelStandardPanel extends JPanel implements ChannelDialog.Tab {
c.gridx = 0;
c.fill = GridBagConstraints.HORIZONTAL;
c.weightx = 1;
- c.insets = new Insets(0, 0, 6, 0);
+ c.insets = UIScale.scale(new Insets(0, 0, 6, 0));
column.add(sort, c);
column.add(moderation, c);
c.weighty = 1;
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ChatPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ChatPanel.java
index a1d7ee0..b274786 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ChatPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ChatPanel.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.text.BBCode;
import com.ts3client.text.TsLink;
@@ -96,8 +98,8 @@ public final class ChatPanel extends JPanel {
dragReorder.attach(tabs);
add(tabs, BorderLayout.CENTER);
- JPanel bottom = new JPanel(new BorderLayout(4, 0));
- bottom.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
+ JPanel bottom = new JPanel(new BorderLayout(UIScale.scale(4), 0));
+ bottom.setBorder(new ScaledEmptyBorder(4, 0, 0, 0));
bottom.add(input, BorderLayout.CENTER);
add(bottom, BorderLayout.SOUTH);
@@ -345,7 +347,8 @@ public final class ChatPanel extends JPanel {
final int clientId;
private String clientUniqueId;
private String title;
- final JEditorPane log = HtmlStyles.pane("font-family:sans-serif; font-size:11px; margin:4px 6px;");
+ final JEditorPane log = HtmlStyles.pane("font-family:sans-serif; font-size:11px; margin:"
+ + HtmlStyles.px(4) + " " + HtmlStyles.px(6) + ";");
final JScrollPane scroll;
/** Set for a static-content tab (e.g. a moved-out description); null for a conversation. */
final String noteKey;
@@ -408,17 +411,17 @@ public final class ChatPanel extends JPanel {
Component header(boolean closable) {
// Only the height is pinned: a fixed width would freeze the tab at the width of
// whatever name it was built with, clipping longer ones (and the bold unread font).
- JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0)) {
+ JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT, UIScale.scale(2), 0)) {
@Override
public Dimension getPreferredSize() {
Dimension d = super.getPreferredSize();
- return new Dimension(d.width, Math.max(d.height, HEADER_HEIGHT));
+ return new Dimension(d.width, Math.max(d.height, UIScale.scale(HEADER_HEIGHT)));
}
};
p.setOpaque(false);
titleLabel = new JLabel(title, icon, JLabel.LEADING);
titleLabel.setFont(Theme.uiFont());
- titleLabel.setIconTextGap(3);
+ titleLabel.setIconTextGap(UIScale.scale(3));
p.add(titleLabel);
dragReorder.attach(p);
dragReorder.attach(titleLabel);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVersionPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVersionPanel.java
index 5073e13..fa002af 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVersionPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVersionPanel.java
@@ -1,9 +1,10 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.ClientVersion;
import com.ts3client.config.Settings;
-import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
@@ -45,10 +46,10 @@ final class ClientVersionPanel extends JPanel {
private boolean filling;
ClientVersionPanel(Settings settings) {
- super(new BorderLayout(0, 8));
+ super(new BorderLayout(0, UIScale.scale(8)));
this.settings = settings;
- setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
+ setBorder(new ScaledEmptyBorder(12, 12, 12, 12));
add(buildForm(), BorderLayout.NORTH);
load();
@@ -67,7 +68,7 @@ final class ClientVersionPanel extends JPanel {
private JComponent buildForm() {
JPanel p = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(2, 2, 2, 2);
+ c.insets = UIScale.scale(new Insets(2, 2, 2, 2));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
@@ -110,7 +111,7 @@ final class ClientVersionPanel extends JPanel {
c.gridx = 0;
c.gridy = row;
c.gridwidth = 2;
- p.add(new JLabel("The three fields belong together: the "
+ p.add(new JLabel("The three fields belong together: the "
+ "signature is TeamSpeak's, over that version and operating system. A server "
+ "answers \"client is modified\" and refuses the connection when it does not "
+ "accept the version — made up, or older than the server's minimum — so prefer "
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVolumeDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVolumeDialog.java
index 754ce63..6f00aec 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVolumeDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ClientVolumeDialog.java
@@ -1,9 +1,10 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ClientEntry;
import com.ts3client.net.TeamspeakConnection;
-import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
@@ -41,11 +42,11 @@ final class ClientVolumeDialog extends JDialog {
slider.addChangeListener(e -> onChanged());
FormPanel.limitWidth(value, 60);
- JPanel content = new JPanel(new BorderLayout(8, 8));
- content.setBorder(BorderFactory.createEmptyBorder(12, 12, 8, 12));
+ JPanel content = new JPanel(new BorderLayout(UIScale.scale(8), UIScale.scale(8)));
+ content.setBorder(new ScaledEmptyBorder(12, 12, 8, 12));
content.add(new JLabel("Volume Modifier"), BorderLayout.NORTH);
content.add(FormPanel.sliderWithLabel(slider, value), BorderLayout.CENTER);
- JLabel hint = new JLabel("Values greater than +6 dB will cause clipping "
+ JLabel hint = new JLabel("Values greater than +6 dB will cause clipping "
+ "and distortion that will negatively affect your audio quality. It is advised to choose "
+ "lower values.");
hint.setForeground(Theme.chatSystem());
@@ -67,7 +68,7 @@ final class ClientVolumeDialog extends JDialog {
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
- setMinimumSize(new Dimension(360, getHeight()));
+ setMinimumSize(new Dimension(UIScale.scale(360), getHeight()));
setLocationRelativeTo(owner);
}
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectDialog.java
index a7d2dca..8cae869 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectDialog.java
@@ -1,9 +1,10 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.IdentityStore;
import com.ts3client.config.Settings;
-import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
@@ -49,9 +50,9 @@ public final class ConnectDialog extends JDialog {
identityChooser = new IdentityChooser(identities, false, settings.defaultIdentityId);
JPanel form = new JPanel(new GridBagLayout());
- form.setBorder(BorderFactory.createEmptyBorder(12, 12, 8, 12));
+ form.setBorder(new ScaledEmptyBorder(12, 12, 8, 12));
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(4, 4, 4, 4);
+ c.insets = UIScale.scale(new Insets(4, 4, 4, 4));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
@@ -83,7 +84,7 @@ public final class ConnectDialog extends JDialog {
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
- setMinimumSize(new Dimension(340, getHeight()));
+ setMinimumSize(new Dimension(UIScale.scale(340), getHeight()));
setLocationRelativeTo(owner);
}
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectionInfoDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectionInfoDialog.java
index f709051..e3c28b5 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectionInfoDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ConnectionInfoDialog.java
@@ -1,9 +1,10 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ConnectionStats;
import com.ts3client.net.TeamspeakConnection;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
@@ -62,9 +63,9 @@ public final class ConnectionInfoDialog extends JDialog {
this.conn = conn;
this.clientId = clientId;
- JPanel content = new JPanel(new BorderLayout(0, 10));
+ JPanel content = new JPanel(new BorderLayout(0, UIScale.scale(10)));
content.setBackground(Theme.windowBg());
- content.setBorder(BorderFactory.createEmptyBorder(12, 14, 12, 14));
+ content.setBorder(new ScaledEmptyBorder(12, 14, 12, 14));
content.add(buildSummary(), BorderLayout.NORTH);
content.add(buildTabs(), BorderLayout.CENTER);
content.add(buildButtons(), BorderLayout.SOUTH);
@@ -138,7 +139,7 @@ public final class ConnectionInfoDialog extends JDialog {
lc.gridx = 0;
lc.gridy = row;
lc.anchor = GridBagConstraints.WEST;
- lc.insets = new Insets(2, 0, 2, 16);
+ lc.insets = UIScale.scale(new Insets(2, 0, 2, 16));
JLabel key = new JLabel(label);
key.setFont(Theme.uiFont());
key.setForeground(new Color(0x5A6B7B));
@@ -150,7 +151,7 @@ public final class ConnectionInfoDialog extends JDialog {
vc.weightx = 1;
vc.anchor = GridBagConstraints.WEST;
vc.fill = GridBagConstraints.HORIZONTAL;
- vc.insets = new Insets(2, 0, 2, 0);
+ vc.insets = UIScale.scale(new Insets(2, 0, 2, 0));
grid.add(valueLabel, vc);
}
@@ -256,7 +257,7 @@ public final class ConnectionInfoDialog extends JDialog {
KindTab() {
super(new GridBagLayout());
setBackground(Theme.windowBg());
- setBorder(BorderFactory.createEmptyBorder(10, 12, 10, 12));
+ setBorder(new ScaledEmptyBorder(10, 12, 10, 12));
int row = 0;
addRow(this, row++, "Packet loss", packetLoss);
addRow(this, row++, "Packets (↑/↓)", packets);
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ContactDefaultsDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ContactDefaultsDialog.java
index 1c05ba3..206845e 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ContactDefaultsDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ContactDefaultsDialog.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Settings;
import com.ts3client.contacts.ContactDefaults;
import com.ts3client.contacts.ContactType;
@@ -50,13 +52,13 @@ final class ContactDefaultsDialog extends JDialog {
JPanel content = new JPanel();
content.setLayout(new javax.swing.BoxLayout(content, javax.swing.BoxLayout.Y_AXIS));
- content.setBorder(BorderFactory.createEmptyBorder(12, 12, 8, 12));
+ content.setBorder(new ScaledEmptyBorder(12, 12, 8, 12));
JLabel intro = new JLabel("Configure default settings when adding a client to the Contact Manager");
intro.setAlignmentX(LEFT_ALIGNMENT);
content.add(intro);
- content.add(javax.swing.Box.createVerticalStrut(10));
+ content.add(javax.swing.Box.createVerticalStrut(UIScale.scale(10)));
content.add(buildFlags());
- content.add(javax.swing.Box.createVerticalStrut(10));
+ content.add(javax.swing.Box.createVerticalStrut(UIScale.scale(10)));
content.add(buildDisplays());
JPanel buttons = new JPanel(new FlowLayout(FlowLayout.RIGHT));
@@ -157,7 +159,7 @@ final class ContactDefaultsDialog extends JDialog {
GridBagConstraints c = new GridBagConstraints();
c.gridx = x;
c.gridy = y;
- c.insets = new Insets(1, 6, 1, 6);
+ c.insets = UIScale.scale(new Insets(1, 6, 1, 6));
c.anchor = GridBagConstraints.CENTER;
return c;
}
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsDialog.java
index 8df73e1..e066840 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsDialog.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Settings;
import com.ts3client.contacts.Contact;
import com.ts3client.contacts.ContactStore;
@@ -8,7 +10,6 @@ import com.ts3client.contacts.NicknameDisplay;
import com.ts3client.teamspeak.TeamSpeakSettingsDb;
import com.ts3client.text.TsLink;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
@@ -99,7 +100,7 @@ final class ContactsDialog extends JDialog {
private final JCheckBox ignoreAway = new JCheckBox("Hide away message");
private final JRadioButton allowWhisper = new JRadioButton("Allow whisper");
private final JRadioButton denyWhisper = new JRadioButton("Deny whisper");
- private final JPanel details = new JPanel(new BorderLayout(0, 8));
+ private final JPanel details = new JPanel(new BorderLayout(0, UIScale.scale(8)));
/** Set while the fields are being filled from a contact, so that does not count as an edit. */
private boolean filling;
@@ -110,22 +111,22 @@ final class ContactsDialog extends JDialog {
this.settings = settings;
buildTree();
- JPanel left = new JPanel(new BorderLayout(0, 4));
- left.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 4));
+ JPanel left = new JPanel(new BorderLayout(0, UIScale.scale(4)));
+ left.setBorder(new ScaledEmptyBorder(8, 8, 8, 4));
JLabel hint = new JLabel("Drag & Drop users here");
hint.setForeground(Theme.chatSystem());
left.add(hint, BorderLayout.NORTH);
JScrollPane scroll = new JScrollPane(tree);
- scroll.setPreferredSize(new Dimension(300, 340));
+ scroll.setPreferredSize(UIScale.scale(new Dimension(300, 340)));
left.add(scroll, BorderLayout.CENTER);
- details.setBorder(BorderFactory.createEmptyBorder(8, 4, 8, 8));
+ details.setBorder(new ScaledEmptyBorder(8, 4, 8, 8));
details.add(buildNicknameRow(), BorderLayout.NORTH);
details.add(buildPages(), BorderLayout.CENTER);
JPanel buttons = new JPanel(new BorderLayout());
- buttons.setBorder(BorderFactory.createEmptyBorder(0, 8, 8, 8));
- JPanel leftButtons = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
+ buttons.setBorder(new ScaledEmptyBorder(0, 8, 8, 8));
+ JPanel leftButtons = new JPanel(new FlowLayout(FlowLayout.LEFT, UIScale.scale(6), 0));
JButton defaults = new JButton("Set Defaults");
defaults.setToolTipText("Set default display, ignore and hide options");
defaults.addActionListener(e -> new ContactDefaultsDialog(this, settings).setVisible(true));
@@ -160,7 +161,7 @@ final class ContactsDialog extends JDialog {
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
- setMinimumSize(new Dimension(620, 400));
+ setMinimumSize(UIScale.scale(new Dimension(620, 400)));
setLocationRelativeTo(owner);
}
@@ -172,7 +173,7 @@ final class ContactsDialog extends JDialog {
root.add(neutral);
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
- tree.setRowHeight(20);
+ tree.setRowHeight(UIScale.scale(20));
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setCellRenderer(new ContactRenderer());
tree.addTreeSelectionListener(e -> showSelected());
@@ -301,7 +302,7 @@ final class ContactsDialog extends JDialog {
private JComponent buildNicknameRow() {
JPanel row = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(2, 2, 2, 2);
+ c.insets = UIScale.scale(new Insets(2, 2, 2, 2));
c.anchor = GridBagConstraints.WEST;
c.gridy = 0;
c.gridx = 0;
@@ -325,7 +326,7 @@ final class ContactsDialog extends JDialog {
JTabbedPane pages = new JTabbedPane();
pages.addTab("General", page(
radioGroup(neutralRadio, blockedRadio, friendRadio),
- Box.createVerticalStrut(8),
+ Box.createVerticalStrut(UIScale.scale(8)),
labelled("Phonetic Nickname:", phoneticField)));
neutralRadio.setToolTipText("Mark as neutral");
blockedRadio.setToolTipText("Mark as blocked");
@@ -375,7 +376,7 @@ final class ContactsDialog extends JDialog {
private static JPanel page(Component... rows) {
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
- p.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+ p.setBorder(new ScaledEmptyBorder(10, 10, 10, 10));
for (Component row : rows) {
if (row instanceof JComponent) ((JComponent) row).setAlignmentX(Component.LEFT_ALIGNMENT);
p.add(row);
@@ -397,7 +398,7 @@ final class ContactsDialog extends JDialog {
}
private static JPanel labelled(String label, JComponent field) {
- JPanel p = new JPanel(new BorderLayout(6, 0));
+ JPanel p = new JPanel(new BorderLayout(UIScale.scale(6), 0));
p.add(new JLabel(label), BorderLayout.WEST);
p.add(field, BorderLayout.CENTER);
p.setMaximumSize(new Dimension(Integer.MAX_VALUE, field.getPreferredSize().height));
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsPanel.java
index f1e74e2..76d5d04 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/ContactsPanel.java
@@ -1,8 +1,9 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Settings;
-import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
import javax.swing.JCheckBox;
@@ -32,11 +33,11 @@ final class ContactsPanel extends JPanel {
ContactsPanel(Settings settings) {
super(new BorderLayout());
this.settings = settings;
- setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
+ setBorder(new ScaledEmptyBorder(12, 12, 12, 12));
JPanel p = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
- c.insets = new Insets(2, 2, 2, 2);
+ c.insets = UIScale.scale(new Insets(2, 2, 2, 2));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
@@ -48,10 +49,10 @@ final class ContactsPanel extends JPanel {
p.add(colorContacts, c);
c.gridy = 1;
- c.insets = new Insets(12, 2, 2, 2);
+ c.insets = UIScale.scale(new Insets(12, 2, 2, 2));
p.add(new JLabel("Whispers: who is allowed to whisper to you"), c);
c.gridy = 2;
- c.insets = new Insets(2, 2, 2, 2);
+ c.insets = UIScale.scale(new Insets(2, 2, 2, 2));
whisperPolicy.setSelectedItem(settings.whisperPolicy);
whisperPolicy.setRenderer(new DefaultListCellRenderer() {
@Override
@@ -68,7 +69,7 @@ final class ContactsPanel extends JPanel {
p.add(whisperPolicy, c);
c.gridy = 3;
- c.insets = new Insets(12, 2, 2, 2);
+ c.insets = UIScale.scale(new Insets(12, 2, 2, 2));
c.fill = GridBagConstraints.NONE;
JButton defaults = new JButton("Contact defaults…");
defaults.setToolTipText("Configure default settings when adding a client to the Contact Manager");
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/DescriptionEditorDialog.java b/ts3-client/swing/src/main/java/com/ts3client/ui/DescriptionEditorDialog.java
index 455aaf2..16ab826 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/DescriptionEditorDialog.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/DescriptionEditorDialog.java
@@ -1,8 +1,9 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.text.BBCode;
-import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JColorChooser;
@@ -47,19 +48,19 @@ final class DescriptionEditorDialog extends JDialog {
area.setText(description);
area.setLineWrap(true);
area.setWrapStyleWord(true);
- area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
+ area.setFont(new Font(Font.MONOSPACED, Font.PLAIN, UIScale.scale(12)));
area.setCaretPosition(0);
preview.setEditable(false);
preview.setBackground(Theme.chatBg());
- preview.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
+ preview.setBorder(new ScaledEmptyBorder(6, 8, 6, 8));
body.add(new JScrollPane(area), EDIT_CARD);
body.add(new JScrollPane(preview), PREVIEW_CARD);
JLabel hint = new JLabel("Press Button to \"Preview\" the changes in channel info.");
hint.setForeground(Theme.chatSystem());
- hint.setBorder(BorderFactory.createEmptyBorder(4, 6, 0, 6));
+ hint.setBorder(new ScaledEmptyBorder(4, 6, 0, 6));
getContentPane().setLayout(new BorderLayout());
getContentPane().add(buildToolbar(), BorderLayout.NORTH);
@@ -71,8 +72,8 @@ final class DescriptionEditorDialog extends JDialog {
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
- setSize(new Dimension(520, 380));
- setMinimumSize(new Dimension(360, 260));
+ setSize(UIScale.scale(new Dimension(520, 380)));
+ setMinimumSize(UIScale.scale(new Dimension(360, 260)));
setLocationRelativeTo(owner);
}
@@ -134,7 +135,7 @@ final class DescriptionEditorDialog extends JDialog {
* box, which would otherwise let one of them soak up all the free width.
*/
private static JButton square(JButton button) {
- Dimension size = new Dimension(Math.max(28, button.getPreferredSize().width),
+ Dimension size = new Dimension(Math.max(UIScale.scale(28), button.getPreferredSize().width),
button.getPreferredSize().height);
button.setPreferredSize(size);
button.setMaximumSize(size);
@@ -162,7 +163,7 @@ final class DescriptionEditorDialog extends JDialog {
private void showPreview(boolean on) {
previewButton.setSelected(on);
if (on) {
- preview.setText(""
+ preview.setText(""
+ BBCode.toHtml(area.getText()) + "");
preview.setCaretPosition(0);
}
diff --git a/ts3-client/swing/src/main/java/com/ts3client/ui/DesignPanel.java b/ts3-client/swing/src/main/java/com/ts3client/ui/DesignPanel.java
index 84ea932..cfab7e0 100644
--- a/ts3-client/swing/src/main/java/com/ts3client/ui/DesignPanel.java
+++ b/ts3-client/swing/src/main/java/com/ts3client/ui/DesignPanel.java
@@ -1,5 +1,7 @@
package com.ts3client.ui;
+import com.formdev.flatlaf.util.ScaledEmptyBorder;
+import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Settings;
import com.ts3client.gfx.IconPack;
@@ -24,14 +26,15 @@ import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.File;
+import java.util.Arrays;
/**
- * Options page for how the client looks: the light or dark look-and-feel, and the icon
- * pack the user interface is drawn with — where to look for more of them, and a viewer
- * showing everything the active pack contains.
+ * Options page for how the client looks: the light or dark look-and-feel, the interface
+ * scale, and the icon pack the user interface is drawn with — where to look for more of
+ * them, and a viewer showing everything the active pack contains.
*
- *
Both apply right away so the change can be seen in the window behind the dialog;
- * cancelling puts the previous ones back.
+ *
Theme and pack apply right away so the change can be seen in the window behind the
+ * dialog; cancelling puts the previous ones back. The scale is only read at startup.
*/
final class DesignPanel extends JPanel {
@@ -42,6 +45,9 @@ final class DesignPanel extends JPanel {
/** Stands for "no pack": the icons the client draws itself. */
private static final String BUILT_IN = "Built-in icons";
+ /** The scale factors offered besides following the desktop (0). */
+ private static final double[] SCALES = {0, 1, 1.25, 1.5, 1.75, 2, 2.5, 3};
+
private final Settings settings;
private final String originalPackId;
private final String originalPackDir;
@@ -49,6 +55,7 @@ final class DesignPanel extends JPanel {
private final JComboBox appearanceCombo =
new JComboBox<>(Settings.Appearance.values());
+ private final JComboBox scaleCombo = new JComboBox<>();
private final JComboBox