diff --git a/customtkinter/__init__.py b/customtkinter/__init__.py index c3cca8f..4ac2e04 100644 --- a/customtkinter/__init__.py +++ b/customtkinter/__init__.py @@ -70,6 +70,9 @@ from .windows.ctk_input_dialog import CTkInputDialog # font classes from .windows.widgets.font.ctk_font import CTkFont +# image classes +from .windows.widgets.image.ctk_image import CTkImage + def set_appearance_mode(mode_string: str): """ possible values: light, dark, system """ diff --git a/customtkinter/windows/ctk_tk.py b/customtkinter/windows/ctk_tk.py index c7e5251..e74730b 100644 --- a/customtkinter/windows/ctk_tk.py +++ b/customtkinter/windows/ctk_tk.py @@ -63,10 +63,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop() if sys.platform.startswith("win"): - if self._appearance_mode == 1: - self._windows_set_titlebar_color("dark") - else: - self._windows_set_titlebar_color("light") + self._windows_set_titlebar_color(self._get_appearance_mode()) self.bind('', self._update_dimensions_event) self.bind('', self._focus_in_event) @@ -97,12 +94,12 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): # detected_width = event.width # detected_height = event.height - if self._current_width != round(detected_width / self._window_scaling) or self._current_height != round(detected_height / self._window_scaling): - self._current_width = round(detected_width / self._window_scaling) # adjust current size according to new size given by event - self._current_height = round(detected_height / self._window_scaling) # _current_width and _current_height are independent of the scale + if self._current_width != self._reverse_window_scaling(detected_width) or self._current_height != self._reverse_window_scaling(detected_height): + self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event + self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale def _set_scaling(self, new_widget_scaling, new_window_scaling): - self._window_scaling = new_window_scaling + super()._set_scaling(new_widget_scaling, new_window_scaling) # block update_dimensions_event to prevent current_width and current_height to get updated self._block_update_dimensions_event = True @@ -164,12 +161,9 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): self._last_resizable_args = ([], {"width": width, "height": height}) if sys.platform.startswith("win"): - if self._appearance_mode == 1: - self._windows_set_titlebar_color("dark") - else: - self._windows_set_titlebar_color("light") + self._windows_set_titlebar_color(self._get_appearance_mode()) - def minsize(self, width=None, height=None): + def minsize(self, width: int = None, height: int = None): self._min_width = width self._min_height = height if self._current_width < width: @@ -178,7 +172,7 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): self._current_height = height super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height)) - def maxsize(self, width=None, height=None): + def maxsize(self, width: int = None, height: int = None): self._max_width = width self._max_height = height if self._current_width > width: @@ -297,16 +291,10 @@ class CTk(tkinter.Tk, CTkAppearanceModeBaseClass, CTkScalingBaseClass): else: pass # wait for update or mainloop to be called - def _set_appearance_mode(self, mode_string): - if mode_string.lower() == "dark": - self._appearance_mode = 1 - elif mode_string.lower() == "light": - self._appearance_mode = 0 + def _set_appearance_mode(self, mode_string: str): + super()._set_appearance_mode(mode_string) if sys.platform.startswith("win"): - if self._appearance_mode == 1: - self._windows_set_titlebar_color("dark") - else: - self._windows_set_titlebar_color("light") + self._windows_set_titlebar_color(mode_string) super().configure(bg=self._apply_appearance_mode(self._fg_color)) diff --git a/customtkinter/windows/ctk_toplevel.py b/customtkinter/windows/ctk_toplevel.py index 6c25077..d26a6ac 100644 --- a/customtkinter/windows/ctk_toplevel.py +++ b/customtkinter/windows/ctk_toplevel.py @@ -60,10 +60,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color if sys.platform.startswith("win"): - if self._appearance_mode == 1: - self._windows_set_titlebar_color("dark") - else: - self._windows_set_titlebar_color("light") + self._windows_set_titlebar_color(self._get_appearance_mode()) self.bind('', self._update_dimensions_event) self.bind('', self._focus_in_event) @@ -85,12 +82,12 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl detected_width = self.winfo_width() # detect current window size detected_height = self.winfo_height() - if self._current_width != round(detected_width / self._window_scaling) or self._current_height != round(detected_height / self._window_scaling): - self._current_width = round(detected_width / self._window_scaling) # adjust current size according to new size given by event - self._current_height = round(detected_height / self._window_scaling) # _current_width and _current_height are independent of the scale + if self._current_width != self._reverse_window_scaling(detected_width) or self._current_height != self._reverse_window_scaling(detected_height): + self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event + self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale def _set_scaling(self, new_widget_scaling, new_window_scaling): - self._window_scaling = new_window_scaling + super()._set_scaling(new_widget_scaling, new_window_scaling) # force new dimensions on window by using min, max, and geometry super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height)) @@ -134,10 +131,7 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._last_resizable_args = ([], {"width": width, "height": height}) if sys.platform.startswith("win"): - if self._appearance_mode == 1: - self.after(10, lambda: self._windows_set_titlebar_color("dark")) - else: - self.after(10, lambda: self._windows_set_titlebar_color("light")) + self.after(10, lambda: self._windows_set_titlebar_color(self._get_appearance_mode())) def minsize(self, width=None, height=None): self._min_width = width @@ -259,15 +253,9 @@ class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseCl self._iconify_called_after_windows_set_titlebar_color = False def _set_appearance_mode(self, mode_string): - if mode_string.lower() == "dark": - self._appearance_mode = 1 - elif mode_string.lower() == "light": - self._appearance_mode = 0 + super()._set_appearance_mode(mode_string) if sys.platform.startswith("win"): - if self._appearance_mode == 1: - self._windows_set_titlebar_color("dark") - else: - self._windows_set_titlebar_color("light") + self._windows_set_titlebar_color(mode_string) super().configure(bg=self._apply_appearance_mode(self._fg_color)) diff --git a/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py b/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py index 8db5978..910d2bf 100644 --- a/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py +++ b/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py @@ -1,27 +1,46 @@ from typing import Union, Tuple, List -from abc import ABC, abstractmethod from .appearance_mode_tracker import AppearanceModeTracker -class CTkAppearanceModeBaseClass(ABC): +class CTkAppearanceModeBaseClass: + """ + Super-class that manages the appearance mode. Methods: + + - destroy() must be called when sub-class is destroyed + - _set_appearance_mode() abstractmethod, gets called when appearance mode changes, must be overridden + - _apply_appearance_mode() + + """ def __init__(self): AppearanceModeTracker.add(self._set_appearance_mode, self) - self._appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark" + 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 - tuple color with (light_color, dark_color). The functions returns - always a single color string """ + def _set_appearance_mode(self, mode_string: str): + """ can be overridden but super method must be called at the beginning """ + if mode_string.lower() == "dark": + self.__appearance_mode = 1 + elif mode_string.lower() == "light": + self.__appearance_mode = 0 - if type(color) == tuple or type(color) == list: - return color[self._appearance_mode] + def _get_appearance_mode(self) -> str: + """ get appearance mode as a string, 'light' or 'dark' """ + if self.__appearance_mode == 0: + return "light" + else: + return "dark" + + 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 isinstance(color, (tuple, list)): + return color[self.__appearance_mode] else: return color - - @abstractmethod - def _set_appearance_mode(self, mode_string: str): - return diff --git a/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py b/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py index 6a47bb0..846400e 100644 --- a/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py +++ b/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py @@ -83,12 +83,12 @@ class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass): else: super().configure(tearoff=False, relief="flat", - activebackground=ThemeManager._apply_appearance_mode(self._hover_color, self._appearance_mode), + activebackground=self._apply_appearance_mode(self._hover_color), borderwidth=0, activeborderwidth=0, - bg=ThemeManager._apply_appearance_mode(self._fg_color, self._appearance_mode), - fg=ThemeManager._apply_appearance_mode(self._text_color, self._appearance_mode), - activeforeground=ThemeManager._apply_appearance_mode(self._text_color, self._appearance_mode), + bg=self._apply_appearance_mode(self._fg_color), + fg=self._apply_appearance_mode(self._text_color), + activeforeground=self._apply_appearance_mode(self._text_color), font=self._apply_font_scaling(self._font)) def _add_menu_commands(self): diff --git a/customtkinter/windows/widgets/core_widget_classes/widget_base_class.py b/customtkinter/windows/widgets/core_widget_classes/widget_base_class.py index 82eef05..fcd6f68 100644 --- a/customtkinter/windows/widgets/core_widget_classes/widget_base_class.py +++ b/customtkinter/windows/widgets/core_widget_classes/widget_base_class.py @@ -98,7 +98,9 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas CTkScalingBaseClass.destroy(self) def _draw(self, no_color_updates: bool = False): - return + """ can be overridden but super method must be called """ + if no_color_updates is False: + super().configure(bg=self._apply_appearance_mode(self._bg_color)) def config(self, *args, **kwargs): raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.") @@ -164,9 +166,9 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas def _update_dimensions_event(self, event): # only redraw if dimensions changed (for performance), independent of scaling - if round(self._current_width) != round(event.width / self._widget_scaling) or round(self._current_height) != round(event.height / self._widget_scaling): - self._current_width = (event.width / self._widget_scaling) # adjust current size according to new size given by event - self._current_height = (event.height / self._widget_scaling) # _current_width and _current_height are independent of the scale + if round(self._current_width) != round(self._reverse_widget_scaling(event.width)) or round(self._current_height) != round(self._reverse_widget_scaling(event.height)): + self._current_width = self._reverse_widget_scaling(event.width) # adjust current size according to new size given by event + self._current_height = self._reverse_widget_scaling(event.height) # _current_width and _current_height are independent of the scale self._draw(no_color_updates=True) # faster drawing without color changes @@ -198,16 +200,11 @@ class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClas return "#FFFFFF", "#000000" def _set_appearance_mode(self, mode_string): - if mode_string.lower() == "dark": - self._appearance_mode = 1 - elif mode_string.lower() == "light": - self._appearance_mode = 0 - - super().configure(bg=self._apply_appearance_mode(self._bg_color)) + super()._set_appearance_mode(mode_string) self._draw() def _set_scaling(self, new_widget_scaling, new_window_scaling): - self._widget_scaling = new_widget_scaling + super()._set_scaling(new_widget_scaling, new_window_scaling) super().configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height)) diff --git a/customtkinter/windows/widgets/ctk_button.py b/customtkinter/windows/widgets/ctk_button.py index f2c83c2..b606ab1 100644 --- a/customtkinter/windows/widgets/ctk_button.py +++ b/customtkinter/windows/widgets/ctk_button.py @@ -48,7 +48,7 @@ class CTkButton(CTkBaseClass): anchor: str = "center", **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # color @@ -74,6 +74,8 @@ class CTkButton(CTkBaseClass): self._image_label: Union[tkinter.Label, None] = None self._text = text self._text_label: Union[tkinter.Label, None] = None + if isinstance(self._image, CTkImage): + self._image.add_configure_callback(self._update_image) # font self._font = CTkFont() if font == "default_theme" else self._check_font_type(font) @@ -135,12 +137,19 @@ class CTkButton(CTkBaseClass): self._canvas.grid_forget() self._canvas.grid(row=0, column=0, rowspan=5, columnspan=5, sticky="nsew") + def _update_image(self): + if self._image_label is not None: + self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(), + self._get_appearance_mode())) + def destroy(self): if isinstance(self._font, CTkFont): self._font.remove_size_configure_callback(self._update_font) super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + if self._background_corner_colors is not None: self._draw_engine.draw_background_corners(self._apply_widget_scaling(self._current_width), self._apply_widget_scaling(self._current_height)) @@ -233,7 +242,7 @@ class CTkButton(CTkBaseClass): else: self._image_label.configure(bg=self._apply_appearance_mode(self._fg_color)) - self._image_label.configure(image=self._image) # set image + self._update_image() # set image else: # delete text_label if no text given @@ -354,7 +363,6 @@ class CTkButton(CTkBaseClass): require_redraw = True # text_label will be created in .draw() else: self._text_label.configure(text=self._text) - self._create_grid() if "font" in kwargs: if isinstance(self._font, CTkFont): @@ -371,8 +379,11 @@ class CTkButton(CTkBaseClass): self._text_label.configure(textvariable=self._textvariable) if "image" in kwargs: + if isinstance(self._image, CTkImage): + self._image.remove_configure_callback(self._update_image) self._image = kwargs.pop("image") - self._create_grid() + if isinstance(self._image, CTkImage): + self._image.add_configure_callback(self._update_image) require_redraw = True if "state" in kwargs: diff --git a/customtkinter/windows/widgets/ctk_checkbox.py b/customtkinter/windows/widgets/ctk_checkbox.py index ed2d459..238de15 100644 --- a/customtkinter/windows/widgets/ctk_checkbox.py +++ b/customtkinter/windows/widgets/ctk_checkbox.py @@ -43,7 +43,7 @@ class CTkCheckBox(CTkBaseClass): variable: tkinter.Variable = None, **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # dimensions @@ -169,6 +169,8 @@ class CTkCheckBox(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + requires_recoloring_1 = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._checkbox_width), self._apply_widget_scaling(self._checkbox_height), self._apply_widget_scaling(self._corner_radius), diff --git a/customtkinter/windows/widgets/ctk_combobox.py b/customtkinter/windows/widgets/ctk_combobox.py index 6889664..86526b9 100644 --- a/customtkinter/windows/widgets/ctk_combobox.py +++ b/customtkinter/windows/widgets/ctk_combobox.py @@ -44,7 +44,7 @@ class CTkComboBox(CTkBaseClass): justify: str = "left", **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # color variables @@ -167,6 +167,8 @@ class CTkComboBox(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + left_section_width = self._current_width - self._current_height requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width), self._apply_widget_scaling(self._current_height), diff --git a/customtkinter/windows/widgets/ctk_entry.py b/customtkinter/windows/widgets/ctk_entry.py index bb4fc3d..a04340c 100644 --- a/customtkinter/windows/widgets/ctk_entry.py +++ b/customtkinter/windows/widgets/ctk_entry.py @@ -148,6 +148,8 @@ class CTkEntry(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color)) requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), diff --git a/customtkinter/windows/widgets/ctk_frame.py b/customtkinter/windows/widgets/ctk_frame.py index 93048ee..18793eb 100644 --- a/customtkinter/windows/widgets/ctk_frame.py +++ b/customtkinter/windows/widgets/ctk_frame.py @@ -29,7 +29,7 @@ class CTkFrame(CTkBaseClass): overwrite_preferred_drawing_method: str = None, **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # color @@ -91,6 +91,8 @@ class CTkFrame(CTkBaseClass): self._draw() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + if not self._canvas.winfo_exists(): return diff --git a/customtkinter/windows/widgets/ctk_label.py b/customtkinter/windows/widgets/ctk_label.py index d0c71a1..c4e92bd 100644 --- a/customtkinter/windows/widgets/ctk_label.py +++ b/customtkinter/windows/widgets/ctk_label.py @@ -35,7 +35,7 @@ class CTkLabel(CTkBaseClass): anchor: str = "center", # label anchor: center, n, e, s, w **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height) # color @@ -117,6 +117,8 @@ class CTkLabel(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width), self._apply_widget_scaling(self._current_height), self._apply_widget_scaling(self._corner_radius), diff --git a/customtkinter/windows/widgets/ctk_optionmenu.py b/customtkinter/windows/widgets/ctk_optionmenu.py index 00541c1..8002ca5 100644 --- a/customtkinter/windows/widgets/ctk_optionmenu.py +++ b/customtkinter/windows/widgets/ctk_optionmenu.py @@ -43,7 +43,7 @@ class CTkOptionMenu(CTkBaseClass): anchor: str = "w", **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # color variables @@ -180,6 +180,8 @@ class CTkOptionMenu(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + left_section_width = self._current_width - self._current_height requires_recoloring = self._draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width), self._apply_widget_scaling(self._current_height), diff --git a/customtkinter/windows/widgets/ctk_progressbar.py b/customtkinter/windows/widgets/ctk_progressbar.py index 88ab230..a8c4815 100644 --- a/customtkinter/windows/widgets/ctk_progressbar.py +++ b/customtkinter/windows/widgets/ctk_progressbar.py @@ -46,7 +46,7 @@ class CTkProgressBar(CTkBaseClass): else: height = 8 - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # color @@ -110,6 +110,8 @@ class CTkProgressBar(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + if self._orientation.lower() == "horizontal": orientation = "w" elif self._orientation.lower() == "vertical": diff --git a/customtkinter/windows/widgets/ctk_radiobutton.py b/customtkinter/windows/widgets/ctk_radiobutton.py index 4946625..25bcd19 100644 --- a/customtkinter/windows/widgets/ctk_radiobutton.py +++ b/customtkinter/windows/widgets/ctk_radiobutton.py @@ -42,7 +42,7 @@ class CTkRadioButton(CTkBaseClass): command: Callable = None, **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # dimensions @@ -163,6 +163,8 @@ class CTkRadioButton(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._radiobutton_width), self._apply_widget_scaling(self._radiobutton_height), self._apply_widget_scaling(self._corner_radius), diff --git a/customtkinter/windows/widgets/ctk_scrollbar.py b/customtkinter/windows/widgets/ctk_scrollbar.py index 2e76e06..745b36d 100644 --- a/customtkinter/windows/widgets/ctk_scrollbar.py +++ b/customtkinter/windows/widgets/ctk_scrollbar.py @@ -44,7 +44,7 @@ class CTkScrollbar(CTkBaseClass): else: height = 200 - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # color @@ -118,6 +118,8 @@ class CTkScrollbar(CTkBaseClass): return self._start_value, self._end_value def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + corrected_start_value, corrected_end_value = self._get_scrollbar_values_for_minimum_pixel_size() requires_recoloring = self._draw_engine.draw_rounded_scrollbar(self._apply_widget_scaling(self._current_width), self._apply_widget_scaling(self._current_height), @@ -219,9 +221,9 @@ class CTkScrollbar(CTkBaseClass): def _clicked(self, event): if self._orientation == "vertical": - value = ((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing)) / self._widget_scaling + value = self._reverse_widget_scaling(((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing))) else: - value = ((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing)) / self._widget_scaling + value = self._reverse_widget_scaling(((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing))) current_scrollbar_length = self._end_value - self._start_value value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) diff --git a/customtkinter/windows/widgets/ctk_slider.py b/customtkinter/windows/widgets/ctk_slider.py index c8011ac..cf6ceeb 100644 --- a/customtkinter/windows/widgets/ctk_slider.py +++ b/customtkinter/windows/widgets/ctk_slider.py @@ -52,7 +52,7 @@ class CTkSlider(CTkBaseClass): else: height = 16 - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # color @@ -145,6 +145,8 @@ class CTkSlider(CTkBaseClass): self.configure(cursor="arrow") def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) + if self._orientation.lower() == "horizontal": orientation = "w" elif self._orientation.lower() == "vertical": diff --git a/customtkinter/windows/widgets/ctk_switch.py b/customtkinter/windows/widgets/ctk_switch.py index 726033d..d876c7b 100644 --- a/customtkinter/windows/widgets/ctk_switch.py +++ b/customtkinter/windows/widgets/ctk_switch.py @@ -45,7 +45,7 @@ class CTkSwitch(CTkBaseClass): state: str = tkinter.NORMAL, **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs) # dimensions @@ -193,6 +193,7 @@ class CTkSwitch(CTkBaseClass): self._text_label.configure(cursor="hand2") def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) if self._check_state is True: requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._switch_width), diff --git a/customtkinter/windows/widgets/ctk_tabview.py b/customtkinter/windows/widgets/ctk_tabview.py index d3065cb..31a8fbd 100644 --- a/customtkinter/windows/widgets/ctk_tabview.py +++ b/customtkinter/windows/widgets/ctk_tabview.py @@ -188,6 +188,8 @@ class CTkTabview(CTkBaseClass): return new_tab def _draw(self, no_color_updates: bool = False): + super()._draw(no_color_updates) + if not self._canvas.winfo_exists(): return diff --git a/customtkinter/windows/widgets/ctk_textbox.py b/customtkinter/windows/widgets/ctk_textbox.py index a914c93..cb302fe 100644 --- a/customtkinter/windows/widgets/ctk_textbox.py +++ b/customtkinter/windows/widgets/ctk_textbox.py @@ -51,7 +51,7 @@ class CTkTextbox(CTkBaseClass): activate_scrollbars: bool = True, **kwargs): - # transfer basic functionality (_bg_color, size, _appearance_mode, scaling) to CTkBaseClass + # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass super().__init__(master=master, bg_color=bg_color, width=width, height=height) # color @@ -210,6 +210,7 @@ class CTkTextbox(CTkBaseClass): super().destroy() def _draw(self, no_color_updates=False): + super()._draw(no_color_updates) if not self._canvas.winfo_exists(): return diff --git a/customtkinter/windows/widgets/image/ctk_image.py b/customtkinter/windows/widgets/image/ctk_image.py index d8709cf..82bdc51 100644 --- a/customtkinter/windows/widgets/image/ctk_image.py +++ b/customtkinter/windows/widgets/image/ctk_image.py @@ -18,11 +18,16 @@ class CTkImage: _checked_PIL_import = False - def __init__(self, light_image: Image.Image = None, dark_image: Image.Image = None, size: Tuple[int, int] = None): + def __init__(self, + light_image: Image.Image = None, + dark_image: Image.Image = None, + size: Tuple[int, int] = (20, 20)): + if not self._checked_PIL_import: self._check_pil_import() self._light_image = light_image + print(self._light_image) self._dark_image = dark_image self._check_images() self._size = size @@ -33,8 +38,10 @@ class CTkImage: @classmethod def _check_pil_import(cls): - if "Image" not in dir() or "ImageTk" not in dir(): - raise ImportError("CTkImage: Couldn't import PIL.Image or PIL.ImageTk. PIL must be installed.") + try: + _, _ = Image, ImageTk + except NameError: + raise ImportError("PIL.Image and PIL.ImageTk couldn't be imported") def add_configure_callback(self, callback: Callable): """ add function, that gets called when image got configured """ @@ -84,7 +91,7 @@ class CTkImage: raise ValueError(f"CTkImage: light_image size {self._light_image.size} must be the same as dark_image size {self._dark_image.size}.") def _get_scaled_size(self, widget_scaling: float) -> Tuple[int, int]: - return round(self._size[0] * widget_scaling), round(self._size[0] * widget_scaling) + return round(self._size[0] * widget_scaling), round(self._size[1] * widget_scaling) def _get_scaled_light_photo_image(self, scaled_size: Tuple[int, int]) -> ImageTk.PhotoImage: if scaled_size in self._scaled_light_photo_images: @@ -100,17 +107,18 @@ class CTkImage: self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size)) return self._scaled_dark_photo_images[scaled_size] - def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: int) -> ImageTk.PhotoImage: + def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: str) -> ImageTk.PhotoImage: scaled_size = self._get_scaled_size(widget_scaling) + print(scaled_size) - if appearance_mode == 0 and self._light_image is not None: + if appearance_mode == "light" and self._light_image is not None: return self._get_scaled_light_photo_image(scaled_size) - elif appearance_mode == 0 and self._light_image is None: + elif appearance_mode == "light" and self._light_image is None: return self._get_scaled_dark_photo_image(scaled_size) - elif appearance_mode == 1 and self._dark_image is not None: + elif appearance_mode == "dark" and self._dark_image is not None: return self._get_scaled_dark_photo_image(scaled_size) - elif appearance_mode == 1 and self._dark_image is None: + elif appearance_mode == "dark" and self._dark_image is None: return self._get_scaled_light_photo_image(scaled_size) diff --git a/customtkinter/windows/widgets/scaling/scaling_base_class.py b/customtkinter/windows/widgets/scaling/scaling_base_class.py index a27a33c..009ec99 100644 --- a/customtkinter/windows/widgets/scaling/scaling_base_class.py +++ b/customtkinter/windows/widgets/scaling/scaling_base_class.py @@ -1,5 +1,5 @@ +import tkinter from typing import Union, Tuple -from abc import ABC, abstractmethod import copy import re try: @@ -9,69 +9,109 @@ except ImportError: from .scaling_tracker import ScalingTracker from ..font.ctk_font import CTkFont +from ..image.ctk_image import CTkImage -class CTkScalingBaseClass(ABC): +class CTkScalingBaseClass(): + """ + Super-class that manages the scaling values and callbacks. + Works for widgets and windows, type must be set in init method with + scaling_type attribute. Methods: + + - _set_scaling() abstractmethod, gets called when scaling changes, must be overridden + - destroy() must be called when sub-class is destroyed + - _apply_widget_scaling() + - _reverse_widget_scaling() + - _apply_window_scaling() + - _reverse_window_scaling() + - _apply_font_scaling() + - _apply_argument_scaling() + - _apply_geometry_scaling() + - _reverse_geometry_scaling() + - _parse_geometry_string() + + """ def __init__(self, scaling_type: Literal["widget", "window"] = "widget"): - self._scaling_type = scaling_type + self.__scaling_type = scaling_type - if self._scaling_type == "widget": + 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": + 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) + self.__window_scaling = ScalingTracker.get_window_scaling(self) def destroy(self): - if self._scaling_type == "widget": + if self.__scaling_type == "widget": ScalingTracker.remove_widget(self._set_scaling, self) - elif self._scaling_type == "window": + 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" + def _set_scaling(self, new_widget_scaling, new_window_scaling): + """ can be overridden, but super method must be called at the beginning """ + self.__widget_scaling = new_widget_scaling + self.__window_scaling = new_window_scaling - if isinstance(value, (int, float)): - return value * self._widget_scaling - else: - return value + def _get_widget_scaling(self) -> float: + return self.__widget_scaling + + def _get_window_scaling(self) -> float: + return self.__window_scaling + + def _apply_widget_scaling(self, value: Union[int, float]) -> Union[float]: + assert self.__scaling_type == "widget" + return value * self.__widget_scaling + + def _reverse_widget_scaling(self, value: Union[int, float]) -> Union[float]: + assert self.__scaling_type == "widget" + return value / self.__widget_scaling + + def _apply_window_scaling(self, value: Union[int, float]) -> int: + assert self.__scaling_type == "window" + return int(value * self.__window_scaling) + + def _reverse_window_scaling(self, scaled_value: Union[int, float]) -> int: + assert self.__scaling_type == "window" + return int(scaled_value / self.__window_scaling) 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" + 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)) + 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] + 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) + 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" + assert self.__scaling_type == "widget" scaled_kwargs = copy.copy(kwargs) + # scale padding values if "pady" in scaled_kwargs: - if isinstance(scaled_kwargs["pady"], (int, float, str)): + if isinstance(scaled_kwargs["pady"], (int, float)): 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)): + if isinstance(scaled_kwargs["padx"], (int, float)): 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"]]) + # scaled x, y values for place geometry manager if "x" in scaled_kwargs: scaled_kwargs["x"] = self._apply_widget_scaling(scaled_kwargs["x"]) if "y" in scaled_kwargs: @@ -93,41 +133,29 @@ class CTkScalingBaseClass(ABC): return width, height, x, y def _apply_geometry_scaling(self, geometry_string: str) -> str: - assert self._scaling_type == "window" + assert self.__scaling_type == "window" width, height, x, y = self._parse_geometry_string(geometry_string) if x is None and y is None: # no and in geometry_string - return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}" + return f"{round(width * self.__window_scaling)}x{round(height * self.__window_scaling)}" elif width is None and height is None: # no and in geometry_string return f"+{x}+{y}" else: - return f"{round(width * self._window_scaling)}x{round(height * self._window_scaling)}+{x}+{y}" + 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" + 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 and in geometry_string - return f"{round(width / self._window_scaling)}x{round(height / self._window_scaling)}" + return f"{round(width / self.__window_scaling)}x{round(height / self.__window_scaling)}" elif width is None and height is None: # no and 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 + return f"{round(width / self.__window_scaling)}x{round(height / self.__window_scaling)}+{x}+{y}" diff --git a/examples/complex_example.py b/examples/complex_example.py index 58e8f26..df9eb98 100644 --- a/examples/complex_example.py +++ b/examples/complex_example.py @@ -13,13 +13,13 @@ class App(customtkinter.CTk): self.title("CustomTkinter complex_example.py") self.geometry(f"{1100}x{580}") - self.minsize(800, 400) + #self.minsize(800, 400) #self.maxsize(1200, 700) self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed # configure grid layout (4x4) self.grid_columnconfigure(1, weight=1) - self.grid_columnconfigure((2, 3, 4), weight=0, minsize=200) + self.grid_columnconfigure((2, 3), weight=0) self.grid_rowconfigure((0, 1, 2), weight=1) # create sidebar frame with widgets @@ -47,63 +47,18 @@ class App(customtkinter.CTk): # create main entry and button self.entry = customtkinter.CTkEntry(self, placeholder_text="CTkEntry") - self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 10), pady=(10, 20), sticky="nsew") + self.entry.grid(row=3, column=1, columnspan=2, padx=(20, 0), pady=(20, 20), sticky="nsew") self.main_button_1 = customtkinter.CTkButton(master=self, fg_color=None, border_width=2) - self.main_button_1.grid(row=3, column=3, padx=(10, 20), pady=(10, 20), sticky="nsew") + self.main_button_1.grid(row=3, column=3, padx=(20, 20), pady=(20, 20), sticky="nsew") # create textbox - self.textbox = customtkinter.CTkTextbox(self) - self.textbox.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew") - - # create radiobutton frame - self.radiobutton_frame = customtkinter.CTkFrame(self) - self.radiobutton_frame.grid(row=0, column=3, padx=(10, 10), pady=(20, 10), sticky="nsew") - self.radio_var = tkinter.IntVar(value=0) - self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:") - self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") - self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0) - self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n") - self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1) - self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n") - self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2) - self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n") - - # create checkbox and switch frame - self.checkbox_slider_frame = customtkinter.CTkFrame(self) - self.checkbox_slider_frame.grid(row=0, column=4, padx=(10, 20), pady=(20, 10), sticky="nsew") - self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) - self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n") - self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) - self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n") - self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame, command=lambda: print("switch 1 toggle")) - self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n") - self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame) - self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n") - - # create slider and progressbar frame - self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color=None) - self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 10), pady=(10, 10), sticky="nsew") - self.slider_progressbar_frame.grid_columnconfigure(0, weight=1) - self.slider_progressbar_frame.grid_rowconfigure(4, weight=1) - - self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame) - self.seg_button_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") - - self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) - self.progressbar_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") - self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) - self.progressbar_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") - self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4) - self.slider_1.grid(row=3, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") - self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical") - self.slider_2.grid(row=0, column=1, rowspan=5, padx=(10, 10), pady=(10, 10), sticky="ns") - self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical") - self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns") + self.textbox = customtkinter.CTkTextbox(self, width=250) + self.textbox.grid(row=0, column=1, padx=(20, 0), pady=(20, 0), sticky="nsew") # create tabview - self.tabview = customtkinter.CTkTabview(self) - self.tabview.grid(row=1, column=3, columnspan=2, padx=(10, 20), pady=(10, 10), sticky="nsew") + self.tabview = customtkinter.CTkTabview(self, width=250) + self.tabview.grid(row=0, column=2, padx=(20, 0), pady=(20, 0), sticky="nsew") self.tabview.add("CTkTabview") self.tabview.add("Tab 2") self.tabview.add("Tab 3") @@ -121,6 +76,51 @@ class App(customtkinter.CTk): command=self.open_input_dialog) self.string_input_button.grid(row=2, column=0, padx=20, pady=(10, 10)) + # create radiobutton frame + self.radiobutton_frame = customtkinter.CTkFrame(self) + self.radiobutton_frame.grid(row=0, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew") + self.radio_var = tkinter.IntVar(value=0) + self.label_radio_group = customtkinter.CTkLabel(master=self.radiobutton_frame, text="CTkRadioButton Group:") + self.label_radio_group.grid(row=0, column=2, columnspan=1, padx=10, pady=10, sticky="") + self.radio_button_1 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=0) + self.radio_button_1.grid(row=1, column=2, pady=10, padx=20, sticky="n") + self.radio_button_2 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=1) + self.radio_button_2.grid(row=2, column=2, pady=10, padx=20, sticky="n") + self.radio_button_3 = customtkinter.CTkRadioButton(master=self.radiobutton_frame, variable=self.radio_var, value=2) + self.radio_button_3.grid(row=3, column=2, pady=10, padx=20, sticky="n") + + # create checkbox and switch frame + self.checkbox_slider_frame = customtkinter.CTkFrame(self) + self.checkbox_slider_frame.grid(row=1, column=3, padx=(20, 20), pady=(20, 0), sticky="nsew") + self.checkbox_1 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) + self.checkbox_1.grid(row=1, column=0, pady=(20, 10), padx=20, sticky="n") + self.checkbox_2 = customtkinter.CTkCheckBox(master=self.checkbox_slider_frame) + self.checkbox_2.grid(row=2, column=0, pady=10, padx=20, sticky="n") + self.switch_1 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame, command=lambda: print("switch 1 toggle")) + self.switch_1.grid(row=3, column=0, pady=10, padx=20, sticky="n") + self.switch_2 = customtkinter.CTkSwitch(master=self.checkbox_slider_frame) + self.switch_2.grid(row=4, column=0, pady=(10, 20), padx=20, sticky="n") + + # create slider and progressbar frame + self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color=None) + self.slider_progressbar_frame.grid(row=1, column=1, columnspan=2, padx=(20, 0), pady=(20, 0), sticky="nsew") + self.slider_progressbar_frame.grid_columnconfigure(0, weight=1) + self.slider_progressbar_frame.grid_rowconfigure(4, weight=1) + + self.seg_button_1 = customtkinter.CTkSegmentedButton(self.slider_progressbar_frame) + self.seg_button_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") + + self.progressbar_1 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) + self.progressbar_1.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") + self.progressbar_2 = customtkinter.CTkProgressBar(self.slider_progressbar_frame) + self.progressbar_2.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") + self.slider_1 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4) + self.slider_1.grid(row=3, column=0, padx=(20, 10), pady=(10, 10), sticky="ew") + self.slider_2 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical") + self.slider_2.grid(row=0, column=1, rowspan=5, padx=(10, 10), pady=(10, 10), sticky="ns") + self.progressbar_3 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical") + self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns") + # set default values self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton") self.checkbox_2.configure(state="disabled") diff --git a/examples/example_button_images.py b/examples/example_button_images.py index caec9b7..ac6fb7a 100644 --- a/examples/example_button_images.py +++ b/examples/example_button_images.py @@ -1,77 +1,22 @@ -import PIL.ImageTk - import customtkinter -import tkinter from PIL import Image, ImageTk import os PATH = os.path.dirname(os.path.realpath(__file__)) -customtkinter.set_appearance_mode("System") # Modes: "System" (standard), "Dark", "Light" -customtkinter.set_default_color_theme("blue") # Themes: "blue" (standard), "green", "dark-blue" +app = customtkinter.CTk() -class App(customtkinter.CTk): - def __init__(self): - super().__init__() - self.geometry("450x260") - self.title("CustomTkinter example_button_images.py") +switch_1 = customtkinter.CTkSwitch(app, text="darkmode", command=lambda: customtkinter.set_appearance_mode("dark" if switch_1.get() == 1 else "light")) +switch_1.pack(padx=20, pady=20) - self.grid_rowconfigure(0, weight=1) - self.grid_columnconfigure(0, weight=1, minsize=200) +image_1 = customtkinter.CTkImage(light_image=Image.open(PATH + "/test_images/add_folder_dark.png"), + dark_image=Image.open(PATH + "/test_images/add_folder_light.png"), + size=(30, 50)) +image_1.configure(dark_image=Image.open(PATH + "/test_images/add_folder_light.png")) - self.frame_1 = customtkinter.CTkFrame(master=self, width=250, height=240, corner_radius=15) - self.frame_1.grid(row=0, column=0, padx=20, pady=20, sticky="nsew") - self.frame_1.grid_columnconfigure(0, weight=1) - self.frame_1.grid_columnconfigure(1, weight=1) +button_1 = customtkinter.CTkButton(app, image=image_1) +button_1.pack(padx=20, pady=20) - self.settings_image = self.load_image("/test_images/settings.png", 20) - self.bell_image = self.load_image("/test_images/bell.png", 20) - self.add_folder_image = self.load_image("/test_images/add-folder.png", 20) - self.add_list_image = self.load_image("/test_images/add-folder.png", 20) - self.add_user_image = self.load_image("/test_images/add-user.png", 20) - self.chat_image = self.load_image("/test_images/chat.png", 20) - self.home_image = self.load_image("/test_images/home.png", 20) +app.mainloop() - self.button_1 = customtkinter.CTkButton(master=self.frame_1, image=self.settings_image, text="Add Folder", height=32, - compound="right", command=self.button_function) - self.button_1.grid(row=1, column=0, columnspan=2, padx=20, pady=(20, 10), sticky="ew") - - self.button_2 = customtkinter.CTkButton(master=self.frame_1, image=self.add_list_image, text="Add Item", height=32, - compound="right", fg_color="#D35B58", hover_color="#C77C78", - command=self.button_function) - self.button_2.grid(row=2, column=0, columnspan=2, padx=20, pady=10, sticky="ew") - - self.button_3 = customtkinter.CTkButton(master=self.frame_1, image=self.chat_image, text="", width=40, height=40, - corner_radius=10, fg_color="gray40", hover_color="gray25", - command=self.button_function) - self.button_3.grid(row=3, column=0, columnspan=1, padx=20, pady=10, sticky="w") - - self.button_4 = customtkinter.CTkButton(master=self.frame_1, image=self.home_image, text="", width=40, height=40, - corner_radius=10, fg_color="gray40", hover_color="gray25", - command=self.button_function) - self.button_4.grid(row=3, column=1, columnspan=1, padx=20, pady=10, sticky="e") - - self.button_5 = customtkinter.CTkButton(master=self, image=self.add_user_image, text="Add User", width=130, height=60, border_width=2, - corner_radius=10, compound="bottom", border_color="#D35B58", fg_color=("gray84", "gray25"), - hover_color="#C77C78", command=self.button_function) - self.button_5.grid(row=0, column=1, padx=20, pady=20) - - self.scaling_button = customtkinter.CTkSegmentedButton(self, values=[0.8, 0.9, 1.0, 1.1, 1.2, 1.5], - command=lambda v: customtkinter.set_widget_scaling(v)) - self.scaling_button.grid(row=1, column=0, pady=(0, 20)) - self.mode_switch = customtkinter.CTkSwitch(self, text="darkmode", onvalue="dark", offvalue="light", - command=lambda: customtkinter.set_appearance_mode(self.mode_switch.get())) - self.mode_switch.grid(row=1, column=1, pady=(0, 20)) - - def load_image(self, path, image_size): - """ load rectangular image with path relative to PATH """ - return ImageTk.PhotoImage(Image.open(PATH + path).resize((image_size, image_size))) - - def button_function(self): - print("button pressed") - - -if __name__ == "__main__": - app = App() - app.mainloop() diff --git a/examples/test_images/add-folder.png b/examples/test_images/add-folder.png deleted file mode 100644 index feff512..0000000 Binary files a/examples/test_images/add-folder.png and /dev/null differ diff --git a/test/manual_integration_tests/test_segmented_button.py b/test/manual_integration_tests/test_segmented_button.py index 60b8832..79ef7ce 100644 --- a/test/manual_integration_tests/test_segmented_button.py +++ b/test/manual_integration_tests/test_segmented_button.py @@ -7,7 +7,7 @@ app.geometry("600x950") switch_1 = customtkinter.CTkSwitch(app, text="darkmode", command=lambda: customtkinter.set_appearance_mode("dark" if switch_1.get() == 1 else "light")) switch_1.pack(padx=20, pady=20) -seg_1 = customtkinter._CTkSegmentedButton(app, values=[]) +seg_1 = customtkinter.CTkSegmentedButton(app, values=[]) seg_1.configure(values=["value 1", "Value 2", "Value 42", "Value 123", "longlonglong"]) seg_1.pack(padx=20, pady=20) @@ -16,7 +16,7 @@ frame_1.pack(padx=20, pady=20, fill="x") seg_2_var = customtkinter.StringVar(value="value 1") -seg_2 = customtkinter._CTkSegmentedButton(frame_1, values=["value 1", "Value 2", "Value 42"], variable=seg_2_var) +seg_2 = customtkinter.CTkSegmentedButton(frame_1, values=["value 1", "Value 2", "Value 42"], variable=seg_2_var) seg_2.configure(values=[]) seg_2.configure(values=["value 1", "Value 2", "Value 42"]) seg_2.pack(padx=20, pady=10) @@ -32,14 +32,14 @@ frame_1_1.pack(padx=20, pady=10, fill="x") switch_2 = customtkinter.CTkSwitch(frame_1_1, text="change fg", command=lambda: frame_1_1.configure(fg_color="red" if switch_2.get() == 1 else "green")) switch_2.pack(padx=20, pady=20) -seg_3 = customtkinter._CTkSegmentedButton(frame_1_1, values=["value 1", "Value 2", "Value 42"]) +seg_3 = customtkinter.CTkSegmentedButton(frame_1_1, values=["value 1", "Value 2", "Value 42"]) seg_3.pack(padx=20, pady=10) -seg_4 = customtkinter._CTkSegmentedButton(app) +seg_4 = customtkinter.CTkSegmentedButton(app) seg_4.pack(padx=20, pady=20) seg_5_var = customtkinter.StringVar(value="kfasjkfdklaj") -seg_5 = customtkinter._CTkSegmentedButton(app, corner_radius=1000, border_width=0, unselected_color="green", +seg_5 = customtkinter.CTkSegmentedButton(app, corner_radius=1000, border_width=0, unselected_color="green", variable=seg_5_var) seg_5.pack(padx=20, pady=20) seg_5.configure(values=["1", "2", "3", "4"]) @@ -56,7 +56,7 @@ label_seg_5 = customtkinter.CTkLabel(app, textvariable=seg_5_var) label_seg_5.pack(padx=20, pady=20) seg_6_var = customtkinter.StringVar(value="kfasjkfdklaj") -seg_6 = customtkinter._CTkSegmentedButton(app, width=300) +seg_6 = customtkinter.CTkSegmentedButton(app, width=300) seg_6.pack(padx=20, pady=20) entry_6 = customtkinter.CTkEntry(app) entry_6.pack(padx=20, pady=(0, 20)) @@ -70,7 +70,7 @@ label_6.pack(padx=20, pady=(0, 20)) seg_6.configure(height=50, variable=seg_6_var) seg_6.delete("CTkSegmentedButton") -seg_7 = customtkinter._CTkSegmentedButton(app, values=["disabled seg button", "2", "3"]) +seg_7 = customtkinter.CTkSegmentedButton(app, values=["disabled seg button", "2", "3"]) seg_7.pack(padx=20, pady=20) seg_7.configure(state="disabled") seg_7.set("2") diff --git a/test/manual_integration_tests/test_tabview.py b/test/manual_integration_tests/test_tabview.py index 7b18df3..707150b 100644 --- a/test/manual_integration_tests/test_tabview.py +++ b/test/manual_integration_tests/test_tabview.py @@ -2,7 +2,7 @@ import customtkinter app = customtkinter.CTk() -tabview_1 = customtkinter._CTkTabview(app) +tabview_1 = customtkinter.CTkTabview(app) tabview_1.pack(padx=20, pady=20) tab_1 = tabview_1.add("tab 1")