Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved DPI Scaling on Windows and Fixed related Bugs #2155

Open
wants to merge 125 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
184d5d4
Fixed bug in call of SetProcessDpiAwarenessContext
proneon267 Oct 15, 2023
986804f
Added a changelog.
proneon267 Oct 15, 2023
5967db9
Fixed winforms scaling bugs.
proneon267 Oct 16, 2023
a9f740b
Updated changelog.
proneon267 Oct 16, 2023
c1bdf9d
Added event handler to detect dpi change when live.
proneon267 Oct 18, 2023
196bf77
Merge branch 'patch-20' of https://github.com/proneon267/toga into pa…
proneon267 Oct 18, 2023
29a55d3
Merge branch 'main' into patch-20
proneon267 Oct 18, 2023
ff23b17
Empty commit for CI/CD
proneon267 Oct 18, 2023
afccfc6
Updated changelog
proneon267 Oct 18, 2023
94dce84
Added support for font scaling based on DPI change
proneon267 Oct 21, 2023
65c6bd7
Empty commit
proneon267 Oct 21, 2023
21d1f56
Miscellaneous fixes
proneon267 Oct 21, 2023
fcfe409
Merge branch 'beeware:main' into patch-20
proneon267 Oct 21, 2023
0129035
Merge branch 'beeware:main' into patch-20
proneon267 Oct 23, 2023
ebd1c29
Fixed Hwnd Related Bugs.
proneon267 Oct 23, 2023
4e4addc
Fixed menubar clipping bug.
proneon267 Oct 23, 2023
7aeeb7c
Miscellaneous fixes
proneon267 Oct 23, 2023
0ff2ec6
Empty commit for CI
proneon267 Oct 23, 2023
1012ac5
Merge branch 'beeware:main' into patch-20
proneon267 Oct 25, 2023
1393b7d
Empty commit for CI
proneon267 Oct 26, 2023
796db51
Added Support for Scaling Stack Trace Dialogs.
proneon267 Oct 27, 2023
ec7617d
Miscellaneous fixes
proneon267 Oct 27, 2023
a4ff196
Miscellaneous fixes
proneon267 Oct 27, 2023
9e3685a
Miscellaneous fixes
proneon267 Oct 27, 2023
20b6793
Merge branch 'main' into patch-20
proneon267 Nov 1, 2023
4619553
Merge branch 'main' into patch-20
proneon267 Nov 3, 2023
6f8019c
Empty commit for CI
proneon267 Nov 3, 2023
407156e
Added support for scaling window toolbar
proneon267 Nov 3, 2023
550839c
Added tests
proneon267 Nov 5, 2023
392487a
Miscellaneous fixes
proneon267 Nov 5, 2023
2d59078
Fixed tests
proneon267 Nov 6, 2023
d54bc4d
Merge branch 'beeware:main' into patch-20
proneon267 Nov 7, 2023
c45aa2e
Modified scaling code
proneon267 Nov 7, 2023
1714095
Merge branch 'patch-20' of https://github.com/proneon267/toga into pa…
proneon267 Nov 7, 2023
5c512cb
Fixed Stack Trace Dialog Scaling issues
proneon267 Nov 9, 2023
cbdc69a
Miscellaneous Fixes
proneon267 Nov 9, 2023
2ae714f
Merge branch 'main' into patch-20
proneon267 Nov 9, 2023
a285a53
Merge branch 'patch-20' of https://github.com/proneon267/toga into HEAD
proneon267 Nov 9, 2023
c5fb1b0
Miscellaneous Fixes
proneon267 Nov 9, 2023
469f287
Merge branch 'main' into patch-20
proneon267 Nov 9, 2023
9929c3a
Merge branch 'patch-20' of https://github.com/proneon267/toga into HEAD
proneon267 Nov 9, 2023
ab754c2
Merge branch 'main' into patch-20
proneon267 Nov 9, 2023
a670a2a
Miscellaneous Fixes
proneon267 Nov 9, 2023
eeeeaa3
Merge branch 'patch-20' of https://github.com/proneon267/toga into dp…
proneon267 Nov 9, 2023
d2e5948
Fixed tests
proneon267 Nov 9, 2023
6aa68d7
Empty commit for CI
proneon267 Nov 9, 2023
083d9da
Added scaling support for moving between screens.
proneon267 Nov 9, 2023
f1ddd6f
Empty commit for CI
proneon267 Nov 9, 2023
0325388
Empty commit for CI
proneon267 Nov 9, 2023
7527b39
Miscellaneous Fixes
proneon267 Nov 11, 2023
4b7791b
Miscellaneous Fixes
proneon267 Nov 11, 2023
995819b
Merge branch 'beeware:main' into patch-20
proneon267 Nov 11, 2023
8c422e8
Miscellaneous Fixes
proneon267 Nov 11, 2023
0eb366a
Merge branch 'main' into patch-20
proneon267 Nov 12, 2023
c7b2f19
Merge branch 'beeware:main' into patch-20
proneon267 Nov 14, 2023
30b088b
Merge branch 'beeware:main' into patch-20
proneon267 Nov 15, 2023
3e7bdea
Merge branch 'main' into patch-20
proneon267 Nov 25, 2023
ee76a14
Miscellaneous Fixes
proneon267 Nov 29, 2023
d92d929
Miscellaneous Fixes
proneon267 Nov 29, 2023
48cd2bf
Merge branch 'beeware:main' into patch-20
proneon267 Dec 2, 2023
0a9e732
Merge branch 'beeware:main' into patch-20
proneon267 Dec 10, 2023
de079fa
Merge branch 'main' into patch-20
proneon267 Dec 17, 2023
81a04ae
Miscellaneous Fixes
proneon267 Dec 17, 2023
279458f
Merge branch 'beeware:main' into patch-20
proneon267 Dec 18, 2023
2e16e16
Merge branch 'beeware:main' into patch-20
proneon267 Dec 22, 2023
acf13d3
Miscellaneous Fixes
proneon267 Dec 22, 2023
c97df03
Merge branch 'beeware:main' into patch-20
proneon267 Jan 13, 2024
b8db567
Merge branch 'beeware:main' into patch-20
proneon267 Jan 14, 2024
4086055
Merge branch 'beeware:main' into patch-20
proneon267 Jan 17, 2024
85bcb6b
Merge branch 'beeware:main' into patch-20
proneon267 Jan 17, 2024
4d77f3f
Merge branch 'beeware:main' into patch-20
proneon267 Jan 20, 2024
7842aff
Empty commit for CI
proneon267 Jan 21, 2024
4f4f988
Merge branch 'beeware:main' into patch-20
proneon267 Jan 25, 2024
c529e5f
Merge branch 'beeware:main' into patch-20
proneon267 Feb 3, 2024
46eb4a9
Misc Fixes
proneon267 Feb 3, 2024
3090935
Removed dialog scaling
proneon267 Feb 6, 2024
a99b83a
Misc Fixes
proneon267 Feb 6, 2024
71d06b5
Misc Fixes
proneon267 Feb 6, 2024
fd718a2
Merge branch 'beeware:main' into patch-20
proneon267 Feb 9, 2024
d0b7f0e
Corrected windows implementation
proneon267 Feb 10, 2024
6ceb39d
Updated to latest main branch
proneon267 Feb 11, 2024
03062b9
Corrected winforms screens dpi scaling
proneon267 Feb 11, 2024
69f83cc
Corrected winforms tests_backend to detect dpi scale
proneon267 Feb 11, 2024
65e6144
Corrected winforms dpi scaling
proneon267 Feb 11, 2024
b6451f5
Corrected winforms tests_backend dpi scaling
proneon267 Feb 11, 2024
8cf5eef
Corrected winforms tests_backend dpi scaling
proneon267 Feb 11, 2024
6a0b6d7
Empty commit for CI
proneon267 Feb 11, 2024
18bc25d
Empty commit for CI
proneon267 Feb 11, 2024
87c88af
Merge branch 'main' into patch-20
proneon267 Mar 21, 2024
6713490
updated to latest main branch
proneon267 Mar 21, 2024
c873366
updated to latest main branch
proneon267 Mar 21, 2024
fac093d
Merge branch 'beeware:main' into patch-20
proneon267 Apr 2, 2024
3084c42
Merge remote-tracking branch 'remotes/origin/main' into patch-20
mhsmith Apr 3, 2024
31e54c9
Fix StackTraceDialog scaling
mhsmith Apr 3, 2024
c793d64
Fix various scaling bugs
mhsmith Apr 4, 2024
c44fef0
Merge branch 'beeware:main' into patch-20
proneon267 Apr 14, 2024
64221ab
Fixed reported dpi scaling bugs
proneon267 Apr 14, 2024
12a70d1
Fixed tests
proneon267 Apr 15, 2024
2cd6213
Empty commit for CI
proneon267 Apr 15, 2024
7c8a9b8
Empty commit for CI
proneon267 Apr 15, 2024
69054c1
Fixed remaining errors
proneon267 Apr 26, 2024
5e58f35
Fixed test
proneon267 Apr 28, 2024
ae20798
Merge branch 'main' into patch-20
proneon267 May 7, 2024
ff6223c
Modified DPI change test
proneon267 May 15, 2024
1313d7e
Fixed DPI change test
proneon267 May 15, 2024
a53a820
Merge branch 'beeware:main' into patch-20
proneon267 May 15, 2024
f3db6ff
Merge branch 'beeware:main' into patch-20
proneon267 Jun 1, 2024
afdbb30
Merge branch 'main' into patch-20
proneon267 Jun 12, 2024
37e1add
Merge branch 'main' into patch-20
proneon267 Jun 17, 2024
9d199f5
Merge branch 'main' into patch-20
proneon267 Jul 10, 2024
41574d4
Updated to latest main branch
proneon267 Jul 10, 2024
9455196
Updated to latest main branch
proneon267 Jul 10, 2024
767e35e
Fixed winforms
proneon267 Jul 10, 2024
a30a8e3
Update 2155.bugfix.rst
proneon267 Jul 11, 2024
a4eb081
Merge branch 'beeware:main' into patch-20
proneon267 Jul 19, 2024
d228109
Merge branch 'main' into patch-20
proneon267 Oct 4, 2024
c4d5c5e
Parameterized test
proneon267 Oct 13, 2024
b7b1061
Merge branch 'beeware:main' into patch-20
proneon267 Oct 13, 2024
83159fe
Parameterized test
proneon267 Oct 13, 2024
0800f3f
Revert unnecessary change to iOS
mhsmith Oct 30, 2024
ddd61db
Merge remote-tracking branch 'remotes/origin/main' into patch-20
mhsmith Nov 3, 2024
617c4fe
Various cleanups and fixes
mhsmith Nov 3, 2024
44f9124
Rewrite DPI tests
mhsmith Nov 6, 2024
ca540a4
Add test of DisplaySettingsChanged event
mhsmith Nov 6, 2024
1188ff9
Correct font scaling
mhsmith Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/2155.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DPI scaling on Windows is now improved and related bugs are fixed.
14 changes: 10 additions & 4 deletions core/src/toga/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __str__(self) -> str:


