enhanced geometry string parsing for CTk and CTkToplevel #345 #287

This commit is contained in:
Tom Schimansky 2022-08-05 20:38:05 +02:00
parent 73ab410a96
commit deebaa9163
4 changed files with 118 additions and 60 deletions

View File

@ -142,41 +142,52 @@ class CTk(tkinter.Tk):
def geometry(self, geometry_string: str = None): def geometry(self, geometry_string: str = None):
if geometry_string is not None: if geometry_string is not None:
print(self.apply_geometry_scaling(geometry_string), geometry_string)
super().geometry(self.apply_geometry_scaling(geometry_string)) super().geometry(self.apply_geometry_scaling(geometry_string))
# update width and height attributes # update width and height attributes
numbers = list(map(int, re.split(r"[x+-]", geometry_string))) # split geometry string into list of numbers width, height, x, y = self.parse_geometry_string(geometry_string)
self.current_width = max(self.min_width, min(numbers[0], self.max_width)) # bound value between min and max if width is not None and height is not None:
self.current_height = max(self.min_height, min(numbers[1], self.max_height)) self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max
self.current_height = max(self.min_height, min(height, self.max_height))
else: else:
return self.reverse_geometry_scaling(super().geometry()) return self.reverse_geometry_scaling(super().geometry())
def apply_geometry_scaling(self, geometry_string): @staticmethod
value_list = re.split(r"[x+-]", geometry_string) def parse_geometry_string(geometry_string: str) -> tuple:
separator_list = re.split(r"\d+", geometry_string) # index: 1 2 3 4 5 6
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
if len(value_list) == 2: width = int(result.group(2)) if result.group(2) is not None else None
scaled_width = str(round(int(value_list[0]) * self.window_scaling)) height = int(result.group(3)) if result.group(3) is not None else None
scaled_height = str(round(int(value_list[1]) * self.window_scaling)) x = int(result.group(5)) if result.group(5) is not None else None
return f"{scaled_width}x{scaled_height}" y = int(result.group(6)) if result.group(6) is not None else None
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): return width, height, x, y
value_list = re.split(r"[x+-]", scaled_geometry_string)
separator_list = re.split(r"\d+", scaled_geometry_string)
if len(value_list) == 2: def apply_geometry_scaling(self, geometry_string: str) -> str:
width = str(round(int(value_list[0]) / self.window_scaling)) width, height, x, y = self.parse_geometry_string(geometry_string)
height = str(round(int(value_list[1]) / self.window_scaling))
return f"{width}x{height}" if x is None and y is None: # no <x> and <y> in geometry_string
elif len(value_list) == 4: return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}"
width = str(round(int(value_list[0]) / self.window_scaling))
height = str(round(int(value_list[1]) / self.window_scaling)) elif width is None and height is None: # no <width> and <height> in geometry_string
return f"{width}x{height}{separator_list[2]}{value_list[2]}{separator_list[3]}{value_list[3]}" return f"+{x}+{y}"
else:
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}"
def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
width, height, x, y = self.parse_geometry_string(scaled_geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}"
else:
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}"
def apply_window_scaling(self, value): def apply_window_scaling(self, value):
if isinstance(value, (int, float)): if isinstance(value, (int, float)):

View File

