diff --git a/Python Version/Assets/CTkRangeSlider/ctk_rangeslider.py b/Python Version/Assets/CTkRangeSlider/ctk_rangeslider.py index 6b170f6..d60f296 100644 --- a/Python Version/Assets/CTkRangeSlider/ctk_rangeslider.py +++ b/Python Version/Assets/CTkRangeSlider/ctk_rangeslider.py @@ -2,11 +2,9 @@ CTkRangeSlider Range slider for customtkinter Author: Akash Bora -Version: 0.2 +Version: 0.3 """ -from __future__ import annotations - import math import tkinter import sys @@ -23,12 +21,90 @@ class CustomDrawEngine: It is tailored towards the range slider. """ - preferred_drawing_method: str = "font_shapes" # 'polygon_shapes', 'font_shapes', 'circle_shapes' + # Use circle_shapes on macOS to avoid rectangular slider heads, font_shapes elsewhere + preferred_drawing_method: str = "circle_shapes" if sys.platform == "darwin" else "font_shapes" def __init__(self, canvas: CTkCanvas): self._canvas = canvas - - def _DrawEngine__draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + + def __calc_optimal_corner_radius(self, user_corner_radius: Union[float, int]) -> Union[float, int]: + # optimize for drawing with polygon shapes + if self.preferred_drawing_method == "polygon_shapes": + if sys.platform == "darwin": + return user_corner_radius + else: + return round(user_corner_radius) + + # optimize for drawing with antialiased font shapes + elif self.preferred_drawing_method == "font_shapes": + return round(user_corner_radius) + + # optimize for drawing with circles and rects + elif self.preferred_drawing_method == "circle_shapes": + user_corner_radius = 0.5 * round(user_corner_radius / 0.5) # round to 0.5 steps + + # make sure the value is always with .5 at the end for smoother corners + if user_corner_radius == 0: + return 0 + elif user_corner_radius % 1 == 0: + return user_corner_radius + 0.5 + else: + return user_corner_radius + + def __draw_rounded_rect_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: + requires_recoloring = False + + # create border button parts (only if border exists) + if border_width > 0: + if not self._canvas.find_withtag("border_parts"): + self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_1", "border_parts")) + requires_recoloring = True + + self._canvas.coords("border_line_1", + (corner_radius, + corner_radius, + width - corner_radius, + corner_radius, + width - corner_radius, + height - corner_radius, + corner_radius, + height - corner_radius)) + self._canvas.itemconfig("border_line_1", + joinstyle=tkinter.ROUND, + width=corner_radius * 2) + + else: + self._canvas.delete("border_parts") + + # create inner button parts + if not self._canvas.find_withtag("inner_parts"): + self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_1", "inner_parts"), joinstyle=tkinter.ROUND) + requires_recoloring = True + + if corner_radius <= border_width: + bottom_right_shift = -1 # weird canvas rendering inaccuracy that has to be corrected in some cases + else: + bottom_right_shift = 0 + + self._canvas.coords("inner_line_1", + border_width + inner_corner_radius, + border_width + inner_corner_radius, + width - (border_width + inner_corner_radius) + bottom_right_shift, + border_width + inner_corner_radius, + width - (border_width + inner_corner_radius) + bottom_right_shift, + height - (border_width + inner_corner_radius) + bottom_right_shift, + border_width + inner_corner_radius, + height - (border_width + inner_corner_radius) + bottom_right_shift) + self._canvas.itemconfig("inner_line_1", + width=inner_corner_radius * 2) + + if requires_recoloring: # new parts were added -> manage z-order + self._canvas.tag_lower("inner_parts") + self._canvas.tag_lower("border_parts") + + return requires_recoloring + + def __draw_rounded_rect_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, exclude_parts: tuple) -> bool: requires_recoloring = False @@ -165,7 +241,257 @@ class CustomDrawEngine: self._canvas.tag_lower("border_parts") return requires_recoloring - + + def __draw_rounded_rect_with_border_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int) -> bool: + requires_recoloring = False + + # create border parts (only if border exists) + if border_width > 0: + if corner_radius > 0: + # create canvas border corner parts if not already created + if not self._canvas.find_withtag("border_oval_1"): + self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_1", "border_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_2", "border_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_3", "border_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_4", "border_parts"), width=0) + requires_recoloring = True + + # set positions of border corner parts + self._canvas.coords("border_oval_1", 0, 0, 2 * corner_radius, 2 * corner_radius) + self._canvas.coords("border_oval_2", width - 2 * corner_radius, 0, width, 2 * corner_radius) + self._canvas.coords("border_oval_3", width - 2 * corner_radius, height - 2 * corner_radius, width, height) + self._canvas.coords("border_oval_4", 0, height - 2 * corner_radius, 2 * corner_radius, height) + + # create canvas border rectangle parts + if not self._canvas.find_withtag("border_rectangle_1"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0) + self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_parts"), width=0) + requires_recoloring = True + + # set positions of border rectangle parts + self._canvas.coords("border_rectangle_1", corner_radius, 0, width - corner_radius, height) + self._canvas.coords("border_rectangle_2", 0, corner_radius, width, height - corner_radius) + else: + self._canvas.delete("border_parts") + + # create inner parts + if not self._canvas.find_withtag("inner_rectangle_1"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_parts"), width=0) + requires_recoloring = True + + if inner_corner_radius > 0: + if not self._canvas.find_withtag("inner_oval_1"): + self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_1", "inner_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_2", "inner_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_3", "inner_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_4", "inner_parts"), width=0) + requires_recoloring = True + + # set positions of inner corner parts + self._canvas.coords("inner_oval_1", border_width, border_width, border_width + 2 * inner_corner_radius, border_width + 2 * inner_corner_radius) + self._canvas.coords("inner_oval_2", width - border_width - 2 * inner_corner_radius, border_width, width - border_width, border_width + 2 * inner_corner_radius) + self._canvas.coords("inner_oval_3", width - border_width - 2 * inner_corner_radius, height - border_width - 2 * inner_corner_radius, width - border_width, height - border_width) + self._canvas.coords("inner_oval_4", border_width, height - border_width - 2 * inner_corner_radius, border_width + 2 * inner_corner_radius, height - border_width) + else: + self._canvas.delete("inner_oval_1", "inner_oval_2", "inner_oval_3", "inner_oval_4") + + # set position of inner rectangle part + self._canvas.coords("inner_rectangle_1", border_width + inner_corner_radius, border_width, width - border_width - inner_corner_radius, height - border_width) + + if requires_recoloring: + self._canvas.tag_lower("inner_parts") + self._canvas.tag_lower("border_parts") + + return requires_recoloring + + def __draw_rounded_progress_bar_with_border_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + progress_value_1: float, progress_value_2: float, orientation: str) -> bool: + + requires_recoloring = self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius) + + if corner_radius <= border_width: + bottom_right_shift = 0 # weird canvas rendering inaccuracy that has to be corrected in some cases + else: + bottom_right_shift = 0 + + # create progress parts + if not self._canvas.find_withtag("progress_parts"): + self._canvas.create_polygon((0, 0, 0, 0), tags=("progress_line_1", "progress_parts"), joinstyle=tkinter.ROUND) + self._canvas.tag_raise("progress_parts", "inner_parts") + requires_recoloring = True + + if orientation == "w": + self._canvas.coords("progress_line_1", + border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1, + border_width + inner_corner_radius, + border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2, + border_width + inner_corner_radius, + border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2, + height - (border_width + inner_corner_radius) + bottom_right_shift, + border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1, + height - (border_width + inner_corner_radius) + bottom_right_shift) + + elif orientation == "s": + self._canvas.coords("progress_line_1", + border_width + inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), + width - (border_width + inner_corner_radius), + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), + width - (border_width + inner_corner_radius), + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), + border_width + inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1)) + + self._canvas.itemconfig("progress_line_1", width=inner_corner_radius * 2) + + return requires_recoloring + + def __draw_rounded_progress_bar_with_border_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + progress_value_1: float, progress_value_2: float, orientation: str) -> bool: + + requires_recoloring, requires_recoloring_2 = False, False + + if inner_corner_radius > 0: + # create canvas border corner parts if not already created + if not self._canvas.find_withtag("progress_oval_1_a"): + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_1_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_1_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_2_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_2_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + + if not self._canvas.find_withtag("progress_oval_3_a") and round(inner_corner_radius) * 2 < height - 2 * border_width: + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_3_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_a", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER) + self._canvas.create_aa_circle(0, 0, 0, tags=("progress_oval_4_b", "progress_corner_part", "progress_parts"), anchor=tkinter.CENTER, angle=180) + requires_recoloring = True + elif self._canvas.find_withtag("progress_oval_3_a") and not round( inner_corner_radius) * 2 < height - 2 * border_width: + self._canvas.delete("progress_oval_3_a", "progress_oval_3_b", "progress_oval_4_a", "progress_oval_4_b") + + if not self._canvas.find_withtag("progress_rectangle_1"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_1", "progress_rectangle_part", "progress_parts"), width=0) + requires_recoloring = True + + if not self._canvas.find_withtag("progress_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_2", "progress_rectangle_part", "progress_parts"), width=0) + requires_recoloring = True + elif self._canvas.find_withtag("progress_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2): + self._canvas.delete("progress_rectangle_2") + + # horizontal orientation from the bottom + if orientation == "w": + requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, + ()) + + # set positions of progress corner parts + self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1, + border_width + inner_corner_radius, inner_corner_radius) + self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1, + border_width + inner_corner_radius, inner_corner_radius) + self._canvas.coords("progress_oval_2_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2, + border_width + inner_corner_radius, inner_corner_radius) + self._canvas.coords("progress_oval_2_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2, + border_width + inner_corner_radius, inner_corner_radius) + self._canvas.coords("progress_oval_3_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2, + height - border_width - inner_corner_radius, inner_corner_radius) + self._canvas.coords("progress_oval_3_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2, + height - border_width - inner_corner_radius, inner_corner_radius) + self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1, + height - border_width - inner_corner_radius, inner_corner_radius) + self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1, + height - border_width - inner_corner_radius, inner_corner_radius) + + # set positions of progress rect parts + self._canvas.coords("progress_rectangle_1", + border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1, + border_width, + border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2, + height - border_width) + self._canvas.coords("progress_rectangle_2", + border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value_1, + border_width + inner_corner_radius, + border_width + 2 * inner_corner_radius + (width - 2 * inner_corner_radius - 2 * border_width) * progress_value_2, + height - inner_corner_radius - border_width) + + # vertical orientation from the bottom + if orientation == "s": + requires_recoloring_2 = self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, + ()) + + # set positions of progress corner parts + self._canvas.coords("progress_oval_1_a", border_width + inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius) + self._canvas.coords("progress_oval_1_b", border_width + inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius) + self._canvas.coords("progress_oval_2_a", width - border_width - inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius) + self._canvas.coords("progress_oval_2_b", width - border_width - inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), inner_corner_radius) + self._canvas.coords("progress_oval_3_a", width - border_width - inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius) + self._canvas.coords("progress_oval_3_b", width - border_width - inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius) + self._canvas.coords("progress_oval_4_a", border_width + inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius) + self._canvas.coords("progress_oval_4_b", border_width + inner_corner_radius, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1), inner_corner_radius) + + # set positions of progress rect parts + self._canvas.coords("progress_rectangle_1", + border_width + inner_corner_radius, + border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), + width - border_width - inner_corner_radius, + border_width + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1)) + self._canvas.coords("progress_rectangle_2", + border_width, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2), + width - border_width, + border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1)) + + return requires_recoloring or requires_recoloring_2 + + def __draw_rounded_progress_bar_with_border_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + progress_value_1: float, progress_value_2: float, orientation: str) -> bool: + requires_recoloring = self.__draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius) + + # create progress parts + if not self._canvas.find_withtag("progress_rectangle_1"): + self._canvas.create_rectangle(0, 0, 0, 0, tags=("progress_rectangle_1", "progress_parts"), width=0) + requires_recoloring = True + + if orientation == "w": + x1 = border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_1 + x2 = border_width + inner_corner_radius + (width - 2 * border_width - 2 * inner_corner_radius) * progress_value_2 + self._canvas.coords("progress_rectangle_1", x1, border_width, x2, height - border_width) + elif orientation == "s": + y1 = border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_2) + y2 = border_width + inner_corner_radius + (height - 2 * border_width - 2 * inner_corner_radius) * (1 - progress_value_1) + self._canvas.coords("progress_rectangle_1", border_width, y1, width - border_width, y2) + + if inner_corner_radius > 0: + if not self._canvas.find_withtag("progress_oval_1"): + self._canvas.create_oval(0, 0, 0, 0, tags=("progress_oval_1", "progress_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("progress_oval_2", "progress_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("progress_oval_3", "progress_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("progress_oval_4", "progress_parts"), width=0) + requires_recoloring = True + + if orientation == "w": + self._canvas.coords("progress_oval_1", x1 - inner_corner_radius, border_width, x1 + inner_corner_radius, border_width + 2 * inner_corner_radius) + self._canvas.coords("progress_oval_2", x2 - inner_corner_radius, border_width, x2 + inner_corner_radius, border_width + 2 * inner_corner_radius) + self._canvas.coords("progress_oval_3", x2 - inner_corner_radius, height - border_width - 2 * inner_corner_radius, x2 + inner_corner_radius, height - border_width) + self._canvas.coords("progress_oval_4", x1 - inner_corner_radius, height - border_width - 2 * inner_corner_radius, x1 + inner_corner_radius, height - border_width) + elif orientation == "s": + self._canvas.coords("progress_oval_1", border_width, y1 - inner_corner_radius, border_width + 2 * inner_corner_radius, y1 + inner_corner_radius) + self._canvas.coords("progress_oval_2", width - border_width - 2 * inner_corner_radius, y1 - inner_corner_radius, width - border_width, y1 + inner_corner_radius) + self._canvas.coords("progress_oval_3", width - border_width - 2 * inner_corner_radius, y2 - inner_corner_radius, width - border_width, y2 + inner_corner_radius) + self._canvas.coords("progress_oval_4", border_width, y2 - inner_corner_radius, border_width + 2 * inner_corner_radius, y2 + inner_corner_radius) + else: + self._canvas.delete("progress_oval_1", "progress_oval_2", "progress_oval_3", "progress_oval_4") + + return requires_recoloring + def draw_rounded_slider_with_border_and_2_button(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int], border_width: Union[float, int], button_length: Union[float, int], button_corner_radius: Union[float, int], slider_value: float, slider_2_value: float, orientation: str) -> bool: @@ -182,25 +508,28 @@ class CustomDrawEngine: button_length = round(button_length) border_width = round(border_width) button_corner_radius = round(button_corner_radius) - corner_radius = DrawEngine._DrawEngine__calc_optimal_corner_radius(self, corner_radius) # optimize corner_radius for different drawing methods (different rounding) + corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding) if corner_radius >= border_width: inner_corner_radius = corner_radius - border_width else: inner_corner_radius = 0 - if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes": + if self.preferred_drawing_method == "polygon_shapes": return self.__draw_rounded_slider_with_border_and_2_button_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, button_length, button_corner_radius, slider_value, slider_2_value, orientation) elif self.preferred_drawing_method == "font_shapes": return self.__draw_rounded_slider_with_border_and_2_button_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, button_length, button_corner_radius, slider_value, slider_2_value, orientation) + elif self.preferred_drawing_method == "circle_shapes": + return self.__draw_rounded_slider_with_border_and_2_button_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius, + button_length, button_corner_radius, slider_value, slider_2_value, orientation) def __draw_rounded_slider_with_border_and_2_button_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, button_length: int, button_corner_radius: int, slider_value: float, slider_2_value: float, orientation: str) -> bool: # draw normal progressbar - requires_recoloring = DrawEngine._DrawEngine__draw_rounded_progress_bar_with_border_polygon_shapes(self, width, height, corner_radius, border_width, inner_corner_radius, + requires_recoloring = self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, slider_value, slider_2_value, orientation) # create slider button part @@ -258,8 +587,7 @@ class CustomDrawEngine: button_length: int, button_corner_radius: int, slider_value: float, slider_2_value: float, orientation: str) -> bool: # draw normal progressbar - requires_recoloring = DrawEngine._DrawEngine__draw_rounded_progress_bar_with_border_font_shapes(self, width, height, corner_radius, border_width, - inner_corner_radius, slider_value, slider_2_value, orientation) + requires_recoloring = self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, slider_value, slider_2_value, orientation) # create 4 circles (if not needed, then less) if not self._canvas.find_withtag("slider_oval_1_a"): @@ -285,14 +613,14 @@ class CustomDrawEngine: self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_a", "slider_corner_part", "slider_parts", "slider_0_parts"), anchor=tkinter.CENTER) self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_b", "slider_corner_part", "slider_parts", "slider_0_parts"), anchor=tkinter.CENTER, angle=180) requires_recoloring = True - elif self._canvas.find_withtag("border_oval_3_a") and not (button_length > 0 and height > 2 * button_corner_radius): + elif self._canvas.find_withtag("slider_oval_3_a") and not (button_length > 0 and height > 2 * button_corner_radius): self._canvas.delete("slider_oval_3_a", "slider_oval_3_b") # create the 2 rectangles (if needed) if not self._canvas.find_withtag("slider_rectangle_1") and button_length > 0: self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_1", "slider_rectangle_part", "slider_parts", "slider_0_parts"), width=0) requires_recoloring = True - elif self._canvas.find_withtag("slider_rectangle_1") and not button_length > 0: + elif self._canvas.find_withtag("slider елдер_1") and not button_length > 0: self._canvas.delete("slider_rectangle_1") if not self._canvas.find_withtag("slider_rectangle_2") and height > 2 * button_corner_radius: @@ -338,7 +666,7 @@ class CustomDrawEngine: button_corner_radius, slider_y_position - (button_length / 2) - button_corner_radius, width - button_corner_radius, slider_y_position + (button_length / 2) + button_corner_radius) - ######## second button ########## + # second button # create 4 circles (if not needed, then less) if not self._canvas.find_withtag("slider_oval_2_1_a"): self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_1_a", "slider_corner_part", "slider_parts", "slider_1_parts"), anchor=tkinter.CENTER) @@ -363,7 +691,7 @@ class CustomDrawEngine: self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_3_a", "slider_corner_part", "slider_parts", "slider_1_parts"), anchor=tkinter.CENTER) self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_3_b", "slider_corner_part", "slider_parts", "slider_1_parts"), anchor=tkinter.CENTER, angle=180) requires_recoloring = True - elif self._canvas.find_withtag("border_oval_2_3_a") and not (button_length > 0 and height > 2 * button_corner_radius): + elif self._canvas.find_withtag("slider_oval_2_3_a") and not (button_length > 0 and height > 2 * button_corner_radius): self._canvas.delete("slider_oval_2_3_a", "slider_oval_2_3_b") # create the 2 rectangles (if needed) @@ -421,6 +749,67 @@ class CustomDrawEngine: return requires_recoloring + def __draw_rounded_slider_with_border_and_2_button_circle_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int, + button_length: int, button_corner_radius: int, slider_value: float, slider_2_value: float, orientation: str) -> bool: + requires_recoloring = self.__draw_rounded_progress_bar_with_border_circle_shapes(width, height, corner_radius, border_width, inner_corner_radius, slider_value, slider_2_value, orientation) + + # create slider button parts + if not self._canvas.find_withtag("slider_0_parts"): + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_1", "slider_parts", "slider_0_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_2", "slider_parts", "slider_0_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_3", "slider_parts", "slider_0_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_4", "slider_parts", "slider_0_parts"), width=0) + self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_1", "slider_parts", "slider_0_parts"), width=0) + self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_2", "slider_parts", "slider_0_parts"), width=0) + requires_recoloring = True + + if not self._canvas.find_withtag("slider_1_parts"): + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_2_1", "slider_parts", "slider_1_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_2_2", "slider_parts", "slider_1_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_2_3", "slider_parts", "slider_1_parts"), width=0) + self._canvas.create_oval(0, 0, 0, 0, tags=("slider_oval_2_4", "slider_parts", "slider_1_parts"), width=0) + self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_2_1", "slider_parts", "slider_1_parts"), width=0) + self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_2_2", "slider_parts", "slider_1_parts"), width=0) + requires_recoloring = True + + if orientation == "w": + slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_value + self._canvas.coords("slider_oval_1", slider_x_position - button_length / 2 - button_corner_radius, 0, slider_x_position - button_length / 2 + button_corner_radius, 2 * button_corner_radius) + self._canvas.coords("slider_oval_2", slider_x_position + button_length / 2 - button_corner_radius, 0, slider_x_position + button_length / 2 + button_corner_radius, 2 * button_corner_radius) + self._canvas.coords("slider_oval_3", slider_x_position + button_length / 2 - button_corner_radius, height - 2 * button_corner_radius, slider_x_position + button_length / 2 + button_corner_radius, height) + self._canvas.coords("slider_oval_4", slider_x_position - button_length / 2 - button_corner_radius, height - 2 * button_corner_radius, slider_x_position - button_length / 2 + button_corner_radius, height) + self._canvas.coords("slider_rectangle_1", slider_x_position - button_length / 2, 0, slider_x_position + button_length / 2, height) + self._canvas.coords("slider_rectangle_2", slider_x_position - button_length / 2 - button_corner_radius, button_corner_radius, slider_x_position + button_length / 2 + button_corner_radius, height - button_corner_radius) + + slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_2_value + self._canvas.coords("slider_oval_2_1", slider_x_position - button_length / 2 - button_corner_radius, 0, slider_x_position - button_length / 2 + button_corner_radius, 2 * button_corner_radius) + self._canvas.coords("slider_oval_2_2", slider_x_position + button_length / 2 - button_corner_radius, 0, slider_x_position + button_length / 2 + button_corner_radius, 2 * button_corner_radius) + self._canvas.coords("slider_oval_2_3", slider_x_position + button_length / 2 - button_corner_radius, height - 2 * button_corner_radius, slider_x_position + button_length / 2 + button_corner_radius, height) + self._canvas.coords("slider_oval_2_4", slider_x_position - button_length / 2 - button_corner_radius, height - 2 * button_corner_radius, slider_x_position - button_length / 2 + button_corner_radius, height) + self._canvas.coords("slider_rectangle_2_1", slider_x_position - button_length / 2, 0, slider_x_position + button_length / 2, height) + self._canvas.coords("slider_rectangle_2_2", slider_x_position - button_length / 2 - button_corner_radius, button_corner_radius, slider_x_position + button_length / 2 + button_corner_radius, height - button_corner_radius) + elif orientation == "s": + slider_y_position = corner_radius + (button_length / 2) + (height - 2 * corner_radius - button_length) * (1 - slider_value) + self._canvas.coords("slider_oval_1", 0, slider_y_position - button_length / 2 - button_corner_radius, 2 * button_corner_radius, slider_y_position - button_length / 2 + button_corner_radius) + self._canvas.coords("slider_oval_2", 0, slider_y_position + button_length / 2 - button_corner_radius, 2 * button_corner_radius, slider_y_position + button_length / 2 + button_corner_radius) + self._canvas.coords("slider_oval_3", width - 2 * button_corner_radius, slider_y_position + button_length / 2 - button_corner_radius, width, slider_y_position + button_length / 2 + button_corner_radius) + self._canvas.coords("slider_oval_4", width - 2 * button_corner_radius, slider_y_position - button_length / 2 - button_corner_radius, width, slider_y_position - button_length / 2 + button_corner_radius) + self._canvas.coords("slider_rectangle_1", 0, slider_y_position - button_length / 2, width, slider_y_position + button_length / 2) + self._canvas.coords("slider_rectangle_2", button_corner_radius, slider_y_position - button_length / 2 - button_corner_radius, width - button_corner_radius, slider_y_position + button_length / 2 + button_corner_radius) + + slider_y_position = corner_radius + (button_length / 2) + (height - 2 * corner_radius - button_length) * (1 - slider_2_value) + self._canvas.coords("slider_oval_2_1", 0, slider_y_position - button_length / 2 - button_corner_radius, 2 * button_corner_radius, slider_y_position - button_length / 2 + button_corner_radius) + self._canvas.coords("slider_oval_2_2", 0, slider_y_position + button_length / 2 - button_corner_radius, 2 * button_corner_radius, slider_y_position + button_length / 2 + button_corner_radius) + self._canvas.coords("slider_oval_2_3", width - 2 * button_corner_radius, slider_y_position + button_length / 2 - button_corner_radius, width, slider_y_position + button_length / 2 + button_corner_radius) + self._canvas.coords("slider_oval_2_4", width - 2 * button_corner_radius, slider_y_position - button_length / 2 - button_corner_radius, width, slider_y_position - button_length / 2 + button_corner_radius) + self._canvas.coords("slider_rectangle_2_1", 0, slider_y_position - button_length / 2, width, slider_y_position + button_length / 2) + self._canvas.coords("slider_rectangle_2_2", button_corner_radius, slider_y_position - button_length / 2 - button_corner_radius, width - button_corner_radius, slider_y_position + button_length / 2 + button_corner_radius) + + if requires_recoloring: + self._canvas.tag_raise("slider_parts") + + return requires_recoloring + class CTkRangeSlider(CTkBaseClass): """ Range slider with rounded corners, border, number of steps, variable support, vertical orientation. diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Buffer.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Buffer.js index 6146ee8..d01f647 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Buffer.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Buffer.js @@ -1,4 +1,4 @@ -// dd_ring_buffer.js +// buffer.js // works kinda like an audio delay // stacks the previous n frames into a buffer diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Delay.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Delay.js index 56aad92..3fe3eed 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Delay.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Delay.js @@ -1,4 +1,4 @@ -// dd_delay.js +// delay.js // works kinda like an audio delay // stacks the previous n frames into a buffer diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Invert-Reverse.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Invert-Reverse.js index 8df79fe..524b19d 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Invert-Reverse.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Invert-Reverse.js @@ -1,4 +1,4 @@ -// dd_RandomDamage(invertRandomN).js +// invertReverse.js // invert x and y component of mv for random number of frames if threshold met for frame let threshold = 95; diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Mirror.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Mirror.js index 16d50a7..aa253c1 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Mirror.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Mirror.js @@ -1,4 +1,4 @@ -// dd_mirror_X.js +// mirror_X.js // clean buffer : var buffer = [ ]; diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Noise.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Noise.js index cb7d6ac..6e87903 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Noise.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Noise.js @@ -1,4 +1,4 @@ -// dd_MultiplySlowest_50.js +// noise.js // Multiply slowest moving mv's var LARGEST = 0; var SOME_PERCENTAGE = 0.5; diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Shear.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Shear.js index d076fb6..14ef588 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Shear.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Shear.js @@ -1,4 +1,4 @@ -// dd_sheer.js +// sheer.js var ZOOM = -20; diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Shift.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Shift.js index 2c463ff..012cee8 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Shift.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Shift.js @@ -1,5 +1,5 @@ -// dd_RandomDamage(antiGrav).js -// anitgravityify if threshold met for frame +// shift.js +// anitgravitify if threshold met for frame let threshold = 98; // global variable holding forward motion vectors from previous frames diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Sink.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Sink.js index a53b0e6..e8c3055 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Sink.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Sink.js @@ -1,4 +1,4 @@ -// dd_zero.js +// sink.js // only mess frames if mv > movement_threshold var movement_threshold = 3; function glitch_frame(frame) diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Slam Zoom.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Slam Zoom.js index 6993c42..c49a28f 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Slam Zoom.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Slam Zoom.js @@ -1,4 +1,4 @@ -// dd_slam_zoom_in.js +// slam_zoom.js var ZOOM = 20; diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Slice.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Slice.js index 4ae40b2..1f83139 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Slice.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Slice.js @@ -1,4 +1,4 @@ -// dd_RandomDamage(progZoom).js +// slice.js // progressive Zoom x and y components of mv if threshold met for frame let threshold = 95; diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Stop.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Stop.js index 3cf8ea4..cd3e657 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Stop.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Stop.js @@ -1,4 +1,4 @@ -// dd_RandomDamage(stopXY).js +// stop.js // stop x and y component of mv for n framesif threshold met for frame let threshold = 95; diff --git a/Python Version/DatamoshLib/FFG_effects/jscripts/Zoom.js b/Python Version/DatamoshLib/FFG_effects/jscripts/Zoom.js index 4f63b58..158c25d 100644 --- a/Python Version/DatamoshLib/FFG_effects/jscripts/Zoom.js +++ b/Python Version/DatamoshLib/FFG_effects/jscripts/Zoom.js @@ -1,4 +1,4 @@ -// dd_zoom_in.js +// zoom.js var ZOOM = 20; diff --git a/Python Version/Datamosher Pro.py b/Python Version/Datamosher Pro.py index bd9f975..3acb1c1 100644 --- a/Python Version/Datamosher Pro.py +++ b/Python Version/Datamosher Pro.py @@ -1,10 +1,10 @@ """ -DATAMOSHER PRO Py version 2.3 +DATAMOSHER PRO Py version 2.4 Author: Akash Bora (Akascape) -License: MIT | Copyright (c) 2024 Akash Bora +License: MIT | Copyright (c) 2025 Akash Bora """ -currentversion = 2.3 +currentversion = 2.4 #Import installed modules import tkinter @@ -744,6 +744,14 @@ dynamic() keepframe.select() param = "" +def open_file(sfile): + if sys.platform.startswith('darwin'): # macOS + subprocess.call(('open', sfile)) + elif os.name == 'nt': # Windows + os.startfile(sfile) + elif os.name == 'posix': # Linux + subprocess.call(('xdg-open', sfile)) + #autosave video name def savename(): global sfile @@ -885,10 +893,10 @@ def do_the_mosh(): if os.path.exists(sfile): res = messagebox.showinfo("Exported!", "File exported successfully, \nFile Location: " +str(sfile)) ProcessLabel.configure(text="Last used: "+last_used) - try: os.startfile(sfile) + try: open_file(sfile) except: pass else: - messagebox.showerror("Oops!", "Something went wrong! \nPlease recheck the settings and try again.") + messagebox.showerror("Oops!", "Something went wrong! \nPlease recheck the settings and try again.\nView error logs for more information.") ProcessLabel.configure(text='Recheck the settings and try again!') button_open.configure(state=tkinter.NORMAL) @@ -904,6 +912,8 @@ button_mosh = customtkinter.CTkButton(master=frame_right, height=30,width=110,co text="MOSH", command=Threadthis) button_mosh.pack(padx=(0,10), pady=10, fill="x", side="right") +root.after(100, root.focus_force) + root.mainloop() #--------------------------------------------------------------------# diff --git a/Python Version/Setup.py b/Python Version/Setup.py index 55d5cd6..0343782 100644 --- a/Python Version/Setup.py +++ b/Python Version/Setup.py @@ -20,6 +20,27 @@ except ImportError: DIRPATH = os.path.dirname(os.path.realpath(__file__)) +#Set executable permissions for macOS +def set_mac_permissions(): + if sys.platform.startswith("darwin"): + print("Setting executable permissions for macOS...") + ffgac = os.path.join(DIRPATH,"FFglitch","ffgac") + ffedit = os.path.join(DIRPATH,"FFglitch","ffedit") + + try: + if os.path.exists(ffgac): + os.chmod(ffgac, 0o755) + print("Permissions set for ffgac") + except Exception as e: + print(f"Failed to set permissions for ffgac: {e} \nPlease go to System Settings -> Security & Privacy -> scroll down and give permission to the ffgac file") + + try: + if os.path.exists(ffedit): + os.chmod(ffedit, 0o755) + print("Permissions set for ffedit") + except Exception as e: + print(f"Failed to set permissions for ffedit: {e} \nPlease go to System Settings -> Security & Privacy -> scroll down and give permission to the ffedit file") + #Checking the required folders folders = ["Assets","FFglitch","DatamoshLib","pymosh"] missingfolder=[] @@ -69,6 +90,8 @@ else: #Check FFglitch Status def checkffglitch(): print("Checking FFglitch:") + # Set permissions for macOS before running + set_mac_permissions() print("Running ffgac...") ffgac = os.path.join(DIRPATH,"FFglitch","ffgac") ffedit = os.path.join(DIRPATH,"FFglitch","ffedit") @@ -130,6 +153,8 @@ else: if os.path.exists(os.path.join("FFglitch","ffgac")) or os.path.exists(os.path.join("FFglitch","ffgac.exe")): os.remove(os.path.join("FFglitch","ffglitch.zip")) time.sleep(1) + # Set permissions after extraction for macOS + set_mac_permissions() checkffglitch() print("FFglitch setup complete!") except: @@ -138,6 +163,7 @@ else: print("ffglitch modes cannot run without ffgac and ffedit packages, download them manually and paste them inside the FFglitch folder.") #Everything done! +time.sleep(1) print("Setup Complete!") time.sleep(5) sys.exit() diff --git a/Python Version/requirements.txt b/Python Version/requirements.txt new file mode 100644 index 0000000..1c82620 --- /dev/null +++ b/Python Version/requirements.txt @@ -0,0 +1,7 @@ +imageio +imageio-ffmpeg +numpy +customtkinter +pillow +requests +packaging \ No newline at end of file