ok-script 2.0.23b1__tar.gz → 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (363) hide show
  1. {ok_script-2.0.23b1 → ok_script-3.0.0}/PKG-INFO +11 -1
  2. {ok_script-2.0.23b1 → ok_script-3.0.0}/README.md +10 -0
  3. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/__init__.py +40 -5
  4. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/events.py +10 -0
  5. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/MainWindow.py +50 -16
  6. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/common/config.py +3 -3
  7. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/__init__.py +2 -2
  8. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/buttons.py +3 -2
  9. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/feedback.py +43 -14
  10. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/icons.py +108 -6
  11. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/qss.py +7 -1
  12. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/tabs.py +3 -2
  13. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/plugins/imported_tabs.py +1 -0
  14. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/settings/settings_page.py +0 -5
  15. ok_script-3.0.0/ok/ui/qt/shell/lazy_page.py +32 -0
  16. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/shell/nav_icons.py +2 -2
  17. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/shell/run_bar.py +24 -6
  18. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/tasks_page.py +123 -32
  19. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/app.py +22 -7
  20. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok_script.egg-info/PKG-INFO +11 -1
  21. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok_script.egg-info/SOURCES.txt +2 -0
  22. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_kit.py +74 -2
  23. ok_script-3.0.0/tests/test_lazy_page.py +47 -0
  24. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_main_window_system_theme.py +1 -1
  25. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_shell.py +17 -4
  26. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_tasks_page.py +78 -0
  27. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/develop/SKILL.md +0 -0
  28. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/develop/agents/openai.yaml +0 -0
  29. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/develop/references/windows-input.md +0 -0
  30. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/generate-templates/SKILL.md +0 -0
  31. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/reload-tasks/SKILL.md +0 -0
  32. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/reload-tasks/agents/openai.yaml +0 -0
  33. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/setup-env/SKILL.md +0 -0
  34. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/setup-env/agents/openai.yaml +0 -0
  35. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/setup-env/assets/ok-script-develop/SKILL.md +0 -0
  36. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/setup-project/SKILL.md +0 -0
  37. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/setup-project/agents/openai.yaml +0 -0
  38. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/use_venv/SKILL.md +0 -0
  39. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/write-test/SKILL.md +0 -0
  40. {ok_script-2.0.23b1 → ok_script-3.0.0}/.agents/skills/write-test/agents/openai.yaml +0 -0
  41. {ok_script-2.0.23b1 → ok_script-3.0.0}/LICENSE.txt +0 -0
  42. {ok_script-2.0.23b1 → ok_script-3.0.0}/MANIFEST.in +0 -0
  43. {ok_script-2.0.23b1 → ok_script-3.0.0}/diagnostics/qq_trace.py +0 -0
  44. {ok_script-2.0.23b1 → ok_script-3.0.0}/docs/agent-setup.md +0 -0
  45. {ok_script-2.0.23b1 → ok_script-3.0.0}/docs/feature-crops.md +0 -0
  46. {ok_script-2.0.23b1 → ok_script-3.0.0}/docs/mcp.md +0 -0
  47. {ok_script-2.0.23b1 → ok_script-3.0.0}/examples/mcp_observe.py +0 -0
  48. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/__main__.py +0 -0
  49. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/alas/__init__.py +0 -0
  50. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/alas/emulator_base.py +0 -0
  51. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/alas/emulator_windows.py +0 -0
  52. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/alas/platform_windows.py +0 -0
  53. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/alas/utils.py +0 -0
  54. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/assets/default-icon.png +0 -0
  55. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/__init__.py +0 -0
  56. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/__init__.py +0 -0
  57. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/deep.py +0 -0
  58. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/minitouch.py +0 -0
  59. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/nemu_ipc.py +0 -0
  60. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/nemu_utils.py +0 -0
  61. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/pool.py +0 -0
  62. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/timer.py +0 -0
  63. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/adb/util.py +0 -0
  64. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/windows/__init__.py +0 -0
  65. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/windows/d3d11.py +0 -0
  66. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/capture/windows/dump.py +0 -0
  67. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/cli.py +0 -0
  68. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/__init__.py +0 -0
  69. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/capture_labels.py +0 -0
  70. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/code_runner.py +0 -0
  71. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/config_schema.py +0 -0
  72. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/downloads.py +0 -0
  73. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/icons.py +0 -0
  74. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/mcp_runner.py +0 -0
  75. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/mcp_server.py +0 -0
  76. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/notifications.py +0 -0
  77. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/plugin_staging.py +0 -0
  78. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/plugins.py +0 -0
  79. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/screenshot.py +0 -0
  80. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/script_editing.py +0 -0
  81. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/script_packager.py +0 -0
  82. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/script_templates.py +0 -0
  83. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/skills.py +0 -0
  84. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/start_controller.py +0 -0
  85. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/task_manager.py +0 -0
  86. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/template_store.py +0 -0
  87. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/translation.py +0 -0
  88. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/core/ui_config.py +0 -0
  89. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/DeviceManager.py +0 -0
  90. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/__init__.py +0 -0
  91. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture.py +0 -0
  92. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/__init__.py +0 -0
  93. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/adb.py +0 -0
  94. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/base.py +0 -0
  95. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/bitblt.py +0 -0
  96. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/bitblt_utils.py +0 -0
  97. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/browser.py +0 -0
  98. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/desktop_duplication.py +0 -0
  99. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/hwnd_window.py +0 -0
  100. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/image.py +0 -0
  101. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/nemu_ipc.py +0 -0
  102. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/types.py +0 -0
  103. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/update.py +0 -0
  104. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/windows_graphics.py +0 -0
  105. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/capture_methods/windows_graphics_hdr.py +0 -0
  106. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction.py +0 -0
  107. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/__init__.py +0 -0
  108. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/adb.py +0 -0
  109. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/base.py +0 -0
  110. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/browser.py +0 -0
  111. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/do_nothing.py +0 -0
  112. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/foreground_post_message.py +0 -0
  113. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/genshin.py +0 -0
  114. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/keys.py +0 -0
  115. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/post_message.py +0 -0
  116. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/pydirect.py +0 -0
  117. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/pynput.py +0 -0
  118. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/interaction_methods/swipe.py +0 -0
  119. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/device/intercation.py +0 -0
  120. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/feature/Box.py +0 -0
  121. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/feature/Feature.py +0 -0
  122. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/feature/FeatureSet.py +0 -0
  123. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/feature/__init__.py +0 -0
  124. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/gui/__init__.py +0 -0
  125. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/gui/util/pyappify_startup.py +0 -0
  126. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/__init__.py +0 -0
  127. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/manager.py +0 -0
  128. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/messenger_images.py +0 -0
  129. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/pipeline.py +0 -0
  130. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/ppocr.py +0 -0
  131. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/providers.py +0 -0
  132. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/system.py +0 -0
  133. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/notification/windows_messenger.py +0 -0
  134. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ocr/__init__.py +0 -0
  135. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ocr/download_paddle_model.py +0 -0
  136. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Foundation/Collections.py +0 -0
  137. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Foundation/__init__.py +0 -0
  138. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Globalization/__init__.py +0 -0
  139. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Graphics/Capture/__init__.py +0 -0
  140. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +0 -0
  141. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Graphics/DirectX/__init__.py +0 -0
  142. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Graphics/Imaging/__init__.py +0 -0
  143. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Graphics/__init__.py +0 -0
  144. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Media/Ocr/__init__.py +0 -0
  145. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Media/__init__.py +0 -0
  146. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Security/Cryptography/__init__.py +0 -0
  147. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Security/__init__.py +0 -0
  148. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Storage/Streams/__init__.py +0 -0
  149. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/Storage/__init__.py +0 -0
  150. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/UI/ViewManagement/__init__.py +0 -0
  151. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/UI/__init__.py +0 -0
  152. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/Windows/__init__.py +0 -0
  153. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/__init__.py +0 -0
  154. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/delegate.py +0 -0
  155. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/export.py +0 -0
  156. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/idldsl.py +0 -0
  157. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/inspectable.py +0 -0
  158. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/roapi.py +0 -0
  159. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/types.py +0 -0
  160. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/rotypes/winstring.py +0 -0
  161. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/task/DiagnosisTask.py +0 -0
  162. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/task/TaskExecutor.py +0 -0
  163. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/task/__init__.py +0 -0
  164. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/task/exceptions.py +0 -0
  165. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/task/task.py +0 -0
  166. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/test/OKTestRunner.py +0 -0
  167. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/test/RunTests.py +0 -0
  168. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/test/TaskTestCase.py +0 -0
  169. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/test/__init__.py +0 -0
  170. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/third_party/__init__.py +0 -0
  171. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/third_party/paperclip.py +0 -0
  172. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/__init__.py +0 -0
  173. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/overlay/__init__.py +0 -0
  174. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/overlay/win32_gdi.py +0 -0
  175. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/Communicate.py +0 -0
  176. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/MessageWindow.py +0 -0
  177. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/StartController.py +0 -0
  178. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/__init__.py +0 -0
  179. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/about/AboutTab.py +0 -0
  180. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/about/UpdateCard.py +0 -0
  181. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/about/__init__.py +0 -0
  182. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/common/__init__.py +0 -0
  183. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/debug/OverlayWidget.py +0 -0
  184. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/debug/Screenshot.py +0 -0
  185. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/debug/__init__.py +0 -0
  186. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/debug/diagnosis_page.py +0 -0
  187. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/events.py +0 -0
  188. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/GettextTranslator.py +0 -0
  189. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/__init__.py +0 -0
  190. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/en_US.qm +0 -0
  191. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/en_US.ts +0 -0
  192. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/es_ES.qm +0 -0
  193. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/es_ES.ts +0 -0
  194. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/ja_JP.qm +0 -0
  195. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/ja_JP.ts +0 -0
  196. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/ko_KR.qm +0 -0
  197. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/ko_KR.ts +0 -0
  198. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/path.py +0 -0
  199. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/update.py +0 -0
  200. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/zh_CN.qm +0 -0
  201. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/zh_CN.ts +0 -0
  202. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/zh_TW.qm +0 -0
  203. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/i18n/zh_TW.ts +0 -0
  204. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/cards.py +0 -0
  205. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/dialogs.py +0 -0
  206. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/gallery.py +0 -0
  207. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/inputs.py +0 -0
  208. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/labels.py +0 -0
  209. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/layout.py +0 -0
  210. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/navigation.py +0 -0
  211. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/options.py +0 -0
  212. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/popover.py +0 -0
  213. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/switch.py +0 -0
  214. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/theme.py +0 -0
  215. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/kit/window.py +0 -0
  216. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/overlay/OverlayWindow.py +0 -0
  217. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/overlay/__init__.py +0 -0
  218. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/plugins/PluginTab.py +0 -0
  219. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/plugins/__init__.py +0 -0
  220. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/qt.qrc +0 -0
  221. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/resources.py +0 -0
  222. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/settings/GlobalConfigTab.py +0 -0
  223. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/settings/__init__.py +0 -0
  224. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/settings/config_section.py +0 -0
  225. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/shell/__init__.py +0 -0
  226. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/shell/connection.py +0 -0
  227. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/shell/diagnostics.py +0 -0
  228. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/shell/log_page.py +0 -0
  229. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/start/__init__.py +0 -0
  230. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/EditTaskTab.py +0 -0
  231. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/MarkUpWindow.py +0 -0
  232. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/ModifyListDialog.py +0 -0
  233. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/PythonHighlighter.py +0 -0
  234. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/RecordScript.py +0 -0
  235. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/ScheduleTaskTab.py +0 -0
  236. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/ScriptPackager.py +0 -0
  237. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/TaskManger.py +0 -0
  238. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/TaskOpButton.py +0 -0
  239. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/TemplateFactory.py +0 -0
  240. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/TemplateTab.py +0 -0
  241. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/__init__.py +0 -0
  242. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/config_editors.py +0 -0
  243. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/config_form.py +0 -0
  244. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/mouse_buttons.py +0 -0
  245. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/tasks/schedule_index_sync.py +0 -0
  246. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/Alert.py +0 -0
  247. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/InitWorker.py +0 -0
  248. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/__init__.py +0 -0
  249. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/download.py +0 -0
  250. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/pip_util.py +0 -0
  251. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/pyappify_startup.py +0 -0
  252. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/touch_scroll.py +0 -0
  253. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/util/windows_thumbnail.py +0 -0
  254. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/Card.py +0 -0
  255. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/CustomTab.py +0 -0
  256. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/FlowLayout.py +0 -0
  257. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/ImageWidget.py +0 -0
  258. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/ImportWarningDialog.py +0 -0
  259. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/ListTableWidgetItem.py +0 -0
  260. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/NumericTableWidgetItem.py +0 -0
  261. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/SortingTableWidgetItem.py +0 -0
  262. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/StartLoadingDialog.py +0 -0
  263. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/Tab.py +0 -0
  264. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/TabWidget.py +0 -0
  265. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/UpdateConfigWidgetItem.py +0 -0
  266. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/YesNonWidgetItem.py +0 -0
  267. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/ui/qt/widget/__init__.py +0 -0
  268. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/update/__init__.py +0 -0
  269. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/update/copy_ok_folder.py +0 -0
  270. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/update/inline_ok_requirements.py +0 -0
  271. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/Analytics.py +0 -0
  272. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/GlobalConfig.py +0 -0
  273. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/__init__.py +0 -0
  274. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/blur.py +0 -0
  275. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/clazz.py +0 -0
  276. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/collection.py +0 -0
  277. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/color.py +0 -0
  278. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/config.py +0 -0
  279. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/explorer.py +0 -0
  280. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/file.py +0 -0
  281. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/gpu_driver_settings.py +0 -0
  282. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/handler.py +0 -0
  283. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/i18n.py +0 -0
  284. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/logger.py +0 -0
  285. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/partial_sync_repo.py +0 -0
  286. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/print_hwnd.py +0 -0
  287. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/process.py +0 -0
  288. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/version.py +0 -0
  289. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/window.py +0 -0
  290. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok/util/windows_schedule.py +0 -0
  291. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok_script.egg-info/dependency_links.txt +0 -0
  292. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok_script.egg-info/entry_points.txt +0 -0
  293. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok_script.egg-info/not-zip-safe +0 -0
  294. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok_script.egg-info/requires.txt +0 -0
  295. {ok_script-2.0.23b1 → ok_script-3.0.0}/ok_script.egg-info/top_level.txt +0 -0
  296. {ok_script-2.0.23b1 → ok_script-3.0.0}/pyproject.toml +0 -0
  297. {ok_script-2.0.23b1 → ok_script-3.0.0}/setup.cfg +0 -0
  298. {ok_script-2.0.23b1 → ok_script-3.0.0}/setup.py +0 -0
  299. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_app.py +0 -0
  300. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_base_interaction.py +0 -0
  301. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_blur.py +0 -0
  302. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_box.py +0 -0
  303. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_capture_update.py +0 -0
  304. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_cli.py +0 -0
  305. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_core_events.py +0 -0
  306. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_core_ui_services.py +0 -0
  307. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_device_manager.py +0 -0
  308. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_diagnosis_page.py +0 -0
  309. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_executor_jobs.py +0 -0
  310. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_explorer.py +0 -0
  311. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_global_config.py +0 -0
  312. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_gpu_driver_settings.py +0 -0
  313. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_handler.py +0 -0
  314. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_headless_imports.py +0 -0
  315. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_inline_ok_requirements.py +0 -0
  316. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_log_page.py +0 -0
  317. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_logger.py +0 -0
  318. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_main_window_notifications.py +0 -0
  319. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_main_window_startup_tab.py +0 -0
  320. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_markup_window.py +0 -0
  321. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_mcp_cleanup.py +0 -0
  322. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_mcp_runner.py +0 -0
  323. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_mcp_server.py +0 -0
  324. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_mcp_setting.py +0 -0
  325. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_missing_frame.py +0 -0
  326. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_mouse_buttons.py +0 -0
  327. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_navigation_state.py +0 -0
  328. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_nemu_ipc_capture.py +0 -0
  329. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_notifications.py +0 -0
  330. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_ok_mutex.py +0 -0
  331. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_overlay_custom_draw.py +0 -0
  332. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_overlay_lifecycle.py +0 -0
  333. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_package_metadata.py +0 -0
  334. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_plugin_staging.py +0 -0
  335. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_plugin_tabs.py +0 -0
  336. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_plugins.py +0 -0
  337. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_process.py +0 -0
  338. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_process_mutex.py +0 -0
  339. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_pyappify_startup_update.py +0 -0
  340. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_qt_events.py +0 -0
  341. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_qt_page_layout.py +0 -0
  342. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_runtime_startup.py +0 -0
  343. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_schedule_startup.py +0 -0
  344. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_schedule_task_index_sync.py +0 -0
  345. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_screenshot_naming.py +0 -0
  346. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_screenshot_rendering.py +0 -0
  347. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_settings_page.py +0 -0
  348. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_skills.py +0 -0
  349. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_start_controller.py +0 -0
  350. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_task_executor_next_frame.py +0 -0
  351. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_task_executor_queue.py +0 -0
  352. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_task_reloading.py +0 -0
  353. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_template_compression.py +0 -0
  354. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_template_generation.py +0 -0
  355. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_template_tab.py +0 -0
  356. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_touch_scroll.py +0 -0
  357. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_translation_fallback.py +0 -0
  358. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_ui_config.py +0 -0
  359. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_version.py +0 -0
  360. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_windows_graphics_capture.py +0 -0
  361. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_windows_graphics_hdr.py +0 -0
  362. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_windows_schedule_target_parse.py +0 -0
  363. {ok_script-2.0.23b1 → ok_script-3.0.0}/tests/test_windows_schedule_xml.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ok-script
