2022-04-19 01:01:33 +03:00
|
|
|
import tkinter
|
|
|
|
import sys
|
|
|
|
|
2022-05-05 21:25:37 +03:00
|
|
|
from .ctk_settings import CTkSettings
|
|
|
|
|
2022-04-19 01:01:33 +03:00
|
|
|
|
|
|
|
class ScalingTracker:
|
|
|
|
|
|
|
|
window_widgets_dict = {} # contains window objects as keys with list of widget callbacks as elements
|
|
|
|
|
2022-05-02 00:29:14 +03:00
|
|
|
window_dpi_scaling_dict = {} # contains window objects as keys and corresponding scaling factors
|
|
|
|
|
|
|
|
widget_scaling = 1 # user values which multiply to detected window scaling factor
|
|
|
|
window_scaling = 1
|
|
|
|
spacing_scaling = 1
|
2022-04-19 01:01:33 +03:00
|
|
|
|
|
|
|
update_loop_running = False
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_widget_scaling(cls, widget):
|
|
|
|
window_root = cls.get_window_root_of_widget(widget)
|
2022-05-02 00:29:14 +03:00
|
|
|
return cls.window_dpi_scaling_dict[window_root] * cls.widget_scaling
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_spacing_scaling(cls, widget):
|
|
|
|
window_root = cls.get_window_root_of_widget(widget)
|
|
|
|
return cls.window_dpi_scaling_dict[window_root] * cls.spacing_scaling
|
2022-04-19 01:01:33 +03:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_window_scaling(cls, window):
|
2022-05-02 00:29:14 +03:00
|
|
|
window_root = cls.get_window_root_of_widget(window)
|
|
|
|
return cls.window_dpi_scaling_dict[window_root] * cls.window_scaling
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def set_widget_scaling(cls, widget_scaling_factor):
|
2022-05-06 15:33:38 +03:00
|
|
|
cls.widget_scaling = max(widget_scaling_factor, 0.4)
|
2022-05-02 00:29:14 +03:00
|
|
|
cls.update_scaling_callbacks()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def set_spacing_scaling(cls, spacing_scaling_factor):
|
2022-05-06 15:33:38 +03:00
|
|
|
cls.spacing_scaling = max(spacing_scaling_factor, 0.4)
|
2022-05-02 00:29:14 +03:00
|
|
|
cls.update_scaling_callbacks()
|
2022-04-19 01:01:33 +03:00
|
|
|
|
|
|
|
@classmethod
|
2022-05-02 00:29:14 +03:00
|
|
|
def set_window_scaling(cls, window_scaling_factor):
|
2022-05-06 15:33:38 +03:00
|
|
|
cls.window_scaling = max(window_scaling_factor, 0.4)
|
2022-04-19 01:01:33 +03:00
|
|
|
cls.update_scaling_callbacks()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_window_root_of_widget(cls, widget):
|
|
|
|
current_widget = widget
|
|
|
|
|
|
|
|
while isinstance(current_widget, tkinter.Tk) is False and\
|
|
|
|
isinstance(current_widget, tkinter.Toplevel) is False:
|
|
|
|
current_widget = current_widget.master
|
|
|
|
|
|
|
|
return current_widget
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def update_scaling_callbacks(cls):
|
|
|
|
for window, callback_list in cls.window_widgets_dict.items():
|
|
|
|
for callback in callback_list:
|
2022-05-05 21:25:37 +03:00
|
|
|
if not CTkSettings.deactivate_automatic_dpi_awareness:
|
|
|
|
callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling,
|
|
|
|
cls.window_dpi_scaling_dict[window] * cls.spacing_scaling,
|
|
|
|
cls.window_dpi_scaling_dict[window] * cls.window_scaling)
|
|
|
|
else:
|
|
|
|
callback(cls.widget_scaling,
|
|
|
|
cls.spacing_scaling,
|
|
|
|
cls.window_scaling)
|
2022-04-19 01:01:33 +03:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def add_widget(cls, widget_callback, widget):
|
|
|
|
window_root = cls.get_window_root_of_widget(widget)
|
|
|
|
|
|
|
|
if window_root not in cls.window_widgets_dict:
|
|
|
|
cls.window_widgets_dict[window_root] = [widget_callback]
|
|
|
|
else:
|
|
|
|
cls.window_widgets_dict[window_root].append(widget_callback)
|
|
|
|
|
|
|
|
if window_root not in cls.window_dpi_scaling_dict:
|
2022-05-02 00:29:14 +03:00
|
|
|
cls.window_dpi_scaling_dict[window_root] = cls.get_window_dpi_scaling(window_root)
|
2022-04-19 01:01:33 +03:00
|
|
|
|
|
|
|
if not cls.update_loop_running:
|
|
|
|
window_root.after(100, cls.check_dpi_scaling)
|
|
|
|
cls.update_loop_running = True
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def add_window(cls, window_callback, window):
|
|
|
|
if window not in cls.window_widgets_dict:
|
|
|
|
cls.window_widgets_dict[window] = [window_callback]
|
|
|
|
else:
|
|
|
|
cls.window_widgets_dict[window].append(window_callback)
|
|
|
|
|
|
|
|
if window not in cls.window_dpi_scaling_dict:
|
2022-05-02 00:29:14 +03:00
|
|
|
cls.window_dpi_scaling_dict[window] = cls.get_window_dpi_scaling(window)
|
2022-04-19 01:01:33 +03:00
|
|
|
|
2022-05-05 21:25:37 +03:00
|
|
|
@classmethod
|
|
|
|
def activate_high_dpi_awareness(cls):
|
|
|
|
""" make process DPI aware, customtkinter elemets will get scaled automatically,
|
|
|
|
only gets activated when CTk object is created """
|
|
|
|
|
|
|
|
if not CTkSettings.deactivate_automatic_dpi_awareness:
|
|
|
|
if sys.platform == "darwin":
|
|
|
|
pass # high DPI scaling works automatically on macOS
|
|
|
|
|
|
|
|
elif sys.platform.startswith("win"):
|
|
|
|
from ctypes import windll
|
|
|
|
windll.shcore.SetProcessDpiAwareness(2)
|
|
|
|
# Microsoft Docs: https://docs.microsoft.com/en-us/windows/win32/api/shellscalingapi/ne-shellscalingapi-process_dpi_awareness
|
|
|
|
|
|
|
|
else:
|
|
|
|
pass # DPI awareness on Linux not implemented
|
|
|
|
|
2022-04-19 01:01:33 +03:00
|
|
|
@classmethod
|
2022-05-02 00:29:14 +03:00
|
|
|
def get_window_dpi_scaling(cls, window):
|
2022-04-19 01:01:33 +03:00
|
|
|
if sys.platform == "darwin":
|
|
|
|
return 1 # scaling works automatically on macOS
|
|
|
|
|
|
|
|
elif sys.platform.startswith("win"):
|
|
|
|
from ctypes import windll, pointer, wintypes
|
|
|
|
|
|
|
|
DPI100pc = 96 # DPI 96 is 100% scaling
|
|
|
|
DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2
|
2022-04-21 19:45:51 +03:00
|
|
|
window_hwnd = wintypes.HWND(window.winfo_id())
|
2022-04-19 01:01:33 +03:00
|
|
|
monitor_handle = windll.user32.MonitorFromWindow(window_hwnd, wintypes.DWORD(2)) # MONITOR_DEFAULTTONEAREST = 2
|
|
|
|
x_dpi, y_dpi = wintypes.UINT(), wintypes.UINT()
|
|
|
|
windll.shcore.GetDpiForMonitor(monitor_handle, DPI_type, pointer(x_dpi), pointer(y_dpi))
|
|
|
|
return (x_dpi.value + y_dpi.value) / (2 * DPI100pc)
|
|
|
|
|
|
|
|
else:
|
2022-05-05 21:25:37 +03:00
|
|
|
return 1 # DPI awareness on Linux not implemented
|
2022-04-19 01:01:33 +03:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def check_dpi_scaling(cls):
|
|
|
|
# find an existing tkinter object for the next call of .after()
|
|
|
|
for root_tk in cls.window_widgets_dict.keys():
|
|
|
|
try:
|
|
|
|
root_tk.after(500, cls.check_dpi_scaling)
|
|
|
|
return
|
|
|
|
except Exception:
|
|
|
|
continue
|
|
|
|
|
|
|
|
cls.update_loop_running = False
|