class Position(NamedTuple):
"""A 2D window position."""
"""A 2D position."""

#: X coordinate, in CSS pixels.
x: int
Expand All @@ -46,15 +46,21 @@ def __add__(self, other):
def __sub__(self, other):
return Position(self.x - other.x, self.y - other.y)

def __mul__(self, other):
return Position(self.x * other, self.y * other)


class Size(NamedTuple):
"""A 2D window size."""
"""A 2D size."""

#: Width
#: Width, in CSS pixels.
width: int

#: Height
#: Height, in CSS pixels.
height: int

def __str__(self) -> str:
return f"({self.width} x {self.height})"

def __mul__(self, other):
return Size(self.width * other, self.height * other)
23 changes: 22 additions & 1 deletion core/tests/test_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ def test_position_properties():
assert p.x == 1
assert p.y == 2
assert str(p) == "(1, 2)"
p == (1, 2) # Tuple equivalence for backwards-compatibility

assert p == Position(1, 2)
assert p != Position(1, 3)

assert p == (1, 2) # Tuple equivalence for backwards-compatibility
assert p != (1, 3)


def test_add_positions():
Expand All @@ -20,10 +25,26 @@ def test_sub_positions():
assert Position(1, 2) - Position(3, 4) == Position(-2, -2)


def test_mul_position():
"""Multiplying a Position multiplies its X and Y values"""
assert Position(1, 2) * 2 == Position(2, 4)
assert Position(1, 2) * 0.5 == Position(0.5, 1)
assert Position(1, 2) * 0 == Position(0, 0)
assert Position(1, 2) * -1 == Position(-1, -2)


