JFormDesigner / FlatLaf

FlatLaf - Swing Look and Feel (with Darcula/IntelliJ themes support)
https://www.formdev.com/flatlaf/
Apache License 2.0
3.34k stars 266 forks source link

Black line sometimes painted on top of (native) window border on Windows 11 #852

Closed remcopoelstra closed 3 months ago

remcopoelstra commented 3 months ago

I have been noticing this already for quite some time now but it has never really bothered me, but this weekend I decided to look into this. I had to take a picture because I am not able to capture the issue with a screenshot or other screen recording tools:

IMG_20240618_124320

It gets more noticeable when setting a native border color on the window:

IMG_20240618_124219

I used the following code to reproduce the issue:

import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

import com.formdev.flatlaf.FlatLightLaf;
import com.formdev.flatlaf.ui.FlatNativeWindowsLibrary;

public class TopBorderTests {

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {

                FlatLightLaf.setup();

                JFrame frame1 = new JFrame();
                frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame1.setBounds(50, 50, 1100, 800);
                frame1.setVisible(true);
                FlatNativeWindowsLibrary.dwmSetWindowAttributeCOLORREF(FlatNativeWindowsLibrary.getHWND(frame1), FlatNativeWindowsLibrary.DWMWA_BORDER_COLOR, Color.green);

                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {

                        JFrame frame2 = new JFrame();
                        frame2.setBounds(225, 225, 800, 600);
                        frame2.setVisible(true);
                        frame2.addMouseListener(new MouseAdapter() {

                            @Override
                            public void mouseClicked(MouseEvent e) {
                                frame1.repaint();
                            }
                        });

                    }
                });

            }
        });
    }

}

It will probably not be easy to reproduce because I see different behavior in different situations, on my hidpi system the black line is visible immediately when running the code, but on a 100% scaled system the black line is visible after the repaint in the mouse-click listener. It also seems to be dependent of the frame sizes and their placement, moving frame2 around also shows the issue.

At first I thought I had found a workaround, one of my applications had the issue especially when selecting menus in the jmenubar, repainting the layeredpane of the frame instead of repainting just the menu made the issue go away. But later I noticed the issue is also present in the above situation where 2 frames overlap.

I have tested many different java runtimes (jbr, openjdk, 11, 19, 21, 22), maybe it's just my laptop, this is a Microsoft Surface Book 3 with Windows 11 pro.

The black line seems to be outside the area that is painted by swing (second picture shows it's painted over the native window border), I have been trying to build the native windows library myself, I wanted to play around with the WM_NCCALCSIZE code for example, but unfortunately I haven't yet been able to stop visual studio from giving build errors (lack of experience on my side).

If you have any idea's whats going on here I'm happy to hear them, I will also see if I can reproduce this on a few more windows 11 systems. Thanks!

remcopoelstra commented 3 months ago

It doesn't seem to be limited to just my development laptop, I was able to reproduce the issue on 2 more systems (surface go and a mini desktop pc).

DevCharly commented 3 months ago

Thanks for reporting and for the test case. Unfortunately I can not reproduce it on my Windows 11 PC. Tried various Java versions and various screen scalings.

Anybody else seen this?

Maybe there has something changed in some Windows 11 update. I have a relative old version here (21H2). What Windows 11 versions are you using? (see Settings > System > About > Windows specifications).

Does the top black line appear only for inactive windows?

... I have been trying to build the native windows library myself, I wanted to play around with the WM_NCCALCSIZE code for example ...

There is also a Java version of that code that uses JNA, which is easier to play around (and can be even debugged). See https://github.com/JFormDesigner/FlatLaf/blob/main/flatlaf-natives/flatlaf-natives-jna/src/main/java/com/formdev/flatlaf/natives/jna/windows/FlatWindowsNativeWindowBorder.java

To enable, build flatlaf-natives-jna project and add it as dependency to your project. Then comment out following line:

https://github.com/JFormDesigner/FlatLaf/blob/0c0d4bffbf38fa5abb5a4ee61a12c0a40717f019/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java#L253

and un-comment following lines:

https://github.com/JFormDesigner/FlatLaf/blob/0c0d4bffbf38fa5abb5a4ee61a12c0a40717f019/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatNativeWindowBorder.java#L248-L252

remcopoelstra commented 3 months ago

Thanks for the jna suggestion! It took me less than a minute to get this working and I'm already getting some interesting results. I will reply with more details later (i'm not sure about the windows versions on my other systems, but I think their probably all 23H2), but first I wanted to let you know my initial findings.

Without really having a idea what I'm doing I wanted to see what the WmNcCalcSize method does so I started increasing the params.rgrc[0].top value expecting to see a margin at the top of the window. I noticed I had to increase it with a value of 3 before I could see space being added between the windows control buttons and the top of the frame. I currently have the following code added at line 619 just before the params.write(); call:

            params.rgrc[0].top += 2;
            // write changed params back to native memory
            params.write();

And this 'solves' the issue (the title bar looks good, there's no space between the controls/menus and the top of the frame, and there's no black line painted anymore). This is probably not the actual fix, but it gives the impression that the height of the title-bar (or the top border thickness) is not what it's expected to be.

I just remembered this issue was reported earlier in #618 by the way.

Thanks again for the helpful suggestions!

DevCharly commented 3 months ago

Seems that there is one pixel missing at top on Windows 11 with FlatLaf window decorations...

If I add a line border:

frame1.getRootPane().setBorder( new LineBorder( Color.red ) );

the top red border is missing (all screenshots at 100% screen scaling):

grafik

Adding one pixel to params.rgrc[0].top makes the top red border visible:

grafik

Adding two pixels shows a gap between the window border and the red border (at 100%):

grafik

The gap is also there at 125%, but not at 150% or at 175%. But at 200%, I had to add two pixels to make the top red border visible...

On Windows 10, the red border is fully visible in current implementation:

Capture

Adding one pixel to params.rgrc[0].top does not hide the Windows 10 title bar:

Capture2

So increasing params.rgrc[0].top on Windows 10 would be a bad idea 😄 Anyway, adding border thickness to params.rgrc[0].top on Windows 11 seems to be a good idea.

Please try branch win11-top-border. There is one commit to the JNA code that uses DWMWA_VISIBLE_FRAME_BORDER_THICKNESS to get border thickness and adds it to top: https://github.com/JFormDesigner/FlatLaf/commits/win11-top-border

Border thickness is 1px at 100% and 125%, 2px at 150% to 225%, 3px at 250% and 300%, 4px at 350%

Does this fix the black top border?

remcopoelstra commented 3 months ago

That fixed it! 🍻🍺

I tested on all 3 systems that originally had the issue (all 23H2), and I checked at 100%, 125%, 150%, 175% and 200%, they all looked good, no more black lines and also no unexpected gaps, the reported border thickness corresponds to your list. Maximized windows also still look good.

It's difficult to say in what situations the black top border could appear, it seemed dependant of different factors like frame size, overlapping of frames, one of my applications showed it regularly when a new JFrame was presented in front of it, and another application would show it when selecting menu's in the menubar (but I think also only at certain frame sizes). I wouldn't be surprised if quite a few users have this issue without really noticing it, especially at higher scale factors the black line looks pretty similar to the native border.

Your implementation looks good to me, it's nice that the additional thickness is read from the OS, and applying it only on Windows 11 (or later) sounds good to me also.

Thanks again for the great work!

DevCharly commented 3 months ago

Many thanks for testing and your insights.

I've updated the C++ code in latest 3.5-SNAPSHOT: https://github.com/JFormDesigner/FlatLaf#snapshots