Fix scaling issues on Linux
This commit is contained in:
4 files changed
+119
-26
No files matched your search
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
package com.loomcom.symon;
|
package com.loomcom.symon;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import com.loomcom.symon.devices.Memory;
|
import com.loomcom.symon.devices.Memory;
|
||||||
import com.loomcom.symon.exceptions.*;
|
import com.loomcom.symon.exceptions.*;
|
||||||
import com.loomcom.symon.machines.Machine;
|
import com.loomcom.symon.machines.Machine;
|
||||||
@@ -53,7 +54,7 @@ public class Simulator {
|
|||||||
|
|
||||||
// UI constants
|
// UI constants
|
||||||
private static final int DEFAULT_FONT_SIZE = 12;
|
private static final int DEFAULT_FONT_SIZE = 12;
|
||||||
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE);
|
private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, UIScale.scale(DEFAULT_FONT_SIZE));
|
||||||
private static final int CONSOLE_BORDER_WIDTH = 10;
|
private static final int CONSOLE_BORDER_WIDTH = 10;
|
||||||
|
|
||||||
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
|
// Clock periods, in NS, for each speed. 0MHz, 1MHz, 2MHz, 3MHz, 4MHz, 5MHz, 6MHz, 7MHz, 8MHz.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
package com.loomcom.symon.ui;
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.formdev.flatlaf.util.UIScale;
|
||||||
import com.loomcom.symon.Cpu;
|
import com.loomcom.symon.Cpu;
|
||||||
import com.loomcom.symon.CpuState;
|
import com.loomcom.symon.CpuState;
|
||||||
import com.loomcom.symon.machines.Machine;
|
import com.loomcom.symon.machines.Machine;
|
||||||
@@ -34,6 +35,7 @@ import javax.swing.plaf.metal.MetalTextFieldUI;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UI component that displays the current state of the simulated CPU.
|
* UI component that displays the current state of the simulated CPU.
|
||||||
@@ -74,7 +76,7 @@ public class StatusPanel extends JPanel {
|
|||||||
|
|
||||||
private static final int EMPTY_BORDER = 10;
|
private static final int EMPTY_BORDER = 10;
|
||||||
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
|
private static final Border LABEL_BORDER = BorderFactory.createEmptyBorder(0, 5, 0, 0);
|
||||||
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 12);
|
private static final Font LABEL_FONT = new Font(Font.SANS_SERIF, Font.BOLD, UIScale.scale(12));
|
||||||
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
|
private static final Dimension LARGE_TEXT_FIELD_SIZE = new Dimension(134, 22);
|
||||||
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
|
private static final Dimension SMALL_TEXT_FIELD_SIZE = new Dimension(65, 22);
|
||||||
|
|
||||||
@@ -99,20 +101,20 @@ public class StatusPanel extends JPanel {
|
|||||||
JPanel statusFlagsPanel = new JPanel();
|
JPanel statusFlagsPanel = new JPanel();
|
||||||
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
statusFlagsPanel.setAlignmentX(LEFT_ALIGNMENT);
|
||||||
|
|
||||||
carryOn = new ImageIcon(this.getClass().getResource("/C_on.png"));
|
carryOn = loadScaledIcon("/C_on.png");
|
||||||
carryOff = new ImageIcon(this.getClass().getResource("/C_off.png"));
|
carryOff = loadScaledIcon("/C_off.png");
|
||||||
zeroOn = new ImageIcon(this.getClass().getResource("/Z_on.png"));
|
zeroOn = loadScaledIcon("/Z_on.png");
|
||||||
zeroOff = new ImageIcon(this.getClass().getResource("/Z_off.png"));
|
zeroOff = loadScaledIcon("/Z_off.png");
|
||||||
irqOn = new ImageIcon(this.getClass().getResource("/I_on.png"));
|
irqOn = loadScaledIcon("/I_on.png");
|
||||||
irqOff = new ImageIcon(this.getClass().getResource("/I_off.png"));
|
irqOff = loadScaledIcon("/I_off.png");
|
||||||
decimalOn = new ImageIcon(this.getClass().getResource("/D_on.png"));
|
decimalOn = loadScaledIcon("/D_on.png");
|
||||||
decimalOff = new ImageIcon(this.getClass().getResource("/D_off.png"));
|
decimalOff = loadScaledIcon("/D_off.png");
|
||||||
breakOn = new ImageIcon(this.getClass().getResource("/B_on.png"));
|
breakOn = loadScaledIcon("/B_on.png");
|
||||||
breakOff = new ImageIcon(this.getClass().getResource("/B_off.png"));
|
breakOff = loadScaledIcon("/B_off.png");
|
||||||
overflowOn = new ImageIcon(this.getClass().getResource("/O_on.png"));
|
overflowOn = loadScaledIcon("/O_on.png");
|
||||||
overflowOff = new ImageIcon(this.getClass().getResource("/O_off.png"));
|
overflowOff = loadScaledIcon("/O_off.png");
|
||||||
negativeOn = new ImageIcon(this.getClass().getResource("/N_on.png"));
|
negativeOn = loadScaledIcon("/N_on.png");
|
||||||
negativeOff = new ImageIcon(this.getClass().getResource("/N_off.png"));
|
negativeOff = loadScaledIcon("/N_off.png");
|
||||||
|
|
||||||
// Initialize all to off
|
// Initialize all to off
|
||||||
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
|
carryFlagLabel = new JLabel(carryOff, JLabel.CENTER);
|
||||||
@@ -387,9 +389,11 @@ public class StatusPanel extends JPanel {
|
|||||||
JTextField textField = new JTextField("");
|
JTextField textField = new JTextField("");
|
||||||
textField.setAlignmentX(LEFT_ALIGNMENT);
|
textField.setAlignmentX(LEFT_ALIGNMENT);
|
||||||
textField.setEditable(editable);
|
textField.setEditable(editable);
|
||||||
textField.setMinimumSize(size);
|
|
||||||
textField.setMaximumSize(size);
|
Dimension scaleSize = UIScale.scale(size);
|
||||||
textField.setPreferredSize(size);
|
textField.setMinimumSize(scaleSize);
|
||||||
|
textField.setMaximumSize(scaleSize);
|
||||||
|
textField.setPreferredSize(scaleSize);
|
||||||
|
|
||||||
return textField;
|
return textField;
|
||||||
}
|
}
|
||||||
@@ -398,4 +402,14 @@ public class StatusPanel extends JPanel {
|
|||||||
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
|
String val = source.getText().replaceAll("[^0-9a-fA-F]", "");
|
||||||
return Integer.parseInt(val, 16);
|
return Integer.parseInt(val, 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImageIcon loadScaledIcon(String resourcePath) {
|
||||||
|
ImageIcon icon = new ImageIcon(Objects.requireNonNull(this.getClass().getResource(resourcePath)));
|
||||||
|
|
||||||
|
int scaledWidth = UIScale.scale(icon.getIconWidth());
|
||||||
|
int scaledHeight = UIScale.scale(icon.getIconHeight());
|
||||||
|
|
||||||
|
Image scaleImage = icon.getImage().getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
|
||||||
|
return new ImageIcon(scaleImage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,13 @@ import com.formdev.flatlaf.FlatLaf;
|
|||||||
import com.formdev.flatlaf.FlatLightLaf;
|
import com.formdev.flatlaf.FlatLightLaf;
|
||||||
import com.formdev.flatlaf.util.SystemInfo;
|
import com.formdev.flatlaf.util.SystemInfo;
|
||||||
import com.jthemedetecor.OsThemeDetector;
|
import com.jthemedetecor.OsThemeDetector;
|
||||||
import com.loomcom.symon.machines.BenEaterMachine;
|
|
||||||
import com.loomcom.symon.machines.MulticompMachine;
|
import java.awt.*;
|
||||||
import com.loomcom.symon.machines.SimpleMachine;
|
import java.beans.PropertyChangeListener;
|
||||||
import com.loomcom.symon.machines.SymonMachine;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public final class Ui {
|
public final class Ui {
|
||||||
private final static Logger logger = LoggerFactory.getLogger(Ui.class.getName());
|
private final static Logger logger = LoggerFactory.getLogger(Ui.class.getName());
|
||||||
@@ -51,10 +54,14 @@ public final class Ui {
|
|||||||
// Location of properties resources for app-specific UI defaults:
|
// Location of properties resources for app-specific UI defaults:
|
||||||
FlatLaf.registerCustomDefaultsSource("com.loomcom.symon.themes");
|
FlatLaf.registerCustomDefaultsSource("com.loomcom.symon.themes");
|
||||||
|
|
||||||
|
logger.debug("AWT Toolkit class: {}", Toolkit.getDefaultToolkit().getClass().getName());
|
||||||
|
|
||||||
// Enable automatic scaling, based on system DPI settings:
|
// Enable automatic scaling, based on system DPI settings:
|
||||||
System.setProperty("sun.java2d.uiScale.enabled", "true");
|
//System.setProperty("sun.java2d.uiScale.enabled", "true");
|
||||||
System.setProperty("sun.java2d.uiScale", "1x");
|
//System.setProperty("sun.java2d.uiScale", "1x");
|
||||||
System.setProperty("flatlaf.uiScale", "1x");
|
//System.setProperty("flatlaf.uiScale", "1x");
|
||||||
|
|
||||||
|
applyDetectedUiScale();
|
||||||
|
|
||||||
// OS-specific tweaking:
|
// OS-specific tweaking:
|
||||||
|
|
||||||
@@ -124,4 +131,21 @@ public final class Ui {
|
|||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void applyDetectedUiScale() {
|
||||||
|
Double rawScale = X11ResourceReader.readXftDpiScale(); // ex: 1.5, 2.0, 2.5...
|
||||||
|
|
||||||
|
if (rawScale != null && rawScale > 1.0) {
|
||||||
|
// flatlaf.uiScale aceita frações — mantém a precisão original
|
||||||
|
System.setProperty("flatlaf.uiScale", rawScale + "x");
|
||||||
|
|
||||||
|
// sun.java2d.uiScale só aceita inteiros — arredonda para cima
|
||||||
|
int javaScale = (int) Math.ceil(rawScale);
|
||||||
|
//System.setProperty("sun.java2d.uiScale", String.valueOf(javaScale));
|
||||||
|
|
||||||
|
logger.debug("Detected scale {}x, applying flatlaf.uiScale={}x, sun.java2d.uiScale={}x",
|
||||||
|
rawScale, rawScale, javaScale);
|
||||||
|
} else {
|
||||||
|
logger.debug("No X11 HiDPI scale detected, using default.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.loomcom.symon.ui;
|
||||||
|
|
||||||
|
import com.sun.jna.Library;
|
||||||
|
import com.sun.jna.Native;
|
||||||
|
import com.sun.jna.Pointer;
|
||||||
|
import com.sun.jna.ptr.IntByReference;
|
||||||
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class X11ResourceReader {
|
||||||
|
|
||||||
|
private final static Logger logger = LoggerFactory.getLogger(X11ResourceReader.class.getName());
|
||||||
|
|
||||||
|
|
||||||
|
public interface X11 extends Library {
|
||||||
|
X11 INSTANCE = Native.load("X11", X11.class);
|
||||||
|
|
||||||
|
Pointer XOpenDisplay(String display);
|
||||||
|
Pointer XResourceManagerString(Pointer display);
|
||||||
|
void XCloseDisplay(Pointer display);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Double readXftDpiScale() {
|
||||||
|
Pointer display = null;
|
||||||
|
try {
|
||||||
|
display = X11.INSTANCE.XOpenDisplay(null);
|
||||||
|
if (display == null) {
|
||||||
|
return null; // não conseguimos ligar ao X server (ex: Wayland puro sem XWayland)
|
||||||
|
}
|
||||||
|
|
||||||
|
Pointer resourceString = X11.INSTANCE.XResourceManagerString(display);
|
||||||
|
if (resourceString == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String resources = resourceString.getString(0);
|
||||||
|
for (String line : resources.split("\n")) {
|
||||||
|
if (line.startsWith("Xft.dpi:")) {
|
||||||
|
int dpi = Integer.parseInt(line.split(":")[1].trim());
|
||||||
|
return Math.round((dpi / 96.0) * 4) / 4.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (UnsatisfiedLinkError | Exception e) {
|
||||||
|
logger.debug("Could not read X11 resource manager (libX11 unavailable?)", e);
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (display != null) {
|
||||||
|
X11.INSTANCE.XCloseDisplay(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user