def test_size_properties():
"""A Size NamedTuple has a width and height."""
s = Size(1, 2)
assert s.width == 1
assert s.height == 2
assert str(s) == "(1 x 2)"
s == (1, 2) # Tuple equivalence for backwards-compatibility


def test_mul_size():
"""Multiplying a Size multiplies its width and height values"""
assert Size(1, 2) * 2 == Size(2, 4)
assert Size(1, 2) * 0.5 == Size(0.5, 1)
assert Size(1, 2) * 0 == Size(0, 0)
assert Size(1, 2) * -1 == Size(-1, -2)
10 changes: 8 additions & 2 deletions examples/window/window/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ def do_right(self, widget, **kwargs):
self.main_window.position = (2000, 500)

def do_left_current_screen(self, widget, **kwargs):
self.main_window.screen_position = (0, 100)
self.main_window.screen_position = (
self.main_window.screen.origin.x,
self.main_window.screen_position.y,
)

def do_right_current_screen(self, widget, **kwargs):
self.main_window.screen_position = (1080, 100)
self.main_window.screen_position = (
self.main_window.screen.size.width - self.main_window.size.width,
self.main_window.screen_position.y,
)

def do_small(self, widget, **kwargs):
self.main_window.size = (400, 300)
Expand Down
167 changes: 167 additions & 0 deletions testbed/tests/app/test_desktop.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from functools import partial
from unittest.mock import Mock

