Open loafthecomputerphile opened 1 year ago
So users would be able to add their own IDs to the .json
file?
This would require an exception to be made in the case that the user passes a ID but that ID doesn't exist in the theme. Alternatively, if the ID doesn't exist in the theme, the widget would just use the default theme colors.
Would these id's not need to be associated with a primitive widget? Otherwise, how would theme manager know which theme properties to resolve to specific widget's and widget properties.
@avalon60 @dishb thanks for pointing that out
i believe that you can set to default by first allowing a secondary theme import which will merge with the base theme dictionary if it is used which helps with backwards compatibility while keeping valid original themes if there isn't conflicts. the priority theme is the user made theme.
then in the button file we check if the id is in the dictionary. if it isnt it reverts to the the original theme via id
class ThemeManager:
theme: dict = {} # contains all the theme data
_priority_loaded_theme : Union[str, None] = None
_built_in_themes: List[str] = ["blue", "green", "dark-blue", "sweetkind"]
_currently_loaded_theme: Union[str, None] = None
@classmethod
def load_theme(cls, theme_name_or_path: str, priority_theme_path: Union[str, None]):
script_directory = os.path.dirname(os.path.abspath(__file__))
if theme_name_or_path in cls._built_in_themes:
customtkinter_path = pathlib.Path(script_directory).parent.parent.parent
with open(os.path.join(customtkinter_path, "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:
cls.theme = json.load(f)
#loads and merges the dictionary and overwrites conflicts if there are any
if priority_theme_path != None:
with open(priority_theme_path, "r") as f:
cls.theme = {**cls.theme, **json.load(f)}
# store theme path for saving
cls._priority_loaded_theme = priority_theme_path
cls._currently_loaded_theme = theme_name_or_path
# filter theme values for platform
for key in cls.theme.keys():
# check if values for key differ on platforms
if "macOS" in cls.theme[key].keys():
if sys.platform == "darwin":
cls.theme[key] = cls.theme[key]["macOS"]
elif sys.platform.startswith("win"):
cls.theme[key] = cls.theme[key]["Windows"]
else:
cls.theme[key] = cls.theme[key]["Linux"]
class CTkButton(CTkBaseClass):
_image_label_spacing: int = 6
def __init__(self,
master: any,
width: int = 140,
height: int = 28,
id: str = "CTkButton", #edit here
corner_radius: Optional[int] = None,
border_width: Optional[int] = None,
border_spacing: int = 2,
bg_color: Union[str, Tuple[str, str]] = "transparent",
fg_color: Optional[Union[str, Tuple[str, str]]] = None,
hover_color: Optional[Union[str, Tuple[str, str]]] = None,
border_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color: Optional[Union[str, Tuple[str, str]]] = None,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
round_width_to_even_numbers: bool = True,
round_height_to_even_numbers: bool = True,
text: str = "CTkButton",
font: Optional[Union[tuple, CTkFont]] = None,
textvariable: Union[tkinter.Variable, None] = None,
image: Union[CTkImage, "ImageTk.PhotoImage", None] = None,
state: str = "normal",
hover: bool = True,
command: Union[Callable[[], None], None] = None,
compound: str = "left",
anchor: str = "center",
**kwargs):
# transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
self.original_id: str = "CTkButton" #sets an original id for reverting
self.id: str = id #made it a class variable
#this checks if id is in the dictionary if not revert to original_id
if ThemeManager.theme.get(self.id, None) == None:
self.id = self.original_id
#+++++++++++++++++++++++++++++++++++++++++#
######## used self.id in place of CTKButton with theme manager as a Key ########
#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^#
# shape
self._corner_radius: int = ThemeManager.theme[self.id]["corner_radius"] if corner_radius is None else corner_radius
self._corner_radius = min(self._corner_radius, round(self._current_height / 2))
self._border_width: int = ThemeManager.theme[self.id]["border_width"] if border_width is None else border_width
self._border_spacing: int = border_spacing
# color
self._fg_color: Union[str, Tuple[str, str]] = ThemeManager.theme[self.id]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
self._hover_color: Union[str, Tuple[str, str]] = ThemeManager.theme[self.id]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
self._border_color: Union[str, Tuple[str, str]] = ThemeManager.theme[self.id]["border_color"] if border_color is None else self._check_color_type(border_color)
self._text_color: Union[str, Tuple[str, str]] = ThemeManager.theme[self.id]["text_color"] if text_color is None else self._check_color_type(text_color)
self._text_color_disabled: Union[str, Tuple[str, str]] = ThemeManager.theme[self.id]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
BTW - great idea. This would allow you to define, as an example, "hot buttons".
So users would be able to add their own IDs to the
.json
file?This would require an exception to be made in the case that the user passes a ID but that ID doesn't exist in the theme. Alternatively, if the ID doesn't exist in the theme, the widget would just use the default theme colors.
Yes @dishb, I would think it should have an option to fall back to the basic widget colours, rather than raise an exception. That way apps could still work, even if a chosen theme didn't have the bespoke styling.
While not raising an exception, a message should be printed out. Some sort of warning telling the developer that the ID doesn't exist in the theme. This way, (in most cases) the developer cannot put the code into production.
tion to fall back to the basic widget colours, rather than raise an exception. That way apps could still work, even if a chosen theme didn't have the bespoke styling.
yeah i believe adding:
warnings.warn(f' {self.id} was not found in theme imports ')
under the if statement that checks if the id is in the theme dictionary would do that
While looking through the source code for some insight on how the json theme files work i believe that i found a new styling method that can be implemented which can help make it easier, faster and more powerful to use theme sheets while keeping the old theme method the same.
This method is similar to how you would style HTML with CSS via the use of classes or ids. This could simply be done by adding an id parameter when calling a widget which is initialized with the name of that widget and then that id is passed to the ThemeManager in the init function of that widget.
Modified code
Theme.json Representation
so now if the id of the button was
id = "primary_button"
theme.json
finally after importing the theme the button would automatically be themed without the need of constant restyling and allows unique theme styles for the same widget type but for different uses
from what i see in the source code this may work and should be easy to implement
thank you for your time reading this