From 16990a566fd68aa32dc0731a44454b36768f9c23 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Thu, 27 Jul 2023 14:06:13 +0200 Subject: [PATCH] added anchor to tabview #1766, fixed tabview fg_color update #1803, added index method to tabview and segmented button, added rename method to tabview #1192, fixed license in Readme.md --- Readme.md | 2 +- .../windows/widgets/ctk_segmented_button.py | 3 + customtkinter/windows/widgets/ctk_tabview.py | 124 +++++++++++++----- examples/simple_example.py | 19 ++- .../test_images/eye-password-hide.png | Bin 0 -> 3356 bytes .../test_images/eye-password-show.png | Bin 0 -> 3334 bytes 6 files changed, 109 insertions(+), 39 deletions(-) create mode 100644 test/manual_integration_tests/test_images/eye-password-hide.png create mode 100644 test/manual_integration_tests/test_images/eye-password-show.png diff --git a/Readme.md b/Readme.md index 73535b8..a9e49ab 100644 --- a/Readme.md +++ b/Readme.md @@ -10,7 +10,7 @@ ![PyPI](https://img.shields.io/pypi/v/customtkinter) ![PyPI - Downloads](https://img.shields.io/pypi/dm/customtkinter?color=green&label=downloads) ![Downloads](https://static.pepy.tech/personalized-badge/customtkinter?period=total&units=international_system&left_color=grey&right_color=green&left_text=downloads) -![PyPI - License](https://img.shields.io/pypi/l/customtkinter) +![PyPI - License](https://img.shields.io/badge/license_MIT) ![](https://tokei.rs/b1/github/tomschimansky/customtkinter) diff --git a/customtkinter/windows/widgets/ctk_segmented_button.py b/customtkinter/windows/widgets/ctk_segmented_button.py index f1ef0da..764f3d3 100644 --- a/customtkinter/windows/widgets/ctk_segmented_button.py +++ b/customtkinter/windows/widgets/ctk_segmented_button.py @@ -383,6 +383,9 @@ class CTkSegmentedButton(CTkFrame): def get(self) -> str: return self._current_value + def index(self, value: str) -> int: + return self._value_list.index(value) + def insert(self, index: int, value: str): if value not in self._buttons_dict: if value != "": diff --git a/customtkinter/windows/widgets/ctk_tabview.py b/customtkinter/windows/widgets/ctk_tabview.py index 599f59c..0635e61 100644 --- a/customtkinter/windows/widgets/ctk_tabview.py +++ b/customtkinter/windows/widgets/ctk_tabview.py @@ -15,8 +15,8 @@ class CTkTabview(CTkBaseClass): For detailed information check out the documentation. """ - _top_spacing: int = 10 # px on top of the buttons - _top_button_overhang: int = 8 # px + _outer_spacing: int = 10 # px on top or below the button + _outer_button_overhang: int = 8 # px _button_height: int = 26 _segmented_button_border_width: int = 3 @@ -41,6 +41,7 @@ class CTkTabview(CTkBaseClass): text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None, command: Union[Callable, None] = None, + anchor: str = "center", state: str = "normal", **kwargs): @@ -65,12 +66,13 @@ class CTkTabview(CTkBaseClass): # shape self._corner_radius = ThemeManager.theme["CTkFrame"]["corner_radius"] if corner_radius is None else corner_radius self._border_width = ThemeManager.theme["CTkFrame"]["border_width"] if border_width is None else border_width + self._anchor = anchor self._canvas = CTkCanvas(master=self, bg=self._apply_appearance_mode(self._bg_color), highlightthickness=0, width=self._apply_widget_scaling(self._desired_width), - height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang)) + height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang)) self._draw_engine = DrawEngine(self._canvas) self._segmented_button = CTkSegmentedButton(self, @@ -99,9 +101,9 @@ class CTkTabview(CTkBaseClass): self._draw() def _segmented_button_callback(self, selected_name): - self._set_grid_tab_by_name(selected_name) self._tab_dict[self._current_name].grid_forget() self._current_name = selected_name + self._set_grid_current_tab() if self._command is not None: self._command() @@ -124,7 +126,7 @@ class CTkTabview(CTkBaseClass): super()._set_scaling(*args, **kwargs) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), - height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang)) + height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang)) self._configure_grid() self._draw(no_color_updates=True) @@ -132,44 +134,62 @@ class CTkTabview(CTkBaseClass): super()._set_dimensions(width, height) self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), - height=self._apply_widget_scaling(self._desired_height - self._top_spacing - self._top_button_overhang)) + height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang)) self._draw() def _configure_segmented_button_background_corners(self): """ needs to be called for changes in fg_color, bg_color """ - if self._fg_color is not None: - self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._fg_color, self._fg_color)) - else: + if self._fg_color == "transparent": self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color)) - - def _configure_tab_background_corners_by_name(self, name: str): - """ needs to be called for changes in fg_color, bg_color, border_width """ - - self._tab_dict[name].configure(background_corner_colors=None) + else: + if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"): + self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._fg_color, self._fg_color)) + else: + self._segmented_button.configure(background_corner_colors=(self._fg_color, self._fg_color, self._bg_color, self._bg_color)) def _configure_grid(self): """ create 3 x 4 grid system """ - self.grid_rowconfigure(0, weight=0, minsize=self._apply_widget_scaling(self._top_spacing)) - self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._top_button_overhang)) - self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._top_button_overhang)) - self.grid_rowconfigure(3, weight=1) + if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"): + self.grid_rowconfigure(0, weight=0, minsize=self._apply_widget_scaling(self._outer_spacing)) + self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._outer_button_overhang)) + self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._outer_button_overhang)) + self.grid_rowconfigure(3, weight=1) + else: + self.grid_rowconfigure(0, weight=1) + self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._outer_button_overhang)) + self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._outer_button_overhang)) + self.grid_rowconfigure(3, weight=0, minsize=self._apply_widget_scaling(self._outer_spacing)) self.grid_columnconfigure(0, weight=1) def _set_grid_canvas(self): - self._canvas.grid(row=2, rowspan=2, column=0, columnspan=1, sticky="nsew") + if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"): + self._canvas.grid(row=2, rowspan=2, column=0, columnspan=1, sticky="nsew") + else: + self._canvas.grid(row=0, rowspan=2, column=0, columnspan=1, sticky="nsew") def _set_grid_segmented_button(self): - """ needs to be called for changes in corner_radius """ - self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="ns") + """ needs to be called for changes in corner_radius, anchor """ - def _set_grid_tab_by_name(self, name: str): + if self._anchor.lower() in ("center", "n", "s"): + self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="ns") + elif self._anchor.lower() in ("nw", "w", "sw"): + self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="nsw") + elif self._anchor.lower() in ("ne", "e", "se"): + self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="nse") + + def _set_grid_current_tab(self): """ needs to be called for changes in corner_radius, border_width """ - self._tab_dict[name].grid(row=3, column=0, sticky="nsew", - padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)), - pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width))) + if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"): + self._tab_dict[self._current_name].grid(row=3, column=0, sticky="nsew", + padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)), + pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width))) + else: + self._tab_dict[self._current_name].grid(row=0, column=0, sticky="nsew", + padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)), + pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width))) def _grid_forget_all_tabs(self, exclude_name=None): for name, frame in self._tab_dict.items(): @@ -180,9 +200,8 @@ class CTkTabview(CTkBaseClass): new_tab = CTkFrame(self, height=0, width=0, - fg_color=self._fg_color, border_width=0, - corner_radius=self._corner_radius) + corner_radius=0) return new_tab def _draw(self, no_color_updates: bool = False): @@ -192,7 +211,7 @@ class CTkTabview(CTkBaseClass): return 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._top_spacing - self._top_button_overhang), + self._apply_widget_scaling(self._current_height - self._outer_spacing - self._outer_button_overhang), self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(self._border_width)) @@ -201,10 +220,16 @@ class CTkTabview(CTkBaseClass): self._canvas.itemconfig("inner_parts", fill=self._apply_appearance_mode(self._bg_color), outline=self._apply_appearance_mode(self._bg_color)) + for tab in self._tab_dict.values(): + tab.configure(fg_color=self._apply_appearance_mode(self._bg_color), + bg_color=self._apply_appearance_mode(self._bg_color)) else: self._canvas.itemconfig("inner_parts", fill=self._apply_appearance_mode(self._fg_color), outline=self._apply_appearance_mode(self._fg_color)) + for tab in self._tab_dict.values(): + tab.configure(fg_color=self._apply_appearance_mode(self._fg_color), + bg_color=self._apply_appearance_mode(self._fg_color)) self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._border_color), @@ -215,13 +240,17 @@ class CTkTabview(CTkBaseClass): def configure(self, require_redraw=False, **kwargs): if "corner_radius" in kwargs: self._corner_radius = kwargs.pop("corner_radius") - require_redraw = True + self._set_grid_segmented_button() + self._set_grid_current_tab() + self._set_grid_canvas() + self._configure_segmented_button_background_corners() + self._segmented_button.configure(corner_radius=self._corner_radius) if "border_width" in kwargs: self._border_width = kwargs.pop("border_width") require_redraw = True - if "fg_color" in kwargs: self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True) + self._configure_segmented_button_background_corners() require_redraw = True if "border_color" in kwargs: self._border_color = self._check_color_type(kwargs.pop("border_color")) @@ -243,6 +272,10 @@ class CTkTabview(CTkBaseClass): if "command" in kwargs: self._command = kwargs.pop("command") + if "anchor" in kwargs: + self._anchor = kwargs.pop("anchor") + self._configure_grid() + self._set_grid_segmented_button() if "state" in kwargs: self._segmented_button.configure(state=kwargs.pop("state")) @@ -275,6 +308,8 @@ class CTkTabview(CTkBaseClass): elif attribute_name == "command": return self._command + elif attribute_name == "anchor": + return self._anchor elif attribute_name == "state": return self._segmented_button.cget(attribute_name) @@ -297,17 +332,16 @@ class CTkTabview(CTkBaseClass): if len(self._tab_dict) == 0: self._set_grid_segmented_button() - self._name_list.insert(index, name) + self._name_list.append(name) self._tab_dict[name] = self._create_tab() self._segmented_button.insert(index, name) - self._configure_tab_background_corners_by_name(name) # if created tab is only tab select this tab if len(self._tab_dict) == 1: self._current_name = name self._segmented_button.set(self._current_name) self._grid_forget_all_tabs() - self._set_grid_tab_by_name(self._current_name) + self._set_grid_current_tab() return self._tab_dict[name] else: @@ -317,6 +351,10 @@ class CTkTabview(CTkBaseClass): """ appends new tab with given name """ return self.insert(len(self._tab_dict), name) + def index(self, name) -> int: + """ get index of tab with given name """ + return self._segmented_button.index(name) + def move(self, new_index: int, name: str): if 0 <= new_index < len(self._name_list): if name in self._tab_dict: @@ -326,6 +364,22 @@ class CTkTabview(CTkBaseClass): else: raise ValueError(f"CTkTabview new_index {new_index} not in range of name list with len {len(self._name_list)}") + def rename(self, old_name: str, new_name: str): + if new_name in self._name_list: + raise ValueError(f"new_name '{new_name}' already exists") + + # segmented button + old_index = self._segmented_button.index(old_name) + self._segmented_button.delete(old_name) + self._segmented_button.insert(old_index, new_name) + + # name list + self._name_list.remove(old_name) + self._name_list.append(new_name) + + # tab dictionary + self._tab_dict[new_name] = self._tab_dict.pop(old_name) + def delete(self, name: str): """ delete tab by name """ @@ -345,7 +399,7 @@ class CTkTabview(CTkBaseClass): self._current_name = self._name_list[0] self._segmented_button.set(self._current_name) self._grid_forget_all_tabs() - self._set_grid_tab_by_name(self._current_name) + self._set_grid_current_tab() # more tabs are left else: @@ -361,7 +415,7 @@ class CTkTabview(CTkBaseClass): if name in self._tab_dict: self._current_name = name self._segmented_button.set(name) - self._set_grid_tab_by_name(name) + self._set_grid_current_tab() self.after(100, lambda: self._grid_forget_all_tabs(exclude_name=name)) else: raise ValueError(f"CTkTabview has no tab named '{name}'") diff --git a/examples/simple_example.py b/examples/simple_example.py index 6999cc0..485caa4 100644 --- a/examples/simple_example.py +++ b/examples/simple_example.py @@ -63,9 +63,22 @@ text_1.insert("0.0", "CTkTextbox\n\n\n\n") segmented_button_1 = customtkinter.CTkSegmentedButton(master=frame_1, values=["CTkSegmentedButton", "Value 2"]) segmented_button_1.pack(pady=10, padx=10) -tabview_1 = customtkinter.CTkTabview(master=frame_1, width=200, height=70) +tabview_1 = customtkinter.CTkTabview(master=frame_1, width=300, height=120, fg_color="transparent", corner_radius=4, anchor="sw") tabview_1.pack(pady=10, padx=10) -tabview_1.add("CTkTabview") -tabview_1.add("Tab 2") +tabview_1.add("1") +tabview_1.add("2") +tabview_1.add("3") + +b1 = customtkinter.CTkButton(tabview_1.tab("1")) +b1.pack() +b2 = customtkinter.CTkButton(tabview_1.tab("2")) +b2.pack() + +tabview_1.move(1, "3") +tabview_1.rename("3", "42") +print(tabview_1.index("42")) + +tabview_1.configure(fg_color='transparent', corner_radius=10, border_width=5) +tabview_1.configure(fg_color='green', corner_radius=10, border_width=5) app.mainloop() diff --git a/test/manual_integration_tests/test_images/eye-password-hide.png b/test/manual_integration_tests/test_images/eye-password-hide.png new file mode 100644 index 0000000000000000000000000000000000000000..f1890de0005ba133b8f1c41a1bdec5f3902e6a0e GIT binary patch literal 3356 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 sCqm&{YX4=(H~*iX|ChbXqnJ*wc?Tat