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.
- {gradienttk-0.1.1 → gradienttk-0.1.2}/PKG-INFO +1 -1
- gradienttk-0.1.2/gradienttk/core/__init__.py +0 -0
- gradienttk-0.1.2/gradienttk/core/colors.py +72 -0
- gradienttk-0.1.2/gradienttk/core/constants.py +6 -0
- gradienttk-0.1.2/gradienttk/core/gradient.py +47 -0
- gradienttk-0.1.2/gradienttk/core/image.py +22 -0
- gradienttk-0.1.2/gradienttk/core/types.py +3 -0
- gradienttk-0.1.2/gradienttk/widgets/__init__.py +1 -0
- gradienttk-0.1.2/gradienttk/widgets/button.py +215 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/gradienttk.egg-info/PKG-INFO +1 -1
- gradienttk-0.1.2/gradienttk.egg-info/SOURCES.txt +17 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/pyproject.toml +4 -3
- gradienttk-0.1.1/gradienttk.egg-info/SOURCES.txt +0 -9
- {gradienttk-0.1.1 → gradienttk-0.1.2}/LICENSE +0 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/README.md +0 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/gradienttk/__init__.py +0 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/gradienttk.egg-info/dependency_links.txt +0 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/gradienttk.egg-info/requires.txt +0 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/gradienttk.egg-info/top_level.txt +0 -0
- {gradienttk-0.1.1 → gradienttk-0.1.2}/setup.cfg +0 -0
|
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,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 @@
|
|
|
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)
|
|
@@ -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.
|
|
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
|
-
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["."]
|
|
36
|
+
include = ["gradienttk*"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|