mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
changeed driectory structure, moved scaling and appearance mode functionality to super classes
This commit is contained in:
parent
e5484cb6cd
commit
c2a5b4881e
@ -10,6 +10,8 @@ ToDo:
|
||||
- image tuple for light/dark mode
|
||||
- change font attribute in wiki
|
||||
- add new button attributes to wiki
|
||||
- cursor configuring
|
||||
- overwrite winfo methods
|
||||
|
||||
## Unreleased - 2022-10-2
|
||||
### Added
|
||||
|
@ -4,11 +4,11 @@ import os
|
||||
import sys
|
||||
|
||||
# import manager classes
|
||||
from .appearance_mode_tracker import AppearanceModeTracker
|
||||
from .widgets.font.font_manager import FontManager
|
||||
from .scaling_tracker import ScalingTracker
|
||||
from .theme_manager import ThemeManager
|
||||
from .widgets.core_rendering.draw_engine import DrawEngine
|
||||
from .windows.widgets.appearance_mode.appearance_mode_tracker import AppearanceModeTracker
|
||||
from .windows.widgets.font.font_manager import FontManager
|
||||
from .windows.widgets.scaling.scaling_tracker import ScalingTracker
|
||||
from .windows.widgets.theme.theme_manager import ThemeManager
|
||||
from .windows.widgets.core_rendering.draw_engine import DrawEngine
|
||||
|
||||
AppearanceModeTracker.init_appearance_mode()
|
||||
|
||||
@ -46,23 +46,21 @@ if FontManager.load_font(os.path.join(script_directory, "assets", "fonts", "Cust
|
||||
DrawEngine.preferred_drawing_method = "circle_shapes"
|
||||
|
||||
# import widgets
|
||||
from customtkinter.widgets.core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .widgets.ctk_button import CTkButton
|
||||
from .widgets.ctk_checkbox import CTkCheckBox
|
||||
from .widgets.ctk_entry import CTkEntry
|
||||
from .widgets.ctk_slider import CTkSlider
|
||||
from .widgets.ctk_frame import CTkFrame
|
||||
from .widgets.ctk_progressbar import CTkProgressBar
|
||||
from .widgets.ctk_label import CTkLabel
|
||||
from .widgets.ctk_radiobutton import CTkRadioButton
|
||||
from .widgets.core_rendering.ctk_canvas import CTkCanvas
|
||||
from .widgets.ctk_switch import CTkSwitch
|
||||
from .widgets.ctk_optionmenu import CTkOptionMenu
|
||||
from .widgets.ctk_combobox import CTkComboBox
|
||||
from .widgets.ctk_scrollbar import CTkScrollbar
|
||||
from .widgets.ctk_textbox import CTkTextbox
|
||||
from .widgets.ctk_tabview import CTkTabview
|
||||
from .widgets.ctk_segmented_button import CTkSegmentedButton
|
||||
from .windows.widgets.ctk_button import CTkButton
|
||||
from .windows.widgets.ctk_checkbox import CTkCheckBox
|
||||
from .windows.widgets.ctk_combobox import CTkComboBox
|
||||
from .windows.widgets.ctk_entry import CTkEntry
|
||||
from .windows.widgets.ctk_frame import CTkFrame
|
||||
from .windows.widgets.ctk_label import CTkLabel
|
||||
from .windows.widgets.ctk_optionmenu import CTkOptionMenu
|
||||
from .windows.widgets.ctk_progressbar import CTkProgressBar
|
||||
from .windows.widgets.ctk_radiobutton import CTkRadioButton
|
||||
from .windows.widgets.ctk_scrollbar import CTkScrollbar
|
||||
from .windows.widgets.ctk_segmented_button import CTkSegmentedButton
|
||||
from .windows.widgets.ctk_slider import CTkSlider
|
||||
from .windows.widgets.ctk_switch import CTkSwitch
|
||||
from .windows.widgets.ctk_tabview import CTkTabview
|
||||
from .windows.widgets.ctk_textbox import CTkTextbox
|
||||
|
||||
# import windows
|
||||
from .windows.ctk_tk import CTk
|
||||
@ -70,7 +68,7 @@ from .windows.ctk_toplevel import CTkToplevel
|
||||
from .windows.ctk_input_dialog import CTkInputDialog
|
||||
|
||||
# font classes
|
||||
from .widgets.font.ctk_font import CTkFont
|
||||
from .windows.widgets.font.ctk_font import CTkFont
|
||||
|
||||
|
||||
def set_appearance_mode(mode_string: str):
|
||||
|
@ -1,3 +0,0 @@
|
||||
from customtkinter.widgets.core_rendering.ctk_canvas import CTkCanvas
|
||||
|
||||
CTkCanvas.init_font_character_mapping()
|
@ -1,11 +1,11 @@
|
||||
from typing import Union, Tuple
|
||||
|
||||
from ..widgets.ctk_label import CTkLabel
|
||||
from ..widgets.ctk_entry import CTkEntry
|
||||
from ..windows.ctk_toplevel import CTkToplevel
|
||||
from ..widgets.ctk_button import CTkButton
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..theme_manager import ThemeManager
|
||||
from .widgets.ctk_label import CTkLabel
|
||||
from .widgets.ctk_entry import CTkEntry
|
||||
from .ctk_toplevel import CTkToplevel
|
||||
from .widgets.ctk_button import CTkButton
|
||||
from .widgets.appearance_mode.appearance_mode_tracker import AppearanceModeTracker
|
||||
from .widgets.theme.theme_manager import ThemeManager
|
||||
|
||||
|
||||
class CTkInputDialog:
|
||||
|
@ -4,17 +4,16 @@ import sys
|
||||
import os
|
||||
import platform
|
||||
import ctypes
|
||||
import re
|
||||
from typing import Union, Tuple, List
|
||||
from typing import Union, Tuple
|
||||
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..theme_manager import ThemeManager
|
||||
from ..scaling_tracker import ScalingTracker
|
||||
from .widgets.theme.theme_manager import ThemeManager
|
||||
from .widgets.scaling.scaling_base_class import CTkScalingBaseClass
|
||||
from .widgets.appearance_mode.appearance_mode_base_class import CTkAppearanceModeBaseClass
|
||||
|
||||
from ..utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
|
||||
|
||||
|
||||
class CTk(tkinter.Tk):
|
||||
class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
"""
|
||||
Main app window with dark titlebar on Windows and macOS.
|
||||
For detailed information check out the documentation.
|
||||
@ -33,21 +32,15 @@ class CTk(tkinter.Tk):
|
||||
fg_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||
**kwargs):
|
||||
|
||||
ScalingTracker.activate_high_dpi_awareness() # make process DPI aware
|
||||
self._enable_macos_dark_title_bar()
|
||||
|
||||
super().__init__(**pop_from_dict_by_set(kwargs, self._valid_tk_constructor_arguments))
|
||||
# call init methods of super classes
|
||||
tkinter.Tk.__init__(self, **pop_from_dict_by_set(kwargs, self._valid_tk_constructor_arguments))
|
||||
CTkAppearanceModeBaseClass.__init__(self)
|
||||
CTkScalingBaseClass.__init__(self, scaling_type="window")
|
||||
check_kwargs_empty(kwargs, raise_error=True)
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self._set_appearance_mode, self)
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
# add set_scaling method to callback list of ScalingTracker for automatic scaling changes
|
||||
ScalingTracker.add_widget(self._set_scaling, self)
|
||||
self._window_scaling = ScalingTracker.get_window_scaling(self)
|
||||
|
||||
self._current_width = 600 # initial window size, always without scaling
|
||||
self._current_width = 600 # initial window size, independent of scaling
|
||||
self._current_height = 500
|
||||
self._min_width: int = 0
|
||||
self._min_height: int = 0
|
||||
@ -57,8 +50,11 @@ class CTk(tkinter.Tk):
|
||||
|
||||
self._fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
|
||||
# set bg of tkinter.Tk
|
||||
super().configure(bg=self._apply_appearance_mode(self._fg_color))
|
||||
super().title("CTk")
|
||||
|
||||
# set title and initial geometry
|
||||
self.title("CTk")
|
||||
self.geometry(f"{self._current_width}x{self._current_height}")
|
||||
|
||||
self._state_before_windows_set_titlebar_color = None
|
||||
@ -77,6 +73,14 @@ class CTk(tkinter.Tk):
|
||||
|
||||
self._block_update_dimensions_event = False
|
||||
|
||||
def destroy(self):
|
||||
self._disable_macos_dark_title_bar()
|
||||
|
||||
# call destroy methods of super classes
|
||||
tkinter.Tk.destroy(self)
|
||||
CTkAppearanceModeBaseClass.destroy(self)
|
||||
CTkScalingBaseClass.destroy(self)
|
||||
|
||||
def _focus_in_event(self, event):
|
||||
# sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
|
||||
if sys.platform == "darwin":
|
||||
@ -123,12 +127,6 @@ class CTk(tkinter.Tk):
|
||||
if self._max_width is not None or self._max_height is not None:
|
||||
super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||
ScalingTracker.remove_window(self._set_scaling, self)
|
||||
self._disable_macos_dark_title_bar()
|
||||
super().destroy()
|
||||
|
||||
def withdraw(self):
|
||||
if self._window_exists is False:
|
||||
self._withdraw_called_before_window_exists = True
|
||||
@ -201,59 +199,6 @@ class CTk(tkinter.Tk):
|
||||
else:
|
||||
return self._reverse_geometry_scaling(super().geometry())
|
||||
|
||||
@staticmethod
|
||||
def _parse_geometry_string(geometry_string: str) -> tuple:
|
||||
# index: 1 2 3 4 5 6
|
||||
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
|
||||
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
|
||||
|
||||
width = int(result.group(2)) if result.group(2) is not None else None
|
||||
height = int(result.group(3)) if result.group(3) is not None else None
|
||||
x = int(result.group(5)) if result.group(5) is not None else None
|
||||
y = int(result.group(6)) if result.group(6) is not None else None
|
||||
|
||||
return width, height, x, y
|
||||
|
||||
def _apply_geometry_scaling(self, geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(scaled_geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def _apply_window_scaling(self, value):
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value * self._window_scaling)
|
||||
else:
|
||||
return value
|
||||
|
||||
def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str:
|
||||
""" color can be either a single hex color string or a color name or it can be a
|
||||
tuple color with (light_color, dark_color). The functions returns
|
||||
always a single color string """
|
||||
|
||||
if type(color) == tuple or type(color) == list:
|
||||
return color[self._appearance_mode]
|
||||
else:
|
||||
return color
|
||||
|
||||
def configure(self, **kwargs):
|
||||
if "fg_color" in kwargs:
|
||||
self._fg_color = kwargs.pop("fg_color")
|
||||
|
@ -4,17 +4,16 @@ import sys
|
||||
import os
|
||||
import platform
|
||||
import ctypes
|
||||
import re
|
||||
from typing import Union, Tuple, List
|
||||
from typing import Union, Tuple
|
||||
|
||||
from ..appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..theme_manager import ThemeManager
|
||||
from ..scaling_tracker import ScalingTracker
|
||||
from .widgets.theme.theme_manager import ThemeManager
|
||||
from .widgets.scaling.scaling_base_class import CTkScalingBaseClass
|
||||
from .widgets.appearance_mode.appearance_mode_base_class import CTkAppearanceModeBaseClass
|
||||
|
||||
from ..utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
|
||||
|
||||
|
||||
class CTkToplevel(tkinter.Toplevel):
|
||||
class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
"""
|
||||
Toplevel window with dark titlebar on Windows and macOS.
|
||||
For detailed information check out the documentation.
|
||||
@ -33,17 +32,12 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
|
||||
self._enable_macos_dark_title_bar()
|
||||
|
||||
# call init methods of super classees
|
||||
super().__init__(*args, **pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments))
|
||||
CTkAppearanceModeBaseClass.__init__(self)
|
||||
CTkScalingBaseClass.__init__(self, scaling_type="window")
|
||||
check_kwargs_empty(kwargs, raise_error=True)
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self._set_appearance_mode, self)
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
# add set_scaling method to callback list of ScalingTracker for automatic scaling changes
|
||||
ScalingTracker.add_widget(self._set_scaling, self)
|
||||
self._window_scaling = ScalingTracker.get_window_scaling(self)
|
||||
|
||||
self._current_width = 200 # initial window size, always without scaling
|
||||
self._current_height = 200
|
||||
self._min_width: int = 0
|
||||
@ -54,7 +48,10 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
|
||||
self._fg_color = ThemeManager.theme["color"]["window_bg_color"] if fg_color == "default_theme" else fg_color
|
||||
|
||||
# set bg color of tkinter.Toplevel
|
||||
super().configure(bg=self._apply_appearance_mode(self._fg_color))
|
||||
|
||||
# set title of tkinter.Toplevel
|
||||
super().title("CTkToplevel")
|
||||
|
||||
self._state_before_windows_set_titlebar_color = None
|
||||
@ -71,6 +68,14 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
self.bind('<Configure>', self._update_dimensions_event)
|
||||
self.bind('<FocusIn>', self._focus_in_event)
|
||||
|
||||
def destroy(self):
|
||||
self._disable_macos_dark_title_bar()
|
||||
|
||||
# call destroy methods of super classes
|
||||
tkinter.Toplevel.destroy(self)
|
||||
CTkAppearanceModeBaseClass.destroy(self)
|
||||
CTkScalingBaseClass.destroy(self)
|
||||
|
||||
def _focus_in_event(self, event):
|
||||
# sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
|
||||
if sys.platform == "darwin":
|
||||
@ -114,65 +119,6 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
else:
|
||||
return self._reverse_geometry_scaling(super().geometry())
|
||||
|
||||
@staticmethod
|
||||
def _parse_geometry_string(geometry_string: str) -> tuple:
|
||||
# index: 1 2 3 4 5 6
|
||||
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
|
||||
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
|
||||
|
||||
width = int(result.group(2)) if result.group(2) is not None else None
|
||||
height = int(result.group(3)) if result.group(3) is not None else None
|
||||
x = int(result.group(5)) if result.group(5) is not None else None
|
||||
y = int(result.group(6)) if result.group(6) is not None else None
|
||||
|
||||
return width, height, x, y
|
||||
|
||||
def _apply_geometry_scaling(self, geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
|
||||
width, height, x, y = self._parse_geometry_string(scaled_geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def _apply_window_scaling(self, value):
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value * self._window_scaling)
|
||||
else:
|
||||
return value
|
||||
|
||||
def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str:
|
||||
""" color can be either a single hex color string or a color name or it can be a
|
||||
tuple color with (light_color, dark_color). The functions returns
|
||||
always a single color string """
|
||||
|
||||
if type(color) == tuple or type(color) == list:
|
||||
return color[self._appearance_mode]
|
||||
else:
|
||||
return color
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||
ScalingTracker.remove_window(self._set_scaling, self)
|
||||
self._disable_macos_dark_title_bar()
|
||||
super().destroy()
|
||||
|
||||
def withdraw(self):
|
||||
if self._windows_set_titlebar_color_called:
|
||||
self._withdraw_called_after_windows_set_titlebar_color = True
|
||||
|
3
customtkinter/windows/widgets/__init__.py
Normal file
3
customtkinter/windows/widgets/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from customtkinter.windows.widgets.core_rendering.ctk_canvas import CTkCanvas
|
||||
|
||||
CTkCanvas.init_font_character_mapping()
|
@ -1,8 +1,16 @@
|
||||
from typing import Union, Tuple, List
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from .appearance_mode_tracker import AppearanceModeTracker
|
||||
|
||||
|
||||
class CTkAppearanceModeBaseClass:
|
||||
class CTkAppearanceModeBaseClass(ABC):
|
||||
def __init__(self):
|
||||
AppearanceModeTracker.add(self._set_appearance_mode, self)
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||
|
||||
def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str:
|
||||
""" color can be either a single hex color string or a color name or it can be a
|
||||
@ -13,3 +21,7 @@ class CTkAppearanceModeBaseClass:
|
||||
return color[self._appearance_mode]
|
||||
else:
|
||||
return color
|
||||
|
||||
@abstractmethod
|
||||
def _set_appearance_mode(self, mode_string: str):
|
||||
return
|
@ -5,7 +5,7 @@ import tkinter
|
||||
from typing import Union, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from customtkinter.widgets.core_rendering.ctk_canvas import CTkCanvas
|
||||
from customtkinter.windows.widgets.core_rendering.ctk_canvas import CTkCanvas
|
||||
|
||||
|
||||
class DrawEngine:
|
@ -2,13 +2,14 @@ import tkinter
|
||||
import sys
|
||||
from typing import Union, Tuple, Callable, List
|
||||
|
||||
from ...theme_manager import ThemeManager
|
||||
from ...appearance_mode_tracker import AppearanceModeTracker
|
||||
from ...scaling_tracker import ScalingTracker
|
||||
from ..theme.theme_manager import ThemeManager
|
||||
from ..scaling.scaling_tracker import ScalingTracker
|
||||
from ..font.ctk_font import CTkFont
|
||||
from ..appearance_mode.appearance_mode_tracker import AppearanceModeTracker
|
||||
from ..appearance_mode.appearance_mode_base_class import CTkAppearanceModeBaseClass
|
||||
|
||||
|
||||
class DropdownMenu(tkinter.Menu):
|
||||
class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass):
|
||||
def __init__(self, *args,
|
||||
min_character_width: int = 18,
|
||||
|
||||
@ -21,7 +22,9 @@ class DropdownMenu(tkinter.Menu):
|
||||
values: List[str] = None,
|
||||
**kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
# call init methods of super classes
|
||||
tkinter.Menu.__init__(self, *args, **kwargs)
|
||||
CTkAppearanceModeBaseClass.__init__(self)
|
||||
|
||||
ScalingTracker.add_widget(self._set_scaling, self)
|
||||
self._widget_scaling = ScalingTracker.get_widget_scaling(self)
|
||||
@ -46,14 +49,17 @@ class DropdownMenu(tkinter.Menu):
|
||||
|
||||
self._add_menu_commands()
|
||||
|
||||
def _update_font(self):
|
||||
""" pass font to tkinter widgets with applied font scaling """
|
||||
super().configure(font=self._apply_font_scaling(self._font))
|
||||
|
||||
def destroy(self):
|
||||
if isinstance(self._font, CTkFont):
|
||||
self._font.remove_size_configure_callback(self._update_font)
|
||||
super().destroy()
|
||||
|
||||
# call destroy methods of super classes
|
||||
tkinter.Menu.destroy(self)
|
||||
CTkAppearanceModeBaseClass.destroy(self)
|
||||
|
||||
def _update_font(self):
|
||||
""" pass font to tkinter widgets with applied font scaling """
|
||||
super().configure(font=self._apply_font_scaling(self._font))
|
||||
|
||||
def _configure_menu_for_platforms(self):
|
||||
""" apply platform specific appearance attributes, configure all colors """
|
@ -1,26 +1,24 @@
|
||||
import sys
|
||||
import tkinter
|
||||
import tkinter.ttk as ttk
|
||||
import copy
|
||||
from typing import Union, Callable, Tuple, List
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Union, Callable, Tuple
|
||||
|
||||
try:
|
||||
from typing import TypedDict
|
||||
except ImportError:
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from ...windows.ctk_tk import CTk
|
||||
from ...windows.ctk_toplevel import CTkToplevel
|
||||
from ...appearance_mode_tracker import AppearanceModeTracker
|
||||
from ...scaling_tracker import ScalingTracker
|
||||
from ...theme_manager import ThemeManager
|
||||
from ...ctk_tk import CTk
|
||||
from ...ctk_toplevel import CTkToplevel
|
||||
from ..theme.theme_manager import ThemeManager
|
||||
from ..font.ctk_font import CTkFont
|
||||
from ..appearance_mode.appearance_mode_base_class import CTkAppearanceModeBaseClass
|
||||
from ..scaling.scaling_base_class import CTkScalingBaseClass
|
||||
|
||||
from ...utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
|
||||
from ....utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
|
||||
|
||||
|
||||
class CTkBaseClass(tkinter.Frame, ABC):
|
||||
class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
|
||||
""" Base class of every CTk widget, handles the dimensions, _bg_color,
|
||||
appearance_mode changes, scaling, bg changes of master if master is not a CTk widget """
|
||||
|
||||
@ -37,21 +35,21 @@ class CTkBaseClass(tkinter.Frame, ABC):
|
||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||
**kwargs):
|
||||
|
||||
super().__init__(master=master, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes))
|
||||
# call init methods of super classes
|
||||
tkinter.Frame.__init__(self, master=master, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes))
|
||||
CTkAppearanceModeBaseClass.__init__(self)
|
||||
CTkScalingBaseClass.__init__(self, scaling_type="widget")
|
||||
|
||||
# check if kwargs is empty, if not raise error for unsupported arguments
|
||||
check_kwargs_empty(kwargs, raise_error=True)
|
||||
|
||||
# dimensions
|
||||
# dimensions independent of scaling
|
||||
self._current_width = width # _current_width and _current_height in pixel, represent current size of the widget
|
||||
self._current_height = height # _current_width and _current_height are independent of the scale
|
||||
self._desired_width = width # _desired_width and _desired_height, represent desired size set by width and height
|
||||
self._desired_height = height
|
||||
|
||||
# scaling
|
||||
ScalingTracker.add_widget(self._set_scaling, self) # add callback for automatic scaling changes
|
||||
self._widget_scaling = ScalingTracker.get_widget_scaling(self)
|
||||
|
||||
# set width and height of tkinter.Frame
|
||||
super().configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
|
||||
@ -59,17 +57,15 @@ class CTkBaseClass(tkinter.Frame, ABC):
|
||||
class GeometryCallDict(TypedDict):
|
||||
function: Callable
|
||||
kwargs: dict
|
||||
|
||||
self._last_geometry_manager_call: Union[GeometryCallDict, None] = None
|
||||
|
||||
# add set_appearance_mode method to callback list of AppearanceModeTracker for appearance mode changes
|
||||
AppearanceModeTracker.add(self._set_appearance_mode, self)
|
||||
self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
|
||||
|
||||
# background color
|
||||
self._bg_color: Union[str, Tuple[str, str]] = self._detect_color_of_master() if bg_color is None else bg_color
|
||||
|
||||
# set bg color of tkinter.Frame
|
||||
super().configure(bg=self._apply_appearance_mode(self._bg_color))
|
||||
|
||||
# add configure callback to tkinter.Frame
|
||||
super().bind('<Configure>', self._update_dimensions_event)
|
||||
|
||||
# overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget as well
|
||||
@ -93,7 +89,14 @@ class CTkBaseClass(tkinter.Frame, ABC):
|
||||
self.master.config = new_configure
|
||||
self.master.configure = new_configure
|
||||
|
||||
@abstractmethod
|
||||
def destroy(self):
|
||||
""" Destroy this and all descendants widgets. """
|
||||
|
||||
# call destroy methods of super classes
|
||||
tkinter.Frame.destroy(self)
|
||||
CTkAppearanceModeBaseClass.destroy(self)
|
||||
CTkScalingBaseClass.destroy(self)
|
||||
|
||||
def _draw(self, no_color_updates: bool = False):
|
||||
return
|
||||
|
||||
@ -221,66 +224,6 @@ class CTkBaseClass(tkinter.Frame, ABC):
|
||||
super().configure(width=self._apply_widget_scaling(self._desired_width),
|
||||
height=self._apply_widget_scaling(self._desired_height))
|
||||
|
||||
def _apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]:
|
||||
if isinstance(value, (int, float)):
|
||||
return value * self._widget_scaling
|
||||
else:
|
||||
return value
|
||||
|
||||
def _apply_font_scaling(self, font: Union[Tuple, CTkFont]) -> tuple:
|
||||
""" Takes CTkFont object and returns tuple font with scaled size, has to be called again for every change of font object """
|
||||
if type(font) == tuple:
|
||||
if len(font) == 1:
|
||||
return font
|
||||
elif len(font) == 2:
|
||||
return font[0], -abs(round(font[1] * self._widget_scaling))
|
||||
elif len(font) == 3:
|
||||
return font[0], -abs(round(font[1] * self._widget_scaling)), font[2]
|
||||
else:
|
||||
raise ValueError(f"Can not scale font {font}. font needs to be tuple of len 1, 2 or 3")
|
||||
|
||||
elif isinstance(font, CTkFont):
|
||||
return font.create_scaled_tuple(self._widget_scaling)
|
||||
else:
|
||||
raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont")
|
||||
|
||||
def _apply_argument_scaling(self, kwargs: dict) -> dict:
|
||||
scaled_kwargs = copy.copy(kwargs)
|
||||
|
||||
if "pady" in scaled_kwargs:
|
||||
if isinstance(scaled_kwargs["pady"], (int, float, str)):
|
||||
scaled_kwargs["pady"] = self._apply_widget_scaling(scaled_kwargs["pady"])
|
||||
elif isinstance(scaled_kwargs["pady"], tuple):
|
||||
scaled_kwargs["pady"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["pady"]])
|
||||
if "padx" in kwargs:
|
||||
if isinstance(scaled_kwargs["padx"], (int, float, str)):
|
||||
scaled_kwargs["padx"] = self._apply_widget_scaling(scaled_kwargs["padx"])
|
||||
elif isinstance(scaled_kwargs["padx"], tuple):
|
||||
scaled_kwargs["padx"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["padx"]])
|
||||
|
||||
if "x" in scaled_kwargs:
|
||||
scaled_kwargs["x"] = self._apply_widget_scaling(scaled_kwargs["x"])
|
||||
if "y" in scaled_kwargs:
|
||||
scaled_kwargs["y"] = self._apply_widget_scaling(scaled_kwargs["y"])
|
||||
|
||||
return scaled_kwargs
|
||||
|
||||
def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str:
|
||||
""" color can be either a single hex color string or a color name or it can be a
|
||||
tuple color with (light_color, dark_color). The functions returns
|
||||
always a single color string """
|
||||
|
||||
if type(color) == tuple or type(color) == list:
|
||||
return color[self._appearance_mode]
|
||||
else:
|
||||
return color
|
||||
|
||||
def destroy(self):
|
||||
""" Destroy this and all descendants widgets. """
|
||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||
ScalingTracker.remove_widget(self._set_scaling, self)
|
||||
super().destroy()
|
||||
|
||||
def place(self, **kwargs):
|
||||
"""
|
||||
Place a widget in the parent widget. Use as options:
|
@ -3,7 +3,7 @@ import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -3,7 +3,7 @@ import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -4,7 +4,7 @@ from typing import Union, Tuple, Callable, List
|
||||
|
||||
from .core_widget_classes.dropdown_menu import DropdownMenu
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -2,7 +2,7 @@ import tkinter
|
||||
from typing import Union, Tuple
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -1,7 +1,7 @@
|
||||
from typing import Union, Tuple, List
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
|
@ -2,7 +2,7 @@ import tkinter
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -3,7 +3,7 @@ import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .core_widget_classes.dropdown_menu import DropdownMenu
|
@ -3,7 +3,7 @@ import math
|
||||
from typing import Union, Tuple
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
|
@ -3,7 +3,7 @@ import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -2,7 +2,7 @@ import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
|
@ -1,7 +1,7 @@
|
||||
import tkinter
|
||||
from typing import Union, Tuple, List, Dict, Callable
|
||||
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .ctk_button import CTkButton
|
||||
from .ctk_frame import CTkFrame
|
||||
from .font.ctk_font import CTkFont
|
@ -3,7 +3,7 @@ import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
|
@ -3,7 +3,7 @@ import sys
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -1,6 +1,6 @@
|
||||
from typing import Union, Tuple, Dict, List, Callable
|
||||
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .ctk_frame import CTkFrame
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .ctk_segmented_button import CTkSegmentedButton
|
@ -3,7 +3,7 @@ from typing import Union, Tuple
|
||||
|
||||
from .core_rendering.ctk_canvas import CTkCanvas
|
||||
from .ctk_scrollbar import CTkScrollbar
|
||||
from ..theme_manager import ThemeManager
|
||||
from .theme.theme_manager import ThemeManager
|
||||
from .core_rendering.draw_engine import DrawEngine
|
||||
from .core_widget_classes.widget_base_class import CTkBaseClass
|
||||
from .font.ctk_font import CTkFont
|
@ -2,7 +2,7 @@ from tkinter.font import Font
|
||||
import copy
|
||||
from typing import List, Callable, Tuple
|
||||
|
||||
from customtkinter.theme_manager import ThemeManager
|
||||
from ..theme.theme_manager import ThemeManager
|
||||
|
||||
|
||||
class CTkFont(Font):
|
0
customtkinter/windows/widgets/image/__init__.py
Normal file
0
customtkinter/windows/widgets/image/__init__.py
Normal file
0
customtkinter/windows/widgets/scaling/__init__.py
Normal file
0
customtkinter/windows/widgets/scaling/__init__.py
Normal file
133
customtkinter/windows/widgets/scaling/scaling_base_class.py
Normal file
133
customtkinter/windows/widgets/scaling/scaling_base_class.py
Normal file
@ -0,0 +1,133 @@
|
||||
from typing import Union, Tuple
|
||||
from abc import ABC, abstractmethod
|
||||
import copy
|
||||
import re
|
||||
try:
|
||||
from typing import Literal
|
||||
except ImportError:
|
||||
from typing_extensions import Literal
|
||||
|
||||
from .scaling_tracker import ScalingTracker
|
||||
from ..font.ctk_font import CTkFont
|
||||
|
||||
|
||||
class CTkScalingBaseClass(ABC):
|
||||
def __init__(self, scaling_type: Literal["widget", "window"] = "widget"):
|
||||
self._scaling_type = scaling_type
|
||||
|
||||
if self._scaling_type == "widget":
|
||||
ScalingTracker.add_widget(self._set_scaling, self) # add callback for automatic scaling changes
|
||||
self._widget_scaling = ScalingTracker.get_widget_scaling(self)
|
||||
elif self._scaling_type == "window":
|
||||
ScalingTracker.activate_high_dpi_awareness() # make process DPI aware
|
||||
ScalingTracker.add_window(self._set_scaling, self) # add callback for automatic scaling changes
|
||||
self._window_scaling = ScalingTracker.get_window_scaling(self)
|
||||
|
||||
def destroy(self):
|
||||
if self._scaling_type == "widget":
|
||||
ScalingTracker.remove_widget(self._set_scaling, self)
|
||||
elif self._scaling_type == "window":
|
||||
ScalingTracker.remove_window(self._set_scaling, self)
|
||||
|
||||
def _apply_widget_scaling(self, value: Union[int, float, str]) -> Union[float, str]:
|
||||
assert self._scaling_type == "widget"
|
||||
|
||||
if isinstance(value, (int, float)):
|
||||
return value * self._widget_scaling
|
||||
else:
|
||||
return value
|
||||
|
||||
def _apply_font_scaling(self, font: Union[Tuple, CTkFont]) -> tuple:
|
||||
""" Takes CTkFont object and returns tuple font with scaled size, has to be called again for every change of font object """
|
||||
assert self._scaling_type == "widget"
|
||||
|
||||
if type(font) == tuple:
|
||||
if len(font) == 1:
|
||||
return font
|
||||
elif len(font) == 2:
|
||||
return font[0], -abs(round(font[1] * self._widget_scaling))
|
||||
elif len(font) == 3:
|
||||
return font[0], -abs(round(font[1] * self._widget_scaling)), font[2]
|
||||
else:
|
||||
raise ValueError(f"Can not scale font {font}. font needs to be tuple of len 1, 2 or 3")
|
||||
|
||||
elif isinstance(font, CTkFont):
|
||||
return font.create_scaled_tuple(self._widget_scaling)
|
||||
else:
|
||||
raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont")
|
||||
|
||||
def _apply_argument_scaling(self, kwargs: dict) -> dict:
|
||||
assert self._scaling_type == "widget"
|
||||
|
||||
scaled_kwargs = copy.copy(kwargs)
|
||||
|
||||
if "pady" in scaled_kwargs:
|
||||
if isinstance(scaled_kwargs["pady"], (int, float, str)):
|
||||
scaled_kwargs["pady"] = self._apply_widget_scaling(scaled_kwargs["pady"])
|
||||
elif isinstance(scaled_kwargs["pady"], tuple):
|
||||
scaled_kwargs["pady"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["pady"]])
|
||||
if "padx" in kwargs:
|
||||
if isinstance(scaled_kwargs["padx"], (int, float, str)):
|
||||
scaled_kwargs["padx"] = self._apply_widget_scaling(scaled_kwargs["padx"])
|
||||
elif isinstance(scaled_kwargs["padx"], tuple):
|
||||
scaled_kwargs["padx"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["padx"]])
|
||||
|
||||
if "x" in scaled_kwargs:
|
||||
scaled_kwargs["x"] = self._apply_widget_scaling(scaled_kwargs["x"])
|
||||
if "y" in scaled_kwargs:
|
||||
scaled_kwargs["y"] = self._apply_widget_scaling(scaled_kwargs["y"])
|
||||
|
||||
return scaled_kwargs
|
||||
|
||||
@staticmethod
|
||||
def _parse_geometry_string(geometry_string: str) -> tuple:
|
||||
# index: 1 2 3 4 5 6
|
||||
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
|
||||
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
|
||||
|
||||
width = int(result.group(2)) if result.group(2) is not None else None
|
||||
height = int(result.group(3)) if result.group(3) is not None else None
|
||||
x = int(result.group(5)) if result.group(5) is not None else None
|
||||
y = int(result.group(6)) if result.group(6) is not None else None
|
||||
|
||||
return width, height, x, y
|
||||
|
||||
def _apply_geometry_scaling(self, geometry_string: str) -> str:
|
||||
assert self._scaling_type == "window"
|
||||
|
||||
width, height, x, y = self._parse_geometry_string(geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
|
||||
assert self._scaling_type == "window"
|
||||
|
||||
width, height, x, y = self._parse_geometry_string(scaled_geometry_string)
|
||||
|
||||
if x is None and y is None: # no <x> and <y> in geometry_string
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}"
|
||||
|
||||
elif width is None and height is None: # no <width> and <height> in geometry_string
|
||||
return f"+{x}+{y}"
|
||||
|
||||
else:
|
||||
return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}+{x}+{y}"
|
||||
|
||||
def _apply_window_scaling(self, value):
|
||||
assert self._scaling_type == "window"
|
||||
|
||||
if isinstance(value, (int, float)):
|
||||
return int(value * self._window_scaling)
|
||||
else:
|
||||
return value
|
||||
|
||||
@abstractmethod
|
||||
def _set_scaling(self, new_widget_scaling, new_window_scaling):
|
||||
return
|
0
customtkinter/windows/widgets/theme/__init__.py
Normal file
0
customtkinter/windows/widgets/theme/__init__.py
Normal file
@ -13,7 +13,7 @@ class ThemeManager:
|
||||
script_directory = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
if theme_name_or_path in cls.built_in_themes:
|
||||
with open(os.path.join(script_directory, "assets", "themes", f"{theme_name_or_path}.json"), "r") as f:
|
||||
with open(os.path.join(script_directory, "../../../assets", "themes", f"{theme_name_or_path}.json"), "r") as f:
|
||||
cls.theme = json.load(f)
|
||||
else:
|
||||
with open(theme_name_or_path, "r") as f:
|
@ -1 +1,2 @@
|
||||
darkdetect~=0.3.1
|
||||
darkdetect~=0.3.1
|
||||
typing-extensions~=4.4.0
|
||||
|
Loading…
Reference in New Issue
Block a user