gradienttk 0.1.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gradienttk
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Modern Gradient Widgets for CustomTkinter
5
5
  Author: Mohammad Amin
6
6
  License-Expression: MIT
File without changes
@@ -0,0 +1,72 @@
1
+ from typing import Tuple
2
+ from .types import GradientType
3
+
4
+ def hex_to_rgb(color: str) -> Tuple[int, int, int]:
5
+ color = color.lstrip("#")
6
+
7
+ if len(color) != 6:
8
+ raise ValueError("Color must be a 6-digit HEX value.")
9
+
10
+ return (
11
+ int(color[0:2], 16),
12
+ int(color[2:4], 16),
13
+ int(color[4:6], 16),
14
+ )
15
+
16
+ def rgb_to_hex(rgb: Tuple[int, int, int]) -> str:
17
+ """
18
+ Convert RGB tuple to HEX.
19
+
20
+ Example:
21
+ (255, 0, 0) -> "#FF0000"
22
+ """
23
+ return "#{:02X}{:02X}{:02X}".format(*rgb)
24
+
25
+ def interpolate_color(
26
+ start: Tuple[int, int, int],
27
+ end: Tuple[int, int, int],
28
+ factor: float
29
+ ) -> Tuple[int, int, int]:
30
+ """
31
+ Returns a color between two RGB colors.
32
+
33
+ factor:
34
+ 0.0 -> start color
35
+ 1.0 -> end color
36
+ """
37
+
38
+ return (
39
+ int(start[0] + (end[0] - start[0]) * factor),
40
+ int(start[1] + (end[1] - start[1]) * factor),
41
+ int(start[2] + (end[2] - start[2]) * factor),
42
+ )
43
+
44
+ def darken_color(color: str, amount: float = 0.15) -> str:
45
+ """
46
+ Darken a HEX color.
47
+
48
+ amount:
49
+ 0.0 -> no change
50
+ 1.0 -> completely black
51
+ """
52
+
53
+ r, g, b = hex_to_rgb(color)
54
+
55
+ r = int(r * (1 - amount))
56
+ g = int(g * (1 - amount))
57
+ b = int(b * (1 - amount))
58
+
59
+ return rgb_to_hex((r, g, b))
60
+
61
+ def darken_gradient(
62
+ gradient: GradientType,
63
+ amount: float = 0.15,
64
+ ) -> GradientType:
65
+ """
66
+ Darken all colors in a gradient.
67
+ """
68
+
69
+ return tuple(
70
+ darken_color(color, amount)
71
+ for color in gradient
72
+ )
@@ -0,0 +1,6 @@
1
+ HORIZONTAL = "horizontal"
2
+ VERTICAL = "vertical"
3
+ NORMAL = "normal"
4
+ HOVER = "hover"
5
+ PRESSED = "pressed"
6
+ PRESS_ANIMATION_TIME = 100
@@ -0,0 +1,47 @@
1
+ from PIL import Image
2
+ from .constants import HORIZONTAL
3
+
4
+ from .colors import hex_to_rgb, interpolate_color
5
+ from .image import apply_corner_radius
6
+ from typing import Tuple
7
+
8
+ GradientType = Tuple[str, str]
9
+
10
+
11
+ class GradientEngine:
12
+ def __init__(
13
+ self,
14
+ gradient: GradientType,
15
+ width: int,
16
+ height: int,
17
+ direction: str = HORIZONTAL,
18
+ corner_radius: int = 12,
19
+ ):
20
+ self.gradient = gradient
21
+ self.width = width
22
+ self.height = height
23
+ self.direction = direction
24
+ self.corner_radius = corner_radius
25
+
26
+ def generate(self) -> Image.Image:
27
+ image = Image.new("RGB", (self.width, self.height))
28
+
29
+ pixels = image.load()
30
+
31
+ start = hex_to_rgb(self.gradient[0])
32
+ end = hex_to_rgb(self.gradient[1])
33
+
34
+ if self.direction == "horizontal":
35
+
36
+ for x in range(self.width):
37
+
38
+ factor = x / (self.width - 1)
39
+
40
+ color = interpolate_color(start, end, factor)
41
+
42
+ for y in range(self.height):
43
+ pixels[x, y] = color
44
+
45
+ image = apply_corner_radius(image, self.corner_radius)
46
+
47
+ return image
@@ -0,0 +1,22 @@
1
+ from PIL import Image, ImageDraw
2
+
3
+
4
+ def apply_corner_radius(image: Image.Image, radius: int) -> Image.Image:
5
+ """Apply rounded corners to a PIL image."""
6
+
7
+ if radius <= 0:
8
+ return image
9
+
10
+ mask = Image.new("L", image.size, 0)
11
+
12
+ draw = ImageDraw.Draw(mask)
13
+
14
+ draw.rounded_rectangle(
15
+ (0, 0, image.width, image.height),
16
+ radius=radius,
17
+ fill=255,
18
+ )
19
+
20
+ image.putalpha(mask)
21
+
22
+ return image
@@ -0,0 +1,3 @@
1
+ from typing import Tuple
2
+
3
+ GradientType = Tuple[str, ...]
@@ -0,0 +1 @@
1
+ from .button import GradientButton
@@ -0,0 +1,215 @@
1
+ import tkinter as tk
2
+
3
+ import customtkinter as ctk
4
+ from PIL import ImageTk
5
+
6
+ from gradienttk.core.colors import darken_gradient
7
+ from gradienttk.core.constants import (
8
+ HORIZONTAL,
9
+ HOVER,
10
+ NORMAL,
11
+ PRESSED,
12
+ PRESS_ANIMATION_TIME
13
+ )
14
+ from gradienttk.core.gradient import GradientEngine
15
+
16
+
17
+ class GradientButton(ctk.CTkFrame):
18
+ """
19
+ GradientTk Button Widget
20
+ """
21
+
22
+ def __init__(
23
+ self,
24
+ master,
25
+ text="",
26
+ gradient=("#6C63FF", "#00C9FF"),
27
+ direction: str = HORIZONTAL,
28
+ command=None,
29
+ corner_radius: int = 12,
30
+ width=140,
31
+ height=35,
32
+ **kwargs
33
+ ):
34
+ super().__init__(
35
+ master,
36
+ width=width,
37
+ height=height,
38
+ fg_color="transparent",
39
+ **kwargs
40
+ )
41
+
42
+ self._canvas_bg = self._apply_appearance_mode(self._bg_color)
43
+
44
+ self._text = text
45
+ self._command = command
46
+ self._width = width
47
+ self._height = height
48
+ self._corner_radius = corner_radius
49
+ self._state = NORMAL
50
+
51
+ self._gradient = gradient
52
+ self._direction = direction
53
+ self._hover_gradient = darken_gradient(self._gradient)
54
+
55
+ self._engine = GradientEngine(
56
+ gradient=self._gradient,
57
+ width=self._width,
58
+ height=self._height,
59
+ direction=self._direction,
60
+ corner_radius=self._corner_radius,
61
+ )
62
+
63
+ self._canvas = tk.Canvas(
64
+ self,
65
+ width=width,
66
+ height=height,
67
+ highlightthickness=0,
68
+ bd=0,
69
+ relief="flat",
70
+ bg=self._canvas_bg,
71
+ )
72
+
73
+ self._canvas.bind("<Enter>", self._on_enter)
74
+ self._canvas.bind("<Leave>", self._on_leave)
75
+
76
+ self._canvas.bind("<ButtonPress-1>", self._on_press)
77
+ self._canvas.bind("<ButtonRelease-1>", self._on_release)
78
+
79
+ self._canvas.pack(fill="both", expand=True)
80
+
81
+ self.bind("<Configure>", self._on_resize)
82
+
83
+ self._render()
84
+
85
+ def configure(self, require_redraw=True, **kwargs):
86
+
87
+ if "text" in kwargs:
88
+ self._text = kwargs.pop("text")
89
+
90
+ if "gradient" in kwargs:
91
+ self._gradient = kwargs.pop("gradient")
92
+ self._hover_gradient = darken_gradient(self._gradient)
93
+ self._engine.gradient = self._gradient
94
+
95
+ if "command" in kwargs:
96
+ self._command = kwargs.pop("command")
97
+
98
+ if "corner_radius" in kwargs:
99
+ self._corner_radius = kwargs.pop("corner_radius")
100
+ self._engine.corner_radius = self._corner_radius
101
+
102
+ super().configure(**kwargs)
103
+
104
+ if require_redraw:
105
+ self._render()
106
+
107
+ def cget(self, attribute):
108
+
109
+ if attribute == "text":
110
+ return self._text
111
+
112
+ if attribute == "gradient":
113
+ return self._gradient
114
+
115
+ if attribute == "command":
116
+ return self._command
117
+
118
+ if attribute == "corner_radius":
119
+ return self._corner_radius
120
+
121
+ return super().cget(attribute)
122
+
123
+ def set_text(self, text):
124
+ self.configure(text=text)
125
+
126
+ def set_gradient(self, start, end):
127
+ self.configure(gradient=(start, end))
128
+
129
+ def _on_resize(self, event):
130
+
131
+ if event.width <= 1 or event.height <= 1:
132
+ return
133
+
134
+ if event.width == self._width and event.height == self._height:
135
+ return
136
+
137
+ self._width = event.width
138
+ self._height = event.height
139
+
140
+ self._engine.width = self._width
141
+ self._engine.height = self._height
142
+
143
+ self._canvas.config(
144
+ width=self._width,
145
+ height=self._height
146
+ )
147
+
148
+ self._render()
149
+
150
+ def _on_enter(self, event):
151
+
152
+ self._state = HOVER
153
+
154
+ self._set_gradient(self._hover_gradient)
155
+
156
+ def _on_leave(self, event):
157
+
158
+ self._state = NORMAL
159
+
160
+ self._set_gradient(self._gradient)
161
+
162
+ def _on_press(self, event):
163
+
164
+ if callable(self._command):
165
+ self._command()
166
+
167
+ self._state = PRESSED
168
+
169
+ self._set_gradient(self._gradient)
170
+
171
+ def _on_release(self, event):
172
+
173
+ if self._state != PRESSED:
174
+ return
175
+
176
+ self.after(
177
+ PRESS_ANIMATION_TIME,
178
+ self._restore_hover
179
+ )
180
+
181
+ def _render(self):
182
+ self._canvas.delete("all")
183
+
184
+ self._photo = self._create_gradient()
185
+
186
+ self._canvas.create_image(
187
+ 0,
188
+ 0,
189
+ anchor="nw",
190
+ image=self._photo,
191
+ )
192
+
193
+ self._canvas.create_text(
194
+ self._width // 2,
195
+ self._height // 2,
196
+ text=self._text,
197
+ fill="white",
198
+ )
199
+
200
+ def _set_gradient(self, gradient):
201
+ self._engine.gradient = gradient
202
+ self._render()
203
+
204
+ def _create_gradient(self):
205
+ image = self._engine.generate()
206
+ return ImageTk.PhotoImage(image)
207
+
208
+ def _restore_hover(self):
209
+
210
+ if self._state != PRESSED:
211
+ return
212
+
213
+ self._state = HOVER
214
+
215
+ self._set_gradient(self._hover_gradient)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gradienttk
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Modern Gradient Widgets for CustomTkinter
5
5
  Author: Mohammad Amin
