mirror of
https://github.com/TomSchimansky/CustomTkinter.git
synced 2023-08-10 21:13:13 +03:00
commit
a6063ade18
@ -1,4 +1,4 @@
|
||||
__version__ = "4.5.4"
|
||||
__version__ = "4.5.10"
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
@ -72,6 +72,8 @@
|
||||
"switch_border_width": 3,
|
||||
"switch_corner_radius": 1000,
|
||||
"switch_button_corner_radius": 1000,
|
||||
"switch_button_length": 0
|
||||
"switch_button_length": 0,
|
||||
"scrollbar_corner_radius": 1000,
|
||||
"scrollbar_border_spacing": 4
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,8 @@
|
||||
"switch_border_width": 3,
|
||||
"switch_corner_radius": 1000,
|
||||
"switch_button_corner_radius": 1000,
|
||||
"switch_button_length": 0
|
||||
"switch_button_length": 0,
|
||||
"scrollbar_corner_radius": 1000,
|
||||
"scrollbar_border_spacing": 4
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +72,8 @@
|
||||
"switch_border_width": 3,
|
||||
"switch_corner_radius": 1000,
|
||||
"switch_button_corner_radius": 1000,
|
||||
"switch_button_length": 2
|
||||
"switch_button_length": 2,
|
||||
"scrollbar_corner_radius": 1000,
|
||||
"scrollbar_border_spacing": 4
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import tkinter
|
||||
import sys
|
||||
from typing import Union, Tuple, Callable, Literal
|
||||
from typing import Union, Tuple, Callable
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
from ..theme_manager import ThemeManager
|
||||
@ -250,7 +250,11 @@ class CTkButton(CTkBaseClass):
|
||||
|
||||
if "image" in kwargs:
|
||||
self.image = kwargs.pop("image")
|
||||
self.set_image(self.image)
|
||||
require_redraw = True
|
||||
|
||||
if "corner_radius" in kwargs:
|
||||
self.corner_radius = kwargs.pop("corner_radius")
|
||||
require_redraw = True
|
||||
|
||||
if "compound" in kwargs:
|
||||
self.compound = kwargs.pop("compound")
|
||||
|
@ -118,6 +118,7 @@ class CTkCheckBox(CTkBaseClass):
|
||||
self.grid_columnconfigure(1, weight=0, minsize=self.apply_widget_scaling(6))
|
||||
self.text_label.configure(font=self.apply_font_scaling(self.text_font))
|
||||
|
||||
self.canvas.delete("checkmark")
|
||||
self.bg_canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
|
@ -1,3 +1,4 @@
|
||||
import sys
|
||||
import tkinter
|
||||
|
||||
from .ctk_canvas import CTkCanvas
|
||||
@ -106,6 +107,10 @@ class CTkLabel(CTkBaseClass):
|
||||
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
|
||||
def config(self, **kwargs):
|
||||
sys.stderr.write("Warning: Use .configure() instead of .config()")
|
||||
self.configure(**kwargs)
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "anchor" in kwargs:
|
||||
self.anchor = kwargs.pop("anchor")
|
||||
@ -114,7 +119,8 @@ class CTkLabel(CTkBaseClass):
|
||||
sticky=text_label_grid_sticky)
|
||||
|
||||
if "text" in kwargs:
|
||||
self.set_text(kwargs["text"])
|
||||
self.text = kwargs["text"]
|
||||
self.text_label.configure(text=self.text)
|
||||
del kwargs["text"]
|
||||
|
||||
if "fg_color" in kwargs:
|
||||
@ -143,5 +149,7 @@ class CTkLabel(CTkBaseClass):
|
||||
self.text_label.configure(**kwargs) # pass remaining kwargs to label
|
||||
|
||||
def set_text(self, text):
|
||||
""" Will be removed in the next major release """
|
||||
|
||||
self.text = text
|
||||
self.text_label.configure(text=self.text, width=len(self.text))
|
||||
self.text_label.configure(text=self.text)
|
||||
|
@ -104,13 +104,13 @@ class CTkSwitch(CTkBaseClass):
|
||||
self.text_label.bind("<Leave>", self.on_leave)
|
||||
self.text_label.bind("<Button-1>", self.toggle)
|
||||
|
||||
self.draw() # initial draw
|
||||
self.set_cursor()
|
||||
|
||||
if self.variable is not None and self.variable != "":
|
||||
self.variable_callback_name = self.variable.trace_add("write", self.variable_callback)
|
||||
self.check_state = True if self.variable.get() == self.onvalue else False
|
||||
|
||||
self.draw() # initial draw
|
||||
self.set_cursor()
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
|
||||
@ -209,14 +209,14 @@ class CTkSwitch(CTkBaseClass):
|
||||
|
||||
self.draw(no_color_updates=True)
|
||||
|
||||
if self.command is not None:
|
||||
self.command()
|
||||
|
||||
if self.variable is not None:
|
||||
self.variable_callback_blocked = True
|
||||
self.variable.set(self.onvalue if self.check_state is True else self.offvalue)
|
||||
self.variable_callback_blocked = False
|
||||
|
||||
if self.command is not None:
|
||||
self.command()
|
||||
|
||||
def select(self, from_variable_callback=False):
|
||||
if self.state is not tkinter.DISABLED or from_variable_callback:
|
||||
self.check_state = True
|
||||
|
@ -28,12 +28,13 @@ class CTkTextbox(CTkBaseClass):
|
||||
# color
|
||||
self.fg_color = ThemeManager.theme["color"]["entry"] if fg_color == "default_theme" else fg_color
|
||||
self.border_color = ThemeManager.theme["color"]["frame_border"] if border_color == "default_theme" else border_color
|
||||
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
|
||||
# shape
|
||||
self.corner_radius = ThemeManager.theme["shape"]["frame_corner_radius"] if corner_radius == "default_theme" else corner_radius
|
||||
self.border_width = ThemeManager.theme["shape"]["frame_border_width"] if border_width == "default_theme" else border_width
|
||||
|
||||
self.text_color = ThemeManager.theme["color"]["text"] if text_color == "default_theme" else text_color
|
||||
# text
|
||||
self.text_font = (ThemeManager.theme["text"]["font"], ThemeManager.theme["text"]["size"]) if text_font == "default_theme" else text_font
|
||||
|
||||
# configure 1x1 grid
|
||||
@ -56,28 +57,18 @@ class CTkTextbox(CTkBaseClass):
|
||||
height=0,
|
||||
font=self.text_font,
|
||||
highlightthickness=0,
|
||||
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
**kwargs)
|
||||
self.textbox.grid(row=0, column=0, padx=self.corner_radius, pady=self.corner_radius, rowspan=1, columnspan=1, sticky="nsew")
|
||||
|
||||
self.bind('<Configure>', self.update_dimensions_event)
|
||||
|
||||
self.draw()
|
||||
|
||||
def winfo_children(self):
|
||||
""" winfo_children of CTkFrame without self.canvas widget,
|
||||
because it's not a child but part of the CTkFrame itself """
|
||||
|
||||
child_widgets = super().winfo_children()
|
||||
try:
|
||||
child_widgets.remove(self.canvas)
|
||||
return child_widgets
|
||||
except ValueError:
|
||||
return child_widgets
|
||||
|
||||
def set_scaling(self, *args, **kwargs):
|
||||
super().set_scaling(*args, **kwargs)
|
||||
|
||||
self.textbox.configure(font=self.apply_font_scaling(self.text_font))
|
||||
self.canvas.configure(width=self.apply_widget_scaling(self._desired_width), height=self.apply_widget_scaling(self._desired_height))
|
||||
self.draw()
|
||||
|
||||
@ -110,17 +101,36 @@ class CTkTextbox(CTkBaseClass):
|
||||
outline=ThemeManager.single_color(self.border_color, self._appearance_mode))
|
||||
self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode))
|
||||
|
||||
self.textbox.configure(fg=ThemeManager.single_color(self.text_color, self._appearance_mode),
|
||||
bg=ThemeManager.single_color(self.fg_color, self._appearance_mode),
|
||||
insertbackground=ThemeManager.single_color(("black", "white"), self._appearance_mode))
|
||||
|
||||
self.canvas.tag_lower("inner_parts")
|
||||
self.canvas.tag_lower("border_parts")
|
||||
|
||||
def yview(self, args, kwargs):
|
||||
self.textbox.yview(args, kwargs)
|
||||
def yview(self, *args):
|
||||
return self.textbox.yview(*args)
|
||||
|
||||
def xview(self, args, kwargs):
|
||||
self.textbox.xview(args, kwargs)
|
||||
def xview(self, *args):
|
||||
return self.textbox.xview(*args)
|
||||
|
||||
def insert(self, args, kwargs):
|
||||
self.textbox.insert(args, kwargs)
|
||||
def insert(self, *args, **kwargs):
|
||||
return self.textbox.insert(*args, **kwargs)
|
||||
|
||||
def focus(self):
|
||||
return self.textbox.focus()
|
||||
|
||||
def tag_add(self, *args, **kwargs):
|
||||
return self.textbox.tag_add(*args, **kwargs)
|
||||
|
||||
def tag_config(self, *args, **kwargs):
|
||||
return self.textbox.tag_config(*args, **kwargs)
|
||||
|
||||
def tag_configure(self, *args, **kwargs):
|
||||
return self.textbox.tag_configure(*args, **kwargs)
|
||||
|
||||
def tag_remove(self, *args, **kwargs):
|
||||
return self.textbox.tag_remove(*args, **kwargs)
|
||||
|
||||
def configure(self, require_redraw=False, **kwargs):
|
||||
if "fg_color" in kwargs:
|
||||
|
@ -42,6 +42,8 @@ class CTkInputDialog:
|
||||
self.top.focus_force()
|
||||
self.top.grab_set()
|
||||
|
||||
self.top.protocol("WM_DELETE_WINDOW", self.on_closing)
|
||||
|
||||
self.top.after(10, self.create_widgets) # create widgets with slight delay, to avoid white flickering of background
|
||||
|
||||
def create_widgets(self):
|
||||
@ -95,6 +97,9 @@ class CTkInputDialog:
|
||||
self.user_input = self.entry.get()
|
||||
self.running = False
|
||||
|
||||
def on_closing(self):
|
||||
self.running = False
|
||||
|
||||
def cancel_event(self):
|
||||
self.running = False
|
||||
|
||||
|
@ -129,16 +129,42 @@ class CTk(tkinter.Tk):
|
||||
if self.current_height > height: self.current_height = height
|
||||
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
|
||||
|
||||
def geometry(self, geometry_string):
|
||||
super().geometry(self.apply_geometry_scaling(geometry_string))
|
||||
def geometry(self, geometry_string: str = None):
|
||||
if geometry_string is not None:
|
||||
super().geometry(self.apply_geometry_scaling(geometry_string))
|
||||
|
||||
# update width and height attributes
|
||||
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers
|
||||
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max
|
||||
self.current_height = max(self.min_height, min(numbers[1], self.max_height))
|
||||
# update width and height attributes
|
||||
numbers = list(map(int, re.split(r"[x+-]", geometry_string))) # split geometry string into list of numbers
|
||||
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max
|
||||
self.current_height = max(self.min_height, min(numbers[1], self.max_height))
|
||||
else:
|
||||
return self.reverse_geometry_scaling(super().geometry())
|
||||
|
||||
def apply_geometry_scaling(self, geometry_string):
|
||||
return re.sub(re.compile("\d+"), lambda match_obj: str(round(int(match_obj.group(0)) * self.window_scaling)), geometry_string)
|
||||
value_list = re.split(r"[x+-]", geometry_string)
|
||||
separator_list = re.split(r"\d+", geometry_string)
|
||||
|
||||
if len(value_list) == 2:
|
||||
scaled_width = str(round(int(value_list[0]) * self.window_scaling))
|
||||
scaled_height = str(round(int(value_list[1]) * self.window_scaling))
|
||||
return f"{scaled_width}x{scaled_height}"
|
||||
elif len(value_list) == 4:
|
||||
scaled_width = str(round(int(value_list[0]) * self.window_scaling))
|
||||
scaled_height = str(round(int(value_list[1]) * self.window_scaling))
|
||||
return f"{scaled_width}x{scaled_height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}"
|
||||
|
||||
def reverse_geometry_scaling(self, scaled_geometry_string):
|
||||
value_list = re.split(r"[x+-]", scaled_geometry_string)
|
||||
separator_list = re.split(r"\d+", scaled_geometry_string)
|
||||
|
||||
if len(value_list) == 2:
|
||||
width = str(round(int(value_list[0]) / self.window_scaling))
|
||||
height = str(round(int(value_list[1]) / self.window_scaling))
|
||||
return f"{width}x{height}"
|
||||
elif len(value_list) == 4:
|
||||
width = str(round(int(value_list[0]) / self.window_scaling))
|
||||
height = str(round(int(value_list[1]) / self.window_scaling))
|
||||
return f"{width}x{height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}"
|
||||
|
||||
def apply_window_scaling(self, value):
|
||||
if isinstance(value, (int, float)):
|
||||
|
@ -84,18 +84,30 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
|
||||
|
||||
def apply_geometry_scaling(self, geometry_string):
|
||||
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers
|
||||
value_list = re.split(r"[x+-]", geometry_string)
|
||||
separator_list = re.split(r"\d+", geometry_string)
|
||||
|
||||
if len(numbers) == 2:
|
||||
return f"{self.apply_window_scaling(numbers[0]):.0f}x" +\
|
||||
f"{self.apply_window_scaling(numbers[1]):.0f}"
|
||||
elif len(numbers) == 4:
|
||||
return f"{self.apply_window_scaling(numbers[0]):.0f}x" +\
|
||||
f"{self.apply_window_scaling(numbers[1]):.0f}+" +\
|
||||
f"{self.apply_window_scaling(numbers[2]):.0f}+" +\
|
||||
f"{self.apply_window_scaling(numbers[3]):.0f}"
|
||||
else:
|
||||
return geometry_string
|
||||
if len(value_list) == 2:
|
||||
scaled_width = str(round(int(value_list[0]) * self.window_scaling))
|
||||
scaled_height = str(round(int(value_list[1]) * self.window_scaling))
|
||||
return f"{scaled_width}x{scaled_height}"
|
||||
elif len(value_list) == 4:
|
||||
scaled_width = str(round(int(value_list[0]) * self.window_scaling))
|
||||
scaled_height = str(round(int(value_list[1]) * self.window_scaling))
|
||||
return f"{scaled_width}x{scaled_height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}"
|
||||
|
||||
def reverse_geometry_scaling(self, scaled_geometry_string):
|
||||
value_list = re.split(r"[x+-]", scaled_geometry_string)
|
||||
separator_list = re.split(r"\d+", scaled_geometry_string)
|
||||
|
||||
if len(value_list) == 2:
|
||||
width = str(round(int(value_list[0]) / self.window_scaling))
|
||||
height = str(round(int(value_list[1]) / self.window_scaling))
|
||||
return f"{width}x{height}"
|
||||
elif len(value_list) == 4:
|
||||
width = str(round(int(value_list[0]) / self.window_scaling))
|
||||
height = str(round(int(value_list[1]) / self.window_scaling))
|
||||
return f"{width}x{height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}"
|
||||
|
||||
def apply_window_scaling(self, value):
|
||||
if isinstance(value, (int, float)):
|
||||
@ -103,13 +115,16 @@ class CTkToplevel(tkinter.Toplevel):
|
||||
else:
|
||||
return value
|
||||
|
||||
def geometry(self, geometry_string):
|
||||
super().geometry(self.apply_geometry_scaling(geometry_string))
|
||||
def geometry(self, geometry_string: str = None):
|
||||
if geometry_string is not None:
|
||||
super().geometry(self.apply_geometry_scaling(geometry_string))
|
||||
|
||||
# update width and height attributes
|
||||
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers
|
||||
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max
|
||||
self.current_height = max(self.min_height, min(numbers[1], self.max_height))
|
||||
# update width and height attributes
|
||||
numbers = list(map(int, re.split(r"[x+]", geometry_string))) # split geometry string into list of numbers
|
||||
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max
|
||||
self.current_height = max(self.min_height, min(numbers[1], self.max_height))
|
||||
else:
|
||||
return self.reverse_geometry_scaling(super().geometry())
|
||||
|
||||
def destroy(self):
|
||||
AppearanceModeTracker.remove(self.set_appearance_mode)
|
||||
|
@ -90,6 +90,7 @@ class App(customtkinter.CTk):
|
||||
"amet consetetur sadipscing elitr,\n" +
|
||||
"sed diam nonumy eirmod tempor" ,
|
||||
height=100,
|
||||
corner_radius=6, # <- custom corner radius
|
||||
fg_color=("white", "gray38"), # <- custom tuple-color
|
||||
justify=tkinter.LEFT)
|
||||
self.label_info_1.grid(column=0, row=0, sticky="nwe", padx=15, pady=15)
|
||||
|
@ -28,6 +28,7 @@ progressbar_1.pack(pady=12, padx=10)
|
||||
|
||||
button_1 = customtkinter.CTkButton(master=frame_1, command=button_callback)
|
||||
button_1.pack(pady=12, padx=10)
|
||||
button_1.configure(state='disabled')
|
||||
|
||||
slider_1 = customtkinter.CTkSlider(master=frame_1, command=slider_callback, from_=0, to=1)
|
||||
slider_1.pack(pady=12, padx=10)
|
||||
|
@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
||||
github_url = "https://github.com/TomSchimansky/CustomTkinter"
|
||||
|
||||
[tool.tbump.version]
|
||||
current = "4.5.4"
|
||||
current = "4.5.10"
|
||||
|
||||
# Example of a semver regexp.
|
||||
# Make sure this matches current_version before
|
||||
|
@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = customtkinter
|
||||
version = 4.5.4
|
||||
version = 4.5.10
|
||||
description = Create modern looking GUIs with Python
|
||||
long_description = CustomTkinter UI-Library\n\n[](https://github.com/TomSchimansky/CustomTkinter/blob/master/documentation_images/Windows_dark.png)\n\nMore Information: https://github.com/TomSchimansky/CustomTkinter
|
||||
long_description_content_type = text/markdown
|
||||
|
@ -37,9 +37,9 @@ class App(customtkinter.CTk):
|
||||
self.appearance_mode_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["Light", "Dark", "System"],
|
||||
command=self.change_appearance_mode)
|
||||
self.appearance_mode_optionemenu.grid(row=6, column=0, padx=20, pady=(10, 10))
|
||||
self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="Widget Scaling:", anchor="w")
|
||||
self.scaling_label = customtkinter.CTkLabel(self.sidebar_frame, text="UI Scaling:", anchor="w")
|
||||
self.scaling_label.grid(row=7, column=0, padx=20, pady=(10, 0))
|
||||
self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["75%", "100%", "150%"],
|
||||
self.scaling_optionemenu = customtkinter.CTkOptionMenu(self.sidebar_frame, values=["80%", "90%", "100%", "110%", "120%"],
|
||||
command=self.change_scaling)
|
||||
self.scaling_optionemenu.grid(row=8, column=0, padx=20, pady=(10, 20))
|
||||
|
||||
@ -50,8 +50,8 @@ class App(customtkinter.CTk):
|
||||
self.main_button_1 = customtkinter.CTkButton(self, fg_color=None, border_width=2)
|
||||
self.main_button_1.grid(row=3, column=3, padx=(10, 20), pady=(10, 20), sticky="nsew")
|
||||
|
||||
self.text_frame = customtkinter.CTkFrame(self)
|
||||
self.text_frame.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew")
|
||||
self.textbox = customtkinter.CTkTextbox(self)
|
||||
self.textbox.grid(row=0, column=1, padx=(20, 10), pady=(20, 10), sticky="nsew")
|
||||
|
||||
# create radiobutton frame
|
||||
self.radiobutton_frame = customtkinter.CTkFrame(self)
|
||||
@ -119,6 +119,10 @@ class App(customtkinter.CTk):
|
||||
self.scaling_optionemenu.set("100%")
|
||||
self.optionmenu_1.set("CTkOptionmenu")
|
||||
self.combobox_1.set("CTkComboBox")
|
||||
self.textbox.insert("1.0",
|
||||
"CTkTextbox\n\nLorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.", )
|
||||
#self.textbox.tag_add("headline", "1.0", "1.end")
|
||||
#self.textbox.tag_config("headline", foreground="red")
|
||||
|
||||
def open_input_dialog(self):
|
||||
dialog = customtkinter.CTkInputDialog(master=None, text="Type in a number:", title="CTkInputDialog")
|
||||
|
Loading…
Reference in New Issue
Block a user