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,124 @@
1
+ """MCP Query window — the mcp_query.py collectors, run live in the studio.
2
+
3
+ The five MCP tools (`find_views`, `describe_view`, `hit_test`,
4
+ `param_sources`, `tile_cache`) are thin wrappers around one collector each:
5
+ the server hands `mcp_eval.request_call` a no-arg closure, the render thread
6
+ runs it between frames, and the answer is JSON-dumped back to the client.
7
+ This window is a second front end onto exactly those collectors — a
8
+ render_func body ALREADY runs on the render thread, so it calls
9
+ `mcp_query.collect_*` directly, with no queue and no server in the way. What
10
+ you read here is what an MCP client reads: the JSON tab dumps the answer the
11
+ way `run_query` does, so its bytes are the tool's bytes.
12
+
13
+ Pick a tool, edit its arguments, and the answer re-collects on every edit
14
+ (and on every Run press, since the studio moves under a fixed set of
15
+ arguments). The collector is looked up by NAME per run, so a hotswap of
16
+ mcp_query.py lands on the next frame — this window is the fast loop for
17
+ iterating on the collectors themselves.
18
+
19
+ `follow pointer` (hit_test only) re-collects at the live mouse position
20
+ every frame, which is the quickest way to sanity-check the BVH stack and the
21
+ subscription table: park the pointer over anything and read what claims it.
22
+ """
23
+
24
+ import json
25
+ import time
26
+ import traceback
27
+
28
+ import meltygui_imgui as imgui
29
+
30
+ import meltygui.core.automation.mcp_query as mcp_query
31
+ from meltygui.core.melty import Melty
32
+ from meltygui.core.conversion.dict_conversion import DictConversion
33
+ from meltygui.core.rendering.render_funcs import RenderFuncs
34
+ from meltygui.core.core_render import render_func
35
+ from meltygui.core.rendering.window_decoration import window
36
+
37
+ # Tool name -> its arguments and their defaults, in the order the real tool
38
+ # declares them. This table IS the API: an argument dict is rendered by
39
+ # draw_collection, so adding an argument to a collector means adding it here
40
+ # and nothing else. The collector itself is resolved by name at call time
41
+ # (`collect_<tool>`), never bound here, so hotswapping mcp_query.py takes
42
+ # effect immediately instead of pinning the function objects this module
43
+ # imported. Same reason studio_data.py keeps _SOURCES module-level: a
44
+ # dictionary the window reads, not a tuning constant (those live in Toggles).
45
+ _TOOL_ARGS = {
46
+ "find_views": {"func": "", "name": "", "window": "", "include_closed": False,
47
+ "limit": 50},
48
+ "describe_view": {"view": "", "children_depth": 1},
49
+ "hit_test": {"x": 0.0, "y": 0.0},
50
+ "param_sources": {"view": "", "param": ""},
51
+ "tile_cache": {"view": "", "history_frames": 200, "limit": 50},
52
+ }
53
+
54
+
55
+ def _seed_args(panel_state):
56
+ """Reconcile the persisted arguments against `_TOOL_ARGS`.
57
+
58
+ A tool or an argument added since the state was saved appears with its
59
+ default; one that no longer exists is dropped. This is what lets a
60
+ hotswap that adds an argument show it on the next frame.
61
+ """
62
+ args = panel_state.args
63
+ for tool, defaults in _TOOL_ARGS.items():
64
+ current = args.get(tool)
65
+ if not isinstance(current, dict):
66
+ current = args[tool] = {}
67
+ for key, value in defaults.items():
68
+ current.setdefault(key, value)
69
+ for key in [key for key in current if key not in defaults]:
70
+ del current[key]
71
+ for tool in [tool for tool in args if tool not in _TOOL_ARGS]:
72
+ del args[tool]
73
+ if panel_state.tool not in _TOOL_ARGS:
74
+ panel_state.tool = next(iter(_TOOL_ARGS))
75
+
76
+
77
+ def _pointer():
78
+ """Where the input handler thinks the mouse is.
79
+
80
+ The same numbers `collect_hit_test` reports as `pointer`, so a followed
81
+ point reads `pointer_matches_point: true` and the subscription table is
82
+ populated. imgui's own mouse position is the fallback for a frame before
83
+ the handler has one.
84
+ """
85
+ handler = getattr(Melty, "event_handler", None)
86
+ x = getattr(handler, "_cursor_x", None) if handler is not None else None
87
+ y = getattr(handler, "_cursor_y", None) if handler is not None else None
88
+ if x is None or y is None:
89
+ x, y = imgui.get_mouse_pos()
90
+ return round(float(x), 1), round(float(y), 1)
91
+
92
+
93
+ def _run(panel_state, tool, args):
94
+ """Collect once, into the SAME result dict.
95
+
96
+ The answer is copied into the held dict rather than replacing it so the
97
+ result tree keeps its identity across runs — expanded rows stay expanded
98
+ while you edit an argument, which is the whole point of following the
99
+ "mutate in place" rule here. A collector that raises is reported in the
100
+ window instead of taking the frame down with it: these walk live state
101
+ and a half-built draw_state is exactly the case worth seeing.
102
+ """
103
+ collect = getattr(mcp_query, "collect_" + tool, None)
104
+ started = time.perf_counter()
105
+ if collect is None:
106
+ answer, error = {}, f"mcp_query has no collect_{tool}"
107
+ else:
108
+ try:
109
+ answer, error = collect(**args), None
110
+ except Exception:
111
+ answer, error = {}, traceback.format_exc()
112
+ elapsed_ms = (time.perf_counter() - started) * 1000.0
113
+ panel_state._result.clear()
114
+ panel_state._result.update(answer)
115
+ panel_state._error = error
116
+ panel_state._json = json.dumps(answer, indent=1, default=str)
117
+ panel_state._stamp = (f"{elapsed_ms:.2f} ms · {len(panel_state._json)} B"
118
+ f" · frame {Melty.frame_count}")
119
+
120
+
121
+ # disable_scroll=False: the window is a scrolling box of panes, and the
122
+ # @window code path defaults it to True.
123
+ from meltygui.view.query_view import draw_mcp_query
124
+ draw_mcp_query = window(icon='\uf188', display_name='MCP Query', tint=(0.1, 0.2, 0.28), initial={'width': 820, 'height': 700}, disable_scroll=False)(draw_mcp_query)
@@ -0,0 +1,26 @@
1
+ """Search core functions and supporting definitions."""
2
+ import types
3
+
4
+
5
+ def search_activate_target(node):
6
+ """The draw_state Ctrl+Enter should 'click' for the current search match
7
+ `node` (meltygui.search_current_node). When the match is one of a collection's
8
+ keys, that's the child at the current key; for a leaf content match it's the
9
+ node itself."""
10
+ if node is None:
11
+ return None
12
+ key = getattr(node, '_search_current_key', None)
13
+ if key is not None:
14
+ child = node._children.get(key)
15
+ if child is not None:
16
+ return child
17
+ # Views that draw their matches as raw draw-list items (no child draw_states
18
+ # - e.g. the Fast Dock) publish the current match's row rect instead; hand
19
+ # back a geometry shim so the caller's center-of-rect click lands on the
20
+ # row rather than the view's center.
21
+ rect = getattr(node, '_search_current_rect', None)
22
+ if rect is not None:
23
+ return types.SimpleNamespace(abs_left=rect[0], abs_top=rect[1],
24
+ width=rect[2], height=rect[3],
25
+ _tile_id=node._tile_id)
26
+ return node
@@ -0,0 +1,340 @@
1
+ """The UI path language: leaf-anchored selectors over the live draw_state
2
+ tree, used by change_value / orchestration cues to name a target the way a
3
+ person would — by the minimal distinguishing tail of its ancestry.
4
+
5
+ A PATH is a sequence of SEGMENTS matched as a SUBSEQUENCE of the target's
6
+ ancestor chain, anchored at the leaf: the last segment matches the target
7
+ itself, the earlier ones match ancestors in order with any depth of
8
+ unnamed/unmatched levels between (descendant matching is the default, so
9
+ nesting is never the caller's problem unless it is the distinguisher).
10
+
11
+ Segment kinds — each one is a data-description level:
12
+ "alpha" name (display name, the "##"-stripped str(ds.name))
13
+ 1 ordinal: the nth match (visual order: abs_top, abs_left)
14
+ of the REMAINING path within the current scope —
15
+ "Loras/[1]/alpha" = the 2nd alpha under Loras. (A
16
+ per-node "nth child of its parent" reading was tried
17
+ and rejected: single-child wrapper levels made it
18
+ match nearly everything.)
19
+ Key("33071d") exact full name (no "##" strip) — collection keys
20
+ Lora type of the value the draw_state renders
21
+ callable predicate fn(ds) -> bool
22
+
23
+ Resolution requires UNIQUENESS: zero matches raise NoMatch, several raise
24
+ Ambiguous listing each candidate's minimal_path — the error is the
25
+ disambiguation. String sugar: "Loras/[1]/alpha" parses to ("Loras", 1,
26
+ "alpha"); tuples are the full-power form (Key/type/callable segments).
27
+
28
+ Everything takes an optional `universe` (iterable of draw_states) so the
29
+ resolver is headless-testable on fakes; the default universe is the live
30
+ cache (Melty.cache.key_to_draw_state), with draw_states under a CLOSED
31
+ window filtered out (their tiles persist across sessions but are not on
32
+ screen). Persisted-but-collapsed subtrees DO resolve — deciding whether a
33
+ resolved target is actually hittable is the gate check's job, not the
34
+ resolver's.
35
+ """
36
+
37
+
38
+ class Key:
39
+ """Exact full-name segment (collection keys): matches str(ds.name)
40
+ verbatim, no "##" stripping."""
41
+
42
+ __slots__ = ("key",)
43
+
44
+ def __init__(self, key):
45
+ self.key = key
46
+
47
+ def __repr__(self):
48
+ return f"Key({self.key!r})"
49
+
50
+
51
+ class NoMatch(LookupError):
52
+ def __init__(self, path, hint=""):
53
+ self.path = path
54
+ super().__init__(f"no draw_state matches {format_path(path)}"
55
+ + (f" ({hint})" if hint else ""))
56
+
57
+
58
+ class Ambiguous(LookupError):
59
+ def __init__(self, path, candidates):
60
+ self.path = path
61
+ self.candidates = candidates
62
+ options = ", ".join(format_path(c) for c in candidates[:8])
63
+ super().__init__(f"{format_path(path)} matches {len(candidates)} targets — "
64
+ f"use one of: {options}")
65
+
66
+
67
+ def format_path(path):
68
+ if isinstance(path, str):
69
+ return repr(path)
70
+ parts = []
71
+ for segment in path:
72
+ if isinstance(segment, int):
73
+ parts.append(f"[{segment}]")
74
+ elif isinstance(segment, Key):
75
+ parts.append(f"{{{segment.key}}}")
76
+ elif isinstance(segment, type):
77
+ parts.append(segment.__name__)
78
+ elif callable(segment):
79
+ parts.append(getattr(segment, "__name__", "<predicate>"))
80
+ else:
81
+ parts.append(str(segment))
82
+ return "/".join(parts)
83
+
84
+
85
+ def parse(path):
86
+ """String sugar -> segment tuple. "Loras/[1]/alpha" -> ("Loras", 1,
87
+ "alpha"). Tuples/lists pass through. Key/type/callable segments exist
88
+ only in tuple form."""
89
+ if not isinstance(path, str):
90
+ return tuple(path)
91
+ segments = []
92
+ for raw in path.split("/"):
93
+ raw = raw.strip()
94
+ if not raw:
95
+ continue
96
+ if raw.startswith("[") and raw.endswith("]"):
97
+ segments.append(int(raw[1:-1]))
98
+ elif raw.startswith("{") and raw.endswith("}"):
99
+ segments.append(Key(raw[1:-1]))
100
+ else:
101
+ segments.append(raw)
102
+ if not segments:
103
+ raise ValueError("empty path")
104
+ return tuple(segments)
105
+
106
+
107
+ def display_name(ds):
108
+ name = getattr(ds, "name", None)
109
+ return str(name).split("##")[0] if name is not None else ""
110
+
111
+
112
+ def full_name(ds):
113
+ name = getattr(ds, "name", None)
114
+ return str(name) if name is not None else ""
115
+
116
+
117
+ def ancestor_chain(ds, cap=64):
118
+ """ds's ancestors, nearest first (ds itself excluded). Follows `_parent`;
119
+ the root's _parent is ITSELF, which terminates the walk."""
120
+ chain = []
121
+ node = getattr(ds, "_parent", None)
122
+ while node is not None and node is not ds and len(chain) < cap:
123
+ chain.append(node)
124
+ parent = getattr(node, "_parent", None)
125
+ if parent is node:
126
+ break
127
+ node, ds = parent, node
128
+ return chain
129
+
130
+
131
+ def _under_closed_window(ds):
132
+ node, steps = ds, 0
133
+ while node is not None and steps < 64:
134
+ if getattr(node, "closed", False):
135
+ return True
136
+ window = getattr(node, "parent_window", None)
137
+ if window is None or window is node:
138
+ return False
139
+ node, steps = window, steps + 1
140
+ return False
141
+
142
+
143
+ # Per-frame memo of the live universe and its name index:
144
+ # (frame_count, cache size, nodes, index). The orchestrator's precondition
145
+ # refresh resolves a few hundred paths per pass (minimal_path tries every
146
+ # suffix of every candidate), and each used to build the universe (an
147
+ # _under_closed_window walk per draw_state) and scan it once per segment
148
+ # until it - ~5.6 M segment_matches, 480 ms every 20 frames on a session
149
+ # with 5.5k draw_states (profiled 09-01). Now: one build per frame, and a
150
+ # name segment is a dict lookup.
151
+ _UNIVERSE_MEMO = None
152
+
153
+
154
+ class _Index:
155
+ """Name lookups over one universe list: display name (the "##"-stripped
156
+ str(ds.name)) and full name → the nodes carrying it, in universe order.
157
+ Type / predicate segments still scan the nodes."""
158
+ __slots__ = ("nodes", "by_display", "by_full")
159
+
160
+ def __init__(self, nodes):
161
+ self.nodes = nodes
162
+ by_display, by_full = {}, {}
163
+ for ds in nodes:
164
+ name = getattr(ds, "name", None)
165
+ if name is None:
166
+ continue
167
+ full = str(name)
168
+ by_full.setdefault(full, []).append(ds)
169
+ by_display.setdefault(full.split("##")[0], []).append(ds)
170
+ self.by_display, self.by_full = by_display, by_full
171
+
172
+
173
+ def default_universe():
174
+ global _UNIVERSE_MEMO
175
+ from meltygui.core.melty import Melty
176
+ cache = getattr(Melty, "cache", None)
177
+ if cache is None:
178
+ return []
179
+ key = (Melty.frame_count, len(cache.key_to_draw_state))
180
+ memo = _UNIVERSE_MEMO
181
+ if memo is not None and memo[0] == key:
182
+ return memo[2]
183
+ nodes = [ds for ds in cache.key_to_draw_state.values()
184
+ if ds is not None and not _under_closed_window(ds)]
185
+ _UNIVERSE_MEMO = (key, None, nodes, None)
186
+ return nodes
187
+
188
+
189
+ def _index_for(universe):
190
+ """The _Index over `universe` — memoized beside the per-frame default
191
+ universe (identity match), built per call for a caller-supplied list."""
192
+ global _UNIVERSE_MEMO
193
+ memo = _UNIVERSE_MEMO
194
+ if memo is not None and memo[2] is universe:
195
+ if memo[3] is None:
196
+ _UNIVERSE_MEMO = memo = (memo[0], None, universe, _Index(universe))
197
+ return memo[3]
198
+ return _Index(universe)
199
+
200
+
201
+ def segment_matches(segment, ds):
202
+ if isinstance(segment, str):
203
+ return display_name(ds) == segment
204
+ if isinstance(segment, (bool, int)): # ordinals are handled by the caller
205
+ return False
206
+ if isinstance(segment, Key):
207
+ return full_name(ds) == segment.key
208
+ if isinstance(segment, type):
209
+ value = getattr(ds, "_raw_input_value", None)
210
+ return value is not None and isinstance(value, segment)
211
+ if callable(segment):
212
+ try:
213
+ return bool(segment(ds))
214
+ except Exception:
215
+ return False
216
+ return False
217
+
218
+
219
+ def _visual_order(matches):
220
+ return sorted(matches, key=lambda ds: (getattr(ds, "abs_top", 0) or 0,
221
+ getattr(ds, "abs_left", 0) or 0))
222
+
223
+
224
+ def _matches_in_scope(segment, scope, universe, index=None):
225
+ """Every universe node matching `segment` with `scope` among its
226
+ ancestors (scope None = anywhere). One hop of the path = descendant
227
+ matching at any depth, which is what makes nesting the caller's
228
+ non-problem. With an `index`, a name / Key segment reads its
229
+ candidates off the name tables instead of scanning the universe."""
230
+ found = []
231
+ if index is not None and isinstance(segment, str):
232
+ candidates = index.by_display.get(segment, ())
233
+ elif index is not None and isinstance(segment, Key):
234
+ candidates = index.by_full.get(segment.key, ())
235
+ else:
236
+ candidates = universe
237
+ for ds in candidates:
238
+ if not segment_matches(segment, ds):
239
+ continue
240
+ # STRICT descendants only: a scope never matches its own next
241
+ # segment. A path is a subsequence of the ANCESTOR chain (module
242
+ # docstring), so "name/name" names a child called name below a
243
+ # parent called name - draw_str's row "name" wrapping the editor
244
+ # "name##innder" - and must not also match the parent alone (it
245
+ # did, and the text field's chain resolved byously, 09-01).
246
+ if scope is not None and not any(a is scope for a in ancestor_chain(ds)):
247
+ continue
248
+ found.append(ds)
249
+ return found
250
+
251
+
252
+ def _resolve_set(segments, scopes, universe, index=None):
253
+ """Segment-at-a-time descent (equivalent to subsequence matching for
254
+ named segments). An int segment indexes the VISUALLY-ORDERED result of
255
+ the remaining path within the current scopes and returns that single
256
+ node — "the nth ⟨rest⟩ here"."""
257
+ if not segments:
258
+ return scopes
259
+ head, rest = segments[0], segments[1:]
260
+ if isinstance(head, int) and not isinstance(head, bool):
261
+ results = _visual_order(_resolve_set(rest, scopes, universe, index))
262
+ return [results[head]] if 0 <= head < len(results) else []
263
+ next_scopes, seen = [], set()
264
+ for scope in scopes:
265
+ for ds in _matches_in_scope(head, scope, universe, index):
266
+ if id(ds) not in seen:
267
+ seen.add(id(ds))
268
+ next_scopes.append(ds)
269
+ return _resolve_set(rest, next_scopes, universe, index)
270
+
271
+
272
+ def find_all(path, within=None, universe=None):
273
+ segments = parse(path)
274
+ if universe is None:
275
+ universe = default_universe()
276
+ elif not isinstance(universe, list):
277
+ universe = list(universe)
278
+ return _resolve_set(segments, [within], universe, _index_for(universe))
279
+
280
+
281
+ def resolve(path, within=None, universe=None):
282
+ """The path's unique live target, or a teaching error: NoMatch names the
283
+ path, Ambiguous lists each candidate as its own minimal_path so the
284
+ error message IS the disambiguation."""
285
+ matches = find_all(path, within=within, universe=universe)
286
+ if len(matches) == 1:
287
+ return matches[0]
288
+ if not matches:
289
+ raise NoMatch(parse(path))
290
+ exact = _exact_chain_matches(parse(path), matches)
291
+ if len(exact) == 1:
292
+ return exact[0]
293
+ if universe is None:
294
+ universe = default_universe()
295
+ raise Ambiguous(parse(path),
296
+ [minimal_path(ds, universe=universe) for ds in matches])
297
+
298
+
299
+ def _exact_chain_matches(segments, matches):
300
+ """The tie-break behind an ambiguous path: candidates whose name chain
301
+ ends with the path CONTIGUOUSLY (no skipped levels). A recorded chain
302
+ is a node's MAXIMAL capture, so a live node whose chain is exactly
303
+ that is the node it was captured from — while a same-named descendant
304
+ (a live-view window `scores` holding a voxel view `scores`: the
305
+ window's own chain also matches the child as a subsequence) is only a
306
+ loose match. Name segments only; a path with an ordinal / Key / type
307
+ segment has no chain to compare and gets no tie-break."""
308
+ if not segments or not all(isinstance(seg, str) for seg in segments):
309
+ return []
310
+ tail = tuple(segments)
311
+ return [ds for ds in matches if name_chain(ds)[-len(tail):] == tail]
312
+
313
+
314
+ def name_chain(ds, cap=64):
315
+ """Root-first display names of ds's ancestry + itself, unnamed levels
316
+ skipped. This is what cues CAPTURE (maximal); minimal_path trims LATE."""
317
+ names = [display_name(node) for node in reversed(ancestor_chain(ds, cap))]
318
+ names.append(display_name(ds))
319
+ return tuple(name for name in names if name)
320
+
321
+
322
+ def minimal_path(ds, universe=None):
323
+ """Shortest name-suffix of ds's chain that resolves uniquely in
324
+ `universe` — computed against the tree it must disambiguate within,
325
+ never at capture time. Falls back to the full chain when even that is
326
+ ambiguous (twins: caller needs an ordinal segment)."""
327
+ if universe is None:
328
+ universe = default_universe()
329
+ elif not isinstance(universe, list):
330
+ universe = list(universe)
331
+ chain = name_chain(ds)
332
+ if not chain:
333
+ return chain
334
+ index = _index_for(universe) # same index for every suffix tried
335
+ for length in range(1, len(chain) + 1):
336
+ suffix = chain[-length:]
337
+ matches = _resolve_set(suffix, [None], universe, index)
338
+ if len(matches) == 1:
339
+ return suffix
340
+ return chain