wxWidgets / Phoenix

wxPython's Project Phoenix. A new implementation of wxPython, better, stronger, faster than he was before.
http://wxpython.org/
2.29k stars 515 forks source link

Some(?) AGW widgets don't display high res text #1542

Open jbhopkins opened 4 years ago

jbhopkins commented 4 years ago

Operating system: MacOS 10.14.6 wxPython version & source: 4.0.4, from conda Python version & source: 3.7.5 from conda

Description of the problem: When using many of the agw widgets on a high resolution screen, such as a retina display laptop in MacOS, the fonts are drawn at low resolution and look fuzzy. This is not the case with other wxwidgets fonts.

I used the agw flatnotebook code demo code, below, to show this. You'll see that the tab labels (the part drawn by the flatnotebook) end up fuzzy.

class MyFrame(wx.Frame):

    def __init__(self, parent):

        wx.Frame.__init__(self, parent, -1, "FlatNotebook Demo")

        panel = wx.Panel(self)

        notebook = fnb.FlatNotebook(panel, -1)

        for i in range(3):
            caption = "Page %d"%(i+1)
            notebook.AddPage(self.CreatePage(notebook, caption), caption)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(notebook, 1, wx.ALL | wx.EXPAND, 5)
        panel.SetSizer(sizer)

    def CreatePage(self, notebook, caption):
        '''
        Creates a simple :class:`Panel` containing a :class:`TextCtrl`.

        :param `notebook`: an instance of `FlatNotebook`;
        :param `caption`: a simple label.
        '''

        p = wx.Panel(notebook)
        wx.StaticText(p, -1, caption, (20,20))
        wx.TextCtrl(p, -1, "", (20,40), (150,-1))
        return p

# our normal wxApp-derived class, as usual
if __name__ == '__main__':
    app = wx.App(0)

    frame = MyFrame(None)
    app.SetTopWindow(frame)
    frame.Show()

    app.MainLoop()

As shown on a high res display (looks fuzzy):

fuzzy

This can be fixed by monkey patching the OnPaint function to use wx.PaintDC instead of wx.BufferedPaintDC:

import wx
import wx.lib.agw.flatnotebook as fnb

#Monkey patch fnb.PageContainer
def OnPaintFNB(self, event):
    """
    Handles the ``wx.EVT_PAINT`` event for :class:`PageContainer`.

    :param `event`: a :class:`PaintEvent` event to be processed.
    """

    if wx.version().split()[0].strip()[0] == '4' and self.GetContentScaleFactor() > 1:
        dc = wx.PaintDC(self)
    else:
        dc = wx.BufferedPaintDC(self)

    parent = self.GetParent()

    renderer = self._mgr.GetRenderer(parent.GetAGWWindowStyleFlag())
    renderer.DrawTabs(self, dc)

    if self.HasAGWFlag(fnb.FNB_HIDE_ON_SINGLE_TAB) and len(self._pagesInfoVec) <= 1 or \
       self.HasAGWFlag(fnb.FNB_HIDE_TABS) or parent._orientation or \
       (parent._customPanel and len(self._pagesInfoVec) == 0):
        self.Hide()
        self.GetParent()._mainSizer.Layout()
        self.Refresh()

fnb.PageContainer.OnPaint = OnPaintFNB

class MyFrame(wx.Frame):

    def __init__(self, parent):

        wx.Frame.__init__(self, parent, -1, "FlatNotebook Demo")

        panel = wx.Panel(self)

        notebook = fnb.FlatNotebook(panel, -1)

        for i in range(3):
            caption = "Page %d"%(i+1)
            notebook.AddPage(self.CreatePage(notebook, caption), caption)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(notebook, 1, wx.ALL | wx.EXPAND, 5)
        panel.SetSizer(sizer)

    def CreatePage(self, notebook, caption):
        '''
        Creates a simple :class:`Panel` containing a :class:`TextCtrl`.

        :param `notebook`: an instance of `FlatNotebook`;
        :param `caption`: a simple label.
        '''

        p = wx.Panel(notebook)
        wx.StaticText(p, -1, caption, (20,20))
        wx.TextCtrl(p, -1, "", (20,40), (150,-1))
        return p

# our normal wxApp-derived class, as usual
if __name__ == '__main__':
    app = wx.App(0)

    frame = MyFrame(None)
    app.SetTopWindow(frame)
    frame.Show()

    app.MainLoop()

Original (fuzzy, top), Result (sharp, bottom):

fuzzy sharp

However, I don't know if there's a reason not to do this. I've also observed this with the ultimate list control, and I suspect it's an issue for any agw widget that uses the BufferedPaintDC.

Metallicow commented 4 years ago

I'm half blind trying to read pt8 text but using firefox and zooming in to 300% showed the problem better with your screenshots. I'm not sure if your proposal is the best solution... I'd guess Robin might have an answer for this, but it looks to me that maybe BufferedPaintDC might need some work at the wxWidgets level to deal with this properly...

Metallicow commented 4 years ago