@ -83,31 +83,54 @@ class CTkToplevel(tkinter.Toplevel):
if self.max_width is not None or self.max_height is not None: if self.max_width is not None or self.max_height is not None:
super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height)) super().maxsize(self.apply_window_scaling(self.max_width), self.apply_window_scaling(self.max_height))
def apply_geometry_scaling(self, geometry_string): def geometry(self, geometry_string: str = None):
value_list = re.split(r"[x+-]", geometry_string) if geometry_string is not None:
separator_list = re.split(r"\d+", geometry_string) super().geometry(self.apply_geometry_scaling(geometry_string))
if len(value_list) == 2: # update width and height attributes
scaled_width = str(round(int(value_list[0]) * self.window_scaling)) width, height, x, y = self.parse_geometry_string(geometry_string)
scaled_height = str(round(int(value_list[1]) * self.window_scaling)) if width is not None and height is not None:
return f"{scaled_width}x{scaled_height}" self.current_width = max(self.min_width, min(width, self.max_width)) # bound value between min and max
elif len(value_list) == 4: self.current_height = max(self.min_height, min(height, self.max_height))
scaled_width = str(round(int(value_list[0]) * self.window_scaling)) else:
scaled_height = str(round(int(value_list[1]) * self.window_scaling)) return self.reverse_geometry_scaling(super().geometry())
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): @staticmethod
value_list = re.split(r"[x+-]", scaled_geometry_string) def parse_geometry_string(geometry_string: str) -> tuple:
separator_list = re.split(r"\d+", scaled_geometry_string) # index: 1 2 3 4 5 6
# regex group structure: ('<width>x<height>', '<width>', '<height>', '+-<x>+-<y>', '-<x>', '-<y>')
result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
if len(value_list) == 2: width = int(result.group(2)) if result.group(2) is not None else None
width = str(round(int(value_list[0]) / self.window_scaling)) height = int(result.group(3)) if result.group(3) is not None else None
height = str(round(int(value_list[1]) / self.window_scaling)) x = int(result.group(5)) if result.group(5) is not None else None
return f"{width}x{height}" y = int(result.group(6)) if result.group(6) is not None else None
elif len(value_list) == 4:
width = str(round(int(value_list[0]) / self.window_scaling)) return width, height, x, y
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_geometry_scaling(self, geometry_string: str) -> str:
width, height, x, y = self.parse_geometry_string(geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}"
else:
return f"{round(width * self.window_scaling)}x{round(height * self.window_scaling)}+{x}+{y}"
def reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
width, height, x, y = self.parse_geometry_string(scaled_geometry_string)
if x is None and y is None: # no <x> and <y> in geometry_string
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}"
elif width is None and height is None: # no <width> and <height> in geometry_string
return f"+{x}+{y}"
else:
return f"{round(width / self.window_scaling)}x{round(height / self.window_scaling)}+{x}+{y}"
def apply_window_scaling(self, value): def apply_window_scaling(self, value):
if isinstance(value, (int, float)): if isinstance(value, (int, float)):
@ -115,17 +138,6 @@ class CTkToplevel(tkinter.Toplevel):
else: else:
return value return value
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))
else:
return self.reverse_geometry_scaling(super().geometry())
def destroy(self): def destroy(self):
AppearanceModeTracker.remove(self.set_appearance_mode) AppearanceModeTracker.remove(self.set_appearance_mode)
ScalingTracker.remove_window(self.set_scaling, self) ScalingTracker.remove_window(self.set_scaling, self)

View File

@ -12,7 +12,7 @@ class App(customtkinter.CTk):
super().__init__() super().__init__()
self.title("CustomTkinter complex_example.py") self.title("CustomTkinter complex_example.py")
self.geometry(f"{920}x{500}") self.geometry(f"{920}x{500}-100-100")
self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed self.protocol("WM_DELETE_WINDOW", self.on_closing) # call .on_closing() when app gets closed
# configure grid layout (4x4) # configure grid layout (4x4)

View File

@ -0,0 +1,35 @@
import customtkinter
customtkinter.set_window_scaling(1.3)
app = customtkinter.CTk()
app.geometry("300x300")
app.geometry("-100-100")
app.geometry("+-100+-100")
app.geometry("+100+100")
app.geometry("300x300-100-100")
app.geometry("300x300+-100+-100")
app.geometry("300x300+100+100")
app.geometry("400x400")
app.geometry("+400+400")
app.update()
print(app.geometry())
assert app.geometry() == "400x400+400+400"
toplevel = customtkinter.CTkToplevel(app)
toplevel.geometry("300x300")
toplevel.geometry("-100-100")
toplevel.geometry("+-100+-100")
toplevel.geometry("+100+100")
toplevel.geometry("300x300-100-100")
toplevel.geometry("300x300+-100+-100")
toplevel.geometry("300x300+100+100")
toplevel.geometry("300x300")
toplevel.geometry("+500+500")
toplevel.update()
print(toplevel.geometry())
assert toplevel.geometry() == "300x300+500+500"
app.mainloop()