3
- Version: 2.0.23b1
3
+ Version: 3.0.0
4
4
  Summary: Vibe Coding Automation Framework
5
5
  Author-email: ok-oldking <firedcto@gmail.com>
6
6
  License: ok-script License
@@ -230,6 +230,16 @@ python -m venv .venv
230
230
  - [白荆回廊](https://github.com/ok-oldking/ok-baijing)(停止维护)
231
231
  - [崩坏:星穹铁道](https://github.com/Shasnow/ok-starrailassistant)(停止维护)
232
232
 
233
+ ## 开发 ok-script
234
+
235
+ 在本仓库中测试界面改动时,可以用 [main.py](main.py) 中的演示配置直接从源码启动 GUI:
236
+
237
+ ```powershell
238
+ .\.venv\Scripts\python.exe main.py
239
+ ```
240
+
241
+ 它以记事本为目标窗口,无需游戏,并注册了一个演示一次性任务和一个触发任务,让任务、计划任务和模板页面都有内容。设置和日志保存在已被 git 忽略的 `configs/` 和 `logs/` 目录中。需要测试其他选项时,修改 `main.py` 中的 `config` 字典或演示任务即可。
242
+
233
243
  ## 社区与发布
234
244
 
235
245
  - 开发者 QQ 群:938132715
@@ -106,6 +106,16 @@ python -m venv .venv
106
106
  - [白荆回廊](https://github.com/ok-oldking/ok-baijing)(停止维护)
107
107
  - [崩坏:星穹铁道](https://github.com/Shasnow/ok-starrailassistant)(停止维护)
108
108
 
109
+ ## 开发 ok-script
110
+
111
+ 在本仓库中测试界面改动时,可以用 [main.py](main.py) 中的演示配置直接从源码启动 GUI:
112
+
113
+ ```powershell
114
+ .\.venv\Scripts\python.exe main.py
115
+ ```
116
+
117
+ 它以记事本为目标窗口,无需游戏,并注册了一个演示一次性任务和一个触发任务,让任务、计划任务和模板页面都有内容。设置和日志保存在已被 git 忽略的 `configs/` 和 `logs/` 目录中。需要测试其他选项时,修改 `main.py` 中的 `config` 字典或演示任务即可。
118
+
109
119
  ## 社区与发布
110
120
 
111
121
  - 开发者 QQ 群:938132715
@@ -310,6 +310,8 @@ class App(_OverlayConfigMixin):
310
310
  self.global_config = task_executor.global_config if task_executor else None
311
311
  from ok.ui.qt.util.app import init_app_config
312
312
  self.app, self.locale = init_app_config()
313
+ from ok.ui.qt.common.config import cfg as ui_config
314
+ ui_config.language_changed.connect(self._on_language_changed)
313
315
  install_qt_event_dispatcher()
314
316
  self.app.aboutToQuit.connect(self._close_device_manager)
315
317
  if task_executor is not None:
@@ -461,6 +463,44 @@ class App(_OverlayConfigMixin):
461
463
  self.do_show_main()
462
464
 
463
465
  def do_show_main(self):
466
+ self._create_main_window()
467
+ self.main_window.show()
468
+ self.main_window.bring_to_front()
469
+
470
+ logger.debug(f'show_main_window end')
471
+
472
+ def _on_language_changed(self):
473
+ # The signal comes from a widget in the window being replaced; rebuild once it returns.
474
+ from PySide6.QtCore import QTimer
475
+ QTimer.singleShot(0, self._apply_language)
476
+
477
+ def _apply_language(self):
478
+ from ok.ui.qt.util.app import install_translator
479
+ self.locale = install_translator(self.app)
480
+ self.po_translation = None
481
+ if og.executor is not None:
482
+ og.executor.locale = self.locale
483
+ og.executor.load_tr()
484
+ logger.info(f'language changed to {self.locale.name()}')
485
+
486
+ old_window = self.main_window
487
+ if old_window is None:
488
+ return
489
+ # Every page translates its text on construction, so build a fresh window.
490
+ visible = old_window.isVisible()
491
+ old_window.update_ok_config()
492
+ old_window.dispose()
493
+ self._create_main_window()
494
+ # Startup work in the first showEvent has already run for this process.
495
+ self.main_window.shown = True
496
+ self.main_window.switch_to(self.main_window.setting_tab)
497
+ if visible:
498
+ self.main_window.show()
499
+ self.main_window.bring_to_front()
500
+ old_window.hide()
501
+ old_window.deleteLater()
502
+
503
+ def _create_main_window(self):
464
504
  from ok.ui.qt.MainWindow import MainWindow
465
505
 
466
506
  self.main_window = MainWindow(self, self.config, self.ok_config, self.icon, self.title, self.version,
@@ -482,11 +522,6 @@ class App(_OverlayConfigMixin):
482
522
  if hasattr(og.my_app, 'on_show_main_window'):
483
523
  og.my_app.on_show_main_window(self.main_window)
484
524
 
485
- self.main_window.show()
486
- self.main_window.bring_to_front()
487
-
488
- logger.debug(f'show_main_window end')
489
-
490
525
  def exec(self):
491
526
  logger.info('app.exec()')
492
527
  import signal
@@ -45,6 +45,10 @@ class EventSignal:
45
45
  elif callback in self._subscribers:
46
46
  self._subscribers.remove(callback)
47
47
 
48
+ def disconnect_where(self, predicate: Callable[[Callable[..., Any]], bool]) -> None:
49
+ with self._lock:
50
+ self._subscribers = [callback for callback in self._subscribers if not predicate(callback)]
51
+
48
52
  def emit(self, *args: Any, **kwargs: Any) -> None:
49
53
  with self._lock:
50
54
  subscribers = tuple(self._subscribers)
@@ -94,6 +98,12 @@ class EventBus:
94
98
  self.any.emit(EventMessage(name, args, kwargs))
95
99
  return forward
96
100
 
101
+ def disconnect_where(self, predicate: Callable[[Callable[..., Any]], bool]) -> None:
102
+ """Remove every subscriber of every event for which ``predicate`` is true."""
103
+ self.any.disconnect_where(predicate)
104
+ for name in self._event_names:
105
+ getattr(self, name).disconnect_where(predicate)
106
+
97
107
  def emit_draw_box(self, key=None, boxes=None, color=None, frame=None, debug=True):
98
108
  self.draw_box.emit(key, boxes, color, frame, debug)
99
109
 
@@ -2,7 +2,7 @@ import os
2
2
  import threading
3
3
 
4
4
  import pyappify
5
- from PySide6.QtCore import QCoreApplication, QEvent, QSize, Qt, QTimer
5
+ from PySide6.QtCore import QCoreApplication, QEvent, QObject, QSize, Qt, QTimer
6
6
  from PySide6.QtGui import QColor
7
7
  from PySide6.QtWidgets import QApplication, QHBoxLayout, QMenu, QStackedWidget, QSystemTrayIcon, QVBoxLayout
8
8
 
@@ -31,9 +31,10 @@ from ok.util.config import Config
31
31
 
32
32
  from ok.ui.qt.Communicate import communicate
33
33
  from ok.ui.qt.common.config import cfg as ui_config
34
- from ok.ui.qt.kit import Dialog, KitWindow, NavigationView, Theme as KitTheme, set_toast_bottom_margin, show_toast, \
34
+ from ok.ui.qt.kit import Dialog, KitWindow, NavigationView, Theme as KitTheme, set_toast_anchor, show_toast, \
35
35
  theme as kit_theme
36
36
  from ok.ui.qt.kit.theme import set_style_property
37
+ from ok.ui.qt.shell.lazy_page import LazyPage
37
38
  from ok.ui.qt.shell.nav_icons import nav_icon
38
39
  from ok.ui.qt.shell.run_bar import RunBar
39
40
  from ok.ui.qt.util.Alert import alert_error
@@ -46,8 +47,6 @@ from ok.util.logger import Logger
46
47
 
47
48
  logger = Logger.get_logger(__name__)
48
49
 
49
- PAGE_MARGIN = 12
50
-
51
50
  SECTION_RUN = "run"
52
51
  SECTION_GAME = "game"
53
52
  SECTION_DEVELOP = "develop"
@@ -114,10 +113,11 @@ class MainWindow(KitWindow):
114
113
  body.setSpacing(0)
115
114
  body.addWidget(self.run_bar)
116
115
  body.addLayout(pages, 1)
117
- set_toast_bottom_margin(self, PAGE_MARGIN)
116
+ # Keep tips flush with the bottom edge of the title bar.
117
+ set_toast_anchor(self, self.title_bar, gap=0)
118
118
 
119
119
  self.schedule_tab = None
120
- self.global_config_tabs = []
120
+ self.global_config_tabs = [] # [(config, LazyPage)]
121
121
 
122
122
  # Prepare custom tabs and separate them by add_after_default_tabs
123
123
  before_custom_tabs = []
@@ -155,9 +155,13 @@ class MainWindow(KitWindow):
155
155
  self.edit_task_tab = EditTaskTab()
156
156
  self.add_page(self.edit_task_tab, "pen", self.tr('Script'), SECTION_DEVELOP)
157
157
 
158
+ # Settings, Templates and global config pages are rarely opened, so they are
159
+ # built on first use (LazyPage) to keep window construction fast.
158
160
  if og.task_manager.has_custom or debug:
159
- from ok.ui.qt.tasks.TemplateTab import TemplateTab
160
- self.template_tab = TemplateTab(config=config)
161
+ def template_tab():
162
+ from ok.ui.qt.tasks.TemplateTab import TemplateTab
163
+ return TemplateTab(config=config)
164
+ self.template_tab = LazyPage(template_tab)
161
165
  self.add_page(self.template_tab, "image", self.tr('Templates'), SECTION_DEVELOP)
162
166
 
163
167
  from ok.ui.qt.debug.diagnosis_page import DiagnosisPage
@@ -192,9 +196,11 @@ class MainWindow(KitWindow):
192
196
  notification_tab = None
193
197
  for name, config_obj, option in global_config.get_all_visible_configs():
194
198
  if getattr(option, 'show_at_tab', False):
195
- from ok.ui.qt.settings.GlobalConfigTab import GlobalConfigTab
196
- config_tab = GlobalConfigTab(config_obj, option)
197
- self.global_config_tabs.append(config_tab)
199
+ def global_config_tab(config_obj=config_obj, option=option):
200
+ from ok.ui.qt.settings.GlobalConfigTab import GlobalConfigTab
201
+ return GlobalConfigTab(config_obj, option)
202
+ config_tab = LazyPage(global_config_tab)
203
+ self.global_config_tabs.append((config_obj, config_tab))
198
204
  if name == NOTIFICATION_OPTION_NAME:
199
205
  notification_tab = config_tab
200
206
  else:
@@ -204,8 +210,10 @@ class MainWindow(KitWindow):
204
210
  self.notification_tab = notification_tab
205
211
  self.add_page(notification_tab, "bell", self.app.tr(NOTIFICATION_OPTION_NAME), SECTION_GAME)
206
212
 
207
- from ok.ui.qt.settings.settings_page import SettingsPage
208
- self.setting_tab = SettingsPage()
213
+ def settings_page():
214
+ from ok.ui.qt.settings.settings_page import SettingsPage
215
+ return SettingsPage()
216
+ self.setting_tab = LazyPage(settings_page)
209
217
  self.add_page(self.setting_tab, "gear", self.tr('Settings'), bottom=True)
210
218
 
211
219
  from ok.ui.qt.about.AboutTab import AboutTab
@@ -275,6 +283,8 @@ class MainWindow(KitWindow):
275
283
  def switch_to(self, widget):
276
284
  if self.stack.indexOf(widget) < 0:
277
285
  return
286
+ if isinstance(widget, LazyPage):
287
+ widget.page()
278
288
  self.stack.setCurrentWidget(widget)
279
289
  self.navigation.set_current(widget.objectName())
280
290
 
@@ -442,12 +452,12 @@ class MainWindow(KitWindow):
442
452
  logger.debug(f'bring_to_front native activation failed: {e}')
443
453
 
444
454
  def goto_global_config(self, key):
445
- for config_tab in self.global_config_tabs:
446
- if config_tab.has_key(key):
455
+ for config_obj, config_tab in self.global_config_tabs:
456
+ if key in config_obj:
447
457
  self.switch_to(config_tab)
448
458
  return
449
459
  self.switch_to(self.setting_tab)
450
- self.setting_tab.goto_config(key)
460
+ self.setting_tab.page().goto_config(key)
451
461
 
452
462
  def tray_quit(self):
453
463
  logger.info('main window tray_quit')
@@ -669,6 +679,30 @@ class MainWindow(KitWindow):
669
679
  except Exception as e:
670
680
  logger.error(f'Error importing .okscript file: {e}')
671
681
 
682
+ def dispose(self):
683
+ """Release app-wide hooks so a replacement window can take over, e.g. after a language change."""
684
+ if self.update_check_timer is not None:
685
+ self.update_check_timer.stop()
686
+ self.tray.hide()
687
+ self.run_bar.dispose()
688
+ notification_manager = getattr(self, 'notification_manager', None)
689
+ if notification_manager is not None:
690
+ # Stopping waits for the pipeline thread; keep the GUI thread free.
691
+ threading.Thread(target=notification_manager.stop, name='NotificationStop', daemon=True).start()
692
+ controller = getattr(self, '_imported_tab_controller', None)
693
+ if controller is not None:
694
+ controller.dispose()
695
+ for page in list(self._pages.values()):
696
+ cleanup = getattr(page, 'dispose', None) or getattr(page, 'stop_worker', None)
697
+ if callable(cleanup):
698
+ try:
699
+ cleanup()
700
+ except Exception as error:
701
+ logger.error(f'Failed to dispose page {page.objectName()}', error)
702
+ # The event bus holds strong references; drop the ones owned by this window's widgets.
703
+ owners = {id(obj) for obj in (self, *self.findChildren(QObject))}
704
+ communicate.disconnect_where(lambda callback: id(getattr(callback, '__self__', None)) in owners)
705
+
672
706
  def closeEvent(self, event):
673
707
  if self.app.exit_event.is_set():
674
708
  logger.info("Window closed exit_event.is_set")
@@ -39,13 +39,13 @@ def deserialize_language(value) -> Language:
39
39
 
40
40
 
41
41
  class UiConfig(QObject):
42
- """The language (applied on restart) and the theme mode (applied live).
42
+ """The language and the theme mode, both applied live.
43
43
 
44
44
  The file also accepts the older qfluentwidgets layout
45
45
  (``MainWindow.Language`` and ``QFluentWidgets.ThemeMode``).
46
46
  """
47
47
 
48
- restart_required = Signal()
48
+ language_changed = Signal()
49
49
  theme_changed = Signal()
50
50
 
51
51
  def __init__(self, path: str):
@@ -83,7 +83,7 @@ class UiConfig(QObject):
83
83
  return
84
84
  self.language = language
85
85
  self.save()
86
- self.restart_required.emit()
86
+ self.language_changed.emit()
87
87
 
88
88
  def set_theme_mode(self, mode: str) -> None:
89
89
  mode = getattr(mode, "value", mode)
@@ -8,7 +8,7 @@ its children; plain Qt inputs inside it are styled too.
8
8
  from ok.ui.qt.kit.buttons import Button
9
9
  from ok.ui.qt.kit.cards import Card, SettingGroup, SettingRow
10
10
  from ok.ui.qt.kit.dialogs import Dialog, confirm
11
- from ok.ui.qt.kit.feedback import Pill, ProgressRing, StatusDot, Toast, set_toast_bottom_margin, show_toast
11
+ from ok.ui.qt.kit.feedback import Pill, ProgressRing, StatusDot, Toast, set_toast_anchor, set_toast_top_margin, show_toast
12
12
  from ok.ui.qt.kit.icons import ICON_NAMES, icon, load_icon
13
13
  from ok.ui.qt.kit.inputs import ComboBox, NumberBox
14
14
  from ok.ui.qt.kit.labels import Label
@@ -25,5 +25,5 @@ __all__ = [
25
25
  "Button", "Card", "ComboBox", "Dialog", "FlowLayout", "ICON_NAMES", "KitWindow", "Label", "NavItem",
26
26
  "NavigationView", "NumberBox", "OptionCard", "OptionGroup", "Pill", "Popover", "ProgressRing", "SettingGroup",
27
27
  "SettingRow", "StatusDot", "Switch", "TabItem", "TabStrip", "Theme", "TitleBar", "Toast", "confirm", "icon",
28
- "load_icon", "set_toast_bottom_margin", "show_toast", "theme",
28
+ "load_icon", "set_toast_anchor", "set_toast_top_margin", "show_toast", "theme",
29
29
  ]
@@ -6,7 +6,7 @@ from ok.ui.qt.kit.icons import icon as kit_icon
6
6
  from ok.ui.qt.kit.theme import set_style_property, theme
7
7
 
8
8
  VARIANTS = ("secondary", "primary", "tonal", "subtle", "danger")
9
- SIZES = {"normal": (36, 14, 16), "small": (30, 10, 14)}
9
+ SIZES = {"normal": (36, 14, 18), "small": (30, 10, 16)}
10
10
 
11
11
  _ICON_COLOR = {"primary": "on_accent", "danger": "danger"}
12
12
 
@@ -48,7 +48,8 @@ class Button(QPushButton):
48
48
  height, padding, _ = SIZES[size]
49
49
  layout = QHBoxLayout(self)
50
50
  layout.setContentsMargins(padding, 0, padding, 0)
51
- layout.setSpacing(8)
51
+ # Glyphs leave a few pixels of air inside their box, so this reads wider than it is.
52
+ layout.setSpacing(5)
52
53
  layout.addStretch(1)
53
54
  layout.addWidget(self._icon_label)
54
55
  layout.addWidget(self._text_label)
@@ -125,13 +125,15 @@ class ProgressRing(QWidget):
125
125
 
126
126
 
127
127
  class Toast(QWidget):
128
- """A message card shown at the bottom right of a window; see :func:`show_toast`."""
128
+ """A message card shown at the top center of a window; see :func:`show_toast`."""
129
+
130
+ WIDTH = 560
129
131
 
130
132
  def __init__(self, message: str, title: str = None, tone: str = "neutral", action: tuple = None, parent=None):
131
133
  super().__init__(parent)
132
134
  self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
133
135
  self.tone = _check_tone(tone)
134
- self.setFixedWidth(360)
136
+ self.setFixedWidth(self.WIDTH)
135
137
 
136
138
  icon_label = QLabel(self)
137
139
  icon_label.setPixmap(kit_icon(_TONE_ICON[tone], _TONE_COLOR[tone], 2).pixmap(QSize(18, 18), self.devicePixelRatioF()))
@@ -165,15 +167,19 @@ class Toast(QWidget):
165
167
 
166
168
 
167
169
  class _ToastHost(QObject):
168
- """Stacks toasts in a window's bottom-right corner above ``bottom_margin``."""
170
+ """Stacks toasts down from a window's top center, below ``anchor`` if set."""
169
171
 
170
172
  SPACING = 8
173
+ MARGIN = 16
174
+ ANCHOR_GAP = 4
171
175
 
172
176
  def __init__(self, window: QWidget):
173
177
  super().__init__(window)
174
178
  self.window = window
175
179
  self.toasts = []
176
- self.bottom_margin = 16
180
+ self.anchor = None
181
+ self.anchor_gap = self.ANCHOR_GAP
182
+ self.top_margin = self.MARGIN
177
183
  window.installEventFilter(self)
178
184
 
179
185
  def add(self, toast: Toast, duration: int) -> None:
@@ -182,8 +188,7 @@ class _ToastHost(QObject):
182
188
  toast.destroyed.connect(lambda *_: self._forget(toast))
183
189
  toast.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
184
190
  self.toasts.append(toast)
185
- # Wrapped labels need height-for-width; adjustSize() would leave gaps.
186
- toast.resize(toast.width(), toast.layout().totalHeightForWidth(toast.width()))
191
+ self._fit(toast)
187
192
  toast.show()
188
193
  toast.raise_()
189
194
  self.layout()
@@ -198,16 +203,30 @@ class _ToastHost(QObject):
198
203
  self.toasts.remove(toast)
199
204
  self.layout()
200
205
 
206
+ def _fit(self, toast) -> None:
207
+ # Narrow windows shrink the toast; wrapped labels need height-for-width,
208
+ # since adjustSize() would leave gaps.
209
+ width = max(240, min(Toast.WIDTH, self.window.width() - 2 * self.MARGIN))
210
+ toast.setFixedWidth(width)
211
+ toast.resize(width, toast.layout().totalHeightForWidth(width))
212
+
213
+ def _top(self) -> int:
214
+ anchor = self.anchor
215
+ if anchor is not None and shiboken6.isValid(anchor) and anchor.isVisible():
216
+ return anchor.mapTo(self.window, anchor.rect().bottomLeft()).y() + 1 + self.anchor_gap
217
+ return self.top_margin
218
+
201
219
  def layout(self) -> None:
202
220
  self.toasts = [toast for toast in self.toasts if toast.isVisible()]
203
- y = self.window.height() - self.bottom_margin
204
- for toast in reversed(self.toasts):
205
- y -= toast.height()
206
- toast.move(self.window.width() - toast.width() - 16, y)
207
- y -= self.SPACING
221
+ y = self._top()
222
+ for toast in self.toasts:
223
+ toast.move((self.window.width() - toast.width()) // 2, y)
224
+ y += toast.height() + self.SPACING
208
225
 
209
226
  def eventFilter(self, watched, event) -> bool:
210
227
  if watched is self.window and event.type() == QEvent.Type.Resize:
228
+ for toast in self.toasts:
229
+ self._fit(toast)
211
230
  self.layout()
212
231
  return False
213
232
 
@@ -231,9 +250,19 @@ def show_toast(parent: QWidget, message: str, title: str = None, tone: str = "ne
231
250
  return toast
232
251
 
233
252
 
234
- def set_toast_bottom_margin(widget: QWidget, margin: int) -> None:
235
- """Keep toasts in ``widget``'s window above e.g. a status bar."""
253
+ def set_toast_anchor(widget: QWidget, anchor: QWidget, *, gap: int = 4) -> None:
254
+ """Show toasts below ``anchor`` with the requested gap in logical pixels."""
255
+
256
+ host = toast_host(widget)
257
+ host.anchor = anchor
258
+ host.anchor_gap = max(0, int(gap))
259
+ host.layout()
260
+
261
+
262
+ def set_toast_top_margin(widget: QWidget, margin: int) -> None:
263
+ """Position toasts directly below the window's top edge."""
236
264
 
237
265
  host = toast_host(widget)
238
- host.bottom_margin = margin
266
+ host.anchor = None
267
+ host.top_margin = max(0, int(margin))
239
268
  host.layout()
@@ -17,9 +17,10 @@ from PySide6.QtGui import QColor, QIcon, QIconEngine, QImage, QPainter, QPixmap
17
17
  from PySide6.QtSvg import QSvgRenderer
18
18
 
19
19
  _PATHS = {
20
- "play": '<path d="M7 4.5v15l12-7.5z" fill="currentColor" stroke="none"/>',
21
- "pause": '<path d="M8 5v14M16 5v14"/>',
22
- "stop": '<rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor" stroke="none"/>',
20
+ "play": '<path d="M7 3.5v17L20.5 12z" fill="currentColor" stroke="currentColor" stroke-width="1.5"/>',
21
+ "pause": '<rect x="5.5" y="4" width="4.5" height="16" rx="1.25" fill="currentColor" stroke="none"/>'
22
+ '<rect x="14" y="4" width="4.5" height="16" rx="1.25" fill="currentColor" stroke="none"/>',
23
+ "stop": '<rect x="4.5" y="4.5" width="15" height="15" rx="2.5" fill="currentColor" stroke="none"/>',
23
24
  "power": '<path d="M12 3v9M6.3 6.8a8 8 0 1 0 11.4 0"/>',
24
25
  "check": '<path d="M5 12.5l4.5 4.5L19 7"/>',
25
26
  "x": '<path d="M6 6l12 12M18 6 6 18"/>',
@@ -219,6 +220,100 @@ class _SvgFileIconEngine(QIconEngine):
219
220
  return _SvgFileIconEngine(self.data)
220
221
 
221
222
 
223
+ class _RasterFileIconEngine(QIconEngine):
224
+ """Fit raster artwork by its visible bounds, preserving its aspect ratio."""
225
+
226
+ def __init__(self, image: QImage):
227
+ super().__init__()
228
+ self.image = image
229
+
230
+ def paint(self, painter, rect, mode, state):
231
+ scale = min(rect.width() / self.image.width(), rect.height() / self.image.height())
232
+ width, height = self.image.width() * scale, self.image.height() * scale
233
+ target = QRectF(rect.x() + (rect.width() - width) / 2,
234
+ rect.y() + (rect.height() - height) / 2, width, height)
235
+ painter.save()
236
+ painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform)
237
+ if mode == QIcon.Mode.Disabled:
238
+ painter.setOpacity(painter.opacity() * 0.4)
239
+ painter.drawImage(target, self.image)
240
+ painter.restore()
241
+
242
+ def pixmap(self, size, mode, state):
243
+ return self.scaledPixmap(size, mode, state, 1.0)
244
+
245
+ def scaledPixmap(self, size, mode, state, scale):
246
+ image = QImage(round(size.width() * scale), round(size.height() * scale),
247
+ QImage.Format.Format_ARGB32_Premultiplied)
248
+ image.fill(Qt.GlobalColor.transparent)
249
+ painter = QPainter(image)
250
+ self.paint(painter, image.rect(), mode, state)
251
+ painter.end()
252
+ result = QPixmap.fromImage(image)
253
+ result.setDevicePixelRatio(scale)
254
+ return result
255
+
256
+ def clone(self):
257
+ return _RasterFileIconEngine(self.image)
258
+
259
+
260
+ class _ColorizedIconEngine(QIconEngine):
261
+ """Tint an icon's alpha mask using a color resolved for the current theme."""
262
+
263
+ def __init__(self, source: QIcon, color):
264
+ super().__init__()
265
+ self.source = source
266
+ self.color = color
267
+
268
+ def paint(self, painter, rect, mode, state):
269
+ size = QSize(max(1, round(rect.width())), max(1, round(rect.height())))
270
+ pixmap = self.scaledPixmap(size, mode, state, painter.device().devicePixelRatioF())
271
+ painter.drawPixmap(rect, pixmap)
272
+
273
+ def pixmap(self, size, mode, state):
274
+ return self.scaledPixmap(size, mode, state, 1.0)
275
+
276
+ def scaledPixmap(self, size, mode, state, scale):
277
+ from ok.ui.qt.kit.theme import theme
278
+
279
+ value = self.color
280
+ if isinstance(value, dict):
281
+ value = value.get("dark" if theme.is_dark() else "light", "text")
282
+ color = theme.color(value) if isinstance(value, str) and not value.startswith("#") else QColor(value)
283
+ if mode == QIcon.Mode.Disabled:
284
+ color.setAlphaF(color.alphaF() * 0.4)
285
+ image = self.source.pixmap(size, scale, QIcon.Mode.Normal, state).toImage()
286
+ painter = QPainter(image)
287
+ painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceIn)
288
+ painter.fillRect(image.rect(), color)
289
+ painter.end()
290
+ result = QPixmap.fromImage(image)
291
+ result.setDevicePixelRatio(image.devicePixelRatio())
292
+ return result
293
+
294
+ def clone(self):
295
+ return _ColorizedIconEngine(self.source, self.color)
296
+
297
+
298
+ def tinted_icon(source: QIcon, color) -> QIcon:
299
+ """Recolor artwork at paint time; ``color`` can supply light/dark shades."""
300
+
301
+ return QIcon(_ColorizedIconEngine(source, color))
302
+
303
+
304
+ def _trim_transparent(image: QImage) -> QImage:
305
+ rgba = image.convertToFormat(QImage.Format.Format_RGBA8888)
306
+ # Ignore nearly invisible export fringes when measuring the artwork.
307
+ occupied = [i for i, alpha in enumerate(bytes(rgba.constBits())[3::4]) if alpha >= 16]
308
+ if not occupied:
309
+ return image
310
+ width = rgba.width()
311
+ left = min(i % width for i in occupied)
312
+ right = max(i % width for i in occupied)
313
+ top, bottom = occupied[0] // width, occupied[-1] // width
314
+ return image.copy(left, top, right - left + 1, bottom - top + 1)
315
+
316
+
222
317
  def icon(name: str, color=None, stroke_width: float = 1.75) -> QIcon:
223
318
  """A bundled icon.
224
319
 
@@ -230,25 +325,32 @@ def icon(name: str, color=None, stroke_width: float = 1.75) -> QIcon:
230
325
 
231
326
 
232
327
  @lru_cache(maxsize=256)
233
- def _load_file_icon(path: str, mtime: float) -> QIcon:
328
+ def _load_file_icon(path: str, mtime: float, trim_transparent: bool = False) -> QIcon:
234
329
  if path.lower().endswith(".svg"):
235
330
  data = Path(path).read_bytes()
236
331
  engine = _SvgFileIconEngine(data)
237
332
  if not engine.renderer.isValid():
238
333
  raise ValueError(f"invalid SVG icon: {path}")
239
334
  return QIcon(engine)
335
+ if trim_transparent:
336
+ image = QImage(path)
337
+ if image.isNull():
338
+ raise ValueError(f"unsupported icon file: {path}")
339
+ return QIcon(_RasterFileIconEngine(_trim_transparent(image)))
240
340
  result = QIcon(path)
241
341
  if result.isNull():
242
342
  raise ValueError(f"unsupported icon file: {path}")
243
343
  return result
244
344
 
245
345
 
246
- def load_icon(source, base_dir=None) -> QIcon:
346
+ def load_icon(source, base_dir=None, *, trim_transparent: bool = False) -> QIcon:
247
347
  """Load an app-provided icon.
248
348
 
249
349
  ``source`` is a path to an ``.svg`` or raster file (relative paths are
250
350
  resolved against ``base_dir`` or the working directory), a ``QIcon``, or
251
351
  ``None`` for no icon.
352
+ ``trim_transparent`` fits raster artwork by its alpha bounds at paint time;
353
+ the source file stays unchanged.
252
354
  """
253
355
 
254
356
  if source is None:
@@ -261,4 +363,4 @@ def load_icon(source, base_dir=None) -> QIcon:
261
363
  path = path.resolve()
262
364
  if not path.is_file():
263
365
  raise FileNotFoundError(f"icon file not found: {path}")
264
- return _load_file_icon(str(path), path.stat().st_mtime)
366
+ return _load_file_icon(str(path), path.stat().st_mtime, trim_transparent)
@@ -218,12 +218,13 @@ QAbstractItemView {{
218
218
  selection-color: {text};
219
219
  outline: 0;
220
220
  }}
221
+ QTableView::item {{ padding: 0 12px; }}
221
222
  QHeaderView::section {{
222
223
  background: {surface_alt};
223
224
  color: {text_secondary};
224
225
  border: 0;
225
226
  border-bottom: 1px solid {border};
226
- padding: 6px 10px;
227
+ padding: 6px 12px;
227
228
  font-weight: 600;
228
229
  }}
229
230
 
@@ -387,6 +388,11 @@ QLabel#taskTile {{
387
388
  font-size: 12px;
388
389
  font-weight: 600;
389
390
  }}
391
+ TaskIconTile#taskTile {{
392
+ background: transparent;
393
+ border: 0;
394
+ border-radius: 0;
395
+ }}
390
396
  QPushButton#sectionHeader {{
391
397
  background: transparent;
392
398
  border: 0;
@@ -54,8 +54,9 @@ class TabItem(QPushButton):
54
54
  self.icon_label.hide()
55
55
  return
56
56
  icon = source if isinstance(source, QIcon) else kit_icon(source, "text" if self.isChecked() else "text_secondary")
57
- self.icon_label.setPixmap(icon.pixmap(QSize(16, 16), self.devicePixelRatioF()))
58
- self.icon_label.setFixedSize(16, 16)
57
+ size = 20 if isinstance(source, QIcon) else 16
58
+ self.icon_label.setPixmap(icon.pixmap(QSize(size, size), self.devicePixelRatioF()))
59
+ self.icon_label.setFixedSize(size, size)
59
60
  self.icon_label.show()
60
61
 
61
62
  def _on_toggled(self, checked: bool) -> None:
@@ -18,6 +18,7 @@ class ImportedTaskTab(TasksPage):
18
18
  def __init__(self, namespace, name):
19
19
  self.namespace = namespace
20
20
  self.imported_file_name = namespace
21
+ self.state_name = f"tasks_page_{namespace}"
21
22
  super().__init__(title=og.app.tr(name))
22
23
 
23
24
  def collect_tasks(self):