@jbhopkins Also note that any additions or extra unnecessary checks you do in a paint handler is going to kill your performance... which is not desirable. for example aui is horrible on raspberry pi and agw.aui is even worse(even tho it is better as far as python fixes are concerned) FlatNotebook hasn't been updated in a loooooooong time. A rewrite specifically for phoenix would do it a lot of justice on many accounts.

jbhopkins commented 4 years ago

Yes, I wasn't really proposing my code as a permanent solution. It was meant to illustrate that using the regular PaintDC rather than BufferedPaintDC solved the problem. But I don't know enough about the internals of wx to know if there's a reason to use the Buffered vs. otherwise, and if changing it would cause other issues with the agw widgets.

Also, the difference in the screenshots is really hard to see unless you're on a high res monitor, then it jumps out pretty clearly (at least to me).

Metallicow commented 4 years ago

If you want to go and make a fork of agw stuff and fix stuff and then submit it back, that is fine. Andrea says he is basically retired but will watch from the sidelines. There is a lot of stuff in there that needs fixed and I'd say that what you pointed out is a good fix to start on, but it might need approached from a bit different angle. I'm not sure if this should be a wxWidgets ticket or not, but it seems like it to me. If you have the ability to test on all 3 major platforms, then submission of a pull req becomes easier. Basically, Win, Linux, and Mac should be tested with most stuff at entry level. This doesn't mean a fix won't be accepted, but it helps a lot of times if you can demonstrate the issue and how to fix it effectively without asking for Robins help(Ex: I don't own a mac, therefore cant test on it)

jbhopkins commented 4 years ago

I'm happy to put in a pull request changing the agw draws from buffered to normal. But I'm not familiar enough with the backend to know if that's the right solution. If it is let me know and I can easily put together and do some testing on that.

RobinD42 commented 4 years ago

There are a couple different issues in play here. 1st is the possible flicker if buffered drawing is not use. 2nd is the bitmap stretching on the HiDPI screens. The flicker is only an issue on Windows since OSX and GTK3 always use double-buffering internally. Using a BufferedDC in that case ends up triple buffering the drawing[1]. So one way to cover the bases here is to do something like this:

    if not self.IsDoubleBuffered() and self.GetContentScaleFactor() == 1.0:
        dc = wx.BufferedPaintDC(self)
    else:
        dc = wx.PaintDC(self)

[1] Although this is not a solid rule, there are still times when using a buffer bitmap on an already buffered display makes sense.

jbhopkins commented 4 years ago

Sorry for the delay in response, I've now got a bit of time to look at this again. So two questions.

First, @RobinD42 is the fact that BufferedDC is not using high resolution fonts (or possibly other bitmaps) an upstream wx issue? If so then it seems wxpython can safely ignore that for now.

Second, I'll defer to folks who know wxpython better than I do, but I'm happy to go through and make the change @RobinD42 suggested in choosing the appropriate dc for the agw widgets I've seen the issue with. Just tell me if that's the preferred solution. I'm a bit concerned because of the flickering issue, I don't know if it's preferable to have some flickering on Windows or to have low res fonts on windows. I personally would prefer high res fonts, but I also don't do a lot of work on Windows so I don't know how annoying the flickering might be.

tianzhuqiao commented 2 years ago

How about replacing wx.BufferedPaintDC with wx.AutoBufferedPaintDC? https://wxpython.org/Phoenix/docs/html/wx.AutoBufferedPaintDC.html

And looks like some other class has similar issue, e.g., agw.aui.auibook.AuiTabCtrl https://github.com/wxWidgets/Phoenix/blob/9a8a9b019ceba2a5140d98ae3ce2a53ec8c3d577/wx/lib/agw/aui/auibook.py#L1992

frozenpandaman commented 2 years ago

New to wxWidgets and I'm able to get my images to display at a higher resolution (via GetContentScaleFactor() as mentioned above) but not the text. Is there still really no support still for HiDPI/retina screens?

@jbhopkins How did you do the monkey patch? My project doesn't use PageContainers but a wx.ScrolledWindow, but I'm unsure how to have it use the code I want (that you posted) instead of the builtins.

jbhopkins commented 2 years ago

The core widgets, including ScrolledWindow, have pretty good support for high DPI these days. You typically have to do something system specific to enable it.

If you're on Windows this is what you have to do: https://stackoverflow.com/questions/50884283/how-to-fix-blurry-text-in-wxpython-controls-on-windows

On Mac it should work by default, but if you build an application you have to something in the info.plist. The wxpython docs provide some guidance: https://wxpython.org/Phoenix/docs/html/high_dpi_overview.html

As far as I know no one has updated the AGW widgets to be high DPI compatible by default.

janopae commented 2 years ago

I just fixed the same problem in the code of limburgher/trelby by not passing a buffer as a second argument to the BufferedPaintDC constructor:

- dc = wx.BufferedPaintDC(self, self.screenBuf)
+ dc = wx.BufferedPaintDC(self)

I can only verify this working with the Gtk backend (as I have no Windows machine here), but it works. That might explain why there is no issue upstream at wxWidgets/wxWidgets?

But still, thanks for the tips on avoiding tripple buffering. I'm actually going to use

dc = wx.AutoBufferedPaintDCFactory(self)

which also works (using the Gtk backend).