Repository: vispy/vispy

Issue:
Using `QT_SCALE_FACTOR` causes a cropped canvas (Qt backend)
Hi, seems like setting the Qt environment variable `QT_SCALE_FACTOR` to a value like `1.8` causes the canvas to appear cropped/without using the space properly. A preview of the behavior:

* Running the [`sphere.py` example](https://github.com/vispy/vispy/blob/main/examples/scene/sphere.py) without setting `QT_SCALE_FACTOR`:

![sphere1](https://github.com/vispy/vispy/assets/16781833/da642553-2c00-4b4d-82e6-a391ce33bdd6)

* Running the [`sphere.py` example](https://github.com/vispy/vispy/blob/main/examples/scene/sphere.py) setting `QT_SCALE_FACTOR` to `1.8` (`set QT_SCALE_FACTOR=1.8`):

![sphere2](https://github.com/vispy/vispy/assets/16781833/122629e2-3697-49f5-8c38-aee390f76426)

Tested the above running (relative to the cloned vispy repo directory) from a Python environment on Windows with vispy 0.14.1 the following:

```cmd
set QT_SCALE_FACTOR=
```
 or

```cmd
set QT_SCALE_FACTOR=1.8
```
and
```cmd
python examples/scene/sphere.py
```

Also, for tracebility, I started investigating this due to https://github.com/napari/napari/issues/6197. A possible fix is explained at https://github.com/napari/napari/issues/6197#issuecomment-1769207938. Let me know if opening a PR with the changes described there makes sense or if more info is needed!


Discussion:
I had never heard of `QT_SCALE_FACTOR` before and I guess that fix in the comment seems reasonable (even though a bug should maybe be filed with Qt) so pull requests are welcome.
If Qt4 compatibility has to be removed to have the code exactly like that then lets make sure we officially remove it from all interfaces and documentation.
> I had never heard of QT_SCALE_FACTOR before and I guess that fix in the comment seems reasonable (even though a bug should maybe be filed with Qt) so pull requests are welcome.

Awesome! Also, checking a little bit more the Qt5 docs I think the issue comes due to the difference between [`devicePixelRatio` ](https://doc.qt.io/qt-5/qpaintdevice.html#devicePixelRatio) and [`devicePixelRatioF`](https://doc.qt.io/qt-5/qpaintdevice.html#devicePixelRatioF) 🤔. So, to support a floating point value, `devicePixelRatioF` is neededand then a possible solution preserving the current logic could look something like:

```python
        # must set physical size before setting visible or fullscreen
        # operations may make the size invalid
        if hasattr(self, 'devicePixelRatioF'):
            # handle high DPI displays in PyQt5+
            ratio = self.devicePixelRatioF()
        else:
            ratio = 1
        self._physical_size = (p.size[0] * ratio, p.size[1] * ratio)
```

and

```python
        if hasattr(self, 'devicePixelRatioF'):
            # We take into account devicePixelRatio, which is non-unity on
            # e.g HiDPI displays.
            # self.devicePixelRatioF() is a float and should have been in Qt5+ according to the documentation
            ratio = self.devicePixelRatioF()
            w = int(w * ratio)
            h = int(h * ratio)
```

However, regarding the Qt4 support comment:

>If Qt4 compatibility has to be removed to have the code exactly like that then lets make sure we officially remove it from all interfaces and documentation.

Indeed, I think Qt4 has no `devicePixelRatio` or `devicePixelRatioF` definitions (probably that's the reason why the current code has the `hasattr` validations). Happy to help with that too but seems like that would be a more involved work (I think the issue tracking that is https://github.com/vispy/vispy/issues/1788) 🤔

Taking that into account, could make sense to do a fix like the one above (just changing calls for `devicePixelRatio` to `devicePixelRatioF`) and in a different PR tackle the Qt4 support removal issue? Let me know what do you think!

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -23,6 +23,7 @@
 """
 
 import os
+import sys
 from os import path as op
 from setuptools import setup, find_packages
 
@@ -59,6 +60,10 @@ def set_builtin(name, value):
                         ),
               ]
 
+install_requires = ['numpy', 'freetype-py', 'hsluv', 'kiwisolver', 'packaging']
+if sys.version_info < (3, 9):
+    install_requires.append("importlib-resources")
+
 readme = open('README.rst', 'r').read()
 setup(
     name=name,
@@ -92,7 +97,7 @@ def set_builtin(name, value):
     platforms='any',
     provides=['vispy'],
     python_requires='>=3.8',
-    install_requires=['numpy', 'freetype-py', 'hsluv', 'kiwisolver', 'packaging'],
+    install_requires=install_requires,
     extras_require={
         'ipython-static': ['ipython'],
         'pyglet': ['pyglet>=1.2'],
diff --git a/vispy/app/backends/_qt.py b/vispy/app/backends/_qt.py
--- a/vispy/app/backends/_qt.py
+++ b/vispy/app/backends/_qt.py
@@ -386,9 +386,9 @@ def __init__(self, vispy_canvas, **kwargs):
 
         # must set physical size before setting visible or fullscreen
         # operations may make the size invalid
-        if hasattr(self, 'devicePixelRatio'):
+        if hasattr(self, 'devicePixelRatioF'):
             # handle high DPI displays in PyQt5
-            ratio = self.devicePixelRatio()
+            ratio = self.devicePixelRatioF()
         else:
             ratio = 1
         self._physical_size = (p.size[0] * ratio, p.size[1] * ratio)
@@ -421,7 +421,7 @@ def screen_changed(self, new_screen):
 
         If display resolutions are the same this is essentially a no-op except for the redraw.
         If the display resolutions differ (HiDPI versus regular displays) the canvas needs to
-        be redrawn to reset the physical size based on the current `devicePixelRatio()` and
+        be redrawn to reset the physical size based on the current `devicePixelRatioF()` and
         redrawn with that new size.
 
         """
@@ -909,11 +909,11 @@ def initializeGL(self):
     def resizeGL(self, w, h):
         if self._vispy_canvas is None:
             return
-        if hasattr(self, 'devicePixelRatio'):
-            # We take into account devicePixelRatio, which is non-unity on
+        if hasattr(self, 'devicePixelRatioF'):
+            # We take into account devicePixelRatioF, which is non-unity on
             # e.g HiDPI displays.
-            # self.devicePixelRatio() is a float and should have been in Qt5 according to the documentation
-            ratio = self.devicePixelRatio()
+            # self.devicePixelRatioF() is a float and should have been in Qt5 according to the documentation
+            ratio = self.devicePixelRatioF()
             w = int(w * ratio)
             h = int(h * ratio)
         self._vispy_set_physical_size(w, h)


Test patch: