meltygui 0.1.0__py3-none-any.whl

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 (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,552 @@
1
+ """Typed MCP queries over live Melty state (the render thread's view of it).
2
+
3
+ The tools in mcp_server.py (`find_views`, `describe_view`, `hit_test`,
4
+ `param_sources`, `tile_cache`) each call one `collect_*` function here through
5
+ `mcp_eval.request_call`, so the walk runs ON the render thread between frames
6
+ (draw_states, the BVH, the input handler's per-frame tables and the tile cache
7
+ are all render-thread state) and comes back as a JSON-serializable dict.
8
+ Every result carries `frame` — `Melty.frame_count` when it was read.
9
+
10
+ Views are addressed by `draw_state._tile_id` (the cache key AND the event
11
+ view_id, `"<name>##<hash>"`), matched exact → unique substring → `ds.id`
12
+ prefix. Plain functions, no imgui: a headless test renders a view through the
13
+ wrapper and calls the collectors directly (tests/test_mcp_query.py).
14
+ """
15
+ import inspect
16
+ import json
17
+
18
+
19
+ # ----------------------------------------------------------------------------
20
+ # Helpers
21
+ # ----------------------------------------------------------------------------
22
+
23
+ def _melty():
24
+ from meltygui.core.melty import Melty
25
+ return Melty
26
+
27
+
28
+ def _short(value, limit=120):
29
+ """One-line repr for kwargs / values, bounded (a tensor repr can be MBs)."""
30
+ try:
31
+ import torch
32
+ if isinstance(value, torch.Tensor):
33
+ return f"Tensor{tuple(value.shape)} {value.dtype} {value.device}"
34
+ except Exception:
35
+ pass
36
+ try:
37
+ text = repr(value)
38
+ except Exception as exc:
39
+ text = f"<repr failed: {type(exc).__name__}>"
40
+ text = text.replace("\n", "\\n")
41
+ return text if len(text) <= limit else text[:limit - 1] + "…"
42
+
43
+
44
+ def _func_info(func):
45
+ """(qualname, "file:line") of a render func, unwrapped past @render_func."""
46
+ if func is None:
47
+ return None, None
48
+ try:
49
+ raw = inspect.unwrap(func)
50
+ except Exception:
51
+ raw = func
52
+ name = getattr(raw, "__qualname__", None) or getattr(raw, "__name__", None) or repr(raw)
53
+ where = None
54
+ code = getattr(raw, "__code__", None)
55
+ if code is not None:
56
+ where = f"{code.co_filename}:{code.co_firstlineno}"
57
+ return name, where
58
+
59
+
60
+ def _rect(ds):
61
+ """Live [left, top, width, height]; the cached abs_* when the live
62
+ accessors aren't available (test stubs)."""
63
+ try:
64
+ left, top = ds._abs_left(), ds._abs_top()
65
+ except Exception:
66
+ left, top = getattr(ds, "abs_left", 0.0), getattr(ds, "abs_top", 0.0)
67
+ return [round(float(left), 1), round(float(top), 1),
68
+ round(float(getattr(ds, "width", 0.0) or 0.0), 1),
69
+ round(float(getattr(ds, "height", 0.0) or 0.0), 1)]
70
+
71
+
72
+ def _rect4(rect):
73
+ if rect is None:
74
+ return None
75
+ try:
76
+ return [round(float(v), 1) for v in rect]
77
+ except Exception:
78
+ return None
79
+
80
+
81
+ def _window_name(ds):
82
+ """The title list_windows shows for a root window (ManagedWindow.name),
83
+ else the draw_state's own name / tile_id prefix (nested windows)."""
84
+ if ds is None:
85
+ return None
86
+ Melty = _melty()
87
+ tile_id = getattr(ds, "_tile_id", None)
88
+ managed = Melty.registered_windows.get(tile_id) if tile_id is not None else None
89
+ if managed is not None and getattr(managed, "name", None):
90
+ return managed.name
91
+ name = getattr(ds, "name", None)
92
+ if name:
93
+ return name
94
+ return str(tile_id).split("##")[0] if tile_id else None
95
+
96
+
97
+ def _all_views():
98
+ """Every draw_state Melty knows about right now: BVH-indexed views, the
99
+ root windows and the paint-ordered window list, deduped by identity."""
100
+ Melty = _melty()
101
+ seen = {}
102
+ for ds in list(Melty._bvh_id_to_ds.values()):
103
+ seen.setdefault(id(ds), ds)
104
+ for managed in list(Melty.registered_windows.values()):
105
+ ds = getattr(managed, "draw_state", None)
106
+ if ds is not None:
107
+ seen.setdefault(id(ds), ds)
108
+ for ds in list(Melty.paint_ordered_ds):
109
+ seen.setdefault(id(ds), ds)
110
+ return list(seen.values())
111
+
112
+
113
+ def resolve_view(ref):
114
+ """The draw_state a tool argument names: exact _tile_id, else the unique
115
+ tile_id substring, else a ds.id prefix. Returns (ds, error_text)."""
116
+ ref = (ref or "").strip()
117
+ if not ref:
118
+ return None, "no view given — pass a _tile_id from find_views / hit_test"
119
+ views = _all_views()
120
+ for ds in views:
121
+ if getattr(ds, "_tile_id", None) == ref:
122
+ return ds, None
123
+ lowered = ref.lower()
124
+ partial = [ds for ds in views
125
+ if lowered in str(getattr(ds, "_tile_id", "")).lower()]
126
+ if len(partial) == 1:
127
+ return partial[0], None
128
+ by_id = [ds for ds in views if str(getattr(ds, "id", "")).startswith(ref)]
129
+ if len(by_id) == 1:
130
+ return by_id[0], None
131
+ candidates = partial or by_id
132
+ if candidates:
133
+ names = sorted(str(getattr(ds, "_tile_id", ds.id)) for ds in candidates)[:12]
134
+ return None, f"'{ref}' matches {len(candidates)} views: " + ", ".join(names)
135
+ return None, f"no view matches '{ref}'"
136
+
137
+
138
+ def _tile_state(ds):
139
+ """The tile-cache facts of one view, or None when it has no tile."""
140
+ Melty = _melty()
141
+ cache = Melty.cache
142
+ key = getattr(ds, "_tile_id", None)
143
+ if cache is None or key is None:
144
+ return None
145
+ tile = cache._tiles.get(key)
146
+ if tile is None:
147
+ return None
148
+ tracker_note = None
149
+ try:
150
+ from meltygui.core.cache.invalidation_tracker import InvalidateTracker
151
+ note = InvalidateTracker.invalidations.get(key)
152
+ if note is not None:
153
+ tracker_note = {"name": note.name, "reason": note.reason, "frame": note.frame}
154
+ except Exception:
155
+ pass
156
+ return {
157
+ "dirty": bool(tile.dirty),
158
+ "force_invalidate": bool(tile.force_invalidate),
159
+ "last_clean_cache_frame": tile.last_clean_frame,
160
+ "last_invalidated_cache_frame": tile.last_invalidated_frame,
161
+ "cache_frame": cache._frame_id,
162
+ "size": list(tile.size) if tile.size else None,
163
+ "alloc_size": list(tile.alloc_size) if tile.alloc_size else None,
164
+ "last_bump": getattr(tile, "_last_bump", None),
165
+ "blit_served_frame": getattr(ds, "_blit_served_frame", None),
166
+ "did_use_cache": getattr(ds, "_did_use_cache", None),
167
+ "tracker_note": tracker_note,
168
+ "parent_key": cache.key_to_parent_key.get(key),
169
+ "child_count": len(cache.parent_key_to_child_keys.get(key, ())),
170
+ }
171
+
172
+
173
+ def view_summary(ds):
174
+ """The one-row description of a view (find_views rows, hit_test stack
175
+ entries, parent / child chains)."""
176
+ Melty = _melty()
177
+ func_name, func_where = _func_info(getattr(ds, "_view_func", None))
178
+ parent_window = getattr(ds, "parent_window", None)
179
+ try:
180
+ root_window = ds.root_window
181
+ except Exception:
182
+ root_window = None
183
+ try:
184
+ clip = _rect4(ds.abs_clip_rect)
185
+ except Exception:
186
+ clip = None
187
+ try:
188
+ abs_closed = bool(ds.abs_closed)
189
+ except Exception:
190
+ abs_closed = None
191
+ try:
192
+ abs_layer = ds.abs_layer
193
+ except Exception:
194
+ abs_layer = None
195
+ raw_input = getattr(ds, "_raw_input_value", None)
196
+ return {
197
+ "tile_id": getattr(ds, "_tile_id", None),
198
+ "id": getattr(ds, "id", None),
199
+ "name": getattr(ds, "name", None),
200
+ "func": func_name,
201
+ "func_where": func_where,
202
+ "rect": _rect(ds),
203
+ "clip_rect": clip,
204
+ "closed": bool(getattr(ds, "closed", False)),
205
+ "abs_closed": abs_closed,
206
+ "hidden_offscreen": bool(getattr(ds, "_hidden_offscreen", False)),
207
+ "closable": bool(getattr(ds, "closable", False)),
208
+ "nested_window": bool(getattr(ds, "nested_window", False)),
209
+ "layer": getattr(ds, "layer", None),
210
+ "abs_layer": abs_layer,
211
+ "z_pos": getattr(ds, "z_pos", None),
212
+ "depth": getattr(ds, "depth", None),
213
+ "hovered": id(ds) in Melty.bvh_hover_ids,
214
+ "bounding_hovered": bool(getattr(ds, "_bounding_hovered", False)),
215
+ "selected": bool(getattr(ds, "selected", False)),
216
+ "parent_window": getattr(parent_window, "_tile_id", None),
217
+ "window": _window_name(root_window),
218
+ "input_type": type(raw_input).__name__ if raw_input is not None else None,
219
+ }
220
+
221
+
222
+ # ----------------------------------------------------------------------------
223
+ # 1. find_views / describe_view
224
+ # ----------------------------------------------------------------------------
225
+
226
+ def collect_find_views(func="", name="", window="", include_closed=False, limit=50):
227
+ """Views matching the case-insensitive substring filters, front-most
228
+ first (z_pos descending). `window` filters on the ROOT window's title."""
229
+ Melty = _melty()
230
+ func = (func or "").lower()
231
+ name = (name or "").lower()
232
+ window = (window or "").lower()
233
+ rows = []
234
+ for ds in _all_views():
235
+ if not include_closed:
236
+ try:
237
+ if ds.closed or ds.abs_closed:
238
+ continue
239
+ except Exception:
240
+ pass
241
+ row = view_summary(ds)
242
+ if func and func not in str(row["func"] or "").lower():
243
+ continue
244
+ if name and name not in f"{row['name'] or ''} {row['tile_id'] or ''}".lower():
245
+ continue
246
+ if window and window not in str(row["window"] or "").lower():
247
+ continue
248
+ rows.append(row)
249
+ rows.sort(key=lambda r: (r["z_pos"] or 0), reverse=True)
250
+ total = len(rows)
251
+ rows = rows[:max(1, int(limit))]
252
+ return {"frame": Melty.frame_count, "total": total, "returned": len(rows), "views": rows}
253
+
254
+
255
+ def _kwargs_summary(ds):
256
+ kwargs = getattr(ds, "_kwargs", None) or {}
257
+ skip = {"next_kwargs", "draw_state", "o_kwargs", "input_value", "style_manager"}
258
+ return {str(k): _short(v) for k, v in kwargs.items()
259
+ if k not in skip and not str(k).startswith("_")}
260
+
261
+
262
+ def _parent_chain(ds, max_hops=32):
263
+ """Render-tree ancestors, nearest first (the root's _parent is itself)."""
264
+ chain = []
265
+ seen = {id(ds)}
266
+ node = getattr(ds, "_parent", None)
267
+ while node is not None and id(node) not in seen and len(chain) < max_hops:
268
+ seen.add(id(node))
269
+ chain.append(view_summary(node))
270
+ node = getattr(node, "_parent", None)
271
+ return chain
272
+
273
+
274
+ def _window_chain(ds, max_hops=32):
275
+ chain = []
276
+ seen = {id(ds)}
277
+ node = getattr(ds, "parent_window", None)
278
+ while node is not None and id(node) not in seen and len(chain) < max_hops:
279
+ seen.add(id(node))
280
+ chain.append(view_summary(node))
281
+ node = getattr(node, "parent_window", None)
282
+ return chain
283
+
284
+
285
+ def _children(ds, depth, budget):
286
+ """Render-tree children (`_view_children`, this frame's, else `_children`)
287
+ as summaries, recursing `depth` levels within a total `budget` of rows."""
288
+ if depth <= 0 or budget[0] <= 0:
289
+ return []
290
+ kids = list((getattr(ds, "_view_children", None) or {}).values())
291
+ if not kids:
292
+ kids = list((getattr(ds, "_children", None) or {}).values())
293
+ out = []
294
+ for kid in kids:
295
+ if budget[0] <= 0:
296
+ break
297
+ if not hasattr(kid, "_tile_id"):
298
+ continue
299
+ budget[0] -= 1
300
+ row = view_summary(kid)
301
+ row["children"] = _children(kid, depth - 1, budget)
302
+ out.append(row)
303
+ return out
304
+
305
+
306
+ def _subscriptions_for(tile_id):
307
+ """This frame's event registrations (view_id, priority, [event names],
308
+ cursor) whose view_id belongs to `tile_id` — from the input handler's
309
+ per-frame hovered table, so only views under the real pointer have any."""
310
+ Melty = _melty()
311
+ handler = getattr(Melty, "event_handler", None)
312
+ if handler is None:
313
+ return []
314
+ import meltygui.core.input.input_handler as IH
315
+ subs = []
316
+ for view_id, priority, keys in list(getattr(handler, "_hovered", [])):
317
+ owner = IH._view_id_to_tile_id.get(view_id, view_id)
318
+ if owner != tile_id and view_id != tile_id:
319
+ continue
320
+ names = IH._view_id_names_cache.get(view_id, {})
321
+ flags = IH._view_id_flags_cache.get(view_id, {})
322
+ events = []
323
+ for key in sorted(keys):
324
+ entry = {"event": names.get(key, f"{key[0]}:{key[1]}")}
325
+ inverted, non_blocking = flags.get(key, (False, False))
326
+ if inverted:
327
+ entry["inverted"] = True
328
+ if non_blocking:
329
+ entry["non_blocking"] = True
330
+ events.append(entry)
331
+ cursor = getattr(handler, "_view_cursor", {}).get(view_id)
332
+ subs.append({
333
+ "view_id": view_id,
334
+ "priority": priority,
335
+ "blocker": view_id in getattr(handler, "_blocker_views", ()),
336
+ "events": events,
337
+ "cursor": (str(cursor[0]) if cursor else None),
338
+ "cursor_rect": (_rect4(cursor[1]) if cursor and cursor[1] else None),
339
+ })
340
+ subs.sort(key=lambda s: s["priority"])
341
+ return subs
342
+
343
+
344
+ def collect_describe_view(view, children_depth=1, max_children=60):
345
+ """Everything about one view: summary, kwargs, auto_params, event rects,
346
+ this frame's subscriptions, the tile-cache entry, the render-tree and
347
+ window ancestor chains and the children subtree."""
348
+ Melty = _melty()
349
+ ds, error = resolve_view(view)
350
+ if ds is None:
351
+ return {"frame": Melty.frame_count, "error": error}
352
+ out = {"frame": Melty.frame_count, "view": view_summary(ds)}
353
+ out["kwargs"] = _kwargs_summary(ds)
354
+ auto_params = getattr(ds, "auto_params", None) or {}
355
+ out["auto_params"] = {str(k): _short(v) for k, v in auto_params.items()}
356
+ event_rects = getattr(ds, "_event_rects", None) or {}
357
+ out["event_rects"] = {str(k): [_rect4(r) for r in rects] for k, rects in event_rects.items()}
358
+ out["subscriptions"] = _subscriptions_for(ds._tile_id)
359
+ out["tile"] = _tile_state(ds)
360
+ out["window_pos"] = _rect4(getattr(ds, "window_pos", None))
361
+ out["content_size"] = [getattr(ds, "content_width", None), getattr(ds, "content_height", None)]
362
+ out["header_height"] = getattr(ds, "header_height", None)
363
+ out["scroll"] = [getattr(ds, "scroll_offset", None), getattr(ds, "scroll_offset_x", None)]
364
+ out["parents"] = _parent_chain(ds)
365
+ out["windows"] = _window_chain(ds)
366
+ out["children"] = _children(ds, int(children_depth), [int(max_children)])
367
+ return out
368
+
369
+
370
+ # ----------------------------------------------------------------------------
371
+ # 2. hit_test
372
+ # ----------------------------------------------------------------------------
373
+
374
+ def collect_hit_test(x, y):
375
+ """The BVH stack at (x, y), front to back (bvh_query's order: z_pos),
376
+ each with its priority and — for the frame's real pointer position — the
377
+ event subscriptions and cursor registrations the input handler holds.
378
+ Subscriptions are only registered for views under the ACTUAL pointer,
379
+ so `pointer` says where they apply; at any other (x, y) the stack is
380
+ still exact but `subscriptions` are empty."""
381
+ Melty = _melty()
382
+ x, y = float(x), float(y)
383
+ try:
384
+ stack = list(Melty.bvh_query(x, y))
385
+ except Exception as exc:
386
+ return {"frame": Melty.frame_count, "error": f"bvh_query failed: {exc!r}"}
387
+ handler = getattr(Melty, "event_handler", None)
388
+ pointer = None
389
+ if handler is not None:
390
+ pointer = [getattr(handler, "_cursor_x", None), getattr(handler, "_cursor_y", None)]
391
+ rows = []
392
+ for ds in stack:
393
+ row = view_summary(ds)
394
+ # The stored z_pos → the priority an on_action from this frame
395
+ # started with (the `priority` property reads the frame's LIVE
396
+ # paint state, meaningless between frames).
397
+ try:
398
+ row["priority"] = ds._action_base_priority(getattr(ds, "z_pos", None) or 0)
399
+ except Exception:
400
+ row["priority"] = None
401
+ row["subscriptions"] = _subscriptions_for(ds._tile_id)
402
+ rows.append(row)
403
+ hovered = getattr(Melty, "hovered_ds", None)
404
+ out = {
405
+ "frame": Melty.frame_count,
406
+ "point": [x, y],
407
+ "pointer": pointer,
408
+ "pointer_matches_point": (pointer is not None and pointer[0] is not None
409
+ and abs(pointer[0] - x) < 0.5 and abs(pointer[1] - y) < 0.5),
410
+ "hovered_ds": getattr(hovered, "_tile_id", None),
411
+ "cursor_shape": str(getattr(handler, "cursor_shape", None)) if handler else None,
412
+ "drag_capture": ({str(k): [str(v[0]), str(v[1])] for k, v in
413
+ getattr(handler, "_drag_capture", {}).items()} if handler else {}),
414
+ "blockers": sorted(str(v) for v in getattr(handler, "_blocker_views", ())) if handler else [],
415
+ "stack": rows,
416
+ }
417
+ return out
418
+
419
+
420
+ # ----------------------------------------------------------------------------
421
+ # 3. param_sources
422
+ # ----------------------------------------------------------------------------
423
+
424
+ def collect_param_sources(view, param=""):
425
+ """The inputs tab as data: per parameter the value the view reads, the
426
+ driving source (the SourcePriority pick), and every source that sets it
427
+ in priority order."""
428
+ Melty = _melty()
429
+ ds, error = resolve_view(view)
430
+ if ds is None:
431
+ return {"frame": Melty.frame_count, "error": error}
432
+ import meltygui.core.rendering.parameter_core as A
433
+ from meltygui.core.rendering.render_dispatch import param_source_matrix
434
+ try:
435
+ srcs = A._sources_for(ds)
436
+ except Exception as exc:
437
+ return {"frame": Melty.frame_count, "view": view_summary(ds),
438
+ "error": f"collect_input_sources failed: {exc!r}"}
439
+ kinds = srcs.get("kinds", {})
440
+ writable = set(srcs.get("writable", ()))
441
+ locations = srcs.get("locations", {})
442
+ matrix = param_source_matrix(srcs["sources"], func=getattr(ds, "_view_func", None),
443
+ include_unmatched=True)
444
+ if isinstance(matrix, tuple):
445
+ matrix = matrix[1]
446
+ wanted = (param or "").strip()
447
+ params = {}
448
+ for name, per_source in matrix.items():
449
+ if wanted and name != wanted:
450
+ continue
451
+ setters = []
452
+ for sname, value in per_source.items():
453
+ setters.append({
454
+ "source": sname,
455
+ "kind": kinds.get(sname),
456
+ "priority": A._source_priority(kinds.get(sname))[0],
457
+ "writable": sname in writable,
458
+ "unset": A._unset_value(value),
459
+ "value": _short(value),
460
+ })
461
+ setters.sort(key=lambda s: (s["priority"], s["source"]))
462
+ try:
463
+ driving = A.get_source_for(name, ds)
464
+ except Exception:
465
+ driving = None
466
+ try:
467
+ value = _short(A.anywhere_value(name, ds))
468
+ except Exception:
469
+ value = None
470
+ params[name] = {"value": value, "driving": driving, "setters": setters}
471
+ # A source's dict is a placeholder `{}` until its code host has parsed
472
+ # (background work over the next frames): report that instead of an
473
+ # empty setter dict that reads as "nothing sets it".
474
+ sources = [{"source": s, "kind": kinds.get(s), "writable": s in writable,
475
+ "parsed": bool(srcs["sources"].get(s)),
476
+ "location": (list(locations[s]) if locations.get(s) else None)}
477
+ for s in sorted(srcs["sources"], key=lambda s: A._source_priority(kinds.get(s)))]
478
+ pending = [s["source"] for s in sources if not s["parsed"] and s["kind"] != "draw state"]
479
+ out = {"frame": Melty.frame_count, "view": view_summary(ds), "sources": sources,
480
+ "params": params}
481
+ if pending:
482
+ out["pending_sources"] = pending
483
+ out["note"] = "unparsed sources are still loading in the background — query again in a few frames"
484
+ if wanted and wanted not in params:
485
+ out["error"] = f"no param '{wanted}' on this view; params: {sorted(matrix)}"
486
+ return out
487
+
488
+
489
+ # ----------------------------------------------------------------------------
490
+ # 4. tile_cache
491
+ # ----------------------------------------------------------------------------
492
+
493
+ def collect_tile_cache(view="", history_frames=0, limit=100):
494
+ """Tile-cache state. With `view`: that tile's entry plus its invalidation
495
+ history over the last `history_frames` frames. Without: totals, the
496
+ per-frame body-run / cache-hit / capture counts over `history_frames`
497
+ (newest last) and the most recent invalidations (`limit`)."""
498
+ Melty = _melty()
499
+ cache = Melty.cache
500
+ if cache is None:
501
+ return {"frame": Melty.frame_count, "error": "no tile cache"}
502
+ now = Melty.frame_count
503
+ since = now - int(history_frames)
504
+ history = list(getattr(cache, "_invalidation_history", None) or ())
505
+ out = {"frame": now, "cache_frame": cache._frame_id, "enabled": bool(getattr(cache, "enabled", True))}
506
+ if view:
507
+ ds, error = resolve_view(view)
508
+ if ds is None:
509
+ return {"frame": now, "error": error}
510
+ out["view"] = view_summary(ds)
511
+ out["tile"] = _tile_state(ds)
512
+ key = ds._tile_id
513
+ rows = [h for h in history if h[1] == key and (history_frames <= 0 or h[0] >= since)]
514
+ out["invalidations"] = [
515
+ {"frame": f, "name": n, "reason": r, "force": force} for (f, _k, n, r, force) in rows[-int(limit):]]
516
+ return out
517
+ tiles = cache._tiles
518
+ dirty = sum(1 for t in tiles.values() if t.dirty)
519
+ out["tiles"] = len(tiles)
520
+ out["dirty_tiles"] = dirty
521
+ out["this_frame"] = {"body_runs": cache._frame_body_runs, "cache_hits": cache._frame_cache_hits}
522
+ stats = list(getattr(cache, "_frame_stats", None) or ())
523
+ if history_frames > 0:
524
+ stats = [s for s in stats if s[0] >= since]
525
+ else:
526
+ stats = stats[-10:]
527
+ out["frames"] = [{"frame": f, "body_runs": runs, "cache_hits": hits, "captures": caps}
528
+ for (f, runs, hits, caps) in stats]
529
+ rows = [h for h in history if history_frames <= 0 or h[0] >= since]
530
+ out["invalidations"] = [
531
+ {"frame": f, "key": k, "name": n, "reason": r, "force": force}
532
+ for (f, k, n, r, force) in rows[-int(limit):]]
533
+ # Who invalidates most over the window of the "per-frame invalidation" tell.
534
+ counts = {}
535
+ for (_f, k, n, _r, _force) in rows:
536
+ counts[(k, n)] = counts.get((k, n), 0) + 1
537
+ top = sorted(counts.items(), key=lambda kv: kv[1], reverse=True)[:15]
538
+ out["top_invalidators"] = [{"key": k, "name": n, "count": c} for ((k, n), c) in top]
539
+ return out
540
+
541
+
542
+ # ----------------------------------------------------------------------------
543
+ # Tool plumbing
544
+ # ----------------------------------------------------------------------------
545
+
546
+ def run_query(collect, model_server, timeout=10.0):
547
+ """Run a no-arg collector on the render thread and return JSON text."""
548
+ from meltygui.core.automation.mcp_eval import request_call
549
+ result, error = request_call(collect, model_server, timeout=timeout)
550
+ if error:
551
+ return json.dumps({"error": error}, indent=1)
552
+ return json.dumps(result, indent=1, default=str)