import pytest

import toga
from toga import Position, Size
from toga.colors import CORNFLOWERBLUE, FIREBRICK, REBECCAPURPLE
from toga.style.pack import Pack

Expand Down Expand Up @@ -391,6 +393,171 @@ async def test_current_window(app, app_probe, main_window):
assert app.current_window == window3


@pytest.mark.parametrize(
"event_path",
[
"SystemEvents.DisplaySettingsChanged",
"Form.LocationChanged",
"Form.Resize",
],
)
@pytest.mark.parametrize("mock_scale", [1.0, 1.25, 1.5, 1.75, 2.0])
async def test_system_dpi_change(
main_window, main_window_probe, event_path, mock_scale
):
if toga.platform.current_platform != "windows":
pytest.xfail("This test is winforms backend specific")

from toga_winforms.libs import shcore

real_scale = main_window_probe.scale_factor
if real_scale == mock_scale:
pytest.skip("mock scale and real scale are the same")
scale_change = mock_scale / real_scale
content_size = main_window_probe.content_size

original_content = main_window.content
GetScaleFactorForMonitor_original = shcore.GetScaleFactorForMonitor
dpi_change_event = find_event(event_path, main_window_probe)

try:
# Include widgets which are sized in different ways, with padding and fixed
# sizes in both dimensions.
main_window.content = toga.Box(
style=Pack(direction="row"),
children=[
toga.Label(
"fixed",
id="fixed",
style=Pack(background_color="yellow", padding_left=20, width=100),
),
toga.Label(
"minimal", # Shrink to fit content
id="minimal",
style=Pack(background_color="cyan", font_size=16),
),
toga.Label(
"flex",
id="flex",
style=Pack(
background_color="pink", flex=1, padding_top=15, height=50
),
),
],
)
await main_window_probe.redraw("main_window is ready for testing")

