mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
finished CTkSegmentedButton, created test_segmented_button.py
This commit is contained in:
parent
1696016d54
commit
327957e97a
@ -65,6 +65,7 @@ from .widgets.ctk_combobox import CTkComboBox
|
|||||||
from .widgets.ctk_scrollbar import CTkScrollbar
|
from .widgets.ctk_scrollbar import CTkScrollbar
|
||||||
from .widgets.ctk_textbox import CTkTextbox
|
from .widgets.ctk_textbox import CTkTextbox
|
||||||
from .widgets.ctk_tabview import CTkTabview as _CTkTabview
|
from .widgets.ctk_tabview import CTkTabview as _CTkTabview
|
||||||
|
from .widgets.ctk_segmented_button import CTkSegmentedButton as _CTkSegmentedButton
|
||||||
|
|
||||||
# import windows
|
# import windows
|
||||||
from .windows.ctk_tk import CTk
|
from .windows.ctk_tk import CTk
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"button_hover": ["#36719F", "#144870"],
|
"button_hover": ["#36719F", "#144870"],
|
||||||
"button_border": ["#3E454A", "#949A9F"],
|
"button_border": ["#3E454A", "#949A9F"],
|
||||||
"checkbox_border": ["#3E454A", "#949A9F"],
|
"checkbox_border": ["#3E454A", "#949A9F"],
|
||||||
"checkmark": ["white", "gray90"],
|
"checkmark": ["#DCE4EE", "gray90"],
|
||||||
"entry": ["#F9F9FA", "#343638"],
|
"entry": ["#F9F9FA", "#343638"],
|
||||||
"entry_border": ["#979DA2", "#565B5E"],
|
"entry_border": ["#979DA2", "#565B5E"],
|
||||||
"entry_placeholder_text": ["gray52", "gray62"],
|
"entry_placeholder_text": ["gray52", "gray62"],
|
||||||
@ -15,7 +15,8 @@
|
|||||||
"label": [null, null],
|
"label": [null, null],
|
||||||
"text": ["gray10", "#DCE4EE"],
|
"text": ["gray10", "#DCE4EE"],
|
||||||
"text_disabled": ["gray60", "#777B80"],
|
"text_disabled": ["gray60", "#777B80"],
|
||||||
"text_button_disabled": ["gray40", "gray74"],
|
"text_button": ["#DCE4EE", "#DCE4EE"],
|
||||||
|
"text_button_disabled": ["gray74", "gray74"],
|
||||||
"progressbar": ["#939BA2", "#4A4D50"],
|
"progressbar": ["#939BA2", "#4A4D50"],
|
||||||
"progressbar_progress": ["#3B8ED0", "#1F6AA5"],
|
"progressbar_progress": ["#3B8ED0", "#1F6AA5"],
|
||||||
"progressbar_border": ["gray", "gray"],
|
"progressbar_border": ["gray", "gray"],
|
||||||
@ -36,6 +37,9 @@
|
|||||||
"dropdown_text": ["gray10", "#DCE4EE"],
|
"dropdown_text": ["gray10", "#DCE4EE"],
|
||||||
"scrollbar_button": ["gray55", "gray41"],
|
"scrollbar_button": ["gray55", "gray41"],
|
||||||
"scrollbar_button_hover": ["gray40", "gray53"],
|
"scrollbar_button_hover": ["gray40", "gray53"],
|
||||||
|
"segmented_button": ["#979DA2", "#4A4D50"],
|
||||||
|
"segmented_button_unselected": ["#979DA2", "#4A4D50"],
|
||||||
|
"segmented_button_unselected_hover": ["gray70", "gray41"],
|
||||||
"tabview_button_frame": ["gray70", "gray35"],
|
"tabview_button_frame": ["gray70", "gray35"],
|
||||||
"tabview_button": ["gray60", "gray45"],
|
"tabview_button": ["gray60", "gray45"],
|
||||||
"tabview_button_hover": ["gray50", "gray55"]
|
"tabview_button_hover": ["gray50", "gray55"]
|
||||||
|
@ -30,6 +30,12 @@ class DrawEngine:
|
|||||||
|
|
||||||
def __init__(self, canvas: CTkCanvas):
|
def __init__(self, canvas: CTkCanvas):
|
||||||
self._canvas = canvas
|
self._canvas = canvas
|
||||||
|
self._round_width_to_even_numbers: bool = True
|
||||||
|
self._round_height_to_even_numbers: bool = True
|
||||||
|
|
||||||
|
def set_round_to_even_numbers(self, round_width_to_even_numbers: bool = True, round_height_to_even_numbers: bool = True):
|
||||||
|
self._round_width_to_even_numbers: bool = round_width_to_even_numbers
|
||||||
|
self._round_height_to_even_numbers: bool = round_height_to_even_numbers
|
||||||
|
|
||||||
def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]:
|
def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]:
|
||||||
# optimize for drawing with polygon shapes
|
# optimize for drawing with polygon shapes
|
||||||
@ -55,6 +61,38 @@ class DrawEngine:
|
|||||||
else:
|
else:
|
||||||
return user_corner_radius
|
return user_corner_radius
|
||||||
|
|
||||||
|
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:
|
||||||
|
height = math.floor(height / 2) * 2
|
||||||
|
|
||||||
|
requires_recoloring = False
|
||||||
|
|
||||||
|
if not self._canvas.find_withtag("background_corner_top_left"):
|
||||||
|
self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_top_left"), width=0)
|
||||||
|
requires_recoloring = True
|
||||||
|
if not self._canvas.find_withtag("background_corner_top_right"):
|
||||||
|
self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_top_right"), width=0)
|
||||||
|
requires_recoloring = True
|
||||||
|
if not self._canvas.find_withtag("background_corner_bottom_right"):
|
||||||
|
self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_bottom_right"), width=0)
|
||||||
|
requires_recoloring = True
|
||||||
|
if not self._canvas.find_withtag("background_corner_bottom_left"):
|
||||||
|
self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_bottom_left"), width=0)
|
||||||
|
requires_recoloring = True
|
||||||
|
|
||||||
|
mid_width, mid_height = round(width / 2), round(height / 2)
|
||||||
|
self._canvas.coords("background_corner_top_left", (0, 0, mid_width, mid_height))
|
||||||
|
self._canvas.coords("background_corner_top_right", (mid_width, 0, width, mid_height))
|
||||||
|
self._canvas.coords("background_corner_bottom_right", (mid_width, mid_height, width, height))
|
||||||
|
self._canvas.coords("background_corner_bottom_left", (0, mid_height, mid_width, height))
|
||||||
|
|
||||||
|
if requires_recoloring: # new parts were added -> manage z-order
|
||||||
|
self._canvas.tag_lower("background_parts")
|
||||||
|
|
||||||
|
return requires_recoloring
|
||||||
|
|
||||||
def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
|
def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
|
||||||
border_width: Union[float, int], overwrite_preferred_drawing_method: str = None) -> bool:
|
border_width: Union[float, int], overwrite_preferred_drawing_method: str = None) -> bool:
|
||||||
""" Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
|
""" Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
|
||||||
@ -62,11 +100,13 @@ class DrawEngine:
|
|||||||
|
|
||||||
returns bool if recoloring is necessary """
|
returns bool if recoloring is necessary """
|
||||||
|
|
||||||
width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
|
if self._round_width_to_even_numbers:
|
||||||
height = math.floor(height / 2) * 2
|
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:
|
||||||
|
height = math.floor(height / 2) * 2
|
||||||
corner_radius = round(corner_radius)
|
corner_radius = round(corner_radius)
|
||||||
|
|
||||||
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too large
|
||||||
corner_radius = min(width / 2, height / 2)
|
corner_radius = min(width / 2, height / 2)
|
||||||
|
|
||||||
border_width = round(border_width)
|
border_width = round(border_width)
|
||||||
@ -139,6 +179,7 @@ class DrawEngine:
|
|||||||
if requires_recoloring: # new parts were added -> manage z-order
|
if requires_recoloring: # new parts were added -> manage z-order
|
||||||
self._canvas.tag_lower("inner_parts")
|
self._canvas.tag_lower("inner_parts")
|
||||||
self._canvas.tag_lower("border_parts")
|
self._canvas.tag_lower("border_parts")
|
||||||
|
self._canvas.tag_lower("background_parts")
|
||||||
|
|
||||||
return requires_recoloring
|
return requires_recoloring
|
||||||
|
|
||||||
@ -277,6 +318,7 @@ class DrawEngine:
|
|||||||
if requires_recoloring: # new parts were added -> manage z-order
|
if requires_recoloring: # new parts were added -> manage z-order
|
||||||
self._canvas.tag_lower("inner_parts")
|
self._canvas.tag_lower("inner_parts")
|
||||||
self._canvas.tag_lower("border_parts")
|
self._canvas.tag_lower("border_parts")
|
||||||
|
self._canvas.tag_lower("background_parts")
|
||||||
|
|
||||||
return requires_recoloring
|
return requires_recoloring
|
||||||
|
|
||||||
@ -364,8 +406,10 @@ class DrawEngine:
|
|||||||
returns bool if recoloring is necessary """
|
returns bool if recoloring is necessary """
|
||||||
|
|
||||||
left_section_width = round(left_section_width)
|
left_section_width = round(left_section_width)
|
||||||
width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
|
if self._round_width_to_even_numbers:
|
||||||
height = math.floor(height / 2) * 2
|
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:
|
||||||
|
height = math.floor(height / 2) * 2
|
||||||
corner_radius = round(corner_radius)
|
corner_radius = round(corner_radius)
|
||||||
|
|
||||||
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
||||||
@ -478,6 +522,7 @@ class DrawEngine:
|
|||||||
if requires_recoloring: # new parts were added -> manage z-order
|
if requires_recoloring: # new parts were added -> manage z-order
|
||||||
self._canvas.tag_lower("inner_parts")
|
self._canvas.tag_lower("inner_parts")
|
||||||
self._canvas.tag_lower("border_parts")
|
self._canvas.tag_lower("border_parts")
|
||||||
|
self._canvas.tag_lower("background_parts")
|
||||||
|
|
||||||
return requires_recoloring
|
return requires_recoloring
|
||||||
|
|
||||||
@ -641,6 +686,7 @@ class DrawEngine:
|
|||||||
if requires_recoloring: # new parts were added -> manage z-order
|
if requires_recoloring: # new parts were added -> manage z-order
|
||||||
self._canvas.tag_lower("inner_parts")
|
self._canvas.tag_lower("inner_parts")
|
||||||
self._canvas.tag_lower("border_parts")
|
self._canvas.tag_lower("border_parts")
|
||||||
|
self._canvas.tag_lower("background_parts")
|
||||||
|
|
||||||
return requires_recoloring
|
return requires_recoloring
|
||||||
|
|
||||||
@ -652,8 +698,10 @@ class DrawEngine:
|
|||||||
|
|
||||||
returns bool if recoloring is necessary """
|
returns bool if recoloring is necessary """
|
||||||
|
|
||||||
width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
|
if self._round_width_to_even_numbers:
|
||||||
height = math.floor(height / 2) * 2
|
width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
|
||||||
|
if self._round_height_to_even_numbers:
|
||||||
|
height = math.floor(height / 2) * 2
|
||||||
|
|
||||||
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
||||||
corner_radius = min(width / 2, height / 2)
|
corner_radius = min(width / 2, height / 2)
|
||||||
@ -824,8 +872,10 @@ class DrawEngine:
|
|||||||
border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int],
|
border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int],
|
||||||
slider_value: float, orientation: str) -> bool:
|
slider_value: float, orientation: str) -> bool:
|
||||||
|
|
||||||
width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
|
if self._round_width_to_even_numbers:
|
||||||
height = math.floor(height / 2) * 2
|
width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
|
||||||
|
if self._round_height_to_even_numbers:
|
||||||
|
height = math.floor(height / 2) * 2
|
||||||
|
|
||||||
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
||||||
corner_radius = min(width / 2, height / 2)
|
corner_radius = min(width / 2, height / 2)
|
||||||
@ -980,8 +1030,11 @@ class DrawEngine:
|
|||||||
|
|
||||||
def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
|
def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
|
||||||
border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool:
|
border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool:
|
||||||
width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
|
|
||||||
height = math.floor(height / 2) * 2
|
if self._round_width_to_even_numbers:
|
||||||
|
width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
|
||||||
|
if self._round_height_to_even_numbers:
|
||||||
|
height = math.floor(height / 2) * 2
|
||||||
|
|
||||||
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
|
||||||
corner_radius = min(width / 2, height / 2)
|
corner_radius = min(width / 2, height / 2)
|
||||||
|
@ -29,6 +29,10 @@ class CTkButton(CTkBaseClass):
|
|||||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
|
|
||||||
|
background_corner_colors: Tuple[Union[str, Tuple[str, str]]] = None,
|
||||||
|
round_width_to_even_numbers: bool = True,
|
||||||
|
round_height_to_even_numbers: bool = True,
|
||||||
|
|
||||||
text: str = "CTkButton",
|
text: str = "CTkButton",
|
||||||
font: any = "default_theme",
|
font: any = "default_theme",
|
||||||
textvariable: tkinter.Variable = None,
|
textvariable: tkinter.Variable = None,
|
||||||
@ -46,12 +50,17 @@ class CTkButton(CTkBaseClass):
|
|||||||
self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
self._fg_color = ThemeManager.theme["color"]["button"] if fg_color == "default_theme" else fg_color
|
||||||
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
self._hover_color = ThemeManager.theme["color"]["button_hover"] if hover_color == "default_theme" else hover_color
|
||||||
self._border_color = ThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color
|
self._border_color = ThemeManager.theme["color"]["button_border"] if border_color == "default_theme" else border_color
|
||||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
self._text_color = ThemeManager.theme["color"]["text_button"] if text_color == "default_theme" else text_color
|
||||||
self._text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
self._text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||||
|
self._background_corner_colors = background_corner_colors # rendering options for DrawEngine
|
||||||
|
|
||||||
# shape
|
# shape
|
||||||
self._corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
self._corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||||
self._border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
|
self._border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
|
||||||
|
self._round_width_to_even_numbers = round_width_to_even_numbers # rendering options for DrawEngine
|
||||||
|
self._round_height_to_even_numbers = round_height_to_even_numbers # rendering options for DrawEngine
|
||||||
|
|
||||||
|
self._corner_radius = min(self._corner_radius, round(self._current_height/2))
|
||||||
|
|
||||||
# text, font, image
|
# text, font, image
|
||||||
self._image = image
|
self._image = image
|
||||||
@ -81,6 +90,7 @@ class CTkButton(CTkBaseClass):
|
|||||||
height=self._apply_widget_scaling(self._desired_height))
|
height=self._apply_widget_scaling(self._desired_height))
|
||||||
self._canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
|
self._canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
|
||||||
self._draw_engine = DrawEngine(self._canvas)
|
self._draw_engine = DrawEngine(self._canvas)
|
||||||
|
self._draw_engine.set_round_to_even_numbers(self._round_width_to_even_numbers, self._round_height_to_even_numbers) # rendering options
|
||||||
|
|
||||||
# canvas event bindings
|
# canvas event bindings
|
||||||
self._canvas.bind("<Enter>", self._on_enter)
|
self._canvas.bind("<Enter>", self._on_enter)
|
||||||
@ -115,6 +125,16 @@ class CTkButton(CTkBaseClass):
|
|||||||
self._draw()
|
self._draw()
|
||||||
|
|
||||||
def _draw(self, no_color_updates=False):
|
def _draw(self, no_color_updates=False):
|
||||||
|
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))
|
||||||
|
self._canvas.itemconfig("background_corner_top_left", fill=ThemeManager.single_color(self._background_corner_colors[0], self._appearance_mode))
|
||||||
|
self._canvas.itemconfig("background_corner_top_right", fill=ThemeManager.single_color(self._background_corner_colors[1], self._appearance_mode))
|
||||||
|
self._canvas.itemconfig("background_corner_bottom_right", fill=ThemeManager.single_color(self._background_corner_colors[2], self._appearance_mode))
|
||||||
|
self._canvas.itemconfig("background_corner_bottom_left", fill=ThemeManager.single_color(self._background_corner_colors[3], self._appearance_mode))
|
||||||
|
else:
|
||||||
|
self._canvas.delete("background_parts")
|
||||||
|
|
||||||
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
|
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._current_height),
|
||||||
self._apply_widget_scaling(self._corner_radius),
|
self._apply_widget_scaling(self._corner_radius),
|
||||||
@ -267,6 +287,10 @@ class CTkButton(CTkBaseClass):
|
|||||||
self._corner_radius = kwargs.pop("corner_radius")
|
self._corner_radius = kwargs.pop("corner_radius")
|
||||||
require_redraw = True
|
require_redraw = True
|
||||||
|
|
||||||
|
if "border_width" in kwargs:
|
||||||
|
self._border_width = kwargs.pop("border_width")
|
||||||
|
require_redraw = True
|
||||||
|
|
||||||
if "compound" in kwargs:
|
if "compound" in kwargs:
|
||||||
self._compound = kwargs.pop("compound")
|
self._compound = kwargs.pop("compound")
|
||||||
require_redraw = True
|
require_redraw = True
|
||||||
@ -301,6 +325,10 @@ class CTkButton(CTkBaseClass):
|
|||||||
if "height" in kwargs:
|
if "height" in kwargs:
|
||||||
self._set_dimensions(height=kwargs.pop("height"))
|
self._set_dimensions(height=kwargs.pop("height"))
|
||||||
|
|
||||||
|
if "background_corner_colors" in kwargs:
|
||||||
|
self._background_corner_colors = kwargs.pop("background_corner_colors")
|
||||||
|
require_redraw = True
|
||||||
|
|
||||||
super().configure(require_redraw=require_redraw, **kwargs)
|
super().configure(require_redraw=require_redraw, **kwargs)
|
||||||
|
|
||||||
def cget(self, attribute_name: str) -> any:
|
def cget(self, attribute_name: str) -> any:
|
||||||
@ -319,6 +347,8 @@ class CTkButton(CTkBaseClass):
|
|||||||
return self._text_color
|
return self._text_color
|
||||||
elif attribute_name == "text_color_disabled":
|
elif attribute_name == "text_color_disabled":
|
||||||
return self._text_color_disabled
|
return self._text_color_disabled
|
||||||
|
elif attribute_name == "background_corner_colors":
|
||||||
|
return self._background_corner_colors
|
||||||
|
|
||||||
elif attribute_name == "text":
|
elif attribute_name == "text":
|
||||||
return self._text
|
return self._text
|
||||||
|
@ -60,7 +60,7 @@ class CTkComboBox(CTkBaseClass):
|
|||||||
|
|
||||||
# text and font
|
# text and font
|
||||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||||
self._text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
self._text_color_disabled = ThemeManager.theme["color"]["text_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||||
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
|
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
|
||||||
|
|
||||||
# callback and hover functionality
|
# callback and hover functionality
|
||||||
|
@ -128,6 +128,12 @@ class CTkFrame(CTkBaseClass):
|
|||||||
if isinstance(child, CTkBaseClass):
|
if isinstance(child, CTkBaseClass):
|
||||||
child.configure(bg_color=self._fg_color)
|
child.configure(bg_color=self._fg_color)
|
||||||
|
|
||||||
|
# only workaround, to enable one layer of passing new bg_color for children with fg_color=None,
|
||||||
|
# but needs to be abstracted to n-layers somehow
|
||||||
|
if isinstance(child, CTkFrame) and child.cget("fg_color") is None:
|
||||||
|
for childrens_child in child.winfo_children():
|
||||||
|
childrens_child.configure(bg_color=self._fg_color)
|
||||||
|
|
||||||
if "border_color" in kwargs:
|
if "border_color" in kwargs:
|
||||||
self._border_color = kwargs.pop("border_color")
|
self._border_color = kwargs.pop("border_color")
|
||||||
require_redraw = True
|
require_redraw = True
|
||||||
|
@ -70,7 +70,7 @@ class CTkLabel(CTkBaseClass):
|
|||||||
|
|
||||||
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
||||||
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
||||||
padx=min(self._apply_widget_scaling(self._corner_radius), round(self._current_height/2)))
|
padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height/2))))
|
||||||
|
|
||||||
self._check_kwargs_empty(kwargs, raise_error=True)
|
self._check_kwargs_empty(kwargs, raise_error=True)
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class CTkLabel(CTkBaseClass):
|
|||||||
|
|
||||||
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
||||||
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
||||||
padx=min(self._apply_widget_scaling(self._corner_radius), round(self._current_height/2)))
|
padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height/2))))
|
||||||
|
|
||||||
self._draw()
|
self._draw()
|
||||||
|
|
||||||
@ -125,7 +125,7 @@ class CTkLabel(CTkBaseClass):
|
|||||||
self._anchor = kwargs.pop("anchor")
|
self._anchor = kwargs.pop("anchor")
|
||||||
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
||||||
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
||||||
padx=min(self._apply_widget_scaling(self._corner_radius), round(self._current_height/2)))
|
padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height/2))))
|
||||||
|
|
||||||
if "text" in kwargs:
|
if "text" in kwargs:
|
||||||
self._text = kwargs.pop("text")
|
self._text = kwargs.pop("text")
|
||||||
@ -153,7 +153,7 @@ class CTkLabel(CTkBaseClass):
|
|||||||
self._corner_radius = kwargs.pop("corner_radius")
|
self._corner_radius = kwargs.pop("corner_radius")
|
||||||
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
|
||||||
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
self._text_label.grid(row=0, column=0, sticky=text_label_grid_sticky,
|
||||||
padx=min(self._apply_widget_scaling(self._corner_radius), round(self._current_height/2)))
|
padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height/2))))
|
||||||
require_redraw = True
|
require_redraw = True
|
||||||
|
|
||||||
self._text_label.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_label_attributes)) # configure tkinter.Label
|
self._text_label.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_label_attributes)) # configure tkinter.Label
|
||||||
|
@ -54,7 +54,7 @@ class CTkOptionMenu(CTkBaseClass):
|
|||||||
self._corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
self._corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||||
|
|
||||||
# text and font
|
# text and font
|
||||||
self._text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
self._text_color = ThemeManager.theme["color"]["text_button"] if text_color == "default_theme" else text_color
|
||||||
self._text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
self._text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||||
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
|
self._font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if font == "default_theme" else font
|
||||||
self._dropdown_text_font = dropdown_text_font
|
self._dropdown_text_font = dropdown_text_font
|
||||||
@ -156,6 +156,12 @@ class CTkOptionMenu(CTkBaseClass):
|
|||||||
height=self._apply_widget_scaling(self._desired_height))
|
height=self._apply_widget_scaling(self._desired_height))
|
||||||
self._draw()
|
self._draw()
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
if self._variable is not None: # remove old callback
|
||||||
|
self._variable.trace_remove("write", self._variable_callback_name)
|
||||||
|
|
||||||
|
super().destroy()
|
||||||
|
|
||||||
def _draw(self, no_color_updates=False):
|
def _draw(self, no_color_updates=False):
|
||||||
left_section_width = self._current_width - self._current_height
|
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),
|
requires_recoloring = self._draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width),
|
||||||
|
@ -1,19 +1,332 @@
|
|||||||
import tkinter
|
import tkinter
|
||||||
from typing import Union, Tuple
|
from typing import Union, Tuple, List, Dict
|
||||||
|
|
||||||
from .widget_base_class import CTkBaseClass
|
from ..theme_manager import ThemeManager
|
||||||
from .ctk_button import CTkButton
|
from .ctk_button import CTkButton
|
||||||
|
from .ctk_frame import CTkFrame
|
||||||
|
|
||||||
|
|
||||||
class CTkSegmentedButton(CTkBaseClass):
|
class CTkSegmentedButton(CTkFrame):
|
||||||
|
"""
|
||||||
|
Segmented button with corner radius, border width, variable support.
|
||||||
|
For detailed information check out the documentation.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
master: any = None,
|
master: any = None,
|
||||||
|
width: int = 140,
|
||||||
|
height: int = 28,
|
||||||
|
corner_radius: Union[int, str] = "default_theme",
|
||||||
|
border_width: Union[int, str] = 3,
|
||||||
|
|
||||||
bg_color: Union[str, Tuple[str, str], None] = None,
|
bg_color: Union[str, Tuple[str, str], None] = None,
|
||||||
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
|
fg_color: Union[str, Tuple[str, str], None] = "default_theme",
|
||||||
hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
selected_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
|
selected_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
|
unselected_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
|
unselected_hover_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
text_color: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
text_color_disabled: Union[str, Tuple[str, str]] = "default_theme",
|
||||||
|
|
||||||
values: list = None):
|
values: list = None,
|
||||||
super().__init__(master=master, )
|
variable: tkinter.Variable = None,
|
||||||
|
dynamic_resizing: bool = True,
|
||||||
|
**kwargs):
|
||||||
|
|
||||||
|
super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
|
||||||
|
|
||||||
|
self._sb_fg_color = ThemeManager.theme["color"]["segmented_button"] if fg_color == "default_theme" else fg_color
|
||||||
|
|
||||||
|
self._sb_selected_color = ThemeManager.theme["color"]["button"] if selected_color == "default_theme" else selected_color
|
||||||
|
self._sb_selected_hover_color = ThemeManager.theme["color"]["button_hover"] if selected_hover_color == "default_theme" else selected_hover_color
|
||||||
|
|
||||||
|
self._sb_unselected_color = ThemeManager.theme["color"]["segmented_button_unselected"] if unselected_color == "default_theme" else unselected_color
|
||||||
|
self._sb_unselected_hover_color = ThemeManager.theme["color"]["segmented_button_unselected_hover"] if unselected_hover_color == "default_theme" else unselected_hover_color
|
||||||
|
|
||||||
|
self._sb_text_color = ThemeManager.theme["color"]["text_button"] if text_color == "default_theme" else text_color
|
||||||
|
self._sb_text_color_disabled = ThemeManager.theme["color"]["text_button_disabled"] if text_color_disabled == "default_theme" else text_color_disabled
|
||||||
|
|
||||||
|
self._sb_corner_radius = ThemeManager.theme["shape"]["button_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||||
|
self._sb_border_width = ThemeManager.theme["shape"]["button_border_width"] if border_width == "default_theme" else border_width
|
||||||
|
|
||||||
|
self._buttons_dict: Dict[str, CTkButton] = {} # mapped from value to button object
|
||||||
|
if values is None:
|
||||||
|
self._value_list: List[str] = ["CTkSegmentedButton"]
|
||||||
|
elif len(values) == 0:
|
||||||
|
raise ValueError("values of CTkSegmentedButton can not be empty")
|
||||||
|
else:
|
||||||
|
self._value_list: List[str] = values # Values ordered like buttons rendered on widget
|
||||||
|
|
||||||
|
self._dynamic_resizing = dynamic_resizing
|
||||||
|
if not self._dynamic_resizing:
|
||||||
|
self.grid_propagate(0)
|
||||||
|
|
||||||
|
self._check_unique_values(self._value_list)
|
||||||
|
self._current_value: str = ""
|
||||||
|
self._create_buttons_from_values()
|
||||||
|
self._create_button_grid()
|
||||||
|
|
||||||
|
self._variable = variable
|
||||||
|
self._variable_callback_blocked: bool = False
|
||||||
|
self._variable_callback_name: Union[str, None] = None
|
||||||
|
|
||||||
|
if self._variable is not None:
|
||||||
|
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||||
|
self.set(self._variable.get(), from_variable_callback=True)
|
||||||
|
|
||||||
|
super().configure(corner_radius=self._sb_corner_radius)
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
if self._variable is not None: # remove old callback
|
||||||
|
self._variable.trace_remove("write", self._variable_callback_name)
|
||||||
|
|
||||||
|
super().destroy()
|
||||||
|
|
||||||
|
def _variable_callback(self, var_name, index, mode):
|
||||||
|
if not self._variable_callback_blocked:
|
||||||
|
self.set(self._variable.get(), from_variable_callback=True)
|
||||||
|
|
||||||
|
def _get_index_by_value(self, value: str):
|
||||||
|
for index, value_from_list in enumerate(self._value_list):
|
||||||
|
if value_from_list == value:
|
||||||
|
return index
|
||||||
|
|
||||||
|
raise ValueError(f"CTkSegmentedButton does not contain value '{value}'")
|
||||||
|
|
||||||
|
def _configure_button_corners_for_index(self, index: int):
|
||||||
|
if index == 0 and len(self._value_list) == 1:
|
||||||
|
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
|
||||||
|
elif index == 0:
|
||||||
|
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._sb_fg_color, self._sb_fg_color, self._bg_color))
|
||||||
|
elif index == len(self._value_list) - 1:
|
||||||
|
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._bg_color, self._bg_color, self._sb_fg_color))
|
||||||
|
else:
|
||||||
|
self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._sb_fg_color, self._sb_fg_color, self._sb_fg_color))
|
||||||
|
|
||||||
|
def _unselect_button_by_value(self, value: str):
|
||||||
|
if value in self._buttons_dict:
|
||||||
|
self._buttons_dict[value].configure(fg_color=self._sb_unselected_color,
|
||||||
|
hover_color=self._sb_unselected_hover_color)
|
||||||
|
|
||||||
|
def _select_button_by_value(self, value: str):
|
||||||
|
if self._current_value is not None and self._current_value != "":
|
||||||
|
self._unselect_button_by_value(self._current_value)
|
||||||
|
|
||||||
|
self._current_value = value
|
||||||
|
|
||||||
|
self._buttons_dict[value].configure(fg_color=self._sb_selected_color,
|
||||||
|
hover_color=self._sb_selected_hover_color)
|
||||||
|
|
||||||
|
def _create_button(self, index: int, value: str) -> CTkButton:
|
||||||
|
new_button = CTkButton(self,
|
||||||
|
height=self._current_height,
|
||||||
|
width=0,
|
||||||
|
corner_radius=self._sb_corner_radius,
|
||||||
|
text=value,
|
||||||
|
border_width=self._sb_border_width,
|
||||||
|
border_color=self._sb_fg_color,
|
||||||
|
fg_color=self._sb_unselected_color,
|
||||||
|
hover_color=self._sb_unselected_hover_color,
|
||||||
|
text_color=self._sb_text_color,
|
||||||
|
text_color_disabled=self._sb_text_color_disabled,
|
||||||
|
command=lambda v=value: self.set(v),
|
||||||
|
background_corner_colors=None,
|
||||||
|
round_width_to_even_numbers=False) # DrawEngine rendering option (so that theres no gap between buttons)
|
||||||
|
|
||||||
|
return new_button
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _check_unique_values(values: List[str]):
|
||||||
|
""" raises exception if values are not unique """
|
||||||
|
if len(values) != len(set(values)):
|
||||||
|
raise ValueError("CTkSegmentedButton values are not unique")
|
||||||
|
|
||||||
|
def _create_button_grid(self):
|
||||||
|
# remove minsize from every grid cell in the first row
|
||||||
|
number_of_columns, _ = self.grid_size()
|
||||||
|
for n in range(number_of_columns):
|
||||||
|
self.grid_columnconfigure(n, weight=1, minsize=0)
|
||||||
|
self.grid_rowconfigure(0, weight=1)
|
||||||
|
|
||||||
|
for index, value in enumerate(self._value_list):
|
||||||
|
self.grid_columnconfigure(index, weight=1, minsize=self._current_height)
|
||||||
|
self._buttons_dict[value].grid(row=0, column=index, sticky="ew")
|
||||||
|
|
||||||
|
def _create_buttons_from_values(self):
|
||||||
|
assert len(self._buttons_dict) == 0
|
||||||
|
assert len(self._value_list) > 0
|
||||||
|
|
||||||
|
for index, value in enumerate(self._value_list):
|
||||||
|
self._buttons_dict[value] = self._create_button(index, value)
|
||||||
|
self._configure_button_corners_for_index(index)
|
||||||
|
|
||||||
|
def configure(self, **kwargs):
|
||||||
|
if "height" in kwargs:
|
||||||
|
for button in self._buttons_dict.values():
|
||||||
|
button.configure(height=kwargs["height"])
|
||||||
|
|
||||||
|
if "bg_color" in kwargs:
|
||||||
|
super().configure(bg_color=kwargs.pop("bg_color"))
|
||||||
|
|
||||||
|
if len(self._buttons_dict) > 0:
|
||||||
|
self._configure_button_corners_for_index(0)
|
||||||
|
if len(self._buttons_dict) > 1:
|
||||||
|
max_index = len(self._buttons_dict) - 1
|
||||||
|
self._configure_button_corners_for_index(max_index)
|
||||||
|
|
||||||
|
if "fg_color" in kwargs:
|
||||||
|
self._sb_fg_color = kwargs.pop("fg_color")
|
||||||
|
for index, button in enumerate(self._buttons_dict.values()):
|
||||||
|
button.configure(border_color=self._sb_fg_color)
|
||||||
|
self._configure_button_corners_for_index(index)
|
||||||
|
|
||||||
|
if "selected_color" in kwargs:
|
||||||
|
self._sb_selected_color = kwargs.pop("selected_color")
|
||||||
|
if self._current_value in self._buttons_dict:
|
||||||
|
self._buttons_dict[self._current_value].configure(fg_color=self._sb_selected_color)
|
||||||
|
|
||||||
|
if "selected_hover_color" in kwargs:
|
||||||
|
self._sb_selected_hover_color = kwargs.pop("selected_hover_color")
|
||||||
|
if self._current_value in self._buttons_dict:
|
||||||
|
self._buttons_dict[self._current_value].configure(hover_color=self._sb_selected_hover_color)
|
||||||
|
|
||||||
|
if "unselected_color" in kwargs:
|
||||||
|
self._sb_unselected_color = kwargs.pop("unselected_color")
|
||||||
|
for value, button in self._buttons_dict.items():
|
||||||
|
if value != self._current_value:
|
||||||
|
button.configure(fg_color=self._sb_unselected_color)
|
||||||
|
|
||||||
|
if "unselected_hover_color" in kwargs:
|
||||||
|
self._sb_unselected_hover_color = kwargs.pop("unselected_hover_color")
|
||||||
|
for value, button in self._buttons_dict.items():
|
||||||
|
if value != self._current_value:
|
||||||
|
button.configure(hover_color=self._sb_unselected_hover_color)
|
||||||
|
|
||||||
|
if "text_color" in kwargs:
|
||||||
|
self._sb_text_color = kwargs.pop("text_color")
|
||||||
|
for button in self._buttons_dict.values():
|
||||||
|
button.configure(text_color=self._sb_text_color)
|
||||||
|
|
||||||
|
if "text_color_disabled" in kwargs:
|
||||||
|
self._sb_text_color_disabled = kwargs.pop("text_color_disabled")
|
||||||
|
for button in self._buttons_dict.values():
|
||||||
|
button.configure(text_color_disabled=self._sb_text_color_disabled)
|
||||||
|
|
||||||
|
if "values" in kwargs:
|
||||||
|
for button in self._buttons_dict.values():
|
||||||
|
button.destroy()
|
||||||
|
self._buttons_dict.clear()
|
||||||
|
self._value_list = kwargs.pop("values")
|
||||||
|
self._current_value = ""
|
||||||
|
|
||||||
|
if len(self._value_list) == 0:
|
||||||
|
raise ValueError("len() of values of CTkSegmentedButton can not be zero")
|
||||||
|
|
||||||
|
self._check_unique_values(self._value_list)
|
||||||
|
self._create_buttons_from_values()
|
||||||
|
self._create_button_grid()
|
||||||
|
|
||||||
|
if "variable" in kwargs:
|
||||||
|
if self._variable is not None: # remove old callback
|
||||||
|
self._variable.trace_remove("write", self._variable_callback_name)
|
||||||
|
|
||||||
|
self._variable = kwargs.pop("variable")
|
||||||
|
|
||||||
|
if self._variable is not None and self._variable != "":
|
||||||
|
self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
|
||||||
|
self.set(self._variable.get(), from_variable_callback=True)
|
||||||
|
else:
|
||||||
|
self._variable = None
|
||||||
|
|
||||||
|
if "dynamic_resizing" in kwargs:
|
||||||
|
self._dynamic_resizing = kwargs.pop("dynamic_resizing")
|
||||||
|
if not self._dynamic_resizing:
|
||||||
|
self.grid_propagate(0)
|
||||||
|
else:
|
||||||
|
self.grid_propagate(1)
|
||||||
|
|
||||||
|
super().configure(**kwargs)
|
||||||
|
|
||||||
|
def cget(self, attribute_name: str) -> any:
|
||||||
|
if attribute_name == "corner_radius":
|
||||||
|
return self._sb_corner_radius
|
||||||
|
elif attribute_name == "border_width":
|
||||||
|
return self._sb_border_width
|
||||||
|
elif attribute_name == "fg_color":
|
||||||
|
return self._sb_fg_color
|
||||||
|
elif attribute_name == "selected_color":
|
||||||
|
return self._sb_selected_color
|
||||||
|
elif attribute_name == "selected_hover_color":
|
||||||
|
return self._sb_selected_hover_color
|
||||||
|
elif attribute_name == "unselected_color":
|
||||||
|
return self._sb_unselected_color
|
||||||
|
elif attribute_name == "unselected_hover_color":
|
||||||
|
return self._sb_unselected_hover_color
|
||||||
|
elif attribute_name == "text_color":
|
||||||
|
return self._sb_text_color
|
||||||
|
elif attribute_name == "text_color_disabled":
|
||||||
|
return self._sb_text_color_disabled
|
||||||
|
elif attribute_name == "values":
|
||||||
|
return self._value_list
|
||||||
|
elif attribute_name == "variable":
|
||||||
|
return self._variable
|
||||||
|
elif attribute_name == "dynamic_resizing":
|
||||||
|
return self._dynamic_resizing
|
||||||
|
else:
|
||||||
|
return super().cget(attribute_name)
|
||||||
|
|
||||||
|
def set(self, value: str, from_variable_callback: bool = False):
|
||||||
|
if value == self._current_value:
|
||||||
|
print("value == self._current_value")
|
||||||
|
elif value in self._buttons_dict:
|
||||||
|
self._select_button_by_value(value)
|
||||||
|
|
||||||
|
if self._variable is not None and not from_variable_callback:
|
||||||
|
self._variable_callback_blocked = True
|
||||||
|
self._variable.set(value)
|
||||||
|
self._variable_callback_blocked = False
|
||||||
|
else:
|
||||||
|
if self._current_value in self._buttons_dict:
|
||||||
|
self._unselect_button_by_value(self._current_value)
|
||||||
|
self._current_value = value
|
||||||
|
|
||||||
|
if self._variable is not None and not from_variable_callback:
|
||||||
|
self._variable_callback_blocked = True
|
||||||
|
self._variable.set(value)
|
||||||
|
self._variable_callback_blocked = False
|
||||||
|
|
||||||
|
def get(self) -> str:
|
||||||
|
return self._current_value
|
||||||
|
|
||||||
|
def insert_value(self, index: int, value: str):
|
||||||
|
if value not in self._buttons_dict:
|
||||||
|
self._value_list.insert(index, value)
|
||||||
|
self._buttons_dict[value] = self._create_button(index, value)
|
||||||
|
|
||||||
|
self._configure_button_corners_for_index(index)
|
||||||
|
if index > 0:
|
||||||
|
self._configure_button_corners_for_index(index - 1)
|
||||||
|
if index < len(self._buttons_dict) - 1:
|
||||||
|
self._configure_button_corners_for_index(index + 1)
|
||||||
|
|
||||||
|
self._create_button_grid()
|
||||||
|
|
||||||
|
if value == self._current_value:
|
||||||
|
self._select_button_by_value(self._current_value)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"CTkSegmentedButton can not insert value '{value}', already part of the values")
|
||||||
|
|
||||||
|
def remove_value(self, value: str):
|
||||||
|
if value in self._buttons_dict:
|
||||||
|
self._buttons_dict[value].destroy()
|
||||||
|
self._buttons_dict.pop(value)
|
||||||
|
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)
|
||||||
|
|
||||||
|
self._create_button_grid()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"CTkSegmentedButton does not contain value '{value}'")
|
||||||
|
|
||||||
|
@ -102,30 +102,78 @@ class CTkBaseClass(tkinter.Frame):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
|
""" Destroy this and all descendants widgets. """
|
||||||
AppearanceModeTracker.remove(self._set_appearance_mode)
|
AppearanceModeTracker.remove(self._set_appearance_mode)
|
||||||
super().destroy()
|
super().destroy()
|
||||||
|
|
||||||
def place(self, **kwargs):
|
def place(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Place a widget in the parent widget. Use as options:
|
||||||
|
in=master - master relative to which the widget is placed
|
||||||
|
in_=master - see 'in' option description
|
||||||
|
x=amount - locate anchor of this widget at position x of master
|
||||||
|
y=amount - locate anchor of this widget at position y of master
|
||||||
|
relx=amount - locate anchor of this widget between 0.0 and 1.0 relative to width of master (1.0 is right edge)
|
||||||
|
rely=amount - locate anchor of this widget between 0.0 and 1.0 relative to height of master (1.0 is bottom edge)
|
||||||
|
anchor=NSEW (or subset) - position anchor according to given direction
|
||||||
|
width=amount - width of this widget in pixel
|
||||||
|
height=amount - height of this widget in pixel
|
||||||
|
relwidth=amount - width of this widget between 0.0 and 1.0 relative to width of master (1.0 is the same width as the master)
|
||||||
|
relheight=amount - height of this widget between 0.0 and 1.0 relative to height of master (1.0 is the same height as the master)
|
||||||
|
bordermode="inside" or "outside" - whether to take border width of master widget into account
|
||||||
|
"""
|
||||||
self._last_geometry_manager_call = {"function": super().place, "kwargs": kwargs}
|
self._last_geometry_manager_call = {"function": super().place, "kwargs": kwargs}
|
||||||
return super().place(**self._apply_argument_scaling(kwargs))
|
return super().place(**self._apply_argument_scaling(kwargs))
|
||||||
|
|
||||||
def place_forget(self):
|
def place_forget(self):
|
||||||
|
""" Unmap this widget. """
|
||||||
self._last_geometry_manager_call = None
|
self._last_geometry_manager_call = None
|
||||||
return super().place_forget()
|
return super().place_forget()
|
||||||
|
|
||||||
def pack(self, **kwargs):
|
def pack(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Pack a widget in the parent widget. Use as options:
|
||||||
|
after=widget - pack it after you have packed widget
|
||||||
|
anchor=NSEW (or subset) - position widget according to given direction
|
||||||
|
before=widget - pack it before you will pack widget
|
||||||
|
expand=bool - expand widget if parent size grows
|
||||||
|
fill=NONE or X or Y or BOTH - fill widget if widget grows
|
||||||
|
in=master - use master to contain this widget
|
||||||
|
in_=master - see 'in' option description
|
||||||
|
ipadx=amount - add internal padding in x direction
|
||||||
|
ipady=amount - add internal padding in y direction
|
||||||
|
padx=amount - add padding in x direction
|
||||||
|
pady=amount - add padding in y direction
|
||||||
|
side=TOP or BOTTOM or LEFT or RIGHT - where to add this widget.
|
||||||
|
"""
|
||||||
self._last_geometry_manager_call = {"function": super().pack, "kwargs": kwargs}
|
self._last_geometry_manager_call = {"function": super().pack, "kwargs": kwargs}
|
||||||
return super().pack(**self._apply_argument_scaling(kwargs))
|
return super().pack(**self._apply_argument_scaling(kwargs))
|
||||||
|
|
||||||
def pack_forget(self):
|
def pack_forget(self):
|
||||||
|
""" Unmap this widget and do not use it for the packing order. """
|
||||||
self._last_geometry_manager_call = None
|
self._last_geometry_manager_call = None
|
||||||
return super().pack_forget()
|
return super().pack_forget()
|
||||||
|
|
||||||
def grid(self, **kwargs):
|
def grid(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Position a widget in the parent widget in a grid. Use as options:
|
||||||
|
column=number - use cell identified with given column (starting with 0)
|
||||||
|
columnspan=number - this widget will span several columns
|
||||||
|
in=master - use master to contain this widget
|
||||||
|
in_=master - see 'in' option description
|
||||||
|
ipadx=amount - add internal padding in x direction
|
||||||
|
ipady=amount - add internal padding in y direction
|
||||||
|
padx=amount - add padding in x direction
|
||||||
|
pady=amount - add padding in y direction
|
||||||
|
row=number - use cell identified with given row (starting with 0)
|
||||||
|
rowspan=number - this widget will span several rows
|
||||||
|
sticky=NSEW - if cell is larger on which sides will this widget stick to the cell boundary
|
||||||
|
"""
|
||||||
self._last_geometry_manager_call = {"function": super().grid, "kwargs": kwargs}
|
self._last_geometry_manager_call = {"function": super().grid, "kwargs": kwargs}
|
||||||
return super().grid(**self._apply_argument_scaling(kwargs))
|
return super().grid(**self._apply_argument_scaling(kwargs))
|
||||||
|
|
||||||
def grid_forget(self):
|
def grid_forget(self):
|
||||||
|
""" Unmap this widget. """
|
||||||
self._last_geometry_manager_call = None
|
self._last_geometry_manager_call = None
|
||||||
return super().grid_forget()
|
return super().grid_forget()
|
||||||
|
|
||||||
@ -158,7 +206,7 @@ class CTkBaseClass(tkinter.Frame):
|
|||||||
raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
|
raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
|
||||||
|
|
||||||
def configure(self, require_redraw=False, **kwargs):
|
def configure(self, require_redraw=False, **kwargs):
|
||||||
""" basic configure with bg_color support, calls configure of tkinter.Frame, calls draw() in the end """
|
""" basic configure with bg_color support, calls configure of tkinter.Frame, updates in the end """
|
||||||
|
|
||||||
if "bg_color" in kwargs:
|
if "bg_color" in kwargs:
|
||||||
new_bg_color = kwargs.pop("bg_color")
|
new_bg_color = kwargs.pop("bg_color")
|
||||||
|
@ -98,17 +98,22 @@ class App(customtkinter.CTk):
|
|||||||
self.slider_progressbar_frame = customtkinter.CTkFrame(self, fg_color=None)
|
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(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_columnconfigure(0, weight=1)
|
||||||
self.slider_progressbar_frame.grid_rowconfigure(3, weight=1)
|
self.slider_progressbar_frame.grid_rowconfigure(4, weight=1)
|
||||||
|
|
||||||
|
self.seg_button = customtkinter._CTkSegmentedButton(self.slider_progressbar_frame, corner_radius=1000,
|
||||||
|
values=["CTkSegmentedButton", "Value 2", "Value 3"])
|
||||||
|
self.seg_button.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 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
|
||||||
self.progressbar_1.grid(row=0, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
|
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 = customtkinter.CTkProgressBar(self.slider_progressbar_frame)
|
||||||
self.progressbar_2.grid(row=1, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
|
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 = customtkinter.CTkSlider(self.slider_progressbar_frame, from_=0, to=1, number_of_steps=4)
|
||||||
self.slider_1.grid(row=2, column=0, padx=(20, 10), pady=(10, 10), sticky="ew")
|
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 = customtkinter.CTkSlider(self.slider_progressbar_frame, orientation="vertical")
|
||||||
self.slider_2.grid(row=0, column=1, rowspan=4, padx=(10, 10), pady=(10, 10), sticky="ns")
|
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 = customtkinter.CTkProgressBar(self.slider_progressbar_frame, orientation="vertical")
|
||||||
self.progressbar_3.grid(row=0, column=2, rowspan=4, padx=(10, 20), pady=(10, 10), sticky="ns")
|
self.progressbar_3.grid(row=0, column=2, rowspan=5, padx=(10, 20), pady=(10, 10), sticky="ns")
|
||||||
|
|
||||||
# set default values
|
# set default values
|
||||||
self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
|
self.sidebar_button_3.configure(state="disabled", text="Disabled CTkButton")
|
||||||
|
72
test/manual_integration_tests/test_segmented_button.py
Normal file
72
test/manual_integration_tests/test_segmented_button.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import customtkinter
|
||||||
|
|
||||||
|
|
||||||
|
app = customtkinter.CTk()
|
||||||
|
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=["value 1", "Value 2", "Value 42", "Value 123", "longlonglong"])
|
||||||
|
seg_1.pack(padx=20, pady=20)
|
||||||
|
|
||||||
|
frame_1 = customtkinter.CTkFrame(app, height=100)
|
||||||
|
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.pack(padx=20, pady=20)
|
||||||
|
|
||||||
|
seg_2.insert_value(0, "insert at 0")
|
||||||
|
seg_2.insert_value(1, "insert at 1")
|
||||||
|
|
||||||
|
label_seg_2 = customtkinter.CTkLabel(frame_1, textvariable=seg_2_var)
|
||||||
|
label_seg_2.pack(padx=20, pady=20)
|
||||||
|
|
||||||
|
frame_1_1 = customtkinter.CTkFrame(frame_1, height=100)
|
||||||
|
frame_1_1.pack(padx=20, pady=20, 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.pack(padx=20, pady=20)
|
||||||
|
|
||||||
|
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",
|
||||||
|
variable=seg_5_var)
|
||||||
|
seg_5.pack(padx=20, pady=20)
|
||||||
|
seg_5.configure(values=["1", "2", "3", "4"])
|
||||||
|
seg_5.insert_value(0, "insert begin")
|
||||||
|
seg_5.insert_value(len(seg_5.cget("values")), "insert 1")
|
||||||
|
seg_5.insert_value(len(seg_5.cget("values")), "insert 2")
|
||||||
|
seg_5.insert_value(len(seg_5.cget("values")), "insert 3")
|
||||||
|
seg_5.configure(fg_color="green")
|
||||||
|
|
||||||
|
seg_5.set("insert 2")
|
||||||
|
seg_5.remove_value("insert 2")
|
||||||
|
|
||||||
|
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.pack(padx=20, pady=20)
|
||||||
|
entry_6 = customtkinter.CTkEntry(app)
|
||||||
|
entry_6.pack(padx=20, pady=(0, 20))
|
||||||
|
button_6 = customtkinter.CTkButton(app, text="set", command=lambda: seg_6.set(entry_6.get()))
|
||||||
|
button_6.pack(padx=20, pady=(0, 20))
|
||||||
|
button_6 = customtkinter.CTkButton(app, text="insert value", command=lambda: seg_6.insert_value(0, entry_6.get()))
|
||||||
|
button_6.pack(padx=20, pady=(0, 20))
|
||||||
|
label_6 = customtkinter.CTkLabel(app, textvariable=seg_6_var)
|
||||||
|
label_6.pack(padx=20, pady=(0, 20))
|
||||||
|
|
||||||
|
seg_6.configure(height=50, variable=seg_6_var)
|
||||||
|
seg_6.remove_value("CTkSegmentedButton")
|
||||||
|
seg_6.configure(values=[])
|
||||||
|
|
||||||
|
app.mainloop()
|
Loading…
Reference in New Issue
Block a user