diff --git a/.gitignore b/.gitignore
index f7408b3..5aca880 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,8 +4,13 @@ __pycache__/
*.pyo
*.egg-info/
.venv-macos-build/
-dist/
-build/
+dist/*
+!dist/TgWsProxy.app/
+!dist/TgWsProxy.app/**
+!dist/TgWsProxy-macos-universal.zip
+build/*
+!build/macos/
+!build/macos/**
*.spec.bak
# PyInstaller
diff --git a/build/macos/TgWsProxy.icns b/build/macos/TgWsProxy.icns
new file mode 100644
index 0000000..3b5e603
Binary files /dev/null and b/build/macos/TgWsProxy.icns differ
diff --git a/build/macos/TgWsProxy.iconset/icon_128x128.png b/build/macos/TgWsProxy.iconset/icon_128x128.png
new file mode 100644
index 0000000..5bd9c73
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_128x128.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_128x128@2x.png b/build/macos/TgWsProxy.iconset/icon_128x128@2x.png
new file mode 100644
index 0000000..091007e
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_128x128@2x.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_16x16.png b/build/macos/TgWsProxy.iconset/icon_16x16.png
new file mode 100644
index 0000000..23abda9
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_16x16.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_16x16@2x.png b/build/macos/TgWsProxy.iconset/icon_16x16@2x.png
new file mode 100644
index 0000000..34e00b4
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_16x16@2x.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_256x256.png b/build/macos/TgWsProxy.iconset/icon_256x256.png
new file mode 100644
index 0000000..091007e
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_256x256.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_256x256@2x.png b/build/macos/TgWsProxy.iconset/icon_256x256@2x.png
new file mode 100644
index 0000000..52b9b9b
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_256x256@2x.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_32x32.png b/build/macos/TgWsProxy.iconset/icon_32x32.png
new file mode 100644
index 0000000..34e00b4
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_32x32.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_32x32@2x.png b/build/macos/TgWsProxy.iconset/icon_32x32@2x.png
new file mode 100644
index 0000000..22e1df9
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_32x32@2x.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_512x512.png b/build/macos/TgWsProxy.iconset/icon_512x512.png
new file mode 100644
index 0000000..52b9b9b
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_512x512.png differ
diff --git a/build/macos/TgWsProxy.iconset/icon_512x512@2x.png b/build/macos/TgWsProxy.iconset/icon_512x512@2x.png
new file mode 100644
index 0000000..587f6de
Binary files /dev/null and b/build/macos/TgWsProxy.iconset/icon_512x512@2x.png differ
diff --git a/dist/TgWsProxy-macos-universal.zip b/dist/TgWsProxy-macos-universal.zip
new file mode 100644
index 0000000..daeeeb9
Binary files /dev/null and b/dist/TgWsProxy-macos-universal.zip differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/AppKit/_AppKit.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/AppKit/_AppKit.cpython-39-darwin.so
new file mode 100755
index 0000000..6e86e9e
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/AppKit/_AppKit.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/AppKit/_inlines.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/AppKit/_inlines.cpython-39-darwin.so
new file mode 100755
index 0000000..8ea4230
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/AppKit/_inlines.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/CoreFoundation/_CoreFoundation.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/CoreFoundation/_CoreFoundation.cpython-39-darwin.so
new file mode 100755
index 0000000..e5d3458
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/CoreFoundation/_CoreFoundation.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/CoreFoundation/_inlines.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/CoreFoundation/_inlines.cpython-39-darwin.so
new file mode 100755
index 0000000..be6bb55
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/CoreFoundation/_inlines.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Foundation/_Foundation.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/Foundation/_Foundation.cpython-39-darwin.so
new file mode 100755
index 0000000..39db16f
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/Foundation/_Foundation.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Foundation/_inlines.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/Foundation/_inlines.cpython-39-darwin.so
new file mode 100755
index 0000000..ca47d47
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/Foundation/_inlines.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imaging.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imaging.cpython-39-darwin.so
new file mode 100755
index 0000000..f9bf648
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imaging.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingcms.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingcms.cpython-39-darwin.so
new file mode 100755
index 0000000..9e12a2f
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingcms.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingft.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingft.cpython-39-darwin.so
new file mode 100755
index 0000000..66a9254
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingft.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingmath.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingmath.cpython-39-darwin.so
new file mode 100755
index 0000000..2bddb6b
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingmath.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingtk.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingtk.cpython-39-darwin.so
new file mode 100755
index 0000000..1872780
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_imagingtk.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/PIL/_webp.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_webp.cpython-39-darwin.so
new file mode 100755
index 0000000..84a5f37
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/PIL/_webp.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Python3 b/dist/TgWsProxy.app/Contents/Frameworks/Python3
new file mode 120000
index 0000000..5e5b056
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/Python3
@@ -0,0 +1 @@
+Python3.framework/Versions/3.9/Python3
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Python3 b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Python3
new file mode 120000
index 0000000..ee0532f
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Python3
@@ -0,0 +1 @@
+Versions/Current/Python3
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Resources b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Resources
new file mode 120000
index 0000000..953ee36
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Resources
@@ -0,0 +1 @@
+Versions/Current/Resources
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/Python3 b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/Python3
new file mode 100755
index 0000000..d0a14dc
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/Python3 differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/Resources/Info.plist b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/Resources/Info.plist
new file mode 100644
index 0000000..0cc34fe
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/Resources/Info.plist differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/_CodeSignature/CodeResources b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/_CodeSignature/CodeResources
new file mode 100644
index 0000000..490967a
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/3.9/_CodeSignature/CodeResources
@@ -0,0 +1,128 @@
+
+
+
+
+ files
+
+ Resources/Info.plist
+
+ mpTMZhoGmuy9cb2MGi/LNXgUe/A=
+
+
+ files2
+
+ Resources/Info.plist
+
+ hash2
+
+ 45iX5G1RMaAUXU6vcQRXzqGTN9fSMfrPO9QWnEAdC6A=
+
+
+
+ rules
+
+ ^Resources/
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
+
+ nested
+
+ weight
+ 10
+
+ ^.*
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^Resources/
+
+ weight
+ 20
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^[^/]+$
+
+ nested
+
+ weight
+ 10
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/Current b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/Current
new file mode 120000
index 0000000..a02597f
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/Python3.framework/Versions/Current
@@ -0,0 +1 @@
+3.9
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/_cffi_backend.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/_cffi_backend.cpython-39-darwin.so
new file mode 100755
index 0000000..a37d195
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/_cffi_backend.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/base_library.zip b/dist/TgWsProxy.app/Contents/Frameworks/base_library.zip
new file mode 120000
index 0000000..89ddc93
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/base_library.zip
@@ -0,0 +1 @@
+../Resources/base_library.zip
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/cryptography-46.0.5.dist-info b/dist/TgWsProxy.app/Contents/Frameworks/cryptography-46.0.5.dist-info
new file mode 120000
index 0000000..d897d8f
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/cryptography-46.0.5.dist-info
@@ -0,0 +1 @@
+../Resources/cryptography-46.0.5.dist-info
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/cryptography/hazmat/bindings/_rust.abi3.so b/dist/TgWsProxy.app/Contents/Frameworks/cryptography/hazmat/bindings/_rust.abi3.so
new file mode 100755
index 0000000..8879894
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/cryptography/hazmat/bindings/_rust.abi3.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/customtkinter b/dist/TgWsProxy.app/Contents/Frameworks/customtkinter
new file mode 120000
index 0000000..540eb03
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/customtkinter
@@ -0,0 +1 @@
+../Resources/customtkinter
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/icon.ico b/dist/TgWsProxy.app/Contents/Frameworks/icon.ico
new file mode 120000
index 0000000..d38da5e
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Frameworks/icon.ico
@@ -0,0 +1 @@
+../Resources/icon.ico
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_asyncio.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_asyncio.cpython-39-darwin.so
new file mode 100755
index 0000000..0477904
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_asyncio.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_bisect.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_bisect.cpython-39-darwin.so
new file mode 100755
index 0000000..b9d09eb
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_bisect.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_blake2.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_blake2.cpython-39-darwin.so
new file mode 100755
index 0000000..3343373
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_blake2.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_bz2.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_bz2.cpython-39-darwin.so
new file mode 100755
index 0000000..cb4fcf1
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_bz2.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_cn.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_cn.cpython-39-darwin.so
new file mode 100755
index 0000000..d567a76
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_cn.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_hk.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_hk.cpython-39-darwin.so
new file mode 100755
index 0000000..21b5511
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_hk.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_iso2022.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_iso2022.cpython-39-darwin.so
new file mode 100755
index 0000000..ef7572f
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_iso2022.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_jp.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_jp.cpython-39-darwin.so
new file mode 100755
index 0000000..68becf0
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_jp.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_kr.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_kr.cpython-39-darwin.so
new file mode 100755
index 0000000..6660ac4
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_kr.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_tw.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_tw.cpython-39-darwin.so
new file mode 100755
index 0000000..16cdeb3
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_codecs_tw.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_contextvars.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_contextvars.cpython-39-darwin.so
new file mode 100755
index 0000000..300309c
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_contextvars.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_csv.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_csv.cpython-39-darwin.so
new file mode 100755
index 0000000..48e0785
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_csv.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_ctypes.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_ctypes.cpython-39-darwin.so
new file mode 100755
index 0000000..1c2982b
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_ctypes.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_curses.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_curses.cpython-39-darwin.so
new file mode 100755
index 0000000..1c6557a
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_curses.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_datetime.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_datetime.cpython-39-darwin.so
new file mode 100755
index 0000000..d1ee21b
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_datetime.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_decimal.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_decimal.cpython-39-darwin.so
new file mode 100755
index 0000000..7d61c06
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_decimal.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_elementtree.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_elementtree.cpython-39-darwin.so
new file mode 100755
index 0000000..ae710bb
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_elementtree.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_hashlib.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_hashlib.cpython-39-darwin.so
new file mode 100755
index 0000000..3bc9189
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_hashlib.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_heapq.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_heapq.cpython-39-darwin.so
new file mode 100755
index 0000000..a368d81
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_heapq.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_json.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_json.cpython-39-darwin.so
new file mode 100755
index 0000000..81bb844
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_json.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_lzma.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_lzma.cpython-39-darwin.so
new file mode 100755
index 0000000..88ca5fa
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_lzma.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_md5.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_md5.cpython-39-darwin.so
new file mode 100755
index 0000000..c428e5c
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_md5.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_multibytecodec.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_multibytecodec.cpython-39-darwin.so
new file mode 100755
index 0000000..8e0f82d
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_multibytecodec.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_multiprocessing.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_multiprocessing.cpython-39-darwin.so
new file mode 100755
index 0000000..644f0c1
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_multiprocessing.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_opcode.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_opcode.cpython-39-darwin.so
new file mode 100755
index 0000000..f6b2ec7
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_opcode.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_pickle.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_pickle.cpython-39-darwin.so
new file mode 100755
index 0000000..be52b1f
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_pickle.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_posixshmem.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_posixshmem.cpython-39-darwin.so
new file mode 100755
index 0000000..f12adc4
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_posixshmem.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_posixsubprocess.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_posixsubprocess.cpython-39-darwin.so
new file mode 100755
index 0000000..6f01668
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_posixsubprocess.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_queue.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_queue.cpython-39-darwin.so
new file mode 100755
index 0000000..27da3f5
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_queue.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_random.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_random.cpython-39-darwin.so
new file mode 100755
index 0000000..c937cb8
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_random.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_scproxy.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_scproxy.cpython-39-darwin.so
new file mode 100755
index 0000000..bf6bc8c
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_scproxy.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha1.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha1.cpython-39-darwin.so
new file mode 100755
index 0000000..ba91a5f
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha1.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha256.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha256.cpython-39-darwin.so
new file mode 100755
index 0000000..230f8c9
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha256.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha3.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha3.cpython-39-darwin.so
new file mode 100755
index 0000000..c458d31
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha3.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha512.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha512.cpython-39-darwin.so
new file mode 100755
index 0000000..97b4e42
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_sha512.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_socket.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_socket.cpython-39-darwin.so
new file mode 100755
index 0000000..fc43ba3
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_socket.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_ssl.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_ssl.cpython-39-darwin.so
new file mode 100755
index 0000000..10a8185
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_ssl.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_statistics.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_statistics.cpython-39-darwin.so
new file mode 100755
index 0000000..9ada109
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_statistics.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_struct.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_struct.cpython-39-darwin.so
new file mode 100755
index 0000000..3d7a434
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_struct.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_tkinter.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_tkinter.cpython-39-darwin.so
new file mode 100755
index 0000000..5b93beb
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_tkinter.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_uuid.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_uuid.cpython-39-darwin.so
new file mode 100755
index 0000000..736be80
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/_uuid.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/array.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/array.cpython-39-darwin.so
new file mode 100755
index 0000000..64ed97e
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/array.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/binascii.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/binascii.cpython-39-darwin.so
new file mode 100755
index 0000000..6a450a2
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/binascii.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/grp.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/grp.cpython-39-darwin.so
new file mode 100755
index 0000000..a616751
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/grp.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/math.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/math.cpython-39-darwin.so
new file mode 100755
index 0000000..b394279
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/math.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/mmap.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/mmap.cpython-39-darwin.so
new file mode 100755
index 0000000..b54740e
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/mmap.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/pyexpat.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/pyexpat.cpython-39-darwin.so
new file mode 100755
index 0000000..a2a37b3
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/pyexpat.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/readline.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/readline.cpython-39-darwin.so
new file mode 100755
index 0000000..104c3d8
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/readline.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/resource.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/resource.cpython-39-darwin.so
new file mode 100755
index 0000000..dc8c686
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/resource.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/select.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/select.cpython-39-darwin.so
new file mode 100755
index 0000000..6664941
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/select.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/termios.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/termios.cpython-39-darwin.so
new file mode 100755
index 0000000..c52d071
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/termios.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/unicodedata.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/unicodedata.cpython-39-darwin.so
new file mode 100755
index 0000000..7a1b9e3
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/unicodedata.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/zlib.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/zlib.cpython-39-darwin.so
new file mode 100755
index 0000000..bbf8e67
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/lib-dynload/zlib.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libXau.6.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libXau.6.dylib
new file mode 100755
index 0000000..243ae4a
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libXau.6.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libXdmcp.6.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libXdmcp.6.dylib
new file mode 100755
index 0000000..cd8f20b
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libXdmcp.6.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libfreetype.6.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libfreetype.6.dylib
new file mode 100755
index 0000000..63d79a5
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libfreetype.6.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libjpeg.8.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libjpeg.8.dylib
new file mode 100755
index 0000000..7d9ffec
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libjpeg.8.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/liblcms2.2.dylib b/dist/TgWsProxy.app/Contents/Frameworks/liblcms2.2.dylib
new file mode 100755
index 0000000..5b4b028
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/liblcms2.2.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/liblzma.5.dylib b/dist/TgWsProxy.app/Contents/Frameworks/liblzma.5.dylib
new file mode 100755
index 0000000..7d9dbbb
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/liblzma.5.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libopenjp2.7.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libopenjp2.7.dylib
new file mode 100755
index 0000000..3e350de
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libopenjp2.7.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libpng16.16.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libpng16.16.dylib
new file mode 100755
index 0000000..861c7c5
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libpng16.16.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libsharpyuv.0.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libsharpyuv.0.dylib
new file mode 100755
index 0000000..bf981f5
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libsharpyuv.0.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libtiff.6.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libtiff.6.dylib
new file mode 100755
index 0000000..fbea890
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libtiff.6.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libwebp.7.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libwebp.7.dylib
new file mode 100755
index 0000000..e33ef06
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libwebp.7.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libwebpdemux.2.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libwebpdemux.2.dylib
new file mode 100755
index 0000000..1bc7ba2
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libwebpdemux.2.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libwebpmux.3.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libwebpmux.3.dylib
new file mode 100755
index 0000000..68c5cfb
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libwebpmux.3.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libxcb.1.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libxcb.1.dylib
new file mode 100755
index 0000000..41135f9
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libxcb.1.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/libzstd.1.dylib b/dist/TgWsProxy.app/Contents/Frameworks/libzstd.1.dylib
new file mode 100755
index 0000000..155d70f
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/libzstd.1.dylib differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/objc/_machsignals.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/objc/_machsignals.cpython-39-darwin.so
new file mode 100755
index 0000000..ade056f
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/objc/_machsignals.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/objc/_objc.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Frameworks/objc/_objc.cpython-39-darwin.so
new file mode 100755
index 0000000..99bbbd0
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/objc/_objc.cpython-39-darwin.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/psutil/_psutil_osx.abi3.so b/dist/TgWsProxy.app/Contents/Frameworks/psutil/_psutil_osx.abi3.so
new file mode 100755
index 0000000..78efb00
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/psutil/_psutil_osx.abi3.so differ
diff --git a/dist/TgWsProxy.app/Contents/Frameworks/psutil/_psutil_posix.abi3.so b/dist/TgWsProxy.app/Contents/Frameworks/psutil/_psutil_posix.abi3.so
new file mode 100755
index 0000000..7ef6599
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Frameworks/psutil/_psutil_posix.abi3.so differ
diff --git a/dist/TgWsProxy.app/Contents/Info.plist b/dist/TgWsProxy.app/Contents/Info.plist
new file mode 100644
index 0000000..55b40a6
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Info.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDisplayName
+ TG WS Proxy
+ CFBundleExecutable
+ TgWsProxy
+ CFBundleIconFile
+ TgWsProxy.icns
+ CFBundleIdentifier
+ org.flowseal.tgwsproxy
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ TG WS Proxy
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0.0
+ LSUIElement
+
+ NSHighResolutionCapable
+
+
+
diff --git a/dist/TgWsProxy.app/Contents/MacOS/TgWsProxy b/dist/TgWsProxy.app/Contents/MacOS/TgWsProxy
new file mode 100755
index 0000000..81740ec
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/MacOS/TgWsProxy differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/AppKit b/dist/TgWsProxy.app/Contents/Resources/AppKit
new file mode 120000
index 0000000..15dda83
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/AppKit
@@ -0,0 +1 @@
+../Frameworks/AppKit
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/CoreFoundation b/dist/TgWsProxy.app/Contents/Resources/CoreFoundation
new file mode 120000
index 0000000..d6839eb
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/CoreFoundation
@@ -0,0 +1 @@
+../Frameworks/CoreFoundation
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/Foundation b/dist/TgWsProxy.app/Contents/Resources/Foundation
new file mode 120000
index 0000000..2d4c792
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/Foundation
@@ -0,0 +1 @@
+../Frameworks/Foundation
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/PIL b/dist/TgWsProxy.app/Contents/Resources/PIL
new file mode 120000
index 0000000..bf9baca
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/PIL
@@ -0,0 +1 @@
+../Frameworks/PIL
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/Python3 b/dist/TgWsProxy.app/Contents/Resources/Python3
new file mode 120000
index 0000000..5e5b056
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/Python3
@@ -0,0 +1 @@
+Python3.framework/Versions/3.9/Python3
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/Python3.framework b/dist/TgWsProxy.app/Contents/Resources/Python3.framework
new file mode 120000
index 0000000..e3e7c60
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/Python3.framework
@@ -0,0 +1 @@
+../Frameworks/Python3.framework
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/TgWsProxy.icns b/dist/TgWsProxy.app/Contents/Resources/TgWsProxy.icns
new file mode 100644
index 0000000..3b5e603
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Resources/TgWsProxy.icns differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/_cffi_backend.cpython-39-darwin.so b/dist/TgWsProxy.app/Contents/Resources/_cffi_backend.cpython-39-darwin.so
new file mode 120000
index 0000000..3e1eda6
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/_cffi_backend.cpython-39-darwin.so
@@ -0,0 +1 @@
+../Frameworks/_cffi_backend.cpython-39-darwin.so
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/base_library.zip b/dist/TgWsProxy.app/Contents/Resources/base_library.zip
new file mode 100644
index 0000000..d0ceec5
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Resources/base_library.zip differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography b/dist/TgWsProxy.app/Contents/Resources/cryptography
new file mode 120000
index 0000000..b2da189
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography
@@ -0,0 +1 @@
+../Frameworks/cryptography
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/INSTALLER b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/METADATA b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/METADATA
new file mode 100644
index 0000000..15080bb
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/METADATA
@@ -0,0 +1,139 @@
+Metadata-Version: 2.4
+Name: cryptography
+Version: 46.0.5
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: POSIX :: BSD
+Classifier: Operating System :: POSIX :: Linux
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
+Classifier: Topic :: Security :: Cryptography
+Requires-Dist: cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy'
+Requires-Dist: cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy'
+Requires-Dist: typing-extensions>=4.13.2 ; python_full_version < '3.11'
+Requires-Dist: bcrypt>=3.1.5 ; extra == 'ssh'
+Requires-Dist: nox[uv]>=2024.4.15 ; extra == 'nox'
+Requires-Dist: cryptography-vectors==46.0.5 ; extra == 'test'
+Requires-Dist: pytest>=7.4.0 ; extra == 'test'
+Requires-Dist: pytest-benchmark>=4.0 ; extra == 'test'
+Requires-Dist: pytest-cov>=2.10.1 ; extra == 'test'
+Requires-Dist: pytest-xdist>=3.5.0 ; extra == 'test'
+Requires-Dist: pretend>=0.7 ; extra == 'test'
+Requires-Dist: certifi>=2024 ; extra == 'test'
+Requires-Dist: pytest-randomly ; extra == 'test-randomorder'
+Requires-Dist: sphinx>=5.3.0 ; extra == 'docs'
+Requires-Dist: sphinx-rtd-theme>=3.0.0 ; extra == 'docs'
+Requires-Dist: sphinx-inline-tabs ; extra == 'docs'
+Requires-Dist: pyenchant>=3 ; extra == 'docstest'
+Requires-Dist: readme-renderer>=30.0 ; extra == 'docstest'
+Requires-Dist: sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest'
+Requires-Dist: build>=1.0.0 ; extra == 'sdist'
+Requires-Dist: ruff>=0.11.11 ; extra == 'pep8test'
+Requires-Dist: mypy>=1.14 ; extra == 'pep8test'
+Requires-Dist: check-sdist ; extra == 'pep8test'
+Requires-Dist: click>=8.0.1 ; extra == 'pep8test'
+Provides-Extra: ssh
+Provides-Extra: nox
+Provides-Extra: test
+Provides-Extra: test-randomorder
+Provides-Extra: docs
+Provides-Extra: docstest
+Provides-Extra: sdist
+Provides-Extra: pep8test
+License-File: LICENSE
+License-File: LICENSE.APACHE
+License-File: LICENSE.BSD
+Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers.
+Author-email: The Python Cryptographic Authority and individual contributors
+License-Expression: Apache-2.0 OR BSD-3-Clause
+Requires-Python: >=3.8, !=3.9.0, !=3.9.1
+Description-Content-Type: text/x-rst; charset=UTF-8
+Project-URL: homepage, https://github.com/pyca/cryptography
+Project-URL: documentation, https://cryptography.io/
+Project-URL: source, https://github.com/pyca/cryptography/
+Project-URL: issues, https://github.com/pyca/cryptography/issues
+Project-URL: changelog, https://cryptography.io/en/latest/changelog/
+
+pyca/cryptography
+=================
+
+.. image:: https://img.shields.io/pypi/v/cryptography.svg
+ :target: https://pypi.org/project/cryptography/
+ :alt: Latest Version
+
+.. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest
+ :target: https://cryptography.io
+ :alt: Latest Docs
+
+.. image:: https://github.com/pyca/cryptography/actions/workflows/ci.yml/badge.svg
+ :target: https://github.com/pyca/cryptography/actions/workflows/ci.yml?query=branch%3Amain
+
+``cryptography`` is a package which provides cryptographic recipes and
+primitives to Python developers. Our goal is for it to be your "cryptographic
+standard library". It supports Python 3.8+ and PyPy3 7.3.11+.
+
+``cryptography`` includes both high level recipes and low level interfaces to
+common cryptographic algorithms such as symmetric ciphers, message digests, and
+key derivation functions. For example, to encrypt something with
+``cryptography``'s high level symmetric encryption recipe:
+
+.. code-block:: pycon
+
+ >>> from cryptography.fernet import Fernet
+ >>> # Put this somewhere safe!
+ >>> key = Fernet.generate_key()
+ >>> f = Fernet(key)
+ >>> token = f.encrypt(b"A really secret message. Not for prying eyes.")
+ >>> token
+ b'...'
+ >>> f.decrypt(token)
+ b'A really secret message. Not for prying eyes.'
+
+You can find more information in the `documentation`_.
+
+You can install ``cryptography`` with:
+
+.. code-block:: console
+
+ $ pip install cryptography
+
+For full details see `the installation documentation`_.
+
+Discussion
+~~~~~~~~~~
+
+If you run into bugs, you can file them in our `issue tracker`_.
+
+We maintain a `cryptography-dev`_ mailing list for development discussion.
+
+You can also join ``#pyca`` on ``irc.libera.chat`` to ask questions or get
+involved.
+
+Security
+~~~~~~~~
+
+Need to report a security issue? Please consult our `security reporting`_
+documentation.
+
+
+.. _`documentation`: https://cryptography.io/
+.. _`the installation documentation`: https://cryptography.io/en/latest/installation/
+.. _`issue tracker`: https://github.com/pyca/cryptography/issues
+.. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev
+.. _`security reporting`: https://cryptography.io/en/latest/security/
+
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/RECORD b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/RECORD
new file mode 100644
index 0000000..1ccd96f
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/RECORD
@@ -0,0 +1,181 @@
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/__about__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/exceptions.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/fernet.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/_oid.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/asn1/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/asn1/asn1.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/backends/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/backends/openssl/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/backends/openssl/backend.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/bindings/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/bindings/openssl/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/bindings/openssl/_conditional.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/bindings/openssl/binding.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/decrepit/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/decrepit/ciphers/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/decrepit/ciphers/algorithms.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/_asymmetric.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/_cipheralgorithm.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/_serialization.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/dh.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/dsa.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/ec.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/ed25519.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/ed448.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/padding.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/rsa.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/types.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/utils.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/x25519.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/asymmetric/x448.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/ciphers/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/ciphers/aead.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/ciphers/algorithms.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/ciphers/base.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/ciphers/modes.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/cmac.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/constant_time.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/hashes.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/hmac.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/argon2.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/concatkdf.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/hkdf.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/kbkdf.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/pbkdf2.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/scrypt.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/kdf/x963kdf.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/keywrap.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/padding.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/poly1305.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/serialization/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/serialization/base.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/serialization/pkcs12.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/serialization/pkcs7.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/serialization/ssh.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/twofactor/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/twofactor/hotp.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/hazmat/primitives/twofactor/totp.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/utils.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/__init__.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/base.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/certificate_transparency.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/extensions.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/general_name.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/name.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/ocsp.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/oid.cpython-39.pyc,,
+../../../../../../Library/Caches/com.apple.python/Users/au/Github/tg-ws-proxy/.venv-macos-build/lib/python3.9/site-packages/cryptography/x509/verification.cpython-39.pyc,,
+cryptography-46.0.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+cryptography-46.0.5.dist-info/METADATA,sha256=aOYB9_B-Ccske76ncMz-w9c_VnzYihv_7kxZlt2i2WQ,5748
+cryptography-46.0.5.dist-info/RECORD,,
+cryptography-46.0.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+cryptography-46.0.5.dist-info/WHEEL,sha256=fPMWsH0x0SkVhDADhT1_1cBQw9LWEszkPTNMfqIZV3E,107
+cryptography-46.0.5.dist-info/licenses/LICENSE,sha256=Pgx8CRqUi4JTO6mP18u0BDLW8amsv4X1ki0vmak65rs,197
+cryptography-46.0.5.dist-info/licenses/LICENSE.APACHE,sha256=qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY_4,11360
+cryptography-46.0.5.dist-info/licenses/LICENSE.BSD,sha256=YCxMdILeZHndLpeTzaJ15eY9dz2s0eymiSMqtwCPtPs,1532
+cryptography/__about__.py,sha256=GWg4NAxg4vsSKUwmDy1HjUeAOhqTA46wIhiY6i03NSU,445
+cryptography/__init__.py,sha256=mthuUrTd4FROCpUYrTIqhjz6s6T9djAZrV7nZ1oMm2o,364
+cryptography/exceptions.py,sha256=835EWILc2fwxw-gyFMriciC2SqhViETB10LBSytnDIc,1087
+cryptography/fernet.py,sha256=3Cvxkh0KJSbX8HbnCHu4wfCW7U0GgfUA3v_qQ8a8iWc,6963
+cryptography/hazmat/__init__.py,sha256=5IwrLWrVp0AjEr_4FdWG_V057NSJGY_W4egNNsuct0g,455
+cryptography/hazmat/_oid.py,sha256=p8ThjwJB56Ci_rAIrjyJ1f8VjgD6e39es2dh8JIUBOw,17240
+cryptography/hazmat/asn1/__init__.py,sha256=hS_EWx3wVvZzfbCcNV8hzcDnyMM8H-BhIoS1TipUosk,293
+cryptography/hazmat/asn1/asn1.py,sha256=eMEThEXa19LQjcyVofgHsW6tsZnjp3ddH7bWkkcxfLM,3860
+cryptography/hazmat/backends/__init__.py,sha256=O5jvKFQdZnXhKeqJ-HtulaEL9Ni7mr1mDzZY5kHlYhI,361
+cryptography/hazmat/backends/openssl/__init__.py,sha256=p3jmJfnCag9iE5sdMrN6VvVEu55u46xaS_IjoI0SrmA,305
+cryptography/hazmat/backends/openssl/backend.py,sha256=tV5AxBoFJ2GfA0DMWSY-0TxQJrpQoexzI9R4Kybb--4,10215
+cryptography/hazmat/bindings/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180
+cryptography/hazmat/bindings/_rust.abi3.so,sha256=FwK4b9caMvTiwLLTo2k7GOoGX0wP0fvJ-M-NMCKmGsY,20998672
+cryptography/hazmat/bindings/_rust/__init__.pyi,sha256=KhqLhXFPArPzzJ7DYO9Fl8FoXB_BagAd_r4Dm_Ze9Xo,1257
+cryptography/hazmat/bindings/_rust/_openssl.pyi,sha256=mpNJLuYLbCVrd5i33FBTmWwL_55Dw7JPkSLlSX9Q7oI,230
+cryptography/hazmat/bindings/_rust/asn1.pyi,sha256=BrGjC8J6nwuS-r3EVcdXJB8ndotfY9mbQYOfpbPG0HA,354
+cryptography/hazmat/bindings/_rust/declarative_asn1.pyi,sha256=2ECFmYue1EPkHEE2Bm7aLwkjB0mSUTpr23v9MN4pri4,892
+cryptography/hazmat/bindings/_rust/exceptions.pyi,sha256=exXr2xw_0pB1kk93cYbM3MohbzoUkjOms1ZMUi0uQZE,640
+cryptography/hazmat/bindings/_rust/ocsp.pyi,sha256=VPVWuKHI9EMs09ZLRYAGvR0Iz0mCMmEzXAkgJHovpoM,4020
+cryptography/hazmat/bindings/_rust/openssl/__init__.pyi,sha256=iOAMDyHoNwwCSZfZzuXDr64g4GpGUeDgEN-LjXqdrBM,1522
+cryptography/hazmat/bindings/_rust/openssl/aead.pyi,sha256=4Nddw6-ynzIB3w2W86WvkGKTLlTDk_6F5l54RHCuy3E,2688
+cryptography/hazmat/bindings/_rust/openssl/ciphers.pyi,sha256=LhPzHWSXJq4grAJXn6zSvSSdV-aYIIscHDwIPlJGGPs,1315
+cryptography/hazmat/bindings/_rust/openssl/cmac.pyi,sha256=nPH0X57RYpsAkRowVpjQiHE566ThUTx7YXrsadmrmHk,564
+cryptography/hazmat/bindings/_rust/openssl/dh.pyi,sha256=Z3TC-G04-THtSdAOPLM1h2G7ml5bda1ElZUcn5wpuhk,1564
+cryptography/hazmat/bindings/_rust/openssl/dsa.pyi,sha256=qBtkgj2albt2qFcnZ9UDrhzoNhCVO7HTby5VSf1EXMI,1299
+cryptography/hazmat/bindings/_rust/openssl/ec.pyi,sha256=zJy0pRa5n-_p2dm45PxECB_-B6SVZyNKfjxFDpPqT38,1691
+cryptography/hazmat/bindings/_rust/openssl/ed25519.pyi,sha256=VXfXd5G6hUivg399R1DYdmW3eTb0EebzDTqjRC2gaRw,532
+cryptography/hazmat/bindings/_rust/openssl/ed448.pyi,sha256=Yx49lqdnjsD7bxiDV1kcaMrDktug5evi5a6zerMiy2s,514
+cryptography/hazmat/bindings/_rust/openssl/hashes.pyi,sha256=OWZvBx7xfo_HJl41Nc--DugVyCVPIprZ3HlOPTSWH9g,984
+cryptography/hazmat/bindings/_rust/openssl/hmac.pyi,sha256=BXZn7NDjL3JAbYW0SQ8pg1iyC5DbQXVhUAiwsi8DFR8,702
+cryptography/hazmat/bindings/_rust/openssl/kdf.pyi,sha256=xXfFBb9QehHfDtEaxV_65Z0YK7NquOVIChpTLkgAs_k,2029
+cryptography/hazmat/bindings/_rust/openssl/keys.pyi,sha256=teIt8M6ZEMJrn4s3W0UnW0DZ-30Jd68WnSsKKG124l0,912
+cryptography/hazmat/bindings/_rust/openssl/poly1305.pyi,sha256=_SW9NtQ5FDlAbdclFtWpT4lGmxKIKHpN-4j8J2BzYfQ,585
+cryptography/hazmat/bindings/_rust/openssl/rsa.pyi,sha256=2OQCNSXkxgc-3uw1xiCCloIQTV6p9_kK79Yu0rhZgPc,1364
+cryptography/hazmat/bindings/_rust/openssl/x25519.pyi,sha256=ewn4GpQyb7zPwE-ni7GtyQgMC0A1mLuqYsSyqv6nI_s,523
+cryptography/hazmat/bindings/_rust/openssl/x448.pyi,sha256=juTZTmli8jO_5Vcufg-vHvx_tCyezmSLIh_9PU3TczI,505
+cryptography/hazmat/bindings/_rust/pkcs12.pyi,sha256=vEEd5wDiZvb8ZGFaziLCaWLzAwoG_tvPUxLQw5_uOl8,1605
+cryptography/hazmat/bindings/_rust/pkcs7.pyi,sha256=txGBJijqZshEcqra6byPNbnisIdlxzOSIHP2hl9arPs,1601
+cryptography/hazmat/bindings/_rust/test_support.pyi,sha256=PPhld-WkO743iXFPebeG0LtgK0aTzGdjcIsay1Gm5GE,757
+cryptography/hazmat/bindings/_rust/x509.pyi,sha256=n9X0IQ6ICbdIi-ExdCFZoBgeY6njm3QOVAVZwDQdnbk,9784
+cryptography/hazmat/bindings/openssl/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180
+cryptography/hazmat/bindings/openssl/_conditional.py,sha256=DMOpA_XN4l70zTc5_J9DpwlbQeUBRTWpfIJ4yRIn1-U,5791
+cryptography/hazmat/bindings/openssl/binding.py,sha256=x8eocEmukO4cm7cHqfVmOoYY7CCXdoF1v1WhZQt9neo,4610
+cryptography/hazmat/decrepit/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216
+cryptography/hazmat/decrepit/ciphers/__init__.py,sha256=wHCbWfaefa-fk6THSw9th9fJUsStJo7245wfFBqmduA,216
+cryptography/hazmat/decrepit/ciphers/algorithms.py,sha256=YrKgHS4MfwWaMmPBYRymRRlC0phwWp9ycICFezeJPGk,2595
+cryptography/hazmat/primitives/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180
+cryptography/hazmat/primitives/_asymmetric.py,sha256=RhgcouUB6HTiFDBrR1LxqkMjpUxIiNvQ1r_zJjRG6qQ,532
+cryptography/hazmat/primitives/_cipheralgorithm.py,sha256=Eh3i7lwedHfi0eLSsH93PZxQKzY9I6lkK67vL4V5tOc,1522
+cryptography/hazmat/primitives/_serialization.py,sha256=chgPCSF2jxI2Cr5gB-qbWXOvOfupBh4CARS0KAhv9AM,5123
+cryptography/hazmat/primitives/asymmetric/__init__.py,sha256=s9oKCQ2ycFdXoERdS1imafueSkBsL9kvbyfghaauZ9Y,180
+cryptography/hazmat/primitives/asymmetric/dh.py,sha256=0v_vEFFz5pQ1QG-FkWDyvgv7IfuVZSH5Q6LyFI5A8rg,3645
+cryptography/hazmat/primitives/asymmetric/dsa.py,sha256=Ld_bbbqQFz12dObHxIkzEQzX0SWWP41RLSWkYSaKhqE,4213
+cryptography/hazmat/primitives/asymmetric/ec.py,sha256=dj0ZR_jTVI1wojjipjbXNVccPSIRObWxSZcTGQKGbHc,13437
+cryptography/hazmat/primitives/asymmetric/ed25519.py,sha256=jZW5cs472wXXV3eB0sE1b8w64gdazwwU0_MT5UOTiXs,3700
+cryptography/hazmat/primitives/asymmetric/ed448.py,sha256=yAetgn2f2JYf0BO8MapGzXeThsvSMG5LmUCrxVOidAA,3729
+cryptography/hazmat/primitives/asymmetric/padding.py,sha256=vQ6l6gOg9HqcbOsvHrSiJRVLdEj9L4m4HkRGYziTyFA,2854
+cryptography/hazmat/primitives/asymmetric/rsa.py,sha256=ZnKOo2f34MCCOupC03Y1uR-_jiSG5IrelHEmxaME3D4,8303
+cryptography/hazmat/primitives/asymmetric/types.py,sha256=LnsOJym-wmPUJ7Knu_7bCNU3kIiELCd6krOaW_JU08I,2996
+cryptography/hazmat/primitives/asymmetric/utils.py,sha256=DPTs6T4F-UhwzFQTh-1fSEpQzazH2jf2xpIro3ItF4o,790
+cryptography/hazmat/primitives/asymmetric/x25519.py,sha256=_4nQeZ3yJ3Lg0RpXnaqA-1yt6vbx1F-wzLcaZHwSpeE,3613
+cryptography/hazmat/primitives/asymmetric/x448.py,sha256=WKBLtuVfJqiBRro654fGaQAlvsKbqbNkK7c4A_ZCdV0,3642
+cryptography/hazmat/primitives/ciphers/__init__.py,sha256=eyEXmjk6_CZXaOPYDr7vAYGXr29QvzgWL2-4CSolLFs,680
+cryptography/hazmat/primitives/ciphers/aead.py,sha256=Fzlyx7w8KYQakzDp1zWgJnIr62zgZrgVh1u2h4exB54,634
+cryptography/hazmat/primitives/ciphers/algorithms.py,sha256=Q7ZJwcsx83Mgxv5y7r6CyJKSdsOwC-my-5A67-ma2vw,3407
+cryptography/hazmat/primitives/ciphers/base.py,sha256=aBC7HHBBoixebmparVr0UlODs3VD0A7B6oz_AaRjDv8,4253
+cryptography/hazmat/primitives/ciphers/modes.py,sha256=20stpwhDtbAvpH0SMf9EDHIciwmTF-JMBUOZ9bU8WiQ,8318
+cryptography/hazmat/primitives/cmac.py,sha256=sz_s6H_cYnOvx-VNWdIKhRhe3Ymp8z8J0D3CBqOX3gg,338
+cryptography/hazmat/primitives/constant_time.py,sha256=xdunWT0nf8OvKdcqUhhlFKayGp4_PgVJRU2W1wLSr_A,422
+cryptography/hazmat/primitives/hashes.py,sha256=M8BrlKB3U6DEtHvWTV5VRjpteHv1kS3Zxm_Bsk04cr8,5184
+cryptography/hazmat/primitives/hmac.py,sha256=RpB3z9z5skirCQrm7zQbtnp9pLMnAjrlTUvKqF5aDDc,423
+cryptography/hazmat/primitives/kdf/__init__.py,sha256=4XibZnrYq4hh5xBjWiIXzaYW6FKx8hPbVaa_cB9zS64,750
+cryptography/hazmat/primitives/kdf/argon2.py,sha256=UFDNXG0v-rw3DqAQTB1UQAsQC2M5Ejg0k_6OCyhLKus,460
+cryptography/hazmat/primitives/kdf/concatkdf.py,sha256=Ua8KoLXXnzgsrAUmHpyKymaPt8aPRP0EHEaBz7QCQ9I,3737
+cryptography/hazmat/primitives/kdf/hkdf.py,sha256=M0lAEfRoc4kpp4-nwDj9yB-vNZukIOYEQrUlWsBNn9o,543
+cryptography/hazmat/primitives/kdf/kbkdf.py,sha256=oZepvo4evhKkkJQWRDwaPoIbyTaFmDc5NPimxg6lfKg,9165
+cryptography/hazmat/primitives/kdf/pbkdf2.py,sha256=1WIwhELR0w8ztTpTu8BrFiYWmK3hUfJq08I79TxwieE,1957
+cryptography/hazmat/primitives/kdf/scrypt.py,sha256=XyWUdUUmhuI9V6TqAPOvujCSMGv1XQdg0a21IWCmO-U,590
+cryptography/hazmat/primitives/kdf/x963kdf.py,sha256=zLTcF665QFvXX2f8TS7fmBZTteXpFjKahzfjjQcCJyw,1999
+cryptography/hazmat/primitives/keywrap.py,sha256=XV4Pj2fqSeD-RqZVvY2cA3j5_7RwJSFygYuLfk2ujCo,5650
+cryptography/hazmat/primitives/padding.py,sha256=QT-U-NvV2eQGO1wVPbDiNGNSc9keRDS-ig5cQOrLz0E,1865
+cryptography/hazmat/primitives/poly1305.py,sha256=P5EPQV-RB_FJPahpg01u0Ts4S_PnAmsroxIGXbGeRRo,355
+cryptography/hazmat/primitives/serialization/__init__.py,sha256=Q7uTgDlt7n3WfsMT6jYwutC6DIg_7SEeoAm1GHZ5B5E,1705
+cryptography/hazmat/primitives/serialization/base.py,sha256=ikq5MJIwp_oUnjiaBco_PmQwOTYuGi-XkYUYHKy8Vo0,615
+cryptography/hazmat/primitives/serialization/pkcs12.py,sha256=mS9cFNG4afzvseoc5e1MWoY2VskfL8N8Y_OFjl67luY,5104
+cryptography/hazmat/primitives/serialization/pkcs7.py,sha256=5OR_Tkysxaprn4FegvJIfbep9rJ9wok6FLWvWwQ5-Mg,13943
+cryptography/hazmat/primitives/serialization/ssh.py,sha256=hPV5obFznz0QhFfXFPOeQ8y6MsurA0xVMQiLnLESEs8,53700
+cryptography/hazmat/primitives/twofactor/__init__.py,sha256=tmMZGB-g4IU1r7lIFqASU019zr0uPp_wEBYcwdDCKCA,258
+cryptography/hazmat/primitives/twofactor/hotp.py,sha256=ivZo5BrcCGWLsqql4nZV0XXCjyGPi_iHfDFltGlOJwk,3256
+cryptography/hazmat/primitives/twofactor/totp.py,sha256=m5LPpRL00kp4zY8gTjr55Hfz9aMlPS53kHmVkSQCmdY,1652
+cryptography/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+cryptography/utils.py,sha256=nFHkPQZycOQGeBtBRkWSA4WjOHFo7pwummQt-PPSkZc,4349
+cryptography/x509/__init__.py,sha256=xloN0swseNx-m2WFZmCA17gOoxQWqeU82UVjEdJBePQ,8257
+cryptography/x509/base.py,sha256=OrmTw3y8B6AE_nGXQPN8x9kq-d7rDWeH13gCq6T6D6U,27997
+cryptography/x509/certificate_transparency.py,sha256=JqoOIDhlwInrYMFW6IFn77WJ0viF-PB_rlZV3vs9MYc,797
+cryptography/x509/extensions.py,sha256=QxYrqR6SF1qzR9ZraP8wDiIczlEVlAFuwDRVcltB6Tk,77724
+cryptography/x509/general_name.py,sha256=sP_rV11Qlpsk4x3XXGJY_Mv0Q_s9dtjeLckHsjpLQoQ,7836
+cryptography/x509/name.py,sha256=ty0_xf0LnHwZAdEf-d8FLO1K4hGqx_7DsD3CHwoLJiY,15101
+cryptography/x509/ocsp.py,sha256=Yey6NdFV1MPjop24Mj_VenjEpg3kUaMopSWOK0AbeBs,12699
+cryptography/x509/oid.py,sha256=BUzgXXGVWilkBkdKPTm9R4qElE9gAGHgdYPMZAp7PJo,931
+cryptography/x509/verification.py,sha256=gR2C2c-XZQtblZhT5T5vjSKOtCb74ef2alPVmEcwFlM,958
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/REQUESTED b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/WHEEL b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/WHEEL
new file mode 100644
index 0000000..28030e8
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: maturin (1.9.4)
+Root-Is-Purelib: false
+Tag: cp38-abi3-macosx_10_9_universal2
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE
new file mode 100644
index 0000000..b11f379
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE
@@ -0,0 +1,3 @@
+This software is made available under the terms of *either* of the licenses
+found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made
+under the terms of *both* these licenses.
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.APACHE b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.APACHE
new file mode 100644
index 0000000..62589ed
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.APACHE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.BSD b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.BSD
new file mode 100644
index 0000000..ec1a29d
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.BSD
@@ -0,0 +1,27 @@
+Copyright (c) Individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. Neither the name of PyCA Cryptography nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/__init__.py
new file mode 100644
index 0000000..d9144ce
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/__init__.py
@@ -0,0 +1,88 @@
+__version__ = "5.2.2"
+
+import os
+import sys
+from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar
+from tkinter.constants import *
+import tkinter.filedialog as filedialog
+
+# import manager classes
+from .windows.widgets.appearance_mode import AppearanceModeTracker
+from .windows.widgets.font import FontManager
+from .windows.widgets.scaling import ScalingTracker
+from .windows.widgets.theme import ThemeManager
+from .windows.widgets.core_rendering import DrawEngine
+
+# import base widgets
+from .windows.widgets.core_rendering import CTkCanvas
+from .windows.widgets.core_widget_classes import CTkBaseClass
+
+# import widgets
+from .windows.widgets import CTkButton
+from .windows.widgets import CTkCheckBox
+from .windows.widgets import CTkComboBox
+from .windows.widgets import CTkEntry
+from .windows.widgets import CTkFrame
+from .windows.widgets import CTkLabel
+from .windows.widgets import CTkOptionMenu
+from .windows.widgets import CTkProgressBar
+from .windows.widgets import CTkRadioButton
+from .windows.widgets import CTkScrollbar
+from .windows.widgets import CTkSegmentedButton
+from .windows.widgets import CTkSlider
+from .windows.widgets import CTkSwitch
+from .windows.widgets import CTkTabview
+from .windows.widgets import CTkTextbox
+from .windows.widgets import CTkScrollableFrame
+
+# import windows
+from .windows import CTk
+from .windows import CTkToplevel
+from .windows import CTkInputDialog
+
+# import font classes
+from .windows.widgets.font import CTkFont
+
+# import image classes
+from .windows.widgets.image import CTkImage
+
+from .windows import ctk_tk
+
+_ = Variable, StringVar, IntVar, DoubleVar, BooleanVar, CENTER, filedialog # prevent IDE from removing unused imports
+
+
+def set_appearance_mode(mode_string: str):
+ """ possible values: light, dark, system """
+ AppearanceModeTracker.set_appearance_mode(mode_string)
+
+
+def get_appearance_mode() -> str:
+ """ get current state of the appearance mode (light or dark) """
+ if AppearanceModeTracker.appearance_mode == 0:
+ return "Light"
+ elif AppearanceModeTracker.appearance_mode == 1:
+ return "Dark"
+
+
+def set_default_color_theme(color_string: str):
+ """ set color theme or load custom theme file by passing the path """
+ ThemeManager.load_theme(color_string)
+
+
+def set_widget_scaling(scaling_value: float):
+ """ set scaling for the widget dimensions """
+ ScalingTracker.set_widget_scaling(scaling_value)
+
+
+def set_window_scaling(scaling_value: float):
+ """ set scaling for window dimensions """
+ ScalingTracker.set_window_scaling(scaling_value)
+
+
+def deactivate_automatic_dpi_awareness():
+ """ deactivate DPI awareness of current process (windll.shcore.SetProcessDpiAwareness(0)) """
+ ScalingTracker.deactivate_automatic_dpi_awareness = True
+
+
+def set_ctk_parent_class(ctk_parent_class):
+ ctk_tk.CTK_PARENT_CLASS = ctk_parent_class
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/CustomTkinter_shapes_font.otf b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/CustomTkinter_shapes_font.otf
new file mode 100644
index 0000000..a891053
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/CustomTkinter_shapes_font.otf differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/Roboto/Roboto-Medium.ttf b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/Roboto/Roboto-Medium.ttf
new file mode 100644
index 0000000..e89b0b7
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/Roboto/Roboto-Medium.ttf differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/Roboto/Roboto-Regular.ttf b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/Roboto/Roboto-Regular.ttf
new file mode 100644
index 0000000..3d6861b
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/fonts/Roboto/Roboto-Regular.ttf differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/icons/CustomTkinter_icon_Windows.ico b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/icons/CustomTkinter_icon_Windows.ico
new file mode 100644
index 0000000..fe8eeaf
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/icons/CustomTkinter_icon_Windows.ico differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/blue.json b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/blue.json
new file mode 100644
index 0000000..192c136
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/blue.json
@@ -0,0 +1,155 @@
+{
+ "CTk": {
+ "fg_color": ["gray92", "gray14"]
+ },
+ "CTkToplevel": {
+ "fg_color": ["gray92", "gray14"]
+ },
+ "CTkFrame": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["gray86", "gray17"],
+ "top_fg_color": ["gray81", "gray20"],
+ "border_color": ["gray65", "gray28"]
+ },
+ "CTkButton": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["#3B8ED0", "#1F6AA5"],
+ "hover_color": ["#36719F", "#144870"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "text_color": ["#DCE4EE", "#DCE4EE"],
+ "text_color_disabled": ["gray74", "gray60"]
+ },
+ "CTkLabel": {
+ "corner_radius": 0,
+ "fg_color": "transparent",
+ "text_color": ["gray10", "#DCE4EE"]
+ },
+ "CTkEntry": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#F9F9FA", "#343638"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "text_color":["gray10", "#DCE4EE"],
+ "placeholder_text_color": ["gray52", "gray62"]
+ },
+ "CTkCheckBox": {
+ "corner_radius": 6,
+ "border_width": 3,
+ "fg_color": ["#3B8ED0", "#1F6AA5"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "hover_color": ["#3B8ED0", "#1F6AA5"],
+ "checkmark_color": ["#DCE4EE", "gray90"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkSwitch": {
+ "corner_radius": 1000,
+ "border_width": 3,
+ "button_length": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["#3B8ED0", "#1F6AA5"],
+ "button_color": ["gray36", "#D5D9DE"],
+ "button_hover_color": ["gray20", "gray100"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkRadioButton": {
+ "corner_radius": 1000,
+ "border_width_checked": 6,
+ "border_width_unchecked": 3,
+ "fg_color": ["#3B8ED0", "#1F6AA5"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "hover_color": ["#36719F", "#144870"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkProgressBar": {
+ "corner_radius": 1000,
+ "border_width": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["#3B8ED0", "#1F6AA5"],
+ "border_color": ["gray", "gray"]
+ },
+ "CTkSlider": {
+ "corner_radius": 1000,
+ "button_corner_radius": 1000,
+ "border_width": 6,
+ "button_length": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["gray40", "#AAB0B5"],
+ "button_color": ["#3B8ED0", "#1F6AA5"],
+ "button_hover_color": ["#36719F", "#144870"]
+ },
+ "CTkOptionMenu": {
+ "corner_radius": 6,
+ "fg_color": ["#3B8ED0", "#1F6AA5"],
+ "button_color": ["#36719F", "#144870"],
+ "button_hover_color": ["#27577D", "#203A4F"],
+ "text_color": ["#DCE4EE", "#DCE4EE"],
+ "text_color_disabled": ["gray74", "gray60"]
+ },
+ "CTkComboBox": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#F9F9FA", "#343638"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "button_color": ["#979DA2", "#565B5E"],
+ "button_hover_color": ["#6E7174", "#7A848D"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray50", "gray45"]
+ },
+ "CTkScrollbar": {
+ "corner_radius": 1000,
+ "border_spacing": 4,
+ "fg_color": "transparent",
+ "button_color": ["gray55", "gray41"],
+ "button_hover_color": ["gray40", "gray53"]
+ },
+ "CTkSegmentedButton": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#979DA2", "gray29"],
+ "selected_color": ["#3B8ED0", "#1F6AA5"],
+ "selected_hover_color": ["#36719F", "#144870"],
+ "unselected_color": ["#979DA2", "gray29"],
+ "unselected_hover_color": ["gray70", "gray41"],
+ "text_color": ["#DCE4EE", "#DCE4EE"],
+ "text_color_disabled": ["gray74", "gray60"]
+ },
+ "CTkTextbox": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["#F9F9FA", "#1D1E1E"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "text_color":["gray10", "#DCE4EE"],
+ "scrollbar_button_color": ["gray55", "gray41"],
+ "scrollbar_button_hover_color": ["gray40", "gray53"]
+ },
+ "CTkScrollableFrame": {
+ "label_fg_color": ["gray78", "gray23"]
+ },
+ "DropdownMenu": {
+ "fg_color": ["gray90", "gray20"],
+ "hover_color": ["gray75", "gray28"],
+ "text_color": ["gray10", "gray90"]
+ },
+ "CTkFont": {
+ "macOS": {
+ "family": "SF Display",
+ "size": 13,
+ "weight": "normal"
+ },
+ "Windows": {
+ "family": "Roboto",
+ "size": 13,
+ "weight": "normal"
+ },
+ "Linux": {
+ "family": "Roboto",
+ "size": 13,
+ "weight": "normal"
+ }
+ }
+}
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/dark-blue.json b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/dark-blue.json
new file mode 100644
index 0000000..54ff211
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/dark-blue.json
@@ -0,0 +1,155 @@
+{
+ "CTk": {
+ "fg_color": ["gray95", "gray10"]
+ },
+ "CTkToplevel": {
+ "fg_color": ["gray95", "gray10"]
+ },
+ "CTkFrame": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["gray90", "gray13"],
+ "top_fg_color": ["gray85", "gray16"],
+ "border_color": ["gray65", "gray28"]
+ },
+ "CTkButton": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["#3a7ebf", "#1f538d"],
+ "hover_color": ["#325882", "#14375e"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "text_color": ["#DCE4EE", "#DCE4EE"],
+ "text_color_disabled": ["gray74", "gray60"]
+ },
+ "CTkLabel": {
+ "corner_radius": 0,
+ "fg_color": "transparent",
+ "text_color": ["gray14", "gray84"]
+ },
+ "CTkEntry": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#F9F9FA", "#343638"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "text_color": ["gray14", "gray84"],
+ "placeholder_text_color": ["gray52", "gray62"]
+ },
+ "CTkCheckBox": {
+ "corner_radius": 6,
+ "border_width": 3,
+ "fg_color": ["#3a7ebf", "#1f538d"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "hover_color": ["#325882", "#14375e"],
+ "checkmark_color": ["#DCE4EE", "gray90"],
+ "text_color": ["gray14", "gray84"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkSwitch": {
+ "corner_radius": 1000,
+ "border_width": 3,
+ "button_length": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["#3a7ebf", "#1f538d"],
+ "button_color": ["gray36", "#D5D9DE"],
+ "button_hover_color": ["gray20", "gray100"],
+ "text_color": ["gray14", "gray84"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkRadioButton": {
+ "corner_radius": 1000,
+ "border_width_checked": 6,
+ "border_width_unchecked": 3,
+ "fg_color": ["#3a7ebf", "#1f538d"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "hover_color": ["#325882", "#14375e"],
+ "text_color": ["gray14", "gray84"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkProgressBar": {
+ "corner_radius": 1000,
+ "border_width": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["#3a7ebf", "#1f538d"],
+ "border_color": ["gray", "gray"]
+ },
+ "CTkSlider": {
+ "corner_radius": 1000,
+ "button_corner_radius": 1000,
+ "border_width": 6,
+ "button_length": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["gray40", "#AAB0B5"],
+ "button_color": ["#3a7ebf", "#1f538d"],
+ "button_hover_color": ["#325882", "#14375e"]
+ },
+ "CTkOptionMenu": {
+ "corner_radius": 6,
+ "fg_color": ["#3a7ebf", "#1f538d"],
+ "button_color": ["#325882", "#14375e"],
+ "button_hover_color": ["#234567", "#1e2c40"],
+ "text_color": ["#DCE4EE", "#DCE4EE"],
+ "text_color_disabled": ["gray74", "gray60"]
+ },
+ "CTkComboBox": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#F9F9FA", "#343638"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "button_color": ["#979DA2", "#565B5E"],
+ "button_hover_color": ["#6E7174", "#7A848D"],
+ "text_color": ["gray14", "gray84"],
+ "text_color_disabled": ["gray50", "gray45"]
+ },
+ "CTkScrollbar": {
+ "corner_radius": 1000,
+ "border_spacing": 4,
+ "fg_color": "transparent",
+ "button_color": ["gray55", "gray41"],
+ "button_hover_color": ["gray40", "gray53"]
+ },
+ "CTkSegmentedButton": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#979DA2", "gray29"],
+ "selected_color": ["#3a7ebf", "#1f538d"],
+ "selected_hover_color": ["#325882", "#14375e"],
+ "unselected_color": ["#979DA2", "gray29"],
+ "unselected_hover_color": ["gray70", "gray41"],
+ "text_color": ["#DCE4EE", "#DCE4EE"],
+ "text_color_disabled": ["gray74", "gray60"]
+ },
+ "CTkTextbox": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["gray100", "gray20"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "text_color": ["gray14", "gray84"],
+ "scrollbar_button_color": ["gray55", "gray41"],
+ "scrollbar_button_hover_color": ["gray40", "gray53"]
+ },
+ "CTkScrollableFrame": {
+ "label_fg_color": ["gray80", "gray21"]
+ },
+ "DropdownMenu": {
+ "fg_color": ["gray90", "gray20"],
+ "hover_color": ["gray75", "gray28"],
+ "text_color": ["gray14", "gray84"]
+ },
+ "CTkFont": {
+ "macOS": {
+ "family": "SF Display",
+ "size": 13,
+ "weight": "normal"
+ },
+ "Windows": {
+ "family": "Roboto",
+ "size": 13,
+ "weight": "normal"
+ },
+ "Linux": {
+ "family": "Roboto",
+ "size": 13,
+ "weight": "normal"
+ }
+ }
+}
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/green.json b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/green.json
new file mode 100644
index 0000000..200012f
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/assets/themes/green.json
@@ -0,0 +1,155 @@
+{
+ "CTk": {
+ "fg_color": ["gray92", "gray14"]
+ },
+ "CTkToplevel": {
+ "fg_color": ["gray92", "gray14"]
+ },
+ "CTkFrame": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["gray86", "gray17"],
+ "top_fg_color": ["gray81", "gray20"],
+ "border_color": ["gray65", "gray28"]
+ },
+ "CTkButton": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["#2CC985", "#2FA572"],
+ "hover_color": ["#0C955A", "#106A43"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "text_color": ["gray98", "#DCE4EE"],
+ "text_color_disabled": ["gray78", "gray68"]
+ },
+ "CTkLabel": {
+ "corner_radius": 0,
+ "fg_color": "transparent",
+ "text_color": ["gray10", "#DCE4EE"]
+ },
+ "CTkEntry": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#F9F9FA", "#343638"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "text_color":["gray10", "#DCE4EE"],
+ "placeholder_text_color": ["gray52", "gray62"]
+ },
+ "CTkCheckBox": {
+ "corner_radius": 6,
+ "border_width": 3,
+ "fg_color": ["#2CC985", "#2FA572"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "hover_color": ["#0C955A", "#106A43"],
+ "checkmark_color": ["#DCE4EE", "gray90"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkSwitch": {
+ "corner_radius": 1000,
+ "border_width": 3,
+ "button_length": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["#2CC985", "#2FA572"],
+ "button_color": ["gray36", "#D5D9DE"],
+ "button_hover_color": ["gray20", "gray100"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkRadioButton": {
+ "corner_radius": 1000,
+ "border_width_checked": 6,
+ "border_width_unchecked": 3,
+ "fg_color": ["#2CC985", "#2FA572"],
+ "border_color": ["#3E454A", "#949A9F"],
+ "hover_color":["#0C955A", "#106A43"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray60", "gray45"]
+ },
+ "CTkProgressBar": {
+ "corner_radius": 1000,
+ "border_width": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["#2CC985", "#2FA572"],
+ "border_color": ["gray", "gray"]
+ },
+ "CTkSlider": {
+ "corner_radius": 1000,
+ "button_corner_radius": 1000,
+ "border_width": 6,
+ "button_length": 0,
+ "fg_color": ["#939BA2", "#4A4D50"],
+ "progress_color": ["gray40", "#AAB0B5"],
+ "button_color": ["#2CC985", "#2FA572"],
+ "button_hover_color": ["#0C955A", "#106A43"]
+ },
+ "CTkOptionMenu": {
+ "corner_radius": 6,
+ "fg_color": ["#2cbe79", "#2FA572"],
+ "button_color": ["#0C955A", "#106A43"],
+ "button_hover_color": ["#0b6e3d", "#17472e"],
+ "text_color": ["gray98", "#DCE4EE"],
+ "text_color_disabled": ["gray78", "gray68"]
+ },
+ "CTkComboBox": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#F9F9FA", "#343638"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "button_color": ["#979DA2", "#565B5E"],
+ "button_hover_color": ["#6E7174", "#7A848D"],
+ "text_color": ["gray10", "#DCE4EE"],
+ "text_color_disabled": ["gray50", "gray45"]
+ },
+ "CTkScrollbar": {
+ "corner_radius": 1000,
+ "border_spacing": 4,
+ "fg_color": "transparent",
+ "button_color": ["gray55", "gray41"],
+ "button_hover_color": ["gray40", "gray53"]
+ },
+ "CTkSegmentedButton": {
+ "corner_radius": 6,
+ "border_width": 2,
+ "fg_color": ["#979DA2", "gray29"],
+ "selected_color": ["#2CC985", "#2FA572"],
+ "selected_hover_color": ["#0C955A", "#106A43"],
+ "unselected_color": ["#979DA2", "gray29"],
+ "unselected_hover_color": ["gray70", "gray41"],
+ "text_color": ["gray98", "#DCE4EE"],
+ "text_color_disabled": ["gray78", "gray68"]
+ },
+ "CTkTextbox": {
+ "corner_radius": 6,
+ "border_width": 0,
+ "fg_color": ["#F9F9FA", "gray23"],
+ "border_color": ["#979DA2", "#565B5E"],
+ "text_color":["gray10", "#DCE4EE"],
+ "scrollbar_button_color": ["gray55", "gray41"],
+ "scrollbar_button_hover_color": ["gray40", "gray53"]
+ },
+ "CTkScrollableFrame": {
+ "label_fg_color": ["gray78", "gray23"]
+ },
+ "DropdownMenu": {
+ "fg_color": ["gray90", "gray20"],
+ "hover_color": ["gray75", "gray28"],
+ "text_color": ["gray10", "gray90"]
+ },
+ "CTkFont": {
+ "macOS": {
+ "family": "SF Display",
+ "size": 13,
+ "weight": "normal"
+ },
+ "Windows": {
+ "family": "Roboto",
+ "size": 13,
+ "weight": "normal"
+ },
+ "Linux": {
+ "family": "Roboto",
+ "size": 13,
+ "weight": "normal"
+ }
+ }
+}
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/__init__.py
new file mode 100644
index 0000000..ca681b7
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/__init__.py
@@ -0,0 +1,3 @@
+from .ctk_tk import CTk
+from .ctk_toplevel import CTkToplevel
+from .ctk_input_dialog import CTkInputDialog
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_input_dialog.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_input_dialog.py
new file mode 100644
index 0000000..6c4669a
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_input_dialog.py
@@ -0,0 +1,117 @@
+from typing import Union, Tuple, Optional
+
+from .widgets import CTkLabel
+from .widgets import CTkEntry
+from .widgets import CTkButton
+from .widgets.theme import ThemeManager
+from .ctk_toplevel import CTkToplevel
+from .widgets.font import CTkFont
+
+
+class CTkInputDialog(CTkToplevel):
+ """
+ Dialog with extra window, message, entry widget, cancel and ok button.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ entry_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ entry_border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ entry_text_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ title: str = "CTkDialog",
+ font: Optional[Union[tuple, CTkFont]] = None,
+ text: str = "CTkDialog"):
+
+ super().__init__(fg_color=fg_color)
+
+ self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(button_hover_color)
+ self._button_fg_color = ThemeManager.theme["CTkButton"]["fg_color"] if button_fg_color is None else self._check_color_type(button_fg_color)
+ self._button_hover_color = ThemeManager.theme["CTkButton"]["hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
+ self._button_text_color = ThemeManager.theme["CTkButton"]["text_color"] if button_text_color is None else self._check_color_type(button_text_color)
+ self._entry_fg_color = ThemeManager.theme["CTkEntry"]["fg_color"] if entry_fg_color is None else self._check_color_type(entry_fg_color)
+ self._entry_border_color = ThemeManager.theme["CTkEntry"]["border_color"] if entry_border_color is None else self._check_color_type(entry_border_color)
+ self._entry_text_color = ThemeManager.theme["CTkEntry"]["text_color"] if entry_text_color is None else self._check_color_type(entry_text_color)
+
+ self._user_input: Union[str, None] = None
+ self._running: bool = False
+ self._title = title
+ self._text = text
+ self._font = font
+
+ self.title(self._title)
+ self.lift() # lift window on top
+ self.attributes("-topmost", True) # stay on top
+ self.protocol("WM_DELETE_WINDOW", self._on_closing)
+ self.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background
+ self.resizable(False, False)
+ self.grab_set() # make other windows not clickable
+
+ def _create_widgets(self):
+ self.grid_columnconfigure((0, 1), weight=1)
+ self.rowconfigure(0, weight=1)
+
+ self._label = CTkLabel(master=self,
+ width=300,
+ wraplength=300,
+ fg_color="transparent",
+ text_color=self._text_color,
+ text=self._text,
+ font=self._font)
+ self._label.grid(row=0, column=0, columnspan=2, padx=20, pady=20, sticky="ew")
+
+ self._entry = CTkEntry(master=self,
+ width=230,
+ fg_color=self._entry_fg_color,
+ border_color=self._entry_border_color,
+ text_color=self._entry_text_color,
+ font=self._font)
+ self._entry.grid(row=1, column=0, columnspan=2, padx=20, pady=(0, 20), sticky="ew")
+
+ self._ok_button = CTkButton(master=self,
+ width=100,
+ border_width=0,
+ fg_color=self._button_fg_color,
+ hover_color=self._button_hover_color,
+ text_color=self._button_text_color,
+ text='Ok',
+ font=self._font,
+ command=self._ok_event)
+ self._ok_button.grid(row=2, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
+
+ self._cancel_button = CTkButton(master=self,
+ width=100,
+ border_width=0,
+ fg_color=self._button_fg_color,
+ hover_color=self._button_hover_color,
+ text_color=self._button_text_color,
+ text='Cancel',
+ font=self._font,
+ command=self._cancel_event)
+ self._cancel_button.grid(row=2, column=1, columnspan=1, padx=(10, 20), pady=(0, 20), sticky="ew")
+
+ self.after(150, lambda: self._entry.focus()) # set focus to entry with slight delay, otherwise it won't work
+ self._entry.bind("", self._ok_event)
+
+ def _ok_event(self, event=None):
+ self._user_input = self._entry.get()
+ self.grab_release()
+ self.destroy()
+
+ def _on_closing(self):
+ self.grab_release()
+ self.destroy()
+
+ def _cancel_event(self):
+ self.grab_release()
+ self.destroy()
+
+ def get_input(self):
+ self.master.wait_window(self)
+ return self._user_input
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_tk.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_tk.py
new file mode 100644
index 0000000..e137dc3
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_tk.py
@@ -0,0 +1,333 @@
+import tkinter
+import sys
+import os
+import platform
+import ctypes
+from typing import Union, Tuple, Optional
+from packaging import version
+
+from .widgets.theme import ThemeManager
+from .widgets.scaling import CTkScalingBaseClass
+from .widgets.appearance_mode import CTkAppearanceModeBaseClass
+
+from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
+
+CTK_PARENT_CLASS = tkinter.Tk
+
+
+class CTk(CTK_PARENT_CLASS, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
+ """
+ Main app window with dark titlebar on Windows and macOS.
+ For detailed information check out the documentation.
+ """
+
+ _valid_tk_constructor_arguments: set = {"screenName", "baseName", "className", "useTk", "sync", "use"}
+
+ _valid_tk_configure_arguments: set = {'bd', 'borderwidth', 'class', 'menu', 'relief', 'screen',
+ 'use', 'container', 'cursor', 'height',
+ 'highlightthickness', 'padx', 'pady', 'takefocus', 'visual', 'width'}
+
+ _deactivate_macos_window_header_manipulation: bool = False
+ _deactivate_windows_window_header_manipulation: bool = False
+
+ def __init__(self,
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ **kwargs):
+
+ self._enable_macos_dark_title_bar()
+
+ # call init methods of super classes
+ CTK_PARENT_CLASS.__init__(self, **pop_from_dict_by_set(kwargs, self._valid_tk_constructor_arguments))
+ CTkAppearanceModeBaseClass.__init__(self)
+ CTkScalingBaseClass.__init__(self, scaling_type="window")
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ self._current_width = 600 # initial window size, independent of scaling
+ self._current_height = 500
+ self._min_width: int = 0
+ self._min_height: int = 0
+ self._max_width: int = 1_000_000
+ self._max_height: int = 1_000_000
+ self._last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
+
+ self._fg_color = ThemeManager.theme["CTk"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+
+ # set bg of tkinter.Tk
+ super().configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ # set title
+ self.title("CTk")
+
+ # indicator variables
+ self._iconbitmap_method_called = False # indicates if wm_iconbitmap method got called
+ self._state_before_windows_set_titlebar_color = None
+ self._window_exists = False # indicates if the window is already shown through update() or mainloop() after init
+ self._withdraw_called_before_window_exists = False # indicates if withdraw() was called before window is first shown through update() or mainloop()
+ self._iconify_called_before_window_exists = False # indicates if iconify() was called before window is first shown through update() or mainloop()
+ self._block_update_dimensions_event = False
+
+ # save focus before calling withdraw
+ self.focused_widget_before_widthdraw = None
+
+ # set CustomTkinter titlebar icon (Windows only)
+ if sys.platform.startswith("win"):
+ self.after(200, self._windows_set_titlebar_icon)
+
+ # set titlebar color (Windows only)
+ if sys.platform.startswith("win"):
+ self._windows_set_titlebar_color(self._get_appearance_mode())
+
+ self.bind('', self._update_dimensions_event)
+ self.bind('', self._focus_in_event)
+
+ def destroy(self):
+ self._disable_macos_dark_title_bar()
+
+ # call destroy methods of super classes
+ tkinter.Tk.destroy(self)
+ CTkAppearanceModeBaseClass.destroy(self)
+ CTkScalingBaseClass.destroy(self)
+
+ def _focus_in_event(self, event):
+ # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
+ if sys.platform == "darwin":
+ self.lift()
+
+ def _update_dimensions_event(self, event=None):
+ if not self._block_update_dimensions_event:
+
+ detected_width = super().winfo_width() # detect current window size
+ detected_height = super().winfo_height()
+
+ # detected_width = event.width
+ # detected_height = event.height
+
+ if self._current_width != self._reverse_window_scaling(detected_width) or self._current_height != self._reverse_window_scaling(detected_height):
+ self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event
+ self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale
+
+ def _set_scaling(self, new_widget_scaling, new_window_scaling):
+ super()._set_scaling(new_widget_scaling, new_window_scaling)
+
+ # Force new dimensions on window by using min, max, and geometry. Without min, max it won't work.
+ super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
+ super().maxsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
+
+ super().geometry(f"{self._apply_window_scaling(self._current_width)}x{self._apply_window_scaling(self._current_height)}")
+
+ # set new scaled min and max with delay (delay prevents weird bug where window dimensions snap to unscaled dimensions when mouse releases window)
+ self.after(1000, self._set_scaled_min_max) # Why 1000ms delay? Experience! (Everything tested on Windows 11)
+
+ def block_update_dimensions_event(self):
+ self._block_update_dimensions_event = False
+
+ def unblock_update_dimensions_event(self):
+ self._block_update_dimensions_event = False
+
+ def _set_scaled_min_max(self):
+ if self._min_width is not None or self._min_height is not None:
+ super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
+ if self._max_width is not None or self._max_height is not None:
+ super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
+
+ def withdraw(self):
+ if self._window_exists is False:
+ self._withdraw_called_before_window_exists = True
+ super().withdraw()
+
+ def iconify(self):
+ if self._window_exists is False:
+ self._iconify_called_before_window_exists = True
+ super().iconify()
+
+ def update(self):
+ if self._window_exists is False:
+ if sys.platform.startswith("win"):
+ if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
+ # print("window dont exists -> deiconify in update")
+ self.deiconify()
+
+ self._window_exists = True
+
+ super().update()
+
+ def mainloop(self, *args, **kwargs):
+ if not self._window_exists:
+ if sys.platform.startswith("win"):
+ self._windows_set_titlebar_color(self._get_appearance_mode())
+
+ if not self._withdraw_called_before_window_exists and not self._iconify_called_before_window_exists:
+ # print("window dont exists -> deiconify in mainloop")
+ self.deiconify()
+
+ self._window_exists = True
+
+ super().mainloop(*args, **kwargs)
+
+ def resizable(self, width: bool = None, height: bool = None):
+ current_resizable_values = super().resizable(width, height)
+ self._last_resizable_args = ([], {"width": width, "height": height})
+
+ if sys.platform.startswith("win"):
+ self._windows_set_titlebar_color(self._get_appearance_mode())
+
+ return current_resizable_values
+
+ def minsize(self, width: int = None, height: int = None):
+ self._min_width = width
+ self._min_height = height
+ if self._current_width < width:
+ self._current_width = width
+ if self._current_height < height:
+ self._current_height = height
+ super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
+
+ def maxsize(self, width: int = None, height: int = None):
+ self._max_width = width
+ self._max_height = height
+ if self._current_width > width:
+ self._current_width = width
+ if self._current_height > height:
+ self._current_height = height
+ super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
+
+ def geometry(self, geometry_string: str = None):
+ if geometry_string is not None:
+ super().geometry(self._apply_geometry_scaling(geometry_string))
+
+ # update width and height attributes
+ width, height, x, y = self._parse_geometry_string(geometry_string)
+ if width is not None and height is not None:
+ self._current_width = max(self._min_width, min(width, self._max_width)) # bound value between min and max
+ self._current_height = max(self._min_height, min(height, self._max_height))
+ else:
+ return self._reverse_geometry_scaling(super().geometry())
+
+ def configure(self, **kwargs):
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ super().configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ for child in self.winfo_children():
+ try:
+ child.configure(bg_color=self._fg_color)
+ except Exception:
+ pass
+
+ super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_configure_arguments))
+ check_kwargs_empty(kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "fg_color":
+ return self._fg_color
+ else:
+ return super().cget(attribute_name)
+
+ def wm_iconbitmap(self, bitmap=None, default=None):
+ self._iconbitmap_method_called = True
+ super().wm_iconbitmap(bitmap, default)
+
+ def iconbitmap(self, bitmap=None, default=None):
+ self._iconbitmap_method_called = True
+ super().wm_iconbitmap(bitmap, default)
+
+ def _windows_set_titlebar_icon(self):
+ try:
+ # if not the user already called iconbitmap method, set icon
+ if not self._iconbitmap_method_called:
+ customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico"))
+ except Exception:
+ pass
+
+ @classmethod
+ def _enable_macos_dark_title_bar(cls):
+ if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
+ if version.parse(platform.python_version()) < version.parse("3.10"):
+ if version.parse(tkinter.Tcl().call("info", "patchlevel")) >= version.parse("8.6.9"): # Tcl/Tk >= 8.6.9
+ os.system("defaults write -g NSRequiresAquaSystemAppearance -bool No")
+ # This command allows dark-mode for all programs
+
+ @classmethod
+ def _disable_macos_dark_title_bar(cls):
+ if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
+ if version.parse(platform.python_version()) < version.parse("3.10"):
+ if version.parse(tkinter.Tcl().call("info", "patchlevel")) >= version.parse("8.6.9"): # Tcl/Tk >= 8.6.9
+ os.system("defaults delete -g NSRequiresAquaSystemAppearance")
+ # This command reverts the dark-mode setting for all programs.
+
+ def _windows_set_titlebar_color(self, color_mode: str):
+ """
+ Set the titlebar color of the window to light or dark theme on Microsoft Windows.
+
+ Credits for this function:
+ https://stackoverflow.com/questions/23836000/can-i-change-the-title-bar-in-tkinter/70724666#70724666
+
+ MORE INFO:
+ https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
+ """
+
+ if sys.platform.startswith("win") and not self._deactivate_windows_window_header_manipulation:
+
+ if self._window_exists:
+ self._state_before_windows_set_titlebar_color = self.state()
+ # print("window_exists -> state_before_windows_set_titlebar_color: ", self.state_before_windows_set_titlebar_color)
+
+ if self._state_before_windows_set_titlebar_color != "iconic" or self._state_before_windows_set_titlebar_color != "withdrawn":
+ self.focused_widget_before_widthdraw = self.focus_get()
+ super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
+ else:
+ # print("window dont exists -> withdraw and update")
+ self.focused_widget_before_widthdraw = self.focus_get()
+ super().withdraw()
+ super().update()
+
+ if color_mode.lower() == "dark":
+ value = 1
+ elif color_mode.lower() == "light":
+ value = 0
+ else:
+ return
+
+ try:
+ hwnd = ctypes.windll.user32.GetParent(self.winfo_id())
+ DWMWA_USE_IMMERSIVE_DARK_MODE = 20
+ DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19
+
+ # try with DWMWA_USE_IMMERSIVE_DARK_MODE
+ if ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
+ ctypes.byref(ctypes.c_int(value)),
+ ctypes.sizeof(ctypes.c_int(value))) != 0:
+
+ # try with DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20h1
+ ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
+ ctypes.byref(ctypes.c_int(value)),
+ ctypes.sizeof(ctypes.c_int(value)))
+
+ except Exception as err:
+ print(err)
+
+ if self._window_exists or True:
+ # print("window_exists -> return to original state: ", self.state_before_windows_set_titlebar_color)
+ if self._state_before_windows_set_titlebar_color == "normal":
+ self.deiconify()
+ elif self._state_before_windows_set_titlebar_color == "iconic":
+ self.iconify()
+ elif self._state_before_windows_set_titlebar_color == "zoomed":
+ self.state("zoomed")
+ else:
+ self.state(self._state_before_windows_set_titlebar_color) # other states
+ else:
+ pass # wait for update or mainloop to be called
+
+ if self.focused_widget_before_widthdraw is not None:
+ self.after(1, self.focused_widget_before_widthdraw.focus)
+ self.focused_widget_before_widthdraw = None
+
+ def _set_appearance_mode(self, mode_string: str):
+ super()._set_appearance_mode(mode_string)
+
+ if sys.platform.startswith("win"):
+ self._windows_set_titlebar_color(mode_string)
+
+ super().configure(bg=self._apply_appearance_mode(self._fg_color))
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_toplevel.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_toplevel.py
new file mode 100644
index 0000000..9780380
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/ctk_toplevel.py
@@ -0,0 +1,307 @@
+import tkinter
+from packaging import version
+import sys
+import os
+import platform
+import ctypes
+from typing import Union, Tuple, Optional
+
+from .widgets.theme import ThemeManager
+from .widgets.scaling import CTkScalingBaseClass
+from .widgets.appearance_mode import CTkAppearanceModeBaseClass
+
+from customtkinter.windows.widgets.utility.utility_functions import pop_from_dict_by_set, check_kwargs_empty
+
+
+class CTkToplevel(tkinter.Toplevel, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
+ """
+ Toplevel window with dark titlebar on Windows and macOS.
+ For detailed information check out the documentation.
+ """
+
+ _valid_tk_toplevel_arguments: set = {"master", "bd", "borderwidth", "class", "container", "cursor", "height",
+ "highlightbackground", "highlightthickness", "menu", "relief",
+ "screen", "takefocus", "use", "visual", "width"}
+
+ _deactivate_macos_window_header_manipulation: bool = False
+ _deactivate_windows_window_header_manipulation: bool = False
+
+ def __init__(self, *args,
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ **kwargs):
+
+ self._enable_macos_dark_title_bar()
+
+ # call init methods of super classes
+ super().__init__(*args, **pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments))
+ CTkAppearanceModeBaseClass.__init__(self)
+ CTkScalingBaseClass.__init__(self, scaling_type="window")
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ try:
+ # Set Windows titlebar icon
+ if sys.platform.startswith("win"):
+ customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ self.after(200, lambda: self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico")))
+ except Exception:
+ pass
+
+ self._current_width = 200 # initial window size, always without scaling
+ self._current_height = 200
+ self._min_width: int = 0
+ self._min_height: int = 0
+ self._max_width: int = 1_000_000
+ self._max_height: int = 1_000_000
+ self._last_resizable_args: Union[Tuple[list, dict], None] = None # (args, kwargs)
+
+ self._fg_color = ThemeManager.theme["CTkToplevel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+
+ # set bg color of tkinter.Toplevel
+ super().configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ # set title of tkinter.Toplevel
+ super().title("CTkToplevel")
+
+ # indicator variables
+ self._iconbitmap_method_called = True
+ self._state_before_windows_set_titlebar_color = None
+ self._windows_set_titlebar_color_called = False # indicates if windows_set_titlebar_color was called, stays True until revert_withdraw_after_windows_set_titlebar_color is called
+ self._withdraw_called_after_windows_set_titlebar_color = False # indicates if withdraw() was called after windows_set_titlebar_color
+ self._iconify_called_after_windows_set_titlebar_color = False # indicates if iconify() was called after windows_set_titlebar_color
+ self._block_update_dimensions_event = False
+
+ # save focus before calling withdraw
+ self.focused_widget_before_widthdraw = None
+
+ # set CustomTkinter titlebar icon (Windows only)
+ if sys.platform.startswith("win"):
+ self.after(200, self._windows_set_titlebar_icon)
+
+ # set titlebar color (Windows only)
+ if sys.platform.startswith("win"):
+ self._windows_set_titlebar_color(self._get_appearance_mode())
+
+ self.bind('', self._update_dimensions_event)
+ self.bind('', self._focus_in_event)
+
+ def destroy(self):
+ self._disable_macos_dark_title_bar()
+
+ # call destroy methods of super classes
+ tkinter.Toplevel.destroy(self)
+ CTkAppearanceModeBaseClass.destroy(self)
+ CTkScalingBaseClass.destroy(self)
+
+ def _focus_in_event(self, event):
+ # sometimes window looses jumps back on macOS if window is selected from Mission Control, so has to be lifted again
+ if sys.platform == "darwin":
+ self.lift()
+
+ def _update_dimensions_event(self, event=None):
+ if not self._block_update_dimensions_event:
+ detected_width = self.winfo_width() # detect current window size
+ detected_height = self.winfo_height()
+
+ if self._current_width != self._reverse_window_scaling(detected_width) or self._current_height != self._reverse_window_scaling(detected_height):
+ self._current_width = self._reverse_window_scaling(detected_width) # adjust current size according to new size given by event
+ self._current_height = self._reverse_window_scaling(detected_height) # _current_width and _current_height are independent of the scale
+
+ def _set_scaling(self, new_widget_scaling, new_window_scaling):
+ super()._set_scaling(new_widget_scaling, new_window_scaling)
+
+ # Force new dimensions on window by using min, max, and geometry. Without min, max it won't work.
+ super().minsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
+ super().maxsize(self._apply_window_scaling(self._current_width), self._apply_window_scaling(self._current_height))
+
+ super().geometry(f"{self._apply_window_scaling(self._current_width)}x{self._apply_window_scaling(self._current_height)}")
+
+ # set new scaled min and max with delay (delay prevents weird bug where window dimensions snap to unscaled dimensions when mouse releases window)
+ self.after(1000, self._set_scaled_min_max) # Why 1000ms delay? Experience! (Everything tested on Windows 11)
+
+ def block_update_dimensions_event(self):
+ self._block_update_dimensions_event = False
+
+ def unblock_update_dimensions_event(self):
+ self._block_update_dimensions_event = False
+
+ def _set_scaled_min_max(self):
+ if self._min_width is not None or self._min_height is not None:
+ super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
+ if self._max_width is not None or self._max_height is not None:
+ super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
+
+ def geometry(self, geometry_string: str = None):
+ if geometry_string is not None:
+ super().geometry(self._apply_geometry_scaling(geometry_string))
+
+ # update width and height attributes
+ width, height, x, y = self._parse_geometry_string(geometry_string)
+ if width is not None and height is not None:
+ self._current_width = max(self._min_width, min(width, self._max_width)) # bound value between min and max
+ self._current_height = max(self._min_height, min(height, self._max_height))
+ else:
+ return self._reverse_geometry_scaling(super().geometry())
+
+ def withdraw(self):
+ if self._windows_set_titlebar_color_called:
+ self._withdraw_called_after_windows_set_titlebar_color = True
+ super().withdraw()
+
+ def iconify(self):
+ if self._windows_set_titlebar_color_called:
+ self._iconify_called_after_windows_set_titlebar_color = True
+ super().iconify()
+
+ def resizable(self, width: bool = None, height: bool = None):
+ current_resizable_values = super().resizable(width, height)
+ self._last_resizable_args = ([], {"width": width, "height": height})
+
+ if sys.platform.startswith("win"):
+ self.after(10, lambda: self._windows_set_titlebar_color(self._get_appearance_mode()))
+
+ return current_resizable_values
+
+ def minsize(self, width=None, height=None):
+ self._min_width = width
+ self._min_height = height
+ if self._current_width < width:
+ self._current_width = width
+ if self._current_height < height:
+ self._current_height = height
+ super().minsize(self._apply_window_scaling(self._min_width), self._apply_window_scaling(self._min_height))
+
+ def maxsize(self, width=None, height=None):
+ self._max_width = width
+ self._max_height = height
+ if self._current_width > width:
+ self._current_width = width
+ if self._current_height > height:
+ self._current_height = height
+ super().maxsize(self._apply_window_scaling(self._max_width), self._apply_window_scaling(self._max_height))
+
+ def configure(self, **kwargs):
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ super().configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ for child in self.winfo_children():
+ try:
+ child.configure(bg_color=self._fg_color)
+ except Exception:
+ pass
+
+ super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_toplevel_arguments))
+ check_kwargs_empty(kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "fg_color":
+ return self._fg_color
+ else:
+ return super().cget(attribute_name)
+
+ def wm_iconbitmap(self, bitmap=None, default=None):
+ self._iconbitmap_method_called = True
+ super().wm_iconbitmap(bitmap, default)
+
+ def _windows_set_titlebar_icon(self):
+ try:
+ # if not the user already called iconbitmap method, set icon
+ if not self._iconbitmap_method_called:
+ customtkinter_directory = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ self.iconbitmap(os.path.join(customtkinter_directory, "assets", "icons", "CustomTkinter_icon_Windows.ico"))
+ except Exception:
+ pass
+
+ @classmethod
+ def _enable_macos_dark_title_bar(cls):
+ if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
+ if version.parse(platform.python_version()) < version.parse("3.10"):
+ if version.parse(tkinter.Tcl().call("info", "patchlevel")) >= version.parse("8.6.9"): # Tcl/Tk >= 8.6.9
+ os.system("defaults write -g NSRequiresAquaSystemAppearance -bool No")
+
+ @classmethod
+ def _disable_macos_dark_title_bar(cls):
+ if sys.platform == "darwin" and not cls._deactivate_macos_window_header_manipulation: # macOS
+ if version.parse(platform.python_version()) < version.parse("3.10"):
+ if version.parse(tkinter.Tcl().call("info", "patchlevel")) >= version.parse("8.6.9"): # Tcl/Tk >= 8.6.9
+ os.system("defaults delete -g NSRequiresAquaSystemAppearance")
+ # This command reverts the dark-mode setting for all programs.
+
+ def _windows_set_titlebar_color(self, color_mode: str):
+ """
+ Set the titlebar color of the window to light or dark theme on Microsoft Windows.
+
+ Credits for this function:
+ https://stackoverflow.com/questions/23836000/can-i-change-the-title-bar-in-tkinter/70724666#70724666
+
+ MORE INFO:
+ https://docs.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
+ """
+
+ if sys.platform.startswith("win") and not self._deactivate_windows_window_header_manipulation:
+
+ self._state_before_windows_set_titlebar_color = self.state()
+ self.focused_widget_before_widthdraw = self.focus_get()
+ super().withdraw() # hide window so that it can be redrawn after the titlebar change so that the color change is visible
+ super().update()
+
+ if color_mode.lower() == "dark":
+ value = 1
+ elif color_mode.lower() == "light":
+ value = 0
+ else:
+ return
+
+ try:
+ hwnd = ctypes.windll.user32.GetParent(self.winfo_id())
+ DWMWA_USE_IMMERSIVE_DARK_MODE = 20
+ DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19
+
+ # try with DWMWA_USE_IMMERSIVE_DARK_MODE
+ if ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE,
+ ctypes.byref(ctypes.c_int(value)),
+ ctypes.sizeof(ctypes.c_int(value))) != 0:
+ # try with DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20h1
+ ctypes.windll.dwmapi.DwmSetWindowAttribute(hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1,
+ ctypes.byref(ctypes.c_int(value)),
+ ctypes.sizeof(ctypes.c_int(value)))
+
+ except Exception as err:
+ print(err)
+
+ self._windows_set_titlebar_color_called = True
+ self.after(5, self._revert_withdraw_after_windows_set_titlebar_color)
+
+ if self.focused_widget_before_widthdraw is not None:
+ self.after(10, self.focused_widget_before_widthdraw.focus)
+ self.focused_widget_before_widthdraw = None
+
+ def _revert_withdraw_after_windows_set_titlebar_color(self):
+ """ if in a short time (5ms) after """
+ if self._windows_set_titlebar_color_called:
+
+ if self._withdraw_called_after_windows_set_titlebar_color:
+ pass # leave it withdrawed
+ elif self._iconify_called_after_windows_set_titlebar_color:
+ super().iconify()
+ else:
+ if self._state_before_windows_set_titlebar_color == "normal":
+ self.deiconify()
+ elif self._state_before_windows_set_titlebar_color == "iconic":
+ self.iconify()
+ elif self._state_before_windows_set_titlebar_color == "zoomed":
+ self.state("zoomed")
+ else:
+ self.state(self._state_before_windows_set_titlebar_color) # other states
+
+ self._windows_set_titlebar_color_called = False
+ self._withdraw_called_after_windows_set_titlebar_color = False
+ self._iconify_called_after_windows_set_titlebar_color = False
+
+ def _set_appearance_mode(self, mode_string):
+ super()._set_appearance_mode(mode_string)
+
+ if sys.platform.startswith("win"):
+ self._windows_set_titlebar_color(mode_string)
+
+ super().configure(bg=self._apply_appearance_mode(self._fg_color))
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/__init__.py
new file mode 100644
index 0000000..a75c63d
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/__init__.py
@@ -0,0 +1,16 @@
+from .ctk_button import CTkButton
+from .ctk_checkbox import CTkCheckBox
+from .ctk_combobox import CTkComboBox
+from .ctk_entry import CTkEntry
+from .ctk_frame import CTkFrame
+from .ctk_label import CTkLabel
+from .ctk_optionmenu import CTkOptionMenu
+from .ctk_progressbar import CTkProgressBar
+from .ctk_radiobutton import CTkRadioButton
+from .ctk_scrollbar import CTkScrollbar
+from .ctk_segmented_button import CTkSegmentedButton
+from .ctk_slider import CTkSlider
+from .ctk_switch import CTkSwitch
+from .ctk_tabview import CTkTabview
+from .ctk_textbox import CTkTextbox
+from .ctk_scrollable_frame import CTkScrollableFrame
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/__init__.py
new file mode 100644
index 0000000..e979ca8
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/__init__.py
@@ -0,0 +1,4 @@
+from .appearance_mode_base_class import CTkAppearanceModeBaseClass
+from .appearance_mode_tracker import AppearanceModeTracker
+
+AppearanceModeTracker.init_appearance_mode()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py
new file mode 100644
index 0000000..b7f757a
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py
@@ -0,0 +1,61 @@
+from typing import Union, Tuple, List
+
+from .appearance_mode_tracker import AppearanceModeTracker
+
+
+class CTkAppearanceModeBaseClass:
+ """
+ Super-class that manages the appearance mode. Methods:
+
+ - destroy() must be called when sub-class is destroyed
+ - _set_appearance_mode() abstractmethod, gets called when appearance mode changes, must be overridden
+ - _apply_appearance_mode() to convert tuple color
+
+ """
+ def __init__(self):
+ AppearanceModeTracker.add(self._set_appearance_mode, self)
+ self.__appearance_mode = AppearanceModeTracker.get_mode() # 0: "Light" 1: "Dark"
+
+ def destroy(self):
+ AppearanceModeTracker.remove(self._set_appearance_mode)
+
+ def _set_appearance_mode(self, mode_string: str):
+ """ can be overridden but super method must be called at the beginning """
+ if mode_string.lower() == "dark":
+ self.__appearance_mode = 1
+ elif mode_string.lower() == "light":
+ self.__appearance_mode = 0
+
+ def _get_appearance_mode(self) -> str:
+ """ get appearance mode as a string, 'light' or 'dark' """
+ if self.__appearance_mode == 0:
+ return "light"
+ else:
+ return "dark"
+
+ def _apply_appearance_mode(self, color: Union[str, Tuple[str, str], List[str]]) -> str:
+ """
+ color can be either a single hex color string or a color name or it can be a
+ tuple color with (light_color, dark_color). The functions returns
+ always a single color string
+ """
+
+ if isinstance(color, (tuple, list)):
+ return color[self.__appearance_mode]
+ else:
+ return color
+
+ @staticmethod
+ def _check_color_type(color: any, transparency: bool = False):
+ if color is None:
+ raise ValueError(f"color is None, for transparency set color='transparent'")
+ elif isinstance(color, (tuple, list)) and (color[0] == "transparent" or color[1] == "transparent"):
+ raise ValueError(f"transparency is not allowed in tuple color {color}, use 'transparent'")
+ elif color == "transparent" and transparency is False:
+ raise ValueError(f"transparency is not allowed for this attribute")
+ elif isinstance(color, str):
+ return color
+ elif isinstance(color, (tuple, list)) and len(color) == 2 and isinstance(color[0], str) and isinstance(color[1], str):
+ return color
+ else:
+ raise ValueError(f"color {color} must be string ('transparent' or 'color-name' or 'hex-color') or tuple of two strings, not {type(color)}")
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py
new file mode 100644
index 0000000..eb20a73
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py
@@ -0,0 +1,122 @@
+import tkinter
+from typing import Callable
+import darkdetect
+
+
+class AppearanceModeTracker:
+
+ callback_list = []
+ app_list = []
+ update_loop_running = False
+ update_loop_interval = 30 # milliseconds
+
+ appearance_mode_set_by = "system"
+ appearance_mode = 0 # Light (standard)
+
+ @classmethod
+ def init_appearance_mode(cls):
+ if cls.appearance_mode_set_by == "system":
+ new_appearance_mode = cls.detect_appearance_mode()
+
+ if new_appearance_mode != cls.appearance_mode:
+ cls.appearance_mode = new_appearance_mode
+ cls.update_callbacks()
+
+ @classmethod
+ def add(cls, callback: Callable, widget=None):
+ cls.callback_list.append(callback)
+
+ if widget is not None:
+ app = cls.get_tk_root_of_widget(widget)
+ if app not in cls.app_list:
+ cls.app_list.append(app)
+
+ if not cls.update_loop_running:
+ app.after(cls.update_loop_interval, cls.update)
+ cls.update_loop_running = True
+
+ @classmethod
+ def remove(cls, callback: Callable):
+ try:
+ cls.callback_list.remove(callback)
+ except ValueError:
+ return
+
+ @staticmethod
+ def detect_appearance_mode() -> int:
+ try:
+ if darkdetect.theme() == "Dark":
+ return 1 # Dark
+ else:
+ return 0 # Light
+ except NameError:
+ return 0 # Light
+
+ @classmethod
+ def get_tk_root_of_widget(cls, widget):
+ current_widget = widget
+
+ while isinstance(current_widget, tkinter.Tk) is False:
+ current_widget = current_widget.master
+
+ return current_widget
+
+ @classmethod
+ def update_callbacks(cls):
+ if cls.appearance_mode == 0:
+ for callback in cls.callback_list:
+ try:
+ callback("Light")
+ except Exception:
+ continue
+
+ elif cls.appearance_mode == 1:
+ for callback in cls.callback_list:
+ try:
+ callback("Dark")
+ except Exception:
+ continue
+
+ @classmethod
+ def update(cls):
+ if cls.appearance_mode_set_by == "system":
+ new_appearance_mode = cls.detect_appearance_mode()
+
+ if new_appearance_mode != cls.appearance_mode:
+ cls.appearance_mode = new_appearance_mode
+ cls.update_callbacks()
+
+ # find an existing tkinter.Tk object for the next call of .after()
+ for app in cls.app_list:
+ try:
+ app.after(cls.update_loop_interval, cls.update)
+ return
+ except Exception:
+ continue
+
+ cls.update_loop_running = False
+
+ @classmethod
+ def get_mode(cls) -> int:
+ return cls.appearance_mode
+
+ @classmethod
+ def set_appearance_mode(cls, mode_string: str):
+ if mode_string.lower() == "dark":
+ cls.appearance_mode_set_by = "user"
+ new_appearance_mode = 1
+
+ if new_appearance_mode != cls.appearance_mode:
+ cls.appearance_mode = new_appearance_mode
+ cls.update_callbacks()
+
+ elif mode_string.lower() == "light":
+ cls.appearance_mode_set_by = "user"
+ new_appearance_mode = 0
+
+ if new_appearance_mode != cls.appearance_mode:
+ cls.appearance_mode = new_appearance_mode
+ cls.update_callbacks()
+
+ elif mode_string.lower() == "system":
+ cls.appearance_mode_set_by = "system"
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/__init__.py
new file mode 100644
index 0000000..ccadbc7
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/__init__.py
@@ -0,0 +1,12 @@
+import sys
+
+from .ctk_canvas import CTkCanvas
+from .draw_engine import DrawEngine
+
+CTkCanvas.init_font_character_mapping()
+
+# determine draw method based on current platform
+if sys.platform == "darwin":
+ DrawEngine.preferred_drawing_method = "polygon_shapes"
+else:
+ DrawEngine.preferred_drawing_method = "font_shapes"
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/ctk_canvas.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/ctk_canvas.py
new file mode 100644
index 0000000..f291e2c
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/ctk_canvas.py
@@ -0,0 +1,117 @@
+import tkinter
+import sys
+from typing import Union, Tuple
+
+
+class CTkCanvas(tkinter.Canvas):
+ """
+ Canvas with additional functionality to draw antialiased circles on Windows/Linux.
+
+ Call .init_font_character_mapping() at program start to load the correct character
+ dictionary according to the operating system. Characters (circle sizes) are optimised
+ to look best for rendering CustomTkinter shapes on the different operating systems.
+
+ - .create_aa_circle() creates antialiased circle and returns int identifier.
+ - .coords() is modified to support the aa-circle shapes correctly like you would expect.
+ - .itemconfig() is also modified to support aa-cricle shapes.
+
+ The aa-circles are created by choosing a character from the custom created and loaded
+ font 'CustomTkinter_shapes_font'. It contains circle shapes with different sizes filling
+ either the whole character space or just pert of it (characters A to R). Circles with a smaller
+ radius need a smaller circle character to look correct when rendered on the canvas.
+
+ For an optimal result, the draw-engine creates two aa-circles on top of each other, while
+ one is rotated by 90 degrees. This helps to make the circle look more symetric, which is
+ not can be a problem when using only a single circle character.
+ """
+
+ radius_to_char_fine: dict = None # dict to map radius to font circle character
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self._aa_circle_canvas_ids = set()
+
+ @classmethod
+ def init_font_character_mapping(cls):
+ """ optimizations made for Windows 10, 11 only """
+
+ radius_to_char_warped = {19: 'B', 18: 'B', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'B', 12: 'B', 11: 'B',
+ 10: 'B',
+ 9: 'C', 8: 'D', 7: 'C', 6: 'E', 5: 'F', 4: 'G', 3: 'H', 2: 'H', 1: 'H', 0: 'A'}
+
+ radius_to_char_fine_windows_10 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C',
+ 11: 'C', 10: 'C',
+ 9: 'D', 8: 'D', 7: 'D', 6: 'C', 5: 'D', 4: 'G', 3: 'G', 2: 'H', 1: 'H',
+ 0: 'A'}
+
+ radius_to_char_fine_windows_11 = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'C', 12: 'C',
+ 11: 'D', 10: 'D',
+ 9: 'E', 8: 'F', 7: 'C', 6: 'I', 5: 'E', 4: 'G', 3: 'P', 2: 'R', 1: 'R',
+ 0: 'A'}
+
+ radius_to_char_fine_linux = {19: 'A', 18: 'A', 17: 'B', 16: 'B', 15: 'B', 14: 'B', 13: 'F', 12: 'C',
+ 11: 'F', 10: 'C',
+ 9: 'D', 8: 'G', 7: 'D', 6: 'F', 5: 'D', 4: 'G', 3: 'M', 2: 'H', 1: 'H',
+ 0: 'A'}
+
+ if sys.platform.startswith("win"):
+ if sys.getwindowsversion().build > 20000: # Windows 11
+ cls.radius_to_char_fine = radius_to_char_fine_windows_11
+ else: # < Windows 11
+ cls.radius_to_char_fine = radius_to_char_fine_windows_10
+ elif sys.platform.startswith("linux"): # Optimized on Kali Linux
+ cls.radius_to_char_fine = radius_to_char_fine_linux
+ else:
+ cls.radius_to_char_fine = radius_to_char_fine_windows_10
+
+ def _get_char_from_radius(self, radius: int) -> str:
+ if radius >= 20:
+ return "A"
+ else:
+ return self.radius_to_char_fine[radius]
+
+ def create_aa_circle(self, x_pos: int, y_pos: int, radius: int, angle: int = 0, fill: str = "white",
+ tags: Union[str, Tuple[str, ...]] = "", anchor: str = tkinter.CENTER) -> int:
+ # create a circle with a font element
+ circle_1 = self.create_text(x_pos, y_pos, text=self._get_char_from_radius(radius), anchor=anchor, fill=fill,
+ font=("CustomTkinter_shapes_font", -radius * 2), tags=tags, angle=angle)
+ self.addtag_withtag("ctk_aa_circle_font_element", circle_1)
+ self._aa_circle_canvas_ids.add(circle_1)
+
+ return circle_1
+
+ def coords(self, tag_or_id, *args):
+
+ if type(tag_or_id) == str and "ctk_aa_circle_font_element" in self.gettags(tag_or_id):
+ coords_id = self.find_withtag(tag_or_id)[0] # take the lowest id for the given tag
+ super().coords(coords_id, *args[:2])
+
+ if len(args) == 3:
+ super().itemconfigure(coords_id, font=("CustomTkinter_shapes_font", -int(args[2]) * 2), text=self._get_char_from_radius(args[2]))
+
+ elif type(tag_or_id) == int and tag_or_id in self._aa_circle_canvas_ids:
+ super().coords(tag_or_id, *args[:2])
+
+ if len(args) == 3:
+ super().itemconfigure(tag_or_id, font=("CustomTkinter_shapes_font", -args[2] * 2), text=self._get_char_from_radius(args[2]))
+
+ else:
+ super().coords(tag_or_id, *args)
+
+ def itemconfig(self, tag_or_id, *args, **kwargs):
+ kwargs_except_outline = kwargs.copy()
+ if "outline" in kwargs_except_outline:
+ del kwargs_except_outline["outline"]
+
+ if type(tag_or_id) == int:
+ if tag_or_id in self._aa_circle_canvas_ids:
+ super().itemconfigure(tag_or_id, *args, **kwargs_except_outline)
+ else:
+ super().itemconfigure(tag_or_id, *args, **kwargs)
+ else:
+ configure_ids = self.find_withtag(tag_or_id)
+ for configure_id in configure_ids:
+ if configure_id in self._aa_circle_canvas_ids:
+ super().itemconfigure(configure_id, *args, **kwargs_except_outline)
+ else:
+ super().itemconfigure(configure_id, *args, **kwargs)
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/draw_engine.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/draw_engine.py
new file mode 100644
index 0000000..5acea56
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_rendering/draw_engine.py
@@ -0,0 +1,1235 @@
+from __future__ import annotations
+import sys
+import math
+import tkinter
+from typing import Union, TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from ..core_rendering import CTkCanvas
+
+
+class DrawEngine:
+ """
+ This is the core of the CustomTkinter library where all the drawing on the tkinter.Canvas happens.
+ A year of experimenting and trying out different drawing methods have led to the current state of this
+ class, and I don't think there's much I can do to make the rendering look better than this with the
+ limited capabilities the tkinter.Canvas offers.
+
+ Functions:
+ - draw_rounded_rect_with_border()
+ - draw_rounded_rect_with_border_vertical_split()
+ - draw_rounded_progress_bar_with_border()
+ - draw_rounded_slider_with_border_and_button()
+ - draw_rounded_scrollbar()
+ - draw_checkmark()
+ - draw_dropdown_arrow()
+
+ """
+
+ preferred_drawing_method: str = None # 'polygon_shapes', 'font_shapes', 'circle_shapes'
+
+ def __init__(self, canvas: CTkCanvas):
+ self._canvas = canvas
+ self._round_width_to_even_numbers: bool = True
+ self._round_height_to_even_numbers: bool = True
+
+ def set_round_to_even_numbers(self, round_width_to_even_numbers: bool = True, round_height_to_even_numbers: bool = True):
+ self._round_width_to_even_numbers: bool = round_width_to_even_numbers
+ self._round_height_to_even_numbers: bool = round_height_to_even_numbers
+
+ 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_background_corners(self, width: Union[float, int], height: Union[float, int], ):
+ if self._round_width_to_even_numbers:
+ width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
+ if self._round_height_to_even_numbers:
+ height = math.floor(height / 2) * 2
+
+ requires_recoloring = False
+
+ if not self._canvas.find_withtag("background_corner_top_left"):
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_top_left"), width=0)
+ requires_recoloring = True
+ if not self._canvas.find_withtag("background_corner_top_right"):
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_top_right"), width=0)
+ requires_recoloring = True
+ if not self._canvas.find_withtag("background_corner_bottom_right"):
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_bottom_right"), width=0)
+ requires_recoloring = True
+ if not self._canvas.find_withtag("background_corner_bottom_left"):
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("background_parts", "background_corner_bottom_left"), width=0)
+ requires_recoloring = True
+
+ mid_width, mid_height = round(width / 2), round(height / 2)
+ self._canvas.coords("background_corner_top_left", (0, 0, mid_width, mid_height))
+ self._canvas.coords("background_corner_top_right", (mid_width, 0, width, mid_height))
+ self._canvas.coords("background_corner_bottom_right", (mid_width, mid_height, width, height))
+ self._canvas.coords("background_corner_bottom_left", (0, mid_height, mid_width, height))
+
+ if requires_recoloring: # new parts were added -> manage z-order
+ self._canvas.tag_lower("background_parts")
+
+ return requires_recoloring
+
+ def draw_rounded_rect_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
+ border_width: Union[float, int], overwrite_preferred_drawing_method: str = None) -> bool:
+ """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
+ the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
+
+ returns bool if recoloring is necessary """
+
+ if self._round_width_to_even_numbers:
+ width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
+ if self._round_height_to_even_numbers:
+ height = math.floor(height / 2) * 2
+ corner_radius = round(corner_radius)
+
+ if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too large
+ corner_radius = min(width / 2, height / 2)
+
+ border_width = round(border_width)
+ 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 overwrite_preferred_drawing_method is not None:
+ preferred_drawing_method = overwrite_preferred_drawing_method
+ else:
+ preferred_drawing_method = self.preferred_drawing_method
+
+ if preferred_drawing_method == "polygon_shapes":
+ return self.__draw_rounded_rect_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius)
+ elif preferred_drawing_method == "font_shapes":
+ return self.__draw_rounded_rect_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, ())
+ elif preferred_drawing_method == "circle_shapes":
+ return self.__draw_rounded_rect_with_border_circle_shapes(width, height, corner_radius, border_width, inner_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")
+ self._canvas.tag_lower("background_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
+
+ # create border button parts
+ if border_width > 0:
+ if corner_radius > 0:
+ # create canvas border corner parts if not already created, but only if needed, and delete if not needed
+ if not self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" in exclude_parts:
+ self._canvas.delete("border_oval_1_a", "border_oval_1_b")
+
+ if not self._canvas.find_withtag("border_oval_2_a") and width > 2 * corner_radius and "border_oval_2" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_2_a") and (not width > 2 * corner_radius or "border_oval_2" in exclude_parts):
+ self._canvas.delete("border_oval_2_a", "border_oval_2_b")
+
+ if not self._canvas.find_withtag("border_oval_3_a") and height > 2 * corner_radius \
+ and width > 2 * corner_radius and "border_oval_3" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_3_a") and (not (height > 2 * corner_radius
+ and width > 2 * corner_radius) or "border_oval_3" in exclude_parts):
+ self._canvas.delete("border_oval_3_a", "border_oval_3_b")
+
+ if not self._canvas.find_withtag("border_oval_4_a") and height > 2 * corner_radius and "border_oval_4" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_a", "border_corner_part", "border_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_4_a") and (not height > 2 * corner_radius or "border_oval_4" in exclude_parts):
+ self._canvas.delete("border_oval_4_a", "border_oval_4_b")
+
+ # change position of border corner parts
+ self._canvas.coords("border_oval_1_a", corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_1_b", corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_2_a", width - corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_2_b", width - corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_3_a", width - corner_radius, height - corner_radius, corner_radius)
+ self._canvas.coords("border_oval_3_b", width - corner_radius, height - corner_radius, corner_radius)
+ self._canvas.coords("border_oval_4_a", corner_radius, height - corner_radius, corner_radius)
+ self._canvas.coords("border_oval_4_b", corner_radius, height - corner_radius, corner_radius)
+
+ else:
+ self._canvas.delete("border_corner_part") # delete border corner parts if not needed
+
+ # create canvas border rectangle parts if not already created
+ if not self._canvas.find_withtag("border_rectangle_1"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"), width=0)
+ requires_recoloring = True
+
+ # change position of border rectangle parts
+ self._canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius))
+ self._canvas.coords("border_rectangle_2", (corner_radius, 0, width - corner_radius, height))
+
+ else:
+ self._canvas.delete("border_parts")
+
+ # create inner button parts
+ if inner_corner_radius > 0:
+
+ # create canvas border corner parts if not already created, but only if they're needed and delete if not needed
+ if not self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" in exclude_parts:
+ self._canvas.delete("inner_oval_1_a", "inner_oval_1_b")
+
+ if not self._canvas.find_withtag("inner_oval_2_a") and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_2" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_2_a") and (not width - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_2" in exclude_parts):
+ self._canvas.delete("inner_oval_2_a", "inner_oval_2_b")
+
+ if not self._canvas.find_withtag("inner_oval_3_a") and height - (2 * border_width) > 2 * inner_corner_radius \
+ and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_3" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_3_a") and (not (height - (2 * border_width) > 2 * inner_corner_radius
+ and width - (2 * border_width) > 2 * inner_corner_radius) or "inner_oval_3" in exclude_parts):
+ self._canvas.delete("inner_oval_3_a", "inner_oval_3_b")
+
+ if not self._canvas.find_withtag("inner_oval_4_a") and height - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_4" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_4_a") and (not height - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_4" in exclude_parts):
+ self._canvas.delete("inner_oval_4_a", "inner_oval_4_b")
+
+ # change position of border corner parts
+ self._canvas.coords("inner_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_2_a", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_2_b", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ else:
+ self._canvas.delete("inner_corner_part") # delete inner corner parts if not needed
+
+ # create canvas inner rectangle parts if not already created
+ if not self._canvas.find_withtag("inner_rectangle_1"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"), width=0)
+ requires_recoloring = True
+
+ if not self._canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0)
+ requires_recoloring = True
+
+ elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
+ self._canvas.delete("inner_rectangle_2")
+
+ # change position of inner rectangle parts
+ self._canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius,
+ border_width,
+ width - border_width - inner_corner_radius,
+ height - border_width))
+ self._canvas.coords("inner_rectangle_2", (border_width,
+ border_width + inner_corner_radius,
+ width - border_width,
+ height - inner_corner_radius - border_width))
+
+ if requires_recoloring: # new parts were added -> manage z-order
+ self._canvas.tag_lower("inner_parts")
+ self._canvas.tag_lower("border_parts")
+ self._canvas.tag_lower("background_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
+
+ # border button parts
+ if border_width > 0:
+ if corner_radius > 0:
+
+ if not self._canvas.find_withtag("border_oval_1"):
+ self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_1", "border_corner_part", "border_parts"), width=0)
+ self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_2", "border_corner_part", "border_parts"), width=0)
+ self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_3", "border_corner_part", "border_parts"), width=0)
+ self._canvas.create_oval(0, 0, 0, 0, tags=("border_oval_4", "border_corner_part", "border_parts"), width=0)
+ self._canvas.tag_lower("border_parts")
+ requires_recoloring = True
+
+ self._canvas.coords("border_oval_1", 0, 0, corner_radius * 2 - 1, corner_radius * 2 - 1)
+ self._canvas.coords("border_oval_2", width - corner_radius * 2, 0, width - 1, corner_radius * 2 - 1)
+ self._canvas.coords("border_oval_3", 0, height - corner_radius * 2, corner_radius * 2 - 1, height - 1)
+ self._canvas.coords("border_oval_4", width - corner_radius * 2, height - corner_radius * 2, width - 1, height - 1)
+
+ else:
+ self._canvas.delete("border_corner_part")
+
+ if not self._canvas.find_withtag("border_rectangle_1"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_rectangle_part", "border_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_2", "border_rectangle_part", "border_parts"), width=0)
+ self._canvas.tag_lower("border_parts")
+ requires_recoloring = True
+
+ self._canvas.coords("border_rectangle_1", (0, corner_radius, width, height - corner_radius))
+ self._canvas.coords("border_rectangle_2", (corner_radius, 0, width - corner_radius, height))
+
+ else:
+ self._canvas.delete("border_parts")
+
+ # inner button parts
+ 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_corner_part", "inner_parts"), width=0)
+ self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_2", "inner_corner_part", "inner_parts"), width=0)
+ self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_3", "inner_corner_part", "inner_parts"), width=0)
+ self._canvas.create_oval(0, 0, 0, 0, tags=("inner_oval_4", "inner_corner_part", "inner_parts"), width=0)
+ self._canvas.tag_raise("inner_parts")
+ requires_recoloring = True
+
+ self._canvas.coords("inner_oval_1", (border_width, border_width,
+ border_width + inner_corner_radius * 2 - 1, border_width + inner_corner_radius * 2 - 1))
+ self._canvas.coords("inner_oval_2", (width - border_width - inner_corner_radius * 2, border_width,
+ width - border_width - 1, border_width + inner_corner_radius * 2 - 1))
+ self._canvas.coords("inner_oval_3", (border_width, height - border_width - inner_corner_radius * 2,
+ border_width + inner_corner_radius * 2 - 1, height - border_width - 1))
+ self._canvas.coords("inner_oval_4", (width - border_width - inner_corner_radius * 2, height - border_width - inner_corner_radius * 2,
+ width - border_width - 1, height - border_width - 1))
+ else:
+ self._canvas.delete("inner_corner_part") # delete inner corner parts if not needed
+
+ if not self._canvas.find_withtag("inner_rectangle_1"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_1", "inner_rectangle_part", "inner_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_2", "inner_rectangle_part", "inner_parts"), width=0)
+ self._canvas.tag_raise("inner_parts")
+ requires_recoloring = True
+
+ self._canvas.coords("inner_rectangle_1", (border_width + inner_corner_radius,
+ border_width,
+ width - border_width - inner_corner_radius,
+ height - border_width))
+ self._canvas.coords("inner_rectangle_2", (border_width,
+ border_width + inner_corner_radius,
+ width - border_width,
+ height - inner_corner_radius - border_width))
+
+ return requires_recoloring
+
+ def draw_rounded_rect_with_border_vertical_split(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
+ border_width: Union[float, int], left_section_width: Union[float, int]) -> bool:
+ """ Draws a rounded rectangle with a corner_radius and border_width on the canvas which is split at left_section_width.
+ The border elements have the tags 'border_parts_left', 'border_parts_lright',
+ the main foreground elements have an 'inner_parts_left' and inner_parts_right' tag,
+ to color the elements accordingly.
+
+ returns bool if recoloring is necessary """
+
+ left_section_width = round(left_section_width)
+ if self._round_width_to_even_numbers:
+ width = math.floor(width / 2) * 2 # round (floor) _current_width and _current_height and restrict them to even values only
+ if self._round_height_to_even_numbers:
+ height = math.floor(height / 2) * 2
+ corner_radius = round(corner_radius)
+
+ if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
+ corner_radius = min(width / 2, height / 2)
+
+ border_width = round(border_width)
+ 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 left_section_width > width - corner_radius * 2:
+ left_section_width = width - corner_radius * 2
+ elif left_section_width < corner_radius * 2:
+ left_section_width = corner_radius * 2
+
+ if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
+ return self.__draw_rounded_rect_with_border_vertical_split_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius, left_section_width)
+ elif self.preferred_drawing_method == "font_shapes":
+ return self.__draw_rounded_rect_with_border_vertical_split_font_shapes(width, height, corner_radius, border_width, inner_corner_radius, left_section_width, ())
+
+ def __draw_rounded_rect_with_border_vertical_split_polygon_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
+ left_section_width: 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_left_1", "border_parts_left", "border_parts", "left_parts"))
+ self._canvas.create_polygon((0, 0, 0, 0), tags=("border_line_right_1", "border_parts_right", "border_parts", "right_parts"))
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_left_1", "border_parts_left", "border_parts", "left_parts"), width=0)
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("border_rect_right_1", "border_parts_right", "border_parts", "right_parts"), width=0)
+ requires_recoloring = True
+
+ self._canvas.coords("border_line_left_1",
+ (corner_radius,
+ corner_radius,
+ left_section_width - corner_radius,
+ corner_radius,
+ left_section_width - corner_radius,
+ height - corner_radius,
+ corner_radius,
+ height - corner_radius))
+ self._canvas.coords("border_line_right_1",
+ (left_section_width + corner_radius,
+ corner_radius,
+ width - corner_radius,
+ corner_radius,
+ width - corner_radius,
+ height - corner_radius,
+ left_section_width + corner_radius,
+ height - corner_radius))
+ self._canvas.coords("border_rect_left_1",
+ (left_section_width - corner_radius,
+ 0,
+ left_section_width,
+ height))
+ self._canvas.coords("border_rect_right_1",
+ (left_section_width,
+ 0,
+ left_section_width + corner_radius,
+ height))
+ self._canvas.itemconfig("border_line_left_1", joinstyle=tkinter.ROUND, width=corner_radius * 2)
+ self._canvas.itemconfig("border_line_right_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_left_1", "inner_parts_left", "inner_parts", "left_parts"), joinstyle=tkinter.ROUND)
+ self._canvas.create_polygon((0, 0, 0, 0), tags=("inner_line_right_1", "inner_parts_right", "inner_parts", "right_parts"), joinstyle=tkinter.ROUND)
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("inner_rect_left_1", "inner_parts_left", "inner_parts", "left_parts"), width=0)
+ self._canvas.create_rectangle((0, 0, 0, 0), tags=("inner_rect_right_1", "inner_parts_right", "inner_parts", "right_parts"), width=0)
+ requires_recoloring = True
+
+ self._canvas.coords("inner_line_left_1",
+ corner_radius,
+ corner_radius,
+ left_section_width - inner_corner_radius,
+ corner_radius,
+ left_section_width - inner_corner_radius,
+ height - corner_radius,
+ corner_radius,
+ height - corner_radius)
+ self._canvas.coords("inner_line_right_1",
+ left_section_width + inner_corner_radius,
+ corner_radius,
+ width - corner_radius,
+ corner_radius,
+ width - corner_radius,
+ height - corner_radius,
+ left_section_width + inner_corner_radius,
+ height - corner_radius)
+ self._canvas.coords("inner_rect_left_1",
+ (left_section_width - inner_corner_radius,
+ border_width,
+ left_section_width,
+ height - border_width))
+ self._canvas.coords("inner_rect_right_1",
+ (left_section_width,
+ border_width,
+ left_section_width + inner_corner_radius,
+ height - border_width))
+ self._canvas.itemconfig("inner_line_left_1", width=inner_corner_radius * 2)
+ self._canvas.itemconfig("inner_line_right_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")
+ self._canvas.tag_lower("background_parts")
+
+ return requires_recoloring
+
+ def __draw_rounded_rect_with_border_vertical_split_font_shapes(self, width: int, height: int, corner_radius: int, border_width: int, inner_corner_radius: int,
+ left_section_width: int, exclude_parts: tuple) -> bool:
+ requires_recoloring = False
+
+ # create border button parts
+ if border_width > 0:
+ if corner_radius > 0:
+ # create canvas border corner parts if not already created, but only if needed, and delete if not needed
+ if not self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_1_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER,
+ angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_1_a") and "border_oval_1" in exclude_parts:
+ self._canvas.delete("border_oval_1_a", "border_oval_1_b")
+
+ if not self._canvas.find_withtag("border_oval_2_a") and width > 2 * corner_radius and "border_oval_2" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
+ anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_2_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
+ anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_2_a") and (not width > 2 * corner_radius or "border_oval_2" in exclude_parts):
+ self._canvas.delete("border_oval_2_a", "border_oval_2_b")
+
+ if not self._canvas.find_withtag("border_oval_3_a") and height > 2 * corner_radius \
+ and width > 2 * corner_radius and "border_oval_3" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_a", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
+ anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_3_b", "border_corner_part", "border_parts_right", "border_parts", "right_parts"),
+ anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_3_a") and (not (height > 2 * corner_radius
+ and width > 2 * corner_radius) or "border_oval_3" in exclude_parts):
+ self._canvas.delete("border_oval_3_a", "border_oval_3_b")
+
+ if not self._canvas.find_withtag("border_oval_4_a") and height > 2 * corner_radius and "border_oval_4" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_a", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("border_oval_4_b", "border_corner_part", "border_parts_left", "border_parts", "left_parts"), anchor=tkinter.CENTER,
+ angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("border_oval_4_a") and (not height > 2 * corner_radius or "border_oval_4" in exclude_parts):
+ self._canvas.delete("border_oval_4_a", "border_oval_4_b")
+
+ # change position of border corner parts
+ self._canvas.coords("border_oval_1_a", corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_1_b", corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_2_a", width - corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_2_b", width - corner_radius, corner_radius, corner_radius)
+ self._canvas.coords("border_oval_3_a", width - corner_radius, height - corner_radius, corner_radius)
+ self._canvas.coords("border_oval_3_b", width - corner_radius, height - corner_radius, corner_radius)
+ self._canvas.coords("border_oval_4_a", corner_radius, height - corner_radius, corner_radius)
+ self._canvas.coords("border_oval_4_b", corner_radius, height - corner_radius, corner_radius)
+
+ else:
+ self._canvas.delete("border_corner_part") # delete border corner parts if not needed
+
+ # create canvas border rectangle parts if not already created
+ if not self._canvas.find_withtag("border_rectangle_1"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_left_1", "border_rectangle_part", "border_parts_left", "border_parts", "left_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_left_2", "border_rectangle_part", "border_parts_left", "border_parts", "left_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_right_1", "border_rectangle_part", "border_parts_right", "border_parts", "right_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_right_2", "border_rectangle_part", "border_parts_right", "border_parts", "right_parts"), width=0)
+ requires_recoloring = True
+
+ # change position of border rectangle parts
+ self._canvas.coords("border_rectangle_left_1", (0, corner_radius, left_section_width, height - corner_radius))
+ self._canvas.coords("border_rectangle_left_2", (corner_radius, 0, left_section_width, height))
+ self._canvas.coords("border_rectangle_right_1", (left_section_width, corner_radius, width, height - corner_radius))
+ self._canvas.coords("border_rectangle_right_2", (left_section_width, 0, width - corner_radius, height))
+
+ else:
+ self._canvas.delete("border_parts")
+
+ # create inner button parts
+ if inner_corner_radius > 0:
+
+ # create canvas border corner parts if not already created, but only if they're needed and delete if not needed
+ if not self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_1_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER,
+ angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_1_a") and "inner_oval_1" in exclude_parts:
+ self._canvas.delete("inner_oval_1_a", "inner_oval_1_b")
+
+ if not self._canvas.find_withtag("inner_oval_2_a") and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_2" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_a", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_2_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER,
+ angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_2_a") and (not width - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_2" in exclude_parts):
+ self._canvas.delete("inner_oval_2_a", "inner_oval_2_b")
+
+ if not self._canvas.find_withtag("inner_oval_3_a") and height - (2 * border_width) > 2 * inner_corner_radius \
+ and width - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_3" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_a", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_3_b", "inner_corner_part", "inner_parts_right", "inner_parts", "right_parts"), anchor=tkinter.CENTER,
+ angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_3_a") and (not (height - (2 * border_width) > 2 * inner_corner_radius
+ and width - (2 * border_width) > 2 * inner_corner_radius) or "inner_oval_3" in exclude_parts):
+ self._canvas.delete("inner_oval_3_a", "inner_oval_3_b")
+
+ if not self._canvas.find_withtag("inner_oval_4_a") and height - (2 * border_width) > 2 * inner_corner_radius and "inner_oval_4" not in exclude_parts:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_a", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("inner_oval_4_b", "inner_corner_part", "inner_parts_left", "inner_parts", "left_parts"), anchor=tkinter.CENTER,
+ angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("inner_oval_4_a") and (not height - (2 * border_width) > 2 * inner_corner_radius or "inner_oval_4" in exclude_parts):
+ self._canvas.delete("inner_oval_4_a", "inner_oval_4_b")
+
+ # change position of border corner parts
+ self._canvas.coords("inner_oval_1_a", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_1_b", border_width + inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_2_a", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_2_b", width - border_width - inner_corner_radius, border_width + inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_3_a", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_3_b", width - border_width - inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_4_a", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ self._canvas.coords("inner_oval_4_b", border_width + inner_corner_radius, height - border_width - inner_corner_radius, inner_corner_radius)
+ else:
+ self._canvas.delete("inner_corner_part") # delete inner corner parts if not needed
+
+ # create canvas inner rectangle parts if not already created
+ if not self._canvas.find_withtag("inner_rectangle_1"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_left_1", "inner_rectangle_part", "inner_parts_left", "inner_parts", "left_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_right_1", "inner_rectangle_part", "inner_parts_right", "inner_parts", "right_parts"), width=0)
+ requires_recoloring = True
+
+ if not self._canvas.find_withtag("inner_rectangle_2") and inner_corner_radius * 2 < height - (border_width * 2):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_left_2", "inner_rectangle_part", "inner_parts_left", "inner_parts", "left_parts"), width=0)
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("inner_rectangle_right_2", "inner_rectangle_part", "inner_parts_right", "inner_parts", "right_parts"), width=0)
+ requires_recoloring = True
+
+ elif self._canvas.find_withtag("inner_rectangle_2") and not inner_corner_radius * 2 < height - (border_width * 2):
+ self._canvas.delete("inner_rectangle_left_2")
+ self._canvas.delete("inner_rectangle_right_2")
+
+ # change position of inner rectangle parts
+ self._canvas.coords("inner_rectangle_left_1", (border_width + inner_corner_radius,
+ border_width,
+ left_section_width,
+ height - border_width))
+ self._canvas.coords("inner_rectangle_left_2", (border_width,
+ border_width + inner_corner_radius,
+ left_section_width,
+ height - inner_corner_radius - border_width))
+ self._canvas.coords("inner_rectangle_right_1", (left_section_width,
+ border_width,
+ width - border_width - inner_corner_radius,
+ height - border_width))
+ self._canvas.coords("inner_rectangle_right_2", (left_section_width,
+ border_width + inner_corner_radius,
+ width - border_width,
+ height - inner_corner_radius - border_width))
+
+ if requires_recoloring: # new parts were added -> manage z-order
+ self._canvas.tag_lower("inner_parts")
+ self._canvas.tag_lower("border_parts")
+ self._canvas.tag_lower("background_parts")
+
+ return requires_recoloring
+
+ def draw_rounded_progress_bar_with_border(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
+ border_width: Union[float, int], progress_value_1: float, progress_value_2: float, orientation: str) -> bool:
+ """ Draws a rounded bar on the canvas, and onntop sits a progress bar from value 1 to value 2 (range 0-1, left to right, bottom to top).
+ The border elements get the 'border_parts' tag", the main elements get the 'inner_parts' tag and
+ the progress elements get the 'progress_parts' tag. The 'orientation' argument defines from which direction the progress starts (n, w, s, e).
+
+ returns bool if recoloring is necessary """
+
+ if self._round_width_to_even_numbers:
+ width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
+ if self._round_height_to_even_numbers:
+ height = math.floor(height / 2) * 2
+
+ if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
+ corner_radius = min(width / 2, height / 2)
+
+ border_width = round(border_width)
+ 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":
+ return self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
+ progress_value_1, progress_value_2, orientation)
+ elif self.preferred_drawing_method == "font_shapes":
+ return self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
+ progress_value_1, progress_value_2, orientation)
+
+ 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_slider_with_border_and_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, orientation: str) -> bool:
+
+ if self._round_width_to_even_numbers:
+ width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
+ if self._round_height_to_even_numbers:
+ height = math.floor(height / 2) * 2
+
+ if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
+ corner_radius = min(width / 2, height / 2)
+
+ if button_corner_radius > width / 2 or button_corner_radius > height / 2: # restrict button_corner_radius if it's too larger
+ button_corner_radius = min(width / 2, height / 2)
+
+ button_length = round(button_length)
+ border_width = round(border_width)
+ button_corner_radius = round(button_corner_radius)
+ 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":
+ return self.__draw_rounded_slider_with_border_and_button_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
+ button_length, button_corner_radius, slider_value, orientation)
+ elif self.preferred_drawing_method == "font_shapes":
+ return self.__draw_rounded_slider_with_border_and_button_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
+ button_length, button_corner_radius, slider_value, orientation)
+
+ def __draw_rounded_slider_with_border_and_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, orientation: str) -> bool:
+
+ # draw normal progressbar
+ requires_recoloring = self.__draw_rounded_progress_bar_with_border_polygon_shapes(width, height, corner_radius, border_width, inner_corner_radius,
+ 0, slider_value, orientation)
+
+ # create slider button part
+ if not self._canvas.find_withtag("slider_parts"):
+ self._canvas.create_polygon((0, 0, 0, 0), tags=("slider_line_1", "slider_parts"), joinstyle=tkinter.ROUND)
+ self._canvas.tag_raise("slider_parts") # manage z-order
+ 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
+
+ if orientation == "w":
+ slider_x_position = corner_radius + (button_length / 2) + (width - 2 * corner_radius - button_length) * slider_value
+ self._canvas.coords("slider_line_1",
+ slider_x_position - (button_length / 2), button_corner_radius,
+ slider_x_position + (button_length / 2), button_corner_radius,
+ slider_x_position + (button_length / 2), height - button_corner_radius,
+ slider_x_position - (button_length / 2), height - button_corner_radius)
+ self._canvas.itemconfig("slider_line_1",
+ width=button_corner_radius * 2)
+ elif orientation == "s":
+ slider_y_position = corner_radius + (button_length / 2) + (height - 2 * corner_radius - button_length) * (1 - slider_value)
+ self._canvas.coords("slider_line_1",
+ button_corner_radius, slider_y_position - (button_length / 2),
+ button_corner_radius, slider_y_position + (button_length / 2),
+ width - button_corner_radius, slider_y_position + (button_length / 2),
+ width - button_corner_radius, slider_y_position - (button_length / 2))
+ self._canvas.itemconfig("slider_line_1",
+ width=button_corner_radius * 2)
+
+ return requires_recoloring
+
+ def __draw_rounded_slider_with_border_and_button_font_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, orientation: str) -> bool:
+
+ # draw normal progressbar
+ requires_recoloring = self.__draw_rounded_progress_bar_with_border_font_shapes(width, height, corner_radius, border_width, inner_corner_radius,
+ 0, slider_value, orientation)
+
+ # create 4 circles (if not needed, then less)
+ if not self._canvas.find_withtag("slider_oval_1_a"):
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_1_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+
+ if not self._canvas.find_withtag("slider_oval_2_a") and button_length > 0:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_2_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("slider_oval_2_a") and not button_length > 0:
+ self._canvas.delete("slider_oval_2_a", "slider_oval_2_b")
+
+ if not self._canvas.find_withtag("slider_oval_4_a") and height > 2 * button_corner_radius:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_4_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_4_b", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("slider_oval_4_a") and not height > 2 * button_corner_radius:
+ self._canvas.delete("slider_oval_4_a", "slider_oval_4_b")
+
+ if not self._canvas.find_withtag("slider_oval_3_a") and button_length > 0 and height > 2 * button_corner_radius:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_a", "slider_corner_part", "slider_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("slider_oval_3_b", "slider_corner_part", "slider_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):
+ 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"), width=0)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("slider_rectangle_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:
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("slider_rectangle_2", "slider_rectangle_part", "slider_parts"), width=0)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("slider_rectangle_2") and not height > 2 * button_corner_radius:
+ self._canvas.delete("slider_rectangle_2")
+
+ # set positions of circles and rectangles
+ 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_a", slider_x_position - (button_length / 2), button_corner_radius, button_corner_radius)
+ self._canvas.coords("slider_oval_1_b", slider_x_position - (button_length / 2), button_corner_radius, button_corner_radius)
+ self._canvas.coords("slider_oval_2_a", slider_x_position + (button_length / 2), button_corner_radius, button_corner_radius)
+ self._canvas.coords("slider_oval_2_b", slider_x_position + (button_length / 2), button_corner_radius, button_corner_radius)
+ self._canvas.coords("slider_oval_3_a", slider_x_position + (button_length / 2), height - button_corner_radius, button_corner_radius)
+ self._canvas.coords("slider_oval_3_b", slider_x_position + (button_length / 2), height - button_corner_radius, button_corner_radius)
+ self._canvas.coords("slider_oval_4_a", slider_x_position - (button_length / 2), height - button_corner_radius, button_corner_radius)
+ self._canvas.coords("slider_oval_4_b", slider_x_position - (button_length / 2), height - button_corner_radius, button_corner_radius)
+
+ 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)
+
+ 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_a", button_corner_radius, slider_y_position - (button_length / 2), button_corner_radius)
+ self._canvas.coords("slider_oval_1_b", button_corner_radius, slider_y_position - (button_length / 2), button_corner_radius)
+ self._canvas.coords("slider_oval_2_a", button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
+ self._canvas.coords("slider_oval_2_b", button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
+ self._canvas.coords("slider_oval_3_a", width - button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
+ self._canvas.coords("slider_oval_3_b", width - button_corner_radius, slider_y_position + (button_length / 2), button_corner_radius)
+ self._canvas.coords("slider_oval_4_a", width - button_corner_radius, slider_y_position - (button_length / 2), button_corner_radius)
+ self._canvas.coords("slider_oval_4_b", width - button_corner_radius, 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)
+
+ if requires_recoloring: # new parts were added -> manage z-order
+ self._canvas.tag_raise("slider_parts")
+
+ return requires_recoloring
+
+ def draw_rounded_scrollbar(self, width: Union[float, int], height: Union[float, int], corner_radius: Union[float, int],
+ border_spacing: Union[float, int], start_value: float, end_value: float, orientation: str) -> bool:
+
+ if self._round_width_to_even_numbers:
+ width = math.floor(width / 2) * 2 # round _current_width and _current_height and restrict them to even values only
+ if self._round_height_to_even_numbers:
+ height = math.floor(height / 2) * 2
+
+ if corner_radius > width / 2 or corner_radius > height / 2: # restrict corner_radius if it's too larger
+ corner_radius = min(width / 2, height / 2)
+
+ border_spacing = round(border_spacing)
+ corner_radius = self.__calc_optimal_corner_radius(corner_radius) # optimize corner_radius for different drawing methods (different rounding)
+
+ if corner_radius >= border_spacing:
+ inner_corner_radius = corner_radius - border_spacing
+ else:
+ inner_corner_radius = 0
+
+ if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
+ return self.__draw_rounded_scrollbar_polygon_shapes(width, height, corner_radius, inner_corner_radius,
+ start_value, end_value, orientation)
+ elif self.preferred_drawing_method == "font_shapes":
+ return self.__draw_rounded_scrollbar_font_shapes(width, height, corner_radius, inner_corner_radius,
+ start_value, end_value, orientation)
+
+ def __draw_rounded_scrollbar_polygon_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int,
+ start_value: float, end_value: float, orientation: str) -> bool:
+ requires_recoloring = False
+
+ if not self._canvas.find_withtag("border_parts"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0)
+ requires_recoloring = True
+ self._canvas.coords("border_rectangle_1", 0, 0, width, height)
+
+ if not self._canvas.find_withtag("scrollbar_parts"):
+ self._canvas.create_polygon((0, 0, 0, 0), tags=("scrollbar_polygon_1", "scrollbar_parts"), joinstyle=tkinter.ROUND)
+ self._canvas.tag_raise("scrollbar_parts", "border_parts")
+ requires_recoloring = True
+
+ if orientation == "vertical":
+ self._canvas.coords("scrollbar_polygon_1",
+ corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
+ width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
+ width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value,
+ corner_radius, corner_radius + (height - 2 * corner_radius) * end_value)
+ elif orientation == "horizontal":
+ self._canvas.coords("scrollbar_polygon_1",
+ corner_radius + (width - 2 * corner_radius) * start_value, corner_radius,
+ corner_radius + (width - 2 * corner_radius) * end_value, corner_radius,
+ corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius,
+ corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius,)
+
+ self._canvas.itemconfig("scrollbar_polygon_1", width=inner_corner_radius * 2)
+
+ return requires_recoloring
+
+ def __draw_rounded_scrollbar_font_shapes(self, width: int, height: int, corner_radius: int, inner_corner_radius: int,
+ start_value: float, end_value: float, orientation: str) -> bool:
+ requires_recoloring = False
+
+ if not self._canvas.find_withtag("border_parts"):
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("border_rectangle_1", "border_parts"), width=0)
+ requires_recoloring = True
+ self._canvas.coords("border_rectangle_1", 0, 0, width, height)
+
+ if inner_corner_radius > 0:
+ if not self._canvas.find_withtag("scrollbar_oval_1_a"):
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_1_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+
+ if not self._canvas.find_withtag("scrollbar_oval_2_a") and width > 2 * corner_radius:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_2_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("scrollbar_oval_2_a") and not width > 2 * corner_radius:
+ self._canvas.delete("scrollbar_oval_2_a", "scrollbar_oval_2_b")
+
+ if not self._canvas.find_withtag("scrollbar_oval_3_a") and height > 2 * corner_radius and width > 2 * corner_radius:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_3_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("scrollbar_oval_3_a") and not (height > 2 * corner_radius and width > 2 * corner_radius):
+ self._canvas.delete("scrollbar_oval_3_a", "scrollbar_oval_3_b")
+
+ if not self._canvas.find_withtag("scrollbar_oval_4_a") and height > 2 * corner_radius:
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_a", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER)
+ self._canvas.create_aa_circle(0, 0, 0, tags=("scrollbar_oval_4_b", "scrollbar_corner_part", "scrollbar_parts"), anchor=tkinter.CENTER, angle=180)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("scrollbar_oval_4_a") and not height > 2 * corner_radius:
+ self._canvas.delete("scrollbar_oval_4_a", "scrollbar_oval_4_b")
+ else:
+ self._canvas.delete("scrollbar_corner_part")
+
+ if not self._canvas.find_withtag("scrollbar_rectangle_1") and height > 2 * corner_radius:
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_1", "scrollbar_rectangle_part", "scrollbar_parts"), width=0)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("scrollbar_rectangle_1") and not height > 2 * corner_radius:
+ self._canvas.delete("scrollbar_rectangle_1")
+
+ if not self._canvas.find_withtag("scrollbar_rectangle_2") and width > 2 * corner_radius:
+ self._canvas.create_rectangle(0, 0, 0, 0, tags=("scrollbar_rectangle_2", "scrollbar_rectangle_part", "scrollbar_parts"), width=0)
+ requires_recoloring = True
+ elif self._canvas.find_withtag("scrollbar_rectangle_2") and not width > 2 * corner_radius:
+ self._canvas.delete("scrollbar_rectangle_2")
+
+ if orientation == "vertical":
+ self._canvas.coords("scrollbar_rectangle_1",
+ corner_radius - inner_corner_radius, corner_radius + (height - 2 * corner_radius) * start_value,
+ width - (corner_radius - inner_corner_radius), corner_radius + (height - 2 * corner_radius) * end_value)
+ self._canvas.coords("scrollbar_rectangle_2",
+ corner_radius, corner_radius - inner_corner_radius + (height - 2 * corner_radius) * start_value,
+ width - (corner_radius), corner_radius + inner_corner_radius + (height - 2 * corner_radius) * end_value)
+
+ self._canvas.coords("scrollbar_oval_1_a", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_1_b", corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_2_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_2_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * start_value, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_3_a", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_3_b", width - corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_4_a", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_4_b", corner_radius, corner_radius + (height - 2 * corner_radius) * end_value, inner_corner_radius)
+
+ if orientation == "horizontal":
+ self._canvas.coords("scrollbar_rectangle_1",
+ corner_radius - inner_corner_radius + (width - 2 * corner_radius) * start_value, corner_radius,
+ corner_radius + inner_corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius)
+ self._canvas.coords("scrollbar_rectangle_2",
+ corner_radius + (width - 2 * corner_radius) * start_value, corner_radius - inner_corner_radius,
+ corner_radius + (width - 2 * corner_radius) * end_value, height - (corner_radius - inner_corner_radius))
+
+ self._canvas.coords("scrollbar_oval_1_a", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_1_b", corner_radius + (width - 2 * corner_radius) * start_value, corner_radius, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_2_a", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_2_b", corner_radius + (width - 2 * corner_radius) * end_value, corner_radius, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_3_a", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_3_b", corner_radius + (width - 2 * corner_radius) * end_value, height - corner_radius, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_4_a", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius)
+ self._canvas.coords("scrollbar_oval_4_b", corner_radius + (width - 2 * corner_radius) * start_value, height - corner_radius, inner_corner_radius)
+
+ return requires_recoloring
+
+ def draw_checkmark(self, width: Union[float, int], height: Union[float, int], size: Union[int, float]) -> bool:
+ """ Draws a rounded rectangle with a corner_radius and border_width on the canvas. The border elements have a 'border_parts' tag,
+ the main foreground elements have an 'inner_parts' tag to color the elements accordingly.
+
+ returns bool if recoloring is necessary """
+
+ size = round(size)
+ requires_recoloring = False
+
+ if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
+ x, y, radius = width / 2, height / 2, size / 2.8
+ if not self._canvas.find_withtag("checkmark"):
+ self._canvas.create_line(0, 0, 0, 0, tags=("checkmark", "create_line"), width=round(height / 8), joinstyle=tkinter.MITER, capstyle=tkinter.ROUND)
+ self._canvas.tag_raise("checkmark")
+ requires_recoloring = True
+
+ self._canvas.coords("checkmark",
+ x + radius, y - radius,
+ x - radius / 4, y + radius * 0.8,
+ x - radius, y + radius / 6)
+ elif self.preferred_drawing_method == "font_shapes":
+ if not self._canvas.find_withtag("checkmark"):
+ self._canvas.create_text(0, 0, text="Z", font=("CustomTkinter_shapes_font", -size), tags=("checkmark", "create_text"), anchor=tkinter.CENTER)
+ self._canvas.tag_raise("checkmark")
+ requires_recoloring = True
+
+ self._canvas.coords("checkmark", round(width / 2), round(height / 2))
+
+ return requires_recoloring
+
+ def draw_dropdown_arrow(self, x_position: Union[int, float], y_position: Union[int, float], size: Union[int, float]) -> bool:
+ """ Draws a dropdown bottom facing arrow at (x_position, y_position) in a given size
+
+ returns bool if recoloring is necessary """
+
+ x_position, y_position, size = round(x_position), round(y_position), round(size)
+ requires_recoloring = False
+
+ if self.preferred_drawing_method == "polygon_shapes" or self.preferred_drawing_method == "circle_shapes":
+ if not self._canvas.find_withtag("dropdown_arrow"):
+ self._canvas.create_line(0, 0, 0, 0, tags="dropdown_arrow", width=round(size / 3), joinstyle=tkinter.ROUND, capstyle=tkinter.ROUND)
+ self._canvas.tag_raise("dropdown_arrow")
+ requires_recoloring = True
+
+ self._canvas.coords("dropdown_arrow",
+ x_position - (size / 2),
+ y_position - (size / 5),
+ x_position,
+ y_position + (size / 5),
+ x_position + (size / 2),
+ y_position - (size / 5))
+
+ elif self.preferred_drawing_method == "font_shapes":
+ if not self._canvas.find_withtag("dropdown_arrow"):
+ self._canvas.create_text(0, 0, text="Y", font=("CustomTkinter_shapes_font", -size), tags="dropdown_arrow", anchor=tkinter.CENTER)
+ self._canvas.tag_raise("dropdown_arrow")
+ requires_recoloring = True
+
+ self._canvas.itemconfigure("dropdown_arrow", font=("CustomTkinter_shapes_font", -size))
+ self._canvas.coords("dropdown_arrow", x_position, y_position)
+
+ return requires_recoloring
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/__init__.py
new file mode 100644
index 0000000..75e2d84
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/__init__.py
@@ -0,0 +1,2 @@
+from .dropdown_menu import DropdownMenu
+from .ctk_base_class import CTkBaseClass
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py
new file mode 100644
index 0000000..afd9431
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py
@@ -0,0 +1,326 @@
+import sys
+import warnings
+import tkinter
+import tkinter.ttk as ttk
+from typing import Union, Callable, Tuple, Any
+
+try:
+ from typing import TypedDict
+except ImportError:
+ from typing_extensions import TypedDict
+
+from .... import windows # import windows for isinstance checks
+
+from ..theme import ThemeManager
+from ..font import CTkFont
+from ..image import CTkImage
+from ..appearance_mode import CTkAppearanceModeBaseClass
+from ..scaling import CTkScalingBaseClass
+
+from ..utility import pop_from_dict_by_set, check_kwargs_empty
+
+
+class CTkBaseClass(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
+ """ Base class of every CTk widget, handles the dimensions, bg_color,
+ appearance_mode changes, scaling, bg changes of master if master is not a CTk widget """
+
+ # attributes that are passed to and managed by the tkinter frame only:
+ _valid_tk_frame_attributes: set = {"cursor"}
+
+ _cursor_manipulation_enabled: bool = True
+
+ def __init__(self,
+ master: Any,
+ width: int = 0,
+ height: int = 0,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ **kwargs):
+
+ # call init methods of super classes
+ tkinter.Frame.__init__(self, master=master, width=width, height=height, **pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes))
+ CTkAppearanceModeBaseClass.__init__(self)
+ CTkScalingBaseClass.__init__(self, scaling_type="widget")
+
+ # check if kwargs is empty, if not raise error for unsupported arguments
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ # dimensions independent of scaling
+ self._current_width = width # _current_width and _current_height in pixel, represent current size of the widget
+ self._current_height = height # _current_width and _current_height are independent of the scale
+ self._desired_width = width # _desired_width and _desired_height, represent desired size set by width and height
+ self._desired_height = height
+
+ # set width and height of tkinter.Frame
+ super().configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ # save latest geometry function and kwargs
+ class GeometryCallDict(TypedDict):
+ function: Callable
+ kwargs: dict
+ self._last_geometry_manager_call: Union[GeometryCallDict, None] = None
+
+ # background color
+ self._bg_color: Union[str, Tuple[str, str]] = self._detect_color_of_master() if bg_color == "transparent" else self._check_color_type(bg_color, transparency=True)
+
+ # set bg color of tkinter.Frame
+ super().configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ # add configure callback to tkinter.Frame
+ super().bind('', self._update_dimensions_event)
+
+ # overwrite configure methods of master when master is tkinter widget, so that bg changes get applied on child CTk widget as well
+ if isinstance(self.master, (tkinter.Tk, tkinter.Toplevel, tkinter.Frame, tkinter.LabelFrame, ttk.Frame, ttk.LabelFrame, ttk.Notebook)) and not isinstance(self.master, (CTkBaseClass, CTkAppearanceModeBaseClass)):
+ master_old_configure = self.master.config
+
+ def new_configure(*args, **kwargs):
+ if "bg" in kwargs:
+ self.configure(bg_color=kwargs["bg"])
+ elif "background" in kwargs:
+ self.configure(bg_color=kwargs["background"])
+
+ # args[0] is dict when attribute gets changed by widget[] syntax
+ elif len(args) > 0 and type(args[0]) == dict:
+ if "bg" in args[0]:
+ self.configure(bg_color=args[0]["bg"])
+ elif "background" in args[0]:
+ self.configure(bg_color=args[0]["background"])
+ master_old_configure(*args, **kwargs)
+
+ self.master.config = new_configure
+ self.master.configure = new_configure
+
+ def destroy(self):
+ """ Destroy this and all descendants widgets. """
+
+ # call destroy methods of super classes
+ tkinter.Frame.destroy(self)
+ CTkAppearanceModeBaseClass.destroy(self)
+ CTkScalingBaseClass.destroy(self)
+
+ def _draw(self, no_color_updates: bool = False):
+ """ can be overridden but super method must be called """
+ if no_color_updates is False:
+ # Configuring color of tkinter.Frame not necessary at the moment?
+ # Causes flickering on Windows and Linux for segmented button for some reason!
+ # super().configure(bg=self._apply_appearance_mode(self._bg_color))
+ pass
+
+ def config(self, *args, **kwargs):
+ raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
+
+ def configure(self, require_redraw=False, **kwargs):
+ """ basic configure with bg_color, width, height support, calls configure of tkinter.Frame, updates in the end """
+
+ if "width" in kwargs:
+ self._set_dimensions(width=kwargs.pop("width"))
+
+ if "height" in kwargs:
+ self._set_dimensions(height=kwargs.pop("height"))
+
+ if "bg_color" in kwargs:
+ new_bg_color = self._check_color_type(kwargs.pop("bg_color"), transparency=True)
+ if new_bg_color == "transparent":
+ self._bg_color = self._detect_color_of_master()
+ else:
+ self._bg_color = self._check_color_type(new_bg_color)
+ require_redraw = True
+
+ super().configure(**pop_from_dict_by_set(kwargs, self._valid_tk_frame_attributes)) # configure tkinter.Frame
+
+ # if there are still items in the kwargs dict, raise ValueError
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ if require_redraw:
+ self._draw()
+
+ def cget(self, attribute_name: str):
+ """ basic cget with bg_color, width, height support, calls cget of tkinter.Frame """
+
+ if attribute_name == "bg_color":
+ return self._bg_color
+ elif attribute_name == "width":
+ return self._desired_width
+ elif attribute_name == "height":
+ return self._desired_height
+
+ elif attribute_name in self._valid_tk_frame_attributes:
+ return super().cget(attribute_name) # cget of tkinter.Frame
+ else:
+ raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.")
+
+ def _check_font_type(self, font: any):
+ """ check font type when passed to widget """
+ if isinstance(font, CTkFont):
+ return font
+
+ elif type(font) == tuple and len(font) == 1:
+ warnings.warn(f"{type(self).__name__} Warning: font {font} given without size, will be extended with default text size of current theme\n")
+ return font[0], ThemeManager.theme["text"]["size"]
+
+ elif type(font) == tuple and 2 <= len(font) <= 6:
+ return font
+
+ else:
+ raise ValueError(f"Wrong font type {type(font)}\n" +
+ f"For consistency, Customtkinter requires the font argument to be a tuple of len 2 to 6 or an instance of CTkFont.\n" +
+ f"\nUsage example:\n" +
+ f"font=customtkinter.CTkFont(family='', size=)\n" +
+ f"font=('', )\n")
+
+ def _check_image_type(self, image: any):
+ """ check image type when passed to widget """
+ if image is None:
+ return image
+ elif isinstance(image, CTkImage):
+ return image
+ else:
+ warnings.warn(f"{type(self).__name__} Warning: Given image is not CTkImage but {type(image)}. Image can not be scaled on HighDPI displays, use CTkImage instead.\n")
+ return image
+
+ def _update_dimensions_event(self, event):
+ # only redraw if dimensions changed (for performance), independent of scaling
+ if round(self._current_width) != round(self._reverse_widget_scaling(event.width)) or round(self._current_height) != round(self._reverse_widget_scaling(event.height)):
+ self._current_width = self._reverse_widget_scaling(event.width) # adjust current size according to new size given by event
+ self._current_height = self._reverse_widget_scaling(event.height) # _current_width and _current_height are independent of the scale
+
+ self._draw(no_color_updates=True) # faster drawing without color changes
+
+ def _detect_color_of_master(self, master_widget=None) -> Union[str, Tuple[str, str]]:
+ """ detect foreground color of master widget for bg_color and transparent color """
+
+ if master_widget is None:
+ master_widget = self.master
+
+ if isinstance(master_widget, (windows.widgets.core_widget_classes.CTkBaseClass, windows.CTk, windows.CTkToplevel, windows.widgets.ctk_scrollable_frame.CTkScrollableFrame)):
+ if master_widget.cget("fg_color") is not None and master_widget.cget("fg_color") != "transparent":
+ return master_widget.cget("fg_color")
+
+ elif isinstance(master_widget, windows.widgets.ctk_scrollable_frame.CTkScrollableFrame):
+ return self._detect_color_of_master(master_widget.master.master.master)
+
+ # if fg_color of master is None, try to retrieve fg_color from master of master
+ elif hasattr(master_widget, "master"):
+ return self._detect_color_of_master(master_widget.master)
+
+ elif isinstance(master_widget, (ttk.Frame, ttk.LabelFrame, ttk.Notebook, ttk.Label)): # master is ttk widget
+ try:
+ ttk_style = ttk.Style()
+ return ttk_style.lookup(master_widget.winfo_class(), 'background')
+ except Exception:
+ return "#FFFFFF", "#000000"
+
+ else: # master is normal tkinter widget
+ try:
+ return master_widget.cget("bg") # try to get bg color by .cget() method
+ except Exception:
+ return "#FFFFFF", "#000000"
+
+ def _set_appearance_mode(self, mode_string):
+ super()._set_appearance_mode(mode_string)
+ self._draw()
+ super().update_idletasks()
+
+ def _set_scaling(self, new_widget_scaling, new_window_scaling):
+ super()._set_scaling(new_widget_scaling, new_window_scaling)
+
+ super().configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ if self._last_geometry_manager_call is not None:
+ self._last_geometry_manager_call["function"](**self._apply_argument_scaling(self._last_geometry_manager_call["kwargs"]))
+
+ def _set_dimensions(self, width=None, height=None):
+ if width is not None:
+ self._desired_width = width
+ if height is not None:
+ self._desired_height = height
+
+ super().configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ def bind(self, sequence=None, command=None, add=None):
+ raise NotImplementedError
+
+ def unbind(self, sequence=None, funcid=None):
+ raise NotImplementedError
+
+ def unbind_all(self, sequence):
+ raise AttributeError("'unbind_all' is not allowed, because it would delete necessary internal callbacks for all widgets")
+
+ def bind_all(self, sequence=None, func=None, add=None):
+ raise AttributeError("'bind_all' is not allowed, could result in undefined behavior")
+
+ def place(self, **kwargs):
+ """
+ Place a widget in the parent widget. Use as options:
+ in=master - master relative to which the widget is placed
+ in_=master - see 'in' option description
+ x=amount - locate anchor of this widget at position x of master
+ y=amount - locate anchor of this widget at position y of master
+ relx=amount - locate anchor of this widget between 0.0 and 1.0 relative to width of master (1.0 is right edge)
+ rely=amount - locate anchor of this widget between 0.0 and 1.0 relative to height of master (1.0 is bottom edge)
+ anchor=NSEW (or subset) - position anchor according to given direction
+ width=amount - width of this widget in pixel
+ height=amount - height of this widget in pixel
+ relwidth=amount - width of this widget between 0.0 and 1.0 relative to width of master (1.0 is the same width as the master)
+ relheight=amount - height of this widget between 0.0 and 1.0 relative to height of master (1.0 is the same height as the master)
+ bordermode="inside" or "outside" - whether to take border width of master widget into account
+ """
+ if "width" in kwargs or "height" in kwargs:
+ raise ValueError("'width' and 'height' arguments must be passed to the constructor of the widget, not the place method")
+ self._last_geometry_manager_call = {"function": super().place, "kwargs": kwargs}
+ return super().place(**self._apply_argument_scaling(kwargs))
+
+ def place_forget(self):
+ """ Unmap this widget. """
+ self._last_geometry_manager_call = None
+ return super().place_forget()
+
+ def pack(self, **kwargs):
+ """
+ Pack a widget in the parent widget. Use as options:
+ after=widget - pack it after you have packed widget
+ anchor=NSEW (or subset) - position widget according to given direction
+ before=widget - pack it before you will pack widget
+ expand=bool - expand widget if parent size grows
+ fill=NONE or X or Y or BOTH - fill widget if widget grows
+ in=master - use master to contain this widget
+ in_=master - see 'in' option description
+ ipadx=amount - add internal padding in x direction
+ ipady=amount - add internal padding in y direction
+ padx=amount - add padding in x direction
+ pady=amount - add padding in y direction
+ side=TOP or BOTTOM or LEFT or RIGHT - where to add this widget.
+ """
+ self._last_geometry_manager_call = {"function": super().pack, "kwargs": kwargs}
+ return super().pack(**self._apply_argument_scaling(kwargs))
+
+ def pack_forget(self):
+ """ Unmap this widget and do not use it for the packing order. """
+ self._last_geometry_manager_call = None
+ return super().pack_forget()
+
+ def grid(self, **kwargs):
+ """
+ Position a widget in the parent widget in a grid. Use as options:
+ column=number - use cell identified with given column (starting with 0)
+ columnspan=number - this widget will span several columns
+ in=master - use master to contain this widget
+ in_=master - see 'in' option description
+ ipadx=amount - add internal padding in x direction
+ ipady=amount - add internal padding in y direction
+ padx=amount - add padding in x direction
+ pady=amount - add padding in y direction
+ row=number - use cell identified with given row (starting with 0)
+ rowspan=number - this widget will span several rows
+ sticky=NSEW - if cell is larger on which sides will this widget stick to the cell boundary
+ """
+ self._last_geometry_manager_call = {"function": super().grid, "kwargs": kwargs}
+ return super().grid(**self._apply_argument_scaling(kwargs))
+
+ def grid_forget(self):
+ """ Unmap this widget. """
+ self._last_geometry_manager_call = None
+ return super().grid_forget()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py
new file mode 100644
index 0000000..a6b8186
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py
@@ -0,0 +1,198 @@
+import tkinter
+import sys
+from typing import Union, Tuple, Callable, List, Optional
+
+from ..theme import ThemeManager
+from ..font import CTkFont
+from ..appearance_mode import CTkAppearanceModeBaseClass
+from ..scaling import CTkScalingBaseClass
+
+
+class DropdownMenu(tkinter.Menu, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
+ def __init__(self, *args,
+ min_character_width: int = 18,
+
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ font: Optional[Union[tuple, CTkFont]] = None,
+ command: Union[Callable, None] = None,
+ values: Optional[List[str]] = None,
+ **kwargs):
+
+ # call init methods of super classes
+ tkinter.Menu.__init__(self, *args, **kwargs)
+ CTkAppearanceModeBaseClass.__init__(self)
+ CTkScalingBaseClass.__init__(self, scaling_type="widget")
+
+ self._min_character_width = min_character_width
+ self._fg_color = ThemeManager.theme["DropdownMenu"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._hover_color = ThemeManager.theme["DropdownMenu"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
+ self._text_color = ThemeManager.theme["DropdownMenu"]["text_color"] if text_color is None else self._check_color_type(text_color)
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._configure_menu_for_platforms()
+
+ self._values = values
+ self._command = command
+
+ self._add_menu_commands()
+
+ def destroy(self):
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ # call destroy methods of super classes
+ tkinter.Menu.destroy(self)
+ CTkAppearanceModeBaseClass.destroy(self)
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling """
+ super().configure(font=self._apply_font_scaling(self._font))
+
+ def _configure_menu_for_platforms(self):
+ """ apply platform specific appearance attributes, configure all colors """
+
+ if sys.platform == "darwin":
+ super().configure(tearoff=False,
+ font=self._apply_font_scaling(self._font))
+
+ elif sys.platform.startswith("win"):
+ super().configure(tearoff=False,
+ relief="flat",
+ activebackground=self._apply_appearance_mode(self._hover_color),
+ borderwidth=self._apply_widget_scaling(4),
+ activeborderwidth=self._apply_widget_scaling(4),
+ bg=self._apply_appearance_mode(self._fg_color),
+ fg=self._apply_appearance_mode(self._text_color),
+ activeforeground=self._apply_appearance_mode(self._text_color),
+ font=self._apply_font_scaling(self._font),
+ cursor="hand2")
+
+ else:
+ super().configure(tearoff=False,
+ relief="flat",
+ activebackground=self._apply_appearance_mode(self._hover_color),
+ borderwidth=0,
+ activeborderwidth=0,
+ bg=self._apply_appearance_mode(self._fg_color),
+ fg=self._apply_appearance_mode(self._text_color),
+ activeforeground=self._apply_appearance_mode(self._text_color),
+ font=self._apply_font_scaling(self._font))
+
+ def _add_menu_commands(self):
+ """ delete existing menu labels and createe new labels with command according to values list """
+
+ self.delete(0, "end") # delete all old commands
+
+ if sys.platform.startswith("linux"):
+ for value in self._values:
+ self.add_command(label=" " + value.ljust(self._min_character_width) + " ",
+ command=lambda v=value: self._button_callback(v),
+ compound="left")
+ else:
+ for value in self._values:
+ self.add_command(label=value.ljust(self._min_character_width),
+ command=lambda v=value: self._button_callback(v),
+ compound="left")
+
+ def _button_callback(self, value):
+ if self._command is not None:
+ self._command(value)
+
+ def open(self, x: Union[int, float], y: Union[int, float]):
+
+ if sys.platform == "darwin":
+ y += self._apply_widget_scaling(8)
+ else:
+ y += self._apply_widget_scaling(3)
+
+ if sys.platform == "darwin" or sys.platform.startswith("win"):
+ self.post(int(x), int(y))
+ else: # Linux
+ self.tk_popup(int(x), int(y))
+
+ def configure(self, **kwargs):
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ super().configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ if "hover_color" in kwargs:
+ self._hover_color = self._check_color_type(kwargs.pop("hover_color"))
+ super().configure(activebackground=self._apply_appearance_mode(self._hover_color))
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ super().configure(fg=self._apply_appearance_mode(self._text_color))
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "values" in kwargs:
+ self._values = kwargs.pop("values")
+ self._add_menu_commands()
+
+ super().configure(**kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "min_character_width":
+ return self._min_character_width
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "hover_color":
+ return self._hover_color
+ elif attribute_name == "text_color":
+ return self._text_color
+
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "values":
+ return self._values
+
+ else:
+ return super().cget(attribute_name)
+
+ @staticmethod
+ def _check_font_type(font: any):
+ if isinstance(font, CTkFont):
+ return font
+
+ elif type(font) == tuple and len(font) == 1:
+ sys.stderr.write(f"Warning: font {font} given without size, will be extended with default text size of current theme\n")
+ return font[0], ThemeManager.theme["text"]["size"]
+
+ elif type(font) == tuple and 2 <= len(font) <= 3:
+ return font
+
+ else:
+ raise ValueError(f"Wrong font type {type(font)} for font '{font}'\n" +
+ f"For consistency, Customtkinter requires the font argument to be a tuple of len 2 or 3 or an instance of CTkFont.\n" +
+ f"\nUsage example:\n" +
+ f"font=customtkinter.CTkFont(family='', size=)\n" +
+ f"font=('', )\n")
+
+ def _set_scaling(self, new_widget_scaling, new_window_scaling):
+ super()._set_scaling(new_widget_scaling, new_window_scaling)
+ self._configure_menu_for_platforms()
+
+ def _set_appearance_mode(self, mode_string):
+ """ colors won't update on appearance mode change when dropdown is open, because it's not necessary """
+ super()._set_appearance_mode(mode_string)
+ self._configure_menu_for_platforms()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_button.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_button.py
new file mode 100644
index 0000000..e9f7839
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_button.py
@@ -0,0 +1,594 @@
+import tkinter
+import sys
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+from .image import CTkImage
+
+
+class CTkButton(CTkBaseClass):
+ """
+ Button with rounded corners, border, hover effect, image support, click command and textvariable.
+ For detailed information check out the documentation.
+ """
+
+ _image_label_spacing: int = 6
+
+ def __init__(self,
+ master: Any,
+ width: int = 140,
+ height: int = 28,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+ border_spacing: int = 2,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+
+ background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
+ round_width_to_even_numbers: bool = True,
+ round_height_to_even_numbers: bool = True,
+
+ text: str = "CTkButton",
+ font: Optional[Union[tuple, CTkFont]] = None,
+ textvariable: Union[tkinter.Variable, None] = None,
+ image: Union[CTkImage, "ImageTk.PhotoImage", None] = None,
+ state: str = "normal",
+ hover: bool = True,
+ command: Union[Callable[[], Any], None] = None,
+ compound: str = "left",
+ anchor: str = "center",
+ **kwargs):
+
+ # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # shape
+ self._corner_radius: int = ThemeManager.theme["CTkButton"]["corner_radius"] if corner_radius is None else corner_radius
+ self._corner_radius = min(self._corner_radius, round(self._current_height / 2))
+ self._border_width: int = ThemeManager.theme["CTkButton"]["border_width"] if border_width is None else border_width
+ self._border_spacing: int = border_spacing
+
+ # color
+ self._fg_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
+ self._hover_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
+ self._border_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["border_color"] if border_color is None else self._check_color_type(border_color)
+ self._text_color: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._text_color_disabled: Union[str, Tuple[str, str]] = ThemeManager.theme["CTkButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
+
+ # rendering options
+ self._background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = background_corner_colors # rendering options for DrawEngine
+ self._round_width_to_even_numbers: bool = round_width_to_even_numbers # rendering options for DrawEngine
+ self._round_height_to_even_numbers: bool = round_height_to_even_numbers # rendering options for DrawEngine
+
+ # text, font
+ self._text = text
+ self._text_label: Union[tkinter.Label, None] = None
+ self._textvariable: tkinter.Variable = textvariable
+ self._font: Union[tuple, CTkFont] = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ # image
+ self._image = self._check_image_type(image)
+ self._image_label: Union[tkinter.Label, None] = None
+ if isinstance(self._image, CTkImage):
+ self._image.add_configure_callback(self._update_image)
+
+ # other
+ self._state: str = state
+ self._hover: bool = hover
+ self._command: Callable = command
+ self._compound: str = compound
+ self._anchor: str = anchor
+ self._click_animation_running: bool = False
+
+ # canvas and draw engine
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.grid(row=0, column=0, rowspan=5, columnspan=5, sticky="nsew")
+ self._draw_engine = DrawEngine(self._canvas)
+ self._draw_engine.set_round_to_even_numbers(self._round_width_to_even_numbers, self._round_height_to_even_numbers) # rendering options
+
+ # configure cursor and initial draw
+ self._create_bindings()
+ self._set_cursor()
+ self._draw()
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_enter)
+
+ if self._text_label is not None:
+ self._text_label.bind("", self._on_enter)
+ if self._image_label is not None:
+ self._image_label.bind("", self._on_enter)
+
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_leave)
+
+ if self._text_label is not None:
+ self._text_label.bind("", self._on_leave)
+ if self._image_label is not None:
+ self._image_label.bind("", self._on_leave)
+
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._clicked)
+
+ if self._text_label is not None:
+ self._text_label.bind("", self._clicked)
+ if self._image_label is not None:
+ self._image_label.bind("", self._clicked)
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._create_grid()
+
+ if self._text_label is not None:
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ self._update_image()
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw(no_color_updates=True)
+
+ def _set_appearance_mode(self, mode_string):
+ super()._set_appearance_mode(mode_string)
+ self._update_image()
+
+ def _set_dimensions(self, width: int = None, height: int = None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ if self._text_label is not None:
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._canvas.grid_forget()
+ self._canvas.grid(row=0, column=0, rowspan=5, columnspan=5, sticky="nsew")
+
+ def _update_image(self):
+ if self._image_label is not None:
+ if isinstance(self._image, CTkImage):
+ self._image_label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(),
+ self._get_appearance_mode()))
+ elif self._image is not None:
+ self._image_label.configure(image=self._image)
+
+ def destroy(self):
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ if self._background_corner_colors is not None:
+ self._draw_engine.draw_background_corners(self._apply_widget_scaling(self._current_width),
+ self._apply_widget_scaling(self._current_height))
+ self._canvas.itemconfig("background_corner_top_left", fill=self._apply_appearance_mode(self._background_corner_colors[0]))
+ self._canvas.itemconfig("background_corner_top_right", fill=self._apply_appearance_mode(self._background_corner_colors[1]))
+ self._canvas.itemconfig("background_corner_bottom_right", fill=self._apply_appearance_mode(self._background_corner_colors[2]))
+ self._canvas.itemconfig("background_corner_bottom_left", fill=self._apply_appearance_mode(self._background_corner_colors[3]))
+ else:
+ self._canvas.delete("background_parts")
+
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(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_width))
+
+ if no_color_updates is False or requires_recoloring:
+
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ # set color for the button border parts (outline)
+ self._canvas.itemconfig("border_parts",
+ outline=self._apply_appearance_mode(self._border_color),
+ fill=self._apply_appearance_mode(self._border_color))
+
+ # set color for inner button parts
+ if self._fg_color == "transparent":
+ self._canvas.itemconfig("inner_parts",
+ outline=self._apply_appearance_mode(self._bg_color),
+ fill=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ outline=self._apply_appearance_mode(self._fg_color),
+ fill=self._apply_appearance_mode(self._fg_color))
+
+ # create text label if text given
+ if self._text is not None and self._text != "":
+
+ if self._text_label is None:
+ self._text_label = tkinter.Label(master=self,
+ font=self._apply_font_scaling(self._font),
+ text=self._text,
+ padx=0,
+ pady=0,
+ borderwidth=1,
+ textvariable=self._textvariable)
+ self._create_grid()
+
+ self._text_label.bind("", self._on_enter)
+ self._text_label.bind("", self._on_leave)
+ self._text_label.bind("", self._clicked)
+ self._text_label.bind("", self._clicked)
+
+ if no_color_updates is False:
+ # set text_label fg color (text color)
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color))
+
+ if self._state == tkinter.DISABLED:
+ self._text_label.configure(fg=(self._apply_appearance_mode(self._text_color_disabled)))
+ else:
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color))
+
+ if self._apply_appearance_mode(self._fg_color) == "transparent":
+ self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._text_label.configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ else:
+ # delete text_label if no text given
+ if self._text_label is not None:
+ self._text_label.destroy()
+ self._text_label = None
+ self._create_grid()
+
+ # create image label if image given
+ if self._image is not None:
+
+ if self._image_label is None:
+ self._image_label = tkinter.Label(master=self)
+ self._update_image() # set image
+ self._create_grid()
+
+ self._image_label.bind("", self._on_enter)
+ self._image_label.bind("", self._on_leave)
+ self._image_label.bind("", self._clicked)
+ self._image_label.bind("", self._clicked)
+
+ if no_color_updates is False:
+ # set image_label bg color (background color of label)
+ if self._apply_appearance_mode(self._fg_color) == "transparent":
+ self._image_label.configure(bg=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._image_label.configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ else:
+ # delete text_label if no text given
+ if self._image_label is not None:
+ self._image_label.destroy()
+ self._image_label = None
+ self._create_grid()
+
+ def _create_grid(self):
+ """ configure grid system (5x5) """
+
+ # Outer rows and columns have weight of 1000 to overpower the rows and columns of the label and image with weight 1.
+ # Rows and columns of image and label need weight of 1 to collapse in case of missing space on the button,
+ # so image and label need sticky option to stick together in the center, and therefore outer rows and columns
+ # need weight of 100 in case of other anchor than center.
+ n_padding_weight, s_padding_weight, e_padding_weight, w_padding_weight = 1000, 1000, 1000, 1000
+ if self._anchor != "center":
+ if "n" in self._anchor:
+ n_padding_weight, s_padding_weight = 0, 1000
+ if "s" in self._anchor:
+ n_padding_weight, s_padding_weight = 1000, 0
+ if "e" in self._anchor:
+ e_padding_weight, w_padding_weight = 1000, 0
+ if "w" in self._anchor:
+ e_padding_weight, w_padding_weight = 0, 1000
+
+ scaled_minsize_rows = self._apply_widget_scaling(max(self._border_width + 1, self._border_spacing))
+ scaled_minsize_columns = self._apply_widget_scaling(max(self._corner_radius, self._border_width + 1, self._border_spacing))
+
+ self.grid_rowconfigure(0, weight=n_padding_weight, minsize=scaled_minsize_rows)
+ self.grid_rowconfigure(4, weight=s_padding_weight, minsize=scaled_minsize_rows)
+ self.grid_columnconfigure(0, weight=e_padding_weight, minsize=scaled_minsize_columns)
+ self.grid_columnconfigure(4, weight=w_padding_weight, minsize=scaled_minsize_columns)
+
+ if self._compound in ("right", "left"):
+ self.grid_rowconfigure(2, weight=1)
+ if self._image_label is not None and self._text_label is not None:
+ self.grid_columnconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._image_label_spacing))
+ else:
+ self.grid_columnconfigure(2, weight=0)
+
+ self.grid_rowconfigure((1, 3), weight=0)
+ self.grid_columnconfigure((1, 3), weight=1)
+ else:
+ self.grid_columnconfigure(2, weight=1)
+ if self._image_label is not None and self._text_label is not None:
+ self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._image_label_spacing))
+ else:
+ self.grid_rowconfigure(2, weight=0)
+
+ self.grid_columnconfigure((1, 3), weight=0)
+ self.grid_rowconfigure((1, 3), weight=1)
+
+ if self._compound == "right":
+ if self._image_label is not None:
+ self._image_label.grid(row=2, column=3, sticky="w")
+ if self._text_label is not None:
+ self._text_label.grid(row=2, column=1, sticky="e")
+ elif self._compound == "left":
+ if self._image_label is not None:
+ self._image_label.grid(row=2, column=1, sticky="e")
+ if self._text_label is not None:
+ self._text_label.grid(row=2, column=3, sticky="w")
+ elif self._compound == "top":
+ if self._image_label is not None:
+ self._image_label.grid(row=1, column=2, sticky="s")
+ if self._text_label is not None:
+ self._text_label.grid(row=3, column=2, sticky="n")
+ elif self._compound == "bottom":
+ if self._image_label is not None:
+ self._image_label.grid(row=3, column=2, sticky="n")
+ if self._text_label is not None:
+ self._text_label.grid(row=1, column=2, sticky="s")
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ self._create_grid()
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ self._create_grid()
+ require_redraw = True
+
+ if "border_spacing" in kwargs:
+ self._border_spacing = kwargs.pop("border_spacing")
+ self._create_grid()
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
+ require_redraw = True
+
+ if "hover_color" in kwargs:
+ self._hover_color = self._check_color_type(kwargs.pop("hover_color"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "text_color_disabled" in kwargs:
+ self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ require_redraw = True
+
+ if "background_corner_colors" in kwargs:
+ self._background_corner_colors = kwargs.pop("background_corner_colors")
+ require_redraw = True
+
+ if "text" in kwargs:
+ self._text = kwargs.pop("text")
+ if self._text_label is None:
+ require_redraw = True # text_label will be created in .draw()
+ else:
+ self._text_label.configure(text=self._text)
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "textvariable" in kwargs:
+ self._textvariable = kwargs.pop("textvariable")
+ if self._text_label is not None:
+ self._text_label.configure(textvariable=self._textvariable)
+
+ if "image" in kwargs:
+ if isinstance(self._image, CTkImage):
+ self._image.remove_configure_callback(self._update_image)
+ self._image = self._check_image_type(kwargs.pop("image"))
+ if isinstance(self._image, CTkImage):
+ self._image.add_configure_callback(self._update_image)
+ self._update_image()
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ self._set_cursor()
+ require_redraw = True
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+ self._set_cursor()
+
+ if "compound" in kwargs:
+ self._compound = kwargs.pop("compound")
+ require_redraw = True
+
+ if "anchor" in kwargs:
+ self._anchor = kwargs.pop("anchor")
+ self._create_grid()
+ require_redraw = True
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+ elif attribute_name == "border_spacing":
+ return self._border_spacing
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "hover_color":
+ return self._hover_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "text_color_disabled":
+ return self._text_color_disabled
+ elif attribute_name == "background_corner_colors":
+ return self._background_corner_colors
+
+ elif attribute_name == "text":
+ return self._text
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "textvariable":
+ return self._textvariable
+ elif attribute_name == "image":
+ return self._image
+ elif attribute_name == "state":
+ return self._state
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "compound":
+ return self._compound
+ elif attribute_name == "anchor":
+ return self._anchor
+ else:
+ return super().cget(attribute_name)
+
+ def _set_cursor(self):
+ if self._cursor_manipulation_enabled:
+ if self._state == tkinter.DISABLED:
+ if sys.platform == "darwin" and self._command is not None:
+ self.configure(cursor="arrow")
+ elif sys.platform.startswith("win") and self._command is not None:
+ self.configure(cursor="arrow")
+
+ elif self._state == tkinter.NORMAL:
+ if sys.platform == "darwin" and self._command is not None:
+ self.configure(cursor="pointinghand")
+ elif sys.platform.startswith("win") and self._command is not None:
+ self.configure(cursor="hand2")
+
+ def _on_enter(self, event=None):
+ if self._hover is True and self._state == "normal":
+ if self._hover_color is None:
+ inner_parts_color = self._fg_color
+ else:
+ inner_parts_color = self._hover_color
+
+ # set color of inner button parts to hover color
+ self._canvas.itemconfig("inner_parts",
+ outline=self._apply_appearance_mode(inner_parts_color),
+ fill=self._apply_appearance_mode(inner_parts_color))
+
+ # set text_label bg color to button hover color
+ if self._text_label is not None:
+ self._text_label.configure(bg=self._apply_appearance_mode(inner_parts_color))
+
+ # set image_label bg color to button hover color
+ if self._image_label is not None:
+ self._image_label.configure(bg=self._apply_appearance_mode(inner_parts_color))
+
+ def _on_leave(self, event=None):
+ self._click_animation_running = False
+
+ if self._fg_color == "transparent":
+ inner_parts_color = self._bg_color
+ else:
+ inner_parts_color = self._fg_color
+
+ # set color of inner button parts
+ self._canvas.itemconfig("inner_parts",
+ outline=self._apply_appearance_mode(inner_parts_color),
+ fill=self._apply_appearance_mode(inner_parts_color))
+
+ # set text_label bg color (label color)
+ if self._text_label is not None:
+ self._text_label.configure(bg=self._apply_appearance_mode(inner_parts_color))
+
+ # set image_label bg color (image bg color)
+ if self._image_label is not None:
+ self._image_label.configure(bg=self._apply_appearance_mode(inner_parts_color))
+
+ def _click_animation(self):
+ if self._click_animation_running:
+ self._on_enter()
+
+ def _clicked(self, event=None):
+ if self._state != tkinter.DISABLED:
+
+ # click animation: change color with .on_leave() and back to normal after 100ms with click_animation()
+ self._on_leave()
+ self._click_animation_running = True
+ self.after(100, self._click_animation)
+
+ if self._command is not None:
+ self._command()
+
+ def invoke(self):
+ """ calls command function if button is not disabled """
+ if self._state != tkinter.DISABLED:
+ if self._command is not None:
+ return self._command()
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+
+ if self._text_label is not None:
+ self._text_label.bind(sequence, command, add=True)
+ if self._image_label is not None:
+ self._image_label.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+
+ if self._text_label is not None:
+ self._text_label.unbind(sequence, None)
+ if self._image_label is not None:
+ self._image_label.unbind(sequence, None)
+
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._text_label.focus()
+
+ def focus_set(self):
+ return self._text_label.focus_set()
+
+ def focus_force(self):
+ return self._text_label.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_checkbox.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_checkbox.py
new file mode 100644
index 0000000..42f04f5
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_checkbox.py
@@ -0,0 +1,469 @@
+import tkinter
+import sys
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+
+
+class CTkCheckBox(CTkBaseClass):
+ """
+ Checkbox with rounded corners, border, variable support and hover effect.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: int = 100,
+ height: int = 24,
+ checkbox_width: int = 24,
+ checkbox_height: int = 24,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ checkmark_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+
+ text: str = "CTkCheckBox",
+ font: Optional[Union[tuple, CTkFont]] = None,
+ textvariable: Union[tkinter.Variable, None] = None,
+ state: str = tkinter.NORMAL,
+ hover: bool = True,
+ command: Union[Callable[[], Any], None] = None,
+ onvalue: Union[int, str] = 1,
+ offvalue: Union[int, str] = 0,
+ variable: Union[tkinter.Variable, None] = None,
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # dimensions
+ self._checkbox_width = checkbox_width
+ self._checkbox_height = checkbox_height
+
+ # color
+ self._fg_color = ThemeManager.theme["CTkCheckBox"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._hover_color = ThemeManager.theme["CTkCheckBox"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
+ self._border_color = ThemeManager.theme["CTkCheckBox"]["border_color"] if border_color is None else self._check_color_type(border_color)
+ self._checkmark_color = ThemeManager.theme["CTkCheckBox"]["checkmark_color"] if checkmark_color is None else self._check_color_type(checkmark_color)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkCheckBox"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkCheckBox"]["border_width"] if border_width is None else border_width
+
+ # text
+ self._text = text
+ self._text_label: Union[tkinter.Label, None] = None
+ self._text_color = ThemeManager.theme["CTkCheckBox"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._text_color_disabled = ThemeManager.theme["CTkCheckBox"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ # callback and hover functionality
+ self._command = command
+ self._state = state
+ self._hover = hover
+ self._check_state = False
+
+ self._onvalue = onvalue
+ self._offvalue = offvalue
+ self._variable: tkinter.Variable = variable
+ self._variable_callback_blocked = False
+ self._textvariable: tkinter.Variable = textvariable
+ self._variable_callback_name = None
+
+ # configure grid system (1x3)
+ self.grid_columnconfigure(0, weight=0)
+ self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
+ self.grid_columnconfigure(2, weight=1)
+ self.grid_rowconfigure(0, weight=1)
+
+ self._bg_canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._checkbox_width),
+ height=self._apply_widget_scaling(self._checkbox_height))
+ self._canvas.grid(row=0, column=0, sticky="e")
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._text_label = tkinter.Label(master=self,
+ bd=0,
+ padx=0,
+ pady=0,
+ text=self._text,
+ justify=tkinter.LEFT,
+ font=self._apply_font_scaling(self._font),
+ textvariable=self._textvariable)
+ self._text_label.grid(row=0, column=2, sticky="w")
+ self._text_label["anchor"] = "w"
+
+ # register variable callback and set state according to variable
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._check_state = True if self._variable.get() == self._onvalue else False
+
+ self._create_bindings()
+ self._set_cursor()
+ self._draw()
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_enter)
+ self._text_label.bind("", self._on_enter)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_leave)
+ self._text_label.bind("", self._on_leave)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self.toggle)
+ self._text_label.bind("", self.toggle)
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ self._canvas.delete("checkmark")
+ self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.configure(width=self._apply_widget_scaling(self._checkbox_width),
+ height=self._apply_widget_scaling(self._checkbox_height))
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width: int = None, height: int = None):
+ super()._set_dimensions(width, height)
+
+ self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ if self._text_label is not None:
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._bg_canvas.grid_forget()
+ self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
+
+ def destroy(self):
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ requires_recoloring_1 = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._checkbox_width),
+ self._apply_widget_scaling(self._checkbox_height),
+ self._apply_widget_scaling(self._corner_radius),
+ self._apply_widget_scaling(self._border_width))
+
+ if self._check_state is True:
+ requires_recoloring_2 = self._draw_engine.draw_checkmark(self._apply_widget_scaling(self._checkbox_width),
+ self._apply_widget_scaling(self._checkbox_height),
+ self._apply_widget_scaling(self._checkbox_height * 0.58))
+ else:
+ requires_recoloring_2 = False
+ self._canvas.delete("checkmark")
+
+ if no_color_updates is False or requires_recoloring_1 or requires_recoloring_2:
+ self._bg_canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ if self._check_state is True:
+ self._canvas.itemconfig("inner_parts",
+ outline=self._apply_appearance_mode(self._fg_color),
+ fill=self._apply_appearance_mode(self._fg_color))
+ self._canvas.itemconfig("border_parts",
+ outline=self._apply_appearance_mode(self._fg_color),
+ fill=self._apply_appearance_mode(self._fg_color))
+
+ if "create_line" in self._canvas.gettags("checkmark"):
+ self._canvas.itemconfig("checkmark", fill=self._apply_appearance_mode(self._checkmark_color))
+ else:
+ self._canvas.itemconfig("checkmark", fill=self._apply_appearance_mode(self._checkmark_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ outline=self._apply_appearance_mode(self._bg_color),
+ fill=self._apply_appearance_mode(self._bg_color))
+ self._canvas.itemconfig("border_parts",
+ outline=self._apply_appearance_mode(self._border_color),
+ fill=self._apply_appearance_mode(self._border_color))
+
+ if self._state == tkinter.DISABLED:
+ self._text_label.configure(fg=(self._apply_appearance_mode(self._text_color_disabled)))
+ else:
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color))
+
+ self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ require_redraw = True
+
+ if "checkbox_width" in kwargs:
+ self._checkbox_width = kwargs.pop("checkbox_width")
+ self._canvas.configure(width=self._apply_widget_scaling(self._checkbox_width))
+ require_redraw = True
+
+ if "checkbox_height" in kwargs:
+ self._checkbox_height = kwargs.pop("checkbox_height")
+ self._canvas.configure(height=self._apply_widget_scaling(self._checkbox_height))
+ require_redraw = True
+
+ if "text" in kwargs:
+ self._text = kwargs.pop("text")
+ self._text_label.configure(text=self._text)
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ self._set_cursor()
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "hover_color" in kwargs:
+ self._hover_color = self._check_color_type(kwargs.pop("hover_color"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "checkmark_color" in kwargs:
+ self._checkmark_color = self._check_color_type(kwargs.pop("checkmark_color"))
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "text_color_disabled" in kwargs:
+ self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ require_redraw = True
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "textvariable" in kwargs:
+ self._textvariable = kwargs.pop("textvariable")
+ self._text_label.configure(textvariable=self._textvariable)
+
+ if "variable" in kwargs:
+ if self._variable is not None and self._variable != "":
+ self._variable.trace_remove("write", self._variable_callback_name) # remove old variable callback
+
+ self._variable = kwargs.pop("variable")
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._check_state = True if self._variable.get() == self._onvalue else False
+ require_redraw = True
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+ elif attribute_name == "checkbox_width":
+ return self._checkbox_width
+ elif attribute_name == "checkbox_height":
+ return self._checkbox_height
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "hover_color":
+ return self._hover_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "checkmark_color":
+ return self._checkmark_color
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "text_color_disabled":
+ return self._text_color_disabled
+
+ elif attribute_name == "text":
+ return self._text
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "textvariable":
+ return self._textvariable
+ elif attribute_name == "state":
+ return self._state
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "onvalue":
+ return self._onvalue
+ elif attribute_name == "offvalue":
+ return self._offvalue
+ elif attribute_name == "variable":
+ return self._variable
+ else:
+ return super().cget(attribute_name)
+
+ def _set_cursor(self):
+ if self._cursor_manipulation_enabled:
+ if self._state == tkinter.DISABLED:
+ if sys.platform == "darwin":
+ self._canvas.configure(cursor="arrow")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="arrow")
+ elif sys.platform.startswith("win"):
+ self._canvas.configure(cursor="arrow")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="arrow")
+
+ elif self._state == tkinter.NORMAL:
+ if sys.platform == "darwin":
+ self._canvas.configure(cursor="pointinghand")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="pointinghand")
+ elif sys.platform.startswith("win"):
+ self._canvas.configure(cursor="hand2")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="hand2")
+
+ def _on_enter(self, event=0):
+ if self._hover is True and self._state == tkinter.NORMAL:
+ if self._check_state is True:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._hover_color),
+ outline=self._apply_appearance_mode(self._hover_color))
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._hover_color),
+ outline=self._apply_appearance_mode(self._hover_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._hover_color),
+ outline=self._apply_appearance_mode(self._hover_color))
+
+ def _on_leave(self, event=0):
+ if self._check_state is True:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+
+ def _variable_callback(self, var_name, index, mode):
+ if not self._variable_callback_blocked:
+ if self._variable.get() == self._onvalue:
+ self.select(from_variable_callback=True)
+ elif self._variable.get() == self._offvalue:
+ self.deselect(from_variable_callback=True)
+
+ def toggle(self, event=0):
+ if self._state == tkinter.NORMAL:
+ if self._check_state is True:
+ self._check_state = False
+ self._draw()
+ else:
+ self._check_state = True
+ self._draw()
+
+ if self._variable is not None:
+ self._variable_callback_blocked = True
+ self._variable.set(self._onvalue if self._check_state is True else self._offvalue)
+ self._variable_callback_blocked = False
+
+ if self._command is not None:
+ self._command()
+
+ def select(self, from_variable_callback=False):
+ self._check_state = True
+ self._draw()
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(self._onvalue)
+ self._variable_callback_blocked = False
+
+ def deselect(self, from_variable_callback=False):
+ self._check_state = False
+ self._draw()
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(self._offvalue)
+ self._variable_callback_blocked = False
+
+ def get(self) -> Union[int, str]:
+ return self._onvalue if self._check_state is True else self._offvalue
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+ self._text_label.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+ self._text_label.unbind(sequence, None)
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._text_label.focus()
+
+ def focus_set(self):
+ return self._text_label.focus_set()
+
+ def focus_force(self):
+ return self._text_label.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_combobox.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_combobox.py
new file mode 100644
index 0000000..9949564
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_combobox.py
@@ -0,0 +1,424 @@
+import tkinter
+import sys
+import copy
+from typing import Union, Tuple, Callable, List, Optional, Any
+
+from .core_widget_classes import DropdownMenu
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+
+
+class CTkComboBox(CTkBaseClass):
+ """
+ Combobox with dropdown menu, rounded corners, border, variable support.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: int = 140,
+ height: int = 28,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+
+ font: Optional[Union[tuple, CTkFont]] = None,
+ dropdown_font: Optional[Union[tuple, CTkFont]] = None,
+ values: Optional[List[str]] = None,
+ state: str = tkinter.NORMAL,
+ hover: bool = True,
+ variable: Union[tkinter.Variable, None] = None,
+ command: Union[Callable[[str], Any], None] = None,
+ justify: str = "left",
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkComboBox"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkComboBox"]["border_width"] if border_width is None else border_width
+
+ # color
+ self._fg_color = ThemeManager.theme["CTkComboBox"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._border_color = ThemeManager.theme["CTkComboBox"]["border_color"] if border_color is None else self._check_color_type(border_color)
+ self._button_color = ThemeManager.theme["CTkComboBox"]["button_color"] if button_color is None else self._check_color_type(button_color)
+ self._button_hover_color = ThemeManager.theme["CTkComboBox"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
+ self._text_color = ThemeManager.theme["CTkComboBox"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._text_color_disabled = ThemeManager.theme["CTkComboBox"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ # callback and hover functionality
+ self._command = command
+ self._variable = variable
+ self._state = state
+ self._hover = hover
+
+ if values is None:
+ self._values = ["CTkComboBox"]
+ else:
+ self._values = values
+
+ self._dropdown_menu = DropdownMenu(master=self,
+ values=self._values,
+ command=self._dropdown_callback,
+ fg_color=dropdown_fg_color,
+ hover_color=dropdown_hover_color,
+ text_color=dropdown_text_color,
+ font=dropdown_font)
+
+ # configure grid system (1x1)
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_columnconfigure(0, weight=1)
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self.draw_engine = DrawEngine(self._canvas)
+
+ self._entry = tkinter.Entry(master=self,
+ state=self._state,
+ width=1,
+ bd=0,
+ justify=justify,
+ highlightthickness=0,
+ font=self._apply_font_scaling(self._font))
+
+ self._create_grid()
+ self._create_bindings()
+ self._draw() # initial draw
+
+ if self._variable is not None:
+ self._entry.configure(textvariable=self._variable)
+
+ # insert default value
+ if self._variable is None:
+ if len(self._values) > 0:
+ self._entry.insert(0, self._values[0])
+ else:
+ self._entry.insert(0, "CTkComboBox")
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None:
+ self._canvas.tag_bind("right_parts", "", self._on_enter)
+ self._canvas.tag_bind("dropdown_arrow", "", self._on_enter)
+ self._canvas.tag_bind("right_parts", "", self._on_leave)
+ self._canvas.tag_bind("dropdown_arrow", "", self._on_leave)
+ self._canvas.tag_bind("right_parts", "", self._clicked)
+ self._canvas.tag_bind("dropdown_arrow", "", self._clicked)
+
+ def _create_grid(self):
+ self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
+
+ left_section_width = self._current_width - self._current_height
+ self._entry.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="ew",
+ padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(3)),
+ max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3))),
+ pady=self._apply_widget_scaling(self._border_width))
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ # change entry font size and grid padding
+ self._entry.configure(font=self._apply_font_scaling(self._font))
+ self._create_grid()
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width: int = None, height: int = None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ self._entry.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._canvas.grid_forget()
+ self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew")
+
+ def destroy(self):
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ left_section_width = self._current_width - self._current_height
+ requires_recoloring = self.draw_engine.draw_rounded_rect_with_border_vertical_split(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_width),
+ self._apply_widget_scaling(left_section_width))
+
+ requires_recoloring_2 = self.draw_engine.draw_dropdown_arrow(self._apply_widget_scaling(self._current_width - (self._current_height / 2)),
+ self._apply_widget_scaling(self._current_height / 2),
+ self._apply_widget_scaling(self._current_height / 3))
+
+ if no_color_updates is False or requires_recoloring or requires_recoloring_2:
+
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ self._canvas.itemconfig("inner_parts_left",
+ outline=self._apply_appearance_mode(self._fg_color),
+ fill=self._apply_appearance_mode(self._fg_color))
+ self._canvas.itemconfig("border_parts_left",
+ outline=self._apply_appearance_mode(self._border_color),
+ fill=self._apply_appearance_mode(self._border_color))
+ self._canvas.itemconfig("inner_parts_right",
+ outline=self._apply_appearance_mode(self._button_color),
+ fill=self._apply_appearance_mode(self._button_color))
+ self._canvas.itemconfig("border_parts_right",
+ outline=self._apply_appearance_mode(self._button_color),
+ fill=self._apply_appearance_mode(self._button_color))
+
+ self._entry.configure(bg=self._apply_appearance_mode(self._fg_color),
+ fg=self._apply_appearance_mode(self._text_color),
+ readonlybackground=self._apply_appearance_mode(self._fg_color),
+ disabledbackground=self._apply_appearance_mode(self._fg_color),
+ disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
+ highlightcolor=self._apply_appearance_mode(self._fg_color),
+ insertbackground=self._apply_appearance_mode(self._text_color))
+
+ if self._state == tkinter.DISABLED:
+ self._canvas.itemconfig("dropdown_arrow",
+ fill=self._apply_appearance_mode(self._text_color_disabled))
+ else:
+ self._canvas.itemconfig("dropdown_arrow",
+ fill=self._apply_appearance_mode(self._text_color))
+
+ def _open_dropdown_menu(self):
+ self._dropdown_menu.open(self.winfo_rootx(),
+ self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ self._create_grid()
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "button_color" in kwargs:
+ self._button_color = self._check_color_type(kwargs.pop("button_color"))
+ require_redraw = True
+
+ if "button_hover_color" in kwargs:
+ self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
+ require_redraw = True
+
+ if "dropdown_fg_color" in kwargs:
+ self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_fg_color"))
+
+ if "dropdown_hover_color" in kwargs:
+ self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
+
+ if "dropdown_text_color" in kwargs:
+ self._dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "text_color_disabled" in kwargs:
+ self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ require_redraw = True
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "dropdown_font" in kwargs:
+ self._dropdown_menu.configure(font=kwargs.pop("dropdown_font"))
+
+ if "values" in kwargs:
+ self._values = kwargs.pop("values")
+ self._dropdown_menu.configure(values=self._values)
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ self._entry.configure(state=self._state)
+ require_redraw = True
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "variable" in kwargs:
+ self._variable = kwargs.pop("variable")
+ self._entry.configure(textvariable=self._variable)
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "justify" in kwargs:
+ self._entry.configure(justify=kwargs.pop("justify"))
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "button_color":
+ return self._button_color
+ elif attribute_name == "button_hover_color":
+ return self._button_hover_color
+ elif attribute_name == "dropdown_fg_color":
+ return self._dropdown_menu.cget("fg_color")
+ elif attribute_name == "dropdown_hover_color":
+ return self._dropdown_menu.cget("hover_color")
+ elif attribute_name == "dropdown_text_color":
+ return self._dropdown_menu.cget("text_color")
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "text_color_disabled":
+ return self._text_color_disabled
+
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "dropdown_font":
+ return self._dropdown_menu.cget("font")
+ elif attribute_name == "values":
+ return copy.copy(self._values)
+ elif attribute_name == "state":
+ return self._state
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "variable":
+ return self._variable
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "justify":
+ return self._entry.cget("justify")
+ else:
+ return super().cget(attribute_name)
+
+ def _on_enter(self, event=0):
+ if self._hover is True and self._state == tkinter.NORMAL and len(self._values) > 0:
+ if sys.platform == "darwin" and len(self._values) > 0 and self._cursor_manipulation_enabled:
+ self._canvas.configure(cursor="pointinghand")
+ elif sys.platform.startswith("win") and len(self._values) > 0 and self._cursor_manipulation_enabled:
+ self._canvas.configure(cursor="hand2")
+
+ # set color of inner button parts to hover color
+ self._canvas.itemconfig("inner_parts_right",
+ outline=self._apply_appearance_mode(self._button_hover_color),
+ fill=self._apply_appearance_mode(self._button_hover_color))
+ self._canvas.itemconfig("border_parts_right",
+ outline=self._apply_appearance_mode(self._button_hover_color),
+ fill=self._apply_appearance_mode(self._button_hover_color))
+
+ def _on_leave(self, event=0):
+ if sys.platform == "darwin" and len(self._values) > 0 and self._cursor_manipulation_enabled:
+ self._canvas.configure(cursor="arrow")
+ elif sys.platform.startswith("win") and len(self._values) > 0 and self._cursor_manipulation_enabled:
+ self._canvas.configure(cursor="arrow")
+
+ # set color of inner button parts
+ self._canvas.itemconfig("inner_parts_right",
+ outline=self._apply_appearance_mode(self._button_color),
+ fill=self._apply_appearance_mode(self._button_color))
+ self._canvas.itemconfig("border_parts_right",
+ outline=self._apply_appearance_mode(self._button_color),
+ fill=self._apply_appearance_mode(self._button_color))
+
+ def _dropdown_callback(self, value: str):
+ if self._state == "readonly":
+ self._entry.configure(state="normal")
+ self._entry.delete(0, tkinter.END)
+ self._entry.insert(0, value)
+ self._entry.configure(state="readonly")
+ else:
+ self._entry.delete(0, tkinter.END)
+ self._entry.insert(0, value)
+
+ if self._command is not None:
+ self._command(value)
+
+ def set(self, value: str):
+ if self._state == "readonly":
+ self._entry.configure(state="normal")
+ self._entry.delete(0, tkinter.END)
+ self._entry.insert(0, value)
+ self._entry.configure(state="readonly")
+ else:
+ self._entry.delete(0, tkinter.END)
+ self._entry.insert(0, value)
+
+ def get(self) -> str:
+ return self._entry.get()
+
+ def _clicked(self, event=None):
+ if self._state is not tkinter.DISABLED and len(self._values) > 0:
+ self._open_dropdown_menu()
+
+ def bind(self, sequence=None, command=None, add=True):
+ """ called on the tkinter.Entry """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._entry.bind(sequence, command, add=True)
+
+ def unbind(self, sequence=None, funcid=None):
+ """ called on the tkinter.Entry """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._entry.unbind(sequence, None) # unbind all callbacks for sequence
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._entry.focus()
+
+ def focus_set(self):
+ return self._entry.focus_set()
+
+ def focus_force(self):
+ return self._entry.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_entry.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_entry.py
new file mode 100644
index 0000000..cdc0220
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_entry.py
@@ -0,0 +1,384 @@
+import tkinter
+from typing import Union, Tuple, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+from .utility import pop_from_dict_by_set, check_kwargs_empty
+
+
+class CTkEntry(CTkBaseClass):
+ """
+ Entry with rounded corners, border, textvariable support, focus and placeholder.
+ For detailed information check out the documentation.
+ """
+
+ _minimum_x_padding = 6 # minimum padding between tkinter entry and frame border
+
+ # attributes that are passed to and managed by the tkinter entry only:
+ _valid_tk_entry_attributes = {"exportselection", "insertborderwidth", "insertofftime",
+ "insertontime", "insertwidth", "justify", "selectborderwidth",
+ "show", "takefocus", "validate", "validatecommand", "xscrollcommand"}
+
+ def __init__(self,
+ master: Any,
+ width: int = 140,
+ height: int = 28,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ placeholder_text_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ textvariable: Union[tkinter.Variable, None] = None,
+ placeholder_text: Union[str, None] = None,
+ font: Optional[Union[tuple, CTkFont]] = None,
+ state: str = tkinter.NORMAL,
+ **kwargs):
+
+ # transfer basic functionality (bg_color, size, appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height)
+
+ # configure grid system (1x1)
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_columnconfigure(0, weight=1)
+
+ # color
+ self._fg_color = ThemeManager.theme["CTkEntry"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
+ self._text_color = ThemeManager.theme["CTkEntry"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._placeholder_text_color = ThemeManager.theme["CTkEntry"]["placeholder_text_color"] if placeholder_text_color is None else self._check_color_type(placeholder_text_color)
+ self._border_color = ThemeManager.theme["CTkEntry"]["border_color"] if border_color is None else self._check_color_type(border_color)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkEntry"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkEntry"]["border_width"] if border_width is None else border_width
+
+ # text and state
+ self._is_focused: bool = True
+ self._placeholder_text = placeholder_text
+ self._placeholder_text_active = False
+ self._pre_placeholder_arguments = {} # some set arguments of the entry will be changed for placeholder and then set back
+ self._textvariable = textvariable
+ self._state = state
+ self._textvariable_callback_name: str = ""
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ if not (self._textvariable is None or self._textvariable == ""):
+ self._textvariable_callback_name = self._textvariable.trace_add("write", self._textvariable_callback)
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._current_width),
+ height=self._apply_widget_scaling(self._current_height))
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._entry = tkinter.Entry(master=self,
+ bd=0,
+ width=1,
+ highlightthickness=0,
+ font=self._apply_font_scaling(self._font),
+ state=self._state,
+ textvariable=self._textvariable,
+ **pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes))
+
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ self._create_grid()
+ self._activate_placeholder()
+ self._create_bindings()
+ self._draw()
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None or sequence == "":
+ self._entry.bind("", self._entry_focus_in)
+ if sequence is None or sequence == "":
+ self._entry.bind("", self._entry_focus_out)
+
+ def _create_grid(self):
+ self._canvas.grid(column=0, row=0, sticky="nswe")
+
+ if self._corner_radius >= self._minimum_x_padding:
+ self._entry.grid(column=0, row=0, sticky="nswe",
+ padx=min(self._apply_widget_scaling(self._corner_radius), round(self._apply_widget_scaling(self._current_height/2))),
+ pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
+ else:
+ self._entry.grid(column=0, row=0, sticky="nswe",
+ padx=self._apply_widget_scaling(self._minimum_x_padding),
+ pady=(self._apply_widget_scaling(self._border_width), self._apply_widget_scaling(self._border_width + 1)))
+
+ def _textvariable_callback(self, var_name, index, mode):
+ if self._textvariable.get() == "":
+ self._activate_placeholder()
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._entry.configure(font=self._apply_font_scaling(self._font))
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
+ self._create_grid()
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw(no_color_updates=True)
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ self._entry.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._canvas.grid_forget()
+ self._canvas.grid(column=0, row=0, sticky="nswe")
+
+ def destroy(self):
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(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_width))
+
+ if requires_recoloring or no_color_updates is False:
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ if self._apply_appearance_mode(self._fg_color) == "transparent":
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ self._entry.configure(bg=self._apply_appearance_mode(self._bg_color),
+ disabledbackground=self._apply_appearance_mode(self._bg_color),
+ readonlybackground=self._apply_appearance_mode(self._bg_color),
+ highlightcolor=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ self._entry.configure(bg=self._apply_appearance_mode(self._fg_color),
+ disabledbackground=self._apply_appearance_mode(self._fg_color),
+ readonlybackground=self._apply_appearance_mode(self._fg_color),
+ highlightcolor=self._apply_appearance_mode(self._fg_color))
+
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+
+ if self._placeholder_text_active:
+ self._entry.config(fg=self._apply_appearance_mode(self._placeholder_text_color),
+ disabledforeground=self._apply_appearance_mode(self._placeholder_text_color),
+ insertbackground=self._apply_appearance_mode(self._placeholder_text_color))
+ else:
+ self._entry.config(fg=self._apply_appearance_mode(self._text_color),
+ disabledforeground=self._apply_appearance_mode(self._text_color),
+ insertbackground=self._apply_appearance_mode(self._text_color))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ self._entry.configure(state=self._state)
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "placeholder_text_color" in kwargs:
+ self._placeholder_text_color = self._check_color_type(kwargs.pop("placeholder_text_color"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ self._create_grid()
+ require_redraw = True
+
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ self._create_grid()
+ require_redraw = True
+
+ if "placeholder_text" in kwargs:
+ self._placeholder_text = kwargs.pop("placeholder_text")
+ if self._placeholder_text_active:
+ self._entry.delete(0, tkinter.END)
+ self._entry.insert(0, self._placeholder_text)
+ else:
+ self._activate_placeholder()
+
+ if "textvariable" in kwargs:
+ self._textvariable = kwargs.pop("textvariable")
+ self._entry.configure(textvariable=self._textvariable)
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "show" in kwargs:
+ if self._placeholder_text_active:
+ self._pre_placeholder_arguments["show"] = kwargs.pop("show") # remember show argument for when placeholder gets deactivated
+ else:
+ self._entry.configure(show=kwargs.pop("show"))
+
+ self._entry.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_entry_attributes)) # configure Tkinter.Entry
+ super().configure(require_redraw=require_redraw, **kwargs) # configure CTkBaseClass
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "placeholder_text_color":
+ return self._placeholder_text_color
+
+ elif attribute_name == "textvariable":
+ return self._textvariable
+ elif attribute_name == "placeholder_text":
+ return self._placeholder_text
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "state":
+ return self._state
+
+ elif attribute_name in self._valid_tk_entry_attributes:
+ return self._entry.cget(attribute_name) # cget of tkinter.Entry
+ else:
+ return super().cget(attribute_name) # cget of CTkBaseClass
+
+ def bind(self, sequence=None, command=None, add=True):
+ """ called on the tkinter.Entry """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._entry.bind(sequence, command, add=True)
+
+ def unbind(self, sequence=None, funcid=None):
+ """ called on the tkinter.Entry """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._entry.unbind(sequence, None) # unbind all callbacks for sequence
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def _activate_placeholder(self):
+ if self._entry.get() == "" and self._placeholder_text is not None and (self._textvariable is None or self._textvariable == ""):
+ self._placeholder_text_active = True
+
+ self._pre_placeholder_arguments = {"show": self._entry.cget("show")}
+ self._entry.config(fg=self._apply_appearance_mode(self._placeholder_text_color),
+ disabledforeground=self._apply_appearance_mode(self._placeholder_text_color),
+ show="")
+ self._entry.delete(0, tkinter.END)
+ self._entry.insert(0, self._placeholder_text)
+
+ def _deactivate_placeholder(self):
+ if self._placeholder_text_active and self._entry.cget("state") != "readonly":
+ self._placeholder_text_active = False
+
+ self._entry.config(fg=self._apply_appearance_mode(self._text_color),
+ disabledforeground=self._apply_appearance_mode(self._text_color),)
+ self._entry.delete(0, tkinter.END)
+ for argument, value in self._pre_placeholder_arguments.items():
+ self._entry[argument] = value
+
+ def _entry_focus_out(self, event=None):
+ self._activate_placeholder()
+ self._is_focused = False
+
+ def _entry_focus_in(self, event=None):
+ self._deactivate_placeholder()
+ self._is_focused = True
+
+ def delete(self, first_index, last_index=None):
+ self._entry.delete(first_index, last_index)
+
+ if not self._is_focused and self._entry.get() == "":
+ self._activate_placeholder()
+
+ def insert(self, index, string):
+ self._deactivate_placeholder()
+
+ return self._entry.insert(index, string)
+
+ def get(self):
+ if self._placeholder_text_active:
+ return ""
+ else:
+ return self._entry.get()
+
+ def focus(self):
+ self._entry.focus()
+
+ def focus_set(self):
+ self._entry.focus_set()
+
+ def focus_force(self):
+ self._entry.focus_force()
+
+ def index(self, index):
+ return self._entry.index(index)
+
+ def icursor(self, index):
+ return self._entry.icursor(index)
+
+ def select_adjust(self, index):
+ return self._entry.select_adjust(index)
+
+ def select_from(self, index):
+ return self._entry.icursor(index)
+
+ def select_clear(self):
+ return self._entry.select_clear()
+
+ def select_present(self):
+ return self._entry.select_present()
+
+ def select_range(self, start_index, end_index):
+ return self._entry.select_range(start_index, end_index)
+
+ def select_to(self, index):
+ return self._entry.select_to(index)
+
+ def xview(self, index):
+ return self._entry.xview(index)
+
+ def xview_moveto(self, f):
+ return self._entry.xview_moveto(f)
+
+ def xview_scroll(self, number, what):
+ return self._entry.xview_scroll(number, what)
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_frame.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_frame.py
new file mode 100644
index 0000000..7bddf3c
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_frame.py
@@ -0,0 +1,196 @@
+from typing import Union, Tuple, List, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+
+
+class CTkFrame(CTkBaseClass):
+ """
+ Frame with rounded corners and border.
+ Default foreground colors are set according to theme.
+ To make the frame transparent set fg_color=None.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: int = 200,
+ height: int = 200,
+ corner_radius: Optional[Union[int, str]] = None,
+ border_width: Optional[Union[int, str]] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
+ overwrite_preferred_drawing_method: Union[str, None] = None,
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # color
+ self._border_color = ThemeManager.theme["CTkFrame"]["border_color"] if border_color is None else self._check_color_type(border_color)
+
+ # determine fg_color of frame
+ if fg_color is None:
+ if isinstance(self.master, CTkFrame):
+ if self.master._fg_color == ThemeManager.theme["CTkFrame"]["fg_color"]:
+ self._fg_color = ThemeManager.theme["CTkFrame"]["top_fg_color"]
+ else:
+ self._fg_color = ThemeManager.theme["CTkFrame"]["fg_color"]
+ else:
+ self._fg_color = ThemeManager.theme["CTkFrame"]["fg_color"]
+ else:
+ self._fg_color = self._check_color_type(fg_color, transparency=True)
+
+ self._background_corner_colors = background_corner_colors # rendering options for DrawEngine
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkFrame"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkFrame"]["border_width"] if border_width is None else border_width
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._current_width),
+ height=self._apply_widget_scaling(self._current_height))
+ self._canvas.place(x=0, y=0, relwidth=1, relheight=1)
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ self._draw_engine = DrawEngine(self._canvas)
+ self._overwrite_preferred_drawing_method = overwrite_preferred_drawing_method
+
+ self._draw(no_color_updates=True)
+
+ def winfo_children(self) -> List[any]:
+ """
+ winfo_children of CTkFrame without self.canvas widget,
+ because it's not a child but part of the CTkFrame itself
+ """
+
+ child_widgets = super().winfo_children()
+ try:
+ child_widgets.remove(self._canvas)
+ return child_widgets
+ except ValueError:
+ return child_widgets
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ if not self._canvas.winfo_exists():
+ return
+
+ if self._background_corner_colors is not None:
+ self._draw_engine.draw_background_corners(self._apply_widget_scaling(self._current_width),
+ self._apply_widget_scaling(self._current_height))
+ self._canvas.itemconfig("background_corner_top_left", fill=self._apply_appearance_mode(self._background_corner_colors[0]))
+ self._canvas.itemconfig("background_corner_top_right", fill=self._apply_appearance_mode(self._background_corner_colors[1]))
+ self._canvas.itemconfig("background_corner_bottom_right", fill=self._apply_appearance_mode(self._background_corner_colors[2]))
+ self._canvas.itemconfig("background_corner_bottom_left", fill=self._apply_appearance_mode(self._background_corner_colors[3]))
+ else:
+ self._canvas.delete("background_parts")
+
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(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_width),
+ overwrite_preferred_drawing_method=self._overwrite_preferred_drawing_method)
+
+ if no_color_updates is False or requires_recoloring:
+ if self._fg_color == "transparent":
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ # self._canvas.tag_lower("inner_parts") # maybe unnecessary, I don't know ???
+ # self._canvas.tag_lower("border_parts")
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
+ require_redraw = True
+
+ # check if CTk widgets are children of the frame and change their bg_color to new frame fg_color
+ for child in self.winfo_children():
+ if isinstance(child, CTkBaseClass):
+ child.configure(bg_color=self._fg_color)
+
+ if "bg_color" in kwargs:
+ # pass bg_color change to children if fg_color is "transparent"
+ if self._fg_color == "transparent":
+ for child in self.winfo_children():
+ if isinstance(child, CTkBaseClass):
+ child.configure(bg_color=self._fg_color)
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "background_corner_colors" in kwargs:
+ self._background_corner_colors = kwargs.pop("background_corner_colors")
+ require_redraw = True
+
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ require_redraw = True
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "background_corner_colors":
+ return self._background_corner_colors
+
+ else:
+ return super().cget(attribute_name)
+
+ def bind(self, sequence=None, command=None, add=True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+
+ def unbind(self, sequence=None, funcid=None):
+ """ called on the tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_label.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_label.py
new file mode 100644
index 0000000..7e59ad7
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_label.py
@@ -0,0 +1,291 @@
+import tkinter
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+from .image import CTkImage
+from .utility import pop_from_dict_by_set, check_kwargs_empty
+
+
+class CTkLabel(CTkBaseClass):
+ """
+ Label with rounded corners. Default is fg_color=None (transparent fg_color).
+ For detailed information check out the documentation.
+
+ state argument will probably be removed because it has no effect
+ """
+
+ # attributes that are passed to and managed by the tkinter entry only:
+ _valid_tk_label_attributes = {"cursor", "justify", "padx", "pady",
+ "textvariable", "state", "takefocus", "underline"}
+
+ def __init__(self,
+ master: Any,
+ width: int = 0,
+ height: int = 28,
+ corner_radius: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+
+ text: str = "CTkLabel",
+ font: Optional[Union[tuple, CTkFont]] = None,
+ image: Union[CTkImage, None] = None,
+ compound: str = "center",
+ anchor: str = "center", # label anchor: center, n, e, s, w
+ wraplength: int = 0,
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height)
+
+ # color
+ self._fg_color = ThemeManager.theme["CTkLabel"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
+ self._text_color = ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else self._check_color_type(text_color)
+
+ if text_color_disabled is None:
+ if "text_color_disabled" in ThemeManager.theme["CTkLabel"]:
+ self._text_color_disabled = ThemeManager.theme["CTkLabel"]["text_color"]
+ else:
+ self._text_color_disabled = self._text_color
+ else:
+ self._text_color_disabled = self._check_color_type(text_color_disabled)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkLabel"]["corner_radius"] if corner_radius is None else corner_radius
+
+ # text
+ self._anchor = anchor
+ self._text = text
+ self._wraplength = wraplength
+
+ # image
+ self._image = self._check_image_type(image)
+ self._compound = compound
+ if isinstance(self._image, CTkImage):
+ self._image.add_configure_callback(self._update_image)
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ # configure grid system (1x1)
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_columnconfigure(0, weight=1)
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.grid(row=0, column=0, sticky="nswe")
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._label = tkinter.Label(master=self,
+ highlightthickness=0,
+ padx=0,
+ pady=0,
+ borderwidth=0,
+ anchor=self._anchor,
+ compound=self._compound,
+ wraplength=self._apply_widget_scaling(self._wraplength),
+ text=self._text,
+ font=self._apply_font_scaling(self._font))
+ self._label.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_label_attributes))
+
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ self._create_grid()
+ self._update_image()
+ self._draw()
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width), height=self._apply_widget_scaling(self._desired_height))
+ self._label.configure(font=self._apply_font_scaling(self._font))
+ self._label.configure(wraplength=self._apply_widget_scaling(self._wraplength))
+
+ self._create_grid()
+ self._update_image()
+ self._draw(no_color_updates=True)
+
+ def _set_appearance_mode(self, mode_string):
+ super()._set_appearance_mode(mode_string)
+ self._update_image()
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._create_grid()
+ self._draw()
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ self._label.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._canvas.grid_forget()
+ self._canvas.grid(row=0, column=0, sticky="nswe")
+
+ def _update_image(self):
+ if isinstance(self._image, CTkImage):
+ self._label.configure(image=self._image.create_scaled_photo_image(self._get_widget_scaling(),
+ self._get_appearance_mode()))
+ elif self._image is not None:
+ self._label.configure(image=self._image)
+
+ def destroy(self):
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ super().destroy()
+
+ def _create_grid(self):
+ """ configure grid system (1x1) """
+
+ text_label_grid_sticky = self._anchor if self._anchor != "center" else ""
+ self._label.grid(row=0, column=0, sticky=text_label_grid_sticky,
+ padx=self._apply_widget_scaling(min(self._corner_radius, round(self._current_height / 2))))
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
+ self._apply_widget_scaling(self._current_height),
+ self._apply_widget_scaling(self._corner_radius),
+ 0)
+
+ if no_color_updates is False or requires_recoloring:
+ if self._apply_appearance_mode(self._fg_color) == "transparent":
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+
+ self._label.configure(fg=self._apply_appearance_mode(self._text_color),
+ disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
+ bg=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+
+ self._label.configure(fg=self._apply_appearance_mode(self._text_color),
+ disabledforeground=self._apply_appearance_mode(self._text_color_disabled),
+ bg=self._apply_appearance_mode(self._fg_color))
+
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ self._create_grid()
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "text_color_disabled" in kwargs:
+ self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ require_redraw = True
+
+ if "text" in kwargs:
+ self._text = kwargs.pop("text")
+ self._label.configure(text=self._text)
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+ self._update_font()
+
+ if "image" in kwargs:
+ if isinstance(self._image, CTkImage):
+ self._image.remove_configure_callback(self._update_image)
+ self._image = self._check_image_type(kwargs.pop("image"))
+ if isinstance(self._image, CTkImage):
+ self._image.add_configure_callback(self._update_image)
+ self._update_image()
+
+ if "compound" in kwargs:
+ self._compound = kwargs.pop("compound")
+ self._label.configure(compound=self._compound)
+
+ if "anchor" in kwargs:
+ self._anchor = kwargs.pop("anchor")
+ self._label.configure(anchor=self._anchor)
+ self._create_grid()
+
+ if "wraplength" in kwargs:
+ self._wraplength = kwargs.pop("wraplength")
+ self._label.configure(wraplength=self._apply_widget_scaling(self._wraplength))
+
+ self._label.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_label_attributes)) # configure tkinter.Label
+ super().configure(require_redraw=require_redraw, **kwargs) # configure CTkBaseClass
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "text_color_disabled":
+ return self._text_color_disabled
+
+ elif attribute_name == "text":
+ return self._text
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "image":
+ return self._image
+ elif attribute_name == "compound":
+ return self._compound
+ elif attribute_name == "anchor":
+ return self._anchor
+ elif attribute_name == "wraplength":
+ return self._wraplength
+
+ elif attribute_name in self._valid_tk_label_attributes:
+ return self._label.cget(attribute_name) # cget of tkinter.Label
+ else:
+ return super().cget(attribute_name) # cget of CTkBaseClass
+
+ def bind(self, sequence: str = None, command: Callable = None, add: str = True):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+ self._label.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: Optional[str] = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+ self._label.unbind(sequence, None)
+
+ def focus(self):
+ return self._label.focus()
+
+ def focus_set(self):
+ return self._label.focus_set()
+
+ def focus_force(self):
+ return self._label.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_optionmenu.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_optionmenu.py
new file mode 100644
index 0000000..491027b
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_optionmenu.py
@@ -0,0 +1,426 @@
+import tkinter
+import copy
+import sys
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .core_widget_classes import DropdownMenu
+from .font import CTkFont
+
+
+class CTkOptionMenu(CTkBaseClass):
+ """
+ Optionmenu with rounded corners, dropdown menu, variable support, command.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: int = 140,
+ height: int = 28,
+ corner_radius: Optional[Union[int]] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+ dropdown_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ dropdown_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ dropdown_text_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ font: Optional[Union[tuple, CTkFont]] = None,
+ dropdown_font: Optional[Union[tuple, CTkFont]] = None,
+ values: Optional[list] = None,
+ variable: Union[tkinter.Variable, None] = None,
+ state: str = tkinter.NORMAL,
+ hover: bool = True,
+ command: Union[Callable[[str], Any], None] = None,
+ dynamic_resizing: bool = True,
+ anchor: str = "w",
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # color variables
+ self._fg_color = ThemeManager.theme["CTkOptionMenu"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._button_color = ThemeManager.theme["CTkOptionMenu"]["button_color"] if button_color is None else self._check_color_type(button_color)
+ self._button_hover_color = ThemeManager.theme["CTkOptionMenu"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkOptionMenu"]["corner_radius"] if corner_radius is None else corner_radius
+
+ # text and font
+ self._text_color = ThemeManager.theme["CTkOptionMenu"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._text_color_disabled = ThemeManager.theme["CTkOptionMenu"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ # callback and hover functionality
+ self._command = command
+ self._variable = variable
+ self._variable_callback_blocked: bool = False
+ self._variable_callback_name: Union[str, None] = None
+ self._state = state
+ self._hover = hover
+ self._dynamic_resizing = dynamic_resizing
+
+ if values is None:
+ self._values = ["CTkOptionMenu"]
+ else:
+ self._values = values
+
+ if len(self._values) > 0:
+ self._current_value = self._values[0]
+ else:
+ self._current_value = "CTkOptionMenu"
+
+ self._dropdown_menu = DropdownMenu(master=self,
+ values=self._values,
+ command=self._dropdown_callback,
+ fg_color=dropdown_fg_color,
+ hover_color=dropdown_hover_color,
+ text_color=dropdown_text_color,
+ font=dropdown_font)
+
+ # configure grid system (1x1)
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_columnconfigure(0, weight=1)
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._text_label = tkinter.Label(master=self,
+ font=self._apply_font_scaling(self._font),
+ anchor=anchor,
+ padx=0,
+ pady=0,
+ borderwidth=1,
+ text=self._current_value)
+
+ if self._cursor_manipulation_enabled:
+ if sys.platform == "darwin":
+ self.configure(cursor="pointinghand")
+ elif sys.platform.startswith("win"):
+ self.configure(cursor="hand2")
+
+ self._create_grid()
+ if not self._dynamic_resizing:
+ self.grid_propagate(0)
+
+ self._create_bindings()
+ self._draw() # initial draw
+
+ if self._variable is not None:
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._current_value = self._variable.get()
+ self._text_label.configure(text=self._current_value)
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_enter)
+ self._text_label.bind("", self._on_enter)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_leave)
+ self._text_label.bind("", self._on_leave)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._clicked)
+ self._text_label.bind("", self._clicked)
+
+ def _create_grid(self):
+ self._canvas.grid(row=0, column=0, sticky="nsew")
+
+ left_section_width = self._current_width - self._current_height
+ self._text_label.grid(row=0, column=0, sticky="ew",
+ padx=(max(self._apply_widget_scaling(self._corner_radius), self._apply_widget_scaling(3)),
+ max(self._apply_widget_scaling(self._current_width - left_section_width + 3), self._apply_widget_scaling(3))))
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ # change label font size and grid padding
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._create_grid()
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width: int = None, height: int = None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._canvas.grid_forget()
+ self._canvas.grid(row=0, column=0, sticky="nsew")
+
+ def destroy(self):
+ if self._variable is not None: # remove old callback
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ left_section_width = self._current_width - self._current_height
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border_vertical_split(self._apply_widget_scaling(self._current_width),
+ self._apply_widget_scaling(self._current_height),
+ self._apply_widget_scaling(self._corner_radius),
+ 0,
+ self._apply_widget_scaling(left_section_width))
+
+ requires_recoloring_2 = self._draw_engine.draw_dropdown_arrow(self._apply_widget_scaling(self._current_width - (self._current_height / 2)),
+ self._apply_widget_scaling(self._current_height / 2),
+ self._apply_widget_scaling(self._current_height / 3))
+
+ if no_color_updates is False or requires_recoloring or requires_recoloring_2:
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ self._canvas.itemconfig("inner_parts_left",
+ outline=self._apply_appearance_mode(self._fg_color),
+ fill=self._apply_appearance_mode(self._fg_color))
+ self._canvas.itemconfig("inner_parts_right",
+ outline=self._apply_appearance_mode(self._button_color),
+ fill=self._apply_appearance_mode(self._button_color))
+
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color))
+
+ if self._state == tkinter.DISABLED:
+ self._text_label.configure(fg=(self._apply_appearance_mode(self._text_color_disabled)))
+ self._canvas.itemconfig("dropdown_arrow",
+ fill=self._apply_appearance_mode(self._text_color_disabled))
+ else:
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color))
+ self._canvas.itemconfig("dropdown_arrow",
+ fill=self._apply_appearance_mode(self._text_color))
+
+ self._text_label.configure(bg=self._apply_appearance_mode(self._fg_color))
+
+ self._canvas.update_idletasks()
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ self._create_grid()
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "button_color" in kwargs:
+ self._button_color = self._check_color_type(kwargs.pop("button_color"))
+ require_redraw = True
+
+ if "button_hover_color" in kwargs:
+ self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "text_color_disabled" in kwargs:
+ self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ require_redraw = True
+
+ if "dropdown_fg_color" in kwargs:
+ self._dropdown_menu.configure(fg_color=kwargs.pop("dropdown_fg_color"))
+
+ if "dropdown_hover_color" in kwargs:
+ self._dropdown_menu.configure(hover_color=kwargs.pop("dropdown_hover_color"))
+
+ if "dropdown_text_color" in kwargs:
+ self._dropdown_menu.configure(text_color=kwargs.pop("dropdown_text_color"))
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "dropdown_font" in kwargs:
+ self._dropdown_menu.configure(font=kwargs.pop("dropdown_font"))
+
+ if "values" in kwargs:
+ self._values = kwargs.pop("values")
+ self._dropdown_menu.configure(values=self._values)
+
+ if "variable" in kwargs:
+ if self._variable is not None: # remove old callback
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ self._variable = kwargs.pop("variable")
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._current_value = self._variable.get()
+ self._text_label.configure(text=self._current_value)
+ else:
+ self._variable = None
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ require_redraw = True
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "dynamic_resizing" in kwargs:
+ self._dynamic_resizing = kwargs.pop("dynamic_resizing")
+ if not self._dynamic_resizing:
+ self.grid_propagate(0)
+ else:
+ self.grid_propagate(1)
+
+ if "anchor" in kwargs:
+ self._text_label.configure(anchor=kwargs.pop("anchor"))
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "button_color":
+ return self._button_color
+ elif attribute_name == "button_hover_color":
+ return self._button_hover_color
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "text_color_disabled":
+ return self._text_color_disabled
+ elif attribute_name == "dropdown_fg_color":
+ return self._dropdown_menu.cget("fg_color")
+ elif attribute_name == "dropdown_hover_color":
+ return self._dropdown_menu.cget("hover_color")
+ elif attribute_name == "dropdown_text_color":
+ return self._dropdown_menu.cget("text_color")
+
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "dropdown_font":
+ return self._dropdown_menu.cget("font")
+ elif attribute_name == "values":
+ return copy.copy(self._values)
+ elif attribute_name == "variable":
+ return self._variable
+ elif attribute_name == "state":
+ return self._state
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "dynamic_resizing":
+ return self._dynamic_resizing
+ elif attribute_name == "anchor":
+ return self._text_label.cget("anchor")
+
+ else:
+ return super().cget(attribute_name)
+
+ def _open_dropdown_menu(self):
+ self._dropdown_menu.open(self.winfo_rootx(),
+ self.winfo_rooty() + self._apply_widget_scaling(self._current_height + 0))
+
+ def _on_enter(self, event=0):
+ if self._hover is True and self._state == tkinter.NORMAL and len(self._values) > 0:
+ # set color of inner button parts to hover color
+ self._canvas.itemconfig("inner_parts_right",
+ outline=self._apply_appearance_mode(self._button_hover_color),
+ fill=self._apply_appearance_mode(self._button_hover_color))
+
+ def _on_leave(self, event=0):
+ # set color of inner button parts
+ self._canvas.itemconfig("inner_parts_right",
+ outline=self._apply_appearance_mode(self._button_color),
+ fill=self._apply_appearance_mode(self._button_color))
+
+ def _variable_callback(self, var_name, index, mode):
+ if not self._variable_callback_blocked:
+ self._current_value = self._variable.get()
+ self._text_label.configure(text=self._current_value)
+
+ def _dropdown_callback(self, value: str):
+ self._current_value = value
+ self._text_label.configure(text=self._current_value)
+
+ if self._variable is not None:
+ self._variable_callback_blocked = True
+ self._variable.set(self._current_value)
+ self._variable_callback_blocked = False
+
+ if self._command is not None:
+ self._command(self._current_value)
+
+ def set(self, value: str):
+ self._current_value = value
+ self._text_label.configure(text=self._current_value)
+
+ if self._variable is not None:
+ self._variable_callback_blocked = True
+ self._variable.set(self._current_value)
+ self._variable_callback_blocked = False
+
+ def get(self) -> str:
+ return self._current_value
+
+ def _clicked(self, event=0):
+ if self._state is not tkinter.DISABLED and len(self._values) > 0:
+ self._open_dropdown_menu()
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+ self._text_label.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+ self._text_label.unbind(sequence, None)
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._text_label.focus()
+
+ def focus_set(self):
+ return self._text_label.focus_set()
+
+ def focus_force(self):
+ return self._text_label.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_progressbar.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_progressbar.py
new file mode 100644
index 0000000..2d6ce59
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_progressbar.py
@@ -0,0 +1,312 @@
+import tkinter
+import math
+from typing import Union, Tuple, Optional, Callable, Any
+try:
+ from typing import Literal
+except ImportError:
+ from typing_extensions import Literal
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+
+
+class CTkProgressBar(CTkBaseClass):
+ """
+ Progressbar with rounded corners, border, variable support,
+ indeterminate mode, vertical orientation.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ progress_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ variable: Union[tkinter.Variable, None] = None,
+ orientation: str = "horizontal",
+ mode: Literal["determinate", "indeterminate"] = "determinate",
+ determinate_speed: float = 1,
+ indeterminate_speed: float = 1,
+ **kwargs):
+
+ # set default dimensions according to orientation
+ if width is None:
+ if orientation.lower() == "vertical":
+ width = 8
+ else:
+ width = 200
+ if height is None:
+ if orientation.lower() == "vertical":
+ height = 200
+ else:
+ height = 8
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # color
+ self._border_color = ThemeManager.theme["CTkProgressBar"]["border_color"] if border_color is None else self._check_color_type(border_color)
+ self._fg_color = ThemeManager.theme["CTkProgressBar"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._progress_color = ThemeManager.theme["CTkProgressBar"]["progress_color"] if progress_color is None else self._check_color_type(progress_color)
+
+ # control variable
+ self._variable = variable
+ self._variable_callback_blocked = False
+ self._variable_callback_name = None
+ self._loop_after_id = None
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkProgressBar"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkProgressBar"]["border_width"] if border_width is None else border_width
+ self._determinate_value: float = 0.5 # range 0-1
+ self._determinate_speed = determinate_speed # range 0-1
+ self._indeterminate_value: float = 0 # range 0-inf
+ self._indeterminate_width: float = 0.4 # range 0-1
+ self._indeterminate_speed = indeterminate_speed # range 0-1 to travel in 50ms
+ self._loop_running: bool = False
+ self._orientation = orientation
+ self._mode = mode # "determinate" or "indeterminate"
+
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_columnconfigure(0, weight=1)
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nswe")
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._draw() # initial draw
+
+ if self._variable is not None:
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._variable_callback_blocked = True
+ self.set(self._variable.get(), from_variable_callback=True)
+ self._variable_callback_blocked = False
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def destroy(self):
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ if self._orientation.lower() == "horizontal":
+ orientation = "w"
+ elif self._orientation.lower() == "vertical":
+ orientation = "s"
+ else:
+ orientation = "w"
+
+ if self._mode == "determinate":
+ requires_recoloring = self._draw_engine.draw_rounded_progress_bar_with_border(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_width),
+ 0,
+ self._determinate_value,
+ orientation)
+ else: # indeterminate mode
+ progress_value = (math.sin(self._indeterminate_value * math.pi / 40) + 1) / 2
+ progress_value_1 = min(1.0, progress_value + (self._indeterminate_width / 2))
+ progress_value_2 = max(0.0, progress_value - (self._indeterminate_width / 2))
+
+ requires_recoloring = self._draw_engine.draw_rounded_progress_bar_with_border(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_width),
+ progress_value_1,
+ progress_value_2,
+ orientation)
+
+ if no_color_updates is False or requires_recoloring:
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ self._canvas.itemconfig("progress_parts",
+ fill=self._apply_appearance_mode(self._progress_color),
+ outline=self._apply_appearance_mode(self._progress_color))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "progress_color" in kwargs:
+ self._progress_color = self._check_color_type(kwargs.pop("progress_color"))
+ require_redraw = True
+
+ if "variable" in kwargs:
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ self._variable = kwargs.pop("variable")
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self.set(self._variable.get(), from_variable_callback=True)
+ else:
+ self._variable = None
+
+ if "mode" in kwargs:
+ self._mode = kwargs.pop("mode")
+ require_redraw = True
+
+ if "determinate_speed" in kwargs:
+ self._determinate_speed = kwargs.pop("determinate_speed")
+
+ if "indeterminate_speed" in kwargs:
+ self._indeterminate_speed = kwargs.pop("indeterminate_speed")
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "progress_color":
+ return self._progress_color
+
+ elif attribute_name == "variable":
+ return self._variable
+ elif attribute_name == "orientation":
+ return self._orientation
+ elif attribute_name == "mode":
+ return self._mode
+ elif attribute_name == "determinate_speed":
+ return self._determinate_speed
+ elif attribute_name == "indeterminate_speed":
+ return self._indeterminate_speed
+
+ else:
+ return super().cget(attribute_name)
+
+ def _variable_callback(self, var_name, index, mode):
+ if not self._variable_callback_blocked:
+ self.set(self._variable.get(), from_variable_callback=True)
+
+ def set(self, value, from_variable_callback=False):
+ """ set determinate value """
+ self._determinate_value = value
+
+ if self._determinate_value > 1:
+ self._determinate_value = 1
+ elif self._determinate_value < 0:
+ self._determinate_value = 0
+
+ self._draw(no_color_updates=True)
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(round(self._determinate_value) if isinstance(self._variable, tkinter.IntVar) else self._determinate_value)
+ self._variable_callback_blocked = False
+
+ def get(self) -> float:
+ """ get determinate value """
+ return self._determinate_value
+
+ def start(self):
+ """ start automatic mode """
+ if not self._loop_running:
+ self._loop_running = True
+ self._internal_loop()
+
+ def stop(self):
+ """ stop automatic mode """
+ if self._loop_after_id is not None:
+ self.after_cancel(self._loop_after_id)
+ self._loop_running = False
+
+ def _internal_loop(self):
+ if self._loop_running:
+ if self._mode == "determinate":
+ self._determinate_value += self._determinate_speed / 50
+ if self._determinate_value > 1:
+ self._determinate_value -= 1
+ self._draw()
+ self._loop_after_id = self.after(20, self._internal_loop)
+ else:
+ self._indeterminate_value += self._indeterminate_speed
+ self._draw()
+ self._loop_after_id = self.after(20, self._internal_loop)
+
+ def step(self):
+ """ increase progress """
+ if self._mode == "determinate":
+ self._determinate_value += self._determinate_speed / 50
+ if self._determinate_value > 1:
+ self._determinate_value -= 1
+ self._draw()
+ else:
+ self._indeterminate_value += self._indeterminate_speed
+ self._draw()
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+
+ def focus(self):
+ return self._canvas.focus()
+
+ def focus_set(self):
+ return self._canvas.focus_set()
+
+ def focus_force(self):
+ return self._canvas.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_radiobutton.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_radiobutton.py
new file mode 100644
index 0000000..c07cd1f
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_radiobutton.py
@@ -0,0 +1,430 @@
+import tkinter
+import sys
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+
+
+class CTkRadioButton(CTkBaseClass):
+ """
+ Radiobutton with rounded corners, border, label, variable support, command.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: int = 100,
+ height: int = 22,
+ radiobutton_width: int = 22,
+ radiobutton_height: int = 22,
+ corner_radius: Optional[int] = None,
+ border_width_unchecked: Optional[int] = None,
+ border_width_checked: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+
+ text: str = "CTkRadioButton",
+ font: Optional[Union[tuple, CTkFont]] = None,
+ textvariable: Union[tkinter.Variable, None] = None,
+ variable: Union[tkinter.Variable, None] = None,
+ value: Union[int, str] = 0,
+ state: str = tkinter.NORMAL,
+ hover: bool = True,
+ command: Union[Callable, Any] = None,
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # dimensions
+ self._radiobutton_width = radiobutton_width
+ self._radiobutton_height = radiobutton_height
+
+ # color
+ self._fg_color = ThemeManager.theme["CTkRadioButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._hover_color = ThemeManager.theme["CTkRadioButton"]["hover_color"] if hover_color is None else self._check_color_type(hover_color)
+ self._border_color = ThemeManager.theme["CTkRadioButton"]["border_color"] if border_color is None else self._check_color_type(border_color)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkRadioButton"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width_unchecked = ThemeManager.theme["CTkRadioButton"]["border_width_unchecked"] if border_width_unchecked is None else border_width_unchecked
+ self._border_width_checked = ThemeManager.theme["CTkRadioButton"]["border_width_checked"] if border_width_checked is None else border_width_checked
+
+ # text
+ self._text = text
+ self._text_label: Union[tkinter.Label, None] = None
+ self._text_color = ThemeManager.theme["CTkRadioButton"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._text_color_disabled = ThemeManager.theme["CTkRadioButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ # callback and control variables
+ self._command = command
+ self._state = state
+ self._hover = hover
+ self._check_state: bool = False
+ self._value = value
+ self._variable: tkinter.Variable = variable
+ self._variable_callback_blocked: bool = False
+ self._textvariable = textvariable
+ self._variable_callback_name: Union[str, None] = None
+
+ # configure grid system (3x1)
+ self.grid_columnconfigure(0, weight=0)
+ self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
+ self.grid_columnconfigure(2, weight=1)
+ self.grid_rowconfigure(0, weight=1)
+
+ self._bg_canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._current_width),
+ height=self._apply_widget_scaling(self._current_height))
+ self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._radiobutton_width),
+ height=self._apply_widget_scaling(self._radiobutton_height))
+ self._canvas.grid(row=0, column=0)
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._text_label = tkinter.Label(master=self,
+ bd=0,
+ padx=0,
+ pady=0,
+ text=self._text,
+ justify=tkinter.LEFT,
+ font=self._apply_font_scaling(self._font),
+ textvariable=self._textvariable)
+ self._text_label.grid(row=0, column=2, sticky="w")
+ self._text_label["anchor"] = "w"
+
+ if self._variable is not None:
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._check_state = True if self._variable.get() == self._value else False
+
+ self._create_bindings()
+ self._set_cursor()
+ self._draw()
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_enter)
+ self._text_label.bind("", self._on_enter)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_leave)
+ self._text_label.bind("", self._on_leave)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self.invoke)
+ self._text_label.bind("", self.invoke)
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.configure(width=self._apply_widget_scaling(self._radiobutton_width),
+ height=self._apply_widget_scaling(self._radiobutton_height))
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width: int = None, height: int = None):
+ super()._set_dimensions(width, height)
+
+ self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._bg_canvas.grid_forget()
+ self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
+
+ def destroy(self):
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ if self._check_state is True:
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._radiobutton_width),
+ self._apply_widget_scaling(self._radiobutton_height),
+ self._apply_widget_scaling(self._corner_radius),
+ self._apply_widget_scaling(self._border_width_checked))
+ else:
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._radiobutton_width),
+ self._apply_widget_scaling(self._radiobutton_height),
+ self._apply_widget_scaling(self._corner_radius),
+ self._apply_widget_scaling(self._border_width_unchecked))
+
+ if no_color_updates is False or requires_recoloring:
+ self._bg_canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ if self._check_state is False:
+ self._canvas.itemconfig("border_parts",
+ outline=self._apply_appearance_mode(self._border_color),
+ fill=self._apply_appearance_mode(self._border_color))
+ else:
+ self._canvas.itemconfig("border_parts",
+ outline=self._apply_appearance_mode(self._fg_color),
+ fill=self._apply_appearance_mode(self._fg_color))
+
+ self._canvas.itemconfig("inner_parts",
+ outline=self._apply_appearance_mode(self._bg_color),
+ fill=self._apply_appearance_mode(self._bg_color))
+
+ if self._state == tkinter.DISABLED:
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color_disabled))
+ else:
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color))
+
+ self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "border_width_unchecked" in kwargs:
+ self._border_width_unchecked = kwargs.pop("border_width_unchecked")
+ require_redraw = True
+
+ if "border_width_checked" in kwargs:
+ self._border_width_checked = kwargs.pop("border_width_checked")
+ require_redraw = True
+
+ if "radiobutton_width" in kwargs:
+ self._radiobutton_width = kwargs.pop("radiobutton_width")
+ self._canvas.configure(width=self._apply_widget_scaling(self._radiobutton_width))
+ require_redraw = True
+
+ if "radiobutton_height" in kwargs:
+ self._radiobutton_height = kwargs.pop("radiobutton_height")
+ self._canvas.configure(height=self._apply_widget_scaling(self._radiobutton_height))
+ require_redraw = True
+
+ if "text" in kwargs:
+ self._text = kwargs.pop("text")
+ self._text_label.configure(text=self._text)
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ self._set_cursor()
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "hover_color" in kwargs:
+ self._hover_color = self._check_color_type(kwargs.pop("hover_color"))
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "text_color_disabled" in kwargs:
+ self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "textvariable" in kwargs:
+ self._textvariable = kwargs.pop("textvariable")
+ self._text_label.configure(textvariable=self._textvariable)
+
+ if "variable" in kwargs:
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ self._variable = kwargs.pop("variable")
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._check_state = True if self._variable.get() == self._value else False
+ require_redraw = True
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width_unchecked":
+ return self._border_width_unchecked
+ elif attribute_name == "border_width_checked":
+ return self._border_width_checked
+ elif attribute_name == "radiobutton_width":
+ return self._radiobutton_width
+ elif attribute_name == "radiobutton_height":
+ return self._radiobutton_height
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "hover_color":
+ return self._hover_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "text_color_disabled":
+ return self._text_color_disabled
+
+ elif attribute_name == "text":
+ return self._text
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "textvariable":
+ return self._textvariable
+ elif attribute_name == "variable":
+ return self._variable
+ elif attribute_name == "value":
+ return self._value
+ elif attribute_name == "state":
+ return self._state
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "command":
+ return self._command
+
+ else:
+ return super().cget(attribute_name)
+
+ def _set_cursor(self):
+ if self._cursor_manipulation_enabled:
+ if self._state == tkinter.DISABLED:
+ if sys.platform == "darwin":
+ self._canvas.configure(cursor="arrow")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="arrow")
+ elif sys.platform.startswith("win"):
+ self._canvas.configure(cursor="arrow")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="arrow")
+
+ elif self._state == tkinter.NORMAL:
+ if sys.platform == "darwin":
+ self._canvas.configure(cursor="pointinghand")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="pointinghand")
+ elif sys.platform.startswith("win"):
+ self._canvas.configure(cursor="hand2")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="hand2")
+
+ def _on_enter(self, event=0):
+ if self._hover is True and self._state == tkinter.NORMAL:
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._hover_color),
+ outline=self._apply_appearance_mode(self._hover_color))
+
+ def _on_leave(self, event=0):
+ if self._check_state is True:
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ else:
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+
+ def _variable_callback(self, var_name, index, mode):
+ if not self._variable_callback_blocked:
+ if self._variable.get() == self._value:
+ self.select(from_variable_callback=True)
+ else:
+ self.deselect(from_variable_callback=True)
+
+ def invoke(self, event=0):
+ if self._state == tkinter.NORMAL:
+ if self._check_state is False:
+ self._check_state = True
+ self.select()
+
+ if self._command is not None:
+ self._command()
+
+ def select(self, from_variable_callback=False):
+ self._check_state = True
+ self._draw()
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(self._value)
+ self._variable_callback_blocked = False
+
+ def deselect(self, from_variable_callback=False):
+ self._check_state = False
+ self._draw()
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set("")
+ self._variable_callback_blocked = False
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+ self._text_label.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+ self._text_label.unbind(sequence, None)
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._text_label.focus()
+
+ def focus_set(self):
+ return self._text_label.focus_set()
+
+ def focus_force(self):
+ return self._text_label.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_scrollable_frame.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_scrollable_frame.py
new file mode 100644
index 0000000..eede091
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_scrollable_frame.py
@@ -0,0 +1,316 @@
+from typing import Union, Tuple, Optional, Any
+try:
+ from typing import Literal
+except ImportError:
+ from typing_extensions import Literal
+import tkinter
+import sys
+
+from .ctk_frame import CTkFrame
+from .ctk_scrollbar import CTkScrollbar
+from .appearance_mode import CTkAppearanceModeBaseClass
+from .scaling import CTkScalingBaseClass
+from .core_widget_classes import CTkBaseClass
+from .ctk_label import CTkLabel
+from .font import CTkFont
+from .theme import ThemeManager
+
+
+class CTkScrollableFrame(tkinter.Frame, CTkAppearanceModeBaseClass, CTkScalingBaseClass):
+ def __init__(self,
+ master: Any,
+ width: int = 200,
+ height: int = 200,
+ corner_radius: Optional[Union[int, str]] = None,
+ border_width: Optional[Union[int, str]] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ scrollbar_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None,
+ scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ label_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ label_text_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ label_text: str = "",
+ label_font: Optional[Union[tuple, CTkFont]] = None,
+ label_anchor: str = "center",
+ orientation: Literal["vertical", "horizontal"] = "vertical"):
+
+ self._orientation = orientation
+
+ # dimensions independent of scaling
+ self._desired_width = width # _desired_width and _desired_height, represent desired size set by width and height
+ self._desired_height = height
+
+ self._parent_frame = CTkFrame(master=master, width=0, height=0, corner_radius=corner_radius,
+ border_width=border_width, bg_color=bg_color, fg_color=fg_color, border_color=border_color)
+ self._parent_canvas = tkinter.Canvas(master=self._parent_frame, highlightthickness=0)
+ self._set_scroll_increments()
+
+ if self._orientation == "horizontal":
+ self._scrollbar = CTkScrollbar(master=self._parent_frame, orientation="horizontal", command=self._parent_canvas.xview,
+ fg_color=scrollbar_fg_color, button_color=scrollbar_button_color, button_hover_color=scrollbar_button_hover_color)
+ self._parent_canvas.configure(xscrollcommand=self._scrollbar.set)
+ elif self._orientation == "vertical":
+ self._scrollbar = CTkScrollbar(master=self._parent_frame, orientation="vertical", command=self._parent_canvas.yview,
+ fg_color=scrollbar_fg_color, button_color=scrollbar_button_color, button_hover_color=scrollbar_button_hover_color)
+ self._parent_canvas.configure(yscrollcommand=self._scrollbar.set)
+
+ self._label_text = label_text
+ self._label = CTkLabel(self._parent_frame, text=label_text, anchor=label_anchor, font=label_font,
+ corner_radius=self._parent_frame.cget("corner_radius"), text_color=label_text_color,
+ fg_color=ThemeManager.theme["CTkScrollableFrame"]["label_fg_color"] if label_fg_color is None else label_fg_color)
+
+ tkinter.Frame.__init__(self, master=self._parent_canvas, highlightthickness=0)
+ CTkAppearanceModeBaseClass.__init__(self)
+ CTkScalingBaseClass.__init__(self, scaling_type="widget")
+
+ self._create_grid()
+
+ self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ self.bind("", lambda e: self._parent_canvas.configure(scrollregion=self._parent_canvas.bbox("all")))
+ self._parent_canvas.bind("", self._fit_frame_dimensions_to_canvas)
+ self.bind_all("", self._mouse_wheel_all, add="+")
+ self.bind_all("", self._keyboard_shift_press_all, add="+")
+ self.bind_all("", self._keyboard_shift_press_all, add="+")
+ self.bind_all("", self._keyboard_shift_release_all, add="+")
+ self.bind_all("", self._keyboard_shift_release_all, add="+")
+ self._create_window_id = self._parent_canvas.create_window(0, 0, window=self, anchor="nw")
+
+ if self._parent_frame.cget("fg_color") == "transparent":
+ tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
+ self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
+ else:
+ tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
+ self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
+
+ self._shift_pressed = False
+
+ def destroy(self):
+ tkinter.Frame.destroy(self)
+ CTkAppearanceModeBaseClass.destroy(self)
+ CTkScalingBaseClass.destroy(self)
+
+ def _create_grid(self):
+ border_spacing = self._apply_widget_scaling(self._parent_frame.cget("corner_radius") + self._parent_frame.cget("border_width"))
+
+ if self._orientation == "horizontal":
+ self._parent_frame.grid_columnconfigure(0, weight=1)
+ self._parent_frame.grid_rowconfigure(1, weight=1)
+ self._parent_canvas.grid(row=1, column=0, sticky="nsew", padx=border_spacing, pady=(border_spacing, 0))
+ self._scrollbar.grid(row=2, column=0, sticky="nsew", padx=border_spacing)
+
+ if self._label_text is not None and self._label_text != "":
+ self._label.grid(row=0, column=0, sticky="ew", padx=border_spacing, pady=border_spacing)
+ else:
+ self._label.grid_forget()
+
+ elif self._orientation == "vertical":
+ self._parent_frame.grid_columnconfigure(0, weight=1)
+ self._parent_frame.grid_rowconfigure(1, weight=1)
+ self._parent_canvas.grid(row=1, column=0, sticky="nsew", padx=(border_spacing, 0), pady=border_spacing)
+ self._scrollbar.grid(row=1, column=1, sticky="nsew", pady=border_spacing)
+
+ if self._label_text is not None and self._label_text != "":
+ self._label.grid(row=0, column=0, columnspan=2, sticky="ew", padx=border_spacing, pady=border_spacing)
+ else:
+ self._label.grid_forget()
+
+ def _set_appearance_mode(self, mode_string):
+ super()._set_appearance_mode(mode_string)
+
+ if self._parent_frame.cget("fg_color") == "transparent":
+ tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
+ self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
+ else:
+ tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
+ self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
+
+ def _set_scaling(self, new_widget_scaling, new_window_scaling):
+ super()._set_scaling(new_widget_scaling, new_window_scaling)
+
+ self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ def _set_dimensions(self, width=None, height=None):
+ if width is not None:
+ self._desired_width = width
+ if height is not None:
+ self._desired_height = height
+
+ self._parent_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ def configure(self, **kwargs):
+ if "width" in kwargs:
+ self._set_dimensions(width=kwargs.pop("width"))
+
+ if "height" in kwargs:
+ self._set_dimensions(height=kwargs.pop("height"))
+
+ if "corner_radius" in kwargs:
+ new_corner_radius = kwargs.pop("corner_radius")
+ self._parent_frame.configure(corner_radius=new_corner_radius)
+ if self._label is not None:
+ self._label.configure(corner_radius=new_corner_radius)
+ self._create_grid()
+
+ if "border_width" in kwargs:
+ self._parent_frame.configure(border_width=kwargs.pop("border_width"))
+ self._create_grid()
+
+ if "fg_color" in kwargs:
+ self._parent_frame.configure(fg_color=kwargs.pop("fg_color"))
+
+ if self._parent_frame.cget("fg_color") == "transparent":
+ tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
+ self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("bg_color")))
+ else:
+ tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
+ self._parent_canvas.configure(bg=self._apply_appearance_mode(self._parent_frame.cget("fg_color")))
+
+ for child in self.winfo_children():
+ if isinstance(child, CTkBaseClass):
+ child.configure(bg_color=self._parent_frame.cget("fg_color"))
+
+ if "scrollbar_fg_color" in kwargs:
+ self._scrollbar.configure(fg_color=kwargs.pop("scrollbar_fg_color"))
+
+ if "scrollbar_button_color" in kwargs:
+ self._scrollbar.configure(button_color=kwargs.pop("scrollbar_button_color"))
+
+ if "scrollbar_button_hover_color" in kwargs:
+ self._scrollbar.configure(button_hover_color=kwargs.pop("scrollbar_button_hover_color"))
+
+ if "label_text" in kwargs:
+ self._label_text = kwargs.pop("label_text")
+ self._label.configure(text=self._label_text)
+ self._create_grid()
+
+ if "label_font" in kwargs:
+ self._label.configure(font=kwargs.pop("label_font"))
+
+ if "label_text_color" in kwargs:
+ self._label.configure(text_color=kwargs.pop("label_text_color"))
+
+ if "label_fg_color" in kwargs:
+ self._label.configure(fg_color=kwargs.pop("label_fg_color"))
+
+ if "label_anchor" in kwargs:
+ self._label.configure(anchor=kwargs.pop("label_anchor"))
+
+ self._parent_frame.configure(**kwargs)
+
+ def cget(self, attribute_name: str):
+ if attribute_name == "width":
+ return self._desired_width
+ elif attribute_name == "height":
+ return self._desired_height
+
+ elif attribute_name == "label_text":
+ return self._label_text
+ elif attribute_name == "label_font":
+ return self._label.cget("font")
+ elif attribute_name == "label_text_color":
+ return self._label.cget("_text_color")
+ elif attribute_name == "label_fg_color":
+ return self._label.cget("fg_color")
+ elif attribute_name == "label_anchor":
+ return self._label.cget("anchor")
+
+ elif attribute_name.startswith("scrollbar_fg_color"):
+ return self._scrollbar.cget("fg_color")
+ elif attribute_name.startswith("scrollbar_button_color"):
+ return self._scrollbar.cget("button_color")
+ elif attribute_name.startswith("scrollbar_button_hover_color"):
+ return self._scrollbar.cget("button_hover_color")
+
+ else:
+ return self._parent_frame.cget(attribute_name)
+
+ def _fit_frame_dimensions_to_canvas(self, event):
+ if self._orientation == "horizontal":
+ self._parent_canvas.itemconfigure(self._create_window_id, height=self._parent_canvas.winfo_height())
+ elif self._orientation == "vertical":
+ self._parent_canvas.itemconfigure(self._create_window_id, width=self._parent_canvas.winfo_width())
+
+ def _set_scroll_increments(self):
+ if sys.platform.startswith("win"):
+ self._parent_canvas.configure(xscrollincrement=1, yscrollincrement=1)
+ elif sys.platform == "darwin":
+ self._parent_canvas.configure(xscrollincrement=4, yscrollincrement=8)
+
+ def _mouse_wheel_all(self, event):
+ if self.check_if_master_is_canvas(event.widget):
+ if sys.platform.startswith("win"):
+ if self._shift_pressed:
+ if self._parent_canvas.xview() != (0.0, 1.0):
+ self._parent_canvas.xview("scroll", -int(event.delta / 6), "units")
+ else:
+ if self._parent_canvas.yview() != (0.0, 1.0):
+ self._parent_canvas.yview("scroll", -int(event.delta / 6), "units")
+ elif sys.platform == "darwin":
+ if self._shift_pressed:
+ if self._parent_canvas.xview() != (0.0, 1.0):
+ self._parent_canvas.xview("scroll", -event.delta, "units")
+ else:
+ if self._parent_canvas.yview() != (0.0, 1.0):
+ self._parent_canvas.yview("scroll", -event.delta, "units")
+ else:
+ if self._shift_pressed:
+ if self._parent_canvas.xview() != (0.0, 1.0):
+ self._parent_canvas.xview("scroll", -event.delta, "units")
+ else:
+ if self._parent_canvas.yview() != (0.0, 1.0):
+ self._parent_canvas.yview("scroll", -event.delta, "units")
+
+ def _keyboard_shift_press_all(self, event):
+ self._shift_pressed = True
+
+ def _keyboard_shift_release_all(self, event):
+ self._shift_pressed = False
+
+ def check_if_master_is_canvas(self, widget):
+ if widget == self._parent_canvas:
+ return True
+ elif widget.master is not None:
+ return self.check_if_master_is_canvas(widget.master)
+ else:
+ return False
+
+ def pack(self, **kwargs):
+ self._parent_frame.pack(**kwargs)
+
+ def place(self, **kwargs):
+ self._parent_frame.place(**kwargs)
+
+ def grid(self, **kwargs):
+ self._parent_frame.grid(**kwargs)
+
+ def pack_forget(self):
+ self._parent_frame.pack_forget()
+
+ def place_forget(self, **kwargs):
+ self._parent_frame.place_forget()
+
+ def grid_forget(self, **kwargs):
+ self._parent_frame.grid_forget()
+
+ def grid_remove(self, **kwargs):
+ self._parent_frame.grid_remove()
+
+ def grid_propagate(self, **kwargs):
+ self._parent_frame.grid_propagate()
+
+ def grid_info(self, **kwargs):
+ return self._parent_frame.grid_info()
+
+ def lift(self, aboveThis=None):
+ self._parent_frame.lift(aboveThis)
+
+ def lower(self, belowThis=None):
+ self._parent_frame.lower(belowThis)
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_scrollbar.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_scrollbar.py
new file mode 100644
index 0000000..8e96221
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_scrollbar.py
@@ -0,0 +1,281 @@
+import sys
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+
+
+class CTkScrollbar(CTkBaseClass):
+ """
+ Scrollbar with rounded corners, configurable spacing.
+ Connect to scrollable widget by passing .set() method and set command attribute.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: Optional[Union[int, str]] = None,
+ height: Optional[Union[int, str]] = None,
+ corner_radius: Optional[int] = None,
+ border_spacing: Optional[int] = None,
+ minimum_pixel_length: int = 20,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ hover: bool = True,
+ command: Union[Callable, Any] = None,
+ orientation: str = "vertical",
+ **kwargs):
+
+ # set default dimensions according to orientation
+ if width is None:
+ if orientation.lower() == "vertical":
+ width = 16
+ else:
+ width = 200
+ if height is None:
+ if orientation.lower() == "horizontal":
+ height = 16
+ else:
+ height = 200
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # color
+ self._fg_color = ThemeManager.theme["CTkScrollbar"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
+ self._button_color = ThemeManager.theme["CTkScrollbar"]["button_color"] if button_color is None else self._check_color_type(button_color)
+ self._button_hover_color = ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkScrollbar"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_spacing = ThemeManager.theme["CTkScrollbar"]["border_spacing"] if border_spacing is None else border_spacing
+
+ self._hover = hover
+ self._hover_state: bool = False
+ self._command = command
+ 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,
+ width=self._apply_widget_scaling(self._current_width),
+ height=self._apply_widget_scaling(self._current_height))
+ self._canvas.place(x=0, y=0, relwidth=1, relheight=1)
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._create_bindings()
+ self._draw()
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None:
+ self._canvas.tag_bind("border_parts", "", self._clicked)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_enter)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_leave)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._clicked)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._mouse_scroll_event)
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ 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):
+ super()._draw(no_color_updates)
+
+ 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),
+ corrected_start_value,
+ corrected_end_value,
+ self._orientation)
+
+ if no_color_updates is False or requires_recoloring:
+ if self._hover_state is True:
+ self._canvas.itemconfig("scrollbar_parts",
+ fill=self._apply_appearance_mode(self._button_hover_color),
+ outline=self._apply_appearance_mode(self._button_hover_color))
+ else:
+ self._canvas.itemconfig("scrollbar_parts",
+ fill=self._apply_appearance_mode(self._button_color),
+ outline=self._apply_appearance_mode(self._button_color))
+
+ if self._fg_color == "transparent":
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.configure(bg=self._apply_appearance_mode(self._fg_color))
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+
+ self._canvas.update_idletasks()
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
+ require_redraw = True
+
+ if "button_color" in kwargs:
+ self._button_color = self._check_color_type(kwargs.pop("button_color"))
+ require_redraw = True
+
+ if "button_hover_color" in kwargs:
+ self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
+ require_redraw = True
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "border_spacing" in kwargs:
+ self._border_spacing = kwargs.pop("border_spacing")
+ require_redraw = True
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_spacing":
+ return self._border_spacing
+ elif attribute_name == "minimum_pixel_length":
+ return self._minimum_pixel_length
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "scrollbar_color":
+ return self._button_color
+ elif attribute_name == "scrollbar_hover_color":
+ return self._button_hover_color
+
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "orientation":
+ return self._orientation
+
+ else:
+ return super().cget(attribute_name)
+
+ def _on_enter(self, event=0):
+ if self._hover is True:
+ self._hover_state = True
+ self._canvas.itemconfig("scrollbar_parts",
+ outline=self._apply_appearance_mode(self._button_hover_color),
+ fill=self._apply_appearance_mode(self._button_hover_color))
+
+ def _on_leave(self, event=0):
+ self._hover_state = False
+ self._canvas.itemconfig("scrollbar_parts",
+ outline=self._apply_appearance_mode(self._button_color),
+ fill=self._apply_appearance_mode(self._button_color))
+
+ def _clicked(self, event):
+ if self._orientation == "vertical":
+ value = self._reverse_widget_scaling(((event.y - self._border_spacing) / (self._current_height - 2 * self._border_spacing)))
+ else:
+ value = self._reverse_widget_scaling(((event.x - self._border_spacing) / (self._current_width - 2 * self._border_spacing)))
+
+ 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:
+ if sys.platform.startswith("win"):
+ self._command('scroll', -int(event.delta/40), 'units')
+ else:
+ self._command('scroll', -event.delta, 'units')
+
+ def set(self, start_value: float, end_value: float):
+ self._start_value = float(start_value)
+ self._end_value = float(end_value)
+ self._draw()
+
+ def get(self):
+ return self._start_value, self._end_value
+
+ def bind(self, sequence=None, command=None, add=True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+
+ def unbind(self, sequence=None, funcid=None):
+ """ called on the tkinter.Canvas, restores internal callbacks """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None) # unbind all callbacks for sequence
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._canvas.focus()
+
+ def focus_set(self):
+ return self._canvas.focus_set()
+
+ def focus_force(self):
+ return self._canvas.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_segmented_button.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_segmented_button.py
new file mode 100644
index 0000000..b8de1e7
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_segmented_button.py
@@ -0,0 +1,447 @@
+import tkinter
+import copy
+from typing import Union, Tuple, List, Dict, Callable, Optional, Any
+try:
+ from typing import Literal
+except ImportError:
+ from typing_extensions import Literal
+
+from .theme import ThemeManager
+from .font import CTkFont
+from .ctk_button import CTkButton
+from .ctk_frame import CTkFrame
+from .utility import check_kwargs_empty
+
+
+class CTkSegmentedButton(CTkFrame):
+ """
+ Segmented button with corner radius, border width, variable support.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: int = 140,
+ height: int = 28,
+ corner_radius: Optional[int] = None,
+ border_width: int = 3,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ selected_color: Optional[Union[str, Tuple[str, str]]] = None,
+ selected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ unselected_color: Optional[Union[str, Tuple[str, str]]] = None,
+ unselected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+ background_corner_colors: Union[Tuple[Union[str, Tuple[str, str]]], None] = None,
+
+ font: Optional[Union[tuple, CTkFont]] = None,
+ values: Optional[list] = None,
+ variable: Union[tkinter.Variable, None] = None,
+ dynamic_resizing: bool = True,
+ command: Union[Callable[[str], Any], None] = None,
+ state: str = "normal"):
+
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height)
+
+ self._sb_fg_color = ThemeManager.theme["CTkSegmentedButton"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+
+ self._sb_selected_color = ThemeManager.theme["CTkSegmentedButton"]["selected_color"] if selected_color is None else self._check_color_type(selected_color)
+ self._sb_selected_hover_color = ThemeManager.theme["CTkSegmentedButton"]["selected_hover_color"] if selected_hover_color is None else self._check_color_type(selected_hover_color)
+
+ self._sb_unselected_color = ThemeManager.theme["CTkSegmentedButton"]["unselected_color"] if unselected_color is None else self._check_color_type(unselected_color)
+ self._sb_unselected_hover_color = ThemeManager.theme["CTkSegmentedButton"]["unselected_hover_color"] if unselected_hover_color is None else self._check_color_type(unselected_hover_color)
+
+ self._sb_text_color = ThemeManager.theme["CTkSegmentedButton"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._sb_text_color_disabled = ThemeManager.theme["CTkSegmentedButton"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
+
+ self._sb_corner_radius = ThemeManager.theme["CTkSegmentedButton"]["corner_radius"] if corner_radius is None else corner_radius
+ self._sb_border_width = ThemeManager.theme["CTkSegmentedButton"]["border_width"] if border_width is None else border_width
+
+ self._background_corner_colors = background_corner_colors # rendering options for DrawEngine
+
+ self._command: Callable[[str], None] = command
+ self._font = CTkFont() if font is None else font
+ self._state = state
+
+ self._buttons_dict: Dict[str, CTkButton] = {} # mapped from value to button object
+ if values is None:
+ self._value_list: List[str] = ["CTkSegmentedButton"]
+ else:
+ self._value_list: List[str] = values # Values ordered like buttons rendered on widget
+
+ self._dynamic_resizing = dynamic_resizing
+ if not self._dynamic_resizing:
+ self.grid_propagate(False)
+
+ self._check_unique_values(self._value_list)
+ self._current_value: str = ""
+ if len(self._value_list) > 0:
+ self._create_buttons_from_values()
+ self._create_button_grid()
+
+ self._variable = variable
+ self._variable_callback_blocked: bool = False
+ self._variable_callback_name: Union[str, None] = None
+
+ if self._variable is not None:
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self.set(self._variable.get(), from_variable_callback=True)
+
+ super().configure(corner_radius=self._sb_corner_radius, fg_color="transparent")
+
+ def destroy(self):
+ if self._variable is not None: # remove old callback
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ super().destroy()
+
+ def _set_dimensions(self, width: int = None, height: int = None):
+ super()._set_dimensions(width, height)
+
+ for button in self._buttons_dict.values():
+ button.configure(height=height)
+
+ def _variable_callback(self, var_name, index, mode):
+ if not self._variable_callback_blocked:
+ self.set(self._variable.get(), from_variable_callback=True)
+
+ def _get_index_by_value(self, value: str):
+ for index, value_from_list in enumerate(self._value_list):
+ if value_from_list == value:
+ return index
+
+ raise ValueError(f"CTkSegmentedButton does not contain value '{value}'")
+
+ def _configure_button_corners_for_index(self, index: int):
+ if index == 0 and len(self._value_list) == 1:
+ if self._background_corner_colors is None:
+ self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
+ else:
+ self._buttons_dict[self._value_list[index]].configure(background_corner_colors=self._background_corner_colors)
+
+ elif index == 0:
+ if self._background_corner_colors is None:
+ self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._bg_color, self._sb_fg_color, self._sb_fg_color, self._bg_color))
+ else:
+ self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._background_corner_colors[0], self._sb_fg_color, self._sb_fg_color, self._background_corner_colors[3]))
+
+ elif index == len(self._value_list) - 1:
+ if self._background_corner_colors is None:
+ self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._bg_color, self._bg_color, self._sb_fg_color))
+ else:
+ self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._background_corner_colors[1], self._background_corner_colors[2], self._sb_fg_color))
+
+ else:
+ self._buttons_dict[self._value_list[index]].configure(background_corner_colors=(self._sb_fg_color, self._sb_fg_color, self._sb_fg_color, self._sb_fg_color))
+
+ def _unselect_button_by_value(self, value: str):
+ if value in self._buttons_dict:
+ self._buttons_dict[value].configure(fg_color=self._sb_unselected_color,
+ hover_color=self._sb_unselected_hover_color)
+
+ def _select_button_by_value(self, value: str):
+ if self._current_value is not None and self._current_value != "":
+ self._unselect_button_by_value(self._current_value)
+
+ self._current_value = value
+
+ self._buttons_dict[value].configure(fg_color=self._sb_selected_color,
+ hover_color=self._sb_selected_hover_color)
+
+ def _create_button(self, index: int, value: str) -> CTkButton:
+ new_button = CTkButton(self,
+ width=0,
+ height=self._current_height,
+ corner_radius=self._sb_corner_radius,
+ border_width=self._sb_border_width,
+ fg_color=self._sb_unselected_color,
+ border_color=self._sb_fg_color,
+ hover_color=self._sb_unselected_hover_color,
+ text_color=self._sb_text_color,
+ text_color_disabled=self._sb_text_color_disabled,
+ text=value,
+ font=self._font,
+ state=self._state,
+ command=lambda v=value: self.set(v, from_button_callback=True),
+ background_corner_colors=None,
+ round_width_to_even_numbers=False,
+ round_height_to_even_numbers=False) # DrawEngine rendering option (so that theres no gap between buttons)
+
+ return new_button
+
+ @staticmethod
+ def _check_unique_values(values: List[str]):
+ """ raises exception if values are not unique """
+ if len(values) != len(set(values)):
+ raise ValueError("CTkSegmentedButton values are not unique")
+
+ def _create_button_grid(self):
+ # remove minsize from every grid cell in the first row
+ number_of_columns, _ = self.grid_size()
+ for n in range(number_of_columns):
+ self.grid_columnconfigure(n, weight=1, minsize=0)
+ self.grid_rowconfigure(0, weight=1)
+
+ for index, value in enumerate(self._value_list):
+ self.grid_columnconfigure(index, weight=1, minsize=self._current_height)
+ self._buttons_dict[value].grid(row=0, column=index, sticky="nsew")
+
+ def _create_buttons_from_values(self):
+ assert len(self._buttons_dict) == 0
+ assert len(self._value_list) > 0
+
+ for index, value in enumerate(self._value_list):
+ self._buttons_dict[value] = self._create_button(index, value)
+ self._configure_button_corners_for_index(index)
+
+ def configure(self, **kwargs):
+ if "width" in kwargs:
+ super().configure(width=kwargs.pop("width"))
+
+ if "height" in kwargs:
+ super().configure(height=kwargs.pop("height"))
+
+ if "corner_radius" in kwargs:
+ self._sb_corner_radius = kwargs.pop("corner_radius")
+ super().configure(corner_radius=self._sb_corner_radius)
+ for button in self._buttons_dict.values():
+ button.configure(corner_radius=self._sb_corner_radius)
+
+ if "border_width" in kwargs:
+ self._sb_border_width = kwargs.pop("border_width")
+ for button in self._buttons_dict.values():
+ button.configure(border_width=self._sb_border_width)
+
+ if "bg_color" in kwargs:
+ super().configure(bg_color=kwargs.pop("bg_color"))
+
+ if len(self._buttons_dict) > 0:
+ self._configure_button_corners_for_index(0)
+ if len(self._buttons_dict) > 1:
+ max_index = len(self._buttons_dict) - 1
+ self._configure_button_corners_for_index(max_index)
+
+ if "fg_color" in kwargs:
+ self._sb_fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ for index, button in enumerate(self._buttons_dict.values()):
+ button.configure(border_color=self._sb_fg_color)
+ self._configure_button_corners_for_index(index)
+
+ if "selected_color" in kwargs:
+ self._sb_selected_color = self._check_color_type(kwargs.pop("selected_color"))
+ if self._current_value in self._buttons_dict:
+ self._buttons_dict[self._current_value].configure(fg_color=self._sb_selected_color)
+
+ if "selected_hover_color" in kwargs:
+ self._sb_selected_hover_color = self._check_color_type(kwargs.pop("selected_hover_color"))
+ if self._current_value in self._buttons_dict:
+ self._buttons_dict[self._current_value].configure(hover_color=self._sb_selected_hover_color)
+
+ if "unselected_color" in kwargs:
+ self._sb_unselected_color = self._check_color_type(kwargs.pop("unselected_color"))
+ for value, button in self._buttons_dict.items():
+ if value != self._current_value:
+ button.configure(fg_color=self._sb_unselected_color)
+
+ if "unselected_hover_color" in kwargs:
+ self._sb_unselected_hover_color = self._check_color_type(kwargs.pop("unselected_hover_color"))
+ for value, button in self._buttons_dict.items():
+ if value != self._current_value:
+ button.configure(hover_color=self._sb_unselected_hover_color)
+
+ if "text_color" in kwargs:
+ self._sb_text_color = self._check_color_type(kwargs.pop("text_color"))
+ for button in self._buttons_dict.values():
+ button.configure(text_color=self._sb_text_color)
+
+ if "text_color_disabled" in kwargs:
+ self._sb_text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ for button in self._buttons_dict.values():
+ button.configure(text_color_disabled=self._sb_text_color_disabled)
+
+ if "background_corner_colors" in kwargs:
+ self._background_corner_colors = kwargs.pop("background_corner_colors")
+ for i in range(len(self._buttons_dict)):
+ self._configure_button_corners_for_index(i)
+
+ if "font" in kwargs:
+ self._font = kwargs.pop("font")
+ for button in self._buttons_dict.values():
+ button.configure(font=self._font)
+
+ if "values" in kwargs:
+ for button in self._buttons_dict.values():
+ button.destroy()
+ self._buttons_dict.clear()
+ self._value_list = kwargs.pop("values")
+
+ self._check_unique_values(self._value_list)
+
+ if len(self._value_list) > 0:
+ self._create_buttons_from_values()
+ self._create_button_grid()
+
+ if self._current_value in self._value_list:
+ self._select_button_by_value(self._current_value)
+
+ if "variable" in kwargs:
+ if self._variable is not None: # remove old callback
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ self._variable = kwargs.pop("variable")
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self.set(self._variable.get(), from_variable_callback=True)
+ else:
+ self._variable = None
+
+ if "dynamic_resizing" in kwargs:
+ self._dynamic_resizing = kwargs.pop("dynamic_resizing")
+ if not self._dynamic_resizing:
+ self.grid_propagate(False)
+ else:
+ self.grid_propagate(True)
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ for button in self._buttons_dict.values():
+ button.configure(state=self._state)
+
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "width":
+ return super().cget(attribute_name)
+ elif attribute_name == "height":
+ return super().cget(attribute_name)
+ elif attribute_name == "corner_radius":
+ return self._sb_corner_radius
+ elif attribute_name == "border_width":
+ return self._sb_border_width
+
+ elif attribute_name == "bg_color":
+ return super().cget(attribute_name)
+ elif attribute_name == "fg_color":
+ return self._sb_fg_color
+ elif attribute_name == "selected_color":
+ return self._sb_selected_color
+ elif attribute_name == "selected_hover_color":
+ return self._sb_selected_hover_color
+ elif attribute_name == "unselected_color":
+ return self._sb_unselected_color
+ elif attribute_name == "unselected_hover_color":
+ return self._sb_unselected_hover_color
+ elif attribute_name == "text_color":
+ return self._sb_text_color
+ elif attribute_name == "text_color_disabled":
+ return self._sb_text_color_disabled
+
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "values":
+ return copy.copy(self._value_list)
+ elif attribute_name == "variable":
+ return self._variable
+ elif attribute_name == "dynamic_resizing":
+ return self._dynamic_resizing
+ elif attribute_name == "command":
+ return self._command
+
+ else:
+ raise ValueError(f"'{attribute_name}' is not a supported argument. Look at the documentation for supported arguments.")
+
+ def set(self, value: str, from_variable_callback: bool = False, from_button_callback: bool = False):
+ if value == self._current_value:
+ return
+ elif value in self._buttons_dict:
+ self._select_button_by_value(value)
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(value)
+ self._variable_callback_blocked = False
+ else:
+ if self._current_value in self._buttons_dict:
+ self._unselect_button_by_value(self._current_value)
+ self._current_value = value
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(value)
+ self._variable_callback_blocked = False
+
+ if from_button_callback:
+ if self._command is not None:
+ self._command(self._current_value)
+
+ def get(self) -> str:
+ return self._current_value
+
+ def index(self, value: str) -> int:
+ return self._value_list.index(value)
+
+ def insert(self, index: int, value: str):
+ if value not in self._buttons_dict:
+ if value != "":
+ self._value_list.insert(index, value)
+ self._buttons_dict[value] = self._create_button(index, value)
+
+ self._configure_button_corners_for_index(index)
+ if index > 0:
+ self._configure_button_corners_for_index(index - 1)
+ if index < len(self._buttons_dict) - 1:
+ self._configure_button_corners_for_index(index + 1)
+
+ self._create_button_grid()
+
+ if value == self._current_value:
+ self._select_button_by_value(self._current_value)
+ else:
+ raise ValueError(f"CTkSegmentedButton can not insert value ''")
+ else:
+ raise ValueError(f"CTkSegmentedButton can not insert value '{value}', already part of the values")
+
+ def move(self, new_index: int, value: str):
+ if 0 <= new_index < len(self._value_list):
+ if value in self._buttons_dict:
+ self.delete(value)
+ self.insert(new_index, value)
+ else:
+ raise ValueError(f"CTkSegmentedButton has no value named '{value}'")
+ else:
+ raise ValueError(f"CTkSegmentedButton new_index {new_index} not in range of value list with len {len(self._value_list)}")
+
+ def delete(self, value: str):
+ if value in self._buttons_dict:
+ self._buttons_dict[value].destroy()
+ self._buttons_dict.pop(value)
+ index_to_remove = self._get_index_by_value(value)
+ self._value_list.pop(index_to_remove)
+
+ # removed index was outer right element
+ if index_to_remove == len(self._buttons_dict) and len(self._buttons_dict) > 0:
+ self._configure_button_corners_for_index(index_to_remove - 1)
+
+ # removed index was outer left element
+ if index_to_remove == 0 and len(self._buttons_dict) > 0:
+ self._configure_button_corners_for_index(0)
+
+ #if index_to_remove <= len(self._buttons_dict) - 1:
+ # self._configure_button_corners_for_index(index_to_remove)
+
+ self._create_button_grid()
+ else:
+ raise ValueError(f"CTkSegmentedButton does not contain value '{value}'")
+
+ def bind(self, sequence=None, command=None, add=None):
+ raise NotImplementedError
+
+ def unbind(self, sequence=None, funcid=None):
+ raise NotImplementedError
+
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_slider.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_slider.py
new file mode 100644
index 0000000..7aa03ee
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_slider.py
@@ -0,0 +1,413 @@
+import tkinter
+import sys
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+
+
+class CTkSlider(CTkBaseClass):
+ """
+ Slider with rounded corners, border, number of steps, variable support, vertical orientation.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: Optional[int] = None,
+ height: Optional[int] = None,
+ corner_radius: Optional[int] = None,
+ button_corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+ button_length: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Union[str, Tuple[str, str]] = "transparent",
+ progress_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ from_: int = 0,
+ to: int = 1,
+ state: str = "normal",
+ number_of_steps: Union[int, None] = None,
+ hover: bool = True,
+ command: Union[Callable[[float], Any], None] = None,
+ variable: Union[tkinter.Variable, None] = None,
+ orientation: str = "horizontal",
+ **kwargs):
+
+ # set default dimensions according to orientation
+ if width is None:
+ if orientation.lower() == "vertical":
+ width = 16
+ else:
+ width = 200
+ if height is None:
+ if orientation.lower() == "vertical":
+ height = 200
+ else:
+ height = 16
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # color
+ self._border_color = self._check_color_type(border_color, transparency=True)
+ self._fg_color = ThemeManager.theme["CTkSlider"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._progress_color = ThemeManager.theme["CTkSlider"]["progress_color"] if progress_color is None else self._check_color_type(progress_color, transparency=True)
+ self._button_color = ThemeManager.theme["CTkSlider"]["button_color"] if button_color is None else self._check_color_type(button_color)
+ self._button_hover_color = ThemeManager.theme["CTkSlider"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkSlider"]["corner_radius"] if corner_radius is None else corner_radius
+ self._button_corner_radius = ThemeManager.theme["CTkSlider"]["button_corner_radius"] if button_corner_radius is None else button_corner_radius
+ self._border_width = ThemeManager.theme["CTkSlider"]["border_width"] if border_width is None else border_width
+ self._button_length = ThemeManager.theme["CTkSlider"]["button_length"] if button_length is None else button_length
+ self._value: float = 0.5 # initial value of slider in percent
+ self._orientation = orientation
+ self._hover_state: bool = False
+ self._hover = hover
+ self._from_ = from_
+ self._to = to
+ self._number_of_steps = number_of_steps
+ self._output_value = self._from_ + (self._value * (self._to - self._from_))
+
+ if self._corner_radius < self._button_corner_radius:
+ self._corner_radius = self._button_corner_radius
+
+ # callback and control variables
+ self._command = command
+ self._variable: tkinter.Variable = variable
+ self._variable_callback_blocked: bool = False
+ self._variable_callback_name: Union[bool, None] = None
+ self._state = state
+
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_columnconfigure(0, weight=1)
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.grid(column=0, row=0, rowspan=1, columnspan=1, sticky="nswe")
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._create_bindings()
+ self._set_cursor()
+ self._draw() # initial draw
+
+ if self._variable is not None:
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._variable_callback_blocked = True
+ self.set(self._variable.get(), from_variable_callback=True)
+ self._variable_callback_blocked = False
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_enter)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_leave)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._clicked)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._clicked)
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def destroy(self):
+ # remove variable_callback from variable callbacks if variable exists
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ super().destroy()
+
+ def _set_cursor(self):
+ if self._state == "normal" and self._cursor_manipulation_enabled:
+ if sys.platform == "darwin":
+ self.configure(cursor="pointinghand")
+ elif sys.platform.startswith("win"):
+ self.configure(cursor="hand2")
+
+ elif self._state == "disabled" and self._cursor_manipulation_enabled:
+ if sys.platform == "darwin":
+ self.configure(cursor="arrow")
+ elif sys.platform.startswith("win"):
+ self.configure(cursor="arrow")
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ if self._orientation.lower() == "horizontal":
+ orientation = "w"
+ elif self._orientation.lower() == "vertical":
+ orientation = "s"
+ else:
+ orientation = "w"
+
+ requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(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_width),
+ self._apply_widget_scaling(self._button_length),
+ self._apply_widget_scaling(self._button_corner_radius),
+ self._value, orientation)
+
+ if no_color_updates is False or requires_recoloring:
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ if self._border_color == "transparent":
+ self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.itemconfig("border_parts", fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+
+ self._canvas.itemconfig("inner_parts", fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+
+ if self._progress_color == "transparent":
+ self._canvas.itemconfig("progress_parts", fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ else:
+ self._canvas.itemconfig("progress_parts", fill=self._apply_appearance_mode(self._progress_color),
+ outline=self._apply_appearance_mode(self._progress_color))
+
+ if self._hover_state is True:
+ self._canvas.itemconfig("slider_parts",
+ fill=self._apply_appearance_mode(self._button_hover_color),
+ outline=self._apply_appearance_mode(self._button_hover_color))
+ else:
+ self._canvas.itemconfig("slider_parts",
+ fill=self._apply_appearance_mode(self._button_color),
+ outline=self._apply_appearance_mode(self._button_color))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "button_corner_radius" in kwargs:
+ self._button_corner_radius = kwargs.pop("button_corner_radius")
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ require_redraw = True
+
+ if "button_length" in kwargs:
+ self._button_length = kwargs.pop("button_length")
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"), transparency=True)
+ require_redraw = True
+
+ if "progress_color" in kwargs:
+ self._progress_color = self._check_color_type(kwargs.pop("progress_color"), transparency=True)
+ require_redraw = True
+
+ if "button_color" in kwargs:
+ self._button_color = self._check_color_type(kwargs.pop("button_color"))
+ require_redraw = True
+
+ if "button_hover_color" in kwargs:
+ self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
+ require_redraw = True
+
+ if "from_" in kwargs:
+ self._from_ = kwargs.pop("from_")
+
+ if "to" in kwargs:
+ self._to = kwargs.pop("to")
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ self._set_cursor()
+ require_redraw = True
+
+ if "number_of_steps" in kwargs:
+ self._number_of_steps = kwargs.pop("number_of_steps")
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "variable" in kwargs:
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ self._variable = kwargs.pop("variable")
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self.set(self._variable.get(), from_variable_callback=True)
+ else:
+ self._variable = None
+
+ if "orientation" in kwargs:
+ self._orientation = kwargs.pop("orientation")
+ require_redraw = True
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "button_corner_radius":
+ return self._button_corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+ elif attribute_name == "button_length":
+ return self._button_length
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "progress_color":
+ return self._progress_color
+ elif attribute_name == "button_color":
+ return self._button_color
+ elif attribute_name == "button_hover_color":
+ return self._button_hover_color
+
+ elif attribute_name == "from_":
+ return self._from_
+ elif attribute_name == "to":
+ return self._to
+ elif attribute_name == "state":
+ return self._state
+ elif attribute_name == "number_of_steps":
+ return self._number_of_steps
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "variable":
+ return self._variable
+ elif attribute_name == "orientation":
+ return self._orientation
+
+ else:
+ return super().cget(attribute_name)
+
+ def _clicked(self, event=None):
+ if self._state == "normal":
+ if self._orientation.lower() == "horizontal":
+ self._value = self._reverse_widget_scaling(event.x / self._current_width)
+ else:
+ self._value = 1 - self._reverse_widget_scaling(event.y / self._current_height)
+
+ if self._value > 1:
+ self._value = 1
+ if self._value < 0:
+ self._value = 0
+
+ self._output_value = self._round_to_step_size(self._from_ + (self._value * (self._to - self._from_)))
+ self._value = (self._output_value - self._from_) / (self._to - self._from_)
+
+ self._draw(no_color_updates=False)
+
+ if self._variable is not None:
+ self._variable_callback_blocked = True
+ self._variable.set(round(self._output_value) if isinstance(self._variable, tkinter.IntVar) else self._output_value)
+ self._variable_callback_blocked = False
+
+ if self._command is not None:
+ self._command(self._output_value)
+
+ def _on_enter(self, event=0):
+ if self._hover is True and self._state == "normal":
+ self._hover_state = True
+ self._canvas.itemconfig("slider_parts",
+ fill=self._apply_appearance_mode(self._button_hover_color),
+ outline=self._apply_appearance_mode(self._button_hover_color))
+
+ def _on_leave(self, event=0):
+ self._hover_state = False
+ self._canvas.itemconfig("slider_parts",
+ fill=self._apply_appearance_mode(self._button_color),
+ outline=self._apply_appearance_mode(self._button_color))
+
+ def _round_to_step_size(self, value) -> float:
+ if self._number_of_steps is not None:
+ step_size = (self._to - self._from_) / self._number_of_steps
+ value = self._to - (round((self._to - value) / step_size) * step_size)
+ return value
+ else:
+ return value
+
+ def get(self) -> float:
+ return self._output_value
+
+ def set(self, output_value, from_variable_callback=False):
+ if self._from_ < self._to:
+ if output_value > self._to:
+ output_value = self._to
+ elif output_value < self._from_:
+ output_value = self._from_
+ else:
+ if output_value < self._to:
+ output_value = self._to
+ elif output_value > self._from_:
+ output_value = self._from_
+
+ self._output_value = self._round_to_step_size(output_value)
+ self._value = (self._output_value - self._from_) / (self._to - self._from_)
+
+ self._draw(no_color_updates=False)
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(round(self._output_value) if isinstance(self._variable, tkinter.IntVar) else self._output_value)
+ self._variable_callback_blocked = False
+
+ def _variable_callback(self, var_name, index, mode):
+ if not self._variable_callback_blocked:
+ self.set(self._variable.get(), from_variable_callback=True)
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._canvas.focus()
+
+ def focus_set(self):
+ return self._canvas.focus_set()
+
+ def focus_force(self):
+ return self._canvas.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_switch.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_switch.py
new file mode 100644
index 0000000..155c174
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_switch.py
@@ -0,0 +1,483 @@
+import tkinter
+import sys
+from typing import Union, Tuple, Callable, Optional, Any
+
+from .core_rendering import CTkCanvas
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+
+
+class CTkSwitch(CTkBaseClass):
+ """
+ Switch with rounded corners, border, label, command, variable support.
+ For detailed information check out the documentation.
+ """
+
+ def __init__(self,
+ master: Any,
+ width: int = 100,
+ height: int = 24,
+ switch_width: int = 36,
+ switch_height: int = 18,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+ button_length: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Union[str, Tuple[str, str]] = "transparent",
+ progress_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_color: Optional[Union[str, Tuple[str, str]]] = None,
+ button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+
+ text: str = "CTkSwitch",
+ font: Optional[Union[tuple, CTkFont]] = None,
+ textvariable: Union[tkinter.Variable, None] = None,
+ onvalue: Union[int, str] = 1,
+ offvalue: Union[int, str] = 0,
+ variable: Union[tkinter.Variable, None] = None,
+ hover: bool = True,
+ command: Union[Callable, Any] = None,
+ state: str = tkinter.NORMAL,
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # dimensions
+ self._switch_width = switch_width
+ self._switch_height = switch_height
+
+ # color
+ self._border_color = self._check_color_type(border_color, transparency=True)
+ self._fg_color = ThemeManager.theme["CTkSwitch"]["fg_color"] if fg_color is None else self._check_color_type(fg_color)
+ self._progress_color = ThemeManager.theme["CTkSwitch"]["progress_color"] if progress_color is None else self._check_color_type(progress_color, transparency=True)
+ self._button_color = ThemeManager.theme["CTkSwitch"]["button_color"] if button_color is None else self._check_color_type(button_color)
+ self._button_hover_color = ThemeManager.theme["CTkSwitch"]["button_hover_color"] if button_hover_color is None else self._check_color_type(button_hover_color)
+ self._text_color = ThemeManager.theme["CTkSwitch"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._text_color_disabled = ThemeManager.theme["CTkSwitch"]["text_color_disabled"] if text_color_disabled is None else self._check_color_type(text_color_disabled)
+
+ # text
+ self._text = text
+ self._text_label = None
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkSwitch"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkSwitch"]["border_width"] if border_width is None else border_width
+ self._button_length = ThemeManager.theme["CTkSwitch"]["button_length"] if button_length is None else button_length
+ self._hover_state: bool = False
+ self._check_state: bool = False # True if switch is activated
+ self._hover = hover
+ self._state = state
+ self._onvalue = onvalue
+ self._offvalue = offvalue
+
+ # callback and control variables
+ self._command = command
+ self._variable = variable
+ self._variable_callback_blocked = False
+ self._variable_callback_name = None
+ self._textvariable = textvariable
+
+ # configure grid system (3x1)
+ self.grid_columnconfigure(0, weight=0)
+ self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
+ self.grid_columnconfigure(2, weight=1)
+ self.grid_rowconfigure(0, weight=1)
+
+ self._bg_canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._current_width),
+ height=self._apply_widget_scaling(self._current_height))
+ self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._switch_width),
+ height=self._apply_widget_scaling(self._switch_height))
+ self._canvas.grid(row=0, column=0, sticky="")
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._text_label = tkinter.Label(master=self,
+ bd=0,
+ padx=0,
+ pady=0,
+ text=self._text,
+ justify=tkinter.LEFT,
+ font=self._apply_font_scaling(self._font),
+ textvariable=self._textvariable)
+ self._text_label.grid(row=0, column=2, sticky="w")
+ self._text_label["anchor"] = "w"
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._check_state = True if self._variable.get() == self._onvalue else False
+
+ self._create_bindings()
+ self._set_cursor()
+ self._draw() # initial draw
+
+ def _create_bindings(self, sequence: Optional[str] = None):
+ """ set necessary bindings for functionality of widget, will overwrite other bindings """
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_enter)
+ self._text_label.bind("", self._on_enter)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self._on_leave)
+ self._text_label.bind("", self._on_leave)
+ if sequence is None or sequence == "":
+ self._canvas.bind("", self.toggle)
+ self._text_label.bind("", self.toggle)
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(6))
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.configure(width=self._apply_widget_scaling(self._switch_width),
+ height=self._apply_widget_scaling(self._switch_height))
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width: int = None, height: int = None):
+ super()._set_dimensions(width, height)
+
+ self._bg_canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ self._text_label.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._bg_canvas.grid_forget()
+ self._bg_canvas.grid(row=0, column=0, columnspan=3, sticky="nswe")
+
+ def destroy(self):
+ # remove variable_callback from variable callbacks if variable exists
+ if self._variable is not None:
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ super().destroy()
+
+ def _set_cursor(self):
+ if self._cursor_manipulation_enabled:
+ if self._state == tkinter.DISABLED:
+ if sys.platform == "darwin":
+ self._canvas.configure(cursor="arrow")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="arrow")
+ elif sys.platform.startswith("win"):
+ self._canvas.configure(cursor="arrow")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="arrow")
+
+ elif self._state == tkinter.NORMAL:
+ if sys.platform == "darwin":
+ self._canvas.configure(cursor="pointinghand")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="pointinghand")
+ elif sys.platform.startswith("win"):
+ self._canvas.configure(cursor="hand2")
+ if self._text_label is not None:
+ self._text_label.configure(cursor="hand2")
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ if self._check_state is True:
+ requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._switch_width),
+ self._apply_widget_scaling(self._switch_height),
+ self._apply_widget_scaling(self._corner_radius),
+ self._apply_widget_scaling(self._border_width),
+ self._apply_widget_scaling(self._button_length),
+ self._apply_widget_scaling(self._corner_radius),
+ 1, "w")
+ else:
+ requires_recoloring = self._draw_engine.draw_rounded_slider_with_border_and_button(self._apply_widget_scaling(self._switch_width),
+ self._apply_widget_scaling(self._switch_height),
+ self._apply_widget_scaling(self._corner_radius),
+ self._apply_widget_scaling(self._border_width),
+ self._apply_widget_scaling(self._button_length),
+ self._apply_widget_scaling(self._corner_radius),
+ 0, "w")
+
+ if no_color_updates is False or requires_recoloring:
+ self._bg_canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ if self._border_color == "transparent":
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+
+ if self._progress_color == "transparent":
+ self._canvas.itemconfig("progress_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ else:
+ self._canvas.itemconfig("progress_parts",
+ fill=self._apply_appearance_mode(self._progress_color),
+ outline=self._apply_appearance_mode(self._progress_color))
+
+ self._canvas.itemconfig("slider_parts",
+ fill=self._apply_appearance_mode(self._button_color),
+ outline=self._apply_appearance_mode(self._button_color))
+
+ if self._state == tkinter.DISABLED:
+ self._text_label.configure(fg=(self._apply_appearance_mode(self._text_color_disabled)))
+ else:
+ self._text_label.configure(fg=self._apply_appearance_mode(self._text_color))
+
+ self._text_label.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ require_redraw = True
+
+ if "button_length" in kwargs:
+ self._button_length = kwargs.pop("button_length")
+ require_redraw = True
+
+ if "switch_width" in kwargs:
+ self._switch_width = kwargs.pop("switch_width")
+ self._canvas.configure(width=self._apply_widget_scaling(self._switch_width))
+ require_redraw = True
+
+ if "switch_height" in kwargs:
+ self._switch_height = kwargs.pop("switch_height")
+ self._canvas.configure(height=self._apply_widget_scaling(self._switch_height))
+ require_redraw = True
+
+ if "text" in kwargs:
+ self._text = kwargs.pop("text")
+ self._text_label.configure(text=self._text)
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ if "state" in kwargs:
+ self._state = kwargs.pop("state")
+ self._set_cursor()
+ require_redraw = True
+
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"))
+ require_redraw = True
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"), transparency=True)
+ require_redraw = True
+
+ if "progress_color" in kwargs:
+ self._progress_color = self._check_color_type(kwargs.pop("progress_color"), transparency=True)
+ require_redraw = True
+
+ if "button_color" in kwargs:
+ self._button_color = self._check_color_type(kwargs.pop("button_color"))
+ require_redraw = True
+
+ if "button_hover_color" in kwargs:
+ self._button_hover_color = self._check_color_type(kwargs.pop("button_hover_color"))
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "text_color_disabled" in kwargs:
+ self._text_color_disabled = self._check_color_type(kwargs.pop("text_color_disabled"))
+ require_redraw = True
+
+ if "hover" in kwargs:
+ self._hover = kwargs.pop("hover")
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+
+ if "textvariable" in kwargs:
+ self._textvariable = kwargs.pop("textvariable")
+ self._text_label.configure(textvariable=self._textvariable)
+
+ if "variable" in kwargs:
+ if self._variable is not None and self._variable != "":
+ self._variable.trace_remove("write", self._variable_callback_name)
+
+ self._variable = kwargs.pop("variable")
+
+ if self._variable is not None and self._variable != "":
+ self._variable_callback_name = self._variable.trace_add("write", self._variable_callback)
+ self._check_state = True if self._variable.get() == self._onvalue else False
+ require_redraw = True
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+ elif attribute_name == "button_length":
+ return self._button_length
+ elif attribute_name == "switch_width":
+ return self._switch_width
+ elif attribute_name == "switch_height":
+ return self._switch_height
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "progress_color":
+ return self._progress_color
+ elif attribute_name == "button_color":
+ return self._button_color
+ elif attribute_name == "button_hover_color":
+ return self._button_hover_color
+ elif attribute_name == "text_color":
+ return self._text_color
+ elif attribute_name == "text_color_disabled":
+ return self._text_color_disabled
+
+ elif attribute_name == "text":
+ return self._text
+ elif attribute_name == "font":
+ return self._font
+ elif attribute_name == "textvariable":
+ return self._textvariable
+ elif attribute_name == "onvalue":
+ return self._onvalue
+ elif attribute_name == "offvalue":
+ return self._offvalue
+ elif attribute_name == "variable":
+ return self._variable
+ elif attribute_name == "hover":
+ return self._hover
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "state":
+ return self._state
+
+ else:
+ return super().cget(attribute_name)
+
+ def toggle(self, event=None):
+ if self._state is not tkinter.DISABLED:
+ if self._check_state is True:
+ self._check_state = False
+ else:
+ self._check_state = True
+
+ self._draw(no_color_updates=True)
+
+ if self._variable is not None:
+ self._variable_callback_blocked = True
+ self._variable.set(self._onvalue if self._check_state is True else self._offvalue)
+ self._variable_callback_blocked = False
+
+ if self._command is not None:
+ self._command()
+
+ def select(self, from_variable_callback=False):
+ if self._state is not tkinter.DISABLED or from_variable_callback:
+ self._check_state = True
+
+ self._draw(no_color_updates=True)
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(self._onvalue)
+ self._variable_callback_blocked = False
+
+ def deselect(self, from_variable_callback=False):
+ if self._state is not tkinter.DISABLED or from_variable_callback:
+ self._check_state = False
+
+ self._draw(no_color_updates=True)
+
+ if self._variable is not None and not from_variable_callback:
+ self._variable_callback_blocked = True
+ self._variable.set(self._offvalue)
+ self._variable_callback_blocked = False
+
+ def get(self) -> Union[int, str]:
+ return self._onvalue if self._check_state is True else self._offvalue
+
+ def _on_enter(self, event=0):
+ if self._hover is True and self._state == "normal":
+ self._hover_state = True
+ self._canvas.itemconfig("slider_parts",
+ fill=self._apply_appearance_mode(self._button_hover_color),
+ outline=self._apply_appearance_mode(self._button_hover_color))
+
+ def _on_leave(self, event=0):
+ self._hover_state = False
+ self._canvas.itemconfig("slider_parts",
+ fill=self._apply_appearance_mode(self._button_color),
+ outline=self._apply_appearance_mode(self._button_color))
+
+ def _variable_callback(self, var_name, index, mode):
+ if not self._variable_callback_blocked:
+ if self._variable.get() == self._onvalue:
+ self.select(from_variable_callback=True)
+ elif self._variable.get() == self._offvalue:
+ self.deselect(from_variable_callback=True)
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._canvas.bind(sequence, command, add=True)
+ self._text_label.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._canvas.unbind(sequence, None)
+ self._text_label.unbind(sequence, None)
+ self._create_bindings(sequence=sequence) # restore internal callbacks for sequence
+
+ def focus(self):
+ return self._text_label.focus()
+
+ def focus_set(self):
+ return self._text_label.focus_set()
+
+ def focus_force(self):
+ return self._text_label.focus_force()
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_tabview.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_tabview.py
new file mode 100644
index 0000000..3b2ea5b
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_tabview.py
@@ -0,0 +1,433 @@
+import tkinter
+from typing import Union, Tuple, Dict, List, Callable, Optional, Any
+
+from .theme import ThemeManager
+from .ctk_frame import CTkFrame
+from .core_rendering import CTkCanvas
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .ctk_segmented_button import CTkSegmentedButton
+
+
+class CTkTabview(CTkBaseClass):
+ """
+ Tabview...
+ For detailed information check out the documentation.
+ """
+
+ _outer_spacing: int = 10 # px on top or below the button
+ _outer_button_overhang: int = 8 # px
+ _button_height: int = 26
+ _segmented_button_border_width: int = 3
+
+ def __init__(self,
+ master: Any,
+ width: int = 300,
+ height: int = 250,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ segmented_button_fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ segmented_button_selected_color: Optional[Union[str, Tuple[str, str]]] = None,
+ segmented_button_selected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+ segmented_button_unselected_color: Optional[Union[str, Tuple[str, str]]] = None,
+ segmented_button_unselected_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ text_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color_disabled: Optional[Union[str, Tuple[str, str]]] = None,
+
+ command: Union[Callable, Any] = None,
+ anchor: str = "center",
+ state: str = "normal",
+ **kwargs):
+
+ # transfer some functionality to CTkFrame
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height, **kwargs)
+
+ # color
+ self._border_color = ThemeManager.theme["CTkFrame"]["border_color"] if border_color is None else self._check_color_type(border_color)
+
+ # determine fg_color of frame
+ if fg_color is None:
+ if isinstance(self.master, (CTkFrame, CTkTabview)):
+ if self.master.cget("fg_color") == ThemeManager.theme["CTkFrame"]["fg_color"]:
+ self._fg_color = ThemeManager.theme["CTkFrame"]["top_fg_color"]
+ else:
+ self._fg_color = ThemeManager.theme["CTkFrame"]["fg_color"]
+ else:
+ self._fg_color = ThemeManager.theme["CTkFrame"]["fg_color"]
+ else:
+ self._fg_color = self._check_color_type(fg_color, transparency=True)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkFrame"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkFrame"]["border_width"] if border_width is None else border_width
+ self._anchor = anchor
+
+ self._canvas = CTkCanvas(master=self,
+ bg=self._apply_appearance_mode(self._bg_color),
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang))
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._segmented_button = CTkSegmentedButton(self,
+ values=[],
+ height=self._button_height,
+ fg_color=segmented_button_fg_color,
+ selected_color=segmented_button_selected_color,
+ selected_hover_color=segmented_button_selected_hover_color,
+ unselected_color=segmented_button_unselected_color,
+ unselected_hover_color=segmented_button_unselected_hover_color,
+ text_color=text_color,
+ text_color_disabled=text_color_disabled,
+ corner_radius=corner_radius,
+ border_width=self._segmented_button_border_width,
+ command=self._segmented_button_callback,
+ state=state)
+ self._configure_segmented_button_background_corners()
+ self._configure_grid()
+ self._set_grid_canvas()
+
+ self._tab_dict: Dict[str, CTkFrame] = {}
+ self._name_list: List[str] = [] # list of unique tab names in order of tabs
+ self._current_name: str = ""
+ self._command = command
+
+ self._draw()
+
+ def _segmented_button_callback(self, selected_name):
+ self._tab_dict[self._current_name].grid_forget()
+ self._current_name = selected_name
+ self._set_grid_current_tab()
+
+ if self._command is not None:
+ self._command()
+
+ def winfo_children(self) -> List[any]:
+ """
+ winfo_children of CTkTabview without canvas and segmented button widgets,
+ because it's not a child but part of the CTkTabview itself
+ """
+
+ child_widgets = super().winfo_children()
+ try:
+ child_widgets.remove(self._canvas)
+ child_widgets.remove(self._segmented_button)
+ return child_widgets
+ except ValueError:
+ return child_widgets
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang))
+ self._configure_grid()
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height - self._outer_spacing - self._outer_button_overhang))
+ self._draw()
+
+ def _configure_segmented_button_background_corners(self):
+ """ needs to be called for changes in fg_color, bg_color """
+
+ if self._fg_color == "transparent":
+ self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._bg_color, self._bg_color))
+ else:
+ if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
+ self._segmented_button.configure(background_corner_colors=(self._bg_color, self._bg_color, self._fg_color, self._fg_color))
+ else:
+ self._segmented_button.configure(background_corner_colors=(self._fg_color, self._fg_color, self._bg_color, self._bg_color))
+
+ def _configure_grid(self):
+ """ create 3 x 4 grid system """
+
+ if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
+ self.grid_rowconfigure(0, weight=0, minsize=self._apply_widget_scaling(self._outer_spacing))
+ self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._outer_button_overhang))
+ self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._outer_button_overhang))
+ self.grid_rowconfigure(3, weight=1)
+ else:
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(self._button_height - self._outer_button_overhang))
+ self.grid_rowconfigure(2, weight=0, minsize=self._apply_widget_scaling(self._outer_button_overhang))
+ self.grid_rowconfigure(3, weight=0, minsize=self._apply_widget_scaling(self._outer_spacing))
+
+ self.grid_columnconfigure(0, weight=1)
+
+ def _set_grid_canvas(self):
+ if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
+ self._canvas.grid(row=2, rowspan=2, column=0, columnspan=1, sticky="nsew")
+ else:
+ self._canvas.grid(row=0, rowspan=2, column=0, columnspan=1, sticky="nsew")
+
+ def _set_grid_segmented_button(self):
+ """ needs to be called for changes in corner_radius, anchor """
+
+ if self._anchor.lower() in ("center", "n", "s"):
+ self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="ns")
+ elif self._anchor.lower() in ("nw", "w", "sw"):
+ self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="nsw")
+ elif self._anchor.lower() in ("ne", "e", "se"):
+ self._segmented_button.grid(row=1, rowspan=2, column=0, columnspan=1, padx=self._apply_widget_scaling(self._corner_radius), sticky="nse")
+
+ def _set_grid_current_tab(self):
+ """ needs to be called for changes in corner_radius, border_width """
+ if self._anchor.lower() in ("center", "w", "nw", "n", "ne", "e", "e"):
+ self._tab_dict[self._current_name].grid(row=3, column=0, sticky="nsew",
+ padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)),
+ pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width)))
+ else:
+ self._tab_dict[self._current_name].grid(row=0, column=0, sticky="nsew",
+ padx=self._apply_widget_scaling(max(self._corner_radius, self._border_width)),
+ pady=self._apply_widget_scaling(max(self._corner_radius, self._border_width)))
+
+ def _grid_forget_all_tabs(self, exclude_name=None):
+ for name, frame in self._tab_dict.items():
+ if name != exclude_name:
+ frame.grid_forget()
+
+ def _create_tab(self) -> CTkFrame:
+ new_tab = CTkFrame(self,
+ height=0,
+ width=0,
+ border_width=0,
+ corner_radius=0)
+
+ if self._fg_color == "transparent":
+ new_tab.configure(fg_color=self._apply_appearance_mode(self._bg_color),
+ bg_color=self._apply_appearance_mode(self._bg_color))
+ else:
+ new_tab.configure(fg_color=self._apply_appearance_mode(self._fg_color),
+ bg_color=self._apply_appearance_mode(self._fg_color))
+
+ return new_tab
+
+ def _draw(self, no_color_updates: bool = False):
+ super()._draw(no_color_updates)
+
+ if not self._canvas.winfo_exists():
+ return
+
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(self._apply_widget_scaling(self._current_width),
+ self._apply_widget_scaling(self._current_height - self._outer_spacing - self._outer_button_overhang),
+ self._apply_widget_scaling(self._corner_radius),
+ self._apply_widget_scaling(self._border_width))
+
+ if no_color_updates is False or requires_recoloring:
+ if self._fg_color == "transparent":
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ for tab in self._tab_dict.values():
+ tab.configure(fg_color=self._apply_appearance_mode(self._bg_color),
+ bg_color=self._apply_appearance_mode(self._bg_color))
+ else:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ for tab in self._tab_dict.values():
+ tab.configure(fg_color=self._apply_appearance_mode(self._fg_color),
+ bg_color=self._apply_appearance_mode(self._fg_color))
+
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ tkinter.Frame.configure(self, bg=self._apply_appearance_mode(self._bg_color)) # configure bg color of tkinter.Frame, cause canvas does not fill frame
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ self._set_grid_segmented_button()
+ self._set_grid_current_tab()
+ self._set_grid_canvas()
+ self._configure_segmented_button_background_corners()
+ self._segmented_button.configure(corner_radius=self._corner_radius)
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ require_redraw = True
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
+ self._configure_segmented_button_background_corners()
+ require_redraw = True
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+ if "segmented_button_fg_color" in kwargs:
+ self._segmented_button.configure(fg_color=kwargs.pop("segmented_button_fg_color"))
+ if "segmented_button_selected_color" in kwargs:
+ self._segmented_button.configure(selected_color=kwargs.pop("segmented_button_selected_color"))
+ if "segmented_button_selected_hover_color" in kwargs:
+ self._segmented_button.configure(selected_hover_color=kwargs.pop("segmented_button_selected_hover_color"))
+ if "segmented_button_unselected_color" in kwargs:
+ self._segmented_button.configure(unselected_color=kwargs.pop("segmented_button_unselected_color"))
+ if "segmented_button_unselected_hover_color" in kwargs:
+ self._segmented_button.configure(unselected_hover_color=kwargs.pop("segmented_button_unselected_hover_color"))
+ if "text_color" in kwargs:
+ self._segmented_button.configure(text_color=kwargs.pop("text_color"))
+ if "text_color_disabled" in kwargs:
+ self._segmented_button.configure(text_color_disabled=kwargs.pop("text_color_disabled"))
+
+ if "command" in kwargs:
+ self._command = kwargs.pop("command")
+ if "anchor" in kwargs:
+ self._anchor = kwargs.pop("anchor")
+ self._configure_grid()
+ self._set_grid_segmented_button()
+ if "state" in kwargs:
+ self._segmented_button.configure(state=kwargs.pop("state"))
+
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str):
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "segmented_button_fg_color":
+ return self._segmented_button.cget(attribute_name)
+ elif attribute_name == "segmented_button_selected_color":
+ return self._segmented_button.cget(attribute_name)
+ elif attribute_name == "segmented_button_selected_hover_color":
+ return self._segmented_button.cget(attribute_name)
+ elif attribute_name == "segmented_button_unselected_color":
+ return self._segmented_button.cget(attribute_name)
+ elif attribute_name == "segmented_button_unselected_hover_color":
+ return self._segmented_button.cget(attribute_name)
+ elif attribute_name == "text_color":
+ return self._segmented_button.cget(attribute_name)
+ elif attribute_name == "text_color_disabled":
+ return self._segmented_button.cget(attribute_name)
+
+ elif attribute_name == "command":
+ return self._command
+ elif attribute_name == "anchor":
+ return self._anchor
+ elif attribute_name == "state":
+ return self._segmented_button.cget(attribute_name)
+
+ else:
+ return super().cget(attribute_name)
+
+ def tab(self, name: str) -> CTkFrame:
+ """ returns reference to the tab with given name """
+
+ if name in self._tab_dict:
+ return self._tab_dict[name]
+ else:
+ raise ValueError(f"CTkTabview has no tab named '{name}'")
+
+ def insert(self, index: int, name: str) -> CTkFrame:
+ """ creates new tab with given name at position index """
+
+ if name not in self._tab_dict:
+ # if no tab exists, set grid for segmented button
+ if len(self._tab_dict) == 0:
+ self._set_grid_segmented_button()
+
+ self._name_list.append(name)
+ self._tab_dict[name] = self._create_tab()
+ self._segmented_button.insert(index, name)
+
+ # if created tab is only tab select this tab
+ if len(self._tab_dict) == 1:
+ self._current_name = name
+ self._segmented_button.set(self._current_name)
+ self._grid_forget_all_tabs()
+ self._set_grid_current_tab()
+
+ return self._tab_dict[name]
+ else:
+ raise ValueError(f"CTkTabview already has tab named '{name}'")
+
+ def add(self, name: str) -> CTkFrame:
+ """ appends new tab with given name """
+ return self.insert(len(self._tab_dict), name)
+
+ def index(self, name) -> int:
+ """ get index of tab with given name """
+ return self._segmented_button.index(name)
+
+ def move(self, new_index: int, name: str):
+ if 0 <= new_index < len(self._name_list):
+ if name in self._tab_dict:
+ self._segmented_button.move(new_index, name)
+ else:
+ raise ValueError(f"CTkTabview has no name '{name}'")
+ else:
+ raise ValueError(f"CTkTabview new_index {new_index} not in range of name list with len {len(self._name_list)}")
+
+ def rename(self, old_name: str, new_name: str):
+ if new_name in self._name_list:
+ raise ValueError(f"new_name '{new_name}' already exists")
+
+ # segmented button
+ old_index = self._segmented_button.index(old_name)
+ self._segmented_button.delete(old_name)
+ self._segmented_button.insert(old_index, new_name)
+
+ # name list
+ self._name_list.remove(old_name)
+ self._name_list.append(new_name)
+
+ # tab dictionary
+ self._tab_dict[new_name] = self._tab_dict.pop(old_name)
+
+ def delete(self, name: str):
+ """ delete tab by name """
+
+ if name in self._tab_dict:
+ self._name_list.remove(name)
+ self._tab_dict[name].grid_forget()
+ self._tab_dict.pop(name)
+ self._segmented_button.delete(name)
+
+ # set current_name to '' and remove segmented button if no tab is left
+ if len(self._name_list) == 0:
+ self._current_name = ""
+ self._segmented_button.grid_forget()
+
+ # if only one tab left, select this tab
+ elif len(self._name_list) == 1:
+ self._current_name = self._name_list[0]
+ self._segmented_button.set(self._current_name)
+ self._grid_forget_all_tabs()
+ self._set_grid_current_tab()
+
+ # more tabs are left
+ else:
+ # if current_name is deleted tab, select first tab at position 0
+ if self._current_name == name:
+ self.set(self._name_list[0])
+ else:
+ raise ValueError(f"CTkTabview has no tab named '{name}'")
+
+ def set(self, name: str):
+ """ select tab by name """
+
+ if name in self._tab_dict:
+ self._current_name = name
+ self._segmented_button.set(name)
+ self._set_grid_current_tab()
+ self.after(100, lambda: self._grid_forget_all_tabs(exclude_name=name))
+ else:
+ raise ValueError(f"CTkTabview has no tab named '{name}'")
+
+ def get(self) -> str:
+ """ returns name of selected tab, returns empty string if no tab selected """
+ return self._current_name
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_textbox.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_textbox.py
new file mode 100644
index 0000000..4b3a165
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/ctk_textbox.py
@@ -0,0 +1,500 @@
+import tkinter
+from typing import Union, Tuple, Optional, Callable, Any
+
+from .core_rendering import CTkCanvas
+from .ctk_scrollbar import CTkScrollbar
+from .theme import ThemeManager
+from .core_rendering import DrawEngine
+from .core_widget_classes import CTkBaseClass
+from .font import CTkFont
+from .utility import pop_from_dict_by_set, check_kwargs_empty
+
+
+class CTkTextbox(CTkBaseClass):
+ """
+ Textbox with x and y scrollbars, rounded corners, and all text features of tkinter.Text widget.
+ Scrollbars only appear when they are needed. Text is wrapped on line end by default,
+ set wrap='none' to disable automatic line wrapping.
+ For detailed information check out the documentation.
+
+ Detailed methods and parameters of the underlaying tkinter.Text widget can be found here:
+ https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/text.html
+ (most of them are implemented here too)
+ """
+
+ _scrollbar_update_time = 200 # interval in ms, to check if scrollbars are needed
+
+ # attributes that are passed to and managed by the tkinter textbox only:
+ _valid_tk_text_attributes = {"autoseparators", "cursor", "exportselection",
+ "insertborderwidth", "insertofftime", "insertontime", "insertwidth",
+ "maxundo", "padx", "pady", "selectborderwidth", "spacing1",
+ "spacing2", "spacing3", "state", "tabs", "takefocus", "undo", "wrap",
+ "xscrollcommand", "yscrollcommand"}
+
+ def __init__(self,
+ master: any,
+ width: int = 200,
+ height: int = 200,
+ corner_radius: Optional[int] = None,
+ border_width: Optional[int] = None,
+ border_spacing: int = 3,
+
+ bg_color: Union[str, Tuple[str, str]] = "transparent",
+ fg_color: Optional[Union[str, Tuple[str, str]]] = None,
+ border_color: Optional[Union[str, Tuple[str, str]]] = None,
+ text_color: Optional[Union[str, str]] = None,
+ scrollbar_button_color: Optional[Union[str, Tuple[str, str]]] = None,
+ scrollbar_button_hover_color: Optional[Union[str, Tuple[str, str]]] = None,
+
+ font: Optional[Union[tuple, CTkFont]] = None,
+ activate_scrollbars: bool = True,
+ **kwargs):
+
+ # transfer basic functionality (_bg_color, size, __appearance_mode, scaling) to CTkBaseClass
+ super().__init__(master=master, bg_color=bg_color, width=width, height=height)
+
+ # color
+ self._fg_color = ThemeManager.theme["CTkTextbox"]["fg_color"] if fg_color is None else self._check_color_type(fg_color, transparency=True)
+ self._border_color = ThemeManager.theme["CTkTextbox"]["border_color"] if border_color is None else self._check_color_type(border_color)
+ self._text_color = ThemeManager.theme["CTkTextbox"]["text_color"] if text_color is None else self._check_color_type(text_color)
+ self._scrollbar_button_color = ThemeManager.theme["CTkTextbox"]["scrollbar_button_color"] if scrollbar_button_color is None else self._check_color_type(scrollbar_button_color)
+ self._scrollbar_button_hover_color = ThemeManager.theme["CTkTextbox"]["scrollbar_button_hover_color"] if scrollbar_button_hover_color is None else self._check_color_type(scrollbar_button_hover_color)
+
+ # shape
+ self._corner_radius = ThemeManager.theme["CTkTextbox"]["corner_radius"] if corner_radius is None else corner_radius
+ self._border_width = ThemeManager.theme["CTkTextbox"]["border_width"] if border_width is None else border_width
+ self._border_spacing = border_spacing
+
+ # font
+ self._font = CTkFont() if font is None else self._check_font_type(font)
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._canvas = CTkCanvas(master=self,
+ highlightthickness=0,
+ width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+ self._draw_engine = DrawEngine(self._canvas)
+
+ self._textbox = tkinter.Text(self,
+ fg=self._apply_appearance_mode(self._text_color),
+ width=0,
+ height=0,
+ font=self._apply_font_scaling(self._font),
+ highlightthickness=0,
+ relief="flat",
+ insertbackground=self._apply_appearance_mode(self._text_color),
+ **pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
+
+ check_kwargs_empty(kwargs, raise_error=True)
+
+ # scrollbars
+ self._scrollbars_activated = activate_scrollbars
+ self._hide_x_scrollbar = True
+ self._hide_y_scrollbar = True
+
+ self._y_scrollbar = CTkScrollbar(self,
+ width=8,
+ height=0,
+ border_spacing=0,
+ fg_color=self._fg_color,
+ button_color=self._scrollbar_button_color,
+ button_hover_color=self._scrollbar_button_hover_color,
+ orientation="vertical",
+ command=self._textbox.yview)
+ self._textbox.configure(yscrollcommand=self._y_scrollbar.set)
+
+ self._x_scrollbar = CTkScrollbar(self,
+ height=8,
+ width=0,
+ border_spacing=0,
+ fg_color=self._fg_color,
+ button_color=self._scrollbar_button_color,
+ button_hover_color=self._scrollbar_button_hover_color,
+ orientation="horizontal",
+ command=self._textbox.xview)
+ self._textbox.configure(xscrollcommand=self._x_scrollbar.set)
+
+ self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
+
+ self.after(50, self._check_if_scrollbars_needed, None, True)
+ self._draw()
+
+ def _create_grid_for_text_and_scrollbars(self, re_grid_textbox=False, re_grid_x_scrollbar=False, re_grid_y_scrollbar=False):
+
+ # configure 2x2 grid
+ self.grid_rowconfigure(0, weight=1)
+ self.grid_rowconfigure(1, weight=0, minsize=self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)))
+ self.grid_columnconfigure(0, weight=1)
+ self.grid_columnconfigure(1, weight=0, minsize=self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)))
+
+ if re_grid_textbox:
+ self._textbox.grid(row=0, column=0, rowspan=1, columnspan=1, sticky="nsew",
+ padx=(self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)), 0),
+ pady=(self._apply_widget_scaling(max(self._corner_radius, self._border_width + self._border_spacing)), 0))
+
+ if re_grid_x_scrollbar:
+ if not self._hide_x_scrollbar and self._scrollbars_activated:
+ self._x_scrollbar.grid(row=1, column=0, rowspan=1, columnspan=1, sticky="ewn",
+ pady=(3, self._border_spacing + self._border_width),
+ padx=(max(self._corner_radius, self._border_width + self._border_spacing), 0)) # scrollbar grid method without scaling
+ else:
+ self._x_scrollbar.grid_forget()
+
+ if re_grid_y_scrollbar:
+ if not self._hide_y_scrollbar and self._scrollbars_activated:
+ self._y_scrollbar.grid(row=0, column=1, rowspan=1, columnspan=1, sticky="nsw",
+ padx=(3, self._border_spacing + self._border_width),
+ pady=(max(self._corner_radius, self._border_width + self._border_spacing), 0)) # scrollbar grid method without scaling
+ else:
+ self._y_scrollbar.grid_forget()
+
+ def _check_if_scrollbars_needed(self, event=None, continue_loop: bool = False):
+ """ Method hides or places the scrollbars if they are needed on key release event of tkinter.text widget """
+
+ if self._scrollbars_activated:
+ if self._textbox.xview() != (0.0, 1.0) and not self._x_scrollbar.winfo_ismapped(): # x scrollbar needed
+ self._hide_x_scrollbar = False
+ self._create_grid_for_text_and_scrollbars(re_grid_x_scrollbar=True)
+ elif self._textbox.xview() == (0.0, 1.0) and self._x_scrollbar.winfo_ismapped(): # x scrollbar not needed
+ self._hide_x_scrollbar = True
+ self._create_grid_for_text_and_scrollbars(re_grid_x_scrollbar=True)
+
+ if self._textbox.yview() != (0.0, 1.0) and not self._y_scrollbar.winfo_ismapped(): # y scrollbar needed
+ self._hide_y_scrollbar = False
+ self._create_grid_for_text_and_scrollbars(re_grid_y_scrollbar=True)
+ elif self._textbox.yview() == (0.0, 1.0) and self._y_scrollbar.winfo_ismapped(): # y scrollbar not needed
+ self._hide_y_scrollbar = True
+ self._create_grid_for_text_and_scrollbars(re_grid_y_scrollbar=True)
+ else:
+ self._hide_x_scrollbar = False
+ self._hide_x_scrollbar = False
+ self._create_grid_for_text_and_scrollbars(re_grid_y_scrollbar=True)
+
+ if self._textbox.winfo_exists() and continue_loop is True:
+ self.after(self._scrollbar_update_time, lambda: self._check_if_scrollbars_needed(continue_loop=True))
+
+ def _set_scaling(self, *args, **kwargs):
+ super()._set_scaling(*args, **kwargs)
+
+ self._textbox.configure(font=self._apply_font_scaling(self._font))
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
+ self._draw(no_color_updates=True)
+
+ def _set_dimensions(self, width=None, height=None):
+ super()._set_dimensions(width, height)
+
+ self._canvas.configure(width=self._apply_widget_scaling(self._desired_width),
+ height=self._apply_widget_scaling(self._desired_height))
+ self._draw()
+
+ def _update_font(self):
+ """ pass font to tkinter widgets with applied font scaling and update grid with workaround """
+ self._textbox.configure(font=self._apply_font_scaling(self._font))
+
+ # Workaround to force grid to be resized when text changes size.
+ # Otherwise grid will lag and only resizes if other mouse action occurs.
+ self._canvas.grid_forget()
+ self._canvas.grid(row=0, column=0, rowspan=2, columnspan=2, sticky="nsew")
+
+ def destroy(self):
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+
+ super().destroy()
+
+ def _draw(self, no_color_updates=False):
+ super()._draw(no_color_updates)
+
+ if not self._canvas.winfo_exists():
+ return
+
+ requires_recoloring = self._draw_engine.draw_rounded_rect_with_border(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_width))
+
+ if no_color_updates is False or requires_recoloring:
+ if self._fg_color == "transparent":
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._bg_color),
+ outline=self._apply_appearance_mode(self._bg_color))
+ self._textbox.configure(fg=self._apply_appearance_mode(self._text_color),
+ bg=self._apply_appearance_mode(self._bg_color),
+ insertbackground=self._apply_appearance_mode(self._text_color))
+ self._x_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
+ button_hover_color=self._scrollbar_button_hover_color)
+ self._y_scrollbar.configure(fg_color=self._bg_color, button_color=self._scrollbar_button_color,
+ button_hover_color=self._scrollbar_button_hover_color)
+ else:
+ self._canvas.itemconfig("inner_parts",
+ fill=self._apply_appearance_mode(self._fg_color),
+ outline=self._apply_appearance_mode(self._fg_color))
+ self._textbox.configure(fg=self._apply_appearance_mode(self._text_color),
+ bg=self._apply_appearance_mode(self._fg_color),
+ insertbackground=self._apply_appearance_mode(self._text_color))
+ self._x_scrollbar.configure(fg_color=self._fg_color, button_color=self._scrollbar_button_color,
+ button_hover_color=self._scrollbar_button_hover_color)
+ self._y_scrollbar.configure(fg_color=self._fg_color, button_color=self._scrollbar_button_color,
+ button_hover_color=self._scrollbar_button_hover_color)
+
+ self._canvas.itemconfig("border_parts",
+ fill=self._apply_appearance_mode(self._border_color),
+ outline=self._apply_appearance_mode(self._border_color))
+ self._canvas.configure(bg=self._apply_appearance_mode(self._bg_color))
+
+ self._canvas.tag_lower("inner_parts")
+ self._canvas.tag_lower("border_parts")
+
+ def configure(self, require_redraw=False, **kwargs):
+ if "fg_color" in kwargs:
+ self._fg_color = self._check_color_type(kwargs.pop("fg_color"), transparency=True)
+ require_redraw = True
+
+ # check if CTk widgets are children of the frame and change their _bg_color to new frame fg_color
+ for child in self.winfo_children():
+ if isinstance(child, CTkBaseClass) and hasattr(child, "_fg_color"):
+ child.configure(bg_color=self._fg_color)
+
+ if "border_color" in kwargs:
+ self._border_color = self._check_color_type(kwargs.pop("border_color"))
+ require_redraw = True
+
+ if "text_color" in kwargs:
+ self._text_color = self._check_color_type(kwargs.pop("text_color"))
+ require_redraw = True
+
+ if "scrollbar_button_color" in kwargs:
+ self._scrollbar_button_color = self._check_color_type(kwargs.pop("scrollbar_button_color"))
+ self._x_scrollbar.configure(button_color=self._scrollbar_button_color)
+ self._y_scrollbar.configure(button_color=self._scrollbar_button_color)
+
+ if "scrollbar_button_hover_color" in kwargs:
+ self._scrollbar_button_hover_color = self._check_color_type(kwargs.pop("scrollbar_button_hover_color"))
+ self._x_scrollbar.configure(button_hover_color=self._scrollbar_button_hover_color)
+ self._y_scrollbar.configure(button_hover_color=self._scrollbar_button_hover_color)
+
+ if "corner_radius" in kwargs:
+ self._corner_radius = kwargs.pop("corner_radius")
+ self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
+ require_redraw = True
+
+ if "border_width" in kwargs:
+ self._border_width = kwargs.pop("border_width")
+ self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
+ require_redraw = True
+
+ if "border_spacing" in kwargs:
+ self._border_spacing = kwargs.pop("border_spacing")
+ self._create_grid_for_text_and_scrollbars(re_grid_textbox=True, re_grid_x_scrollbar=True, re_grid_y_scrollbar=True)
+ require_redraw = True
+
+ if "font" in kwargs:
+ if isinstance(self._font, CTkFont):
+ self._font.remove_size_configure_callback(self._update_font)
+ self._font = self._check_font_type(kwargs.pop("font"))
+ if isinstance(self._font, CTkFont):
+ self._font.add_size_configure_callback(self._update_font)
+
+ self._update_font()
+
+ self._textbox.configure(**pop_from_dict_by_set(kwargs, self._valid_tk_text_attributes))
+ super().configure(require_redraw=require_redraw, **kwargs)
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "corner_radius":
+ return self._corner_radius
+ elif attribute_name == "border_width":
+ return self._border_width
+ elif attribute_name == "border_spacing":
+ return self._border_spacing
+
+ elif attribute_name == "fg_color":
+ return self._fg_color
+ elif attribute_name == "border_color":
+ return self._border_color
+ elif attribute_name == "text_color":
+ return self._text_color
+
+ elif attribute_name == "font":
+ return self._font
+
+ else:
+ return super().cget(attribute_name)
+
+ def bind(self, sequence: str = None, command: Callable = None, add: Union[str, bool] = True):
+ """ called on the tkinter.Canvas """
+ if not (add == "+" or add is True):
+ raise ValueError("'add' argument can only be '+' or True to preserve internal callbacks")
+ self._textbox.bind(sequence, command, add=True)
+
+ def unbind(self, sequence: str = None, funcid: str = None):
+ """ called on the tkinter.Label and tkinter.Canvas """
+ if funcid is not None:
+ raise ValueError("'funcid' argument can only be None, because there is a bug in" +
+ " tkinter and its not clear whether the internal callbacks will be unbinded or not")
+ self._textbox.unbind(sequence, None)
+
+ def focus(self):
+ return self._textbox.focus()
+
+ def focus_set(self):
+ return self._textbox.focus_set()
+
+ def focus_force(self):
+ return self._textbox.focus_force()
+
+ def insert(self, index, text, tags=None):
+ return self._textbox.insert(index, text, tags)
+
+ def get(self, index1, index2=None):
+ return self._textbox.get(index1, index2)
+
+ def bbox(self, index):
+ return self._textbox.bbox(index)
+
+ def compare(self, index, op, index2):
+ return self._textbox.compare(index, op, index2)
+
+ def delete(self, index1, index2=None):
+ return self._textbox.delete(index1, index2)
+
+ def dlineinfo(self, index):
+ return self._textbox.dlineinfo(index)
+
+ def edit_modified(self, arg=None):
+ return self._textbox.edit_modified(arg)
+
+ def edit_redo(self):
+ self._check_if_scrollbars_needed()
+ return self._textbox.edit_redo()
+
+ def edit_reset(self):
+ return self._textbox.edit_reset()
+
+ def edit_separator(self):
+ return self._textbox.edit_separator()
+
+ def edit_undo(self):
+ self._check_if_scrollbars_needed()
+ return self._textbox.edit_undo()
+
+ def image_create(self, index, **kwargs):
+ raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
+
+ def image_cget(self, index, option):
+ raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
+
+ def image_configure(self, index):
+ raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
+
+ def image_names(self):
+ raise AttributeError("embedding images is forbidden, because would be incompatible with scaling")
+
+ def index(self, i):
+ return self._textbox.index(i)
+
+ def mark_gravity(self, mark, gravity=None):
+ return self._textbox.mark_gravity(mark, gravity)
+
+ def mark_names(self):
+ return self._textbox.mark_names()
+
+ def mark_next(self, index):
+ return self._textbox.mark_next(index)
+
+ def mark_previous(self, index):
+ return self._textbox.mark_previous(index)
+
+ def mark_set(self, mark, index):
+ return self._textbox.mark_set(mark, index)
+
+ def mark_unset(self, mark):
+ return self._textbox.mark_unset(mark)
+
+ def scan_dragto(self, x, y):
+ return self._textbox.scan_dragto(x, y)
+
+ def scan_mark(self, x, y):
+ return self._textbox.scan_mark(x, y)
+
+ def search(self, pattern, index, *args, **kwargs):
+ return self._textbox.search(pattern, index, *args, **kwargs)
+
+ def see(self, index):
+ return self._textbox.see(index)
+
+ def tag_add(self, tagName, index1, index2=None):
+ return self._textbox.tag_add(tagName, index1, index2)
+
+ def tag_bind(self, tagName, sequence, func, add=None):
+ return self._textbox.tag_bind(tagName, sequence, func, add)
+
+ def tag_cget(self, tagName, option):
+ return self._textbox.tag_cget(tagName, option)
+
+ def tag_config(self, tagName, **kwargs):
+ if "font" in kwargs:
+ raise AttributeError("'font' option forbidden, because would be incompatible with scaling")
+ return self._textbox.tag_config(tagName, **kwargs)
+
+ def tag_delete(self, *tagName):
+ return self._textbox.tag_delete(*tagName)
+
+ def tag_lower(self, tagName, belowThis=None):
+ return self._textbox.tag_lower(tagName, belowThis)
+
+ def tag_names(self, index=None):
+ return self._textbox.tag_names(index)
+
+ def tag_nextrange(self, tagName, index1, index2=None):
+ return self._textbox.tag_nextrange(tagName, index1, index2)
+
+ def tag_prevrange(self, tagName, index1, index2=None):
+ return self._textbox.tag_prevrange(tagName, index1, index2)
+
+ def tag_raise(self, tagName, aboveThis=None):
+ return self._textbox.tag_raise(tagName, aboveThis)
+
+ def tag_ranges(self, tagName):
+ return self._textbox.tag_ranges(tagName)
+
+ def tag_remove(self, tagName, index1, index2=None):
+ return self._textbox.tag_remove(tagName, index1, index2)
+
+ def tag_unbind(self, tagName, sequence, funcid=None):
+ return self._textbox.tag_unbind(tagName, sequence, funcid)
+
+ def window_cget(self, index, option):
+ raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
+
+ def window_configure(self, index, option):
+ raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
+
+ def window_create(self, index, **kwargs):
+ raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
+
+ def window_names(self):
+ raise AttributeError("embedding widgets is forbidden, would probably cause all kinds of problems ;)")
+
+ def xview(self, *args):
+ return self._textbox.xview(*args)
+
+ def xview_moveto(self, fraction):
+ return self._textbox.xview_moveto(fraction)
+
+ def xview_scroll(self, n, what):
+ return self._textbox.xview_scroll(n, what)
+
+ def yview(self, *args):
+ return self._textbox.yview(*args)
+
+ def yview_moveto(self, fraction):
+ return self._textbox.yview_moveto(fraction)
+
+ def yview_scroll(self, n, what):
+ return self._textbox.yview_scroll(n, what)
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/__init__.py
new file mode 100644
index 0000000..64a49f1
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/__init__.py
@@ -0,0 +1,24 @@
+import os
+import sys
+
+from .ctk_font import CTkFont
+from .font_manager import FontManager
+
+# import DrawEngine to set preferred_drawing_method if loading shapes font fails
+from ..core_rendering import DrawEngine
+
+FontManager.init_font_manager()
+
+# load Roboto fonts (used on Windows/Linux)
+customtkinter_directory = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
+FontManager.load_font(os.path.join(customtkinter_directory, "assets", "fonts", "Roboto", "Roboto-Regular.ttf"))
+FontManager.load_font(os.path.join(customtkinter_directory, "assets", "fonts", "Roboto", "Roboto-Medium.ttf"))
+
+# load font necessary for rendering the widgets (used on Windows/Linux)
+if FontManager.load_font(os.path.join(customtkinter_directory, "assets", "fonts", "CustomTkinter_shapes_font.otf")) is False:
+ # change draw method if font loading failed
+ if DrawEngine.preferred_drawing_method == "font_shapes":
+ sys.stderr.write("customtkinter.windows.widgets.font warning: " +
+ "Preferred drawing method 'font_shapes' can not be used because the font file could not be loaded.\n" +
+ "Using 'circle_shapes' instead. The rendering quality will be bad!\n")
+ DrawEngine.preferred_drawing_method = "circle_shapes"
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/ctk_font.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/ctk_font.py
new file mode 100644
index 0000000..e0eca0d
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/ctk_font.py
@@ -0,0 +1,94 @@
+from tkinter.font import Font
+import copy
+from typing import List, Callable, Tuple, Optional
+try:
+ from typing import Literal
+except ImportError:
+ from typing_extensions import Literal
+
+from ..theme import ThemeManager
+
+
+class CTkFont(Font):
+ """
+ Font object with size in pixel, independent of scaling.
+ To get scaled tuple representation use create_scaled_tuple() method.
+
+ family The font family name as a string.
+ size The font height as an integer in pixel.
+ weight 'bold' for boldface, 'normal' for regular weight.
+ slant 'italic' for italic, 'roman' for unslanted.
+ underline 1 for underlined text, 0 for normal.
+ overstrike 1 for overstruck text, 0 for normal.
+
+ Tkinter Font: https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/fonts.html
+ """
+
+ def __init__(self,
+ family: Optional[str] = None,
+ size: Optional[int] = None,
+ weight: Literal["normal", "bold"] = None,
+ slant: Literal["italic", "roman"] = "roman",
+ underline: bool = False,
+ overstrike: bool = False):
+
+ self._size_configure_callback_list: List[Callable] = []
+
+ self._size = ThemeManager.theme["CTkFont"]["size"] if size is None else size
+
+ super().__init__(family=ThemeManager.theme["CTkFont"]["family"] if family is None else family,
+ size=-abs(self._size),
+ weight=ThemeManager.theme["CTkFont"]["weight"] if weight is None else weight,
+ slant=slant,
+ underline=underline,
+ overstrike=overstrike)
+
+ self._family = super().cget("family")
+ self._tuple_style_string = f"{super().cget('weight')} {slant} {'underline' if underline else ''} {'overstrike' if overstrike else ''}"
+
+ def add_size_configure_callback(self, callback: Callable):
+ """ add function, that gets called when font got configured """
+ self._size_configure_callback_list.append(callback)
+
+ def remove_size_configure_callback(self, callback: Callable):
+ """ remove function, that gets called when font got configured """
+ try:
+ self._size_configure_callback_list.remove(callback)
+ except ValueError:
+ pass
+
+ def create_scaled_tuple(self, font_scaling: float) -> Tuple[str, int, str]:
+ """ return scaled tuple representation of font in the form (family: str, size: int, style: str)"""
+ return self._family, round(-abs(self._size) * font_scaling), self._tuple_style_string
+
+ def config(self, *args, **kwargs):
+ raise AttributeError("'config' is not implemented for CTk widgets. For consistency, always use 'configure' instead.")
+
+ def configure(self, **kwargs):
+ if "size" in kwargs:
+ self._size = kwargs.pop("size")
+ super().configure(size=-abs(self._size))
+
+ if "family" in kwargs:
+ super().configure(family=kwargs.pop("family"))
+ self._family = super().cget("family")
+
+ super().configure(**kwargs)
+
+ # update style string for create_scaled_tuple() method
+ self._tuple_style_string = f"{super().cget('weight')} {super().cget('slant')} {'underline' if super().cget('underline') else ''} {'overstrike' if super().cget('overstrike') else ''}"
+
+ # call all functions registered with add_size_configure_callback()
+ for callback in self._size_configure_callback_list:
+ callback()
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "size":
+ return self._size
+ if attribute_name == "family":
+ return self._family
+ else:
+ return super().cget(attribute_name)
+
+ def copy(self) -> "CTkFont":
+ return copy.deepcopy(self)
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/font_manager.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/font_manager.py
new file mode 100644
index 0000000..b3ef369
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/font/font_manager.py
@@ -0,0 +1,66 @@
+import sys
+import os
+import shutil
+from typing import Union
+
+
+class FontManager:
+
+ linux_font_path = "~/.fonts/"
+
+ @classmethod
+ def init_font_manager(cls):
+ # Linux
+ if sys.platform.startswith("linux"):
+ try:
+ if not os.path.isdir(os.path.expanduser(cls.linux_font_path)):
+ os.mkdir(os.path.expanduser(cls.linux_font_path))
+ return True
+ except Exception as err:
+ sys.stderr.write("FontManager error: " + str(err) + "\n")
+ return False
+
+ # other platforms
+ else:
+ return True
+
+ @classmethod
+ def windows_load_font(cls, font_path: Union[str, bytes], private: bool = True, enumerable: bool = False) -> bool:
+ """ Function taken from: https://stackoverflow.com/questions/11993290/truly-custom-font-in-tkinter/30631309#30631309 """
+
+ from ctypes import windll, byref, create_unicode_buffer, create_string_buffer
+
+ FR_PRIVATE = 0x10
+ FR_NOT_ENUM = 0x20
+
+ if isinstance(font_path, bytes):
+ path_buffer = create_string_buffer(font_path)
+ add_font_resource_ex = windll.gdi32.AddFontResourceExA
+ elif isinstance(font_path, str):
+ path_buffer = create_unicode_buffer(font_path)
+ add_font_resource_ex = windll.gdi32.AddFontResourceExW
+ else:
+ raise TypeError('font_path must be of type bytes or str')
+
+ flags = (FR_PRIVATE if private else 0) | (FR_NOT_ENUM if not enumerable else 0)
+ num_fonts_added = add_font_resource_ex(byref(path_buffer), flags, 0)
+ return bool(min(num_fonts_added, 1))
+
+ @classmethod
+ def load_font(cls, font_path: str) -> bool:
+ # Windows
+ if sys.platform.startswith("win"):
+ return cls.windows_load_font(font_path, private=True, enumerable=False)
+
+ # Linux
+ elif sys.platform.startswith("linux"):
+ try:
+ shutil.copy(font_path, os.path.expanduser(cls.linux_font_path))
+ return True
+ except Exception as err:
+ sys.stderr.write("FontManager error: " + str(err) + "\n")
+ return False
+
+ # macOS and others
+ else:
+ return False
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/image/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/image/__init__.py
new file mode 100644
index 0000000..b712c89
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/image/__init__.py
@@ -0,0 +1 @@
+from .ctk_image import CTkImage
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/image/ctk_image.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/image/ctk_image.py
new file mode 100644
index 0000000..0247cdd
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/image/ctk_image.py
@@ -0,0 +1,122 @@
+from typing import Tuple, Dict, Callable, List
+try:
+ from PIL import Image, ImageTk
+except ImportError:
+ pass
+
+
+class CTkImage:
+ """
+ Class to store one or two PIl.Image.Image objects and display size independent of scaling:
+
+ light_image: PIL.Image.Image for light mode
+ dark_image: PIL.Image.Image for dark mode
+ size: tuple (, ) with display size for both images
+
+ One of the two images can be None and will be replaced by the other image.
+ """
+
+ _checked_PIL_import = False
+
+ def __init__(self,
+ light_image: "Image.Image" = None,
+ dark_image: "Image.Image" = None,
+ size: Tuple[int, int] = (20, 20)):
+
+ if not self._checked_PIL_import:
+ self._check_pil_import()
+
+ self._light_image = light_image
+ self._dark_image = dark_image
+ self._check_images()
+ self._size = size
+
+ self._configure_callback_list: List[Callable] = []
+ self._scaled_light_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {}
+ self._scaled_dark_photo_images: Dict[Tuple[int, int], ImageTk.PhotoImage] = {}
+
+ @classmethod
+ def _check_pil_import(cls):
+ try:
+ _, _ = Image, ImageTk
+ except NameError:
+ raise ImportError("PIL.Image and PIL.ImageTk couldn't be imported")
+
+ def add_configure_callback(self, callback: Callable):
+ """ add function, that gets called when image got configured """
+ self._configure_callback_list.append(callback)
+
+ def remove_configure_callback(self, callback: Callable):
+ """ remove function, that gets called when image got configured """
+ self._configure_callback_list.remove(callback)
+
+ def configure(self, **kwargs):
+ if "light_image" in kwargs:
+ self._light_image = kwargs.pop("light_image")
+ self._scaled_light_photo_images = {}
+ self._check_images()
+ if "dark_image" in kwargs:
+ self._dark_image = kwargs.pop("dark_image")
+ self._scaled_dark_photo_images = {}
+ self._check_images()
+ if "size" in kwargs:
+ self._size = kwargs.pop("size")
+
+ # call all functions registered with add_configure_callback()
+ for callback in self._configure_callback_list:
+ callback()
+
+ def cget(self, attribute_name: str) -> any:
+ if attribute_name == "light_image":
+ return self._light_image
+ if attribute_name == "dark_image":
+ return self._dark_image
+ if attribute_name == "size":
+ return self._size
+
+ def _check_images(self):
+ # check types
+ if self._light_image is not None and not isinstance(self._light_image, Image.Image):
+ raise ValueError(f"CTkImage: light_image must be instance if PIL.Image.Image, not {type(self._light_image)}")
+ if self._dark_image is not None and not isinstance(self._dark_image, Image.Image):
+ raise ValueError(f"CTkImage: dark_image must be instance if PIL.Image.Image, not {type(self._dark_image)}")
+
+ # check values
+ if self._light_image is None and self._dark_image is None:
+ raise ValueError("CTkImage: No image given, light_image is None and dark_image is None.")
+
+ # check sizes
+ if self._light_image is not None and self._dark_image is not None and self._light_image.size != self._dark_image.size:
+ raise ValueError(f"CTkImage: light_image size {self._light_image.size} must be the same as dark_image size {self._dark_image.size}.")
+
+ def _get_scaled_size(self, widget_scaling: float) -> Tuple[int, int]:
+ return round(self._size[0] * widget_scaling), round(self._size[1] * widget_scaling)
+
+ def _get_scaled_light_photo_image(self, scaled_size: Tuple[int, int]) -> "ImageTk.PhotoImage":
+ if scaled_size in self._scaled_light_photo_images:
+ return self._scaled_light_photo_images[scaled_size]
+ else:
+ self._scaled_light_photo_images[scaled_size] = ImageTk.PhotoImage(self._light_image.resize(scaled_size))
+ return self._scaled_light_photo_images[scaled_size]
+
+ def _get_scaled_dark_photo_image(self, scaled_size: Tuple[int, int]) -> "ImageTk.PhotoImage":
+ if scaled_size in self._scaled_dark_photo_images:
+ return self._scaled_dark_photo_images[scaled_size]
+ else:
+ self._scaled_dark_photo_images[scaled_size] = ImageTk.PhotoImage(self._dark_image.resize(scaled_size))
+ return self._scaled_dark_photo_images[scaled_size]
+
+ def create_scaled_photo_image(self, widget_scaling: float, appearance_mode: str) -> "ImageTk.PhotoImage":
+ scaled_size = self._get_scaled_size(widget_scaling)
+
+ if appearance_mode == "light" and self._light_image is not None:
+ return self._get_scaled_light_photo_image(scaled_size)
+ elif appearance_mode == "light" and self._light_image is None:
+ return self._get_scaled_dark_photo_image(scaled_size)
+
+ elif appearance_mode == "dark" and self._dark_image is not None:
+ return self._get_scaled_dark_photo_image(scaled_size)
+ elif appearance_mode == "dark" and self._dark_image is None:
+ return self._get_scaled_light_photo_image(scaled_size)
+
+
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/__init__.py
new file mode 100644
index 0000000..8fc0db8
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/__init__.py
@@ -0,0 +1,7 @@
+import sys
+
+from .scaling_base_class import CTkScalingBaseClass
+from .scaling_tracker import ScalingTracker
+
+if sys.platform.startswith("win") and sys.getwindowsversion().build < 9000: # No automatic scaling on Windows < 8.1
+ ScalingTracker.deactivate_automatic_dpi_awareness = True
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/scaling_base_class.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/scaling_base_class.py
new file mode 100644
index 0000000..0d7b29b
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/scaling_base_class.py
@@ -0,0 +1,159 @@
+from typing import Union, Tuple
+import copy
+import re
+try:
+ from typing import Literal
+except ImportError:
+ from typing_extensions import Literal
+
+from .scaling_tracker import ScalingTracker
+from ..font import CTkFont
+
+
+class CTkScalingBaseClass:
+ """
+ Super-class that manages the scaling values and callbacks.
+ Works for widgets and windows, type must be set in init method with
+ scaling_type attribute. Methods:
+
+ - _set_scaling() abstractmethod, gets called when scaling changes, must be overridden
+ - destroy() must be called when sub-class is destroyed
+ - _apply_widget_scaling()
+ - _reverse_widget_scaling()
+ - _apply_window_scaling()
+ - _reverse_window_scaling()
+ - _apply_font_scaling()
+ - _apply_argument_scaling()
+ - _apply_geometry_scaling()
+ - _reverse_geometry_scaling()
+ - _parse_geometry_string()
+
+ """
+ def __init__(self, scaling_type: Literal["widget", "window"] = "widget"):
+ self.__scaling_type = scaling_type
+
+ if self.__scaling_type == "widget":
+ ScalingTracker.add_widget(self._set_scaling, self) # add callback for automatic scaling changes
+ self.__widget_scaling = ScalingTracker.get_widget_scaling(self)
+ elif self.__scaling_type == "window":
+ ScalingTracker.activate_high_dpi_awareness() # make process DPI aware
+ ScalingTracker.add_window(self._set_scaling, self) # add callback for automatic scaling changes
+ self.__window_scaling = ScalingTracker.get_window_scaling(self)
+
+ def destroy(self):
+ if self.__scaling_type == "widget":
+ ScalingTracker.remove_widget(self._set_scaling, self)
+ elif self.__scaling_type == "window":
+ ScalingTracker.remove_window(self._set_scaling, self)
+
+ def _set_scaling(self, new_widget_scaling, new_window_scaling):
+ """ can be overridden, but super method must be called at the beginning """
+ self.__widget_scaling = new_widget_scaling
+ self.__window_scaling = new_window_scaling
+
+ def _get_widget_scaling(self) -> float:
+ return self.__widget_scaling
+
+ def _get_window_scaling(self) -> float:
+ return self.__window_scaling
+
+ def _apply_widget_scaling(self, value: Union[int, float]) -> Union[float]:
+ assert self.__scaling_type == "widget"
+ return value * self.__widget_scaling
+
+ def _reverse_widget_scaling(self, value: Union[int, float]) -> Union[float]:
+ assert self.__scaling_type == "widget"
+ return value / self.__widget_scaling
+
+ def _apply_window_scaling(self, value: Union[int, float]) -> int:
+ assert self.__scaling_type == "window"
+ return int(value * self.__window_scaling)
+
+ def _reverse_window_scaling(self, scaled_value: Union[int, float]) -> int:
+ assert self.__scaling_type == "window"
+ return int(scaled_value / self.__window_scaling)
+
+ def _apply_font_scaling(self, font: Union[Tuple, CTkFont]) -> tuple:
+ """ Takes CTkFont object and returns tuple font with scaled size, has to be called again for every change of font object """
+ assert self.__scaling_type == "widget"
+
+ if type(font) == tuple:
+ if len(font) == 1:
+ return font
+ elif len(font) == 2:
+ return font[0], -abs(round(font[1] * self.__widget_scaling))
+ elif 3 <= len(font) <= 6:
+ return font[0], -abs(round(font[1] * self.__widget_scaling)), font[2:]
+ else:
+ raise ValueError(f"Can not scale font {font}. font needs to be tuple of len 1, 2 or 3")
+
+ elif isinstance(font, CTkFont):
+ return font.create_scaled_tuple(self.__widget_scaling)
+ else:
+ raise ValueError(f"Can not scale font '{font}' of type {type(font)}. font needs to be tuple or instance of CTkFont")
+
+ def _apply_argument_scaling(self, kwargs: dict) -> dict:
+ assert self.__scaling_type == "widget"
+
+ scaled_kwargs = copy.copy(kwargs)
+
+ # scale padding values
+ if "pady" in scaled_kwargs:
+ if isinstance(scaled_kwargs["pady"], (int, float)):
+ scaled_kwargs["pady"] = self._apply_widget_scaling(scaled_kwargs["pady"])
+ elif isinstance(scaled_kwargs["pady"], tuple):
+ scaled_kwargs["pady"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["pady"]])
+ if "padx" in kwargs:
+ if isinstance(scaled_kwargs["padx"], (int, float)):
+ scaled_kwargs["padx"] = self._apply_widget_scaling(scaled_kwargs["padx"])
+ elif isinstance(scaled_kwargs["padx"], tuple):
+ scaled_kwargs["padx"] = tuple([self._apply_widget_scaling(v) for v in scaled_kwargs["padx"]])
+
+ # scaled x, y values for place geometry manager
+ if "x" in scaled_kwargs:
+ scaled_kwargs["x"] = self._apply_widget_scaling(scaled_kwargs["x"])
+ if "y" in scaled_kwargs:
+ scaled_kwargs["y"] = self._apply_widget_scaling(scaled_kwargs["y"])
+
+ return scaled_kwargs
+
+ @staticmethod
+ def _parse_geometry_string(geometry_string: str) -> tuple:
+ # index: 1 2 3 4 5 6
+ # regex group structure: ('x', '', '', '+-+-', '-', '-')
+ result = re.search(r"((\d+)x(\d+)){0,1}(\+{0,1}([+-]{0,1}\d+)\+{0,1}([+-]{0,1}\d+)){0,1}", geometry_string)
+
+ width = int(result.group(2)) if result.group(2) is not None else None
+ height = int(result.group(3)) if result.group(3) is not None else None
+ x = int(result.group(5)) if result.group(5) is not None else None
+ y = int(result.group(6)) if result.group(6) is not None else None
+
+ return width, height, x, y
+
+ def _apply_geometry_scaling(self, geometry_string: str) -> str:
+ assert self.__scaling_type == "window"
+
+ width, height, x, y = self._parse_geometry_string(geometry_string)
+
+ if x is None and y is None: # no and in geometry_string
+ return f"{round(width * self.__window_scaling)}x{round(height * self.__window_scaling)}"
+
+ elif width is None and height is None: # no and in geometry_string
+ return f"+{x}+{y}"
+
+ else:
+ return f"{round(width * self.__window_scaling)}x{round(height * self.__window_scaling)}+{x}+{y}"
+
+ def _reverse_geometry_scaling(self, scaled_geometry_string: str) -> str:
+ assert self.__scaling_type == "window"
+
+ width, height, x, y = self._parse_geometry_string(scaled_geometry_string)
+
+ if x is None and y is None: # no and in geometry_string
+ return f"{round(width / self.__window_scaling)}x{round(height / self.__window_scaling)}"
+
+ elif width is None and height is None: # no and in geometry_string
+ return f"+{x}+{y}"
+
+ else:
+ return f"{round(width / self.__window_scaling)}x{round(height / self.__window_scaling)}+{x}+{y}"
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/scaling_tracker.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/scaling_tracker.py
new file mode 100644
index 0000000..d3627c2
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/scaling/scaling_tracker.py
@@ -0,0 +1,206 @@
+import tkinter
+import sys
+from typing import Callable
+
+
+class ScalingTracker:
+ deactivate_automatic_dpi_awareness = False
+
+ window_widgets_dict = {} # contains window objects as keys with list of widget callbacks as elements
+ window_dpi_scaling_dict = {} # contains window objects as keys and corresponding scaling factors
+
+ widget_scaling = 1 # user values which multiply to detected window scaling factor
+ window_scaling = 1
+
+ update_loop_running = False
+ update_loop_interval = 100 # ms
+ loop_pause_after_new_scaling = 1500 # ms
+
+ @classmethod
+ def get_widget_scaling(cls, widget) -> float:
+ window_root = cls.get_window_root_of_widget(widget)
+ return cls.window_dpi_scaling_dict[window_root] * cls.widget_scaling
+
+ @classmethod
+ def get_window_scaling(cls, window) -> float:
+ window_root = cls.get_window_root_of_widget(window)
+ return cls.window_dpi_scaling_dict[window_root] * cls.window_scaling
+
+ @classmethod
+ def set_widget_scaling(cls, widget_scaling_factor: float):
+ cls.widget_scaling = max(widget_scaling_factor, 0.4)
+ cls.update_scaling_callbacks_all()
+
+ @classmethod
+ def set_window_scaling(cls, window_scaling_factor: float):
+ cls.window_scaling = max(window_scaling_factor, 0.4)
+ cls.update_scaling_callbacks_all()
+
+ @classmethod
+ def get_window_root_of_widget(cls, widget):
+ current_widget = widget
+
+ while isinstance(current_widget, tkinter.Tk) is False and\
+ isinstance(current_widget, tkinter.Toplevel) is False:
+ current_widget = current_widget.master
+
+ return current_widget
+
+ @classmethod
+ def update_scaling_callbacks_all(cls):
+ for window, callback_list in cls.window_widgets_dict.items():
+ for set_scaling_callback in callback_list:
+ if not cls.deactivate_automatic_dpi_awareness:
+ set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling,
+ cls.window_dpi_scaling_dict[window] * cls.window_scaling)
+ else:
+ set_scaling_callback(cls.widget_scaling,
+ cls.window_scaling)
+
+ @classmethod
+ def update_scaling_callbacks_for_window(cls, window):
+ for set_scaling_callback in cls.window_widgets_dict[window]:
+ if not cls.deactivate_automatic_dpi_awareness:
+ set_scaling_callback(cls.window_dpi_scaling_dict[window] * cls.widget_scaling,
+ cls.window_dpi_scaling_dict[window] * cls.window_scaling)
+ else:
+ set_scaling_callback(cls.widget_scaling,
+ cls.window_scaling)
+
+ @classmethod
+ def add_widget(cls, widget_callback: Callable, widget):
+ window_root = cls.get_window_root_of_widget(widget)
+
+ if window_root not in cls.window_widgets_dict:
+ cls.window_widgets_dict[window_root] = [widget_callback]
+ else:
+ cls.window_widgets_dict[window_root].append(widget_callback)
+
+ if window_root not in cls.window_dpi_scaling_dict:
+ cls.window_dpi_scaling_dict[window_root] = cls.get_window_dpi_scaling(window_root)
+
+ if not cls.update_loop_running:
+ window_root.after(100, cls.check_dpi_scaling)
+ cls.update_loop_running = True
+
+ @classmethod
+ def remove_widget(cls, widget_callback, widget):
+ window_root = cls.get_window_root_of_widget(widget)
+ try:
+ cls.window_widgets_dict[window_root].remove(widget_callback)
+ except:
+ pass
+
+ @classmethod
+ def remove_window(cls, window_callback, window):
+ try:
+ del cls.window_widgets_dict[window]
+ except:
+ pass
+
+ @classmethod
+ def add_window(cls, window_callback, window):
+ if window not in cls.window_widgets_dict:
+ cls.window_widgets_dict[window] = [window_callback]
+ else:
+ cls.window_widgets_dict[window].append(window_callback)
+
+ if window not in cls.window_dpi_scaling_dict:
+ cls.window_dpi_scaling_dict[window] = cls.get_window_dpi_scaling(window)
+
+ @classmethod
+ def activate_high_dpi_awareness(cls):
+ """ make process DPI aware, customtkinter elements will get scaled automatically,
+ only gets activated when CTk object is created """
+
+ if not cls.deactivate_automatic_dpi_awareness:
+ if sys.platform == "darwin":
+ pass # high DPI scaling works automatically on macOS
+
+ elif sys.platform.startswith("win"):
+ import ctypes
+
+ # Values for SetProcessDpiAwareness and SetProcessDpiAwarenessContext:
+ # internal enum PROCESS_DPI_AWARENESS
+ # {
+ # Process_DPI_Unaware = 0,
+ # Process_System_DPI_Aware = 1,
+ # Process_Per_Monitor_DPI_Aware = 2
+ # }
+ #
+ # internal enum DPI_AWARENESS_CONTEXT
+ # {
+ # DPI_AWARENESS_CONTEXT_UNAWARE = 16,
+ # DPI_AWARENESS_CONTEXT_SYSTEM_AWARE = 17,
+ # DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = 18,
+ # DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = 34
+ # }
+
+ # ctypes.windll.user32.SetProcessDpiAwarenessContext(34) # Non client area scaling at runtime (titlebar)
+ # does not work with resizable(False, False), window starts growing on monitor with different scaling (weird tkinter bug...)
+ # ctypes.windll.user32.EnableNonClientDpiScaling(hwnd) does not work for some reason (tested on Windows 11)
+
+ # It's too bad, that these Windows API methods don't work properly with tkinter. But I tested days with multiple monitor setups,
+ # and I don't think there is anything left to do. So this is the best option at the moment:
+
+ ctypes.windll.shcore.SetProcessDpiAwareness(2) # Titlebar does not scale at runtime
+ else:
+ pass # DPI awareness on Linux not implemented
+
+ @classmethod
+ def get_window_dpi_scaling(cls, window) -> float:
+ if not cls.deactivate_automatic_dpi_awareness:
+ if sys.platform == "darwin":
+ return 1 # scaling works automatically on macOS
+
+ elif sys.platform.startswith("win"):
+ from ctypes import windll, pointer, wintypes
+
+ DPI100pc = 96 # DPI 96 is 100% scaling
+ DPI_type = 0 # MDT_EFFECTIVE_DPI = 0, MDT_ANGULAR_DPI = 1, MDT_RAW_DPI = 2
+ window_hwnd = wintypes.HWND(window.winfo_id())
+ monitor_handle = windll.user32.MonitorFromWindow(window_hwnd, wintypes.DWORD(2)) # MONITOR_DEFAULTTONEAREST = 2
+ x_dpi, y_dpi = wintypes.UINT(), wintypes.UINT()
+ windll.shcore.GetDpiForMonitor(monitor_handle, DPI_type, pointer(x_dpi), pointer(y_dpi))
+ return (x_dpi.value + y_dpi.value) / (2 * DPI100pc)
+
+ else:
+ return 1 # DPI awareness on Linux not implemented
+ else:
+ return 1
+
+ @classmethod
+ def check_dpi_scaling(cls):
+ new_scaling_detected = False
+
+ # check for every window if scaling value changed
+ for window in cls.window_widgets_dict:
+ if window.winfo_exists() and not window.state() == "iconic":
+ current_dpi_scaling_value = cls.get_window_dpi_scaling(window)
+ if current_dpi_scaling_value != cls.window_dpi_scaling_dict[window]:
+ cls.window_dpi_scaling_dict[window] = current_dpi_scaling_value
+
+ if sys.platform.startswith("win"):
+ window.attributes("-alpha", 0.15)
+
+ window.block_update_dimensions_event()
+ cls.update_scaling_callbacks_for_window(window)
+ window.unblock_update_dimensions_event()
+
+ if sys.platform.startswith("win"):
+ window.attributes("-alpha", 1)
+
+ new_scaling_detected = True
+
+ # find an existing tkinter object for the next call of .after()
+ for app in cls.window_widgets_dict.keys():
+ try:
+ if new_scaling_detected:
+ app.after(cls.loop_pause_after_new_scaling, cls.check_dpi_scaling)
+ else:
+ app.after(cls.update_loop_interval, cls.check_dpi_scaling)
+ return
+ except Exception:
+ continue
+
+ cls.update_loop_running = False
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/theme/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/theme/__init__.py
new file mode 100644
index 0000000..8931f35
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/theme/__init__.py
@@ -0,0 +1,9 @@
+from .theme_manager import ThemeManager
+
+# load default blue theme
+try:
+ ThemeManager.load_theme("blue")
+except FileNotFoundError as err:
+ raise FileNotFoundError(f"{err}\nThe .json theme file for CustomTkinter could not be found.\n" +
+ f"If packaging with pyinstaller was used, have a look at the wiki:\n" +
+ f"https://github.com/TomSchimansky/CustomTkinter/wiki/Packaging#windows-pyinstaller-auto-py-to-exe")
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/theme/theme_manager.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/theme/theme_manager.py
new file mode 100644
index 0000000..cf22858
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/theme/theme_manager.py
@@ -0,0 +1,55 @@
+import sys
+import os
+import pathlib
+import json
+from typing import List, Union
+
+
+class ThemeManager:
+
+ theme: dict = {} # contains all the theme data
+ _built_in_themes: List[str] = ["blue", "green", "dark-blue", "sweetkind"]
+ _currently_loaded_theme: Union[str, None] = None
+
+ @classmethod
+ def load_theme(cls, theme_name_or_path: str):
+ script_directory = os.path.dirname(os.path.abspath(__file__))
+
+ if theme_name_or_path in cls._built_in_themes:
+ customtkinter_path = pathlib.Path(script_directory).parent.parent.parent
+ with open(os.path.join(customtkinter_path, "assets", "themes", f"{theme_name_or_path}.json"), "r") as f:
+ cls.theme = json.load(f)
+ else:
+ with open(theme_name_or_path, "r") as f:
+ cls.theme = json.load(f)
+
+ # store theme path for saving
+ cls._currently_loaded_theme = theme_name_or_path
+
+ # filter theme values for platform
+ for key in cls.theme.keys():
+ # check if values for key differ on platforms
+ if "macOS" in cls.theme[key].keys():
+ if sys.platform == "darwin":
+ cls.theme[key] = cls.theme[key]["macOS"]
+ elif sys.platform.startswith("win"):
+ cls.theme[key] = cls.theme[key]["Windows"]
+ else:
+ cls.theme[key] = cls.theme[key]["Linux"]
+
+ # fix name inconsistencies
+ if "CTkCheckbox" in cls.theme.keys():
+ cls.theme["CTkCheckBox"] = cls.theme.pop("CTkCheckbox")
+ if "CTkRadiobutton" in cls.theme.keys():
+ cls.theme["CTkRadioButton"] = cls.theme.pop("CTkRadiobutton")
+
+ @classmethod
+ def save_theme(cls):
+ if cls._currently_loaded_theme is not None:
+ if cls._currently_loaded_theme not in cls._built_in_themes:
+ with open(cls._currently_loaded_theme, "r") as f:
+ json.dump(cls.theme, f, indent=2)
+ else:
+ raise ValueError(f"cannot modify builtin theme '{cls._currently_loaded_theme}'")
+ else:
+ raise ValueError(f"cannot save theme, no theme is loaded")
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/utility/__init__.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/utility/__init__.py
new file mode 100644
index 0000000..c4b6fe8
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/utility/__init__.py
@@ -0,0 +1 @@
+from .utility_functions import pop_from_dict_by_set, check_kwargs_empty
diff --git a/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/utility/utility_functions.py b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/utility/utility_functions.py
new file mode 100644
index 0000000..a9968bb
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/customtkinter/windows/widgets/utility/utility_functions.py
@@ -0,0 +1,22 @@
+
+def pop_from_dict_by_set(dictionary: dict, valid_keys: set) -> dict:
+ """ remove and create new dict with key value pairs of dictionary, where key is in valid_keys """
+ new_dictionary = {}
+
+ for key in list(dictionary.keys()):
+ if key in valid_keys:
+ new_dictionary[key] = dictionary.pop(key)
+
+ return new_dictionary
+
+
+def check_kwargs_empty(kwargs_dict, raise_error=False) -> bool:
+ """ returns True if kwargs are empty, False otherwise, raises error if not empty """
+
+ if len(kwargs_dict) > 0:
+ if raise_error:
+ raise ValueError(f"{list(kwargs_dict.keys())} are not supported arguments. Look at the documentation for supported arguments.")
+ else:
+ return True
+ else:
+ return False
diff --git a/dist/TgWsProxy.app/Contents/Resources/icon.ico b/dist/TgWsProxy.app/Contents/Resources/icon.ico
new file mode 100644
index 0000000..86c4b19
Binary files /dev/null and b/dist/TgWsProxy.app/Contents/Resources/icon.ico differ
diff --git a/dist/TgWsProxy.app/Contents/Resources/lib-dynload b/dist/TgWsProxy.app/Contents/Resources/lib-dynload
new file mode 120000
index 0000000..5319f7a
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/lib-dynload
@@ -0,0 +1 @@
+../Frameworks/lib-dynload
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libXau.6.dylib b/dist/TgWsProxy.app/Contents/Resources/libXau.6.dylib
new file mode 120000
index 0000000..d9804a9
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libXau.6.dylib
@@ -0,0 +1 @@
+../Frameworks/libXau.6.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libXdmcp.6.dylib b/dist/TgWsProxy.app/Contents/Resources/libXdmcp.6.dylib
new file mode 120000
index 0000000..bcec597
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libXdmcp.6.dylib
@@ -0,0 +1 @@
+../Frameworks/libXdmcp.6.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libfreetype.6.dylib b/dist/TgWsProxy.app/Contents/Resources/libfreetype.6.dylib
new file mode 120000
index 0000000..540867c
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libfreetype.6.dylib
@@ -0,0 +1 @@
+../Frameworks/libfreetype.6.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libjpeg.8.dylib b/dist/TgWsProxy.app/Contents/Resources/libjpeg.8.dylib
new file mode 120000
index 0000000..4b4d3ce
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libjpeg.8.dylib
@@ -0,0 +1 @@
+../Frameworks/libjpeg.8.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/liblcms2.2.dylib b/dist/TgWsProxy.app/Contents/Resources/liblcms2.2.dylib
new file mode 120000
index 0000000..3d93e16
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/liblcms2.2.dylib
@@ -0,0 +1 @@
+../Frameworks/liblcms2.2.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/liblzma.5.dylib b/dist/TgWsProxy.app/Contents/Resources/liblzma.5.dylib
new file mode 120000
index 0000000..fbed50f
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/liblzma.5.dylib
@@ -0,0 +1 @@
+../Frameworks/liblzma.5.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libopenjp2.7.dylib b/dist/TgWsProxy.app/Contents/Resources/libopenjp2.7.dylib
new file mode 120000
index 0000000..bb15d6b
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libopenjp2.7.dylib
@@ -0,0 +1 @@
+../Frameworks/libopenjp2.7.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libpng16.16.dylib b/dist/TgWsProxy.app/Contents/Resources/libpng16.16.dylib
new file mode 120000
index 0000000..8fa509d
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libpng16.16.dylib
@@ -0,0 +1 @@
+../Frameworks/libpng16.16.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libsharpyuv.0.dylib b/dist/TgWsProxy.app/Contents/Resources/libsharpyuv.0.dylib
new file mode 120000
index 0000000..cce0719
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libsharpyuv.0.dylib
@@ -0,0 +1 @@
+../Frameworks/libsharpyuv.0.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libtiff.6.dylib b/dist/TgWsProxy.app/Contents/Resources/libtiff.6.dylib
new file mode 120000
index 0000000..d952ee7
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libtiff.6.dylib
@@ -0,0 +1 @@
+../Frameworks/libtiff.6.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libwebp.7.dylib b/dist/TgWsProxy.app/Contents/Resources/libwebp.7.dylib
new file mode 120000
index 0000000..51ec6e0
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libwebp.7.dylib
@@ -0,0 +1 @@
+../Frameworks/libwebp.7.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libwebpdemux.2.dylib b/dist/TgWsProxy.app/Contents/Resources/libwebpdemux.2.dylib
new file mode 120000
index 0000000..01341e7
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libwebpdemux.2.dylib
@@ -0,0 +1 @@
+../Frameworks/libwebpdemux.2.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libwebpmux.3.dylib b/dist/TgWsProxy.app/Contents/Resources/libwebpmux.3.dylib
new file mode 120000
index 0000000..b4a6411
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libwebpmux.3.dylib
@@ -0,0 +1 @@
+../Frameworks/libwebpmux.3.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libxcb.1.dylib b/dist/TgWsProxy.app/Contents/Resources/libxcb.1.dylib
new file mode 120000
index 0000000..9f2e1a3
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libxcb.1.dylib
@@ -0,0 +1 @@
+../Frameworks/libxcb.1.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/libzstd.1.dylib b/dist/TgWsProxy.app/Contents/Resources/libzstd.1.dylib
new file mode 120000
index 0000000..79b1ec7
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/libzstd.1.dylib
@@ -0,0 +1 @@
+../Frameworks/libzstd.1.dylib
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/objc b/dist/TgWsProxy.app/Contents/Resources/objc
new file mode 120000
index 0000000..175e8d0
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/objc
@@ -0,0 +1 @@
+../Frameworks/objc
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/Resources/psutil b/dist/TgWsProxy.app/Contents/Resources/psutil
new file mode 120000
index 0000000..c33e0f8
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/Resources/psutil
@@ -0,0 +1 @@
+../Frameworks/psutil
\ No newline at end of file
diff --git a/dist/TgWsProxy.app/Contents/_CodeSignature/CodeResources b/dist/TgWsProxy.app/Contents/_CodeSignature/CodeResources
new file mode 100644
index 0000000..ad79788
--- /dev/null
+++ b/dist/TgWsProxy.app/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,1726 @@
+
+
+
+
+ files
+
+ Resources/TgWsProxy.icns
+
+ lqpRlQDrqdKZLY5Ge8CUJekEfAI=
+
+ Resources/base_library.zip
+
+ uspcAd7Yj+s1/jx3bnpQtuIF/5Y=
+
+ Resources/cryptography-46.0.5.dist-info/INSTALLER
+
+ 16AxQdXWseiLa1nvCLZoHfISxZk=
+
+ Resources/cryptography-46.0.5.dist-info/METADATA
+
+ u0BJTKjElebt8SFNQZ1E1XrRHyM=
+
+ Resources/cryptography-46.0.5.dist-info/RECORD
+
+ C2uUJAOUdRZzbmUy76azkPE1SVE=
+
+ Resources/cryptography-46.0.5.dist-info/REQUESTED
+
+ 2jmj7l5rSw0yVb/vlWAYkK/YBwk=
+
+ Resources/cryptography-46.0.5.dist-info/WHEEL
+
+ SYu5m/a19yba1hVx0mJzAQNrbsk=
+
+ Resources/cryptography-46.0.5.dist-info/licenses/LICENSE
+
+ 5EIEDCbNdtG5RoIsrykBGlH3XW0=
+
+ Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.APACHE
+
+ 3jPq0r7mQ1JUTOCqnkEMDET999k=
+
+ Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.BSD
+
+ 6ltBLAnzspuh2BphuHjFwW/+adg=
+
+ Resources/customtkinter/__init__.py
+
+ Wln+Qw0tEjfEuvROIKJm7gguSEs=
+
+ Resources/customtkinter/assets/fonts/CustomTkinter_shapes_font.otf
+
+ INBhs7dCz6MeX7yGLTT1V1NO/b8=
+
+ Resources/customtkinter/assets/fonts/Roboto/Roboto-Medium.ttf
+
+ /dyLHGiO87rtDVpGq/XwHw7a8Cs=
+
+ Resources/customtkinter/assets/fonts/Roboto/Roboto-Regular.ttf
+
+ hNECSIc4sOu8elCHlz7/vVTJW9U=
+
+ Resources/customtkinter/assets/icons/CustomTkinter_icon_Windows.ico
+
+ s2ExZOWH0JwFLDTM3E1E2sT/ROI=
+
+ Resources/customtkinter/assets/themes/blue.json
+
+ Bv/IEe5RYJgJ2IiUAi4iKzOa7+4=
+
+ Resources/customtkinter/assets/themes/dark-blue.json
+
+ +evgfnnhRvedyIp/+JQsDkMEnw0=
+
+ Resources/customtkinter/assets/themes/green.json
+
+ TwB32rbJhqZKuTkmMAJMsJdyseg=
+
+ Resources/customtkinter/windows/__init__.py
+
+ AXewfnqN2cPZWrlNUOH0rZhgE7A=
+
+ Resources/customtkinter/windows/ctk_input_dialog.py
+
+ qSCFzm7g9oLaNqpIJMcLjHf7Bc4=
+
+ Resources/customtkinter/windows/ctk_tk.py
+
+ RZn0egqCBDOEiXhq9HMpF/KRn0c=
+
+ Resources/customtkinter/windows/ctk_toplevel.py
+
+ zdwCZ0vykVwxFvchH7u7SJXsyLU=
+
+ Resources/customtkinter/windows/widgets/__init__.py
+
+ NLtGXvMXOGjzl+HX9s84CV7gFwA=
+
+ Resources/customtkinter/windows/widgets/appearance_mode/__init__.py
+
+ WualDnHMf0Wdr1nGiXFo3sf1Q0Y=
+
+ Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py
+
+ 67qSTVWF6ipthpMxiQ/JOUIr2rw=
+
+ Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py
+
+ r5uR2pG0IVovgE5AU9i3b8dfAGo=
+
+ Resources/customtkinter/windows/widgets/core_rendering/__init__.py
+
+ +o2+YODiaiuggPPrb5Cl0vGp/Oo=
+
+ Resources/customtkinter/windows/widgets/core_rendering/ctk_canvas.py
+
+ xn7Sx4zGdnTxI4GHtdXVETtVm78=
+
+ Resources/customtkinter/windows/widgets/core_rendering/draw_engine.py
+
+ ojZuYVXjCkIgcLsohWog5oM1CN0=
+
+ Resources/customtkinter/windows/widgets/core_widget_classes/__init__.py
+
+ at5fLm39WwpKIVSGQ4CatYcbGpk=
+
+ Resources/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py
+
+ 3r+t6ku1t4rv2CYmhJYzek9192M=
+
+ Resources/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py
+
+ YLCnwzZUoLDGhiIVmY1aNF9J+Mk=
+
+ Resources/customtkinter/windows/widgets/ctk_button.py
+
+ hL7fPuTeOE8JX/nCmF1agDYdSQU=
+
+ Resources/customtkinter/windows/widgets/ctk_checkbox.py
+
+ SKY9XY8AYnVfnY6Xt742sBbkCzY=
+
+ Resources/customtkinter/windows/widgets/ctk_combobox.py
+
+ 0R1YvJ9PfAXy9bqqyHxeyFHdbjA=
+
+ Resources/customtkinter/windows/widgets/ctk_entry.py
+
+ 1elVPws9ZuUIXDghjd/lE1xP1fo=
+
+ Resources/customtkinter/windows/widgets/ctk_frame.py
+
+ 68Qx4SXoqMrhE6u5DHT2mEjfeF0=
+
+ Resources/customtkinter/windows/widgets/ctk_label.py
+
+ WkiWnC7OVjXd2H9D6f+kbZlCzbA=
+
+ Resources/customtkinter/windows/widgets/ctk_optionmenu.py
+
+ kKikSLPv5f9kH3a1w9yIkMOWKp8=
+
+ Resources/customtkinter/windows/widgets/ctk_progressbar.py
+
+ GtCBlk0WUEmYJJqqRDl867NMz+o=
+
+ Resources/customtkinter/windows/widgets/ctk_radiobutton.py
+
+ F3vvOznnrSdIQi5lxNwxZCPVhH0=
+
+ Resources/customtkinter/windows/widgets/ctk_scrollable_frame.py
+
+ lX0RUgyc7OU3jaB4YTq2ls/7FxM=
+
+ Resources/customtkinter/windows/widgets/ctk_scrollbar.py
+
+ f8NisTav3ahbJ6IaLZx2edgJNxQ=
+
+ Resources/customtkinter/windows/widgets/ctk_segmented_button.py
+
+ DZBt5jqwZ/sIa6gMqdUBOsYgvtE=
+
+ Resources/customtkinter/windows/widgets/ctk_slider.py
+
+ SWtK8a05rMVUshb8gcf1s9/GXYM=
+
+ Resources/customtkinter/windows/widgets/ctk_switch.py
+
+ oFmeJTKdwVw9lURm7K9zsen3pxA=
+
+ Resources/customtkinter/windows/widgets/ctk_tabview.py
+
+ YAYwjG6LJQw3wOxSpH+XMHVOcG8=
+
+ Resources/customtkinter/windows/widgets/ctk_textbox.py
+
+ vVDQCAh9Pnjq6qOmHVu4qxCm/co=
+
+ Resources/customtkinter/windows/widgets/font/__init__.py
+
+ M7kEVJFHYh4wCdun8tX/SWPZOa8=
+
+ Resources/customtkinter/windows/widgets/font/ctk_font.py
+
+ f83WvnTzAklSbVSvPwsGykC3YI8=
+
+ Resources/customtkinter/windows/widgets/font/font_manager.py
+
+ HiGTReaV/YEWFcX9fzdDi/xRkcs=
+
+ Resources/customtkinter/windows/widgets/image/__init__.py
+
+ m780kLv9afgV0QQwheBjmtOFykc=
+
+ Resources/customtkinter/windows/widgets/image/ctk_image.py
+
+ yiHJo2Rgm7NKk95zLYLlobYxYvs=
+
+ Resources/customtkinter/windows/widgets/scaling/__init__.py
+
+ npm5iUcMgYK2XzKFeCOrekteQQw=
+
+ Resources/customtkinter/windows/widgets/scaling/scaling_base_class.py
+
+ gLpwMzYjBP3CLztbHWJPDpwPxiY=
+
+ Resources/customtkinter/windows/widgets/scaling/scaling_tracker.py
+
+ sQlAqv7+IErafejTT0cUcCiLARw=
+
+ Resources/customtkinter/windows/widgets/theme/__init__.py
+
+ CiCr0sQnTlxvw8pAZlgPdJQ+uac=
+
+ Resources/customtkinter/windows/widgets/theme/theme_manager.py
+
+ UVpdgFN/gIj6uifRhfT4a/h5bmo=
+
+ Resources/customtkinter/windows/widgets/utility/__init__.py
+
+ 5Yh9ARtnY0nRW+ttuAuli1j0jzc=
+
+ Resources/customtkinter/windows/widgets/utility/utility_functions.py
+
+ Ud9BJrZWgGxK8WjMwEizs6e4fq0=
+
+ Resources/icon.ico
+
+ +qPL9bwuCiKIyat/lmaSSotphf8=
+
+
+ files2
+
+ Frameworks/AppKit/_AppKit.cpython-39-darwin.so
+
+ cdhash
+
+ PFS3XC85rmBacihdhUqd6ALUraA=
+
+ requirement
+ cdhash H"d99c4e33a66ac404c406a8789e95d6241a47905b" or cdhash H"3c54b75c2f39ae605a72285d854a9de802d4ada0" or cdhash H"6d3c23598d90e839e79acfae2d58623a6b60d0ee" or cdhash H"1b0b1900d74f4f04c2ceb368d77b1feaa5da795b"
+
+ Frameworks/AppKit/_inlines.cpython-39-darwin.so
+
+ cdhash
+
+ cbAlVeLofZKYEQINlK69OaVdV6M=
+
+ requirement
+ cdhash H"370a7d66a85492dfb1ebe548e2680e526dfb3119" or cdhash H"71b02555e2e87d929811020d94aebd39a55d57a3" or cdhash H"ec60a1320fa28c0c6fa1745cedd37c477fe37afb" or cdhash H"47d4d4ee6c18b70833645e2c4bf95edcad4d902b"
+
+ Frameworks/CoreFoundation/_CoreFoundation.cpython-39-darwin.so
+
+ cdhash
+
+ moSSyoTrNp5Dp7MFkvM9x856JK0=
+
+ requirement
+ cdhash H"91839532b6c66504125b11197b0f0d4d798a52ba" or cdhash H"9a8492ca84eb369e43a7b30592f33dc7ce7a24ad" or cdhash H"4738540aec8b636ce65f54b116d240a8552fff3c" or cdhash H"8acee2661e7ade6e71f5ffdc23eb8b45161c68b9"
+
+ Frameworks/CoreFoundation/_inlines.cpython-39-darwin.so
+
+ cdhash
+
+ Ne70nqX4i+OreR8CMA5aAb8QSLs=
+
+ requirement
+ cdhash H"5167b4f6d68daedf097c31e968a8b4a389a22890" or cdhash H"35eef49ea5f88be3ab791f02300e5a01bf1048bb" or cdhash H"c731263b9d077102c3b2b74782f71b399eecbc11" or cdhash H"84e98a9f117b59e295d17c51a87c574613235719"
+
+ Frameworks/Foundation/_Foundation.cpython-39-darwin.so
+
+ cdhash
+
+ xXJaO2CDcODlu/mbHrchpTyu5Q0=
+
+ requirement
+ cdhash H"baf8b9caaff2c2874a8c5267abc52283c342eea6" or cdhash H"c5725a3b608370e0e5bbf99b1eb721a53caee50d" or cdhash H"b872c2e19440947c348b15719b6a9668cdb69c5b" or cdhash H"2719d4907915c1a5766ab753ed22eb17da5056e3"
+
+ Frameworks/Foundation/_inlines.cpython-39-darwin.so
+
+ cdhash
+
+ Ly+NfaqQMkH/iMgicAQg8wXt3c8=
+
+ requirement
+ cdhash H"5716c009f816bc8d88f73f3f790d778e59f24af9" or cdhash H"2f2f8d7daa903241ff88c822700420f305edddcf" or cdhash H"5ff2f56b59e02f9bdb9e1e9701fbc936de0f9fbc" or cdhash H"d82ed7e49cdbf64c783c1271ebca81522abc8684"
+
+ Frameworks/PIL/_imaging.cpython-39-darwin.so
+
+ cdhash
+
+ qpwlzxyfKrIP99JFpABikNjQGLo=
+
+ requirement
+ cdhash H"aa9c25cf1c9f2ab20ff7d245a4006290d8d018ba" or cdhash H"881e3b7138b919f1a5cb845b69bceb58abeff26a"
+
+ Frameworks/PIL/_imagingcms.cpython-39-darwin.so
+
+ cdhash
+
+ rbYjZvn7HppZ//ihyfzeuJBRrjc=
+
+ requirement
+ cdhash H"adb62366f9fb1e9a59fff8a1c9fcdeb89051ae37" or cdhash H"a7d702c3efa28ab812ee483817d4589b57fa0b41"
+
+ Frameworks/PIL/_imagingft.cpython-39-darwin.so
+
+ cdhash
+
+ q5uj0OTonzdT3pqhf5kFS+bIxDw=
+
+ requirement
+ cdhash H"ab9ba3d0e4e89f3753de9aa17f99054be6c8c43c" or cdhash H"665abd5d5dca7f55613f160b094ca617272bc7e3"
+
+ Frameworks/PIL/_imagingmath.cpython-39-darwin.so
+
+ cdhash
+
+ GoOv8g+3Xc0G3zeNi9s4/1lJOr8=
+
+ requirement
+ cdhash H"1a83aff20fb75dcd06df378d8bdb38ff59493abf" or cdhash H"58402ffedf64831ba64f3a8eaaa01bf6db2b4c0e"
+
+ Frameworks/PIL/_imagingtk.cpython-39-darwin.so
+
+ cdhash
+
+ 1H0oy5q0uqi1th/Z9qV/ZrOdMYA=
+
+ requirement
+ cdhash H"d47d28cb9ab4baa8b5b61fd9f6a57f66b39d3180" or cdhash H"e0c9ff0d4f8889c5d9028fb13dedff6bf24fc2ad"
+
+ Frameworks/PIL/_webp.cpython-39-darwin.so
+
+ cdhash
+
+ H0Ury3L2qrjnGdhl+HJp50PXoYM=
+
+ requirement
+ cdhash H"1f452bcb72f6aab8e719d865f87269e743d7a183" or cdhash H"b3595771df669f85b19b5db61003b3f97b1a382f"
+
+ Frameworks/Python3
+
+ symlink
+ Python3.framework/Versions/3.9/Python3
+
+ Frameworks/Python3.framework
+
+ cdhash
+
+ wbUgk41rJe3aiU9Oq692xaLXtvY=
+
+ requirement
+ cdhash H"c1b520938d6b25edda894f4eabaf76c5a2d7b6f6" or cdhash H"ca20add06676a32da4f4cd064609dde941f6e0c8"
+
+ Frameworks/_cffi_backend.cpython-39-darwin.so
+
+ cdhash
+
+ V0NQeOIrS747EFZ8zNUXZlpN/gU=
+
+ requirement
+ cdhash H"57435078e22b4bbe3b10567cccd517665a4dfe05" or cdhash H"81a3958601a6933fc8e53a80e8b8af71f922a26c"
+
+ Frameworks/base_library.zip
+
+ symlink
+ ../Resources/base_library.zip
+
+ Frameworks/cryptography-46.0.5.dist-info
+
+ symlink
+ ../Resources/cryptography-46.0.5.dist-info
+
+ Frameworks/cryptography/hazmat/bindings/_rust.abi3.so
+
+ cdhash
+
+ HM54aBigkWjnTomfmab6Po056FM=
+
+ requirement
+ cdhash H"1cce786818a09168e74e899f99a6fa3e8d39e853" or cdhash H"f0d32cc83cfb017ed7d91d533c992bb6689a63c0"
+
+ Frameworks/customtkinter
+
+ symlink
+ ../Resources/customtkinter
+
+ Frameworks/icon.ico
+
+ symlink
+ ../Resources/icon.ico
+
+ Frameworks/lib-dynload/_asyncio.cpython-39-darwin.so
+
+ cdhash
+
+ I76yoEik+0ROibXtOhncM/9emqI=
+
+ requirement
+ cdhash H"23beb2a048a4fb444e89b5ed3a19dc33ff5e9aa2" or cdhash H"bb0e45974e9f317be0dfd0fb15911f48064fe48c"
+
+ Frameworks/lib-dynload/_bisect.cpython-39-darwin.so
+
+ cdhash
+
+ dpzT1AaRC1xfXd5WMmV5mwKqSus=
+
+ requirement
+ cdhash H"769cd3d406910b5c5f5dde563265799b02aa4aeb" or cdhash H"5415e6eb5bc0fc0fd4636955cc17a52677948f62"
+
+ Frameworks/lib-dynload/_blake2.cpython-39-darwin.so
+
+ cdhash
+
+ vBD5q3gaax/avyzwnfQ9NTAf5tc=
+
+ requirement
+ cdhash H"bc10f9ab781a6b1fdabf2cf09df43d35301fe6d7" or cdhash H"2469ea8bc657d3e3e70d1433782f7d34ab67fe6e"
+
+ Frameworks/lib-dynload/_bz2.cpython-39-darwin.so
+
+ cdhash
+
+ JrGZBzOwiAK6p8MSggMlNJQKuzo=
+
+ requirement
+ cdhash H"26b1990733b08802baa7c31282032534940abb3a" or cdhash H"7c0141383d2e7509e3d5f1480f03ee9b02b04006"
+
+ Frameworks/lib-dynload/_codecs_cn.cpython-39-darwin.so
+
+ cdhash
+
+ b7nHV5/MuBfU17V5r8bvuc3Cc3k=
+
+ requirement
+ cdhash H"6fb9c7579fccb817d4d7b579afc6efb9cdc27379" or cdhash H"e6b0fef0b8b5310d275325f3127b9f4d35072031"
+
+ Frameworks/lib-dynload/_codecs_hk.cpython-39-darwin.so
+
+ cdhash
+
+ lxxDTX5wH05+aN24YQcvzRZ0lBo=
+
+ requirement
+ cdhash H"971c434d7e701f4e7e68ddb861072fcd1674941a" or cdhash H"d7aea5f273c564487a79b36220c585dda3d64dd3"
+
+ Frameworks/lib-dynload/_codecs_iso2022.cpython-39-darwin.so
+
+ cdhash
+
+ T272XYBjkhoLqKFLwQDJyIG9whQ=
+
+ requirement
+ cdhash H"4f6ef65d8063921a0ba8a14bc100c9c881bdc214" or cdhash H"e63ea6693b0b8ea17c2b206b809a22c4bdebfd70"
+
+ Frameworks/lib-dynload/_codecs_jp.cpython-39-darwin.so
+
+ cdhash
+
+ gAvFMhR4OPQADkhZy7JSZTEwCtw=
+
+ requirement
+ cdhash H"800bc532147838f4000e4859cbb2526531300adc" or cdhash H"21a1802ef68e5e652926b72b78ba116dc6138232"
+
+ Frameworks/lib-dynload/_codecs_kr.cpython-39-darwin.so
+
+ cdhash
+
+ iywkG+T1OVC8Fxk/Ut+dj6kPWB0=
+
+ requirement
+ cdhash H"8b2c241be4f53950bc17193f52df9d8fa90f581d" or cdhash H"1d4761954200e81e5793a964eaa6043acf667734"
+
+ Frameworks/lib-dynload/_codecs_tw.cpython-39-darwin.so
+
+ cdhash
+
+ M6N5I2P+gpxFEV0UCMk5ukS2ZqM=
+
+ requirement
+ cdhash H"33a3792363fe829c45115d1408c939ba44b666a3" or cdhash H"4f702cda88d34031199bef2e2c608a51b763eb67"
+
+ Frameworks/lib-dynload/_contextvars.cpython-39-darwin.so
+
+ cdhash
+
+ XzYW98FfoMYzKu1YBOJPGz0qN18=
+
+ requirement
+ cdhash H"5f3616f7c15fa0c6332aed5804e24f1b3d2a375f" or cdhash H"dda5dba7fd7119ea971ae88c55566fb91383b921"
+
+ Frameworks/lib-dynload/_csv.cpython-39-darwin.so
+
+ cdhash
+
+ 1zAkXNTfRE0lxST2Oui+egW/Hk8=
+
+ requirement
+ cdhash H"d730245cd4df444d25c524f63ae8be7a05bf1e4f" or cdhash H"540e0d8afcfd1f6c4de3de481662f356dfa71b26"
+
+ Frameworks/lib-dynload/_ctypes.cpython-39-darwin.so
+
+ cdhash
+
+ 0rUYVo0e1iA0pTMUxbAdLAPd+TY=
+
+ requirement
+ cdhash H"d2b518568d1ed62034a53314c5b01d2c03ddf936" or cdhash H"2a6e65b57ea429383ccf8f327b60fa4c418941e7"
+
+ Frameworks/lib-dynload/_curses.cpython-39-darwin.so
+
+ cdhash
+
+ 1gY6OBwLjmkIbOWtSjPgGAtM2Mg=
+
+ requirement
+ cdhash H"d6063a381c0b8e69086ce5ad4a33e0180b4cd8c8" or cdhash H"07067df327d36b5484375f0d10ae147d8083eecd"
+
+ Frameworks/lib-dynload/_datetime.cpython-39-darwin.so
+
+ cdhash
+
+ v6SlpZH6wF7Eyx2Il8OkK+C8eFI=
+
+ requirement
+ cdhash H"bfa4a5a591fac05ec4cb1d8897c3a42be0bc7852" or cdhash H"a094d01c0e944e5ad1c110ee8e5c7d3a075dc13b"
+
+ Frameworks/lib-dynload/_decimal.cpython-39-darwin.so
+
+ cdhash
+
+ zbmeQOpWYik6fmol/B3/FtINo9A=
+
+ requirement
+ cdhash H"cdb99e40ea5662293a7e6a25fc1dff16d20da3d0" or cdhash H"c3ead8150e3b11ec5748f362291110b984d0cdcb"
+
+ Frameworks/lib-dynload/_elementtree.cpython-39-darwin.so
+
+ cdhash
+
+ 0dugyk0WwgJMks+nXIpS1aHFuGM=
+
+ requirement
+ cdhash H"d1dba0ca4d16c2024c92cfa75c8a52d5a1c5b863" or cdhash H"de40ca6ffbb781789ea1b0f1528fb2b9a9eb5855"
+
+ Frameworks/lib-dynload/_hashlib.cpython-39-darwin.so
+
+ cdhash
+
+ aAaavI8EobHZiQgfmNEYFEWY+OQ=
+
+ requirement
+ cdhash H"68069abc8f04a1b1d989081f98d118144598f8e4" or cdhash H"7bac6f125cd3470e25d3665a4431be2d56056f95"
+
+ Frameworks/lib-dynload/_heapq.cpython-39-darwin.so
+
+ cdhash
+
+ vai0kTJOHHi3Qvx862i0m0Uwzc4=
+
+ requirement
+ cdhash H"bda8b491324e1c78b742fc7ceb68b49b4530cdce" or cdhash H"89e5e75c0fb13c134c82ace806d1ab39639f4d72"
+
+ Frameworks/lib-dynload/_json.cpython-39-darwin.so
+
+ cdhash
+
+ HqzDtShZHk1NOH27QuDmdRXakyM=
+
+ requirement
+ cdhash H"1eacc3b528591e4d4d387dbb42e0e67515da9323" or cdhash H"45a021a92652461b993240a5d5220479455d44ba"
+
+ Frameworks/lib-dynload/_lzma.cpython-39-darwin.so
+
+ cdhash
+
+ 2nbfYMpTTT9zvmnJQraF43S4N0s=
+
+ requirement
+ cdhash H"da76df60ca534d3f73be69c942b685e374b8374b" or cdhash H"d0bccfdb9fdaba71e2da94ef5113c99712824895"
+
+ Frameworks/lib-dynload/_md5.cpython-39-darwin.so
+
+ cdhash
+
+ ohGkws9VSZbWjs1/M6/u0Lmml9k=
+
+ requirement
+ cdhash H"a211a4c2cf554996d68ecd7f33afeed0b9a697d9" or cdhash H"19eafc1c27e89475e57d0089bea93c36bdfd38ab"
+
+ Frameworks/lib-dynload/_multibytecodec.cpython-39-darwin.so
+
+ cdhash
+
+ uLkLFlgiauIl4Jat5BxGb0k5PHE=
+
+ requirement
+ cdhash H"b8b90b1658226ae225e096ade41c466f49393c71" or cdhash H"f3160fc36d128183d1ca8e5e917425e4303aac7b"
+
+ Frameworks/lib-dynload/_multiprocessing.cpython-39-darwin.so
+
+ cdhash
+
+ lNE1zHxI/mBAo5WD3jEE4KBxwBo=
+
+ requirement
+ cdhash H"94d135cc7c48fe6040a39583de3104e0a071c01a" or cdhash H"23dc7091dab8051ef0db35de53d5b6aa117b0440"
+
+ Frameworks/lib-dynload/_opcode.cpython-39-darwin.so
+
+ cdhash
+
+ Z97Mk1EwlfMCfJsA6sar75J3YTQ=
+
+ requirement
+ cdhash H"67decc93513095f3027c9b00eac6abef92776134" or cdhash H"ac877e15f69c0eed15703528635ff6a081273070"
+
+ Frameworks/lib-dynload/_pickle.cpython-39-darwin.so
+
+ cdhash
+
+ h0/ZQ+6sqhWE3kx7NNQsPmWJ2x4=
+
+ requirement
+ cdhash H"874fd943eeacaa1584de4c7b34d42c3e6589db1e" or cdhash H"d53efc9b0be5ede9099e1c64024a426928989779"
+
+ Frameworks/lib-dynload/_posixshmem.cpython-39-darwin.so
+
+ cdhash
+
+ ZOsqbI5Q2/1npA+zAcY5+LNNLc0=
+
+ requirement
+ cdhash H"64eb2a6c8e50dbfd67a40fb301c639f8b34d2dcd" or cdhash H"0ec8e3e44da958e61e49bebc055effdc9cbf4e16"
+
+ Frameworks/lib-dynload/_posixsubprocess.cpython-39-darwin.so
+
+ cdhash
+
+ JHCDO4+fTFZ7VhhFv+cATVpoSkk=
+
+ requirement
+ cdhash H"2470833b8f9f4c567b561845bfe7004d5a684a49" or cdhash H"7358e9ce52709b6935c63ac1e9cb02b55d193744"
+
+ Frameworks/lib-dynload/_queue.cpython-39-darwin.so
+
+ cdhash
+
+ Ky/WQ3rZc1rFd81dZuuqjM4mpZ8=
+
+ requirement
+ cdhash H"2b2fd6437ad9735ac577cd5d66ebaa8cce26a59f" or cdhash H"1843e69ca89fb103916bf06b153ea74c23cfa8ab"
+
+ Frameworks/lib-dynload/_random.cpython-39-darwin.so
+
+ cdhash
+
+ r1zSinh8a7kx3Lcs3TgvpfmCHFc=
+
+ requirement
+ cdhash H"af5cd28a787c6bb931dcb72cdd382fa5f9821c57" or cdhash H"d45d2ccf51ed85317fc05afb7a4f4f2edd44a712"
+
+ Frameworks/lib-dynload/_scproxy.cpython-39-darwin.so
+
+ cdhash
+
+ U1TcevlVPlk2plDHFR+6iQQxwjc=
+
+ requirement
+ cdhash H"5354dc7af9553e5936a650c7151fba890431c237" or cdhash H"e7576c55d98f3c41fc531c6a60fdaee1cf7fbc86"
+
+ Frameworks/lib-dynload/_sha1.cpython-39-darwin.so
+
+ cdhash
+
+ 0TramRsorzYG34Iz3tZgjp6oVnM=
+
+ requirement
+ cdhash H"d13ada991b28af3606df8233ded6608e9ea85673" or cdhash H"7a4df8dabb50c48635500f3ee1d6e8a555f69e7a"
+
+ Frameworks/lib-dynload/_sha256.cpython-39-darwin.so
+
+ cdhash
+
+ dpMZWJukXILGygxNQVcXKexMtSQ=
+
+ requirement
+ cdhash H"769319589ba45c82c6ca0c4d41571729ec4cb524" or cdhash H"0ff84306718c6bcc60d82c8a82f00f4225025f1f"
+
+ Frameworks/lib-dynload/_sha3.cpython-39-darwin.so
+
+ cdhash
+
+ vHkuRkyfb+8/sKw4HLmA5fFVmgE=
+
+ requirement
+ cdhash H"bc792e464c9f6fef3fb0ac381cb980e5f1559a01" or cdhash H"40855da093a965c64da161556c2a977f630d7447"
+
+ Frameworks/lib-dynload/_sha512.cpython-39-darwin.so
+
+ cdhash
+
+ 8I9uhNUfFCx8ur4WuTB4SlozTNk=
+
+ requirement
+ cdhash H"f08f6e84d51f142c7cbabe16b930784a5a334cd9" or cdhash H"c20f657714e6c8a1b90ffff2443a90959ebc24bc"
+
+ Frameworks/lib-dynload/_socket.cpython-39-darwin.so
+
+ cdhash
+
+ I9fuMJ+c6cUxQTHuH4vkdoBXa40=
+
+ requirement
+ cdhash H"23d7ee309f9ce9c5314131ee1f8be47680576b8d" or cdhash H"2a3fd65273df79359339f4162415a2b0de13f287"
+
+ Frameworks/lib-dynload/_ssl.cpython-39-darwin.so
+
+ cdhash
+
+ bp0XF+1zxD8r1rmJaiEFaqpXWiQ=
+
+ requirement
+ cdhash H"6e9d1717ed73c43f2bd6b9896a21056aaa575a24" or cdhash H"de801101c7eaa0ff55a0a9869a7aa0140a436d1c"
+
+ Frameworks/lib-dynload/_statistics.cpython-39-darwin.so
+
+ cdhash
+
+ iaY9HpIu9FQSam1hhJPrdnsKX98=
+
+ requirement
+ cdhash H"89a63d1e922ef454126a6d618493eb767b0a5fdf" or cdhash H"f3b824e452002e4c404ed48c4e7ee7c9fcb5c09e"
+
+ Frameworks/lib-dynload/_struct.cpython-39-darwin.so
+
+ cdhash
+
+ FRFxs95aZea0hO6mJJNaXhub4Dk=
+
+ requirement
+ cdhash H"151171b3de5a65e6b484eea624935a5e1b9be039" or cdhash H"f547364b1a07b374df50d10b0a361599c6253749"
+
+ Frameworks/lib-dynload/_tkinter.cpython-39-darwin.so
+
+ cdhash
+
+ 3XGOIgmpj7KoMhgpGIcMtU8TscA=
+
+ requirement
+ cdhash H"dd718e2209a98fb2a832182918870cb54f13b1c0" or cdhash H"a2a90f463f03ca1092ec3aa5cdebba7d8af09306"
+
+ Frameworks/lib-dynload/_uuid.cpython-39-darwin.so
+
+ cdhash
+
+ h2665Ntzbx+Zwzz6GWBXRwFsm8c=
+
+ requirement
+ cdhash H"876ebae4db736f1f99c33cfa19605747016c9bc7" or cdhash H"7de2b7ac3513c3574309648cfb3452889be7da06"
+
+ Frameworks/lib-dynload/array.cpython-39-darwin.so
+
+ cdhash
+
+ JbBQcXUAaomv7RDl0UENGApYAEM=
+
+ requirement
+ cdhash H"25b0507175006a89afed10e5d1410d180a580043" or cdhash H"0cd21f5b43de92a3bfde18227e252bce72569c81"
+
+ Frameworks/lib-dynload/binascii.cpython-39-darwin.so
+
+ cdhash
+
+ ZvSOrQosvU5eg7peHGsMacgsSak=
+
+ requirement
+ cdhash H"66f48ead0a2cbd4e5e83ba5e1c6b0c69c82c49a9" or cdhash H"9d73c6517777b52f99b31f698058f71080fe454f"
+
+ Frameworks/lib-dynload/grp.cpython-39-darwin.so
+
+ cdhash
+
+ VKUV6bR1VL/lEqLy+UHOyBJvHgU=
+
+ requirement
+ cdhash H"54a515e9b47554bfe512a2f2f941cec8126f1e05" or cdhash H"24c840ac32f9dc687b8392acff24c51d1d048f3a"
+
+ Frameworks/lib-dynload/math.cpython-39-darwin.so
+
+ cdhash
+
+ GpgGIcQKTqXGcgNO2CrUT5MHFmI=
+
+ requirement
+ cdhash H"1a980621c40a4ea5c672034ed82ad44f93071662" or cdhash H"f03ad850cf05e16f687c024d9fb87a3f52f3a5d5"
+
+ Frameworks/lib-dynload/mmap.cpython-39-darwin.so
+
+ cdhash
+
+ SEKTtXjOn8Ksq2I5Fwn8kCxk+EI=
+
+ requirement
+ cdhash H"484293b578ce9fc2acab62391709fc902c64f842" or cdhash H"b90ad4f2148f34f8eba4853ef3e9e23bddb348cc"
+
+ Frameworks/lib-dynload/pyexpat.cpython-39-darwin.so
+
+ cdhash
+
+ Y+Qsj7aPs22FfWADEEUPkxrzzdA=
+
+ requirement
+ cdhash H"63e42c8fb68fb36d857d600310450f931af3cdd0" or cdhash H"201764723b943121591840cfd4779ee5dbd0446c"
+
+ Frameworks/lib-dynload/readline.cpython-39-darwin.so
+
+ cdhash
+
+ cOLo4TvZjqROsuo8YbAlWnmQlJg=
+
+ requirement
+ cdhash H"70e2e8e13bd98ea44eb2ea3c61b0255a79909498" or cdhash H"c3bc9a5e71bc8dcde1ec034aae5fb3e07cc0c289"
+
+ Frameworks/lib-dynload/resource.cpython-39-darwin.so
+
+ cdhash
+
+ +pZgjt77rZBllJ1xUNPli/EhTqA=
+
+ requirement
+ cdhash H"fa96608edefbad9065949d7150d3e58bf1214ea0" or cdhash H"96e88534284df1ab73865cab181a103803f26c2c"
+
+ Frameworks/lib-dynload/select.cpython-39-darwin.so
+
+ cdhash
+
+ P9v1b6A3szM2nAazDR1dIQ+3YvY=
+
+ requirement
+ cdhash H"3fdbf56fa037b333369c06b30d1d5d210fb762f6" or cdhash H"a65a8f110d83fa06337930a0498743b587a7b86b"
+
+ Frameworks/lib-dynload/termios.cpython-39-darwin.so
+
+ cdhash
+
+ B/Nbtyn5P9IhKoxNKuZAz7G3nog=
+
+ requirement
+ cdhash H"07f35bb729f93fd2212a8c4d2ae640cfb1b79e88" or cdhash H"03779e0e9b9e8cb63a4693f6897a6b473de848f9"
+
+ Frameworks/lib-dynload/unicodedata.cpython-39-darwin.so
+
+ cdhash
+
+ Py1DCLkHoeDqner34FJzggGxjzI=
+
+ requirement
+ cdhash H"3f2d4308b907a1e0ea9deaf7e052738201b18f32" or cdhash H"609bd5153c0d763834e578a3b319c930f7adf7fb"
+
+ Frameworks/lib-dynload/zlib.cpython-39-darwin.so
+
+ cdhash
+
+ WI4BJgHYdSAt4YiEszMVfQ90Pzo=
+
+ requirement
+ cdhash H"588e012601d875202de18884b333157d0f743f3a" or cdhash H"8b103f261da47d83f97c03f23bdf2affd7190331"
+
+ Frameworks/libXau.6.dylib
+
+ cdhash
+
+ PclUsctkOvnkqKjX8L6kTeELxq8=
+
+ requirement
+ cdhash H"3dc954b1cb643af9e4a8a8d7f0bea44de10bc6af"
+
+ Frameworks/libXdmcp.6.dylib
+
+ cdhash
+
+ R53Z845f2U7adWRJE9/vcGioCBI=
+
+ requirement
+ cdhash H"479dd9f38e5fd94eda75644913dfef7068a80812"
+
+ Frameworks/libfreetype.6.dylib
+
+ cdhash
+
+ 4Fc0H8sxKRG+WsxISrkPtbAkqLU=
+
+ requirement
+ cdhash H"e057341fcb312911be5acc484ab90fb5b024a8b5"
+
+ Frameworks/libjpeg.8.dylib
+
+ cdhash
+
+ 5/BYt6/hFCa1Hm71mqIL6PDnpvw=
+
+ requirement
+ cdhash H"e7f058b7afe11426b51e6ef59aa20be8f0e7a6fc"
+
+ Frameworks/liblcms2.2.dylib
+
+ cdhash
+
+ 22adDDFYLuiLk9ULqRhonnJyymw=
+
+ requirement
+ cdhash H"db669d0c31582ee88b93d50ba918689e7272ca6c"
+
+ Frameworks/liblzma.5.dylib
+
+ cdhash
+
+ 0xg+ZWAz5xzUdbaOi37PSxfJA90=
+
+ requirement
+ cdhash H"d3183e656033e71cd475b68e8b7ecf4b17c903dd"
+
+ Frameworks/libopenjp2.7.dylib
+
+ cdhash
+
+ nCJRk040f/assS9JxOvSRxfc1Hg=
+
+ requirement
+ cdhash H"9c2251934e347ff6acb12f49c4ebd24717dcd478"
+
+ Frameworks/libpng16.16.dylib
+
+ cdhash
+
+ 6nZ9HKOx5HF/rzoVJU8R+JRJ6Dw=
+
+ requirement
+ cdhash H"ea767d1ca3b1e4717faf3a15254f11f89449e83c"
+
+ Frameworks/libsharpyuv.0.dylib
+
+ cdhash
+
+ U/PkocmUjE04gLvTGRnhlZJbk4s=
+
+ requirement
+ cdhash H"53f3e4a1c9948c4d3880bbd31919e195925b938b"
+
+ Frameworks/libtiff.6.dylib
+
+ cdhash
+
+ 7YsFtFOawNFvGRnr+6Lvkt1PNmU=
+
+ requirement
+ cdhash H"ed8b05b4539ac0d16f1919ebfba2ef92dd4f3665"
+
+ Frameworks/libwebp.7.dylib
+
+ cdhash
+
+ fwDC48Y6jXx7sfjlH6PYEwtyB/k=
+
+ requirement
+ cdhash H"7f00c2e3c63a8d7c7bb1f8e51fa3d8130b7207f9"
+
+ Frameworks/libwebpdemux.2.dylib
+
+ cdhash
+
+ VV9Oaa1bUNPI8Xj1StEiwhVv9aA=
+
+ requirement
+ cdhash H"555f4e69ad5b50d3c8f178f54ad122c2156ff5a0"
+
+ Frameworks/libwebpmux.3.dylib
+
+ cdhash
+
+ aZs2HtqDK2JTGLI+QAuV1E7K8VQ=
+
+ requirement
+ cdhash H"699b361eda832b625318b23e400b95d44ecaf154"
+
+ Frameworks/libxcb.1.dylib
+
+ cdhash
+
+ jtOg8oxTtaI2xhTzLL/d+xC+9fQ=
+
+ requirement
+ cdhash H"8ed3a0f28c53b5a236c614f32cbfddfb10bef5f4"
+
+ Frameworks/libzstd.1.dylib
+
+ cdhash
+
+ QIRT7hhG5sASsJBa4K0IneMKKu8=
+
+ requirement
+ cdhash H"408453ee1846e6c012b0905ae0ad089de30a2aef"
+
+ Frameworks/objc/_machsignals.cpython-39-darwin.so
+
+ cdhash
+
+ rYychktquqfTfZEE7zxMwacxLec=
+
+ requirement
+ cdhash H"b90b3325e2032b493f1f757a6045083b3b819511" or cdhash H"ad8c9c864b6abaa7d37d9104ef3c4cc1a7312de7" or cdhash H"ae7404b025253267fe29f6bc1e060d393f8483d8" or cdhash H"3a7e929ffb4dc1ef0955cc8c4bd2dc7365b28a82"
+
+ Frameworks/objc/_objc.cpython-39-darwin.so
+
+ cdhash
+
+ lt/i/HnS+oGRfdzbZw8jiF7EAqM=
+
+ requirement
+ cdhash H"36e2cc390c883f7d4e6026e6fe3bd521c654d276" or cdhash H"96dfe2fc79d2fa81917ddcdb670f23885ec402a3" or cdhash H"c9c432c75f9042df12694a9909303b7e9d0a2168" or cdhash H"8dc62fa74418faa2c2f4387d054cd95ef3624cb6"
+
+ Frameworks/psutil/_psutil_osx.abi3.so
+
+ cdhash
+
+ ZNtF72qbtpEYM2EnChSos8JatS4=
+
+ requirement
+ cdhash H"64db45ef6a9bb691183361270a14a8b3c25ab52e" or cdhash H"91b1d15c3a6c55200335b2ac11e953e2cb16e3c6"
+
+ Frameworks/psutil/_psutil_posix.abi3.so
+
+ cdhash
+
+ aJ/W+BJ1wwYFBwdv79ebvc4CEYo=
+
+ requirement
+ cdhash H"689fd6f81275c3060507076fefd79bbdce02118a" or cdhash H"b7e294ab502aa8233b2811cfcacd1d3236275b0a"
+
+ Resources/AppKit
+
+ symlink
+ ../Frameworks/AppKit
+
+ Resources/CoreFoundation
+
+ symlink
+ ../Frameworks/CoreFoundation
+
+ Resources/Foundation
+
+ symlink
+ ../Frameworks/Foundation
+
+ Resources/PIL
+
+ symlink
+ ../Frameworks/PIL
+
+ Resources/Python3
+
+ symlink
+ Python3.framework/Versions/3.9/Python3
+
+ Resources/Python3.framework
+
+ symlink
+ ../Frameworks/Python3.framework
+
+ Resources/TgWsProxy.icns
+
+ hash2
+
+ E3bWeBZq9Io+vxfqBRk71ONht2ak38E7Dq69nxWFH3Q=
+
+
+ Resources/_cffi_backend.cpython-39-darwin.so
+
+ symlink
+ ../Frameworks/_cffi_backend.cpython-39-darwin.so
+
+ Resources/base_library.zip
+
+ hash2
+
+ Jl6CW8Xe7/HJelEx1TkX1bIUpBikdr2XRgqlYmNeHCU=
+
+
+ Resources/cryptography
+
+ symlink
+ ../Frameworks/cryptography
+
+ Resources/cryptography-46.0.5.dist-info/INSTALLER
+
+ hash2
+
+ zuuue4knoyJ+UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg=
+
+
+ Resources/cryptography-46.0.5.dist-info/METADATA
+
+ hash2
+
+ aOYB9/B+Ccske76ncMz+w9c/VnzYihv/7kxZlt2i2WQ=
+
+
+ Resources/cryptography-46.0.5.dist-info/RECORD
+
+ hash2
+
+ CgxHeapkfFm4ahu+oNdpkI1XPeSpd7E04zZXkB40CC8=
+
+
+ Resources/cryptography-46.0.5.dist-info/REQUESTED
+
+ hash2
+
+ 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=
+
+
+ Resources/cryptography-46.0.5.dist-info/WHEEL
+
+ hash2
+
+ fPMWsH0x0SkVhDADhT1/1cBQw9LWEszkPTNMfqIZV3E=
+
+
+ Resources/cryptography-46.0.5.dist-info/licenses/LICENSE
+
+ hash2
+
+ Pgx8CRqUi4JTO6mP18u0BDLW8amsv4X1ki0vmak65rs=
+
+
+ Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.APACHE
+
+ hash2
+
+ qsc7MUj20dcRHbyjIJn2jSbGRMaBOuHk8F9leaomY/4=
+
+
+ Resources/cryptography-46.0.5.dist-info/licenses/LICENSE.BSD
+
+ hash2
+
+ YCxMdILeZHndLpeTzaJ15eY9dz2s0eymiSMqtwCPtPs=
+
+
+ Resources/customtkinter/__init__.py
+
+ hash2
+
+ /eLjpybhQB49OORUMIU+op8w0/W4Gxus5AQi3yzQcjg=
+
+
+ Resources/customtkinter/assets/fonts/CustomTkinter_shapes_font.otf
+
+ hash2
+
+ +tZ+KwYMMYtshkbQh/vTrdk4tmdiQ/FLDFJiMXlkEnQ=
+
+
+ Resources/customtkinter/assets/fonts/Roboto/Roboto-Medium.ttf
+
+ hash2
+
+ SsjgNgb/pMN/YaZRCiCA8fN6cFT0cmwhSIfTsj9y42k=
+
+
+ Resources/customtkinter/assets/fonts/Roboto/Roboto-Regular.ttf
+
+ hash2
+
+ MZz/bnox8PKkHEddykKJCqXRn+FgF+IpD4wdThT3ZIE=
+
+
+ Resources/customtkinter/assets/icons/CustomTkinter_icon_Windows.ico
+
+ hash2
+
+ EjTAF8hx6y4g029mj5PgZs3Lk9tGTVzvnXpb+DUG0ow=
+
+
+ Resources/customtkinter/assets/themes/blue.json
+
+ hash2
+
+ yUF0cMFs7XpD1sSo4Cevpu3GLCTVrufEwtzRE4WWTTs=
+
+
+ Resources/customtkinter/assets/themes/dark-blue.json
+
+ hash2
+
+ alf6b4+4lhowzmQpUisYDXbjr5uODarCWQWYQThqa9M=
+
+
+ Resources/customtkinter/assets/themes/green.json
+
+ hash2
+
+ ea2Gv+p/BVesHiCAKJKrtEqWevFbkxWwA5zXXItyp3Y=
+
+
+ Resources/customtkinter/windows/__init__.py
+
+ hash2
+
+ eSoo0x/4XkSok/BaQbtM+AtwMRu1za6MPhd6YfWz6sI=
+
+
+ Resources/customtkinter/windows/ctk_input_dialog.py
+
+ hash2
+
+ /mAWVEBSPiuBgISbXaWE8LG5Fs7hp0KOM+BmVfBwnp0=
+
+
+ Resources/customtkinter/windows/ctk_tk.py
+
+ hash2
+
+ pvYD7mP9SXdVKIomRbNKt0Zu2pFsIOf3246DqPFz1Pg=
+
+
+ Resources/customtkinter/windows/ctk_toplevel.py
+
+ hash2
+
+ jEaJpXnAXW6wsdeh3Tr9oABk39GSuk0Z/7OgG8F9Nf8=
+
+
+ Resources/customtkinter/windows/widgets/__init__.py
+
+ hash2
+
+ DNohOpF5R2Ae1iP4wNpsDYkOnMRiVrcNdwCPWIxNWGI=
+
+
+ Resources/customtkinter/windows/widgets/appearance_mode/__init__.py
+
+ hash2
+
+ fKKK0fVUHFiqmSZw0GZFY0GKgJGDs8A5OXvSBEC3qSg=
+
+
+ Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_base_class.py
+
+ hash2
+
+ nTXJC4vpwjrgCiaOv3V20xVvtkxKR9pVJykIkhWKFz8=
+
+
+ Resources/customtkinter/windows/widgets/appearance_mode/appearance_mode_tracker.py
+
+ hash2
+
+ L+X+eLxWFhenWtHr7Z/Mp5msaBTMEIxc+hhKrIVn7bc=
+
+
+ Resources/customtkinter/windows/widgets/core_rendering/__init__.py
+
+ hash2
+
+ 212WNYQ90/4YY/QB6x0E+xCqqmUXsWegNPQ4VeXSKO0=
+
+
+ Resources/customtkinter/windows/widgets/core_rendering/ctk_canvas.py
+
+ hash2
+
+ uXHYqdTns1LAp2GYadyBDcKt5PpNUHka/7l4aV1jqqg=
+
+
+ Resources/customtkinter/windows/widgets/core_rendering/draw_engine.py
+
+ hash2
+
+ eeXpsYiCDOqUQo5Pz5nBHDQqg99drt4QDjODmrdee8w=
+
+
+ Resources/customtkinter/windows/widgets/core_widget_classes/__init__.py
+
+ hash2
+
+ DsG3zo4hzAUmdFbt3aXgen0Vs0Y9+ppycKTIfrHTYMA=
+
+
+ Resources/customtkinter/windows/widgets/core_widget_classes/ctk_base_class.py
+
+ hash2
+
+ PTxPeqvfnct+wuXBqodbGDxpfXJ67Cal201qJxCt3EE=
+
+
+ Resources/customtkinter/windows/widgets/core_widget_classes/dropdown_menu.py
+
+ hash2
+
+ dC/SEWk/XQbX47NjLDyhsKh8YKNyPFndoIIxqAnOGRE=
+
+
+ Resources/customtkinter/windows/widgets/ctk_button.py
+
+ hash2
+
+ TE/6T88cL8ae1p8ZOy/WE4inpxjP0iaXej0qmjcjbCU=
+
+
+ Resources/customtkinter/windows/widgets/ctk_checkbox.py
+
+ hash2
+
+ h8w7SG/9oWE4x4/iBQ+qXvxTq1J/o0Dj4C6JZAqXANU=
+
+
+ Resources/customtkinter/windows/widgets/ctk_combobox.py
+
+ hash2
+
+ p1dYS2r9nq7BFfidIbt9JCaZRB8XtjV4XHUV8nGpdGo=
+
+
+ Resources/customtkinter/windows/widgets/ctk_entry.py
+
+ hash2
+
+ bxhcZAQt81S59UA5v9fJtsXLLHf9NuUy4qDh4K2m+Ec=
+
+
+ Resources/customtkinter/windows/widgets/ctk_frame.py
+
+ hash2
+
+ hcyGh8OzB2CzwCb9c4DMC29QqJS9awzNzEZZmzv5Usc=
+
+
+ Resources/customtkinter/windows/widgets/ctk_label.py
+
+ hash2
+
+ 4WDRL/gbctV9t+SIKp1oowbvXB+x3CN5o5yajrOogxE=
+
+
+ Resources/customtkinter/windows/widgets/ctk_optionmenu.py
+
+ hash2
+
+ 5g2OzI6mexzGZxmrUStsyuG+MiihAr+UhyzVS5y5vko=
+
+
+ Resources/customtkinter/windows/widgets/ctk_progressbar.py
+
+ hash2
+
+ k8aa/RUIVb+gNUsOUxIHAzBkw8yDgZuU0ar5JeRp4EA=
+
+
+ Resources/customtkinter/windows/widgets/ctk_radiobutton.py
+
+ hash2
+
+ hEY0uDuStmkizlpkP8QsfLXRgRNexaj/JPM8/2Iyf3Q=
+
+
+ Resources/customtkinter/windows/widgets/ctk_scrollable_frame.py
+
+ hash2
+
+ wKJI1U36m5FhDjIcuDoYPWbTQbLDtusBXu3c1UYQOzk=
+
+
+ Resources/customtkinter/windows/widgets/ctk_scrollbar.py
+
+ hash2
+
+ cO6NwExutdSXUBSRdbVve9b0dvV9djUs9Pxetqfxfn8=
+
+
+ Resources/customtkinter/windows/widgets/ctk_segmented_button.py
+
+ hash2
+
+ zKPj1J/ZbEXbt5en7vHdZhooqNIrDkr3DKo+QEQ3rvc=
+
+
+ Resources/customtkinter/windows/widgets/ctk_slider.py
+
+ hash2
+
+ 1xY9vnha+djmym0iLpBkVK30aCDw92lqTMOgZiJ4Hhk=
+
+
+ Resources/customtkinter/windows/widgets/ctk_switch.py
+
+ hash2
+
+ aOscR68hoHVkmugnWMqTlsKlLZgGCiO1rfXkDOQSC0Y=
+
+
+ Resources/customtkinter/windows/widgets/ctk_tabview.py
+
+ hash2
+
+ ieGuEQqUH7miPQWIkSyon7KVWdbqFFKW5k7oBv53jRg=
+
+
+ Resources/customtkinter/windows/widgets/ctk_textbox.py
+
+ hash2
+
+ l+XEQQYVKMB7cTEk4h150b2s+o2HpRh7zHROVc9H3bA=
+
+
+ Resources/customtkinter/windows/widgets/font/__init__.py
+
+ hash2
+
+ zYlSsJH+8rL77t7dkz0vXOofHh8NdiIuMQK4V8mfINM=
+
+
+ Resources/customtkinter/windows/widgets/font/ctk_font.py
+
+ hash2
+
+ 5cdHvujB/tfDlsw9ZRMiGBASvmRSg/WIbSF99x/21vA=
+
+
+ Resources/customtkinter/windows/widgets/font/font_manager.py
+
+ hash2
+
+ jwpu61Z00noq0UWcFaHftAMRvXgmKQHEmQiDq+CrqNw=
+
+
+ Resources/customtkinter/windows/widgets/image/__init__.py
+
+ hash2
+
+ pzbwlIAV4OE+CkdxJIrl1ELcRg7vbQKkC6HaHLGDNI8=
+
+
+ Resources/customtkinter/windows/widgets/image/ctk_image.py
+
+ hash2
+
+ uL/oJrlq8Z134qjiii44Lg03utETSf7rBJ6kgPHeYzk=
+
+
+ Resources/customtkinter/windows/widgets/scaling/__init__.py
+
+ hash2
+
+ 2GjeG2/A4gq8g7pROKPw6QR2mIH7b6iRDQvT26qz8R4=
+
+
+ Resources/customtkinter/windows/widgets/scaling/scaling_base_class.py
+
+ hash2
+
+ VbzzA9GPujOYbEJjDsQRnvoY6v7RSitZwr37lZAkk/8=
+
+
+ Resources/customtkinter/windows/widgets/scaling/scaling_tracker.py
+
+ hash2
+
+ cuilXEeSTOylgDRNf2kh+eDQShup+i2Nc7oOcLo3Vtw=
+
+
+ Resources/customtkinter/windows/widgets/theme/__init__.py
+
+ hash2
+
+ iZ6T6O+v39hOLfTLuNbTLt0XA/raCYtxwGnmqNB2Lmk=
+
+
+ Resources/customtkinter/windows/widgets/theme/theme_manager.py
+
+ hash2
+
+ E+VtzWKZ2IXrc/hvfGUdj50GNr8eII7S/fWBk58xix0=
+
+
+ Resources/customtkinter/windows/widgets/utility/__init__.py
+
+ hash2
+
+ STsgOQvThAoUarV9ovM6M40araQUDNU/QmmHbyMaRx0=
+
+
+ Resources/customtkinter/windows/widgets/utility/utility_functions.py
+
+ hash2
+
+ d0Wj5Aioxhb3qtC0ITuNZHjwfFHegUfK+bfkDXrrufk=
+
+
+ Resources/icon.ico
+
+ hash2
+
+ nRj69m3HMM8F/ysB78uKafDFhcHOyUCM8iLcjhmNERw=
+
+
+ Resources/lib-dynload
+
+ symlink
+ ../Frameworks/lib-dynload
+
+ Resources/libXau.6.dylib
+
+ symlink
+ ../Frameworks/libXau.6.dylib
+
+ Resources/libXdmcp.6.dylib
+
+ symlink
+ ../Frameworks/libXdmcp.6.dylib
+
+ Resources/libfreetype.6.dylib
+
+ symlink
+ ../Frameworks/libfreetype.6.dylib
+
+ Resources/libjpeg.8.dylib
+
+ symlink
+ ../Frameworks/libjpeg.8.dylib
+
+ Resources/liblcms2.2.dylib
+
+ symlink
+ ../Frameworks/liblcms2.2.dylib
+
+ Resources/liblzma.5.dylib
+
+ symlink
+ ../Frameworks/liblzma.5.dylib
+
+ Resources/libopenjp2.7.dylib
+
+ symlink
+ ../Frameworks/libopenjp2.7.dylib
+
+ Resources/libpng16.16.dylib
+
+ symlink
+ ../Frameworks/libpng16.16.dylib
+
+ Resources/libsharpyuv.0.dylib
+
+ symlink
+ ../Frameworks/libsharpyuv.0.dylib
+
+ Resources/libtiff.6.dylib
+
+ symlink
+ ../Frameworks/libtiff.6.dylib
+
+ Resources/libwebp.7.dylib
+
+ symlink
+ ../Frameworks/libwebp.7.dylib
+
+ Resources/libwebpdemux.2.dylib
+
+ symlink
+ ../Frameworks/libwebpdemux.2.dylib
+
+ Resources/libwebpmux.3.dylib
+
+ symlink
+ ../Frameworks/libwebpmux.3.dylib
+
+ Resources/libxcb.1.dylib
+
+ symlink
+ ../Frameworks/libxcb.1.dylib
+
+ Resources/libzstd.1.dylib
+
+ symlink
+ ../Frameworks/libzstd.1.dylib
+
+ Resources/objc
+
+ symlink
+ ../Frameworks/objc
+
+ Resources/psutil
+
+ symlink
+ ../Frameworks/psutil
+
+
+ rules
+
+ ^Resources/
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^version.plist$
+
+
+ rules2
+
+ .*\.dSYM($|/)
+
+ weight
+ 11
+
+ ^(.*/)?\.DS_Store$
+
+ omit
+
+ weight
+ 2000
+
+ ^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/
+
+ nested
+
+ weight
+ 10
+
+ ^.*
+
+ ^Info\.plist$
+
+ omit
+
+ weight
+ 20
+
+ ^PkgInfo$
+
+ omit
+
+ weight
+ 20
+
+ ^Resources/
+
+ weight
+ 20
+
+ ^Resources/.*\.lproj/
+
+ optional
+
+ weight
+ 1000
+
+ ^Resources/.*\.lproj/locversion.plist$
+
+ omit
+
+ weight
+ 1100
+
+ ^Resources/Base\.lproj/
+
+ weight
+ 1010
+
+ ^[^/]+$
+
+ nested
+
+ weight
+ 10
+
+ ^embedded\.provisionprofile$
+
+ weight
+ 20
+
+ ^version\.plist$
+
+ weight
+ 20
+
+
+
+