6
6
  License-Expression: MIT
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ gradienttk/__init__.py
5
+ gradienttk.egg-info/PKG-INFO
6
+ gradienttk.egg-info/SOURCES.txt
7
+ gradienttk.egg-info/dependency_links.txt
8
+ gradienttk.egg-info/requires.txt
9
+ gradienttk.egg-info/top_level.txt
10
+ gradienttk/core/__init__.py
11
+ gradienttk/core/colors.py
12
+ gradienttk/core/constants.py
13
+ gradienttk/core/gradient.py
14
+ gradienttk/core/image.py
15
+ gradienttk/core/types.py
16
+ gradienttk/widgets/__init__.py
17
+ gradienttk/widgets/button.py
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "gradienttk"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "Modern Gradient Widgets for CustomTkinter"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -31,5 +31,6 @@ dependencies = [
31
31
  "Pillow>=10.0.0"
32
32
  ]
33
33
 
34
- [tool.setuptools]
35
- packages = ["gradienttk"]
34
+ [tool.setuptools.packages.find]
35
+ where = ["."]
36
+ include = ["gradienttk*"]
@@ -1,9 +0,0 @@
1
- LICENSE
2
- README.md
3
- pyproject.toml
4
- gradienttk/__init__.py
5
- gradienttk.egg-info/PKG-INFO
6
- gradienttk.egg-info/SOURCES.txt
7
- gradienttk.egg-info/dependency_links.txt
8
- gradienttk.egg-info/requires.txt
9
- gradienttk.egg-info/top_level.txt
File without changes
File without changes
File without changes