Scale the interface fractionally on Linux

Java2D on X11 (and so under XWayland) truncates its scale to a whole
number, drawing 150 % at 100 %. On Linux, pin Java2D to 1 and hand the
full factor to FlatLaf, taken from XSETTINGS Xft/DPI, KWin's Xwayland
scale or GDK_SCALE, or chosen in Options → Design.

The client's own sizes now go through UIScale: borders, gaps, dialog and
row sizes, icons (rendered at the scaled size, so they stay crisp),
custom-painted components and font sizes in the HTML views.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-23 22:33:27 +00:00
parent 1fddae3f0b
commit 9d996f48a2
57 changed files with 643 additions and 311 deletions

View File

@@ -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);

View File

@@ -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("<html>Images up to " + (conn.maxAvatarBytes() / 1024)
+ " KiB may be uploaded to this server. Animated GIFs stay animated.</html>");
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);

View File

@@ -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 <img>} 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 "<img src='" + avatar.file().toURI() + "'" + dims + ">";
}

View File

@@ -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();

View File

@@ -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() {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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;
}

View File

@@ -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("<html><p>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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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<ChannelNode> 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;

View File

@@ -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);

View File

@@ -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("<html><body style='width:320px'>The three fields belong together: the "
p.add(new JLabel("<html><body style='width:" + HtmlStyles.px(320) + "'>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 "

View File

@@ -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("<html><body style='width:300px'>Values greater than +6 dB will cause clipping "
JLabel hint = new JLabel("<html><body style='width:" + HtmlStyles.px(300) + "'>Values greater than +6 dB will cause clipping "
+ "and distortion that will negatively affect your audio quality. It is advised to choose "
+ "lower values.</body></html>");
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);
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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));

View File

@@ -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");

View File

@@ -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("<html><body style=\"font-family:sans-serif;font-size:9pt\">"
preview.setText("<html><body style=\"font-family:sans-serif;font-size:" + UIScale.scale(9) + "pt\">"
+ BBCode.toHtml(area.getText()) + "</body></html>");
preview.setCaretPosition(0);
}

View File

@@ -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.
*
* <p>Both apply right away so the change can be seen in the window behind the dialog;
* cancelling puts the previous ones back.
* <p>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<Settings.Appearance> appearanceCombo =
new JComboBox<>(Settings.Appearance.values());
private final JComboBox<Double> scaleCombo = new JComboBox<>();
private final JComboBox<Object> packCombo = new JComboBox<>();
private final JLabel packInfo = new JLabel();
private final JTextField packDirField;
@@ -56,14 +63,14 @@ final class DesignPanel extends JPanel {
private final JList<String> preview = new JList<>(previewModel);
DesignPanel(Settings settings) {
super(new BorderLayout(0, 8));
super(new BorderLayout(0, UIScale.scale(8)));
this.settings = settings;
this.originalPackId = settings.iconPack;
this.originalPackDir = settings.iconPackDir;
this.originalAppearance = settings.appearance;
this.packDirField = new JTextField(settings.iconPackDir, 18);
setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
setBorder(new ScaledEmptyBorder(12, 12, 12, 12));
add(buildHeader(), BorderLayout.NORTH);
add(buildPreview(), BorderLayout.CENTER);
@@ -73,6 +80,7 @@ final class DesignPanel extends JPanel {
/** Copies the chosen appearance and pack into the settings; the caller saves them. */
void apply() {
settings.appearance = selectedAppearance();
if (scaleCombo.getSelectedItem() instanceof Double scale) settings.uiScale = scale;
settings.iconPackDir = packDirField.getText().trim();
Object selected = packCombo.getSelectedItem();
settings.iconPack = selected instanceof IconPack pack ? pack.id() : "";
@@ -94,7 +102,7 @@ final class DesignPanel extends JPanel {
private JComponent buildHeader() {
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;
@@ -125,13 +133,17 @@ final class DesignPanel extends JPanel {
int row = 0;
addRow(p, c, row++, new JLabel("Theme:"), appearanceCombo);
if (DisplayScale.isManaged()) {
buildScaleCombo();
addRow(p, c, row++, new JLabel("Interface scale:"), scaleCombo);
}
addRow(p, c, row++, new JLabel("Icon pack:"), packCombo);
c.gridx = 1;
c.gridy = row++;
packInfo.setEnabled(false);
p.add(packInfo, c);
JPanel dirRow = new JPanel(new BorderLayout(6, 0));
JPanel dirRow = new JPanel(new BorderLayout(UIScale.scale(6), 0));
JButton browse = new JButton("Browse…");
browse.addActionListener(e -> browseForPackFolder());
dirRow.add(packDirField, BorderLayout.CENTER);
@@ -141,18 +153,42 @@ final class DesignPanel extends JPanel {
return p;
}
private void buildScaleCombo() {
for (double scale : SCALES) scaleCombo.addItem(scale);
if (settings.uiScale > 0 && Arrays.stream(SCALES).noneMatch(s -> s == settings.uiScale)) {
scaleCombo.addItem(settings.uiScale);
}
scaleCombo.setSelectedItem(Math.max(0, settings.uiScale));
scaleCombo.setToolTipText("Size of text, icons and controls; takes effect after a restart");
scaleCombo.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean selected, boolean focus) {
super.getListCellRendererComponent(list, value, index, selected, focus);
double scale = value instanceof Double d ? d : 0;
setText(scale > 0 ? percent(scale)
: "Automatic (" + percent(DisplayScale.detected()) + ")");
return this;
}
});
}
private static String percent(double scale) {
return Math.round(scale * 100) + "%";
}
private JComponent buildPreview() {
preview.setLayoutOrientation(JList.HORIZONTAL_WRAP);
preview.setVisibleRowCount(-1);
preview.setFixedCellWidth(TILE);
preview.setFixedCellHeight(TILE);
preview.setFixedCellWidth(UIScale.scale(TILE));
preview.setFixedCellHeight(UIScale.scale(TILE));
preview.setCellRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean selected, boolean focus) {
super.getListCellRendererComponent(list, value, index, selected, focus);
String key = String.valueOf(value);
ImageIcon icon = Icons.of(key, PREVIEW_SIZE);
ImageIcon icon = Icons.of(key, UIScale.scale(PREVIEW_SIZE));
setText("");
setIcon(icon);
setToolTipText(key);
@@ -162,7 +198,7 @@ final class DesignPanel extends JPanel {
});
JScrollPane scroll = new JScrollPane(preview);
scroll.setPreferredSize(new Dimension(360, 200));
scroll.setPreferredSize(UIScale.scale(new Dimension(360, 200)));
scroll.setBorder(BorderFactory.createTitledBorder("Icons in this pack"));
return scroll;
}

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.audio.VoiceInput;
import com.ts3client.audio.VoiceOutput;
import com.ts3client.audio.desktop.AudioDevices;
@@ -75,9 +76,9 @@ final class DevicesPanel extends FormPanel {
c.gridx = 0;
c.gridy = row++;
c.gridwidth = 2;
c.insets = new Insets(14, 4, 2, 4);
c.insets = UIScale.scale(new Insets(14, 4, 2, 4));
add(new JLabel("Noise reduction"), c);
c.insets = new Insets(4, 4, 4, 4);
c.insets = UIScale.scale(new Insets(4, 4, 4, 4));
c.gridwidth = 1;
denoiseCheck = new JCheckBox("Remove background noise", settings.denoise);

View File

@@ -0,0 +1,134 @@
package com.ts3client.ui;
import java.awt.Toolkit;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
/**
* Decides how large the user interface is drawn on Linux, where Java2D only knows
* whole-number scale factors: asked for 150&nbsp;%, it draws at 100&nbsp;% — or, told
* 2 by the desktop, at 200&nbsp;%, while FlatLaf's own scaling goes on top of that.
*
* <p>So on Linux Java2D is pinned to 1 and the whole factor, fractional or not, is
* handed to FlatLaf, which scales fonts and its own metrics; the client scales its own
* sizes through FlatLaf's {@link com.formdev.flatlaf.util.UIScale} to match.
*
* <p>The desktop's factor is read the way an X11 toolkit would learn it:
* <ol>
* <li>{@code Xft/DPI} from XSETTINGS — GNOME and Plasma (through xsettingsd) publish
* the device-pixel DPI there, fractional scale and text scaling included;</li>
* <li>KWin's {@code [Xwayland] Scale}, which Plasma sets when X11 apps are left to
* scale themselves — for sessions with no XSETTINGS daemon running;</li>
* <li>{@code GDK_SCALE}, which Java2D would otherwise have honoured itself.</li>
* </ol>
* With the compositor scaling X11 apps (GNOME's default, and Plasma's "Scaled by the
* system") the client sees 96&nbsp;DPI and is drawn at 100&nbsp;%, which is what those
* modes expect; they then upscale the window, blurry but the right size.
*
* <p>A {@code sun.java2d.uiScale} or {@code flatlaf.uiScale} given on the command line
* turns all of this off.
*/
public final class DisplayScale {
private static final String JAVA2D_SCALE = "sun.java2d.uiScale";
private static final String FLATLAF_SCALE = "flatlaf.uiScale";
private static final double BASE_DPI = 96;
private static final double MAX_SCALE = 4;
private static boolean managed;
private static double detected = 1;
private DisplayScale() {
}
/**
* Sets the scale up; call before anything touches AWT.
*
* @param override the user's chosen factor, or 0 to follow the desktop
*/
public static void configure(double override) {
if (!isLinux() || System.getProperty(JAVA2D_SCALE) != null
|| System.getProperty(FLATLAF_SCALE) != null) {
return;
}
System.setProperty(JAVA2D_SCALE, "1");
managed = true;
detected = detect();
double factor = override > 0 ? override : detected;
System.setProperty(FLATLAF_SCALE, Double.toString(factor));
}
/** Whether the client sets the scale itself, so that the user may choose one. */
public static boolean isManaged() {
return managed;
}
/** The desktop's factor, as found at startup. */
public static double detected() {
return detected;
}
private static double detect() {
double scale = xftScale();
if (scale <= 0) scale = kwinXwaylandScale();
scale = Math.max(scale, gdkScale());
return Math.max(1, Math.min(MAX_SCALE, scale));
}
private static double xftScale() {
// The XSETTINGS value is in 1024ths of a DPI; -1 means "unset".
Object dpi = Toolkit.getDefaultToolkit().getDesktopProperty("gnome.Xft/DPI");
return dpi instanceof Integer d && d > 0 ? d / 1024.0 / BASE_DPI : 0;
}
private static double kwinXwaylandScale() {
// Left behind by a Wayland session, the key would be stale in an X11 one.
if (System.getenv("WAYLAND_DISPLAY") == null) return 0;
String configHome = System.getenv("XDG_CONFIG_HOME");
Path kwinrc = (configHome == null || configHome.isEmpty()
? Path.of(System.getProperty("user.home"), ".config")
: Path.of(configHome)).resolve("kwinrc");
try {
return parseDouble(iniValue(Files.readAllLines(kwinrc), "Xwayland", "Scale"));
} catch (IOException e) {
return 0;
}
}
private static double gdkScale() {
return parseDouble(System.getenv("GDK_SCALE"));
}
/** The value of {@code key} in {@code [group]} of a KConfig-style file, or {@code null}. */
static String iniValue(List<String> lines, String group, String key) {
boolean inGroup = false;
for (String line : lines) {
String t = line.trim();
if (t.startsWith("[")) {
inGroup = t.equals("[" + group + "]");
} else if (inGroup) {
int eq = t.indexOf('=');
if (eq > 0 && t.substring(0, eq).trim().equals(key)) {
return t.substring(eq + 1).trim();
}
}
}
return null;
}
private static double parseDouble(String s) {
if (s == null) return 0;
try {
return Double.parseDouble(s.trim());
} catch (NumberFormatException e) {
return 0;
}
}
private static boolean isLinux() {
return System.getProperty("os.name", "").toLowerCase(Locale.ROOT).startsWith("linux");
}
}

View File

@@ -1,5 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
@@ -52,7 +54,7 @@ public final class DropDownToggleButton extends JPanel {
// width comes out of the toggle half instead of being added to it.
Dimension plain = button.getPreferredSize();
button.setPreferredSize(new Dimension(
Math.max(icon.getIconWidth() + 8, plain.width - arrow.getPreferredSize().width),
Math.max(icon.getIconWidth() + UIScale.scale(8), plain.width - arrow.getPreferredSize().width),
plain.height));
add(button, BorderLayout.CENTER);
@@ -109,12 +111,12 @@ public final class DropDownToggleButton extends JPanel {
@Override
public int getIconWidth() {
return 7;
return UIScale.scale(7);
}
@Override
public int getIconHeight() {
return 4;
return UIScale.scale(4);
}
}
}

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ChannelNode;
import com.ts3client.net.ClientEntry;
import com.ts3client.net.ServerModel;
@@ -160,14 +161,16 @@ final class DropIndicatorTree extends JTree {
if (highlight != null || loc.getChildIndex() < 0) {
Rectangle r = getPathBounds(highlight != null ? highlight : loc.getPath());
if (r != null) {
g2.setStroke(new BasicStroke(2f));
g2.drawRoundRect(r.x, r.y + 1, r.width - 1, r.height - 3, 4, 4);
g2.setStroke(new BasicStroke(UIScale.scale(2f)));
int arc = UIScale.scale(4);
g2.drawRoundRect(r.x, r.y + 1, r.width - 1, r.height - 3, arc, arc);
}
} else {
Rectangle line = insertLine(loc);
if (line != null) {
g2.fillRect(line.x, line.y - 1, line.width, 2);
g2.fillOval(line.x - 3, line.y - 4, 7, 7);
int dot = UIScale.scale(7);
g2.fillRect(line.x, line.y - line.height / 2, line.width, line.height);
g2.fillOval(line.x - dot / 2, line.y - dot / 2 - 1, dot, dot);
}
}
g2.dispose();
@@ -210,19 +213,19 @@ final class DropIndicatorTree extends JTree {
return obj instanceof ChannelNode && Spacers.isSpacer(((ChannelNode) obj).name);
}
/** The 1px-tall strip where the insertion line goes, in tree coordinates. */
/** The strip where the insertion line goes, in tree coordinates. */
private Rectangle insertLine(JTree.DropLocation loc) {
DefaultMutableTreeNode parent = (DefaultMutableTreeNode) loc.getPath().getLastPathComponent();
int index = loc.getChildIndex();
if (index < parent.getChildCount()) {
Rectangle r = getPathBounds(loc.getPath().pathByAddingChild(parent.getChildAt(index)));
return r == null ? null : new Rectangle(r.x, r.y, getWidth() - r.x, 2);
return r == null ? null : new Rectangle(r.x, r.y, getWidth() - r.x, UIScale.scale(2));
}
if (parent.getChildCount() == 0) {
Rectangle r = getPathBounds(loc.getPath());
if (r == null) return null;
int x = r.x + getRowHeight();
return new Rectangle(x, r.y + r.height, getWidth() - x, 2);
return new Rectangle(x, r.y + r.height, getWidth() - x, UIScale.scale(2));
}
// Past the last child: below that child's whole (expanded) subtree.
TreePath lastChild = loc.getPath().pathByAddingChild(parent.getChildAt(parent.getChildCount() - 1));
@@ -250,7 +253,7 @@ final class DropIndicatorTree extends JTree {
*/
private void paintBadges(Graphics g) {
Rectangle visible = getVisibleRect();
int right = visible.x + visible.width - BADGE_MARGIN;
int right = visible.x + visible.width - UIScale.scale(BADGE_MARGIN);
for (int row = 0; row < getRowCount(); row++) {
Rectangle bounds = getRowBounds(row);
if (bounds == null || bounds.y + bounds.height < visible.y) continue;
@@ -262,7 +265,7 @@ final class DropIndicatorTree extends JTree {
if (icons.isEmpty()) continue;
GroupIcons.Row strip = new GroupIcons.Row(icons);
int x = Math.max(bounds.x + bounds.width + BADGE_GAP, right - strip.getIconWidth());
int x = Math.max(bounds.x + bounds.width + UIScale.scale(BADGE_GAP), right - strip.getIconWidth());
strip.paintIcon(this, g, x, bounds.y + (bounds.height - strip.getIconHeight()) / 2);
}
}

View File

@@ -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.TeamspeakConnection;
import com.ts3client.net.filetransfer.FileTransfer;
@@ -65,15 +67,15 @@ public final class FileBrowserDialog extends JDialog {
this.channelId = channel.id;
this.channelPassword = "";
JPanel content = new JPanel(new BorderLayout(0, 8));
JPanel content = new JPanel(new BorderLayout(0, UIScale.scale(8)));
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(buildTransfers(), BorderLayout.SOUTH);
setContentPane(content);
setPreferredSize(new Dimension(640, 480));
setPreferredSize(UIScale.scale(new Dimension(640, 480)));
pack();
setLocationRelativeTo(owner);
@@ -98,17 +100,17 @@ public final class FileBrowserDialog extends JDialog {
deleteButton.addActionListener(e -> deleteSelected());
bar.add(upButton);
bar.add(Box.createHorizontalStrut(6));
bar.add(Box.createHorizontalStrut(UIScale.scale(6)));
bar.add(refresh);
bar.add(Box.createHorizontalStrut(16));
bar.add(Box.createHorizontalStrut(UIScale.scale(16)));
bar.add(pathLabel);
bar.add(Box.createHorizontalGlue());
bar.add(mkdir);
bar.add(Box.createHorizontalStrut(6));
bar.add(Box.createHorizontalStrut(UIScale.scale(6)));
bar.add(upload);
bar.add(Box.createHorizontalStrut(6));
bar.add(Box.createHorizontalStrut(UIScale.scale(6)));
bar.add(downloadButton);
bar.add(Box.createHorizontalStrut(6));
bar.add(Box.createHorizontalStrut(UIScale.scale(6)));
bar.add(deleteButton);
pathLabel.setFont(Theme.uiFont());
@@ -118,14 +120,14 @@ public final class FileBrowserDialog extends JDialog {
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.getColumnModel().getColumn(0).setPreferredWidth(300);
table.getColumnModel().getColumn(0).setPreferredWidth(UIScale.scale(300));
table.getColumnModel().getColumn(0).setCellRenderer(new NameRenderer());
table.getColumnModel().getColumn(1).setPreferredWidth(90);
table.getColumnModel().getColumn(2).setPreferredWidth(70);
table.getColumnModel().getColumn(3).setPreferredWidth(150);
table.getColumnModel().getColumn(1).setPreferredWidth(UIScale.scale(90));
table.getColumnModel().getColumn(2).setPreferredWidth(UIScale.scale(70));
table.getColumnModel().getColumn(3).setPreferredWidth(UIScale.scale(150));
table.getSelectionModel().addListSelectionListener(e -> updateButtons());
table.addMouseListener(new MouseAdapter() {
@Override
@@ -146,7 +148,7 @@ public final class FileBrowserDialog extends JDialog {
transfersPanel.setBackground(Theme.windowBg());
JScrollPane scroll = new JScrollPane(transfersPanel);
scroll.setBorder(BorderFactory.createTitledBorder("Transfers"));
scroll.setPreferredSize(new Dimension(10, 120));
scroll.setPreferredSize(UIScale.scale(new Dimension(10, 120)));
scroll.getViewport().setBackground(Theme.windowBg());
return scroll;
}
@@ -325,10 +327,10 @@ public final class FileBrowserDialog extends JDialog {
private FileTransfer transfer;
TransferRow() {
setLayout(new BorderLayout(8, 0));
setLayout(new BorderLayout(UIScale.scale(8), 0));
setBackground(Theme.windowBg());
setBorder(BorderFactory.createEmptyBorder(3, 2, 3, 2));
setMaximumSize(new Dimension(Integer.MAX_VALUE, 44));
setBorder(new ScaledEmptyBorder(3, 2, 3, 2));
setMaximumSize(new Dimension(Integer.MAX_VALUE, UIScale.scale(44)));
label.setFont(Theme.uiFont());
bar.setStringPainted(true);
add(label, BorderLayout.NORTH);

View File

@@ -1,6 +1,8 @@
package com.ts3client.ui;
import javax.swing.BorderFactory;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -27,7 +29,7 @@ class FormPanel extends JPanel implements Scrollable {
FormPanel() {
super(new GridBagLayout());
setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
setBorder(new ScaledEmptyBorder(12, 12, 12, 12));
}
@Override
@@ -57,7 +59,7 @@ class FormPanel extends JPanel implements Scrollable {
static GridBagConstraints gbc() {
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;
return c;
@@ -81,8 +83,8 @@ class FormPanel extends JPanel implements Scrollable {
*/
static void limitWidth(JComponent comp, int preferredWidth) {
int height = comp.getPreferredSize().height;
comp.setPreferredSize(new Dimension(preferredWidth, height));
comp.setMinimumSize(new Dimension(MIN_FIELD_WIDTH, height));
comp.setPreferredSize(new Dimension(UIScale.scale(preferredWidth), height));
comp.setMinimumSize(new Dimension(UIScale.scale(MIN_FIELD_WIDTH), height));
}
static JPanel sliderWithLabel(JSlider slider, JLabel valueLabel) {
@@ -94,7 +96,7 @@ class FormPanel extends JPanel implements Scrollable {
* the longest value so the slider does not jump around as it is dragged.
*/
static JPanel sliderWithLabel(JSlider slider, JLabel valueLabel, int labelWidth) {
JPanel panel = new JPanel(new BorderLayout(6, 0));
JPanel panel = new JPanel(new BorderLayout(UIScale.scale(6), 0));
limitWidth(slider, SLIDER_WIDTH);
panel.add(slider, BorderLayout.CENTER);
valueLabel.setPreferredSize(new Dimension(labelWidth, valueLabel.getPreferredSize().height));

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.Group;
import com.ts3client.net.IconRepository;
@@ -42,7 +43,7 @@ public final class GroupIcons {
// its own style; only server-uploaded icons come off the wire. The theme keeps
// its own cache, so those are not cached again here.
if (iconId > 0 && iconId <= IconRepository.MAX_BUNDLED_ID) {
ImageIcon themed = IconTheme.icon("GROUP_" + iconId, SIZE);
ImageIcon themed = IconTheme.icon("GROUP_" + iconId, UIScale.scale(SIZE));
if (themed != null) return themed;
}
@@ -54,8 +55,9 @@ public final class GroupIcons {
ImageIcon icon = new ImageIcon(data);
if (icon.getIconWidth() <= 0) return null;
if (icon.getIconWidth() != SIZE || icon.getIconHeight() != SIZE) {
icon = new ImageIcon(icon.getImage().getScaledInstance(SIZE, SIZE, Image.SCALE_SMOOTH));
int size = UIScale.scale(SIZE);
if (icon.getIconWidth() != size || icon.getIconHeight() != size) {
icon = new ImageIcon(icon.getImage().getScaledInstance(size, size, Image.SCALE_SMOOTH));
}
decoded.put(iconId, icon);
return icon;
@@ -89,15 +91,16 @@ public final class GroupIcons {
int offset = x;
for (Icon icon : icons) {
icon.paintIcon(c, g, offset, y + (getIconHeight() - icon.getIconHeight()) / 2);
offset += icon.getIconWidth() + GAP;
offset += icon.getIconWidth() + UIScale.scale(GAP);
}
}
@Override
public int getIconWidth() {
int width = 0;
for (Icon icon : icons) width += icon.getIconWidth() + GAP;
return Math.max(0, width - GAP);
int gap = UIScale.scale(GAP);
for (Icon icon : icons) width += icon.getIconWidth() + gap;
return Math.max(0, width - gap);
}
@Override

View File

@@ -1,11 +1,12 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.hotkey.Hotkey;
import com.ts3client.hotkey.HotkeyAction;
import com.ts3client.hotkey.HotkeyCombo;
import com.ts3client.hotkey.HotkeyEngine;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JButton;
@@ -120,8 +121,8 @@ final class HotkeyDialog extends JDialog {
argumentField.setText(hotkey.argument == null ? "" : hotkey.argument);
hint.setFont(hint.getFont().deriveFont(Font.ITALIC, hint.getFont().getSize2D() - 1f));
getContentPane().setLayout(new BorderLayout(8, 8));
((JComponent) getContentPane()).setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
getContentPane().setLayout(new BorderLayout(UIScale.scale(8), UIScale.scale(8)));
((JComponent) getContentPane()).setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
getContentPane().add(buildActionPane(), BorderLayout.CENTER);
getContentPane().add(buildForm(), BorderLayout.SOUTH);
@@ -129,12 +130,12 @@ final class HotkeyDialog extends JDialog {
updateForAction();
Dialogs.closeOnEscape(this, this::cancel);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(new Dimension(470, 560));
setSize(UIScale.scale(new Dimension(470, 560)));
setLocationRelativeTo(owner);
}
private JPanel buildActionPane() {
JPanel p = new JPanel(new BorderLayout(0, 4));
JPanel p = new JPanel(new BorderLayout(0, UIScale.scale(4)));
p.add(new JLabel("Action:"), BorderLayout.NORTH);
p.add(new JScrollPane(tree), BorderLayout.CENTER);
p.add(advancedCheck, BorderLayout.SOUTH);
@@ -144,7 +145,7 @@ final class HotkeyDialog extends JDialog {
private JPanel buildForm() {
JPanel p = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.insets = new Insets(3, 3, 3, 3);
c.insets = UIScale.scale(new Insets(3, 3, 3, 3));
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;

View File

@@ -1,8 +1,9 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.hotkey.Hotkey;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
@@ -34,18 +35,18 @@ final class HotkeysPanel extends JPanel {
private final JTable table = new JTable(model);
HotkeysPanel(HotkeyService service) {
super(new BorderLayout(6, 6));
super(new BorderLayout(UIScale.scale(6), UIScale.scale(6)));
this.service = service;
setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
synchronized (service.all()) {
for (Hotkey h : service.all()) working.add(h.copy());
}
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setRowHeight(table.getRowHeight() + 4);
table.getColumnModel().getColumn(0).setPreferredWidth(240);
table.getColumnModel().getColumn(1).setPreferredWidth(150);
table.setRowHeight(table.getRowHeight() + UIScale.scale(4));
table.getColumnModel().getColumn(0).setPreferredWidth(UIScale.scale(240));
table.getColumnModel().getColumn(1).setPreferredWidth(UIScale.scale(150));
table.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {

View File

@@ -1,13 +1,16 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.text.BBCode;
import javax.swing.JEditorPane;
import javax.swing.JViewport;
import javax.swing.SwingUtilities;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.AttributeSet;
import javax.swing.text.html.StyleSheet;
import java.awt.Color;
import java.awt.Font;
/**
* The HTML panes showing TeamSpeak text — chat logs, client and channel information —
@@ -70,7 +73,7 @@ final class HtmlStyles {
private static void installKit(JEditorPane pane, String html, boolean atEnd) {
HTMLEditorKit kit = new HTMLEditorKit();
StyleSheet css = new StyleSheet();
StyleSheet css = new ScaledStyleSheet();
css.addStyleSheet(kit.getStyleSheet());
Object bodyStyle = pane.getClientProperty(BODY_STYLE);
@@ -102,7 +105,24 @@ final class HtmlStyles {
}
}
/** A CSS length of {@code px} at the interface scale. */
static String px(int px) {
return UIScale.scale(px) + "px";
}
private static String hex(Color c) {
return String.format("#%02x%02x%02x", c.getRed(), c.getGreen(), c.getBlue());
}
/**
* Scales every font size, the way a browser scales CSS pixels: Swing takes a
* {@code font-size} in pixels literally, and the text is written in unscaled ones.
*/
private static final class ScaledStyleSheet extends StyleSheet {
@Override
public Font getFont(AttributeSet a) {
Font font = super.getFont(a);
return UIScale.getUserScaleFactor() == 1f ? font : font.deriveFont(UIScale.scale(font.getSize2D()));
}
}
}

View File

@@ -1,5 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.IconRepository;
import com.ts3client.net.TeamspeakConnection;
@@ -69,7 +71,7 @@ final class IconChooserDialog extends JDialog {
group("Local", localList, localFooter()));
split.setResizeWeight(0.6);
status.setBorder(BorderFactory.createEmptyBorder(2, 8, 2, 8));
status.setBorder(new ScaledEmptyBorder(2, 8, 2, 8));
status.setForeground(Theme.chatSystem());
getContentPane().setLayout(new BorderLayout());
@@ -81,7 +83,7 @@ final class IconChooserDialog extends JDialog {
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(new Dimension(520, 360));
setSize(UIScale.scale(new Dimension(520, 360)));
setLocationRelativeTo(owner);
loadRemoteIcons();
@@ -101,8 +103,8 @@ final class IconChooserDialog extends JDialog {
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(-1);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setFixedCellWidth(32);
list.setFixedCellHeight(32);
list.setFixedCellWidth(UIScale.scale(32));
list.setFixedCellHeight(UIScale.scale(32));
list.setCellRenderer(new IconCellRenderer());
list.addListSelectionListener(e -> onSelectionChanged(list));
list.addMouseListener(new MouseAdapter() {
@@ -129,7 +131,7 @@ final class IconChooserDialog extends JDialog {
}
private JPanel group(String title, JList<Long> list, Component footer) {
JPanel panel = new JPanel(new BorderLayout(0, 4));
JPanel panel = new JPanel(new BorderLayout(0, UIScale.scale(4)));
panel.setBorder(BorderFactory.createTitledBorder(title));
panel.add(new JScrollPane(list), BorderLayout.CENTER);
panel.add(footer, BorderLayout.SOUTH);
@@ -150,7 +152,7 @@ final class IconChooserDialog extends JDialog {
private Component localFooter() {
com.ts3client.gfx.IconPack active = IconTheme.get().activePack();
JLabel pack = new JLabel("Icon Pack: " + (active == null ? "none" : active.name()));
pack.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
pack.setBorder(new ScaledEmptyBorder(0, 4, 0, 4));
return pack;
}

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.github.weisj.jsvg.SVGDocument;
import com.github.weisj.jsvg.parser.LoaderContext;
import com.github.weisj.jsvg.parser.SVGLoader;
@@ -33,6 +34,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
*
* <p>Rendering is cached per key and size: vector art is rasterised at the size it
* is asked for, bitmaps are scaled from the closest resolution the pack ships.
* {@link #SIZE} and {@link #TOOLBAR_SIZE} are unscaled; sizes passed in are pixels.
*/
public final class IconTheme {
@@ -86,12 +88,12 @@ public final class IconTheme {
listeners.add(listener);
}
/** @return the icon for a TeamSpeak icon key at 16&times;16, or {@code null} if no pack has it */
/** @return the icon for a TeamSpeak icon key at tree-row size, or {@code null} if no pack has it */
public static ImageIcon icon(String key) {
return icon(key, SIZE);
return icon(key, UIScale.scale(SIZE));
}
/** @return the icon for a TeamSpeak icon key, or {@code null} if no pack has it */
/** @return the icon for a TeamSpeak icon key at {@code size} pixels, or {@code null} if no pack has it */
public static ImageIcon icon(String key, int size) {
return INSTANCE.lookup(key, size);
}

View File

@@ -1,5 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import javax.swing.ImageIcon;
import java.awt.BasicStroke;
import java.awt.Color;
@@ -15,14 +17,25 @@ import java.awt.image.BufferedImage;
* so a machine with no icon pack installed still gets a complete (if plain) set.
* Everything else — menu and dialog decoration — simply stays blank without a pack,
* which is why {@link #of(String)} may return {@code null}.
*
* <p>Icons come at the interface's scale; the variants taking a size take it in pixels.
*/
public final class Icons {
/** The box the built-in painters draw in. */
private static final int SZ = IconTheme.SIZE;
private Icons() {
}
private static int small() {
return UIScale.scale(IconTheme.SIZE);
}
private static int large() {
return UIScale.scale(IconTheme.TOOLBAR_SIZE);
}
private interface Painter {
void paint(Graphics2D g);
}
@@ -44,13 +57,13 @@ public final class Icons {
return null;
}
/** @return the pack's icon at a given size, or {@code null} when it has none */
/** @return the pack's icon at {@code size} pixels, or {@code null} when it has none */
public static ImageIcon of(String key, int size) {
return IconTheme.icon(key, size);
}
private static ImageIcon themed(String key, Painter fallback) {
return themed(key, SZ, fallback);
return themed(key, small(), fallback);
}
private static ImageIcon themed(String key, int size, Painter fallback) {
@@ -64,14 +77,14 @@ public final class Icons {
*/
private static ImageIcon themed(String[] keys, Painter fallback) {
for (String key : keys) {
ImageIcon icon = IconTheme.icon(key, SZ);
ImageIcon icon = IconTheme.icon(key, small());
if (icon != null) return icon;
}
return make(fallback);
}
private static ImageIcon make(Painter p) {
return make(SZ, p);
return make(small(), p);
}
/** Runs a painter (which draws in a 16&times;16 box) scaled to {@code size}. */
@@ -129,7 +142,7 @@ public final class Icons {
// ---- client status icons ----
public static ImageIcon clientIdle() {
return clientIdle(SZ);
return clientIdle(small());
}
public static ImageIcon clientIdle(int size) {
@@ -137,7 +150,7 @@ public final class Icons {
}
public static ImageIcon clientTalking() {
return clientTalking(SZ);
return clientTalking(small());
}
public static ImageIcon clientTalking(int size) {
@@ -145,7 +158,7 @@ public final class Icons {
}
public static ImageIcon clientAway() {
return clientAway(SZ);
return clientAway(small());
}
public static ImageIcon clientAway(int size) {
@@ -154,12 +167,12 @@ public final class Icons {
/** A channel commander that is not talking. */
public static ImageIcon clientCommander() {
return clientCommander(SZ);
return clientCommander(small());
}
/** A channel commander that is talking. */
public static ImageIcon clientCommanderTalking() {
return clientCommanderTalking(SZ);
return clientCommanderTalking(small());
}
public static ImageIcon clientQuery() {
@@ -167,7 +180,7 @@ public final class Icons {
}
public static ImageIcon micMuted() {
return micMuted(SZ);
return micMuted(small());
}
public static ImageIcon micMuted(int size) {
@@ -175,7 +188,7 @@ public final class Icons {
}
public static ImageIcon speakerMuted() {
return speakerMuted(SZ);
return speakerMuted(small());
}
public static ImageIcon speakerMuted(int size) {
@@ -184,7 +197,7 @@ public final class Icons {
/** The capture device is unavailable (another tab holds it), as opposed to muted. */
public static ImageIcon micDisabled() {
return micDisabled(SZ);
return micDisabled(small());
}
public static ImageIcon micDisabled(int size) {
@@ -193,7 +206,7 @@ public final class Icons {
/** The playback device is unavailable, as opposed to muted/deafened. */
public static ImageIcon speakerDisabled() {
return speakerDisabled(SZ);
return speakerDisabled(small());
}
public static ImageIcon speakerDisabled(int size) {
@@ -202,7 +215,7 @@ public final class Icons {
/** TS3's "Local Mic Mute": silenced, but not reported as muted. */
public static ImageIcon micLocalMuted() {
return micLocalMuted(SZ);
return micLocalMuted(small());
}
public static ImageIcon micLocalMuted(int size) {
@@ -222,32 +235,32 @@ public final class Icons {
// ---- toolbar / action icons ----
public static ImageIcon connect() {
return themed("CONNECT", IconTheme.TOOLBAR_SIZE, Icons::paintConnect);
return themed("CONNECT", large(), Icons::paintConnect);
}
public static ImageIcon disconnect() {
return themed("DISCONNECT", IconTheme.TOOLBAR_SIZE, Icons::paintDisconnect);
return themed("DISCONNECT", large(), Icons::paintDisconnect);
}
public static ImageIcon mic() {
return themed("CAPTURE", IconTheme.TOOLBAR_SIZE, Icons::paintMic);
return themed("CAPTURE", large(), Icons::paintMic);
}
public static ImageIcon speaker() {
return themed("PLAYBACK", IconTheme.TOOLBAR_SIZE, Icons::paintSpeaker);
return themed("PLAYBACK", large(), Icons::paintSpeaker);
}
public static ImageIcon micMutedLarge() {
return themed("INPUT_MUTED", IconTheme.TOOLBAR_SIZE, Icons::paintMicMuted);
return themed("INPUT_MUTED", large(), Icons::paintMicMuted);
}
public static ImageIcon speakerMutedLarge() {
return themed("OUTPUT_MUTED", IconTheme.TOOLBAR_SIZE, Icons::paintSpeakerMuted);
return themed("OUTPUT_MUTED", large(), Icons::paintSpeakerMuted);
}
/** Marks the server that currently owns the microphone. */
public static ImageIcon micActive() {
return themed("ACTIVATE_MICROPHONE", IconTheme.TOOLBAR_SIZE, Icons::paintMicActive);
return themed("ACTIVATE_MICROPHONE", large(), Icons::paintMicActive);
}
/** The same marker at tab-label size. */
@@ -262,7 +275,7 @@ public final class Icons {
public static ImageIcon channelClientPair() {
ImageIcon channel = channel(true);
ImageIcon edit = themed("EDIT", Icons::paintEdit);
int gap = 8;
int gap = UIScale.scale(8);
int width = channel.getIconWidth() + gap + edit.getIconWidth();
int height = Math.max(channel.getIconHeight(), edit.getIconHeight());
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
@@ -282,15 +295,15 @@ public final class Icons {
/** The toolbar's away marker. */
public static ImageIcon away() {
return themed("AWAY", IconTheme.TOOLBAR_SIZE, g -> paintPerson(g, Theme.away()));
return themed("AWAY", large(), g -> paintPerson(g, Theme.away()));
}
public static ImageIcon settings() {
return themed("SETTINGS", IconTheme.TOOLBAR_SIZE, Icons::paintSettings);
return themed("SETTINGS", large(), Icons::paintSettings);
}
public static ImageIcon app() {
return app(SZ);
return app(small());
}
public static ImageIcon app(int size) {

View File

@@ -1,5 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Bookmarks;
import com.ts3client.config.IdentityEntry;
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.DefaultListCellRenderer;
@@ -73,12 +74,12 @@ public final class IdentitiesDialog extends JDialog {
list.setVisibleRowCount(10);
JScrollPane scroll = new JScrollPane(list);
scroll.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
scroll.setPreferredSize(new Dimension(220, 260));
scroll.setBorder(new ScaledEmptyBorder(8, 8, 8, 8));
scroll.setPreferredSize(UIScale.scale(new Dimension(220, 260)));
JPanel buttons = new JPanel();
buttons.setLayout(new BoxLayout(buttons, BoxLayout.Y_AXIS));
buttons.setBorder(BorderFactory.createEmptyBorder(8, 0, 8, 8));
buttons.setBorder(new ScaledEmptyBorder(8, 0, 8, 8));
addButton(buttons, "New…", this::newIdentity);
addButton(buttons, "Import…", this::importIdentity);
if (TeamSpeakSettingsDb.locate() != null) {
@@ -103,15 +104,15 @@ public final class IdentitiesDialog extends JDialog {
Dialogs.closeOnEscape(this);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setMinimumSize(new Dimension(640, 320));
setMinimumSize(UIScale.scale(new Dimension(640, 320)));
setLocationRelativeTo(owner);
}
private JPanel buildDetails() {
JPanel form = new JPanel(new GridBagLayout());
form.setBorder(BorderFactory.createEmptyBorder(12, 4, 12, 8));
form.setBorder(new ScaledEmptyBorder(12, 4, 12, 8));
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.NORTHWEST;
c.fill = GridBagConstraints.HORIZONTAL;
@@ -149,7 +150,7 @@ public final class IdentitiesDialog extends JDialog {
b.setMaximumSize(new Dimension(Integer.MAX_VALUE, b.getPreferredSize().height));
b.addActionListener(e -> action.run());
panel.add(b);
panel.add(Box.createVerticalStrut(4));
panel.add(Box.createVerticalStrut(UIScale.scale(4)));
}
private String label(IdentityEntry e) {
@@ -335,8 +336,8 @@ public final class IdentitiesDialog extends JDialog {
JDialog progress = new JDialog(this, "Please wait", true);
JLabel status = new JLabel(message);
JPanel panel = new JPanel(new BorderLayout(8, 8));
panel.setBorder(BorderFactory.createEmptyBorder(16, 16, 12, 16));
JPanel panel = new JPanel(new BorderLayout(UIScale.scale(8), UIScale.scale(8)));
panel.setBorder(new ScaledEmptyBorder(16, 16, 12, 16));
panel.add(status, BorderLayout.CENTER);
if (cancellable) {
JButton cancel = new JButton("Stop");

View File

@@ -1,5 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.contacts.Contact;
import com.ts3client.net.ChannelNode;
import com.ts3client.net.ClientEntry;
@@ -65,7 +67,7 @@ public final class InfoPanel extends JPanel {
public InfoPanel() {
super(new BorderLayout());
pane.setBorder(BorderFactory.createEmptyBorder(6, 8, 6, 8));
pane.setBorder(new ScaledEmptyBorder(6, 8, 6, 8));
pane.addHyperlinkListener(e -> {
if (e.getEventType() != javax.swing.event.HyperlinkEvent.EventType.ACTIVATED) return;
Links.open(e.getDescription(), this);
@@ -82,7 +84,7 @@ public final class InfoPanel extends JPanel {
toggle.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
toggle.setToolTipText("Show these details in a chat tab instead");
toggle.setOpaque(true);
toggle.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
toggle.setBorder(new ScaledEmptyBorder(1, 4, 1, 4));
toggle.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
@@ -188,7 +190,7 @@ public final class InfoPanel extends JPanel {
private String body() {
if (shownChannel != null) return channelBody(shownChannel);
if (shownClient != null) {
renderedAvatarBox = inChatTab ? AvatarHtml.MAX_SIZE : avatarBox();
renderedAvatarBox = inChatTab ? UIScale.scale(AvatarHtml.MAX_SIZE) : avatarBox();
return clientBody(shownClient, shownModel, shownIcons, shownContact, shownAvatar, renderedAvatarBox);
}
return "<i class='muted'>Select a channel or client to see details.</i>";
@@ -201,9 +203,11 @@ public final class InfoPanel extends JPanel {
*/
private int avatarBox() {
int width = scroll.getViewport().getWidth();
if (width <= 0) return AvatarHtml.MAX_SIZE;
int box = width * 2 / 5 / AVATAR_BOX_STEP * AVATAR_BOX_STEP;
return Math.max(AVATAR_MIN_SIZE, Math.min(AvatarHtml.MAX_SIZE, box));
int max = UIScale.scale(AvatarHtml.MAX_SIZE);
if (width <= 0) return max;
int step = UIScale.scale(AVATAR_BOX_STEP);
int box = width * 2 / 5 / step * step;
return Math.max(UIScale.scale(AVATAR_MIN_SIZE), Math.min(max, box));
}
private static String channelBody(ChannelNode ch) {
@@ -231,7 +235,7 @@ public final class InfoPanel extends JPanel {
// The avatar sits top-right beside the details, like the official client's info frame.
return "<table width='100%' cellpadding='0' cellspacing='0'><tr>"
+ "<td valign='top'>" + details + "</td>"
+ "<td valign='top' align='right' style='padding-left:8px'>" + picture + "</td>"
+ "<td valign='top' align='right' style='padding-left:" + HtmlStyles.px(8) + "'>" + picture + "</td>"
+ "</tr></table>";
}
@@ -297,11 +301,11 @@ public final class InfoPanel extends JPanel {
if (group.iconId == 0) return "";
File file = icons.file(group.iconId);
if (file == null) return "";
return "<img src='" + file.toURI() + "' width='14' height='14'> ";
return "<img src='" + file.toURI() + "' width='" + UIScale.scale(14) + "' height='" + UIScale.scale(14) + "'> ";
}
private static String heading(String text) {
return "<div style='font-weight:bold;font-size:13px;margin-bottom:4px'>" + text + "</div>";
return "<div style='font-weight:bold;font-size:13px;margin-bottom:" + HtmlStyles.px(4) + "'>" + text + "</div>";
}
private static void row(StringBuilder sb, String label, String value) {

View File

@@ -1,5 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import javax.swing.JComponent;
import java.awt.Color;
import java.awt.Dimension;
@@ -25,11 +27,11 @@ public final class LevelMeter extends JComponent {
private volatile boolean transmitting;
public LevelMeter() {
setPreferredSize(new Dimension(240, 18));
setPreferredSize(UIScale.scale(new Dimension(240, 18)));
// A bare JComponent reports no minimum of its own, so a layout tight on space
// collapses the bar to nothing. Keep the height and let only the width give.
setMinimumSize(new Dimension(60, 18));
setMaximumSize(new Dimension(Integer.MAX_VALUE, 18));
setMinimumSize(UIScale.scale(new Dimension(60, 18)));
setMaximumSize(new Dimension(Integer.MAX_VALUE, UIScale.scale(18)));
}
public void setLevel(double db) {
@@ -70,16 +72,17 @@ public final class LevelMeter extends JComponent {
int h = getHeight();
g.setColor(new Color(0x2B2B2B));
g.fillRoundRect(0, 0, w - 1, h - 1, 6, 6);
int arc = UIScale.scale(6);
g.fillRoundRect(0, 0, w - 1, h - 1, arc, arc);
int level = dbToX(levelDb, w - 2);
g.setColor(transmitting ? Theme.talking() : new Color(0x5A9BD4));
g.fillRoundRect(1, 1, Math.max(0, level), h - 3, 5, 5);
g.fillRoundRect(1, 1, Math.max(0, level), h - 3, arc - 1, arc - 1);
if (showThreshold) {
int tx = dbToX(thresholdDb, w - 2);
g.setColor(new Color(0xF0C419));
g.fillRect(tx, 1, 2, h - 3);
g.fillRect(tx, 1, UIScale.scale(2), h - 3);
}
}
}

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.audio.AudioBackend;
import com.ts3client.audio.desktop.DesktopAudioBackend;
import com.ts3client.config.AwayMessages;
@@ -14,7 +15,6 @@ import com.ts3client.net.ChannelNode;
import com.ts3client.sound.SoundNotifier;
import com.ts3client.sound.SoundPlayer;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
@@ -104,7 +104,7 @@ public final class MainFrame extends JFrame implements ServerTabPane.Listener {
});
// Catches Ctrl+C / SIGTERM so we still leave the servers cleanly.
Runtime.getRuntime().addShutdownHook(shutdownHook);
setMinimumSize(new Dimension(720, 480));
setMinimumSize(UIScale.scale(new Dimension(720, 480)));
tray = new TrayController(this, this::quit);
menuBar = buildMenuBar();
@@ -128,7 +128,7 @@ public final class MainFrame extends JFrame implements ServerTabPane.Listener {
statusTimer = new javax.swing.Timer(3000, e -> updateConnectionStatus());
statusTimer.start();
setSize(880, 560);
setSize(UIScale.scale(new Dimension(880, 560)));
setLocationRelativeTo(null);
// Deferred so the window is on screen before we start connecting.
@@ -639,7 +639,7 @@ public final class MainFrame extends JFrame implements ServerTabPane.Listener {
JTextField labelField = new JTextField(selected.title());
JCheckBox joinChannel = new JCheckBox("Join \"" + channelPath + "\" on connect", !channelPath.isEmpty());
JPanel form = new JPanel(new GridLayout(0, 1, 0, 2));
JPanel form = new JPanel(new GridLayout(0, 1, 0, UIScale.scale(2)));
form.add(new JLabel("Bookmark label:"));
form.add(labelField);
if (!channelPath.isEmpty()) form.add(joinChannel);

View File

@@ -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.Box;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
@@ -74,7 +75,7 @@ final class MainToolbar extends JToolBar {
setFloatable(false);
setBackground(Theme.toolbarBg());
setBorder(BorderFactory.createEmptyBorder(4, 6, 4, 6));
setBorder(new ScaledEmptyBorder(4, 6, 4, 6));
setComponentPopupMenu(buildContextMenu());
connectButton = new JButton(Icons.connect());
@@ -152,7 +153,7 @@ final class MainToolbar extends JToolBar {
/** The Master Volume slider: multiplies both voice and notification volume. */
private JComponent buildMasterVolumeControl() {
JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 0));
JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, UIScale.scale(4), 0));
panel.setOpaque(false);
panel.setAlignmentY(Component.CENTER_ALIGNMENT);
@@ -164,7 +165,7 @@ final class MainToolbar extends JToolBar {
slider.setOpaque(false);
slider.setToolTipText("Master volume (voice + notifications) — scroll to adjust");
slider.setAlignmentY(Component.CENTER_ALIGNMENT);
slider.setPreferredSize(new Dimension(135, slider.getPreferredSize().height));
slider.setPreferredSize(new Dimension(UIScale.scale(135), slider.getPreferredSize().height));
slider.addChangeListener(e -> {
settings.masterVolume = slider.getValue() / 100.0;
listener.onMasterVolumeChanged();

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.net.ClientEntry;
import javax.swing.DefaultCellEditor;
@@ -103,7 +104,7 @@ final class NicknameCellEditor extends DefaultTreeCellEditor {
if (!(c instanceof JLabel)) return;
editingIcon = ((JLabel) c).getIcon();
offset = editingIcon == null ? 0
: editingIcon.getIconWidth() + renderer.getIconTextGap() - FIELD_NUDGE;
: editingIcon.getIconWidth() + renderer.getIconTextGap() - UIScale.scale(FIELD_NUDGE);
}
/** The edited nickname, trimmed; empty when nothing usable was typed. */

View File

@@ -1,12 +1,13 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Settings;
import com.ts3client.sound.NotificationSettings;
import com.ts3client.sound.SoundEvent;
import com.ts3client.sound.SoundNotifier;
import com.ts3client.sound.SoundPack;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
@@ -79,7 +80,7 @@ final class NotificationsPanel extends JPanel {
private final EventTableModel tableModel = new EventTableModel();
NotificationsPanel(Settings settings, SoundNotifier sounds) {
super(new BorderLayout(0, 8));
super(new BorderLayout(0, UIScale.scale(8)));
this.settings = settings;
this.sounds = sounds;
this.working = settings.notifications.copy();
@@ -91,7 +92,7 @@ final class NotificationsPanel extends JPanel {
buildRows();
this.table = buildTable();
setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
setBorder(new ScaledEmptyBorder(12, 12, 12, 12));
add(buildHeader(), BorderLayout.NORTH);
add(new JScrollPane(table), BorderLayout.CENTER);
add(buildFooter(), BorderLayout.SOUTH);
@@ -122,7 +123,7 @@ final class NotificationsPanel extends JPanel {
private JComponent buildHeader() {
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;
@@ -132,7 +133,7 @@ final class NotificationsPanel extends JPanel {
test.setToolTipText("Play this pack's test sound");
test.addActionListener(e -> sounds.preview(SoundEvent.SPECIAL_SOUND_TEST));
JPanel packRow = new JPanel(new BorderLayout(6, 0));
JPanel packRow = new JPanel(new BorderLayout(UIScale.scale(6), 0));
packRow.add(packCombo, BorderLayout.CENTER);
packRow.add(test, BorderLayout.EAST);
@@ -146,7 +147,7 @@ final class NotificationsPanel extends JPanel {
volume.setToolTipText("Volume of the notification sounds");
addRow(p, c, row++, new JLabel("Sound volume:"), volume);
JPanel dirRow = new JPanel(new BorderLayout(6, 0));
JPanel dirRow = new JPanel(new BorderLayout(UIScale.scale(6), 0));
JButton browse = new JButton("Browse…");
browse.addActionListener(e -> browseForPackFolder());
dirRow.add(packDirField, BorderLayout.CENTER);
@@ -157,10 +158,10 @@ final class NotificationsPanel extends JPanel {
}
private JComponent buildFooter() {
JPanel p = new JPanel(new BorderLayout(6, 0));
JPanel p = new JPanel(new BorderLayout(UIScale.scale(6), 0));
JLabel hint = new JLabel("<html><b>Bold</b> actions are important: they are still played "
+ "while your speakers are muted.</html>");
hint.setBorder(BorderFactory.createEmptyBorder(4, 0, 0, 0));
hint.setBorder(new ScaledEmptyBorder(4, 0, 0, 0));
JPanel buttons = new JPanel();
JButton important = new JButton("Toggle important");
@@ -187,12 +188,12 @@ final class NotificationsPanel extends JPanel {
t.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
t.setShowGrid(false);
t.setTableHeader(null);
t.setRowHeight(Math.max(t.getRowHeight(), t.getFontMetrics(t.getFont()).getHeight() + 6));
t.getColumnModel().getColumn(0).setMaxWidth(TOGGLE_COLUMN_WIDTH);
t.getColumnModel().getColumn(0).setMinWidth(TOGGLE_COLUMN_WIDTH);
t.setRowHeight(Math.max(t.getRowHeight(), t.getFontMetrics(t.getFont()).getHeight() + UIScale.scale(6)));
t.getColumnModel().getColumn(0).setMaxWidth(UIScale.scale(TOGGLE_COLUMN_WIDTH));
t.getColumnModel().getColumn(0).setMinWidth(UIScale.scale(TOGGLE_COLUMN_WIDTH));
t.getColumnModel().getColumn(0).setCellRenderer(new ToggleRenderer(t.getDefaultRenderer(Boolean.class)));
t.getColumnModel().getColumn(1).setCellRenderer(new ActionRenderer());
t.setPreferredScrollableViewportSize(new Dimension(360, 260));
t.setPreferredScrollableViewportSize(UIScale.scale(new Dimension(360, 260)));
t.addMouseListener(new MouseAdapter() {
@Override
@@ -381,11 +382,11 @@ final class NotificationsPanel extends JPanel {
if (row.isHeading()) {
setFont(base.deriveFont(Font.BOLD));
setForeground(selected ? table.getSelectionForeground() : table.getForeground());
setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
setBorder(new ScaledEmptyBorder(2, 2, 2, 2));
setToolTipText(null);
return this;
}
setBorder(BorderFactory.createEmptyBorder(2, 18, 2, 2));
setBorder(new ScaledEmptyBorder(2, 18, 2, 2));
setFont(base.deriveFont(working.isImportant(row.event) ? Font.BOLD : Font.PLAIN));
boolean silent = !sounds.hasSound(row.event);
setForeground(selected ? table.getSelectionForeground()

View File

@@ -1,6 +1,7 @@
package com.ts3client.ui;
import javax.swing.BorderFactory;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import javax.swing.BoxLayout;
import javax.swing.JComponent;
import javax.swing.JLabel;
@@ -54,7 +55,7 @@ final class ReasonDialog {
static JLabel label(String text) {
JLabel label = new JLabel(text);
label.setAlignmentX(Component.LEFT_ALIGNMENT);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));
label.setBorder(new ScaledEmptyBorder(0, 0, 4, 0));
return label;
}

View File

@@ -1,10 +1,11 @@
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.Group;
import com.ts3client.net.TeamspeakConnection;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.GrayFilter;
import javax.swing.ImageIcon;
@@ -48,10 +49,10 @@ final class ServerGroupsDialog extends JDialog {
list.setLayout(new BoxLayout(list, BoxLayout.Y_AXIS));
JScrollPane scroll = new JScrollPane(list);
scroll.setPreferredSize(new Dimension(280, 360));
scroll.setPreferredSize(UIScale.scale(new Dimension(280, 360)));
scroll.getVerticalScrollBar().setUnitIncrement(16);
header.setBorder(BorderFactory.createEmptyBorder(8, 10, 4, 10));
header.setBorder(new ScaledEmptyBorder(8, 10, 4, 10));
header.setHorizontalAlignment(SwingConstants.LEFT);
JButton close = new JButton("Close");

View File

@@ -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.JButton;
import javax.swing.JComponent;
@@ -150,9 +153,9 @@ final class ServerTabPane extends JPanel {
/** A tab's label: title, microphone marker and a close button. */
private JComponent buildTabComponent(ServerTab tab, boolean hasMic) {
JPanel cell = new JPanel(new FlowLayout(FlowLayout.LEFT, 4, 0));
JPanel cell = new JPanel(new FlowLayout(FlowLayout.LEFT, UIScale.scale(4), 0));
cell.setOpaque(false);
cell.setBorder(BorderFactory.createEmptyBorder(1, 0, 1, 0));
cell.setBorder(new ScaledEmptyBorder(1, 0, 1, 0));
JLabel label = new JLabel(tab.title(), hasMic ? Icons.micActiveSmall() : null, JLabel.LEADING);
label.setFont(Theme.uiFont());
@@ -175,7 +178,7 @@ final class ServerTabPane extends JPanel {
close.setContentAreaFilled(false);
close.setMargin(new Insets(0, 0, 0, 0));
close.setForeground(Theme.chatSystem());
close.setPreferredSize(new Dimension(14, 14));
close.setPreferredSize(UIScale.scale(new Dimension(14, 14)));
close.setToolTipText("Close this connection");
close.addActionListener(e -> listener.closeTab(tab));
cell.add(close);

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.config.Settings;
import com.ts3client.contacts.ContactStore;
import com.ts3client.contacts.ContactType;
@@ -168,7 +169,7 @@ public final class ServerTreePanel extends JScrollPane {
ui.setLeftChildIndent(4);
ui.setRightChildIndent(10);
}
tree.setRowHeight(20);
tree.setRowHeight(UIScale.scale(20));
tree.setBackground(Theme.treeBg());
tree.setFont(Theme.uiFont());
ServerTreeCellRenderer renderer = new ServerTreeCellRenderer(id -> id == selfClientId, contacts, settings);

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.audio.OpusParameters;
import com.ts3client.audio.VoiceInput;
import com.ts3client.audio.VoiceOutput;
@@ -92,8 +93,8 @@ public final class SettingsDialog extends JDialog {
});
pack();
setSize(new Dimension(480, 540));
setMinimumSize(new Dimension(420, 360));
setSize(UIScale.scale(new Dimension(480, 540)));
setMinimumSize(UIScale.scale(new Dimension(420, 360)));
setLocationRelativeTo(owner);
}

View File

@@ -1,5 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import javax.swing.JSpinner;
import java.awt.Dimension;
@@ -19,7 +21,7 @@ final class Spinners {
static JSpinner compact(JSpinner spinner) {
JSpinner.NumberEditor editor = new JSpinner.NumberEditor(spinner, "#");
spinner.setEditor(editor);
Dimension size = new Dimension(WIDTH, spinner.getPreferredSize().height);
Dimension size = new Dimension(UIScale.scale(WIDTH), spinner.getPreferredSize().height);
spinner.setPreferredSize(size);
spinner.setMaximumSize(size);
return spinner;

View File

@@ -1,6 +1,7 @@
package com.ts3client.ui;
import javax.swing.BorderFactory;
import com.formdev.flatlaf.util.ScaledEmptyBorder;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
@@ -14,7 +15,7 @@ final class StatusBar extends JPanel {
StatusBar() {
super(new BorderLayout());
setBackground(Theme.statusBg());
setBorder(BorderFactory.createEmptyBorder(3, 8, 3, 8));
setBorder(new ScaledEmptyBorder(3, 8, 3, 8));
statusLabel.setFont(Theme.uiFont());
codecLabel.setFont(Theme.uiFont());
codecLabel.setForeground(Theme.chatSystem());

View File

@@ -1,6 +1,7 @@
package com.ts3client.ui;
import com.formdev.flatlaf.ui.FlatUIUtils;
import com.formdev.flatlaf.util.UIScale;
import javax.swing.UIManager;
import java.awt.Color;
@@ -36,7 +37,7 @@ public final class Theme {
public static Font uiFont() {
Font f = UIManager.getFont("Label.font");
return f != null ? f : new Font("SansSerif", Font.PLAIN, 12);
return f != null ? f : new Font("SansSerif", Font.PLAIN, UIScale.scale(12));
}
public static Font uiBold() {

View File

@@ -1,5 +1,6 @@
package com.ts3client.ui;
import com.formdev.flatlaf.util.UIScale;
import com.ts3client.audio.InputLevel;
import com.ts3client.audio.OpusParameters;
import com.ts3client.audio.VoiceInput;
@@ -105,11 +106,11 @@ final class VoiceActivationPanel extends FormPanel {
c.gridx = 0;
c.gridy = row;
c.gridwidth = 2;
c.insets = new Insets(10, 4, 2, 4);
c.insets = UIScale.scale(new Insets(10, 4, 2, 4));
add(new JLabel("Input level:"), c);
c.gridy = ++row;
add(meter, c);
c.insets = new Insets(4, 4, 4, 4);
c.insets = UIScale.scale(new Insets(4, 4, 4, 4));
c.gridy = ++row;
add(buildTestControls(), c);
c.gridwidth = 1;