From ec3fdc40ff54ae8d80c285802a38358740505902 Mon Sep 17 00:00:00 2001 From: Tom Schimansky Date: Thu, 23 Jun 2022 17:41:12 +0200 Subject: [PATCH] fixed scrollbar for horizontal orientation --- customtkinter/widgets/ctk_scrollbar.py | 58 ++++++++++++++----- .../test_optionmenu_combobox.py | 2 +- .../test_scrollbar.py | 21 ++++--- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/customtkinter/widgets/ctk_scrollbar.py b/customtkinter/widgets/ctk_scrollbar.py index 135fe20..a70908d 100644 --- a/customtkinter/widgets/ctk_scrollbar.py +++ b/customtkinter/widgets/ctk_scrollbar.py @@ -1,7 +1,5 @@ import sys -import tkinter -from .ctk_frame import CTkFrame from .ctk_canvas import CTkCanvas from ..theme_manager import ThemeManager from ..draw_engine import DrawEngine @@ -18,6 +16,7 @@ class CTkScrollbar(CTkBaseClass): corner_radius="default_theme", width=None, height=None, + minimum_pixel_length=20, orientation="vertical", command=None, hover=True, @@ -30,10 +29,10 @@ class CTkScrollbar(CTkBaseClass): else: width = 200 if height is None: - if orientation.lower() == "vertical": - height = 200 - else: + if orientation.lower() == "horizontal": height = 16 + else: + height = 200 # transfer basic functionality (bg_color, size, _appearance_mode, scaling) to CTkBaseClass super().__init__(*args, bg_color=bg_color, width=width, height=height, **kwargs) @@ -53,6 +52,7 @@ class CTkScrollbar(CTkBaseClass): self.orientation = orientation self.start_value: float = 0 # 0 to 1 self.end_value: float = 1 # 0 to 1 + self.minimum_pixel_length = minimum_pixel_length self.canvas = CTkCanvas(master=self, highlightthickness=0, @@ -83,12 +83,38 @@ class CTkScrollbar(CTkBaseClass): height=self.apply_widget_scaling(self._desired_height)) self.draw(no_color_updates=True) + def get_scrollbar_values_for_minimum_pixel_size(self): + # correct scrollbar float values if scrollbar is too small + if self.orientation == "vertical": + scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_height + if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_height != 0: + # calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length + interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_height) + corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor + corrected_start_value = self.start_value - self.start_value * interval_extend_factor + return corrected_start_value, corrected_end_value + else: + return self.start_value, self.end_value + + else: + scrollbar_pixel_length = (self.end_value - self.start_value) * self._current_width + if scrollbar_pixel_length < self.minimum_pixel_length and -scrollbar_pixel_length + self._current_width != 0: + # calculate how much to increase the float interval values so that the scrollbar width is self.minimum_pixel_length + interval_extend_factor = (-scrollbar_pixel_length + self.minimum_pixel_length) / (-scrollbar_pixel_length + self._current_width) + corrected_end_value = self.end_value + (1 - self.end_value) * interval_extend_factor + corrected_start_value = self.start_value - self.start_value * interval_extend_factor + return corrected_start_value, corrected_end_value + else: + return self.start_value, self.end_value + def draw(self, no_color_updates=False): + corrected_start_value, corrected_end_value = self.get_scrollbar_values_for_minimum_pixel_size() requires_recoloring = self.draw_engine.draw_rounded_scrollbar(self.apply_widget_scaling(self._current_width), self.apply_widget_scaling(self._current_height), self.apply_widget_scaling(self.corner_radius), self.apply_widget_scaling(self.border_spacing), - self.start_value, self.end_value, + corrected_start_value, + corrected_end_value, self.orientation) if no_color_updates is False or requires_recoloring: @@ -102,10 +128,12 @@ class CTkScrollbar(CTkBaseClass): outline=ThemeManager.single_color(self.scrollbar_color, self._appearance_mode)) if self.fg_color is None: + self.canvas.configure(bg=ThemeManager.single_color(self.bg_color, self._appearance_mode)) self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.bg_color, self._appearance_mode), outline=ThemeManager.single_color(self.bg_color, self._appearance_mode)) else: + self.canvas.configure(bg=ThemeManager.single_color(self.fg_color, self._appearance_mode)) self.canvas.itemconfig("border_parts", fill=ThemeManager.single_color(self.fg_color, self._appearance_mode), outline=ThemeManager.single_color(self.fg_color, self._appearance_mode)) @@ -115,7 +143,6 @@ class CTkScrollbar(CTkBaseClass): def set(self, start_value: float, end_value: float): self.start_value = float(start_value) self.end_value = float(end_value) - self.scrollbar_height = self.end_value - self.start_value self.draw() def get(self): @@ -190,14 +217,17 @@ class CTkScrollbar(CTkBaseClass): def clicked(self, event): if self.orientation == "vertical": value = ((event.y - self.border_spacing) / (self._current_height - 2 * self.border_spacing)) / self._widget_scaling - current_scrollbar_length = self.end_value - self.start_value - value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) - self.start_value = value - (current_scrollbar_length / 2) - self.end_value = value + (current_scrollbar_length / 2) - self.draw() + else: + value = ((event.x - self.border_spacing) / (self._current_width - 2 * self.border_spacing)) / self._widget_scaling - if self.command is not None: - self.command('moveto', self.start_value) + current_scrollbar_length = self.end_value - self.start_value + value = max(current_scrollbar_length / 2, min(value, 1 - (current_scrollbar_length / 2))) + self.start_value = value - (current_scrollbar_length / 2) + self.end_value = value + (current_scrollbar_length / 2) + self.draw() + + if self.command is not None: + self.command('moveto', self.start_value) def mouse_scroll_event(self, event=None): if self.command is not None: diff --git a/test/manual_integration_tests/test_optionmenu_combobox.py b/test/manual_integration_tests/test_optionmenu_combobox.py index 32b29e8..91ac241 100644 --- a/test/manual_integration_tests/test_optionmenu_combobox.py +++ b/test/manual_integration_tests/test_optionmenu_combobox.py @@ -12,7 +12,7 @@ def select_callback(choice): print("display_selected", choice) -countries = ['Bahamas', 'Canada', 'Cuba', 'United States'] +countries = ['Bahamas', 'Canada', 'Cuba', 'United States', "long sdhfhjgdshjafghdgshfhjdsfj"] variable = tkinter.StringVar() variable.set("test") diff --git a/test/manual_integration_tests/test_scrollbar.py b/test/manual_integration_tests/test_scrollbar.py index 8293a94..5fcced0 100644 --- a/test/manual_integration_tests/test_scrollbar.py +++ b/test/manual_integration_tests/test_scrollbar.py @@ -1,14 +1,15 @@ import tkinter import customtkinter -# customtkinter.DrawEngine.preferred_drawing_method = "font_shapes" -#customtkinter.set_widget_scaling(2) -#customtkinter.set_window_scaling(2) -#customtkinter.set_spacing_scaling(2) +# test with scaling +# customtkinter.set_widget_scaling(2) +# customtkinter.set_window_scaling(2) +# customtkinter.set_spacing_scaling(2) -customtkinter.set_appearance_mode("light") +customtkinter.set_appearance_mode("dark") app = customtkinter.CTk() +app.title("test_scrollbar.py") app.grid_rowconfigure(0, weight=1) app.grid_columnconfigure((0, 2), weight=1) @@ -27,16 +28,20 @@ tk_textbox_1.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) ctk_textbox_scrollbar_1 = customtkinter.CTkScrollbar(frame_1, command=tk_textbox_1.yview) ctk_textbox_scrollbar_1.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) tk_textbox_1.configure(yscrollcommand=ctk_textbox_scrollbar_1.set) +ctk_textbox_scrollbar_1.configure(scrollbar_color="red", scrollbar_hover_color="darkred", + border_spacing=0, width=12, fg_color="green", corner_radius=4) frame_2 = customtkinter.CTkFrame(frame_1) frame_2.grid(row=1, column=0, columnspan=2, padx=20, pady=20, sticky="nsew") frame_2.grid_rowconfigure((0, 1), weight=1) frame_2.grid_columnconfigure((0, ), weight=1) -tk_textbox_2 = tkinter.Text(frame_2, highlightthickness=0, padx=5, pady=5) +tk_textbox_2 = tkinter.Text(frame_2, highlightthickness=0, padx=5, pady=5, wrap="none") tk_textbox_2.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5) ctk_textbox_scrollbar_2 = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.yview) ctk_textbox_scrollbar_2.grid(row=0, column=1, sticky="ns", padx=(0, 5), pady=5) -tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set) +ctk_textbox_scrollbar_2_horizontal = customtkinter.CTkScrollbar(frame_2, command=tk_textbox_2.xview, orientation="horizontal") +ctk_textbox_scrollbar_2_horizontal.grid(row=1, column=0, sticky="ew", padx=(5, 0), pady=(0, 5)) +tk_textbox_2.configure(yscrollcommand=ctk_textbox_scrollbar_2.set, xscrollcommand=ctk_textbox_scrollbar_2_horizontal.set) tk_textbox.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) tk_textbox_1.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], customtkinter.ThemeManager.theme["text"]["size"])) @@ -44,6 +49,6 @@ tk_textbox_2.configure(font=(customtkinter.ThemeManager.theme["text"]["font"], c tk_textbox.insert("insert", "\n".join([str(i) for i in range(100)])) tk_textbox_1.insert("insert", "\n".join([str(i) for i in range(1000)])) -tk_textbox_2.insert("insert", "\n".join([str(i) for i in range(10000)])) +tk_textbox_2.insert("insert", "\n".join([str(i) + " - "*30 for i in range(10000)])) app.mainloop()