ids = ["fixed", "minimal", "flex"]
probes = {id: get_probe(main_window.widgets[id]) for id in ids}

def get_metrics():
return (
{id: Position(probes[id].x, probes[id].y) for id in ids},
{id: Size(probes[id].width, probes[id].height) for id in ids},
{id: probes[id].font_size for id in ids},
)

positions, sizes, font_sizes = get_metrics()

# Because of hinting, font size changes can have non-linear effects on pixel
# sizes.
approx_fixed = partial(pytest.approx, abs=1)
approx_font = partial(pytest.approx, rel=0.25)

assert font_sizes["fixed"] == 9 # Default font size on Windows
assert positions["fixed"] == approx_fixed((20, 0))
assert sizes["fixed"].width == approx_fixed(100)

assert font_sizes["minimal"] == 16
assert positions["minimal"] == approx_fixed((120, 0))
assert sizes["minimal"].height == approx_font(sizes["fixed"].height * 16 / 9)

assert font_sizes["flex"] == 9
assert positions["flex"] == approx_fixed((120 + sizes["minimal"].width, 15))
assert sizes["flex"] == approx_fixed(
(content_size.width - positions["flex"].x, 50)
)

# Mock the function Toga uses to get the scale factor.
def GetScaleFactorForMonitor_mock(hMonitor, pScale):
pScale.value = int(mock_scale * 100)

# Set and Trigger dpi change event with the specified dpi scale
shcore.GetScaleFactorForMonitor = GetScaleFactorForMonitor_mock
dpi_change_event(None)
await main_window_probe.redraw(
f"Triggered dpi change event with {mock_scale} dpi scale"
)

# Check Widget size DPI scaling
positions_scaled, sizes_scaled, font_sizes_scaled = get_metrics()
for id in ids:
assert font_sizes_scaled[id] == approx_fixed(font_sizes[id] * scale_change)

assert positions_scaled["fixed"] == approx_fixed(Position(20, 0) * scale_change)
assert sizes_scaled["fixed"] == (
approx_fixed(100 * scale_change),
approx_font(sizes["fixed"].height * scale_change),
)

assert positions_scaled["minimal"] == approx_fixed(
Position(120, 0) * scale_change
)
assert sizes_scaled["minimal"] == approx_font(sizes["minimal"] * scale_change)

assert positions_scaled["flex"] == approx_fixed(
(
positions_scaled["minimal"].x + sizes_scaled["minimal"].width,
15 * scale_change,
)
)
assert sizes_scaled["flex"] == approx_fixed(
(
content_size.width - positions_scaled["flex"].x,
50 * scale_change,
)
)

finally:
# Restore original state
shcore.GetScaleFactorForMonitor = GetScaleFactorForMonitor_original
dpi_change_event(None)
await main_window_probe.redraw("Restored original state of main_window")
assert get_metrics() == (positions, sizes, font_sizes)
main_window.content = original_content


def find_event(event_path, main_window_probe):
from Microsoft.Win32 import SystemEvents
from System import Array, Object
from System.Reflection import BindingFlags

event_class, event_name = event_path.split(".")
if event_class == "Form":
return getattr(main_window_probe.native, f"On{event_name}")

elif event_class == "SystemEvents":
# There are no "On" methods in this class, so we need to use reflection.
SystemEvents_type = SystemEvents().GetType()
binding_flags = BindingFlags.Static | BindingFlags.NonPublic
RaiseEvent = [
method
for method in SystemEvents_type.GetMethods(binding_flags)
if method.Name == "RaiseEvent" and len(method.GetParameters()) == 2
][0]

event_key = SystemEvents_type.GetField(
f"On{event_name}Event", binding_flags
).GetValue(None)

return lambda event_args: RaiseEvent.Invoke(
None, [event_key, Array[Object]([None, event_args])]
)

else:
raise AssertionError(f"unknown event class {event_class}")


async def test_session_based_app(
monkeypatch,
app,
Expand Down
19 changes: 19 additions & 0 deletions winforms/src/toga_winforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

import toga

from .libs.user32 import (
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
SetProcessDpiAwarenessContext,
)

# Add a reference to the Winforms assembly
clr.AddReference("System.Windows.Forms")

Expand All @@ -16,4 +21,18 @@
"WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
)


