From e34d37b8ebc400cec50084a38a22f731a262b192 Mon Sep 17 00:00:00 2001 From: "Divine U. Afam-Ifediogor" <38317208+definite-d@users.noreply.github.com> Date: Sun, 18 Feb 2024 16:31:49 +0100 Subject: [PATCH] v3.1.0 upload. --- src/__init__.py | 27 +++++++++++++ src/colorprocessor.py | 6 +++ src/easings.py | 90 +++++++++++++++++++++++++++++++++++++++++++ src/psg_reskinner.py | 42 +++++++++++++++++--- src/version.py | 4 +- 5 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 src/easings.py diff --git a/src/__init__.py b/src/__init__.py index d3201ad..aabded7 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -27,5 +27,32 @@ name = "psg_reskinner" from .constants import HSL_INTERPOLATION, HUE_INTERPOLATION, RGB_INTERPOLATION +from .easings import ( + EASE_CONSTANT, + EASE_IN_SINE, + EASE_OUT_SINE, + EASE_IN_OUT_SINE, + EASE_IN_QUAD, + EASE_OUT_QUAD, + EASE_IN_OUT_QUAD, + EASE_IN_CUBIC, + EASE_OUT_CUBIC, + EASE_IN_OUT_CUBIC, + EASE_IN_QUART, + EASE_OUT_QUART, + EASE_IN_OUT_QUART, + EASE_IN_QUINT, + EASE_OUT_QUINT, + EASE_IN_OUT_QUINT, + EASE_IN_EXPO, + EASE_OUT_EXPO, + EASE_IN_OUT_EXPO, + EASE_IN_CIRC, + EASE_OUT_CIRC, + EASE_IN_OUT_CIRC, + EASE_OUT_BOUNCE, + EASE_IN_BOUNCE, + EASE_IN_OUT_BOUNCE, +) from .psg_reskinner import animated_reskin, reskin, toggle_transparency from .version import __version__ diff --git a/src/colorprocessor.py b/src/colorprocessor.py index 03d0367..0239a3e 100644 --- a/src/colorprocessor.py +++ b/src/colorprocessor.py @@ -419,6 +419,12 @@ def menu_entry( index: int, configs: Dict[str, Union[str, Tuple[str, int]]], ): + configs = dict( + filter( + lambda item: item[0] in menu.entryconfigure(index).keys(), + configs.items(), + ) + ) # Filter the configs for menu entries that don't accept the full config dict. Fixes issue #11. self.config( configs, lambda **cnf: menu.entryconfigure(index, cnf), diff --git a/src/easings.py b/src/easings.py new file mode 100644 index 0000000..8063095 --- /dev/null +++ b/src/easings.py @@ -0,0 +1,90 @@ +# PSG_Reskinner +# +# Enables changing the themes of your PySimpleGUI windows and elements +# instantaneously on the fly without the need for re-instantiating the window. +# +# MIT License +# +# Copyright (c) 2023 Divine Afam-Ifediogor +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from math import cos, sin, pi, sqrt, pow + +# Easing functions adapted from https://easings.net + +EASE_CONSTANT = lambda x: x +EASE_IN_SINE = lambda x: 1 - cos((x * pi) / 2) +EASE_OUT_SINE = lambda x: sin((x * pi) / 2) +EASE_IN_OUT_SINE = lambda x: -(cos(x * pi) - 1) / 2 +EASE_IN_QUAD = lambda x: pow(x, 2) +EASE_OUT_QUAD = lambda x: 1 - (1 - x) * (1 - x) +EASE_IN_OUT_QUAD = lambda x: 2 * pow(x, 2) if x < 0.5 else 1 - pow(-2 * x + 2, 2) / 2 +EASE_IN_CUBIC = lambda x: pow(x, 3) +EASE_OUT_CUBIC = lambda x: 1 - pow(1 - x, 3) +EASE_IN_OUT_CUBIC = lambda x: 4 * pow(x, 3) if x < 0.5 else 1 - pow(-2 * x + 2, 3) / 2 +EASE_IN_QUART = lambda x: pow(x, 4) +EASE_OUT_QUART = lambda x: 1 - pow(1 - x, 4) +EASE_IN_OUT_QUART = lambda x: 8 * pow(x, 4) if x < 0.5 else 1 - pow(-2 * x + 2, 4) / 2 +EASE_IN_QUINT = lambda x: pow(x, 5) +EASE_OUT_QUINT = lambda x: 1 - pow(1 - x, 5) +EASE_IN_OUT_QUINT = lambda x: 16 * pow(x, 5) if x < 0.5 else 1 - pow(-2 * x + 2, 5) / 2 +EASE_IN_EXPO = lambda x: 0 if x == 0 else pow(2, 10 * x - 10) +EASE_OUT_EXPO = lambda x: 1 if x == 1 else 1 - pow(2, -10 * x) +EASE_IN_OUT_EXPO = ( + lambda x: 0 + if x == 0 + else 1 + if x == 1 + else pow(2, 20 * x - 10) / 2 + if x < 0.5 + else (2 - pow(2, -20 * x + 10)) / 2 +) +EASE_IN_CIRC = lambda x: 1 - sqrt(1 - pow(x, 2)) +EASE_OUT_CIRC = lambda x: sqrt(1 - pow(x - 1, 2)) +EASE_IN_OUT_CIRC = ( + lambda x: (1 - sqrt(1 - pow(2 * x, 2))) / 2 + if x < 0.5 + else (sqrt(1 - pow(-2 * x + 2, 2)) + 1) / 2 +) + + +def _ease_out_bounce(x): + n1 = 7.5625 + d1 = 2.75 + if x < 1 / d1: + return n1 * x * x + elif x < 2 / d1: + x -= 1.5 + return n1 * (x / d1) * x + 0.75 + elif x < 2.5 / d1: + x -= 2.25 + return n1 * (x / d1) * x + 0.9375 + else: + x -= 2.625 + return n1 * (x / d1) * x + 0.984375 + + +EASE_OUT_BOUNCE = lambda x: _ease_out_bounce(x) # For uniformity. +EASE_IN_BOUNCE = lambda x: 1 - _ease_out_bounce(1 - x) +EASE_IN_OUT_BOUNCE = ( + lambda x: (1 - _ease_out_bounce(1 - 2 * x)) / 2 + if x < 0.5 + else (1 + _ease_out_bounce(2 * x - 1)) / 2 +) diff --git a/src/psg_reskinner.py b/src/psg_reskinner.py index ac345c3..3bc50ac 100644 --- a/src/psg_reskinner.py +++ b/src/psg_reskinner.py @@ -55,6 +55,7 @@ WINDOW_THEME_MAP, ) from .deprecation import deprecation_trigger +from .easings import EASE_CONSTANT from .utilities import _lower_class_name from .version import __version__ @@ -113,7 +114,6 @@ def reskin( cp: ColorProcessor = kwargs.get("_color_processor") old_theme = kwargs.get("_old_theme") new_theme = kwargs.get("_new_theme") - styler = cp.styler old_theme_dict = cp.old_theme_dict new_theme_dict = cp.new_theme_dict @@ -155,9 +155,9 @@ def reskin( # TTK Scrollbars if getattr(element, "vsb_style_name", False): - cp.scrollbar(element.vsb_style_name, "Vertical.TScrollbar") + cp.scrollbar(element.vsb_style_name, "Vertical.TScrollbar") # noqa if getattr(element, "hsb_style_name", False): - cp.scrollbar(element.hsb_style_name, "Horizontal.TScrollbar") + cp.scrollbar(element.hsb_style_name, "Horizontal.TScrollbar") # noqa if getattr( element, "ttk_style_name", False ) and element.ttk_style_name.endswith("TScrollbar"): @@ -238,7 +238,7 @@ def reskin( }, ) if getattr(element, "TKMenu", False): - cp.recurse_menu(element.TKMenu) + cp.recurse_menu(element.TKMenu) # noqa continue elif el == "canvas": @@ -395,7 +395,31 @@ def animated_reskin( interpolation_mode: Union[ RGB_INTERPOLATION, HUE_INTERPOLATION, HSL_INTERPOLATION ] = RGB_INTERPOLATION, + easing_function: Callable[[float], float] = EASE_CONSTANT, ): + """ + Does the same as a regular reskin, but animates the effect over time. + + First available from v2.2.0. + The `interpolation_mode` argument was added in v2.3.4. + The `easing_function` argument was added in v3.1.0. + + :param duration_in_milliseconds: The duration of the animation in milliseconds. + :param interpolation_mode: Determines how interpolation is to be handled. May be `RGB_INTERPOLATION`, + `HUE_INTERPOLATION`, or `HSL_INTERPOLATION`. + :param easing_function: A callable acting as an easing function. Other available modes are prefixed with `EASE`. + + :param window: The window to operate on. + :param new_theme: The theme to transition to. + :param theme_function: The theme change function from PySimpleGUI within your code. Required because of namespaces. + :param lf_table: The LOOK_AND_FEEL_TABLE from PySimpleGUI from within your code. The `new_theme` should be in there. + :param set_future: If set to True, the `new_theme` will be applied to all future windows. + :param element_filter: A callable of your choice that takes an element as its only parameter and returns True or + False. Elements that result in True will be reskinned, and others will be skipped. + :param reskin_background: If True, the background color of the window will be reskinned. Else, it won't. + + :return: + """ delta = timedelta(milliseconds=duration_in_milliseconds) start_time = datetime.now() end_time = start_time + delta @@ -421,7 +445,7 @@ def animated_reskin( old_theme_dict, new_theme_dict, styler, 0, interpolation_mode ) while datetime.now() <= end_time: - cp.progress = round((datetime.now() - start_time) / delta, 4) + cp.progress = easing_function(round((datetime.now() - start_time) / delta, 4)) try: reskin( window, @@ -492,7 +516,13 @@ def main(): right_click_menu = [ "", - [["Hi", ["Next Level", ["Deeper Level", ["a", "b", "c"]], "Hoho"]], "There"], + [ + [ + "Hi", + ["Next Level", ["Deeper Level", "---", "as", ["a", "b", "c"]], "Hoho"], + ], + "There", + ], ] window_layout = [ diff --git a/src/version.py b/src/version.py index 4b3f2e9..943c279 100644 --- a/src/version.py +++ b/src/version.py @@ -25,5 +25,5 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -__version__: str = "3.0.0" -__deprecation_condition__: bool = int(__version__.split(".")[1]) >= 1 +__version__: str = "3.1.0" +__deprecation_condition__: bool = int(__version__.split(".")[1]) >= 2