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 + + + +