466 Commits
Author SHA1 Message Date
YoursFunny 89716a498d fix: update configs and asset files 2026-10-05 22:47:30 +08:00
YoursFunny e0a1ac0b13 fix(bounty): add 2 new options (road, mansion) and ui assets 2026-10-05 22:46:10 +08:00
YoursFunny 38ac1f3d7e fix: update assets file 2026-08-18 23:05:13 +08:00
YoursFunny 396d57ddf2 fix: update assets for zht 2026-08-18 23:04:24 +08:00
YoursFunny 78bc677469 fix: update assets for en and combine shared assets 2026-08-18 23:04:06 +08:00
YoursFunny f69b390f08 fix: update assets for jp 2026-08-18 23:04:05 +08:00
YoursFunny 79126c891a fix: disable close_popup() for jp to fix ensure_page() 2026-08-05 17:41:16 +08:00
YoursFunny acddb456ff fix: update assets file 2026-08-05 10:40:47 +08:00
YoursFunny 61f862c665 fix: update assets for jp 2026-08-05 10:40:18 +08:00
YoursFunny b6ebd8481f fix: change main page check 2026-08-05 09:58:58 +08:00
YoursFunny 4233f13994 fix: update assets file 2026-07-13 16:14:59 +08:00
YoursFunny 47c773b90c fix(momotalk): update ui assets for all servers 2026-07-13 16:14:40 +08:00
YoursFunny ecc747ff94 fix: pip upgrade using mirror 2026-07-06 23:35:27 +08:00
YoursFunny f04f38ec07 fix: remove set_repo() to fix in-app update 2026-07-06 23:34:29 +08:00
YoursFunny 98088eeaf0 fix: revert to en model in ocr for stability 2026-07-06 23:18:44 +08:00
YoursFunny e1c9c16bcb Merge pull request #38 from TheFunny/sync_src
Sync with src
2026-07-06 22:46:55 +08:00
YoursFunny 830c7bfaa2 fixup! dep: update to pywebio 1.8.3 2026-07-06 22:41:04 +08:00
YoursFunny 04f8b2fdc5 fix: add external_renderer_ipc.dll path for mumu12 6.0 2026-07-06 22:41:04 +08:00
YoursFunny 1ff838bdfa fix: support get MuMuPlayer12_id from mumu15 2026-07-06 22:41:04 +08:00
YoursFunny 292d1d1e3a fix: solve MuMuPlayer12 failing to launch when starting multiple instances concurrently 2026-07-06 22:41:04 +08:00
YoursFunny ad186c5d34 fix: enhance warning handling for AMD GPU driver when running redroid on minimal linux system 2026-07-06 22:41:04 +08:00
YoursFunny 6c007c0e35 feat: backport removeprefix and removesuffix functions for compatibility with Python <3.9 2026-07-06 22:41:03 +08:00
YoursFunny 7e0f33be23 fix: handle another format of multiscreen warning 2026-07-06 22:41:03 +08:00
YoursFunny f905524fb4 feat: support LDPlayer14 2026-07-06 22:41:03 +08:00
YoursFunny 47cf57771a fix: limit Android SDK version when benchmarking Droidcast 2026-07-06 22:41:03 +08:00
YoursFunny b431ab02d3 fix: use search by emulator type first, which is more trustworthy 2026-07-06 22:41:02 +08:00
YoursFunny bb1df87280 fix: skip permission errors when replacing adb 2026-07-06 22:41:02 +08:00
YoursFunny 58fc1a2be9 feat: enhance Ocr class with matched keyword handling and button generation 2026-07-06 22:41:02 +08:00
YoursFunny 089ffc78f6 feat: add fix_py37_subprocess_communicate for Windows Python 3.7 compatibility 2026-07-06 22:41:02 +08:00
YoursFunny 825f2409b9 fix: handle serial like 5555,16384 2026-07-06 22:41:02 +08:00
YoursFunny bf3cbeaaba feat: add patch_mimetype function for consistent mimetype handling 2026-07-06 22:41:01 +08:00
YoursFunny 0ca581f1e6 fix: add retry on first nemu_ipc connect 2026-07-06 22:41:01 +08:00
YoursFunny 4b10dcc7f3 fix: try to reuse existing server when converting package to server 2026-07-06 22:41:01 +08:00
YoursFunny f3cecca1f8 feat: add timeout support to loop method for enhanced state management 2026-07-06 22:41:01 +08:00
YoursFunny 1bb468c84e feat: add swipe handling to switch logic 2026-07-06 22:41:01 +08:00
YoursFunny 41ac4a69f0 perf: improve RGB565 to RGB888 speed 2026-07-06 22:41:00 +08:00
YoursFunny eaf806e02d fix: handle first screenshot in loop() 2026-07-06 22:41:00 +08:00
YoursFunny d033dd17c7 refactor: enhance Timer class with improved timing methods and access count tracking 2026-07-06 22:41:00 +08:00
YoursFunny 0c6c866142 feat: expose lang and ignore_punctuation in matched_ocr 2026-07-06 22:41:00 +08:00
YoursFunny 9ca70847f9 feat: add Keyword.find_startswith 2026-07-06 22:40:59 +08:00
YoursFunny a76d47b6cf fix: revert local remote in _adb_reverse_transport 2026-07-06 22:40:59 +08:00
YoursFunny 8ddce8d648 feat: add thread-safe singleton and named singleton metaclasses 2026-07-06 22:40:59 +08:00
YoursFunny 97ead3487e fix: implement custom adb reverse transport method to backport fixes 2026-07-06 22:40:59 +08:00
YoursFunny 04083cb309 fix: add fallback of nemu_ipc and ldopengl if not running on windows 2026-07-06 22:40:59 +08:00
YoursFunny 3d32669431 fix: handle emulator restart command by clearing cached properties 2026-07-06 22:40:58 +08:00
YoursFunny eb5e07c9ff fix: ensure BlueStacks Air serial auto increase from 127.0.0.1:5555 + 10*n 2026-07-06 22:40:58 +08:00
YoursFunny 6ad9b29ba1 fix: log warning when emulator instance is not found in check_mumu_bridge_network 2026-07-06 22:40:58 +08:00
YoursFunny 9135b74484 fix: handle copy-pasted serial 5555,16384 to be fool-proof 2026-07-06 22:40:58 +08:00
YoursFunny d7277fb22d fix: handle upside-down emulator in ldopengl screenshot 2026-07-06 22:40:58 +08:00
YoursFunny 7cfd24f042 fix: allow close emulator when free 2026-07-06 22:40:57 +08:00
YoursFunny 58bd699c3f fix: allow gracefully exit of workers when killed 2026-07-06 22:40:57 +08:00
YoursFunny bdc16b19e1 fix: remove warnings from multiscreen devices 2026-07-06 22:40:57 +08:00
YoursFunny ea2de5d17c perf: avoid import lz4 globally in ascreencap.py 2026-07-06 22:40:57 +08:00
YoursFunny 380b1f9e6e fix: handle nemu ipc lib being none 2026-07-06 22:40:56 +08:00
YoursFunny ed6370b9f9 fix: extend exception handling to include JobTimeout 2026-07-06 22:40:56 +08:00
YoursFunny e7b60ba3ea fix: reuse Switch.get() in Switch.appear() 2026-07-06 22:40:56 +08:00
YoursFunny fbc63d0a54 fix: optimize screenshot handling by removing unnecessary copy 2026-07-06 22:40:56 +08:00
YoursFunny 148a39b591 fix: update image flipping to create a writable copy 2026-07-06 22:40:56 +08:00
YoursFunny 2073af147b fix: allow hide as plain dict 2026-07-06 22:40:55 +08:00
YoursFunny 605fde2d7c feat: add SSL support 2026-07-06 22:40:55 +08:00
YoursFunny 593799a0a6 fix: handle connection exceptions gracefully 2026-07-06 22:40:55 +08:00
YoursFunny 56875e46b3 fix: add notice to disable network bridging 2026-07-06 22:40:55 +08:00
YoursFunny bfcbf268fc perf: replace ThreadPoolExecutor with alas threadpool 2026-07-06 22:40:54 +08:00
YoursFunny cedc003f08 fix: match 7555 in vbox config with MuMu instance_id 2026-07-06 22:40:54 +08:00
YoursFunny 3dc24d56e1 feat: support MuMuPlayer12 5.0 2026-07-06 22:40:54 +08:00
YoursFunny 6774c2070b fix: close pillow Image object 2026-07-06 22:40:54 +08:00
YoursFunny 0942a9bba7 fix: handle empty rows from "ldconsole.exe list2" 2026-07-06 22:40:54 +08:00
YoursFunny 3d32cccc7e feat: add state loop methods for screenshots and hierarchy dumping 2026-07-06 22:40:53 +08:00
YoursFunny 30b77d2dbe perf: faster get_bbox() 2026-07-06 22:40:53 +08:00
YoursFunny 9922e25d38 fix: remove render errors on waydroid 2026-07-06 22:40:53 +08:00
YoursFunny 5fbd775b9a fix: hide traceback of notify 2026-07-06 22:40:53 +08:00
YoursFunny fe2998aea6 refactor: improve logic for nemud player version checks in nemu_ipc.py 2026-07-06 22:40:53 +08:00
YoursFunny a2ab36408d refactor: simplify type hints, enhance docstrings, and solve various race conditions in pool.py 2026-07-06 22:40:52 +08:00
YoursFunny 88cc9fbd8f dep: mark dev only dependencies 2026-07-06 22:40:52 +08:00
YoursFunny 6e75e3d19c refactor: update type hint for modify parameter in run_set function 2026-07-06 22:40:52 +08:00
YoursFunny 2f4afcd5e8 feat: add cached_property for OCR initialization in draggable_list 2026-07-06 22:40:52 +08:00
YoursFunny dcd4b8f0f4 fix: handle FileNotFoundError in atomic_rmtree 2026-07-06 22:40:51 +08:00
YoursFunny b854f1f858 perf: improve performance in base/utils/utils.py functions 2026-07-06 22:40:51 +08:00
YoursFunny 576e73b5dc refactor: streamline file operations and enhance atomic read/write functions 2026-07-06 22:40:51 +08:00
YoursFunny ba4e63c401 perf: improve iter_process speed by directly access psutil internal functions 2026-07-06 22:40:51 +08:00
YoursFunny 748fb94562 fix: do not import platform while importing emulator_windows 2026-07-06 22:40:51 +08:00
YoursFunny 86e0f30e58 fix: use site to get site-packages 2026-07-06 22:40:50 +08:00
YoursFunny c275f8cb7e perf: optimize image cropping by avoiding unnecessary copies 2026-07-06 22:40:50 +08:00
YoursFunny 2c4f0bf848 feat: implement rendered state caching and dynamic instance status updates 2026-07-06 22:40:50 +08:00
YoursFunny 744680a93a fix: treat GameNotRunningError as task failure 2026-07-06 22:40:50 +08:00
YoursFunny e560f9fc58 feat: enhance atomic file operations with Windows compatibility and temp file management 2026-07-06 22:40:49 +08:00
YoursFunny 237f42ef72 fix: ensure wait for emulator-* devices appear after reconnect 2026-07-06 22:40:49 +08:00
YoursFunny f9ac1f79f5 fix: handle adb service reset error and prompt client reconnection 2026-07-06 22:40:49 +08:00
YoursFunny 266ceac297 refactor: add app_current() 2026-07-06 22:40:49 +08:00
YoursFunny 003ec83a87 fix: change hierarchy dump compression to false for better compatibility 2026-07-06 22:40:49 +08:00
YoursFunny 9b388c554d feat: add ModuleBase.match_template_luma() 2026-07-06 22:40:48 +08:00
YoursFunny f420d72906 fix: update task handling to properly restart and clear cached properties 2026-07-06 22:40:48 +08:00
YoursFunny f1ea051b85 feat: add support for MuMU Pro emulator detection and connection handling 2026-07-06 22:40:48 +08:00
YoursFunny 6d85dfc7ce perf: replace filelock with threading lock to reduce IO operations 2026-07-06 22:40:48 +08:00
YoursFunny 251b2eada4 perf: reduce deep_get in config_update 2026-07-06 22:40:47 +08:00
YoursFunny e513fdde51 refactor: improve deep methods for better performance 2026-07-06 22:40:47 +08:00
YoursFunny b95a45496b refactor: use atomic file read/write for better performance 2026-07-06 22:40:47 +08:00
YoursFunny 844d390017 fix: ensure screenshot interval is not applied to scrcpy if screenshot_metho_override is true 2026-07-06 22:40:47 +08:00
YoursFunny bbc0d1352d fix: refactor merge_result_button to use keywords for result identification 2026-07-06 22:40:47 +08:00
YoursFunny 4a59ec58bd fix: disable redirect 5555 to MuMu serials on Mac due to being occupied by BlueStacks Air 2026-07-06 22:40:46 +08:00
YoursFunny 9c4e25fd36 fix: use get method for safer config comparison 2026-07-06 22:40:46 +08:00
YoursFunny 2e610bc8ef fix: reduce unnecessary file writes at startup 2026-07-06 22:40:46 +08:00
YoursFunny 2789be852e fix: add missing import 2026-07-06 22:40:46 +08:00
YoursFunny 32da27567c fix: correct threshold default in get_bbox_reversed 2026-07-06 22:40:46 +08:00
YoursFunny 9b078b02d7 fix: enhance subprocess.Popen to prevent emulator termination 2026-07-06 22:40:45 +08:00
YoursFunny df34d2c1fe fix: allow ScreenshotMethod to be configured manually 2026-07-06 22:40:45 +08:00
YoursFunny 5226a9542c fix: prefer MaaTouch over minitouch when both are fastest 2026-07-06 22:40:45 +08:00
YoursFunny 091a35009b feat: add support for BlueStacks Air detection in connection logic 2026-07-06 22:40:45 +08:00
YoursFunny 0f8df69f6b fix: make benchmark available 2026-07-06 22:40:44 +08:00
YoursFunny 3193c7c327 perf: patch aiofiles to reduce thread pool size 2026-07-06 22:40:44 +08:00
YoursFunny dcd4619d63 refactor: update return types in OCR methods to use Keyword instead of OcrResultButton 2026-07-06 22:40:44 +08:00
YoursFunny b9e0f0fc19 fix: mark nemu_ipc and ldopengl unavailable on non-Windows 2026-07-06 22:40:44 +08:00
YoursFunny 5a7428ac9b perf: use match luma template in match_template_color() 2026-07-06 22:40:44 +08:00
YoursFunny 6582754025 fix: extend nemu_ipc timeout on retries dynamically for slow PCs 2026-07-06 22:40:43 +08:00
YoursFunny 1e152c4641 refactor: use trio-like thread pool in nemu_ipc instead of asyncio.run_in_executor() 2026-07-06 22:40:43 +08:00
YoursFunny 2d745d41b6 fix: fallback to auto if nemu_ipc and ldopengl are selected on non-corresponding emulators 2026-07-06 22:40:43 +08:00
YoursFunny bebb68d3b2 fix: ensure handlers wait for unknown first 2026-07-06 22:40:43 +08:00
YoursFunny 21a1088ea4 fix: remove unnecessary return statement in switch logic 2026-07-06 22:40:42 +08:00
YoursFunny 1799547606 dep: lock deps version 2026-07-06 22:40:42 +08:00
YoursFunny d83ce3bdc3 feat: add error postprocessing method and invoke on exceptions 2026-07-06 22:40:42 +08:00
YoursFunny 892fabe501 fix: add wait for list end after swiping 2026-07-06 22:40:42 +08:00
YoursFunny fbf2970f88 fix: add missing wait after down event 2026-07-06 22:40:42 +08:00
YoursFunny cf52a2fc2d feat: save error log on ScriptError 2026-07-06 22:40:41 +08:00
YoursFunny 4112509fe3 fix: skip trying ldopengl if it is not a LDPlayer 2026-07-06 22:40:41 +08:00
YoursFunny 7c3ef72447 feat: add screenshot method ldopengl 2026-07-06 22:40:41 +08:00
YoursFunny d817bb15e8 fix: skip trying nemu_ipc on MuMu Global 2026-07-06 22:40:41 +08:00
YoursFunny e69983ee85 fix: add merge_result_button function for combining OCR results 2026-07-06 22:40:41 +08:00
YoursFunny dfd0a4086b fix: share screenshot_method_override across tasks 2026-07-06 22:40:40 +08:00
YoursFunny dda8e6b992 fix: add area_distance function and return sorted pair buttons 2026-07-06 22:40:40 +08:00
YoursFunny dbf39d025a fix: improve deep_get, deep_set, deep_pop, and deep_iter functions for better error handling and performance 2026-07-06 22:40:40 +08:00
YoursFunny 04ce2efbeb fix: limit pool size when using run_in_executor() 2026-07-06 22:40:40 +08:00
YoursFunny 1e8686c2a4 fix: catch OSError when iter running emulators 2026-07-06 22:40:40 +08:00
YoursFunny 8c8dd37ce0 fix: handle MuMu12 v4.0.4 default instance serial and improve instance naming 2026-07-06 22:40:39 +08:00
YoursFunny e45eb6082b fix: check app keep alive on mumu >= 4.0 2026-07-06 22:40:39 +08:00
YoursFunny cc7905e6ee fix: connect all possible serial at once 2026-07-06 22:40:39 +08:00
YoursFunny 643a66a0b9 fix: add launch parameters on waydroid 2026-07-06 22:40:39 +08:00
YoursFunny dcb15deb4b fix: handle empty getprop on mumu12 >= 4.0 2026-07-06 22:40:38 +08:00
YoursFunny b535ee5f7e fix: keep uiautomator_2.py start method same as adb 2026-07-06 22:40:38 +08:00
YoursFunny 0ad395b949 fix: catch Permission Denial on am start 2026-07-06 22:40:38 +08:00
YoursFunny ae19dbeb0d feat: launch app with activity name 2026-07-06 22:40:38 +08:00
YoursFunny abdaa277f8 feat: add LDPlayer_id property and improve emulator stop logic 2026-07-06 22:40:38 +08:00
YoursFunny 194043a892 fix: handle dynamic MuMu serial in nemu_ipc.py 2026-07-06 22:40:37 +08:00
YoursFunny 9331d9fdf0 fix: skip nemud_app_keep_alive check on other emulators 2026-07-06 22:40:37 +08:00
YoursFunny f4bbc2f2c3 feat: add emulator console convert and quit method 2026-07-06 22:40:37 +08:00
YoursFunny 47f75460f7 fix: release cache after adb_restart() 2026-07-06 22:40:37 +08:00
YoursFunny 10ea7b1034 fix: try handle MuMu12 port switches 2026-07-06 22:40:37 +08:00
YoursFunny 36d6831ce8 fix: update cached property names for minitouch and maatouch builders 2026-07-06 22:40:36 +08:00
YoursFunny 430d69695c feat: add handle AdbError: unknown host service 2026-07-06 22:40:36 +08:00
YoursFunny 63d238171f fix: increase emulator startup trials and adjust timeout duration 2026-07-06 22:40:36 +08:00
YoursFunny 94ca975d56 fix: use autofill emulator info on Windows only 2026-07-06 22:40:36 +08:00
YoursFunny c9edcd0a40 fix: allow Hermit on VMOS only 2026-07-06 22:40:36 +08:00
YoursFunny 67bd1d9a1c fix: update Device.config during scheduler run 2026-07-06 22:40:35 +08:00
YoursFunny 3d5c2ee537 feat: add interval on dump_hierarchy() 2026-07-06 22:40:35 +08:00
YoursFunny dd8f28621f fix: correct command line argument for BlueStacks 5 instance 2026-07-06 22:40:35 +08:00
YoursFunny 4c0dd1b7d9 fix: enhance nemud_app_keep_alive check for Mac compatibility 2026-07-06 22:40:35 +08:00
YoursFunny 99f86e359f refactor: abstract platform checks 2026-07-06 22:40:35 +08:00
YoursFunny 07df89653c fix: remove chinese comma from ocr results 2026-07-06 22:40:34 +08:00
YoursFunny 8af0639490 fix: update rootName array in getAlasABSPath function 2026-07-06 22:40:34 +08:00
YoursFunny b89637b195 fix: add themeConfig.ts 2026-07-06 22:40:34 +08:00
YoursFunny b3f4f48251 fix: show empty stored 2026-07-06 22:40:34 +08:00
YoursFunny a59cd12c01 fix: patch to fix atx_agent_url on arm64-v8a 2026-07-06 22:40:33 +08:00
YoursFunny e9f12b556c dep: upgrade uiautomator2cache to 0.3.1 2026-07-06 22:40:33 +08:00
YoursFunny 07cb9480c2 dep: lock deps version 2026-07-06 22:40:33 +08:00
YoursFunny c57d64040a fix: use dynamic nemu_ipc timeout to fit screenshot interval 2026-07-06 22:40:33 +08:00
YoursFunny 013ba8b749 fix: update return type documentation for OCR counter method 2026-07-06 22:40:33 +08:00
YoursFunny 3d8f06db85 fix: increase sleep duration and timeout for process shutdown 2026-07-06 22:40:32 +08:00
YoursFunny a640960383 feat: add Switch.wait() 2026-07-06 22:40:32 +08:00
YoursFunny c45aad9f67 fix: remove eta of weekly items since it's meaningless 2026-07-06 22:40:32 +08:00
YoursFunny c793672933 feat: add set_cached_property function to manage cached properties 2026-07-06 22:40:32 +08:00
YoursFunny 2504a03680 fix: add return statement to prevent further execution on server change 2026-07-06 22:40:32 +08:00
YoursFunny 4f56aece00 feat: enhance dashboard display with conditional ETA formatting 2026-07-06 22:40:31 +08:00
YoursFunny be70c88036 feat: add clear method to StoredInt and StoredCounter classes 2026-07-06 22:40:31 +08:00
YoursFunny b029d9f732 feat: add weekly limit for next run in scheduler 2026-07-06 22:40:31 +08:00
YoursFunny feb1e1ae5c fix: update parameter types in switch function documentation 2026-07-06 22:40:31 +08:00
YoursFunny 415304b7a4 fix: comment out logging message for game server status check 2026-07-06 22:40:31 +08:00
YoursFunny 2bb2c2f4a4 feat: add ETA display and comment support in dashboard 2026-07-06 22:40:30 +08:00
YoursFunny e15eb7a9a5 refactor: rename image processing methods to use luma instead of binary 2026-07-06 22:40:30 +08:00
YoursFunny f30deb0614 fix: update logging messages to reflect AAS instead of Alas 2026-07-06 22:40:30 +08:00
YoursFunny caf74d924d fix: handle psutil.NoSuchProcess 2026-07-06 22:40:30 +08:00
YoursFunny 05c30a2e75 fix: try nc command on android first 2026-07-06 22:40:30 +08:00
YoursFunny 3f38ac3827 fix: add more fool-proof serial input redirection 2026-07-06 22:40:29 +08:00
YoursFunny 4dfdcf42f5 perf: reuse image array to improve memory performance 2026-07-06 22:40:29 +08:00
YoursFunny 5168e4edf0 refactor: move func methods to the Alas class 2026-07-06 22:40:29 +08:00
YoursFunny c878ece7c8 fix: prevent starting task if command is 'Restart' 2026-07-06 22:40:29 +08:00
YoursFunny 1831356cee fix: log ScriptError as exception so traceback can be logged 2026-07-06 22:40:29 +08:00
YoursFunny 7afc408587 feat: extract ButtonGrid assets 2026-07-06 22:40:28 +08:00
YoursFunny 77d4b19f9c feat: add HandledError exception and log errors in alas.py 2026-07-06 22:40:28 +08:00
YoursFunny 511a5b40ca feat: implement error logging functionality in new error.py module 2026-07-06 22:40:28 +08:00
YoursFunny 9637d6f9ee fix: improve detection of single digit in TextDetector 2026-07-06 22:40:28 +08:00
YoursFunny 5b6e25d73b fix: remove set_repo() 2026-07-06 22:40:27 +08:00
YoursFunny 41d9c02331 feat: show planner on gui 2026-07-06 22:40:27 +08:00
YoursFunny b00676261d fix: typo in doc 2026-07-06 22:40:27 +08:00
YoursFunny db1a470d94 feat: add white preprocessing option in OcrWhiteLetterOnComplexBackground 2026-07-06 22:40:26 +08:00
YoursFunny 622f2a467d fix: update REGEX_PUNCTUATION to include the pipe character 2026-07-06 22:40:26 +08:00
YoursFunny 090a141307 feat: add Button.match_template_binary() 2026-07-06 22:40:26 +08:00
YoursFunny 41d29438c1 fix: use corresponding model in Digit and DigitCounter 2026-07-06 22:40:26 +08:00
YoursFunny eb59fdb477 fix: set nemu_ipc before screenshot_interval_set 2026-07-06 22:40:25 +08:00
YoursFunny de3d7b60d7 feat: improve ui appearance of stored object 2026-07-06 22:40:25 +08:00
YoursFunny 2616a0ebe6 fix: update REGEX_PUNCTUATION to include the correct dash character 2026-07-06 00:00:32 +08:00
YoursFunny 3ae8ac3c7a feat: add Button.is_offset_in() 2026-07-05 23:52:42 +08:00
YoursFunny d674c7fd4d fix: handle socket timeouts and improve error logging in ScrcpyCore 2026-07-05 23:51:28 +08:00
YoursFunny b601c324b6 perf: improve DroidCast_raw image parsing 2026-07-05 23:49:05 +08:00
YoursFunny 22e132db0a fix: ensure minimum points in minitouch for valid distance calculation 2026-07-05 23:48:07 +08:00
YoursFunny 4e01310b5f perf: improve memory performance in utils 2026-07-05 23:46:44 +08:00
YoursFunny 8db00715b1 fix: patch uiautomator2 loggers so they can be logged 2026-07-05 23:42:42 +08:00
YoursFunny 42459df920 fix: add method uninstall_uiautomator2 2026-07-05 23:41:55 +08:00
YoursFunny 8eed513497 fix: patch u2.init.appdir in runtime 2026-07-05 23:40:56 +08:00
YoursFunny f280648153 perf: import onepush lazily 2026-07-05 23:39:51 +08:00
YoursFunny 0b9da16ff3 fix: handle non-existent adb forward/reverse removal gracefully 2026-07-05 23:36:13 +08:00
YoursFunny 70b784c2f4 fix: remove minitouch port forwarding on reconnect 2026-07-05 23:33:59 +08:00
YoursFunny 38b6ce50c6 fix: use current python executable if deploy.yaml one does not exist 2026-07-05 23:29:37 +08:00
YoursFunny d6e52d7720 fix: redirect deploy config after Updator.delay() 2026-07-05 23:27:02 +08:00
YoursFunny 99862137f1 fix: add default value for _minitouch_client 2026-07-05 23:10:45 +08:00
YoursFunny 7415c7e217 fix: re-init MaaTouch when orientation changes 2026-07-05 23:08:36 +08:00
YoursFunny 6d2b536050 dep: upgrade MaaTouch binary to 1.10 2026-07-05 23:03:18 +08:00
YoursFunny 0481463c07 fix: add detection of global MuMu12 id 2026-07-05 23:01:17 +08:00
YoursFunny 810e669118 fix: add retry for minitouch and MaaTouch builders 2026-07-05 23:00:23 +08:00
YoursFunny 63c464b51a fix: site-packages detection on manual package builds 2026-07-05 22:56:40 +08:00
YoursFunny 433931477e perf: patch pkg_resources for faster startup 2026-07-05 22:55:00 +08:00
YoursFunny dbc1789ae1 perf: early init minitouch and MaaTouch for faster startup 2026-07-05 22:52:14 +08:00
YoursFunny 2f4eb5d047 fix: update config_updater to yield default values 2026-07-05 22:27:48 +08:00
YoursFunny ef9dde3da5 Merge pull request #37 from TheFunny/sync_src
Sync with src
2026-07-05 21:20:49 +08:00
YoursFunny 723aa66e69 Merge branch 'dev' into sync_src 2026-07-05 21:17:22 +08:00
YoursFunny 0b28ed6f89 feat: migrate notify.py from ALAS and upgrade dep onepush 1.3.0 2026-07-05 20:21:50 +08:00
YoursFunny d54ff72565 feat: use nemu_ipc if avaliable 2026-07-05 20:18:18 +08:00
YoursFunny a2bd36f570 feat: add nemu_ipc 2026-07-05 20:16:51 +08:00
YoursFunny 50c4bc5bba feat: enhance device connection handling and droidcast resolution management 2026-07-05 20:06:53 +08:00
YoursFunny 3cef2ee01b feat: improve emulator management 2026-07-05 19:40:17 +08:00
YoursFunny 0bd294582b refactor: move ThreadPoolExecutor import inside worker method for better encapsulation 2026-07-05 19:27:47 +08:00
YoursFunny 49149413d5 fix: add timeout check to prevent infinite loop in draggable list 2026-07-05 19:27:22 +08:00
YoursFunny 108f648517 fix: set Windows event loop policy conditionally based on platform 2026-07-05 18:57:05 +08:00
YoursFunny aa4afbf6a2 perf: reuse image array to improve memory performance 2026-07-05 18:55:37 +08:00
YoursFunny d34c082ef3 feat: add AdaptiveScroll to handle transparent scroll on complex background 2026-07-05 18:51:41 +08:00
YoursFunny d48427abb2 refactor: cache nemud.app_keep_alive 2026-07-05 18:49:35 +08:00
YoursFunny ce8eb1c452 refactor: streamline platform imports and remove redundant type method 2026-07-05 18:47:10 +08:00
YoursFunny d0e6c78cc1 feat: autofill emulator info even not used 2026-07-05 18:43:46 +08:00
YoursFunny 75b8b78b24 refactor: rename list_azurlane_packages to list_known_packages for clarity 2026-07-05 18:39:45 +08:00
YoursFunny cac19c10c3 fix: enhance auto device detection, and ignore 127.0.0.1:7555 from MuMu12 2026-07-05 18:38:33 +08:00
YoursFunny d806c5e9e7 fix: exclude hidden files from asset extraction 2026-07-05 18:35:33 +08:00
YoursFunny 87975760ae feat: proxy element props 2026-07-05 18:33:25 +08:00
YoursFunny 6a4b1998fc fix: ignore files that are added by deployer 2026-07-05 18:29:59 +08:00
YoursFunny d7db56eb5a fix: update input prompts in screenshot.py 2026-07-05 18:27:58 +08:00
YoursFunny d017d9eefd fix: update REGEX_PUNCTUATION to include additional symbols 2026-07-05 18:26:01 +08:00
YoursFunny 97c3ed30e9 feat: add set_search_offset() to be compatible with Alas 2026-07-05 18:25:31 +08:00
YoursFunny 52964c2ff0 feat: add TaskError exception for task error handling 2026-07-05 18:21:29 +08:00
YoursFunny be44b23d70 fix: support emulator index parsing from MuMu ArkNights version 2026-07-05 18:19:44 +08:00
YoursFunny 0590eebd44 fix: handle FileNotFoundError when accessing Windows registry keys 2026-07-05 18:15:19 +08:00
YoursFunny 11f5f1dd64 fix: copy raw attributes of first frame to all 2026-07-05 18:11:56 +08:00
YoursFunny 5ffaec0fb1 feat: add stop method to Login class and implement button handling in base.py 2026-07-05 18:09:57 +08:00
YoursFunny 24e9bde5c7 fix: update punctuation regex to include ellipsis 2026-07-05 18:01:38 +08:00
YoursFunny 64bb8e3a12 feat: add method to increment value in StoredCounter 2026-07-05 18:00:49 +08:00
YoursFunny baddf23df5 refactor: update scroll methods to return results for better chaining 2026-07-05 17:56:17 +08:00
YoursFunny fc5847d6d8 feat: add KeywordDigitCounter class for filtering digit counters in OCR results 2026-07-05 17:55:16 +08:00
YoursFunny 84ac3dc9b8 feat: add is_format_matched method and improve regex for result formatting 2026-07-05 17:51:46 +08:00
YoursFunny af3e178c88 fix: limit length of screenshot deque 2026-07-05 17:50:14 +08:00
YoursFunny 7d084db107 fix: do not track screenshot when save error disabled 2026-07-05 17:47:26 +08:00
YoursFunny fcab832be1 fix: update func_list handling to ensure proper task binding 2026-07-05 17:36:36 +08:00
YoursFunny e8cf0a3517 fix: extend height of log-bar in daemon overview 2026-07-05 17:34:39 +08:00
YoursFunny 808a02ddf5 feat: add find_name method to retrieve Keyword instances by name 2026-07-05 17:31:12 +08:00
YoursFunny 5df288b577 feat: hide custom settings if custom not selected 2026-07-05 17:29:56 +08:00
YoursFunny 0a10c61c59 feat: show function calls on error 2026-07-05 17:22:09 +08:00
YoursFunny 247a9a8770 feat: save tracking screenshots 2026-07-05 17:19:11 +08:00
YoursFunny 63d4b50461 fix: add retry for check_mumu_app_keep_alive() 2026-07-05 17:12:50 +08:00
YoursFunny 8741cb6142 fix: checkbox cannot be updated dynamically 2026-07-05 17:11:07 +08:00
YoursFunny 091b5c011a refactor: replace direct import of deque with collections.deque for clarity 2026-07-05 17:07:17 +08:00
YoursFunny c43f8e5458 feat: add is_draggable method to improve swipe responsiveness 2026-07-05 17:02:27 +08:00
YoursFunny 6f6dab1c04 fix: correct function to retrieve last server update 2026-07-05 16:57:17 +08:00
YoursFunny f6f2470e5b refactor: add config update callback 2026-07-05 16:56:12 +08:00
YoursFunny 2275133638 dep: update to pywebio 1.8.3 2026-07-05 16:42:50 +08:00
YoursFunny da34dc4b7d fix: recheck in-game text language during waiting for next run 2026-07-05 16:41:20 +08:00
YoursFunny 07a7a19929 feat: add area_center function and enhance OCR processing for white letters 2026-07-05 16:40:00 +08:00
YoursFunny a7290f253e fix: remove another dash in ocr results 2026-07-05 16:37:22 +08:00
YoursFunny 0b1a4efd09 fix: renew timer in interval_is_reached() only 2026-07-05 16:36:01 +08:00
YoursFunny aaa56b928f feat: add interval timer management methods for buttons 2026-07-05 16:34:09 +08:00
YoursFunny 8dc527b06b fix: remove semicolon and dot in ocr results 2026-07-05 16:22:33 +08:00
YoursFunny 004af69d8f fix: use 'area' in 'ClickButton' 2026-07-05 16:20:03 +08:00
YoursFunny a397539c69 feat: add simple markdown table generator 2026-07-05 16:12:19 +08:00
YoursFunny 864ac15282 dep: upgrade to pydantic v2 2026-07-05 16:07:37 +08:00
YoursFunny ff2f226713 fix: update assets file 2026-05-26 21:54:23 +08:00
YoursFunny 8177d0f56d fix: update ui assets for OVERSEA server 2026-05-26 21:53:53 +08:00
YoursFunny b606108871 fix: update assets file 2026-04-24 19:30:05 +08:00
YoursFunny be09b39bbe fix: update ui assets for jp 2026-04-24 19:26:36 +08:00
YoursFunny fafb9d5a5c fix: update assets file 2026-01-20 19:45:18 +08:00
YoursFunny 998d52b56c fix: update ui assets for jp 2026-01-20 19:43:55 +08:00
YoursFunny 3e6a82764a fix(login): disable clicking back when login for jp 2025-10-27 14:30:45 +08:00
YoursFunny 3cd8161afb feat(momotalk): add a single tool page for momotalk,
remove its scheduler,
and some minor improvements
2025-10-13 11:41:48 +08:00
YoursFunny 02b8de6d18 fix: update assets file 2025-09-23 20:02:23 +08:00
YoursFunny 7a321eae48 fix: update configs to enable new bounty stage for OVERSEA server 2025-09-23 20:01:32 +08:00
YoursFunny ad19268e5a fix: update ui assets for zht 2025-09-23 19:59:49 +08:00
YoursFunny 8bc170e170 fix: update ui assets for en 2025-09-23 19:59:26 +08:00
YoursFunny 1d618d1624 fix: update assets file 2025-08-09 13:19:41 +08:00
YoursFunny 9eb3bde649 fix: update ui assets for OVERSEA server 2025-08-09 13:18:23 +08:00
YoursFunny 62ef4c733c fix: update assets file 2025-07-22 20:13:27 +08:00
YoursFunny 27dbd31434 fix: update ui assets for jp 2025-07-22 20:12:46 +08:00
YoursFunny 74ef1a7b4d fix: update configs 2025-05-18 19:57:13 +08:00
YoursFunny 43282354cd fix(bounty): update stage select option 2025-05-18 19:53:27 +08:00
YoursFunny f92d92aec0 fix: update assets file 2025-05-13 21:54:43 +08:00
YoursFunny 3a00c47125 fix(cafe): update ui assets for zht 2025-05-13 21:53:18 +08:00
YoursFunny bca3395cb9 fix(cafe): update ui assets for jp & en 2025-05-13 21:53:08 +08:00
YoursFunny 766cb78377 fix(cafe): synchronize Cafe No.2 for global server 2025-05-13 21:52:07 +08:00
YoursFunny 84acb67061 fix: update assets file 2025-04-22 19:53:10 +08:00
YoursFunny 9c07251669 fix: update ui assets for jp 2025-04-22 19:52:59 +08:00
YoursFunny 19272d6cbe fix(cafe): correct invitation detection of Cafe No.2 in OVERSEA server 2025-03-08 18:43:25 +08:00
YoursFunny f268cfeda8 fix: update assets file 2025-01-29 16:41:31 +08:00
YoursFunny 37b5d4e4d2 fix(cafe): update ui assets for zht 2025-01-29 16:40:32 +08:00
YoursFunny 29bd71ccc8 fix(cafe): adjust timer of Cafe No.2 2025-01-22 15:01:49 +08:00
YoursFunny d5f4891d4e fix(cafe): adjust click template offset 2025-01-22 15:01:20 +08:00
YoursFunny 9a500b9191 fix: update assets file 2025-01-20 23:20:29 +08:00
YoursFunny 180fa6aa7d fix(cafe): update ui assets for jp 2025-01-20 23:19:46 +08:00
YoursFunny 4325118562 fix(cafe): update latest Cafe No.2 switch for jp 2025-01-20 23:19:45 +08:00
YoursFunny 2adf3f9efe fix: update assets file 2024-11-12 18:16:27 +08:00
YoursFunny 648bbb8a63 fix(cafe): update ui assets for zht 2024-11-12 18:16:05 +08:00
YoursFunny f73bba3e20 fix(cafe): update ui assets for en 2024-11-12 18:16:05 +08:00
YoursFunny e321fd7e28 lang(cafe): correct Cafe No.2 description 2024-11-12 18:16:05 +08:00
YoursFunny 6215f061c8 fix(cafe): enable Cafe No.2 for OVERSEA server 2024-11-12 18:09:13 +08:00
YoursFunny 72a948da68 fix: update assets file 2024-10-23 23:35:14 +08:00
YoursFunny c69a8543c6 fix: update ui assets for en
(cherry picked from commit a5d20a95be)
2024-10-23 23:34:54 +08:00
YoursFunny c16e50157e fix: update ui assets for zht 2024-10-23 23:29:51 +08:00
YoursFunny 2cd705b5ba fix: update assets file 2024-09-24 14:08:29 +08:00
YoursFunny f70e75f1a6 fix: update ui assets for en
(cherry picked from commit daf55b76e2)
2024-09-24 14:07:36 +08:00
YoursFunny 861cf9a2da fix: update ui assets for zht 2024-09-24 13:58:49 +08:00
YoursFunny 437977ef63 fix(circle): improve circle enter 2024-09-03 15:10:36 +08:00
YoursFunny 123c248f43 fix: update assets file 2024-09-03 15:07:40 +08:00
YoursFunny 1461cc41c6 fix(circle): update ui assets 2024-09-03 15:07:11 +08:00
YoursFunny 34af75245e fix(tc): improve stability of reward 2024-08-10 17:49:26 +08:00
YoursFunny 6e3d5fc4c5 fix(popup): update assets files 2024-08-10 17:48:25 +08:00
YoursFunny 16d5f214d8 fix(popup): update ui assets 2024-08-10 17:48:07 +08:00
YoursFunny f669130e33 fix(cafe): improve stability of closing reward window 2024-08-10 17:10:46 +08:00
YoursFunny 8fe8ed5f86 fix: update assets file 2024-07-24 17:32:37 +08:00
YoursFunny e070bb839e fix: update ui assets for zht 2024-07-24 17:32:28 +08:00
YoursFunny 90c47745d7 fix: update ui assets and circle for en
(cherry picked from commit 2ec7e45172)
2024-07-24 17:28:34 +08:00
YoursFunny e832e3c27f fix: update assets file 2024-07-11 18:04:45 +08:00
YoursFunny 3391874151 fix: update jp ui assets 2024-07-11 18:03:54 +08:00
YoursFunny b02528f14b fix: update assets file 2024-07-09 16:09:29 +08:00
YoursFunny addb984c8e fix: update zht ui assets 2024-07-09 16:08:52 +08:00
YoursFunny 1d837b5441 fix: update en ui assets 2024-07-09 16:05:38 +08:00
YoursFunny 99a82242d7 fix: correct tray tooltip 2024-05-07 16:40:28 +08:00
YoursFunny b2ae30b45c fix(ocr): force substitute % with / in DigitCounter 2024-05-05 16:08:35 +08:00
YoursFunny bd0749c058 fix: update assets file 2024-05-04 20:06:26 +08:00
YoursFunny d87827a098 fix: update en ui assets 2024-05-04 20:04:25 +08:00
YoursFunny 92feadc228 fix: update assets file 2024-05-04 19:54:04 +08:00
YoursFunny 4c020dbfd3 fix: update en ui assets 2024-05-04 19:52:49 +08:00
YoursFunny 67b9cfee2d fix: update zht ui assets 2024-05-04 19:44:39 +08:00
YoursFunny 76f80bb6b1 fix: update ui assets for jp 2024-04-24 19:34:46 +08:00
YoursFunny ac2cfe3974 fix: update ui assets for jp 2024-03-27 20:21:40 +08:00
YoursFunny f4fd4a4d86 fix: update ui assets for jp 2024-02-21 19:51:10 +08:00
YoursFunny 07261d5438 fix: update zht assets 2024-01-30 23:02:10 +08:00
YoursFunny 5d9c14b5d4 fix: change network reconnect button 2024-01-26 23:25:05 +08:00
YoursFunny daca32d4fe fix: update ui assets and circle for jp 2024-01-25 19:50:04 +08:00
YoursFunny a5d478ce56 fix(task): add correct complete check for jp 2024-01-15 21:57:51 +08:00
YoursFunny 830bc8d211 lang: update zh gui 2024-01-15 21:51:57 +08:00
YoursFunny 08bb139476 fix: disable year detection for jp temporarily 2024-01-15 21:49:23 +08:00
YoursFunny 92702fdc04 feat: add log when sending back 2024-01-15 21:36:32 +08:00
YoursFunny 55b4dc9748 feat: add assets for JP 2024-01-15 20:55:50 +08:00
YoursFunny e6a3b79733 style: format 2024-01-15 19:25:15 +08:00
RedDeadDepresso e5f91e0c0a chore: updated GUI 2024-01-15 17:45:36 +08:00
RedDeadDepresso 8287822fe4 feat: momotalk zht 2024-01-15 17:45:36 +08:00
RedDeadDepresso 1c2ebf6b8b docs: updated gui 2024-01-15 17:45:36 +08:00
RedDeadDepresso d713121a86 fix: ui 2024-01-15 17:45:36 +08:00
RedDeadDepresso de73446f73 fix: distinguish task and campaign in zht 2024-01-15 17:45:35 +08:00
RedDeadDepresso e7878d63b0 chore: added comments in ui 2024-01-15 17:45:35 +08:00
RedDeadDepresso 9927f0550b fix: ui 2024-01-15 17:45:35 +08:00
RedDeadDepresso c4c7df1f21 feat: start and exit ldplayer9 2024-01-15 17:45:35 +08:00
RedDeadDepresso 4fa4478d77 feat: added quit for zht 2024-01-15 17:45:35 +08:00
RedDeadDepresso ccd9466b77 fix: shop 2024-01-15 17:45:34 +08:00
RedDeadDepresso a8caafb292 fix: login 2024-01-15 17:45:34 +08:00
RedDeadDepresso 7e1070e740 perf: mission 2024-01-15 17:45:34 +08:00
RedDeadDepresso 01a3fdfce5 feat: 7 choices in lesson 2024-01-15 17:45:34 +08:00
RedDeadDepresso 9d0c276db5 perf: mission 2024-01-15 17:45:34 +08:00
RedDeadDepresso fd4ec3aff0 fix: back in ui 2024-01-15 17:45:34 +08:00
RedDeadDepresso 0cbde8077e fix: exit aas 2024-01-15 17:45:33 +08:00
RedDeadDepresso 7c1620c0f0 fix: exit aas 2024-01-15 17:45:33 +08:00
RedDeadDepresso 6cca8f1082 perf: login 2024-01-15 17:45:33 +08:00
RedDeadDepresso a74d05aeda fix: login 2024-01-15 17:45:33 +08:00
RedDeadDepresso 31a69f11ed fix: login 2024-01-15 17:45:33 +08:00
RedDeadDepresso 726662d8b7 fix: schedule 2024-01-15 17:45:33 +08:00
RedDeadDepresso 1aa9a50e62 fix: lesson 2024-01-15 17:45:32 +08:00
RedDeadDepresso 08ee060f34 fix: player selection in tactical challenge 2024-01-15 17:45:32 +08:00
RedDeadDepresso 4bb95b61ff fix: login 2024-01-15 17:45:32 +08:00
RedDeadDepresso 17ee226a5a perf: tasks 2024-01-15 17:45:32 +08:00
RedDeadDepresso a1af2b0b74 perf: lesson 2024-01-15 17:45:32 +08:00
RedDeadDepresso e5fe0d096c fix: commissions 2024-01-15 17:45:32 +08:00
RedDeadDepresso f4ad80d17e feat: traditional chinese 2024-01-15 17:45:31 +08:00
RedDeadDepresso e28b2f1e2e chore: updated lesson help 2024-01-15 17:45:31 +08:00
RedDeadDepresso 7d03615916 chore: updated gui cafe and lesson help 2024-01-15 17:45:31 +08:00
RedDeadDepresso 0184376076 perf: shop 2024-01-15 17:45:31 +08:00
RedDeadDepresso 17851b86de fix: schedule popups 2024-01-15 17:45:31 +08:00
RedDeadDepresso b5d2c13259 feat: added level up and location level up popup 2024-01-15 17:45:30 +08:00
RedDeadDepresso 659172cdd3 fix: exit emulator, aas, shutdown
allow tactical challenge to be completed
2024-01-15 17:45:30 +08:00
RedDeadDepresso ec543c6db2 chore: updated gui task queue empty 2024-01-15 17:45:30 +08:00
RedDeadDepresso a02c05bd69 fix: exit aas & emulator 2024-01-15 17:45:30 +08:00
RedDeadDepresso c674c7a53b feat: exit AAS & emulator 2024-01-15 17:45:30 +08:00
RedDeadDepresso 1ab7c5c40f fix: bluestacks start emulator 2024-01-15 17:45:30 +08:00
RedDeadDepresso 64d63bdd24 test: exit emulator 2024-01-15 17:45:29 +08:00
RedDeadDepresso f932b2ac10 feat: shutdown when task queue empty 2024-01-15 17:45:29 +08:00
RedDeadDepresso 73568fe48c feat: add up in MCE manager 2024-01-15 17:45:29 +08:00
RedDeadDepresso 0d35587940 fix: MCE Manager tooltip 2024-01-15 17:45:29 +08:00
RedDeadDepresso ee87f92252 perf: momotalk 2024-01-15 17:45:29 +08:00
RedDeadDepresso a627f76197 chore: updated requirements.txt 2024-01-15 17:45:28 +08:00
RedDeadDepresso d7722c044e fix: momotalk 2024-01-15 17:45:28 +08:00
RedDeadDepresso 9bf970e5fb fix: lesson classroom order 2024-01-15 17:45:28 +08:00
RedDeadDepresso c4e12c4194 fix: mission AP calculations 2024-01-15 17:45:28 +08:00
RedDeadDepresso 92aaac7b5a fix: mission event 2024-01-15 17:45:28 +08:00
RedDeadDepresso 782e61ad9a fix: shop 2024-01-15 17:45:28 +08:00
RedDeadDepresso 149e6ea1ef chore: updated lesson gui section 2024-01-15 17:45:27 +08:00
RedDeadDepresso 69bff3f757 feat: 7 hours mission delay 2024-01-15 17:45:27 +08:00
RedDeadDepresso 9b9d0a5bcd fix: mission ocr 2024-01-15 17:45:27 +08:00
RedDeadDepresso 10607f9c3d fix: shop 2024-01-15 17:45:27 +08:00
RedDeadDepresso 390082fa50 fix: shop 2024-01-15 17:45:27 +08:00
RedDeadDepresso ca1b1b2efd fix: lesson 2024-01-15 17:45:27 +08:00
RedDeadDepresso 4817b6768f chore: added licenses 2024-01-15 17:45:26 +08:00
RedDeadDepresso 9c7fc247a1 refactor: mission 2024-01-15 17:45:26 +08:00
RedDeadDepresso fe7c6f92a8 feat:lesson 2024-01-15 17:45:26 +08:00
RedDeadDepresso 93b7ca8cfc feat: input helper
Just a small program for users to quickly find students name.
2024-01-15 17:45:26 +08:00
RedDeadDepresso 151a085a9c fix: task 2024-01-15 17:45:25 +08:00
RedDeadDepresso a241484e24 fix: sweep event 2024-01-15 17:45:25 +08:00
RedDeadDepresso 3ac2009737 feat: added assets event 2024-01-15 17:45:25 +08:00
RedDeadDepresso 59b310b6df fix: task for EN 2024-01-15 17:45:25 +08:00
RedDeadDepresso fb02cedd8d refactor: MCE Manager
changed queue thread into daemon thread
2024-01-15 17:45:25 +08:00
RedDeadDepresso 204c56efac fix: close random popup in main after login 2024-01-15 17:45:25 +08:00
RedDeadDepresso fc1edefa79 feat: auto-generate MCE/config.json 2024-01-15 17:45:24 +08:00
RedDeadDepresso a915ce396b revert changes in task 2024-01-15 17:45:24 +08:00
RedDeadDepresso e4afe9a112 Update .gitignore 2024-01-15 17:45:24 +08:00
RedDeadDepresso d34a8a39d5 fix: updated regex for mission 2024-01-15 17:45:24 +08:00
RedDeadDepresso 9aaf1e8f3b fix: tasks 2024-01-15 17:45:24 +08:00
RedDeadDepresso bcbe10d291 feat: mission/commissions/event 2024-01-15 17:45:23 +08:00
YoursFunny 69a612dc55 fix(tc): change refresh time 2024-01-15 17:45:23 +08:00
YoursFunny b8b59e7dbc feat: add tasks assets for jp 2024-01-15 17:45:23 +08:00
YoursFunny 41e2d188b9 fix(momotalk): correct sort switch 2024-01-15 17:45:23 +08:00
YoursFunny 107392a900 fix(momotalk): correct sidebar switch 2024-01-15 17:45:23 +08:00
YoursFunny 8ccb3d3d22 fix(task): check all claimed 2024-01-15 17:45:22 +08:00
YoursFunny 694d88a339 lang: add zh for new settings 2024-01-15 17:45:22 +08:00
RedDeadDepressoandYoursFunny 17964cbd9a Added Tasks, Shop, MomoTalk (#11)
* feat: tasks

Added module tasks for EN

* refactor: gui

added tree view Farm and Reward.

* feat: shop

* feat: momotalk

---------

Co-authored-by: YoursFunny <admin@yoursfunny.top>
2024-01-15 17:45:22 +08:00
YoursFunny f26dffa221 perf: change tc priority 2024-01-15 17:44:59 +08:00
Cheong Sik Feng 2f44074400 Fix Tactical Challenge button mask image
With the addition of the Grand Assault, the Joint Firing Drill button was moved to the top half of where the Tactical Challenge button previously was, and the new Tactical Challenge button is only the lower half.
2023-12-25 16:26:57 +08:00
YoursFunny 0f6dd93608 fix(sweep): remove index if equal 0 2023-12-09 21:22:11 +08:00
YoursFunny f5cf0a7fbe fix(tc): add handle ui_additional 2023-12-07 18:24:09 +08:00
YoursFunny 16d78e1e16 fix(sweep): correct load index situation 2023-12-07 13:04:05 +08:00
YoursFunny 7b707be841 refactor(scrimmage): use stage ap count 2023-12-06 17:11:50 +08:00
YoursFunny 93bf1f73e2 feat: separate stage ap count 2023-12-05 16:21:19 +08:00
YoursFunny 71da6fd996 feat: add auto select for bounty and scrimmage 2023-11-30 14:21:19 +08:00
YoursFunny ecd4ba0a7c doc: update readme 2023-11-29 18:38:59 +08:00
YoursFunny d684fd79f6 feat(sweep): support finding max sweepable index 2023-11-29 14:55:55 +08:00
YoursFunny 6ae785634c refactor(sweep): separate sweepable check and search_box generate 2023-11-29 14:11:51 +08:00
YoursFunny a0d3fd75af refactor(sweep): change current_indexes 2023-11-29 14:05:50 +08:00
YoursFunny 257e092936 refactor(sweep): use regex for index match 2023-11-29 13:54:55 +08:00
YoursFunny 32cee3f6b5 fix(cafe): adjust name of invitation 2023-11-29 12:39:51 +08:00
YoursFunny 659f58db38 fix(cafe): add check of null invitation name 2023-11-28 21:46:21 +08:00
YoursFunny 6dac8a1de2 feat(cafe): add invitation assets for en 2023-11-28 20:53:24 +08:00
YoursFunny b1aeb64768 lang: change option description 2023-11-28 20:53:01 +08:00
YoursFunny 88e8a98b76 fix(cafe): apply config of choice only when not set before 2023-11-28 16:18:34 +08:00
YoursFunny 39d00ac549 fix(cafe): correct target name check 2023-11-28 16:11:50 +08:00
YoursFunny 11d2a6ef7e feat(cafe): add invitation condition check 2023-11-28 14:04:37 +08:00
YoursFunny 1f5b68d095 feat(webui): add invitation condition options 2023-11-28 14:04:07 +08:00
YoursFunny 5f3ff140dd feat(cafe): add invitation 2023-11-27 22:02:50 +08:00
YoursFunny 8698fa20c2 feat(webui): add config of invitation options 2023-11-27 20:48:37 +08:00
YoursFunny b426c6caac feat(cafe): add invitation assets for jp 2023-11-27 15:44:56 +08:00
YoursFunny 8d83ec1657 fix: add swipe name 2023-11-27 15:26:27 +08:00
YoursFunny 7fcda15329 perf(sweep): improve list insight logic 2023-11-27 13:17:29 +08:00
YoursFunny 54aa1bafb5 refactor(sweep): simplify list enter match 2023-11-26 18:54:26 +08:00
YoursFunny d756b0dc3f perf: reduce config save times 2023-11-25 15:28:57 +08:00
YoursFunny e71118c09e fix: improve data update stability 2023-11-25 15:28:07 +08:00
YoursFunny 44b6d5cdf8 fix: expand ticket ocr region 2023-11-25 14:27:01 +08:00
YoursFunny f92dba92c9 fix(sweep): use swipe instead of drag in list 2023-11-24 14:30:43 +08:00
YoursFunny 86ce04cff9 fix(sweep): extend timer stable 2023-11-24 13:59:39 +08:00
YoursFunny f2065507c2 fix(ui): change task order 2023-11-23 16:37:16 +08:00
635 changed files with 18486 additions and 2427 deletions
+9
View File
@@ -20,6 +20,15 @@ config/reloadalas
test.py
test/
note.md
MCE/config.json
resources/
locales/
*.pak
*.dll
*.dat
v8_context_snapshot.bin
snapshot_blob.bin
vk_swiftshader_icd.json
# Created by .ignore support plugin (hsz.mobi)
+102
View File
@@ -0,0 +1,102 @@
import customtkinter
import json
from MCE.custom_widgets.ctk_scrollable_dropdown import CTkScrollableDropdown
import os
from tkinter import END
class InputHelper(customtkinter.CTk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.create_tabview()
self.create_invite_student_widgets()
self.create_lessons_widgets()
def create_tabview(self):
self.tabview = customtkinter.CTkTabview(master=self)
self.tabview.grid(row=0, column=0)
self.cafe_tab = self.tabview.add("Cafe") # add tab at the end
self.lessons_tab = self.tabview.add("Lessons") # add tab at the end
self.tabview.set("Cafe") # set currently visible
def create_invite_student_widgets(self):
self.invitation_label = customtkinter.CTkLabel(master=self.cafe_tab, text="Copy and paste this in AAS Invitation Settings:")
self.invitation_label.grid(row=0, column=0, padx=60)
self.invitation_entry = customtkinter.CTkEntry(master=self.cafe_tab, width=500)
self.invitation_entry.grid(row=1, column=0)
self.invite_copy_button = customtkinter.CTkButton(master=self.cafe_tab, text="Copy", width=40, command=lambda : self.copy_entry(self.invitation_entry, self.invite_copy_button))
self.invite_copy_button.grid(row=1, column=3, padx=5)
self.invite_clear_button = customtkinter.CTkButton(master=self.cafe_tab, text="Clear", width=40, fg_color="crimson", command=lambda : self.invitation_entry.delete(0, END))
self.invite_clear_button.grid(row=1, column=4, padx=5)
self.invite_frame = customtkinter.CTkFrame(master=self.cafe_tab, fg_color="transparent")
self.invite_frame.grid(row=2, column=0, padx=20, pady=20)
self.server_dropdown = customtkinter.CTkOptionMenu(master=self.invite_frame, values=self.find_json_files("MCE/student_list"), command=self.switch_server, width=40)
self.server_dropdown.grid(row=0, column=0)
self.student_entry = customtkinter.CTkComboBox(master=self.invite_frame, width=300)
self.student_entry.grid(row=0, column=1, padx=(50,0))
self.student_dropdown = CTkScrollableDropdown(self.student_entry, width=300, height=550, autocomplete=True, command=lambda choice: self.insert(choice, self.invitation_entry), values=[""])
self.server_dropdown.set("EN")
self.switch_server("EN")
def create_lessons_widgets(self):
self.lessons_label = customtkinter.CTkLabel(master=self.lessons_tab, text="Copy and paste this in AAS Lessons Settings:")
self.lessons_label.grid(row=0, column=0, padx=60)
self.lessons_entry = customtkinter.CTkEntry(master=self.lessons_tab, width=500)
self.lessons_entry.grid(row=1, column=0)
self.lessons_copy_button = customtkinter.CTkButton(master=self.lessons_tab, text="Copy", width=40, command=lambda : self.copy_entry(self.lessons_entry, self.lessons_copy_button))
self.lessons_copy_button.grid(row=1, column=1, padx=5)
self.lessons_clear_button = customtkinter.CTkButton(master=self.lessons_tab, text="Clear", width=40, fg_color="crimson", command=lambda : self.lessons_entry.delete(0, END))
self.lessons_clear_button.grid(row=1, column=2, padx=5)
self.lessons_buttons_frame = customtkinter.CTkFrame(master=self.lessons_tab, fg_color="transparent")
self.lessons_buttons_frame.grid(row=2, column=0, padx=20, pady=20)
for i in range(9):
self.lesson_button = customtkinter.CTkButton(master=self.lessons_buttons_frame, text=str(i+1), command=lambda choice=str(i+1): self.insert(choice, self.lessons_entry), width=40)
self.lesson_button.grid(row=0, column=i, padx=5)
def find_json_files(self,folder_path):
json_files = []
for root, dirs, files in os.walk(folder_path):
for file in files:
if file.endswith(".json"):
json_files.append(os.path.splitext(file)[0])
return json_files
def switch_server(self, server):
with open(f"MCE/student_list/{server}.json", "r") as f:
student_list = json.load(f)
self.student_dropdown.configure(values=student_list)
def insert(self, value, entry):
entry.insert(index=END, string=value + " > ")
def copy_entry(self, entry, button):
text_to_copy = entry.get()
# Check if there is text to copy
if text_to_copy:
# Clear the clipboard and set the new text
self.clipboard_clear()
self.clipboard_append(text_to_copy)
self.update() # This is necessary on some systems to update the clipboard
button_color = button.cget("fg_color")
button.configure(fg_color="green")
self.after(2000, lambda : button.configure(fg_color=['#3B8ED0', '#1F6AA5']))
if __name__ == "__main__":
app = InputHelper()
app.title("Input Helper")
app.mainloop()
+442
View File
@@ -0,0 +1,442 @@
import customtkinter
import tkinter as tk
import random
import re
from MCE.custom_widgets.ctkmessagebox import CTkMessagebox
from MCE.custom_widgets.ctk_tooltip import CTkToolTip
from MCE.custom_widgets.ctk_timeentry import CTkTimeEntry
from MCE.custom_widgets.ctk_integerspinbox import CTkIntegerSpinbox
from MCE.custom_widgets.ctk_templatedialog import CTkTemplateDialog
from MCE.custom_widgets.ctk_notification import CTkNotification
from MCE.custom_widgets.ctk_add_button import CTkAddButton
from MCE.utils import Linker, Config
from filelock import FileLock, Timeout
import threading
import time
class MCE_Manager(customtkinter.CTk):
def __init__(self, linker, config, **kwargs):
super().__init__(**kwargs)
self.linker = linker
self.config = config
self.create_widgets()
# Load Template Data
self.load_template_data()
# Load queue Data
self.load_queue_data()
def create_widgets(self):
self.create_mission_commissions_widgets()
def create_mission_commissions_widgets(self):
# Create Mission/Commissions/Event Checkbox
self.create_mission_commissions_checkbox()
# Create Reset Daily Widgets
self.create_reset_daily_widgets()
# Create Recharge AP and Event Checkboxes
self.create_recharge_and_event_checkboxes()
# Create Preferred Template Selection
self.create_preferred_template_selection()
# Create Mission Tabview with Template and Queue Tabs
self.create_mission_tabview()
# Create Top-Level Window for Template Editing
self.create_template_queue_editor()
# Create Template Frame and Queue Frame
self.create_template_and_queue_frames()
# Create Lists to Store Frame Widgets
self.create_frame_lists()
# Initialize Preferred Template and Templates List
self.initialize_preferred_template()
# Create OptionMenu for Selecting a Template
self.create_template_option_menu()
# Create Delete Template Button
self.create_delete_template_button()
# Helper method to create Mission/Commissions/Event Checkbox
def create_mission_commissions_checkbox(self):
self.mission_commissions_checkbox = customtkinter.CTkLabel(self, text="Mission/Commissions/Event", width=60, font=customtkinter.CTkFont(family="Inter", size=20, weight="bold"))
self.mission_commissions_checkbox.grid(row=11, column=0, sticky="nw", padx=20, pady=20)
self.notification = CTkNotification(master=self, text="Config saved")
self.notification.grid(row=11, column=1)
# Helper method to create Reset Daily Widgets
def create_reset_daily_widgets(self):
self.reset_daily = customtkinter.CTkCheckBox(self, text="Reset Daily", font=customtkinter.CTkFont(family="Inter", size=16, underline=True), command=lambda x=["ResetDaily"]: self.config.save_to_json(x))
self.reset_daily.grid(row=12, column=0, sticky="nw", padx=80)
self.reset_daily_tooltip = CTkToolTip(self.reset_daily, wraplength=400,
message="If enabled and if current time >= reset time,\
the queue will automatically be cleared and repopulated with preferred template stages. Only activated once a day.")
self.reset_daily_sub_label = customtkinter.CTkLabel(self, text="hh/mm/ss", font=customtkinter.CTkFont(family="Inter", size=12))
self.reset_daily_sub_label.grid(row=13, column=0, padx=80)
self.reset_time = CTkTimeEntry(self)
self.reset_time.grid(row=12, column=1)
self.reset_time.hour_entry.bind("<KeyRelease>", lambda event, x=["ResetTime"]: self.config.save_to_json(x))
self.reset_time.minute_entry.bind("<KeyRelease>", lambda event, x=["ResetTime"]: self.config.save_to_json(x))
self.reset_time.second_entry.bind("<KeyRelease>", lambda event, x=["ResetTime"]: self.config.save_to_json(x))
self.linker.widgets["ResetDaily"] = self.reset_daily
self.linker.widgets["ResetTime"] = self.reset_time
# Helper method to create Recharge AP and Event Checkboxes
def create_recharge_and_event_checkboxes(self):
self.recharge_checkbox = customtkinter.CTkCheckBox(self, text="Recharge AP", command=lambda x=["RechargeAP"]: self.config.save_to_json(x), font=customtkinter.CTkFont(family="Inter", size=16, underline=True))
self.recharge_checkbox.grid(row=14, column=0, sticky="nw", padx=80, pady=20)
self.linker.widgets["RechargeAP"] = self.recharge_checkbox
self.recharge_tooltip = CTkToolTip(self.recharge_checkbox, wraplength=400,
message="When enabled, recharge AP when low via cafe earnings, tasks, club and mailbox if they are enabled in their respective sections.")
self.event_checkbox = customtkinter.CTkCheckBox(self, text="Sweep Event Stages", command=lambda x=["Event"]: self.config.save_to_json(x), font=customtkinter.CTkFont(family="Inter", size=16, underline=True))
self.event_tooltip = CTkToolTip(self.event_checkbox, wraplength=400, message="When enabled, the script will sweep event stages. Otherwise, it will ignore them.")
self.event_checkbox.grid(row=15, column=0, sticky="nw", padx=80)
self.linker.widgets["Event"] = self.event_checkbox
# Helper method to create Preferred Template Selection
def create_preferred_template_selection(self):
self.templates = self.config.config_data["Templates"]
self.templates_list = list(self.templates.keys())
self.preferred_template_label = customtkinter.CTkLabel(self, text="Preferred Template:", font=customtkinter.CTkFont(family="Inter", size=16, underline=True))
self.preferred_template_label.grid(row=16, column=0, pady=20)
self.preferred_template_tooltip = CTkToolTip(self.preferred_template_label, wraplength=400,
message="The template from which to repopulate the queue when it is empty or reset daily is activated")
self.preferred_template_optionmenu = customtkinter.CTkOptionMenu(self, values=self.templates_list, command=lambda x, y=["PreferredTemplate"]: self.config.save_to_json(y))
self.preferred_template_optionmenu.grid(row=16, column=1, pady=20)
self.linker.widgets["PreferredTemplate"] = self.preferred_template_optionmenu
# Helper method to create Mission Tabview with Template and Queue Tabs
def create_mission_tabview(self):
self.mission_tabview = customtkinter.CTkTabview(self)
self.mission_tabview.grid(row=17, column=0, columnspan=3, padx=20)
self.tab_template = self.mission_tabview.add('Template')
self.tab_queue = self.mission_tabview.add('Queue')
# Helper method to create Template Queue Editor
def create_template_queue_editor(self):
self.queue_buttons = []
for i in [self.tab_queue, self.tab_template]:
queue = True if i == self.tab_queue else False
self.template_labels = customtkinter.CTkFrame(i)
self.template_labels.grid(row=0, column=0, sticky="ew")
self.mode_label = customtkinter.CTkLabel(self.template_labels, text="Mode:", font=customtkinter.CTkFont(underline=True))
self.mode_tooltip = CTkToolTip(self.mode_label, message="N : Mission Normal\nH : Mission Hard\nE : Event Quest\nXP : Commissions EXP\nCR : Commissions Credits\n", justify=tk.LEFT)
self.mode_label.grid(row=1, column=0, padx=(130, 0), pady=5)
self.stage_label = customtkinter.CTkLabel(self.template_labels, text="Stage:", font=customtkinter.CTkFont(underline=True))
self.stage_tooltip = CTkToolTip(self.stage_label, message="Valid format\nMission: 1-1, 3-A\nCommissions & Event: 01", justify=tk.LEFT)
self.stage_label.grid(row=1, column=1, padx=(40, 20), pady=5)
self.run_times_label = customtkinter.CTkLabel(self.template_labels, text="Number of Sweeps:", font=customtkinter.CTkFont(underline=True))
self.run_times_tooltip = CTkToolTip(self.run_times_label, message="How many times do you want to sweep the stage?")
self.run_times_label.grid(row=1, column=2, pady=5)
self.template_buttons_frame = customtkinter.CTkFrame(i)
self.template_buttons_frame.grid(row=3, column=0)
self.highlight_label = customtkinter.CTkLabel(self.template_buttons_frame, text="*You can double click an entry and press up or down arrow to change its position", font=customtkinter.CTkFont(family="Inter", size=12))
self.highlight_label.grid(row=0, column=0, columnspan=3)
self.add_button = CTkAddButton(master=self.template_buttons_frame)
self.add_button.button.configure(command=lambda queue=queue, button=self.add_button.button: self.add_frame(queue=queue, button=button))
self.add_button.grid(row=1, column=0, padx=5, pady=5)
# Clear button to clear all frames
self.clear_button = customtkinter.CTkButton(self.template_buttons_frame, text="Clear All", command=lambda queue=queue: self.clear_frames(queue=queue), fg_color="crimson")
self.clear_button.grid(row=1, column=1, padx=5, pady=5)
# Save button to save data
self.save_button = customtkinter.CTkButton(self.template_buttons_frame, text="Save", command=lambda queue=queue: self.save_data(queue=queue), fg_color="#DC621D")
self.save_button.grid(row=1, column=2, padx=5, pady=5)
if queue:
self.queue_buttons = [self.add_button, self.clear_button, self.save_button]
# Helper method to create Template Frame and Queue Frame
def create_template_and_queue_frames(self):
self.template_frame = customtkinter.CTkScrollableFrame(self.tab_template, width=435, height=350)
self.template_frame.grid(row=1, column=0, sticky="nsew")
self.queue_frame = customtkinter.CTkScrollableFrame(self.tab_queue, width=435, height=350)
self.queue_frame.grid(row=1, column=0, sticky="nsew")
# Helper method to create Lists to Store Frame Widgets
def create_frame_lists(self):
self.template_frames = []
self.queue_frames = []
self.highlighted_frame = None
# Helper method to initialize Preferred Template and Templates List
def initialize_preferred_template(self):
self.preferred_template = self.config.config_data["PreferredTemplate"]
self.templates_list.append("Add New Template")
# Helper method to create OptionMenu for Selecting a Template
def create_template_option_menu(self):
self.selected_template = tk.StringVar(self.template_frame)
self.selected_template.set(self.preferred_template) # Set the initial value to the preferred template
self.template_optionmenu = customtkinter.CTkOptionMenu(self.template_labels, values=self.templates_list, variable=self.selected_template, command=lambda *args: self.load_template_data())
self.template_optionmenu.grid(row=0, column=0, padx=5, pady=5)
# Helper method to create Delete Template Button
def create_delete_template_button(self):
self.delete_template_button = customtkinter.CTkButton(self.template_labels, width=40, text="Delete", command=self.delete_template)
self.delete_template_button.grid(row=0, column=1)
# Helper method to add frames from Configuration Data
def load_queue_data(self, state="normal"):
for entry in self.config.config_data['Queue']:
self.add_frame(entry, queue=True, state=state)
# Function to load template data into frames
def load_template_data(self):
selected = self.selected_template.get()
if selected == "Add New Template":
dialog = CTkTemplateDialog(text="Type in new template name. Template name MUST be different from other templates!", title="Template Name", values=self.templates_list[:-1])
template_name, template_import = dialog.get_input()
if template_name.replace(" ", "") == "":
self.template_optionmenu.set(self.previous_selected)
return
elif template_name in self.templates_list:
CTkMessagebox(title="Error", message="Name is invalid.", icon="MCE\icons\cancel.png")
self.template_optionmenu.set(self.previous_selected)
return
else:
if template_import != "":
self.templates[template_name] = self.templates[template_import]
else:
self.templates[template_name] = []
self.templates_list[-1] = template_name
self.preferred_template_optionmenu.configure(values=self.templates_list)
selected = template_name
self.template_optionmenu.set(selected)
self.templates_list.append("Add New Template")
self.template_optionmenu.configure(values=self.templates_list)
self.clear_frames()
for entry in self.templates[selected]:
self.add_frame(entry)
self.previous_selected = selected
def delete_template(self):
msg = CTkMessagebox(title="Template Deletetion", message=f"Are you sure you want to delete Template {self.previous_selected}?",
icon="MCE\icons\question.png", option_1="No", option_2="Yes")
response = msg.get()
if response=="Yes":
if len(self.templates) != 1:
del self.templates[self.previous_selected]
self.templates_list = list(self.templates.keys())
self.preferred_template_optionmenu.configure(values=self.templates_list)
if self.preferred_template == self.previous_selected:
self.preferred_template = random.choice(self.templates_list)
self.config.config_data["PreferredTemplate"] = self.preferred_template
self.selected_template.set(self.preferred_template) # Set the initial value to the preferred template
self.preferred_template_optionmenu.set(self.preferred_template)
self.load_template_data()
self.config.save_file()
self.templates_list.append("Add New Template")
self.template_optionmenu.configure(values=self.templates_list)
self.template_optionmenu.set(self.preferred_template)
else:
CTkMessagebox(title="Error", message="At least one template must exist!!!", icon="MCE\icons\cancel.png")
return
# Function to add a frame with widgets
def add_frame(self, inner_list=None, queue=False, state="normal", button=None):
position = button.cget("text") if button else "Add Down"
frames = self.queue_frames if queue else self.template_frames
parent_frame = self.queue_frame if queue else self.template_frame
row_index = len(frames) + 1 # Calculate the row for the new frame
# Create a frame
frame = tk.Frame(parent_frame, bg="gray17")
frame.grid(row=row_index, column=0, columnspan=4, padx=10, pady=10, sticky="w")
frames.append(frame) if position == "Add Down" else frames.insert(0, frame)
# "Up" button to move the frame up
up_button = customtkinter.CTkButton(frame, text="Up", width=5, command=lambda f=frame, queue=queue: self.move_frame_up(f, queue), state=state)
up_button.grid(row=0, column=0, padx=5, pady=5, sticky="w")
# "Down" button to move the frame down
down_button = customtkinter.CTkButton(frame, text="Down", width=5, command=lambda f=frame, queue=queue: self.move_frame_down(f, queue), state=state)
down_button.grid(row=0, column=1, padx=5, pady=5, sticky="w")
# Dropdown menu for mode
mode_optionmenu = customtkinter.CTkOptionMenu(frame, width=60, values=["N", "H", "E", "XP", "CR"], state=state)
mode_optionmenu.set(inner_list[0] if inner_list else "N")
mode_optionmenu.grid(row=0, column=2, padx=5, pady=5, sticky="w")
# Entry widget for stage
stage_var = tk.StringVar(value=inner_list[1] if inner_list else "")
stage_entry = customtkinter.CTkEntry(frame, width=60, textvariable=stage_var, state=state)
stage_entry.grid(row=0, column=3, padx=5, pady=5, sticky="w")
mode_optionmenu.configure(command=lambda choice, x=mode_optionmenu, y=stage_entry : self.check_entry(x,y))
stage_entry.bind('<KeyRelease>', command=lambda event, x=mode_optionmenu, y=stage_entry : self.check_entry(x,y))
self.check_entry(mode_optionmenu, stage_entry)
# Entry widget for run times (only accepts numbers)
run_times_spinbox = CTkIntegerSpinbox(frame, step_size=1, min_value=1)
run_times_spinbox.set(value=inner_list[2] if inner_list else 1)
run_times_spinbox.grid(row=0, column=4, padx=5, pady=5, sticky="w")
# Delete button to delete the frame
delete_button = customtkinter.CTkButton(frame, text="Delete", width=5, command=lambda f=frame, queue=queue: self.delete_frame(f, queue), state=state)
delete_button.grid(row=0, column=5, padx=5, pady=5, sticky="w")
frame.bind("<Double-Button-1>", lambda event, f=frame: self.highlight_frame(f))
if position == "Add Up":
self.update_frame_positions(queue=queue)
# Function to clear all frames
def clear_frames(self, queue=False):
frames = self.queue_frames if queue else self.template_frames
for frame in frames:
frame.destroy()
frames.clear()
# Function to save frames as data
def save_data(self, queue=False):
entries = []
frames = self.queue_frames if queue else self.template_frames
name = "Queue" if queue else "Template"
for frame in frames:
mode_optionmenu = frame.winfo_children()[2]
stage_entry = frame.winfo_children()[3]
if not self.check_entry(mode_optionmenu, stage_entry):
CTkMessagebox(title="Error", message="Configuration not saved. Some entries are incomplete or have incorect input.", icon="MCE\icons\cancel.png")
return
mode = frame.winfo_children()[2].get()
stage = frame.winfo_children()[3].get().strip()
run_times = frame.winfo_children()[4].get()
entries.append([mode, stage, int(run_times)])
if queue:
self.config.config_data['Queue'] = entries
else:
selected = self.selected_template.get()
self.templates[selected] = entries
self.config.save_file(name)
def check_entry(self, mode_dropdown, stage_entry):
mode = mode_dropdown.get()
stage = stage_entry.get()
if mode in ["N", "H"]:
pattern = r'\d{1,2}-[0-9A-Z]'
else:
pattern = r"^\d{2}$"
if re.match(pattern, stage):
stage_entry.configure(border_color=['#979DA2', '#565B5E'])
return True
else:
stage_entry.configure(border_color='crimson')
return False
# Function to move a frame up
def move_frame_up(self, frame, queue=False):
frames = self.queue_frames if queue else self.template_frames
index = frames.index(frame)
if index > 0:
frames[index], frames[index - 1] = frames[index - 1], frames[index]
self.update_frame_positions(queue=queue)
# Function to move a frame down
def move_frame_down(self, frame, queue=False):
frames = self.queue_frames if queue else self.template_frames
index = frames.index(frame)
if index < len(frames) - 1:
frames[index], frames[index + 1] = frames[index + 1], frames[index]
self.update_frame_positions(queue=queue)
# Function to update frame positions in the grid
def update_frame_positions(self, queue=False):
frames = self.queue_frames if queue else self.template_frames
for index, frame in enumerate(frames):
frame.grid(row=index, column=0, columnspan=4, padx=10, pady=10, sticky="w")
# Function to delete a frame
def delete_frame(self, frame, queue=False):
if queue:
self.queue_frames.remove(frame)
else:
self.template_frames.remove(frame)
frame.destroy()
# Update the positions of remaining frames
self.update_frame_positions(queue=queue)
def highlight_frame(self, frame):
try:
if self.highlighted_frame is not None:
self.highlighted_frame.unbind("<Up>")
self.highlighted_frame.unbind("<Down>")
self.highlighted_frame.config(bg="gray17")
except:
pass
if self.highlighted_frame == frame:
self.highlighted_frame = None
else:
up_button = frame.winfo_children()[0]
down_button = frame.winfo_children()[1]
frame.config(bg="yellow")
frame.bind("<Up>", lambda event: up_button.invoke())
frame.bind("<Down>", lambda event: down_button.invoke())
frame.focus_set()
self.highlighted_frame = frame
def check_lock(self):
while 1:
try:
lock = FileLock("MCE\config.json.lock")
lock.acquire(timeout=1)
except Timeout:
if not self.config.locked:
self.after(10, lambda : (self.queue_changed(), self.update_queue(), self.switch_queue_state("disabled")))
self.config.locked = True
elif self.config.locked and self.queue_changed():
self.after(10, lambda : (self.update_queue(), self.switch_queue_state("disabled")))
else:
lock.release()
if self.config.locked:
self.after(10, lambda : (self.queue_changed(), self.update_queue(), self.switch_queue_state("normal")))
self.config.locked = False
finally:
time.sleep(2)
def switch_queue_state(self, state):
for button in self.queue_buttons:
button.configure(state=state)
for frame in self.queue_frames:
for widget in frame.winfo_children():
widget.configure(state=state)
def update_queue(self):
self.clear_frames(queue=True)
for entry in self.config.config_data['Queue']:
self.add_frame(entry, queue=True)
def queue_changed(self):
new_config_data = self.config.read()
changed = self.config.config_data["Queue"] != new_config_data["Queue"] or self.config.config_data["LastRun"] != new_config_data["LastRun"]
if changed:
self.config.config_data["LastRun"] = new_config_data["LastRun"]
self.config.config_data['Queue'] = new_config_data['Queue']
return changed
if __name__ == "__main__":
linker = Linker()
config = Config(linker, "MCE\config.json")
app = MCE_Manager(linker, config)
app.title("MCE Manager")
linker.sidebar = app
config.load_config()
daemon_thread = threading.Thread(target=app.check_lock, daemon=True)
daemon_thread.start()
app.mainloop()
View File
+19
View File
@@ -0,0 +1,19 @@
import customtkinter
class CTkAddButton(customtkinter.CTkFrame):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.button = customtkinter.CTkButton(self, text="Add Down", corner_radius=0, width=120)
self.button.grid(row=0, column=0)
self.option_menu = customtkinter.CTkOptionMenu(
self, values= ["Add Up", "Add Down"], width=10, command=self.set_button, corner_radius=0
)
self.option_menu.set("")
self.option_menu.grid(row=0, column=1)
def set_button(self, value):
self.option_menu.set("")
self.button.configure(text=value)
def configure(self, *args, **kwargs):
self.button.configure(*args, **kwargs)
+95
View File
@@ -0,0 +1,95 @@
import customtkinter
import re
from typing import Callable
class CTkIntegerSpinbox(customtkinter.CTkFrame):
def __init__(self, *args,
width: int = 100,
height: int = 32,
step_size: int = 1,
min_value: int = 0,
command: Callable = None,
**kwargs):
super().__init__(*args, width=width, height=height, **kwargs)
self.step_size = step_size
self.min_value = min_value
self.command = command
self.after_id = None
self.grid_columnconfigure((0, 2), weight=0)
self.grid_columnconfigure(1, weight=1)
self.subtract_button = customtkinter.CTkButton(self, text="-", width=height-6, height=height-6)
self.subtract_button.grid(row=0, column=0, padx=(3, 0), pady=3)
self.subtract_button.bind('<ButtonPress-1>', self.start_decrementing)
self.subtract_button.bind('<ButtonRelease-1>', self.stop_decrementing)
self.entry = customtkinter.CTkEntry(self, width=width-(2*height), height=height-6, border_width=0)
self.entry.grid(row=0, column=1, columnspan=1, padx=3, pady=3, sticky="ew")
self.add_button = customtkinter.CTkButton(self, text="+", width=height-6, height=height-6)
self.add_button.grid(row=0, column=2, padx=(0, 3), pady=3)
self.add_button.bind('<ButtonPress-1>', self.start_incrementing)
self.add_button.bind('<ButtonRelease-1>', self.stop_incrementing)
self.entry.insert(0, "0")
# Configure validatecommand to allow only integers
vcmd = (self.entry.register(self.validate_input), '%P')
self.entry.configure(validate='key', validatecommand=vcmd)
def start_incrementing(self, event):
self.increment()
self.after_id = self.after(150, self.start_incrementing, event)
def stop_incrementing(self, event):
if self.after_id:
self.after_cancel(self.after_id)
self.after_id = None
def start_decrementing(self, event):
self.decrement()
self.after_id = self.after(150, self.start_decrementing, event)
def stop_decrementing(self, event):
if self.after_id:
self.after_cancel(self.after_id)
self.after_id = None
def increment(self):
value = int(self.entry.get()) + self.step_size
self.entry.delete(0, "end")
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 1
if self.command is not None:
self.command()
def decrement(self):
value = int(self.entry.get()) - self.step_size
self.entry.delete(0, "end")
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 0
if self.command is not None:
self.command()
def validate_input(self, new_value):
# Validate that the input is a non-negative integer
return re.match(r'^\d*$', new_value) is not None
def get(self) -> int:
try:
return int(self.entry.get())
except ValueError:
return 0
def set(self, value: int):
self.entry.delete(0, "end")
self.entry.insert(0, max(self.min_value, value)) # Ensure the value is not less than 0
def configure(self, **kwargs):
state = kwargs.get("state", None)
if state is not None:
self.subtract_button.configure(state=state)
self.add_button.configure(state=state)
self.entry.configure(state=state)
kwargs.pop("state")
super().configure(**kwargs)
+27
View File
@@ -0,0 +1,27 @@
import customtkinter
class CTkNotification(customtkinter.CTkFrame):
def __init__(self, text, master, **kwargs):
self.master_color = master.cget("fg_color")
super().__init__(master=master, **kwargs, fg_color=self.master_color)
self.label = customtkinter.CTkLabel(self, text=text, text_color=self.master_color, width=200, wraplength=200, font=("Inter", 16))
self.label.grid(row=0, column=0, sticky="nsew")
self.close_button = customtkinter.CTkButton(
self, width=40, text="X", text_color_disabled=self.master_color, command=self.hide, fg_color="transparent", state="disabled")
self.close_button.grid(row=0, column=1)
self.progress_bar = customtkinter.CTkProgressBar(self, determinate_speed=0.4, fg_color=self.master_color, progress_color=self.master_color)
self.progress_bar.grid(row=1, column=0, columnspan=2, sticky="nsew")
def hide(self):
self.configure(fg_color="transparent")
self.progress_bar.stop()
self.progress_bar.configure(progress_color=self.master_color)
self.close_button.configure(state="disabled")
self.label.configure(text_color=self.master_color)
def show(self):
self.configure(fg_color="green")
self.progress_bar.configure(progress_color="white")
self.progress_bar.set(0)
self.progress_bar.start()
self.close_button.configure(state="normal")
self.label.configure(text_color="white")
@@ -0,0 +1,337 @@
'''
Advanced Scrollable Dropdown class for customtkinter widgets
Author: Akash Bora
'''
import customtkinter
import sys
import time
import difflib
class CTkScrollableDropdown(customtkinter.CTkToplevel):
def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None,
fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None,
scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[],
command=None, image_values=[], alpha: float = 0.97, frame_corner_radius=20, double_click=False,
resize=True, frame_border_color=None, text_color=None, autocomplete=False, **button_kwargs):
super().__init__(takefocus=1)
self.focus()
self.lift()
self.alpha = alpha
self.attach = attach
self.corner = frame_corner_radius
self.padding = 0
self.focus_something = False
self.disable = True
self.update()
if sys.platform.startswith("win"):
self.after(100, lambda: self.overrideredirect(True))
self.transparent_color = self._apply_appearance_mode(self._fg_color)
self.attributes("-transparentcolor", self.transparent_color)
elif sys.platform.startswith("darwin"):
self.overrideredirect(True)
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
self.focus_something = True
else:
self.overrideredirect(True)
self.transparent_color = '#000001'
self.corner = 0
self.padding = 18
self.withdraw()
self.hide = True
self.attach.bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
self.attach.winfo_toplevel().bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
self.attach.winfo_toplevel().bind("<ButtonPress>", lambda e: self._withdraw() if not self.disable else None, add="+")
self.attributes('-alpha', 0)
self.disable = False
self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color
self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color
self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color
self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color
self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color
if scrollbar is False:
self.scroll_button_color = self.fg_color
self.scroll_hover_color = self.fg_color
self.frame = customtkinter.CTkScrollableFrame(self, bg_color=self.transparent_color, fg_color=self.fg_color,
scrollbar_button_hover_color=self.scroll_hover_color,
corner_radius=self.corner, border_width=frame_border_width,
scrollbar_button_color=self.scroll_button_color,
border_color=self.frame_border_color)
self.frame._scrollbar.grid_configure(padx=3)
self.frame.pack(expand=True, fill="both")
self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1)
self.no_match = customtkinter.CTkLabel(self.frame, text="No Match")
self.height = height
self.height_new = height
self.width = width
self.command = command
self.fade = False
self.resize = resize
self.autocomplete = autocomplete
self.var_update = customtkinter.StringVar()
self.appear = False
if justify.lower()=="left":
self.justify = "w"
elif justify.lower()=="right":
self.justify = "e"
else:
self.justify = "c"
self.button_height = button_height
self.values = values
self.button_num = len(self.values)
self.image_values = None if len(image_values)!=len(self.values) else image_values
self.resizable(width=False, height=False)
self.transient(self.master)
self._init_buttons(**button_kwargs)
# Add binding for different ctk widgets
if double_click or self.attach.winfo_name().startswith("!ctkentry") or self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach.bind('<Double-Button-1>', lambda e: self._iconify(), add="+")
else:
self.attach.bind('<Button-1>', lambda e: self._iconify(), add="+")
if self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach._canvas.tag_bind("right_parts", "<Button-1>", lambda e: self._iconify())
self.attach._canvas.tag_bind("dropdown_arrow", "<Button-1>", lambda e: self._iconify())
if self.command is None:
self.command = self.attach.set
if self.attach.winfo_name().startswith("!ctkoptionmenu"):
self.attach._canvas.bind("<Button-1>", lambda e: self._iconify())
self.attach._text_label.bind("<Button-1>", lambda e: self._iconify())
if self.command is None:
self.command = self.attach.set
self.attach.bind("<Destroy>", lambda _: self._destroy(), add="+")
self.update_idletasks()
self.x = x
self.y = y
if self.autocomplete:
self.bind_autocomplete()
self.deiconify()
self.withdraw()
self.attributes("-alpha", self.alpha)
def _destroy(self):
self.after(500, self.destroy_popup)
def _withdraw(self):
if self.winfo_viewable() and self.hide:
self.withdraw()
self.event_generate("<<Closed>>")
self.hide = True
def _update(self, a, b, c):
self.live_update(self.attach._entry.get())
def bind_autocomplete(self, ):
def appear(x):
self.appear = True
if self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach._entry.configure(textvariable=self.var_update)
self.attach._entry.bind("<Key>", appear)
self.attach.set(self.values[0])
self.var_update.trace_add('write', self._update)
if self.attach.winfo_name().startswith("!ctkentry"):
self.attach.configure(textvariable=self.var_update)
self.attach.bind("<Key>", appear)
self.var_update.trace_add('write', self._update)
def fade_out(self):
for i in range(100,0,-10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/100)
def fade_in(self):
for i in range(0,100,10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/100)
def _init_buttons(self, **button_kwargs):
self.i = 0
self.widgets = {}
for row in self.values:
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
text=row,
height=self.button_height,
fg_color=self.button_color,
text_color=self.text_color,
image=self.image_values[i] if self.image_values is not None else None,
anchor=self.justify,
command=lambda k=row: self._attach_key_press(k), **button_kwargs)
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
self.i+=1
self.hide = False
def destroy_popup(self):
self.destroy()
self.disable = True
def place_dropdown(self):
self.x_pos = self.attach.winfo_rootx() if self.x is None else self.x + self.attach.winfo_rootx()
self.y_pos = self.attach.winfo_rooty() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty()
self.width_new = self.attach.winfo_width() if self.width is None else self.width
if self.resize:
if self.button_num<=5:
self.height_new = self.button_height * self.button_num + 55
else:
self.height_new = self.button_height * self.button_num + 35
if self.height_new>self.height:
self.height_new = self.height
self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new,
self.x_pos, self.y_pos))
self.fade_in()
self.attributes('-alpha', self.alpha)
self.attach.focus()
def _iconify(self):
if self.disable: return
if self.hide:
self.event_generate("<<Opened>>")
self._deiconify()
self.focus()
self.hide = False
self.place_dropdown()
if self.focus_something:
self.dummy_entry.pack()
self.dummy_entry.focus_set()
self.after(100, self.dummy_entry.pack_forget)
else:
self.withdraw()
self.hide = True
def _attach_key_press(self, k):
self.event_generate("<<Selected>>")
self.fade = True
if self.command:
self.command(k)
self.fade = False
self.fade_out()
self.withdraw()
self.hide = True
def live_update(self, string=None):
if not self.appear: return
if self.disable: return
if self.fade: return
if string:
string = string.lower()
self._deiconify()
i=1
for key in self.widgets.keys():
s = self.widgets[key].cget("text").lower()
text_similarity = difflib.SequenceMatcher(None, s[0:len(string)], string).ratio()
similar = s.startswith(string) or text_similarity > 0.75
if not similar:
self.widgets[key].pack_forget()
else:
self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0))
i+=1
if i==1:
self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0))
else:
self.no_match.pack_forget()
self.button_num = i
self.place_dropdown()
else:
self.no_match.pack_forget()
self.button_num = len(self.values)
for key in self.widgets.keys():
self.widgets[key].destroy()
self._init_buttons()
self.place_dropdown()
self.frame._parent_canvas.yview_moveto(0.0)
self.appear = False
def insert(self, value, **kwargs):
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
text=value,
height=self.button_height,
fg_color=self.button_color,
text_color=self.text_color,
anchor=self.justify,
command=lambda k=value: self._attach_key_press(k), **kwargs)
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
self.i+=1
self.values.append(value)
def _deiconify(self):
if len(self.values)>0:
self.deiconify()
def popup(self, x=None, y=None):
self.x = x
self.y = y
self.hide = True
self._iconify()
def configure(self, **kwargs):
if "height" in kwargs:
self.height = kwargs.pop("height")
self.height_new = self.height
if "alpha" in kwargs:
self.alpha = kwargs.pop("alpha")
if "width" in kwargs:
self.width = kwargs.pop("width")
if "fg_color" in kwargs:
self.frame.configure(fg_color=kwargs.pop("fg_color"))
if "values" in kwargs:
self.values = kwargs.pop("values")
self.image_values = None
self.button_num = len(self.values)
for key in self.widgets.keys():
self.widgets[key].destroy()
self._init_buttons()
if "image_values" in kwargs:
self.image_values = kwargs.pop("image_values")
self.image_values = None if len(self.image_values)!=len(self.values) else self.image_values
if self.image_values is not None:
i=0
for key in self.widgets.keys():
self.widgets[key].configure(image=self.image_values[i])
i+=1
if "button_color" in kwargs:
for key in self.widgets.keys():
self.widgets[key].configure(fg_color=kwargs.pop("button_color"))
for key in self.widgets.keys():
self.widgets[key].configure(**kwargs)
@@ -0,0 +1,291 @@
'''
Advanced Scrollable Dropdown Frame class for customtkinter widgets
Author: Akash Bora
'''
import customtkinter
import sys
import difflib
class CTkScrollableDropdownFrame(customtkinter.CTkFrame):
def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None,
fg_color=None, button_height: int = 20, justify="center", scrollbar_button_color=None,
scrollbar=True, scrollbar_button_hover_color=None, frame_border_width=2, values=[],
command=None, image_values=[], double_click=False, frame_corner_radius=True, resize=True, frame_border_color=None,
text_color=None, autocomplete=False, **button_kwargs):
super().__init__(master=attach.winfo_toplevel(), bg_color=attach.cget("bg_color"))
self.attach = attach
self.corner = 11 if frame_corner_radius else 0
self.padding = 0
self.disable = True
self.hide = True
self.attach.bind('<Configure>', lambda e: self._withdraw() if not self.disable else None, add="+")
self.attach.winfo_toplevel().bind("<ButtonPress>", lambda e: self._withdraw() if not self.disable else None, add="+")
self.disable = False
self.fg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if fg_color is None else fg_color
self.scroll_button_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_color"] if scrollbar_button_color is None else scrollbar_button_color
self.scroll_hover_color = customtkinter.ThemeManager.theme["CTkScrollbar"]["button_hover_color"] if scrollbar_button_hover_color is None else scrollbar_button_hover_color
self.frame_border_color = customtkinter.ThemeManager.theme["CTkFrame"]["border_color"] if frame_border_color is None else frame_border_color
self.button_color = customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"] if button_color is None else button_color
self.text_color = customtkinter.ThemeManager.theme["CTkLabel"]["text_color"] if text_color is None else text_color
if scrollbar is False:
self.scroll_button_color = self.fg_color
self.scroll_hover_color = self.fg_color
self.frame = customtkinter.CTkScrollableFrame(self, fg_color=self.fg_color, bg_color=attach.cget("bg_color"),
scrollbar_button_hover_color=self.scroll_hover_color,
corner_radius=self.corner, border_width=frame_border_width,
scrollbar_button_color=self.scroll_button_color,
border_color=self.frame_border_color)
self.frame._scrollbar.grid_configure(padx=3)
self.frame.pack(expand=True, fill="both")
if self.corner==0:
self.corner = 21
self.dummy_entry = customtkinter.CTkEntry(self.frame, fg_color="transparent", border_width=0, height=1, width=1)
self.no_match = customtkinter.CTkLabel(self.frame, text="No Match")
self.height = height
self.height_new = height
self.width = width
self.command = command
self.fade = False
self.resize = resize
self.autocomplete = autocomplete
self.var_update = customtkinter.StringVar()
self.appear = False
if justify.lower()=="left":
self.justify = "w"
elif justify.lower()=="right":
self.justify = "e"
else:
self.justify = "c"
self.button_height = button_height
self.values = values
self.button_num = len(self.values)
self.image_values = None if len(image_values)!=len(self.values) else image_values
self._init_buttons(**button_kwargs)
# Add binding for different ctk widgets
if double_click or self.attach.winfo_name().startswith("!ctkentry") or self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach.bind('<Double-Button-1>', lambda e: self._iconify(), add="+")
self.attach._entry.bind('<FocusOut>', lambda e: self._withdraw() if not self.disable else None, add="+")
else:
self.attach.bind('<Button-1>', lambda e: self._iconify(), add="+")
if self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach._canvas.tag_bind("right_parts", "<Button-1>", lambda e: self._iconify())
self.attach._canvas.tag_bind("dropdown_arrow", "<Button-1>", lambda e: self._iconify())
if self.command is None:
self.command = self.attach.set
if self.attach.winfo_name().startswith("!ctkoptionmenu"):
self.attach._canvas.bind("<Button-1>", lambda e: self._iconify())
self.attach._text_label.bind("<Button-1>", lambda e: self._iconify())
if self.command is None:
self.command = self.attach.set
self.x = x
self.y = y
self.attach.bind("<Destroy>", lambda _: self._destroy(), add="+")
if self.autocomplete:
self.bind_autocomplete()
def _destroy(self):
self.after(500, self.destroy_popup)
def _withdraw(self):
if self.winfo_viewable() and self.hide:
self.place_forget()
self.event_generate("<<Closed>>")
self.hide = True
def _update(self, a, b, c):
self.live_update(self.attach._entry.get())
def bind_autocomplete(self, ):
def appear(x):
self.appear = True
if self.attach.winfo_name().startswith("!ctkcombobox"):
self.attach._entry.configure(textvariable=self.var_update)
self.attach.set(self.values[0])
self.attach._entry.bind("<Key>", appear)
self.var_update.trace_add('write', self._update)
if self.attach.winfo_name().startswith("!ctkentry"):
self.attach.configure(textvariable=self.var_update)
self.attach.bind("<Key>", appear)
self.var_update.trace_add('write', self._update)
def _init_buttons(self, **button_kwargs):
self.i = 0
self.widgets = {}
for row in self.values:
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
text=row,
height=self.button_height,
fg_color=self.button_color,
text_color=self.text_color,
image=self.image_values[i] if self.image_values is not None else None,
anchor=self.justify,
command=lambda k=row: self._attach_key_press(k), **button_kwargs)
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
self.i+=1
self.hide = False
def destroy_popup(self):
self.destroy()
self.disable = True
def place_dropdown(self):
self.x_pos = self.attach.winfo_x() if self.x is None else self.x + self.attach.winfo_rootx()
self.y_pos = self.attach.winfo_y() + self.attach.winfo_reqheight() + 5 if self.y is None else self.y + self.attach.winfo_rooty()
self.width_new = self.attach.winfo_width()-45+self.corner if self.width is None else self.width
if self.resize:
if self.button_num<=5:
self.height_new = self.button_height * self.button_num + 55
else:
self.height_new = self.button_height * self.button_num + 35
if self.height_new>self.height:
self.height_new = self.height
self.frame.configure(width=self.width_new, height=self.height_new)
self.place(x=self.x_pos, y=self.y_pos)
if sys.platform.startswith("darwin"):
self.dummy_entry.pack()
self.after(100, self.dummy_entry.pack_forget())
self.lift()
self.attach.focus()
def _iconify(self):
if self.disable: return
if self.hide:
self.event_generate("<<Opened>>")
self.hide = False
self.place_dropdown()
else:
self.place_forget()
self.hide = True
def _attach_key_press(self, k):
self.event_generate("<<Selected>>")
self.fade = True
if self.command:
self.command(k)
self.fade = False
self.place_forget()
self.hide = True
def live_update(self, string=None):
if not self.appear: return
if self.disable: return
if self.fade: return
if string:
string = string.lower()
self._deiconify()
i=1
for key in self.widgets.keys():
s = self.widgets[key].cget("text").lower()
text_similarity = difflib.SequenceMatcher(None, s[0:len(string)], string).ratio()
similar = s.startswith(string) or text_similarity > 0.75
if not similar:
self.widgets[key].pack_forget()
else:
self.widgets[key].pack(fill="x", pady=2, padx=(self.padding, 0))
i+=1
if i==1:
self.no_match.pack(fill="x", pady=2, padx=(self.padding, 0))
else:
self.no_match.pack_forget()
self.button_num = i
self.place_dropdown()
else:
self.no_match.pack_forget()
self.button_num = len(self.values)
for key in self.widgets.keys():
self.widgets[key].destroy()
self._init_buttons()
self.place_dropdown()
self.frame._parent_canvas.yview_moveto(0.0)
self.appear = False
def insert(self, value, **kwargs):
self.widgets[self.i] = customtkinter.CTkButton(self.frame,
text=value,
height=self.button_height,
fg_color=self.button_color,
text_color=self.text_color,
anchor=self.justify,
command=lambda k=value: self._attach_key_press(k), **kwargs)
self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0))
self.i+=1
self.values.append(value)
def _deiconify(self):
if len(self.values)>0:
self.pack_forget()
def popup(self, x=None, y=None):
self.x = x
self.y = y
self.hide = True
self._iconify()
def configure(self, **kwargs):
if "height" in kwargs:
self.height = kwargs.pop("height")
self.height_new = self.height
if "alpha" in kwargs:
self.alpha = kwargs.pop("alpha")
if "width" in kwargs:
self.width = kwargs.pop("width")
if "fg_color" in kwargs:
self.frame.configure(fg_color=kwargs.pop("fg_color"))
if "values" in kwargs:
self.values = kwargs.pop("values")
self.image_values = None
self.button_num = len(self.values)
for key in self.widgets.keys():
self.widgets[key].destroy()
self._init_buttons()
if "image_values" in kwargs:
self.image_values = kwargs.pop("image_values")
self.image_values = None if len(self.image_values)!=len(self.values) else self.image_values
if self.image_values is not None:
i=0
for key in self.widgets.keys():
self.widgets[key].configure(image=self.image_values[i])
i+=1
if "button_color" in kwargs:
for key in self.widgets.keys():
self.widgets[key].configure(fg_color=kwargs.pop("button_color"))
for key in self.widgets.keys():
self.widgets[key].configure(**kwargs)
+133
View File
@@ -0,0 +1,133 @@
from typing import Union, Tuple, Optional
from customtkinter import CTkLabel, CTkEntry, CTkButton, ThemeManager, CTkToplevel, CTkFont, CTkOptionMenu
class CTkTemplateDialog(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",
values = []):
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 = ("", "")
self._running: bool = False
self._title = title
self._text = text
self._font = font
self._values = [""] + values
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._label2 = CTkLabel(master=self,
width=100,
wraplength=100,
fg_color="transparent",
text_color=self._text_color,
text="Import stages from: ",
font=self._font)
self._label2.grid(row=2, column=0, columnspan=1, padx=(20, 10), pady=(0, 20), sticky="ew")
self._template_optionmenu = CTkOptionMenu(master=self,
width=100,
fg_color=self._button_fg_color,
button_hover_color=self._button_hover_color,
text_color=self._button_text_color,
font=self._font,
values=self._values
)
self._template_optionmenu.grid(row=2, column=1, columnspan=1, padx=(10, 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=3, 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=3, 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("<Return>", self._ok_event)
def _ok_event(self, event=None):
self._user_input = self._entry.get(), self._template_optionmenu.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
+36
View File
@@ -0,0 +1,36 @@
import customtkinter
import tkinter as tk
class CTkTimeEntry(customtkinter.CTkFrame):
def __init__(self, master=None, **kwargs):
super().__init__(master, **kwargs)
self.hour = tk.StringVar()
self.minute = tk.StringVar()
self.second = tk.StringVar()
self.hour_entry = customtkinter.CTkEntry(self, width=50, textvariable=self.hour, validate="key", validatecommand=(self.register(self.validate_hour), '%P'))
self.hour_entry.pack(side=tk.LEFT)
self.minute_entry = customtkinter.CTkEntry(self,width=50, textvariable=self.minute, validate="key", validatecommand=(self.register(self.validate_min_sec), '%P'))
self.minute_entry.pack(side=tk.LEFT)
self.second_entry = customtkinter.CTkEntry(self, width=50, textvariable=self.second, validate="key", validatecommand=(self.register(self.validate_min_sec), '%P'))
self.second_entry.pack(side=tk.LEFT)
def validate_hour(self, P):
return len(P) <= 2 and (P.isdigit() and int(P) <= 23 or P == "")
def validate_min_sec(self, P):
return len(P) <= 2 and (P.isdigit() and int(P) <= 59 or P == "")
def set(self, time_str):
h, m, s = map(str, time_str.split(':'))
self.hour.set(h)
self.minute.set(m)
self.second.set(s)
def get(self):
h = self.hour.get() if self.hour.get() else "00"
m = self.minute.get() if self.minute.get() else "00"
s = self.second.get() if self.second.get() else "00"
return f"{h.zfill(2)}:{m.zfill(2)}:{s.zfill(2)}"
+212
View File
@@ -0,0 +1,212 @@
"""
CTkToolTip Widget
version: 0.8
"""
import time
import sys
import customtkinter
from tkinter import Toplevel, Frame
class CTkToolTip(Toplevel):
"""
Creates a ToolTip (pop-up) widget for customtkinter.
"""
def __init__(
self,
widget: any = None,
message: str = None,
delay: float = 0.2,
follow: bool = True,
x_offset: int = +20,
y_offset: int = +10,
bg_color: str = None,
corner_radius: int = 10,
border_width: int = 0,
border_color: str = None,
alpha: float = 0.95,
padding: tuple = (10, 2),
**message_kwargs):
super().__init__()
self.widget = widget
self.withdraw()
# Disable ToolTip's title bar
self.overrideredirect(True)
if sys.platform.startswith("win"):
self.transparent_color = self.widget._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkToplevel"]["fg_color"])
self.attributes("-transparentcolor", self.transparent_color)
self.transient()
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
self.transient(self.master)
else:
self.transparent_color = '#000001'
corner_radius = 0
self.transient()
self.resizable(width=True, height=True)
# Make the background transparent
self.config(background=self.transparent_color)
# StringVar instance for msg string
self.messageVar = customtkinter.StringVar()
self.message = message
self.messageVar.set(self.message)
self.delay = delay
self.follow = follow
self.x_offset = x_offset
self.y_offset = y_offset
self.corner_radius = corner_radius
self.alpha = alpha
self.border_width = border_width
self.padding = padding
self.bg_color = customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"] if bg_color is None else bg_color
self.border_color = border_color
self.disable = False
# visibility status of the ToolTip inside|outside|visible
self.status = "outside"
self.last_moved = 0
self.attributes('-alpha', self.alpha)
if sys.platform.startswith("win"):
if self.widget._apply_appearance_mode(self.bg_color) == self.transparent_color:
self.transparent_color = "#000001"
self.config(background=self.transparent_color)
self.attributes("-transparentcolor", self.transparent_color)
# Add the message widget inside the tooltip
self.transparent_frame = Frame(self, bg=self.transparent_color)
self.transparent_frame.pack(padx=0, pady=0, fill="both", expand=True)
self.frame = customtkinter.CTkFrame(self.transparent_frame, bg_color=self.transparent_color,
corner_radius=self.corner_radius,
border_width=self.border_width, fg_color=self.bg_color,
border_color=self.border_color)
self.frame.pack(padx=0, pady=0, fill="both", expand=True)
self.message_label = customtkinter.CTkLabel(self.frame, textvariable=self.messageVar, **message_kwargs)
self.message_label.pack(fill="both", padx=self.padding[0] + self.border_width,
pady=self.padding[1] + self.border_width, expand=True)
if self.widget.winfo_name() != "tk":
if self.frame.cget("fg_color") == self.widget.cget("bg_color"):
if not bg_color:
self._top_fg_color = self.frame._apply_appearance_mode(
customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
if self._top_fg_color != self.transparent_color:
self.frame.configure(fg_color=self._top_fg_color)
# Add bindings to the widget without overriding the existing ones
self.widget.bind("<Enter>", self.on_enter, add="+")
self.widget.bind("<Leave>", self.on_leave, add="+")
self.widget.bind("<Motion>", self.on_enter, add="+")
self.widget.bind("<B1-Motion>", self.on_enter, add="+")
self.widget.bind("<Destroy>", lambda _: self.hide(), add="+")
def show(self) -> None:
"""
Enable the widget.
"""
self.disable = False
def on_enter(self, event) -> None:
"""
Processes motion within the widget including entering and moving.
"""
if self.disable:
return
self.last_moved = time.time()
# Set the status as inside for the very first time
if self.status == "outside":
self.status = "inside"
# If the follow flag is not set, motion within the widget will make the ToolTip dissapear
if not self.follow:
self.status = "inside"
self.withdraw()
# Calculate available space on the right side of the widget relative to the screen
root_width = self.winfo_screenwidth()
widget_x = event.x_root
space_on_right = root_width - widget_x
# Calculate the width of the tooltip's text based on the length of the message string
text_width = self.message_label.winfo_reqwidth()
# Calculate the offset based on available space and text width to avoid going off-screen on the right side
offset_x = self.x_offset
if space_on_right < text_width + 20: # Adjust the threshold as needed
offset_x = -text_width - 20 # Negative offset when space is limited on the right side
# Offsets the ToolTip using the coordinates od an event as an origin
self.geometry(f"+{event.x_root + offset_x}+{event.y_root + self.y_offset}")
# Time is in integer: milliseconds
self.after(int(self.delay * 1000), self._show)
def on_leave(self, event=None) -> None:
"""
Hides the ToolTip temporarily.
"""
if self.disable: return
self.status = "outside"
self.withdraw()
def _show(self) -> None:
"""
Displays the ToolTip.
"""
if not self.widget.winfo_exists():
self.hide()
self.destroy()
if self.status == "inside" and time.time() - self.last_moved >= self.delay:
self.status = "visible"
self.deiconify()
def hide(self) -> None:
"""
Disable the widget from appearing.
"""
if not self.winfo_exists():
return
self.withdraw()
self.disable = True
def is_disabled(self) -> None:
"""
Return the window state
"""
return self.disable
def get(self) -> None:
"""
Returns the text on the tooltip.
"""
return self.messageVar.get()
def configure(self, message: str = None, delay: float = None, bg_color: str = None, **kwargs):
"""
Set new message or configure the label parameters.
"""
if delay: self.delay = delay
if bg_color: self.frame.configure(fg_color=bg_color)
self.messageVar.set(message)
self.message_label.configure(**kwargs)
+448
View File
@@ -0,0 +1,448 @@
"""
CustomTkinter Messagebox
Author: Akash Bora
Version: 2.5
"""
import customtkinter
from PIL import Image, ImageTk
import os
import sys
import time
from typing import Literal
class CTkMessagebox(customtkinter.CTkToplevel):
ICONS = {
"check": None,
"cancel": None,
"info": None,
"question": None,
"warning": None
}
ICON_BITMAP = {}
def __init__(self,
master: any = None,
width: int = 400,
height: int = 200,
title: str = "CTkMessagebox",
message: str = "This is a CTkMessagebox!",
option_1: str = "OK",
option_2: str = None,
option_3: str = None,
options: list = [],
border_width: int = 1,
border_color: str = "default",
button_color: str = "default",
bg_color: str = "default",
fg_color: str = "default",
text_color: str = "default",
title_color: str = "default",
button_text_color: str = "default",
button_width: int = None,
button_height: int = None,
cancel_button_color: str = None,
cancel_button: str = None, # types: circle, cross or none
button_hover_color: str = "default",
icon: str = "info",
icon_size: tuple = None,
corner_radius: int = 15,
justify: str = "right",
font: tuple = None,
header: bool = False,
topmost: bool = True,
fade_in_duration: int = 0,
sound: bool = False,
option_focus: Literal[1, 2, 3] = None):
super().__init__()
self.master_window = master
self.width = 250 if width<250 else width
self.height = 150 if height<150 else height
if self.master_window is None:
self.spawn_x = int((self.winfo_screenwidth()-self.width)/2)
self.spawn_y = int((self.winfo_screenheight()-self.height)/2)
else:
self.spawn_x = int(self.master_window.winfo_width() * .5 + self.master_window.winfo_x() - .5 * self.width + 7)
self.spawn_y = int(self.master_window.winfo_height() * .5 + self.master_window.winfo_y() - .5 * self.height + 20)
self.after(10)
self.geometry(f"{self.width}x{self.height}+{self.spawn_x}+{self.spawn_y}")
self.title(title)
self.resizable(width=False, height=False)
self.fade = fade_in_duration
if self.fade:
self.fade = 20 if self.fade<20 else self.fade
self.attributes("-alpha", 0)
if not header:
self.overrideredirect(1)
if topmost:
self.attributes("-topmost", True)
else:
self.transient(self.master_window)
if sys.platform.startswith("win"):
self.transparent_color = self._apply_appearance_mode(self.cget("fg_color"))
self.attributes("-transparentcolor", self.transparent_color)
default_cancel_button = "cross"
elif sys.platform.startswith("darwin"):
self.transparent_color = 'systemTransparent'
self.attributes("-transparent", True)
default_cancel_button = "circle"
else:
self.transparent_color = '#000001'
corner_radius = 0
default_cancel_button = "cross"
self.lift()
self.config(background=self.transparent_color)
self.protocol("WM_DELETE_WINDOW", self.button_event)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.x = self.winfo_x()
self.y = self.winfo_y()
self._title = title
self.message = message
self.font = font
self.justify = justify
self.sound = sound
self.cancel_button = cancel_button if cancel_button else default_cancel_button
self.round_corners = corner_radius if corner_radius<=30 else 30
self.button_width = button_width if button_width else self.width/4
self.button_height = button_height if button_height else 28
if self.fade: self.attributes("-alpha", 0)
if self.button_height>self.height/4: self.button_height = self.height/4 -20
self.dot_color = cancel_button_color
self.border_width = border_width if border_width<6 else 5
if type(options) is list and len(options)>0:
try:
option_1 = options[-1]
option_2 = options[-2]
option_3 = options[-3]
except IndexError: None
if bg_color=="default":
self.bg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["fg_color"])
else:
self.bg_color = bg_color
if fg_color=="default":
self.fg_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["top_fg_color"])
else:
self.fg_color = fg_color
default_button_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["fg_color"])
if sys.platform.startswith("win"):
if self.bg_color==self.transparent_color or self.fg_color==self.transparent_color:
self.configure(fg_color="#000001")
self.transparent_color = "#000001"
self.attributes("-transparentcolor", self.transparent_color)
if button_color=="default":
self.button_color = (default_button_color, default_button_color, default_button_color)
else:
if type(button_color) is tuple:
if len(button_color)==2:
self.button_color = (button_color[0], button_color[1], default_button_color)
elif len(button_color)==1:
self.button_color = (button_color[0], default_button_color, default_button_color)
else:
self.button_color = button_color
else:
self.button_color = (button_color, button_color, button_color)
if text_color=="default":
self.text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.text_color = text_color
if title_color=="default":
self.title_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkLabel"]["text_color"])
else:
self.title_color = title_color
if button_text_color=="default":
self.bt_text_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["text_color"])
else:
self.bt_text_color = button_text_color
if button_hover_color=="default":
self.bt_hv_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkButton"]["hover_color"])
else:
self.bt_hv_color = button_hover_color
if border_color=="default":
self.border_color = self._apply_appearance_mode(customtkinter.ThemeManager.theme["CTkFrame"]["border_color"])
else:
self.border_color = border_color
if icon_size:
self.size_height = icon_size[1] if icon_size[1]<=self.height-100 else self.height-100
self.size = (icon_size[0], self.size_height)
else:
self.size = (self.height/4, self.height/4)
self.icon = self.load_icon(icon, icon_size) if icon else None
self.frame_top = customtkinter.CTkFrame(self, corner_radius=self.round_corners, width=self.width, border_width=self.border_width,
bg_color=self.transparent_color, fg_color=self.bg_color, border_color=self.border_color)
self.frame_top.grid(sticky="nswe")
if button_width:
self.frame_top.grid_columnconfigure(0, weight=1)
else:
self.frame_top.grid_columnconfigure((1,2,3), weight=1)
if button_height:
self.frame_top.grid_rowconfigure((0,1,3), weight=1)
else:
self.frame_top.grid_rowconfigure((0,1,2), weight=1)
self.frame_top.bind("<B1-Motion>", self.move_window)
self.frame_top.bind("<ButtonPress-1>", self.oldxyset)
if self.cancel_button=="cross":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=0, height=0, hover=False, border_width=0,
text_color=self.dot_color if self.dot_color else self.title_color,
text="✕", fg_color="transparent", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=5+self.border_width, pady=5+self.border_width)
elif self.cancel_button=="circle":
self.button_close = customtkinter.CTkButton(self.frame_top, corner_radius=10, width=10, height=10, hover=False, border_width=0,
text="", fg_color=self.dot_color if self.dot_color else "#c42b1c", command=self.button_event)
self.button_close.grid(row=0, column=5, sticky="ne", padx=10, pady=10)
self.title_label = customtkinter.CTkLabel(self.frame_top, width=1, text=self._title, text_color=self.title_color, font=self.font)
self.title_label.grid(row=0, column=0, columnspan=6, sticky="nw", padx=(15,30), pady=5)
self.title_label.bind("<B1-Motion>", self.move_window)
self.title_label.bind("<ButtonPress-1>", self.oldxyset)
self.info = customtkinter.CTkButton(self.frame_top, width=1, height=self.height/2, corner_radius=0, text=self.message, font=self.font,
fg_color=self.fg_color, hover=False, text_color=self.text_color, image=self.icon)
self.info._text_label.configure(wraplength=self.width/2, justify="left")
self.info.grid(row=1, column=0, columnspan=6, sticky="nwes", padx=self.border_width)
if self.info._text_label.winfo_reqheight()>self.height/2:
height_offset = int((self.info._text_label.winfo_reqheight())-(self.height/2) + self.height)
self.geometry(f"{self.width}x{height_offset}")
self.option_text_1 = option_1
self.button_1 = customtkinter.CTkButton(self.frame_top, text=self.option_text_1, fg_color=self.button_color[0],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_1))
self.option_text_2 = option_2
if option_2:
self.button_2 = customtkinter.CTkButton(self.frame_top, text=self.option_text_2, fg_color=self.button_color[1],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_2))
self.option_text_3 = option_3
if option_3:
self.button_3 = customtkinter.CTkButton(self.frame_top, text=self.option_text_3, fg_color=self.button_color[2],
width=self.button_width, font=self.font, text_color=self.bt_text_color,
hover_color=self.bt_hv_color, height=self.button_height,
command=lambda: self.button_event(self.option_text_3))
if self.justify=="center":
if button_width:
columns = [4,3,2]
span = 1
else:
columns = [4,2,0]
span = 2
if option_3:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.frame_top.columnconfigure((0,5), weight=1)
columns = [2,3]
self.button_1.grid(row=2, column=columns[0], sticky="news", padx=(0,5), pady=10)
self.button_2.grid(row=2, column=columns[1], sticky="news", padx=(5,0), pady=10)
else:
if button_width:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
else:
self.frame_top.columnconfigure((0,2,4), weight=2)
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=(0,10), pady=10)
elif self.justify=="left":
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [0,1,2]
span = 1
else:
columns = [0,2,4]
span = 2
if option_3:
self.button_1.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(0,10), pady=10)
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_3.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
elif option_2:
self.button_1.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
self.button_2.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(10,0), pady=10)
else:
self.frame_top.columnconfigure((0,1,2,3,4,5), weight=1)
if button_width:
columns = [5,4,3]
span = 1
else:
columns = [4,2,0]
span = 2
self.button_1.grid(row=2, column=columns[0], columnspan=span, sticky="news", padx=(0,10), pady=10)
if option_2:
self.button_2.grid(row=2, column=columns[1], columnspan=span, sticky="news", padx=10, pady=10)
if option_3:
self.button_3.grid(row=2, column=columns[2], columnspan=span, sticky="news", padx=(10,0), pady=10)
if header:
self.title_label.configure(text="")
self.title_label.grid_configure(pady=0)
self.button_close.configure(text_color=self.bg_color)
self.frame_top.configure(corner_radius=0)
if self.winfo_exists():
self.grab_set()
if self.sound:
self.bell()
if self.fade:
self.fade_in()
if option_focus:
self.option_focus = option_focus
self.focus_button(self.option_focus)
else:
if not self.option_text_2 and not self.option_text_3:
self.button_1.focus()
self.button_1.bind("<Return>", lambda event: self.button_event(self.option_text_1))
self.bind("<Escape>", lambda e: self.button_event())
def focus_button(self, option_focus):
try:
self.selected_button = getattr(self, "button_"+str(option_focus))
self.selected_button.focus()
self.selected_button.configure(border_color=self.bt_hv_color, border_width=3)
self.selected_option = getattr(self, "option_text_"+str(option_focus))
self.selected_button.bind("<Return>", lambda event: self.button_event(self.selected_option))
except AttributeError:
return
self.bind("<Left>", lambda e: self.change_left())
self.bind("<Right>", lambda e: self.change_right())
def change_left(self):
if self.option_focus==3:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==1:
if self.option_text_2:
self.option_focus = 2
elif self.option_focus==2:
if self.option_text_3:
self.option_focus = 3
self.focus_button(self.option_focus)
def change_right(self):
if self.option_focus==1:
return
self.selected_button.unbind("<Return>")
self.selected_button.configure(border_width=0)
if self.option_focus==2:
self.option_focus = 1
elif self.option_focus==3:
self.option_focus = 2
self.focus_button(self.option_focus)
def load_icon(self, icon, icon_size):
if icon not in self.ICONS or self.ICONS[icon] is None:
if icon in ["check", "cancel", "info", "question", "warning"]:
image_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'icons', icon + '.png')
else:
image_path = icon
if icon_size:
size_height = icon_size[1] if icon_size[1] <= self.height - 100 else self.height - 100
size = (icon_size[0], size_height)
else:
size = (self.height / 4, self.height / 4)
self.ICONS[icon] = customtkinter.CTkImage(Image.open(image_path), size=size)
self.ICON_BITMAP[icon] = ImageTk.PhotoImage(file=image_path)
self.after(200, lambda: self.iconphoto(False, self.ICON_BITMAP[icon]))
return self.ICONS[icon]
def fade_in(self):
for i in range(0,110,10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def fade_out(self):
for i in range(100,0,-10):
if not self.winfo_exists():
break
self.attributes("-alpha", i/100)
self.update()
time.sleep(1/self.fade)
def get(self):
if self.winfo_exists():
self.master.wait_window(self)
return self.event
def oldxyset(self, event):
self.oldx = event.x
self.oldy = event.y
def move_window(self, event):
self.y = event.y_root - self.oldy
self.x = event.x_root - self.oldx
self.geometry(f'+{self.x}+{self.y}')
def button_event(self, event=None):
try:
self.button_1.configure(state="disabled")
self.button_2.configure(state="disabled")
self.button_3.configure(state="disabled")
except AttributeError:
pass
if self.fade:
self.fade_out()
self.grab_release()
self.destroy()
self.event = event
if __name__ == "__main__":
app = CTkMessagebox()
app.mainloop()
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+158
View File
@@ -0,0 +1,158 @@
[
"Airi",
"Akane",
"Akane (Bunny Girl)",
"Akari",
"Ako",
"Arisu",
"Arisu (Maid)",
"Aru",
"Aru (New Year)",
"Asuna",
"Asuna (Bunny Girl)",
"Atsuko",
"Ayane",
"Ayane (Swimsuit)",
"Azusa",
"Azusa (Swimsuit)",
"Cherino",
"Cherino (Hot Spring)",
"Chihiro",
"Chinatsu",
"Chinatsu (Hot Spring)",
"Chise",
"Chise (Swimsuit)",
"Eimi",
"Fubuki",
"Fuuka",
"Fuuka (New Year)",
"Hanae",
"Hanae (Christmas)",
"Hanako",
"Hanako (Swimsuit)",
"Hare",
"Haruka",
"Haruka (New Year)",
"Haruna",
"Haruna (New Year)",
"Haruna (Sportswear)",
"Hasumi",
"Hasumi (Sportswear)",
"Hatsune Miku",
"Hibiki",
"Hibiki (Cheerleader)",
"Hifumi",
"Hifumi (Swimsuit)",
"Himari",
"Hina",
"Hina (Swimsuit)",
"Hinata",
"Hinata (Swimsuit)",
"Hiyori",
"Hoshino",
"Hoshino (Swimsuit)",
"Iori",
"Iori (Swimsuit)",
"Iroha",
"Izumi",
"Izumi (Swimsuit)",
"Izuna",
"Izuna (Swimsuit)",
"Junko",
"Junko (New Year)",
"Juri",
"Kaede",
"Kaho",
"Kanna",
"Karin",
"Karin (Bunny Girl)",
"Kayoko",
"Kayoko (New Year)",
"Kazusa",
"Kirino",
"Koharu",
"Koharu (Swimsuit)",
"Kokona",
"Kotama",
"Kotori",
"Kotori (Cheerleader)",
"Koyuki",
"Maki",
"Mari",
"Mari (Sportswear)",
"Marina",
"Mashiro",
"Mashiro (Swimsuit)",
"Megu",
"Meru",
"Michiru",
"Midori",
"Mika",
"Mimori",
"Mimori (Swimsuit)",
"Mina",
"Mine",
"Minori",
"Misaki",
"Miyako",
"Miyako (Swimsuit)",
"Miyu",
"Miyu (Swimsuit)",
"Moe",
"Momiji",
"Momoi",
"Mutsuki",
"Mutsuki (New Year)",
"Nagisa",
"Natsu",
"Neru",
"Neru (Bunny Girl)",
"Noa",
"Nodoka",
"Nodoka (Hot Spring)",
"Nonomi",
"Nonomi (Swimsuit)",
"Pina",
"Reisa",
"Rumi",
"Saki",
"Saki (Swimsuit)",
"Sakurako",
"Saori",
"Saya",
"Saya (Casual)",
"Sena",
"Serika",
"Serika (New Year)",
"Serina",
"Serina (Christmas)",
"Shigure",
"Shimiko",
"Shiroko",
"Shiroko (Riding)",
"Shiroko (Swimsuit)",
"Shizuko",
"Shizuko (Swimsuit)",
"Shun",
"Shun (Kid)",
"Sumire",
"Suzumi",
"Toki",
"Toki (Bunny Girl)",
"Tomoe",
"Tsubaki",
"Tsukuyo",
"Tsurugi",
"Tsurugi (Swimsuit)",
"Ui",
"Ui (Swimsuit)",
"Utaha",
"Utaha (Cheerleader)",
"Wakamo",
"Wakamo (Swimsuit)",
"Yoshimi",
"Yuuka",
"Yuuka (Sportswear)",
"Yuzu",
"Yuzu (Maid)"
]
+168
View File
@@ -0,0 +1,168 @@
[
"\u30a2\u30a4\u30ea",
"\u30a2\u30ab\u30cd",
"\u30a2\u30ab\u30cd\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
"\u30a2\u30ab\u30ea",
"\u30a2\u30b3",
"\u30a2\u30b9\u30ca",
"\u30a2\u30b9\u30ca\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
"\u30a2\u30ba\u30b5",
"\u30a2\u30ba\u30b5\uff08\u6c34\u7740\uff09",
"\u30a2\u30c4\u30b3",
"\u30a2\u30e4\u30cd",
"\u30a2\u30e4\u30cd\uff08\u6c34\u7740\uff09",
"\u30a2\u30ea\u30b9",
"\u30a2\u30ea\u30b9\uff08\u30e1\u30a4\u30c9\uff09",
"\u30a2\u30eb",
"\u30a2\u30eb\uff08\u6b63\u6708\uff09",
"\u30a4\u30aa\u30ea",
"\u30a4\u30aa\u30ea\uff08\u6c34\u7740\uff09",
"\u30a4\u30ba\u30ca",
"\u30a4\u30ba\u30ca\uff08\u6c34\u7740\uff09",
"\u30a4\u30ba\u30df",
"\u30a4\u30ba\u30df\uff08\u6c34\u7740\uff09",
"\u30a4\u30c1\u30ab",
"\u30a4\u30ed\u30cf",
"\u30a6\u30a4",
"\u30a6\u30a4\uff08\u6c34\u7740\uff09",
"\u30a6\u30bf\u30cf",
"\u30a6\u30bf\u30cf\uff08\u5fdc\u63f4\u56e3\uff09",
"\u30a8\u30a4\u30df",
"\u30a8\u30a4\u30df\uff08\u6c34\u7740\uff09",
"\u30ab\u30a8\u30c7",
"\u30ab\u30b9\u30df",
"\u30ab\u30ba\u30b5",
"\u30ab\u30db",
"\u30ab\u30e8\u30b3",
"\u30ab\u30e8\u30b3\uff08\u6b63\u6708\uff09",
"\u30ab\u30ea\u30f3",
"\u30ab\u30ea\u30f3\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
"\u30ab\u30f3\u30ca",
"\u30ad\u30ad\u30e7\u30a6",
"\u30ad\u30ea\u30ce",
"\u30b3\u30b3\u30ca",
"\u30b3\u30bf\u30de",
"\u30b3\u30c8\u30ea",
"\u30b3\u30c8\u30ea\uff08\u5fdc\u63f4\u56e3\uff09",
"\u30b3\u30cf\u30eb",
"\u30b3\u30cf\u30eb\uff08\u6c34\u7740\uff09",
"\u30b3\u30e6\u30ad",
"\u30b5\u30aa\u30ea",
"\u30b5\u30ad",
"\u30b5\u30ad\uff08\u6c34\u7740\uff09",
"\u30b5\u30af\u30e9\u30b3",
"\u30b5\u30e4",
"\u30b5\u30e4\uff08\u79c1\u670d\uff09",
"\u30b7\u30b0\u30ec",
"\u30b7\u30b0\u30ec\uff08\u6e29\u6cc9\uff09",
"\u30b7\u30ba\u30b3",
"\u30b7\u30ba\u30b3\uff08\u6c34\u7740\uff09",
"\u30b7\u30df\u30b3",
"\u30b7\u30e5\u30f3",
"\u30b7\u30e5\u30f3\uff08\u5e7c\u5973\uff09",
"\u30b7\u30ed\u30b3",
"\u30b7\u30ed\u30b3\n\uff08\u30e9\u30a4\u30c7\u30a3\u30f3\u30b0\uff09",
"\u30b7\u30ed\u30b3\uff08\u6c34\u7740\uff09",
"\u30b8\u30e5\u30ea",
"\u30b8\u30e5\u30f3\u30b3",
"\u30b8\u30e5\u30f3\u30b3\uff08\u6b63\u6708\uff09",
"\u30b9\u30ba\u30df",
"\u30b9\u30df\u30ec",
"\u30bb\u30ca",
"\u30bb\u30ea\u30ab",
"\u30bb\u30ea\u30ab\uff08\u6b63\u6708\uff09",
"\u30bb\u30ea\u30ca",
"\u30bb\u30ea\u30ca\n\uff08\u30af\u30ea\u30b9\u30de\u30b9\uff09",
"\u30c1\u30a7\u30ea\u30ce",
"\u30c1\u30a7\u30ea\u30ce\uff08\u6e29\u6cc9\uff09",
"\u30c1\u30bb",
"\u30c1\u30bb\uff08\u6c34\u7740\uff09",
"\u30c1\u30ca\u30c4",
"\u30c1\u30ca\u30c4\uff08\u6e29\u6cc9\uff09",
"\u30c1\u30d2\u30ed",
"\u30c4\u30af\u30e8",
"\u30c4\u30d0\u30ad",
"\u30c4\u30eb\u30ae",
"\u30c4\u30eb\u30ae\uff08\u6c34\u7740\uff09",
"\u30c8\u30ad",
"\u30c8\u30ad\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
"\u30c8\u30e2\u30a8",
"\u30ca\u30ae\u30b5",
"\u30ca\u30c4",
"\u30cd\u30eb",
"\u30cd\u30eb\n\uff08\u30d0\u30cb\u30fc\u30ac\u30fc\u30eb\uff09",
"\u30ce\u30a2",
"\u30ce\u30c9\u30ab",
"\u30ce\u30c9\u30ab\uff08\u6e29\u6cc9\uff09",
"\u30ce\u30ce\u30df",
"\u30ce\u30ce\u30df\uff08\u6c34\u7740\uff09",
"\u30cf\u30b9\u30df",
"\u30cf\u30b9\u30df\uff08\u4f53\u64cd\u670d\uff09",
"\u30cf\u30ca\u30a8",
"\u30cf\u30ca\u30a8\n\uff08\u30af\u30ea\u30b9\u30de\u30b9\uff09",
"\u30cf\u30ca\u30b3",
"\u30cf\u30ca\u30b3\uff08\u6c34\u7740\uff09",
"\u30cf\u30eb\u30ab",
"\u30cf\u30eb\u30ab\uff08\u6b63\u6708\uff09",
"\u30cf\u30eb\u30ca",
"\u30cf\u30eb\u30ca\uff08\u4f53\u64cd\u670d\uff09",
"\u30cf\u30eb\u30ca\uff08\u6b63\u6708\uff09",
"\u30cf\u30ec",
"\u30d2\u30ca",
"\u30d2\u30ca\u30bf",
"\u30d2\u30ca\u30bf\uff08\u6c34\u7740\uff09",
"\u30d2\u30ca\uff08\u6c34\u7740\uff09",
"\u30d2\u30d3\u30ad",
"\u30d2\u30d3\u30ad\uff08\u5fdc\u63f4\u56e3\uff09",
"\u30d2\u30d5\u30df",
"\u30d2\u30d5\u30df\uff08\u6c34\u7740\uff09",
"\u30d2\u30de\u30ea",
"\u30d2\u30e8\u30ea",
"\u30d5\u30a3\u30fc\u30ca",
"\u30d5\u30a6\u30ab",
"\u30d5\u30a6\u30ab\uff08\u6b63\u6708\uff09",
"\u30d5\u30d6\u30ad",
"\u30db\u30b7\u30ce",
"\u30db\u30b7\u30ce\uff08\u6c34\u7740\uff09",
"\u30de\u30ad",
"\u30de\u30b7\u30ed",
"\u30de\u30b7\u30ed\uff08\u6c34\u7740\uff09",
"\u30de\u30ea\u30ca",
"\u30de\u30ea\u30fc",
"\u30de\u30ea\u30fc\uff08\u4f53\u64cd\u670d\uff09",
"\u30df\u30ab",
"\u30df\u30b5\u30ad",
"\u30df\u30c1\u30eb",
"\u30df\u30c9\u30ea",
"\u30df\u30ca",
"\u30df\u30cd",
"\u30df\u30ce\u30ea",
"\u30df\u30e2\u30ea",
"\u30df\u30e2\u30ea\uff08\u6c34\u7740\uff09",
"\u30df\u30e4\u30b3",
"\u30df\u30e4\u30b3\uff08\u6c34\u7740\uff09",
"\u30df\u30e6",
"\u30df\u30e6\uff08\u6c34\u7740\uff09",
"\u30e0\u30c4\u30ad",
"\u30e0\u30c4\u30ad\uff08\u6b63\u6708\uff09",
"\u30e1\u30b0",
"\u30e1\u30eb",
"\u30e2\u30a8",
"\u30e2\u30df\u30b8",
"\u30e2\u30e2\u30a4",
"\u30e6\u30a6\u30ab",
"\u30e6\u30a6\u30ab\uff08\u4f53\u64cd\u670d\uff09",
"\u30e6\u30ab\u30ea",
"\u30e6\u30ba",
"\u30e6\u30ba\uff08\u30e1\u30a4\u30c9\uff09",
"\u30e8\u30b7\u30df",
"\u30eb\u30df",
"\u30ec\u30a4\u30b5",
"\u30ec\u30f3\u30b2",
"\u30ef\u30ab\u30e2",
"\u30ef\u30ab\u30e2\uff08\u6c34\u7740\uff09",
"\u4f50\u5929\u6d99\u5b50",
"\u521d\u97f3\u30df\u30af",
"\u5fa1\u5742\u7f8e\u7434",
"\u98df\u8702\u64cd\u7948"
]
+109
View File
@@ -0,0 +1,109 @@
import customtkinter
import json
import sys
import os
class Config:
def __init__(self, linker, config_file):
self.default_config = {
"ResetDaily": False,
"LastRun": "2023-12-24 21:41:55",
"ResetTime": "11:21:30",
"RechargeAP": False,
"PreferredTemplate": "template1",
"Queue": [],
"Event": False,
"Templates": {
"template1": []
}
}
self.linker = linker
self.config_file = config_file
if not os.path.exists(self.config_file):
with open(self.config_file, "w") as f:
json.dump(self.default_config, f, indent=2)
self.config_data = self.read()
self.linker.widgets = self.set_values_to_none(self.config_data)
self.locked = False
linker.config = self
def read(self):
# Read the JSON file
try:
with open(self.config_file, 'r') as json_file:
config_data = json.load(json_file)
return config_data
except FileNotFoundError:
print(f"Config file '{self.config_file}' not found.")
sys.exit(1)
except json.JSONDecodeError:
print(f"Invalid JSON format in '{self.config_file}'.")
sys.exit(1)
def set_values_to_none(self, input_dict):
result = {}
for key, value in input_dict.items():
if isinstance(value, dict):
result[key] = self.set_values_to_none(value)
else:
result[key] = None
return result
def load_config(self, widgets=None, config_data=None):
if widgets == None:
widgets = self.linker.widgets
config_data = self.config_data
for key in widgets:
if isinstance(widgets[key], dict) and isinstance(config_data[key], dict):
self.load_config(widgets[key], config_data[key])
else:
if widgets[key] is not None:
if isinstance(widgets[key], customtkinter.CTkCheckBox):
if config_data[key] == True:
widgets[key].select()
else:
widgets[key].deselect()
elif isinstance(widgets[key], customtkinter.CTkEntry):
widgets[key].insert(0, config_data[key])
else:
widgets[key].set(config_data[key])
def save_to_json(self, list_keys):
widget = self.linker.widgets
data = self.config_data
for i in list_keys[:-1]:
widget = widget[i]
data = data[i]
widget = widget[list_keys[-1]]
value = widget.get()
if isinstance(widget, customtkinter.CTkCheckBox):
value = True if value==1 else False
data[list_keys[-1]] = value
self.save_file("Configuration")
def save_file(self, name=None):
if self.locked:
with open("MCE\config.json", "r") as config_file:
new_config = json.load(config_file)
self.config_data["Queue"] = new_config["Queue"]
self.config_data["LastRun"] = new_config["LastRun"]
with open("MCE\config.json", "w") as config_file:
json.dump(self.config_data, config_file, indent=2)
if name:
self.linker.show_notification(name)
class Linker:
def __init__(self):
self.capitalise = lambda word: " ".join(x.title() for x in word.split("_"))
self.config = None
self.widgets = {}
self.sidebar = None
self.event_id = None
def show_notification(self, text):
if self.event_id:
self.sidebar.after_cancel(self.event_id)
self.sidebar.notification.show()
self.event_id = self.sidebar.after(2500, self.sidebar.notification.hide)
+1 -1
View File
@@ -12,7 +12,7 @@
The script is still under active development. The following features have been implemented:
- [x] **Cafe** Claim rewards / Interact / Second floor
- [x] **Cafe** Claim rewards / Interact / Invitation / Second floor
- [x] **Club** Claim AP
- [x] **Mailbox** Claim rewards
- [x] **Bounty** Auto sweep
+1 -1
View File
@@ -12,7 +12,7 @@
当前脚本还在活跃开发中,已经实现的功能有:
- [x] **咖啡厅** 领取奖励 / 互动 / 第二咖啡厅
- [x] **咖啡厅** 领取奖励 / 互动 / 邀请 / 第二咖啡厅
- [x] **公会** 领取体力
- [x] **邮箱** 领取奖励
- [x] **悬赏通缉** 自动扫荡
+27
View File
@@ -11,6 +11,10 @@ class ArisuAutoSweeper(AzurLaneAutoScript):
from tasks.login.login import Login
Login(self.config, device=self.device).app_start()
def stop(self):
from tasks.login.login import Login
Login(self.config, device=self.device).app_stop()
def goto_main(self):
from tasks.login.login import Login
from tasks.base.ui import UI
@@ -46,10 +50,33 @@ class ArisuAutoSweeper(AzurLaneAutoScript):
from tasks.tactical_challenge.tactical_challenge import TacticalChallenge
TacticalChallenge(config=self.config, device=self.device).run()
def task(self):
from tasks.task.task import Task
Task(config=self.config, device=self.device).run()
def shop(self):
from tasks.shop.shop import Shop
Shop(config=self.config, device=self.device).run()
def momotalk(self):
from tasks.momotalk.momotalk import MomoTalk
MomoTalk(config=self.config, device=self.device, task="Momotalk").run()
def mission(self):
from tasks.mission.mission import Mission
Mission(config=self.config, device=self.device).run()
def schedule(self):
from tasks.schedule.schedule import Schedule
Schedule(config=self.config, device=self.device).run()
def data_update(self):
from tasks.item.data_update import DataUpdate
DataUpdate(config=self.config, device=self.device).run()
def benchmark(self):
from module.daemon.benchmark import run_benchmark
run_benchmark(config=self.config)
if __name__ == '__main__':
aas = ArisuAutoSweeper('aas')
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Some files were not shown because too many files have changed in this diff Show More