From 6ba384eb0b43e7e5f565c30ecdaed4064222d138 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Fri, 14 Oct 2022 01:15:35 +0200 Subject: [PATCH] finished basic tabview mechanics --- customtkinter/draw_engine.py | 2 +- customtkinter/widgets/ctk_segmented_button.py | 20 +++- customtkinter/widgets/ctk_tabview.py | 109 ++++++++++++++---- test/manual_integration_tests/test_tabview.py | 25 +++- 4 files changed, 122 insertions(+), 34 deletions(-) diff --git a/customtkinter/draw_engine.py b/customtkinter/draw_engine.py index 0528b38..0a5c7fb 100644 --- a/customtkinter/draw_engine.py +++ b/customtkinter/draw_engine.py @@ -61,7 +61,7 @@ class DrawEngine: else: return user_corner_radius - def draw_background_corners(self, width: Union[float, int], height: Union[float, int]): + def draw_background_corners(self, width: Union[float, int], height: Union[float, int], ): if self._round_width_to_even_numbers: width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only if self._round_height_to_even_numbers: diff --git a/customtkinter/widgets/ctk_segmented_button.py b/customtkinter/widgets/ctk_segmented_button.py index 7cda0d8..a2f1ff1 100644 --- a/customtkinter/widgets/ctk_segmented_button.py +++ b/customtkinter/widgets/ctk_segmented_button.py @@ -325,10 +325,6 @@ class CTkSegmentedButton(CTkFrame): return super().cget(attribute_name) def set(self, value: str, from_variable_callback: bool = False, from_button_callback: bool = False): - if from_button_callback: - if self._command is not None: - self._command(self._current_value) - if value == self._current_value: return elif value in self._buttons_dict: @@ -348,6 +344,10 @@ class CTkSegmentedButton(CTkFrame): self._variable.set(value) self._variable_callback_blocked = False + if from_button_callback: + if self._command is not None: + self._command(self._current_value) + def get(self) -> str: return self._current_value @@ -376,8 +376,16 @@ class CTkSegmentedButton(CTkFrame): index_to_remove = self._get_index_by_value(value) self._value_list.pop(index_to_remove) - if index_to_remove <= len(self._buttons_dict) - 1: - self._configure_button_corners_for_index(index_to_remove) + # removed index was outer right element + if index_to_remove == len(self._buttons_dict) and len(self._buttons_dict) > 0: + self._configure_button_corners_for_index(index_to_remove - 1) + + # removed index was outer left element + if index_to_remove == 0 and len(self._buttons_dict) > 0: + self._configure_button_corners_for_index(0) + + #if index_to_remove <= len(self._buttons_dict) - 1: + # self._configure_button_corners_for_index(index_to_remove) self._create_button_grid() else: diff --git a/customtkinter/widgets/ctk_tabview.py b/customtkinter/widgets/ctk_tabview.py index 6e15edd..2b402fe 100644 --- a/customtkinter/widgets/ctk_tabview.py +++ b/customtkinter/widgets/ctk_tabview.py @@ -1,4 +1,4 @@ -from typing import Union, Tuple, Dict, List +from typing import Union, Tuple, Dict, List, Callable from ..theme_manager import ThemeManager from .ctk_frame import CTkFrame @@ -16,7 +16,7 @@ class CTkTabview(CTkBaseClass): _top_spacing = 10 # px on top of the buttons _top_button_overhang = 8 # px - _button_height = 10 + _button_height = 26 def __init__(self, master: any = None, @@ -38,6 +38,8 @@ class CTkTabview(CTkBaseClass): text_color: Union[str, Tuple[str, str]] = "default_theme", text_color_disabled: Union[str, Tuple[str, str]] = "default_theme", + command: Callable = None, + state: str = "normal", **kwargs): # transfer some functionality to CTkFrame @@ -80,7 +82,9 @@ class CTkTabview(CTkBaseClass): text_color=text_color, text_color_disabled=text_color_disabled, corner_radius=corner_radius, - border_width=self._apply_widget_scaling(3)) + border_width=self._apply_widget_scaling(3), + command=self._segmented_button_callback, + state=state) self._configure_segmented_button_background_corners() self._configure_grid() self._set_grid_canvas() @@ -89,8 +93,18 @@ class CTkTabview(CTkBaseClass): self._name_list: List[str] = [] # list of unique tab names in order of tabs self._current_name: str = "" + self._command = command + super().bind('', self._update_dimensions_event) + def _segmented_button_callback(self, selected_name): + self._current_name = selected_name + self._grid_forget_all_tabs() + self._set_grid_tab_by_name(self._current_name) + + if self._command is not None: + self._command() + def winfo_children(self) -> List[any]: """ winfo_children of CTkTabview without canvas and segmented button widgets, @@ -130,13 +144,15 @@ class CTkTabview(CTkBaseClass): def _configure_tab_background_corners_by_name(self, name: str): """ needs to be called for changes in fg_color, bg_color, border_width """ - if self._border_width == 0: - if self._fg_color is not None: - self._tab_dict[name].configure(background_corner_colors=(self._fg_color, self._fg_color, self._bg_color, self._bg_color)) - else: - self._tab_dict[name].configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color)) - else: - self._tab_dict[name].configure(background_corner_colors=None) + # if self._border_width == 0: + # if self._fg_color is not None: + # self._tab_dict[name].configure(background_corner_colors=(self._fg_color, self._fg_color, self._bg_color, self._bg_color)) + # else: + # self._tab_dict[name].configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color)) + # else: + # self._tab_dict[name].configure(background_corner_colors=None) + + self._tab_dict[name].configure(background_corner_colors=None) def _configure_grid(self): """ create 3 x 4 grid system """ @@ -157,12 +173,13 @@ class CTkTabview(CTkBaseClass): def _set_grid_tab_by_name(self, name: str): """ needs to be called for changes in corner_radius, border_width """ - if self._border_width == 0: - self._tab_dict[name].grid(row=3, column=0, sticky="nsew") - else: - 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))) + 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))) + + def _grid_forget_all_tabs(self): + for frame in self._tab_dict.values(): + frame.grid_forget() def _create_tab(self) -> CTkFrame: new_tab = CTkFrame(self, @@ -203,7 +220,7 @@ class CTkTabview(CTkBaseClass): else: raise ValueError(f"CTkTabview has no tab named '{name}'") - def insert(self, index: int, name: str): + def insert(self, index: int, name: str) -> CTkFrame: """ creates new tab with given name at position index """ if name not in self._tab_dict: @@ -214,13 +231,63 @@ class CTkTabview(CTkBaseClass): self._name_list.insert(index, 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) + + return self._tab_dict[name] else: raise ValueError(f"CTkTabview already has tab named '{name}'") - def add(self, name: str): + def add(self, name: str) -> CTkFrame: """ appends new tab with given name """ - self.insert(len(self._tab_dict), name) + return self.insert(len(self._tab_dict), name) def delete(self, name: str): - """ deletes tab with given name """ - return + """ delete tab by name """ + + if name in self._tab_dict: + self._name_list.remove(name) + self._tab_dict[name].grid_forget() + self._tab_dict.pop(name) + self._segmented_button.delete(name) + + # set current_name to '' and remove segmented button if no tab is left + if len(self._name_list) == 0: + self._current_name = "" + self._segmented_button.grid_forget() + + # if only one tab left, select this tab + elif len(self._name_list) == 1: + 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) + + # more tabs are left + else: + # if current_name is deleted tab, select first tab at position 0 + if self._current_name == name: + self.set(self._name_list[0]) + else: + raise ValueError(f"CTkTabview has no tab named '{name}'") + + def set(self, name: str): + """ select tab by name """ + + if name in self._tab_dict: + self._current_name = name + self._segmented_button.set(name) + self._grid_forget_all_tabs() + self._set_grid_tab_by_name(name) + else: + raise ValueError(f"CTkTabview has no tab named '{name}'") + + def get(self) -> str: + """ returns name of selected tab, returns empty string if no tab selected """ + return self._current_name diff --git a/test/manual_integration_tests/test_tabview.py b/test/manual_integration_tests/test_tabview.py index 8afabf2..e168342 100644 --- a/test/manual_integration_tests/test_tabview.py +++ b/test/manual_integration_tests/test_tabview.py @@ -2,13 +2,26 @@ import customtkinter app = customtkinter.CTk() -tabview_1 = customtkinter._CTkTabview(app) +tabview_1 = customtkinter._CTkTabview(app, state="disabled") tabview_1.pack(padx=20, pady=20) -tabview_1.add("tab 1") -tabview_1.insert(0, "tab 0 g |รŸ$ยง ๐Ÿ˜€") -app.update() +tab_1 = tabview_1.add("tab 2") +tabview_1.insert(0, "tab 1") + +tabview_1.add("tab 42") +tabview_1.set("tab 42") +tabview_1.delete("tab 42") +tabview_1.insert(0, "tab 42") +tabview_1.delete("tab 42") +tabview_1.insert(1, "tab 42") +tabview_1.delete("tab 42") + +#b1 = customtkinter.CTkButton(master=tab_1, text="button tab 1") +#b1.pack(pady=20) +b2 = customtkinter.CTkButton(master=tabview_1.tab("tab 2"), text="button tab 2") +b2.pack() + +tabview_1.tab("tab 2").configure(fg_color="red") +# tabview_1.delete("tab 1") -tabview_1._segmented_button._buttons_dict["tab 0 g |รŸ$ยง ๐Ÿ˜€"]._text_label.configure(padx=0, pady=0, bd=1) -tabview_1._segmented_button._buttons_dict["tab 0 g |รŸ$ยง ๐Ÿ˜€"]._text_label.configure(bg="red") app.mainloop()