# Enable DPI awareness. This must be done before calling any other UI-related code
# (https://learn.microsoft.com/en-us/dotnet/desktop/winforms/high-dpi-support-in-windows-forms).
import System.Windows.Forms as WinForms # noqa: E402

WinForms.Application.EnableVisualStyles()
WinForms.Application.SetCompatibleTextRenderingDefault(False)

if SetProcessDpiAwarenessContext is not None:
if not SetProcessDpiAwarenessContext(
DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
): # pragma: no cover
print("WARNING: Failed to set the DPI Awareness mode for the app.")

__version__ = toga._package_version(__file__, __name__)
54 changes: 26 additions & 28 deletions winforms/src/toga_winforms/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import re
import sys
import threading
from ctypes import windll

import System.Windows.Forms as WinForms
from System import Environment, Threading
from Microsoft.Win32 import SystemEvents
from System import Threading
from System.Media import SystemSounds
from System.Net import SecurityProtocolType, ServicePointManager
from System.Windows.Threading import Dispatcher
Expand Down Expand Up @@ -76,32 +76,17 @@ def create(self):
self.app_context = WinForms.ApplicationContext()
self.app_dispatcher = Dispatcher.CurrentDispatcher

# Check the version of windows and make sure we are setting the DPI mode
# with the most up to date API
# Windows Versioning Check Sources : https://www.lifewire.com/windows-version-numbers-2625171
# and https://docs.microsoft.com/en-us/windows/release-information/
win_version = Environment.OSVersion.Version
if win_version.Major >= 6: # Checks for Windows Vista or later
# Represents Windows 8.1 up to Windows 10 before Build 1703 which should use
# SetProcessDpiAwareness(True)
if (win_version.Major == 6 and win_version.Minor == 3) or (
win_version.Major == 10 and win_version.Build < 15063
): # pragma: no cover
windll.shcore.SetProcessDpiAwareness(True)
print(
"WARNING: Your Windows version doesn't support DPI-independent rendering. "
"We recommend you upgrade to at least Windows 10 Build 1703."
)
# Represents Windows 10 Build 1703 and beyond which should use
# SetProcessDpiAwarenessContext(-2)
elif win_version.Major == 10 and win_version.Build >= 15063:
windll.user32.SetProcessDpiAwarenessContext(-2)
# Any other version of windows should use SetProcessDPIAware()
else: # pragma: no cover
windll.user32.SetProcessDPIAware()

self.native.EnableVisualStyles()
self.native.SetCompatibleTextRenderingDefault(False)
# We would prefer to detect DPI changes directly, using the DpiChanged,
# DpiChangedBeforeParent or DpiChangedAfterParent events on the window. But none
# of these events ever fire, possibly because we're missing some app metadata
# (https://github.com/beeware/toga/pull/2155#issuecomment-2460374101). So
# instead we need to listen to all events which could cause a DPI change:
# * DisplaySettingsChanged
# * Form.LocationChanged and Form.Resize, since a window's DPI is determined
# by which screen most of its area is on.
SystemEvents.DisplaySettingsChanged += WeakrefCallable(
self.winforms_DisplaySettingsChanged
)

# Ensure that TLS1.2 and TLS1.3 are enabled for HTTPS connections.
# For some reason, some Windows installs have these protocols
Expand All @@ -126,6 +111,19 @@ def create(self):
# Populate the main window as soon as the event loop is running.
self.loop.call_soon_threadsafe(self.interface._startup)

######################################################################
# Native event handlers
######################################################################

def winforms_DisplaySettingsChanged(self, sender, event):
# This event is NOT called on the UI thread, so it's not safe for it to access
# the UI directly.
self.interface.loop.call_soon_threadsafe(self.update_dpi)

def update_dpi(self):
for window in self.interface.windows:
window._impl.update_dpi()

######################################################################
# Commands and menus
######################################################################
Expand Down
Loading