Skip to content

Commit

Permalink
added anchor to tabview TomSchimansky#1766, fixed tabview fg_color up…
Browse files Browse the repository at this point in the history
…date TomSchimansky#1803, added index method to tabview and segmented button, added rename method to tabview TomSchimansky#1192, fixed license in Readme.md
  • Loading branch information
TomSchimansky committed Jul 27, 2023
1 parent f629e4c commit 16990a5
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

</div>
Expand Down
3 changes: 3 additions & 0 deletions customtkinter/windows/widgets/ctk_segmented_button.py
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,9 @@ def set(self, value: str, from_variable_callback: bool = False, from_button_call
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 != "":
Expand Down
124 changes: 89 additions & 35 deletions customtkinter/windows/widgets/ctk_tabview.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -41,6 +41,7 @@ def __init__(self,
text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,

command: Union[Callable, None] = None,
anchor: str = "center",
state: str = "normal",
**kwargs):

Expand All @@ -65,12 +66,13 @@ def __init__(self,
# 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,
Expand Down Expand Up @@ -99,9 +101,9 @@ def __init__(self,
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()
Expand All @@ -124,52 +126,70 @@ def _set_scaling(self, *args, **kwargs):
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)

def _set_dimensions(self, width=None, height=None):
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 """

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_tab_by_name(self, name: str):
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():
Expand All @@ -180,9 +200,8 @@ def _create_tab(self) -> CTkFrame:
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):
Expand All @@ -192,7 +211,7 @@ def _draw(self, no_color_updates: bool = False):
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))

Expand All @@ -201,10 +220,16 @@ def _draw(self, no_color_updates: bool = False):
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),
Expand All @@ -215,13 +240,17 @@ def _draw(self, no_color_updates: bool = False):
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"))
Expand All @@ -243,6 +272,10 @@ def configure(self, require_redraw=False, **kwargs):

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"))

Expand Down Expand Up @@ -275,6 +308,8 @@ def cget(self, attribute_name: str):

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)

Expand All @@ -297,17 +332,16 @@ def insert(self, index: int, name: str) -> CTkFrame:
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:
Expand All @@ -317,6 +351,10 @@ def add(self, name: str) -> CTkFrame:
""" 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:
Expand All @@ -326,6 +364,22 @@ def move(self, new_index: int, name: str):
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 """

Expand All @@ -345,7 +399,7 @@ def delete(self, name: str):
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:
Expand All @@ -361,7 +415,7 @@ def set(self, name: str):
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}'")
Expand Down
19 changes: 16 additions & 3 deletions examples/simple_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,22 @@ def slider_callback(value):
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()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 16990a5

Please sign in to comment.