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,1891 @@
1
+ import inspect
2
+ import os
3
+ import re
4
+ import sys
5
+ import threading
6
+ import time
7
+ import traceback
8
+ import types
9
+ from collections import deque, defaultdict, namedtuple
10
+ from collections.abc import MutableMapping
11
+ from enum import Enum
12
+ from inspect import Parameter
13
+ from math import sqrt
14
+ from pathlib import Path
15
+ from types import NoneType
16
+ from typing import Any
17
+
18
+ import OpenGL.GL as gl
19
+ import meltygui.core.windowing.window_api as glfw
20
+ import math
21
+ import numpy
22
+ from meltygui_imgui.core import _DrawList
23
+
24
+ from meltygui.core.styling.fonts import Font
25
+ from meltygui.core.styling.global_style import GlobalStyle
26
+ from meltygui.core.melty import Melty
27
+ from meltygui.core.melty import CollectionAction
28
+ from meltygui.core.melty import ManagedWindow
29
+ from meltygui.core.melty import SearchTerm
30
+ from meltygui.core.conversion.render_host import RenderHost
31
+ from meltygui.core.rendering.shaped import Shaped
32
+ from meltygui.core.conversion.dict_conversion import DictConversion
33
+ from meltygui.core.rendering.modes import Modes
34
+ from meltygui.core.diagnostics.notifications import display
35
+ from meltygui.core.rendering.render_funcs import RenderFuncs
36
+ from meltygui.core.runtime.toggles import Toggles
37
+ from meltygui.core.runtime.toggles import Tint
38
+ from meltygui.core.runtime.toggles import mix
39
+ from meltygui.core.runtime.toggles import rgb_to_hsv
40
+ from meltygui.core.runtime.toggles import hsv_to_rgb
41
+ from meltygui.core.graphics.gl_state import GLState
42
+ from meltygui.utils.render_utils import print_colored_traceback
43
+ from meltygui.utils.render_utils import push_style_var
44
+ from meltygui.utils.render_utils import pop_style_var
45
+ from meltygui.utils.render_utils import end
46
+ from meltygui.utils.render_utils import begin
47
+ from meltygui.core.windowing.glfw_utils import print_stack_trace
48
+ from meltygui.core.windowing.glfw_utils import request_render
49
+ from meltygui.core.conversion.bubbling import _BubblingDict
50
+ from meltygui.core.conversion.bubbling import _DeepPath
51
+ from meltygui.core.conversion.cache_tree import UNSET_VALUE
52
+ from meltygui.code.libcst_conversion import Comment
53
+ from meltygui.code.libcst_conversion import GeneralParse
54
+ from meltygui.code.libcst_conversion import UsageRef
55
+ from meltygui.code.libcst_conversion import CallParse
56
+ from meltygui.code.libcst_conversion import ClassParse
57
+ from meltygui.code.libcst_conversion import EnumParse
58
+ from meltygui.code.libcst_conversion import FunctionParse
59
+ from meltygui.code.libcst_conversion import SymbolUsage
60
+ from meltygui.code.libcst_conversion import cst_module_to_dict
61
+ from meltygui.code.libcst_conversion import dict_to_cst_module
62
+ from meltygui.code.new_codecs import CallSite
63
+ from meltygui.code.new_converters import code_file_io
64
+ from meltygui.code.new_converters import convert_in_and_out_value
65
+ from meltygui.code.new_converters import cst_module_to_string
66
+ from meltygui.code.new_converters import string_to_cst_module
67
+ from meltygui.code.new_converters import code_hosts_for
68
+ from meltygui.code.new_converters import host_code_state
69
+ from meltygui.code.new_converters import recompile_button
70
+ from meltygui.code.new_converters import recompile_status
71
+ from meltygui.code.new_converters import run_recompile
72
+ from meltygui.core.conversion.path_finder import Pending
73
+ from meltygui.core.layout.cursor_core import same_line
74
+ from meltygui.core.cache.tile_cache import snap_int
75
+ from meltygui.core.cache.tile_cache import add_shadow
76
+ from meltygui.core.core_render import render_func
77
+ from meltygui.core.core_render import render_func_kwarg_names
78
+ from meltygui.core.core_render import SCROLL_BAR_WIDTH_DEFAULT
79
+ from meltygui.core.core_render import SCROLLBAR_MARGIN
80
+ from meltygui.core.rendering.parameter_core import SourcePriority
81
+ from meltygui.core.rendering.parameter_core import _source_priority
82
+ from meltygui.core.rendering.parameter_core import _sources_for
83
+ from meltygui.core.rendering.parameter_core import _driving_source
84
+ from meltygui.core.rendering.parameter_core import _setting_source
85
+ from meltygui.core.rendering.parameter_core import default_write_source
86
+ from meltygui.core.rendering.parameter_core import get_value_for_source
87
+ from meltygui.core.rendering.parameter_core import get_source_for
88
+ from meltygui.core.rendering.parameter_core import from_anywhere
89
+ from meltygui.core.rendering.parameter_core import anywhere_value
90
+ from meltygui.core.rendering.parameter_core import set_anywhere
91
+ from meltygui.core.rendering.parameter_core import flush_deferred_writes
92
+ from meltygui.core.rendering.parameter_core import SET_ANYWHERE_PARAMS
93
+ # Module import (not "from ... import DragDrop`) so hotswaps rebind cleanly.
94
+ import meltygui.core.input.drag_drop_core as _drag_drop
95
+ from meltygui.model.code_proxy_model import *
96
+ from meltygui.core.rendering.core_decoration import hotkey
97
+ from meltygui.core.rendering.core_decoration import Core
98
+ from meltygui.core.cache.invalidation_decoration import live
99
+ from meltygui.core.rendering.window_decoration import window
100
+ from meltygui.view.header_view import draw_header
101
+ from meltygui.core.diagnostics.inspection_core import set_fn_defaults
102
+ from meltygui.view.text_view import draw_text
103
+ from meltygui.editor.text_editor import _scroll_into_view
104
+ from meltygui.graphics.texture_manager import PendingTexture
105
+ from meltygui.core.rendering.core_decoration import defaults
106
+ from meltygui.code.symbol_roster import pass_scope
107
+
108
+
109
+ def some_text(input_value: str, draw_state, **kwargs):
110
+ imgui.text(f"Text: {input_value}")
111
+
112
+
113
+ # --- Word-aware fuzzy matcher (global search) ---------------------------------
114
+ # Identifiers are WORDS ("draw_any" -> draw, any; "TextEditor" -> text,
115
+ # editor). A query matches when its words each claim a DISTINCT target word:
116
+ # * a query word claims a target word it PREFIXES exactly ("dr" -> draw),
117
+ # or is an exact mid-word substring of when >= 2 chars ("raw" -> draw,
118
+ # "ny" -> any -- never a lone char: "a" must START a word, so "draw_a"
119
+ # never lands on draw_int / draw_collection via the "a" in draw), or
120
+ # fuzzily matches when its FIRST character matches ("amy" -> any: 1 edit)
121
+ # -- fuzz budget is only spent where the word start agrees;
122
+ # * words are unordered ("any_draw" -> draw_any);
123
+ # * a query without separators ("anydraw", "drawany", "rawany", "dra") is
124
+ # tried as one word, then SEGMENTED into pieces that each claim a word by
125
+ # the same rules ("any" + "draw").
126
+ # _word_match() returns the total edit cost (0 = exact) or None.
127
+ _WORD_RE = re.compile(r"[A-Z]+(?![a-z])|[A-Z]?[a-z]+|[0-9]+")
128
+
129
+
130
+ def print_hello():
131
+ print("Hello, world!")
132
+
133
+
134
+ from meltygui.core.automation.search_core import search_activate_target
135
+
136
+
137
+ from meltygui.view.collection_view import draw_collection
138
+
139
+
140
+ def main_header(input_value, name, **kwargs):
141
+ imgui.text("Main Header")
142
+
143
+
144
+ # value = input_value.fget(input_value)
145
+ # draw_any(value, name="Value", show_bg=True, draw_state=draw_state)
146
+
147
+
148
+ @render_func(is_lens_for=(type), skip_draw=True)
149
+ def type_lens(input_value, view_func, child_kwargs, **kwargs):
150
+ changed, value = view_func(**child_kwargs)
151
+ if changed:
152
+ for k, v in value.items():
153
+ if hasattr(input_value, k):
154
+ if k.startswith("_"):
155
+ continue
156
+ try:
157
+ setattr(input_value, k, v)
158
+ except Exception as e:
159
+ pass
160
+
161
+ return changed, value
162
+
163
+
164
+ @render_func()
165
+ def class_to_var_dict(input_value: type, changed, draw_state, **kwargs):
166
+ class_vars = {**{k: getattr(input_value, k) for k in vars(input_value)}}
167
+ class_vars["__original__"] = input_value
168
+
169
+ return changed, class_vars
170
+
171
+
172
+ @render_func()
173
+ def var_dict_to_class(input_value, changed, **kwargs):
174
+ original_class = input_value.get("__original__", None)
175
+ if original_class is None:
176
+ imgui.text_colored("Error: No original class found in dict", 1.0, 0.0, 0.0, 1.0)
177
+ return False, input_value
178
+
179
+ if changed:
180
+ for k, v in input_value.items():
181
+ if k.startswith("_"):
182
+ continue
183
+ try:
184
+ imgui.text(f"Setting attribute {k} to value {v} on class {original_class.__name__}")
185
+ setattr(original_class, k, v)
186
+ except Exception as e:
187
+ imgui.text(f"Error setting attribute {k} on class {original_class.__name__}: {e}")
188
+
189
+ return False, input_value
190
+
191
+
192
+ some_float = [0.0]
193
+ cst_dict = {}
194
+ test_code = None
195
+ selected_tabs = ["Alpha"]
196
+
197
+
198
+ @render_func(show_bg=True, with_header=draw_header)
199
+ def test_columns():
200
+ draw_str("Column 1", name="col1", column=0)
201
+ draw_int(123, name="col2", column=1)
202
+ draw_float(0.5, name="col3", column=2)
203
+ draw_float(0.5, name="test_5", column=5)
204
+ draw_float(0.5, name="test_5_b", column=5)
205
+
206
+ for i in range(10):
207
+ draw_float(0.4, name=f"float_{i}", column=2)
208
+
209
+
210
+ @render_func(use_cache=False, shadow=False, show_bg=False, disable_scroll=False, selectable=False)
211
+ def run_chain(input_value, chain=None, draw_state=None, route=None,
212
+ s_key_pressed=False, enter_key_pressed=False, unique=None, debug=False, **kwargs):
213
+ """Debug render function: executes a chain step by step with imgui output.
214
+
215
+ Shows function name, changed flag, output type, and a value preview
216
+ at each stage. Color coded: green=changed, gray=cached, yellow=pending.
217
+ """
218
+ if chain is None:
219
+ imgui.text("No chain provided")
220
+ return False, input_value
221
+
222
+ value = input_value
223
+ changed = False
224
+
225
+ if debug:
226
+ imgui.text(f"Chain: {len(chain)} nodes")
227
+ imgui.text(f"Input: {type(input_value).__name__}")
228
+ imgui.separator()
229
+
230
+ cache_tree = draw_state._chain_stack
231
+ cache_tree.begin()
232
+
233
+ mode_cache = chain[0][1].get("mode_cache", False) if isinstance(chain[0], dict) else False
234
+ if mode_cache:
235
+ value = cache_tree.step(changed, value)
236
+
237
+ to_route = {}
238
+
239
+ for i, func in enumerate(chain):
240
+ if isinstance(func, tuple):
241
+ func, func_kwargs = func[0], func[1]
242
+ else:
243
+ func_kwargs = {}
244
+
245
+ if debug:
246
+ if not changed:
247
+ name = getattr(func, '__name__', repr(func))
248
+ imgui.text(f" [{i}] {name} — (no change)")
249
+
250
+ func_kwargs['name'] = f"{func.__name__}{i}{kwargs.get('name', f'')}{unique}"
251
+ func_kwargs['shadow'] = False
252
+ func_kwargs['changed'] = changed
253
+ func_kwargs['show_header'] = False
254
+ func_kwargs['s_key_pressed'] = s_key_pressed
255
+ func_kwargs['enter_key_pressed'] = enter_key_pressed
256
+ func_kwargs['draw'] = True
257
+ func_kwargs['real_type'] = type(input_value)
258
+ for arg_name, arg_val in to_route.values():
259
+ func_kwargs[arg_name] = arg_val
260
+
261
+ next_cached = cache_tree.peek()
262
+ if isinstance(value, str):
263
+ imgui.text(f" [{i}] {func.__name__} — str: '{value[:30]}'")
264
+
265
+ imgui.begin_group()
266
+ changed, value = func(input_value=value, reference=next_cached, **func_kwargs)
267
+ imgui.end_group()
268
+ #
269
+ # if not changed:
270
+ # value = None
271
+
272
+ if isinstance(value, Pending):
273
+ changed = False
274
+ value = None
275
+
276
+ mode_cache = func_kwargs.get('mode_cache', True)
277
+ if mode_cache:
278
+ value = cache_tree.step(changed, value)
279
+
280
+ if route is not None:
281
+ if func in route:
282
+ arg_name = route[func]
283
+ to_route[arg_name] = arg_name, value
284
+
285
+ cache_tree.end()
286
+
287
+ return changed, value
288
+
289
+ # Nested sample data for the recursive dropdown demo.
290
+ dropdown_demo_data = {
291
+ "small": 12,
292
+ "medium": 16,
293
+ "large": 24,
294
+ "color": {
295
+ "rgb": {"red": (1.0, 0.0, 0.0), "green": (0.0, 1.0, 0.0)},
296
+ "named": {"steel": "#4682b4", "teal": "#008080"},
297
+ },
298
+ "alignment": ["left", "center", "right"],
299
+ }
300
+
301
+
302
+ drop_down_selection = None
303
+ # draw_main logs its section split to the perf log for any call slower than
304
+ # this (ms) - the root handler's frame-by-frame spikes were untraceable
305
+ # otherwise. [tint=(0.95, 0.55, 0.15)]
306
+ _DM_TRACE_MS = 1.5
307
+ # hey there
308
+
309
+ @render_func
310
+ def test_widget(input_value, name, unique, **kwargs):
311
+ imgui.text("Test Widget")
312
+ draw_text("Editable Text", name="editable_text", show_bg=True)
313
+
314
+
315
+ source = "x = foo(val=1)\nprint(x)\nsome_list=[0, 1, 2, 3]\n"
316
+ module = cst.parse_module(source)
317
+ proxy = cst_wrap(module)
318
+ name_edits = {}
319
+ code_export_str = "Test"
320
+
321
+
322
+ # Main draw function, called by the GUI framework
323
+
324
+ @live
325
+ class TestObj:
326
+ def __init__(self):
327
+ self.test_val = 0.0
328
+ self.test_list = [1, 2, 3, 4, 5]
329
+
330
+
331
+ test_obj = TestObj()
332
+
333
+
334
+ def draw(vis):
335
+ draw_melty_windows(vis)
336
+
337
+
338
+ def export_code(test_param_2: int = 5):
339
+ # print(f"hello {test_param_2}")
340
+ global code_export_str
341
+ code_export_str = proxy.node.code
342
+
343
+
344
+ @hotkey(glfw.KEY_O)
345
+ def toggle_offscreen():
346
+ if Core.melty.cache.enabled:
347
+ Core.melty.cache.set_enabled(False)
348
+ else:
349
+ Core.melty.cache.set_enabled(True)
350
+
351
+
352
+ import meltygui_imgui as imgui
353
+ from meltygui.hdr_color import pack_color
354
+ from meltygui.hdr_color import scale_saturation
355
+ # new comment
356
+
357
+
358
+ bg_style_default = {
359
+ "value": 0.01,
360
+ "saturation": 1.2,
361
+ "alpha": 1.0,
362
+ 'max_value': 1.0
363
+ }
364
+
365
+
366
+ def get_bg_color(depth, rounding, style_manager, auto_resize):
367
+ depth_factor = GlobalStyle.get_global_constant("depth_factor", default=1.0, folder="bg_styles") * 0.95
368
+ depth_offset = GlobalStyle.get_global_constant("depth_offset", default=0.0, folder="bg_styles") - 1.3
369
+ dynamic_value = max(0, (float(depth + depth_offset) * depth_factor))
370
+
371
+ hovered_offset = 0.0
372
+
373
+ def mix_colors(c1, c2, fac):
374
+ return (c1[0] * (1 - fac) + c2[0] * fac,
375
+ c1[1] * (1 - fac) + c2[1] * fac,
376
+ c1[2] * (1 - fac) + c2[2] * fac)
377
+
378
+ global bg_style_default
379
+ bg_style = GlobalStyle.get_global_constant("bg_style", default=bg_style_default, folder="bg_styles")
380
+ outline_factor = GlobalStyle.get_global_constant("outline_factor", default=1.0, folder="bg_styles") * 1.4
381
+
382
+ if not auto_resize:
383
+ outline_factor *= 1.3
384
+
385
+ if auto_resize:
386
+ bleed_factor = 0.2
387
+ else:
388
+ bleed_factor = 0.0
389
+ bg_bleed = Core.melty.get_bg_color(-1)
390
+ bg_bleed = style_manager.make_custom_styled(*bg_bleed, input=bg_style,
391
+ value=0.6,
392
+ alpha=1.0, saturation=1.8)
393
+ bg_color = (style_manager.
394
+ make_color_style_value(input=bg_style, value=max(0, dynamic_value) + hovered_offset))
395
+ bg_color = mix_colors(bg_color, bg_bleed, bleed_factor)
396
+ return bg_color
397
+
398
+
399
+ def seperator(height):
400
+ imgui.dummy(0, snap_int(height / 2))
401
+ imgui.separator()
402
+ imgui.dummy(0, snap_int(height / 2))
403
+
404
+
405
+ def test_func():
406
+ # Some comment
407
+ # Comment here
408
+ some_val = 1.706
409
+ some_dict = {"some_key": -0.02,
410
+ "key": False,
411
+ "key_2": 2.421
412
+ }
413
+
414
+
415
+ from meltygui.model.color_model import _clamp_bg_value
416
+
417
+
418
+ def compute_bg_color(bg_offset=0, tint=None, nested_bg=False, max_bg_depth=None, max_bg_value=None):
419
+ depth_wrap = 34
420
+ depth_scale = 1.629
421
+ intensity_factor = 0.021
422
+ intensity_offset = -0.336
423
+ outline_depth_mul = 0.786
424
+ # More text
425
+ bleed_style = {'value': -0.111, 'alpha': 1.12, 'saturation': 7.045}
426
+
427
+ bg_style = {
428
+ 'value': -0.004, 'saturation': 1.101,
429
+ 'alpha': 0.504, 'max_value': 1.8,
430
+ }
431
+
432
+ def mix_colors(color_a, color_b, factor):
433
+ return (
434
+ color_a[0] * (1 - factor) + color_b[0] * factor,
435
+ color_a[1] * (1 - factor) + color_b[1] * factor,
436
+ color_a[2] * (1 - factor) + color_b[2] * factor,
437
+ )
438
+
439
+ # -- Depth calculation -------------------
440
+ max_depth = 15
441
+ bg_depth = Core.melty.bg_depth if Core.melty.bg_depth is not None else 0
442
+ bg_offset = bg_offset if bg_offset is not None else 0
443
+ wrapped_depth = min(max_depth, (bg_depth % depth_wrap) + bg_offset)
444
+ # Caller-supplied ceiling on the effective depth: past this step the color
445
+ # keeps the palette value for max_bg_depth instead of getting lighter.
446
+ if max_bg_depth is not None:
447
+ wrapped_depth = min(wrapped_depth, max_bg_depth)
448
+ scaled_depth = wrapped_depth * depth_scale
449
+ depth_intensity = (scaled_depth + intensity_offset) * intensity_factor
450
+ max_depth_intensity = 0.652
451
+ depth_intensity = min(depth_intensity, max_depth_intensity)
452
+
453
+ # ── Outline color ──────────────────────────────────────────
454
+ depth_mul = outline_depth_mul
455
+ if not nested_bg:
456
+ depth_mul *= 1.00
457
+
458
+ # ── Background bleed color ─────────────────────────────────
459
+ bleed_factor = 0.501 if nested_bg else 0.446
460
+
461
+ bleed_base = Core.melty.get_bg_color(-1)
462
+ bleed_color = Melty.style_manager.make_custom_styled(
463
+ *bleed_base, input=bg_style, **bleed_style,
464
+ )
465
+
466
+ # ── Fill rendering ─────────────────────────────────────────
467
+ bg_color = Melty.style_manager.make_color_style_value(input=bg_style, value=max(0.0, depth_intensity))
468
+ bg_color = mix_colors(bg_color, bleed_color, bleed_factor)
469
+
470
+ return _clamp_bg_value(bg_color, max_bg_value)
471
+
472
+
473
+ # (style hsv, bg colour −2, bg colour −1, outline value, sat) → (bleed, outline)
474
+ _DRAW_BG_COLOUR_MEMO = globals().get("_DRAW_BG_COLOUR_MEMO", {})
475
+ _DRAW_BG_FILL_MEMO = globals().get("_DRAW_BG_FILL_MEMO", {}) # (colour key, sat, depth, bleed) → fill rgb
476
+
477
+
478
+ from meltygui.view.control_view import text
479
+
480
+
481
+ from meltygui.view.control_view import draw_str
482
+
483
+
484
+ def sort_dict_alphabetically(input_value, **kwargs):
485
+ changed = False
486
+ attr_name = "name"
487
+ first_item = next(iter(input_value.items()), None)[1]
488
+ if hasattr(first_item, attr_name):
489
+ sorted_dict = dict(sorted(input_value.items(), key=lambda item: str(getattr(item[1], attr_name)).lower()))
490
+ return changed, sorted_dict
491
+ else:
492
+ imgui.text("Cannot sort: items do not have 'name' attribute")
493
+ return False, input_value
494
+
495
+
496
+ @render_func()
497
+ def unsort_dict_alphabetically(input_value, ref=None, changed=False):
498
+ if ref is None:
499
+ imgui.text("Original order not available")
500
+ return False, input_value
501
+ else:
502
+ # Ref is the original dict
503
+ ref.update(input_value)
504
+ return changed, ref
505
+
506
+
507
+ def param_source_matrix(input_value, keys=None, func=None, include_unmatched=False, **kwargs):
508
+ """Pivot a render function's possible INPUTS into a parameter × source
509
+ table — the inputs-tab aggregator. `input_value` is the collected sources,
510
+ a {source_name: {param: value}} mapping (caller kwargs, @defaults on the
511
+ model class, mode kwargs, @render_func decorator kwargs, signature
512
+ defaults, …); `keys` is the function's parameter-name list (rows) — pass it
513
+ directly, or pass `func` and they're derived via inspect (unwrapped, minus
514
+ the catch-all params). Returns {param: {source_name: value}}:
515
+
516
+ rows one per parameter, in parameter order — an EMPTY row means no
517
+ source sets it (still shown: the point is mapping the full
518
+ input surface in one spot)
519
+ columns one per source that sets the param, in source order
520
+
521
+ Cells alias the source values (no copies). With include_unmatched, keys a
522
+ source sets that are NOT parameters append as extra rows at the end —
523
+ typos and **kwargs ride-throughs stay visible instead of vanishing.
524
+ Shaped like sort_dict_alphabetically: a plain (changed, value) chain node;
525
+ apply_param_source_matrix below is the unsort-style reverse.
526
+ Source COLOR-CODING is not this function's job: codecs carry a tint
527
+ (new_codecs.render_kwargs) that core_render merges in as the lowest
528
+ kwargs layer, so codec-backed values color themselves wherever drawn."""
529
+ changed = False
530
+ if isinstance(input_value, dict):
531
+ items = list(input_value.items())
532
+ else:
533
+ items = [(f"source_{i}", s) for i, s in enumerate(input_value or ())]
534
+ items = [(str(n), s) for n, s in items if isinstance(s, dict)]
535
+
536
+ if keys is None and func is not None:
537
+ try:
538
+ keys = [p for p in inspect.signature(inspect.unwrap(func)).parameters
539
+ if p not in ("args", "kwargs", "o_kwargs", "next_kwargs")]
540
+ except (TypeError, ValueError):
541
+ keys = []
542
+ # A render func's input surface is its signature PLUS the kwargs the
543
+ # @render_func machinery itself consumes (width/height/tint/shadow and
544
+ # the flag zoo) - shared by every render func, loaded once per process
545
+ # by re-scanning the decorator source (render_func_kwarg_names).
546
+ if getattr(func, "__render_func__", False):
547
+ _seen = set(keys)
548
+ keys += [k for k in render_func_kwarg_names() if k not in _seen]
549
+ keys = list(keys or [])
550
+
551
+ matrix = {}
552
+ for k in keys:
553
+ row = {}
554
+ for sname, sdict in items:
555
+ if k in sdict:
556
+ row[sname] = sdict[k]
557
+ matrix[k] = row
558
+ if include_unmatched:
559
+ for sname, sdict in items:
560
+ for k in sdict:
561
+ # Parse metadata is NOT an input: skip non-plain-str keys
562
+ # (Comment objects keying their own line in a class-body
563
+ # parse), dunders/underscored bookkeeping, and the parse's
564
+ # section keys - only real attribute names become rows.
565
+ if (type(k) is not str or k.startswith('_')
566
+ or k in _PARSE_SECTION_KEYS):
567
+ continue
568
+ if k not in matrix or (k not in keys and sname not in matrix[k]):
569
+ matrix.setdefault(k, {})[sname] = sdict[k]
570
+ return changed, matrix
571
+
572
+
573
+ # Attributes ALWAYS in the inputs-tab param list (pinned right after the view
574
+ # function's own signature params), even if no source sets them organically -
575
+ # they come from the @render_func machinery, not the view function's signature.
576
+ MATRIX_DEFAULT_PRIORITY = ("view_func", "width", "height", "min_height", "min_width", "tint")
577
+
578
+ # Structural sections of a GeneralParse dict - never attribute names, so
579
+ # param_source_matrix's include_unmatched must not turn them into rows when a
580
+ # a class/function parse is registered as a source (the class-var source).
581
+ _PARSE_SECTION_KEYS = frozenset({"decorators", "parameters", "locals"})
582
+
583
+ # Framework-injected parameters: present in most view-function signatures but
584
+ # never user-tunable, so they don't belong in the signature section.
585
+ _MATRIX_FRAMEWORK_PARAMS = {"input_value", "draw_state", "args", "kwargs",
586
+ "o_kwargs", "next_kwargs", "meta", "viewstate",
587
+ "self", "unique", "changed"}
588
+
589
+
590
+ def signature_param_names(func):
591
+ """The view function's OWN tunable parameters (unwrapped signature minus
592
+ the framework-injected names) plus MATRIX_DEFAULT_PRIORITY — the params
593
+ pinned to the front of the inputs-tab list. For draw_float that's
594
+ min_value/max_value/speed/…; width/height/min_height/min_width/tint ride
595
+ along from the default list."""
596
+ try:
597
+ params = inspect.signature(inspect.unwrap(func)).parameters
598
+ except (TypeError, ValueError):
599
+ return []
600
+ names = [p for p in params if p not in _MATRIX_FRAMEWORK_PARAMS]
601
+ names += [k for k in MATRIX_DEFAULT_PRIORITY if k not in names]
602
+ return names
603
+
604
+
605
+ @render_func()
606
+ def apply_param_source_matrix(input_value, ref=None, changed=False):
607
+ """Reverse of param_source_matrix — the unsort_dict_alphabetically analog.
608
+ `ref` is the ORIGINAL {source_name: dict} sources mapping; every edited
609
+ cell writes back into the source dict it came from (a tint edited under
610
+ the 'caller' column lands in the caller-kwargs dict), so each source's own
611
+ save path can persist it. Sources absent from ref are left untouched."""
612
+ if ref is None:
613
+ imgui.text("Original sources not available")
614
+ return False, input_value
615
+ for param, row in input_value.items():
616
+ if not isinstance(row, dict):
617
+ continue
618
+ for sname, val in row.items():
619
+ src = ref.get(sname) if isinstance(ref, dict) else None
620
+ # Skip already-equal cells: bubbling-wrapped source dicts mark
621
+ # their host dirty on each write, so only real edits write back.
622
+ if isinstance(src, dict) and (param not in src or src[param] is not val):
623
+ src[param] = val
624
+ return changed, ref
625
+
626
+
627
+ # Picker layout shared by the popover callers (draw_tuple, draw_tuple_fast,
628
+ # the editor's swatches) — they size the fixed popover window from it.
629
+ # [tint=(0.85, 0.75, 0.05)]
630
+ PICKER_SQUARE = Toggles.ColorPicker.square_size
631
+ # [tint=(0.85, 0.75, 0.05)]
632
+ PICKER_TABS_HEIGHT = Toggles.ColorPicker.tabs_height
633
+ # The sRGB+ tab's wide-gamut extension, to the RIGHT of the classic square
634
+ # (same height as the square; its width is what the tab adds to the popover).
635
+ # [tint=(0.85, 0.75, 0.05)]
636
+ PICKER_EXTENSION = Toggles.ColorPicker.extension_width
637
+ # The sRGB+ tab's exposure band ABOVE the square (and its extension): white
638
+ # at the seam up to 2^Toggles.HDR.picker_max_stops at the top. Every tab
639
+ # reserves this height above its square, so the classic square sits at the
640
+ # same screen spot whichever tab is showing.
641
+ # [tint=(0.85, 0.75, 0.05)]
642
+ PICKER_EXPOSURE_BAND = Toggles.ColorPicker.exposure_band_height
643
+ # Gap between the swatch's bottom edge and the popover's top.
644
+ # [tint=(0.85, 0.75, 0.05)]
645
+ PICKER_ANCHOR_GAP = Toggles.ColorPicker.anchor_gap
646
+ # The view-offset rows (draw_view_offsets_fast) under the colour tabs when
647
+ # the picker edits a view: a separator + one drag row per offset.
648
+ # [tint=(0.85, 0.75, 0.05)]
649
+ PICKER_OFFSETS_HEIGHT = Toggles.ColorPicker.offsets_height
650
+
651
+
652
+ from meltygui.core.styling.color_core import _style_policy_source
653
+
654
+
655
+ from meltygui.core.styling.color_core import _add_style_policy
656
+
657
+
658
+ # The view params the picker edits beside the colour, with their drag rows:
659
+ # (param, label, drag speed). Both are ints read and written through the
660
+ # owner's `locate_<param>` — the wrapper's `bg_offset` (palette depth the
661
+ # view's background is sampled at) and `z_offset` (its shadow / paint depth).
662
+ # Add a row here to expose another wrapper kwarg.
663
+ # [tint=(0.85, 0.75, 0.05)]
664
+ VIEW_OFFSET_ROWS = Toggles.ColorPicker.view_offset_rows
665
+
666
+
667
+ class TestClass(DictConversion):
668
+ def __init__(self):
669
+ super().__init__()
670
+ self.value = 2
671
+ self.str_val = "Test"
672
+
673
+
674
+ from meltygui.view.control_view import draw_float
675
+
676
+
677
+ @render_func(wraps=render_func, show_add_delete=False, with_header=draw_header)
678
+ def eval_function(input_value, draw_state):
679
+ signature = inspect.signature(input_value)
680
+ params = signature.parameters
681
+ changed, new_val = draw_any(params, name="Parameters", show_add_delete=False)
682
+ if changed:
683
+ set_fn_defaults(input_value, new_val)
684
+
685
+ push_style_var(imgui.STYLE_ITEM_SPACING, (2, 4))
686
+ push_style_var(imgui.STYLE_FRAME_PADDING, (8, 6))
687
+ push_style_var(imgui.STYLE_FRAME_ROUNDING, 6)
688
+
689
+ function_args = inspect.signature(input_value).parameters
690
+ kwargs = {}
691
+ for name, param in function_args.items():
692
+ if param.default is not inspect.Parameter.empty:
693
+ kwargs[name] = param.default
694
+ else:
695
+ kwargs[name] = None
696
+ try:
697
+ result = input_value(**kwargs)
698
+ draw_any(result, name="Result", show_header=True, show_add_delete=False)
699
+ if draw_state._result != result:
700
+ Core.melty.cache.invalidate_all()
701
+ draw_state._result = result
702
+
703
+ except Exception as e:
704
+ print(f"Error calling function '{input_value.__name__}': {e}")
705
+ print_colored_traceback(*sys.exc_info())
706
+
707
+ pop_style_var(3)
708
+
709
+ return changed, input_value
710
+
711
+
712
+ def _format_run_error(exc):
713
+ """One compact, UI-ready error: `Type: message`, then the deepest
714
+ traceback frame in PROJECT code (site-packages/stdlib frames are where
715
+ the error SURFACED, not where it's fixable) with its source line."""
716
+ import traceback
717
+ frames = traceback.extract_tb(exc.__traceback__)
718
+ target = None
719
+ for fr in reversed(frames):
720
+ if "site-packages" not in fr.filename and "/lib/python" not in fr.filename:
721
+ target = fr
722
+ break
723
+ if target is None and frames:
724
+ target = frames[-1]
725
+ text = f"{type(exc).__name__}: {exc}"
726
+ if target is not None:
727
+ text += f"\n{Path(target.filename).name}:{target.lineno} in {target.name}"
728
+ if target.line:
729
+ text += f"\n {target.line}"
730
+ return text
731
+
732
+
733
+ # Runner draw states holding a run's `result` - what the CUDA-OOM responder
734
+ # frees (a forward pass result is typically the largest single live object).
735
+ _RUN_RESULT_HOLDERS = globals().get("_RUN_RESULT_HOLDERS")
736
+ if _RUN_RESULT_HOLDERS is None:
737
+ import weakref as _weakref
738
+ _RUN_RESULT_HOLDERS = _weakref.WeakSet()
739
+
740
+
741
+ # Runner draw_states with a threaded run IN FLIGHT - draw_function's
742
+ # single-flight policy. Process-living and NOT serialized: this latch used
743
+ # to be `draw_state.misc["_run_busy"]`, and it rides in custom.pkl with
744
+ # the draw_state, so a restart while a run was in flight (2026-08-23: the
745
+ # Pending Saves recompile) reloaded the runner as "busy" in every later
746
+ # session - locked forever, every new run refused as "already running".
747
+ _RUN_BUSY = globals().get("_RUN_BUSY")
748
+ if _RUN_BUSY is None:
749
+ import weakref as _weakref
750
+ _RUN_BUSY = _weakref.WeakSet()
751
+
752
+
753
+ def is_run_busy(draw_state) -> bool:
754
+ return draw_state in _RUN_BUSY
755
+
756
+
757
+ def run_busy_begin(draw_state) -> bool:
758
+ """Claim the runner for a run. False if one is already in flight."""
759
+ if draw_state in _RUN_BUSY:
760
+ return False
761
+ _RUN_BUSY.add(draw_state)
762
+ return True
763
+
764
+
765
+ def run_busy_end(draw_state) -> None:
766
+ _RUN_BUSY.discard(draw_state)
767
+
768
+
769
+ def _release_run_results():
770
+ for ds in list(_RUN_RESULT_HOLDERS):
771
+ try:
772
+ ds.result = None
773
+ ds.misc.pop("_result_frame", None)
774
+ except Exception:
775
+ pass
776
+ _RUN_RESULT_HOLDERS.clear()
777
+
778
+
779
+ def _respond_to_cuda_oom(exc, where):
780
+ try:
781
+ from meltygui.core.runtime.gc_manager import respond_to_cuda_oom
782
+ from meltygui.core.runtime.gc_manager import OOM_RELEASE_HOOKS
783
+ if not any(getattr(h, "__name__", None) == "_release_run_results"
784
+ for h in OOM_RELEASE_HOOKS):
785
+ OOM_RELEASE_HOOKS.append(_release_run_results)
786
+ respond_to_cuda_oom(exc, where=where)
787
+ except Exception:
788
+ pass
789
+
790
+
791
+ from meltygui.view.control_view import draw_int
792
+
793
+
794
+ def eval_input_scope(draw_state):
795
+ """{param: value} for every input the context menu's INPUTS tab lists on
796
+ `draw_state`'s view: the view function's own params, the header
797
+ function's params (the resolved `with_header`), and the kwargs the
798
+ @render_func wrapper itself consumes (show_bg, width, tint, ...). Each
799
+ value is what the inputs tab DISPLAYS for it — anywhere_value (the
800
+ wrapper-resolved `_kwargs`, the ds field fallback, an in-flight
801
+ set-anywhere value), else the declared signature default — so typing
802
+ `show_bg` in the Eval tab answers with the value actually driving the
803
+ view. Layered UNDER the call-time locals by run_scoped_eval: a name the
804
+ view function receives keeps its exact argument."""
805
+ from meltygui.core.rendering.parameter_core import anywhere_value
806
+ from meltygui.core.rendering.parameter_core import header_param_names
807
+ from meltygui.core.rendering.parameter_core import signature_default_for
808
+ from meltygui.core.rendering.parameter_core import view_param_names
809
+ from meltygui.core.core_render import render_func_kwarg_names
810
+ names = []
811
+ seen = set()
812
+ for group in (view_param_names(draw_state), header_param_names(draw_state),
813
+ render_func_kwarg_names()):
814
+ for name in group:
815
+ if name not in seen and name.isidentifier():
816
+ seen.add(name)
817
+ names.append(name)
818
+ scope = {}
819
+ for name in names:
820
+ try:
821
+ value = anywhere_value(name, draw_state)
822
+ if value is None:
823
+ value = signature_default_for(name, draw_state)
824
+ except Exception:
825
+ value = None
826
+ scope[name] = value
827
+ return scope
828
+
829
+
830
+ def run_scoped_eval(code, view_func, draw_state, local_vars):
831
+ """Run `code` with the view function's ACTUAL call-time locals in scope.
832
+
833
+ Called from the render wrapper (core_render) right before it invokes the
834
+ view function, so `local_vars` is the exact set of arguments the function is
835
+ about to receive -- its initial locals (`input_value`, `draw_state`, and
836
+ every kwarg by name). On top of those we layer the function's module globals
837
+ (so the snippet resolves the same free names the body would) plus the `value`
838
+ and `ds` aliases. Locals win over globals, mirroring normal scoping.
839
+
840
+ Delegates to the same eval/exec + stdout-capture machinery the MCP
841
+ `eval_python` tool uses, so a trailing expression's repr comes back alongside
842
+ any printed output. The namespace is a fresh dict layered over a *copy* of
843
+ the module globals, so assignments in the snippet don't leak back into the
844
+ module.
845
+ """
846
+ from meltygui.core.automation.mcp_eval import _run_code
847
+ ns = {}
848
+ if view_func is not None:
849
+ # Same free names the function body resolves.
850
+ ns.update(getattr(view_func, "__globals__", {}))
851
+ # Every input the eval tab lists (view / header / wrapper params) at its
852
+ # displayed value; the call-time locals below override the ones the
853
+ # function actually receives.
854
+ try:
855
+ ns.update(eval_input_scope(draw_state))
856
+ except Exception:
857
+ pass
858
+ if local_vars:
859
+ ns.update(local_vars) # the view function's call-time locals
860
+ ns.setdefault("draw_state", draw_state)
861
+ ns.setdefault("ds", ns.get("draw_state"))
862
+ ns.setdefault("value", ns.get("input_value"))
863
+
864
+ # Record the EXACT eval scope (the function's call-time locals + the ds/value
865
+ # aliases) into the per-function metadata cache, so the eval tab's autocomplete
866
+ # gives type-accurate suggestions on the next load. Keyed on the wrapper
867
+ # (draw_state._view_func) to match what the eval tab looks up. Globals aren't
868
+ # recorded -- they're resolved live from the function's __globals__ at
869
+ # completion time. Best-effort; a hiccup here must never fail the eval.
870
+ try:
871
+ from meltygui.core.rendering.func_metadata import FuncsMetadata
872
+ cache_key = getattr(draw_state, "_view_func", None) or view_func
873
+ scope = eval_input_scope(draw_state)
874
+ scope.update(local_vars or {})
875
+ for alias in ("draw_state", "ds", "value", "input_value"):
876
+ scope[alias] = ns.get(alias)
877
+ FuncsMetadata.record(cache_key, scope)
878
+ except Exception:
879
+ pass
880
+
881
+ out, result, error = _run_code(code, ns)
882
+ parts = []
883
+ if out.strip():
884
+ parts.append(out.rstrip())
885
+ if result is not None:
886
+ parts.append("=> " + result)
887
+ if error:
888
+ parts.append(error.rstrip())
889
+ return "\n".join(parts) if parts else "(no output)"
890
+
891
+
892
+ # ── Context menu tabs ────────────────────────────────────────────────────────
893
+ # Each tab body is its own render_func. draw_context_menu places one per selected
894
+ # column by calling it with column=t_idx; the tab then owns a single-column
895
+ # region, so the inner views inside it no longer pass column themselves.
896
+
897
+ class _SourceItem(str):
898
+ """A source name as a dropdown value. `.tint` colors its row and the
899
+ trigger via _dd_obj_tint — yellow marks the source actively driving
900
+ the param."""
901
+
902
+ def __new__(cls, name, tint=None):
903
+ self = str.__new__(cls, name)
904
+ self.tint = tint
905
+ return self
906
+
907
+
908
+ _ACTIVE_SRC_TINT = (0.9, 0.8, 0.2)
909
+
910
+ # Multi-key child kwargs carried by the collection dict itself (it's
911
+ # __overrides__ path): the info tab's 'header' group starts collapsed —
912
+ # `initial` only applies on the child's first frames, so the chevron still
913
+ # works afterwards. The dunder key never renders (underscore-skipped).
914
+ _INFO_GROUP_OVERRIDES = {"__header__": {"initial": {"expanded": False}}}
915
+
916
+
917
+ from meltygui.core.conversion.render_host import RenderHost
918
+
919
+
920
+ class _InstanceAttrSource(dict):
921
+ """The 'instance attr' source row: the value object's own whitelisted
922
+ params (core_render.OBJ_ATTR_PARAMS — the attrs the wrapper injects, e.g.
923
+ Loras.tint). Reads snapshot at collection time; a write goes straight to
924
+ setattr on the LIVE object — in place and immediate, no code round trip
925
+ (the same storage the instance_attr lens edits). Unlike the host-backed
926
+ sources, a plain setattr triggers NOTHING — so the write also invalidates
927
+ the target view's subtree (the wrapper re-injects the attr on the next
928
+ render, which is also what clears the anywhere in-flight cache)."""
929
+
930
+ def __init__(self, obj, target_ds=None):
931
+ from meltygui.core.core_render import OBJ_ATTR_PARAMS
932
+ super().__init__({p: getattr(obj, p) for p in OBJ_ATTR_PARAMS
933
+ if getattr(obj, p, None) is not None
934
+ and (p != "view_func" or p in getattr(obj, "__dict__", {}))})
935
+ self._obj = obj
936
+ self._target_ds = target_ds
937
+
938
+ def __setitem__(self, k, v):
939
+ setattr(self._obj, k, v)
940
+ super().__setitem__(k, v)
941
+ ds = self._target_ds
942
+ if ds is not None:
943
+ # invalidate_up: the attr (tint) paints the whole subtree's bg -
944
+ # cached middle tiles would blit-skip dirty grandchildren.
945
+ ds.invalidate_up(max_depth=6)
946
+ request_render()
947
+
948
+
949
+ class _CodecSource(dict):
950
+ """The 'codec' source row: the ACTIVE codec's render_kwargs — the
951
+ wrapper's lowest kwargs merge layer and the provenance color (the green
952
+ on import views etc.). The codec rides every draw_state as ds._codec.
953
+
954
+ Writes are PER-FILE when the element resolves to one: the codec stamps
955
+ the attribute into that file's entry in AppModel.file_meta_collection
956
+ (Codec.update_file_meta), which persists with the root save and feeds the
957
+ folder tree's row kwargs. Reads overlay that entry back over the
958
+ codec-wide render_kwargs. Only when no file resolves does a write mutate
959
+ the LIVE class attr in place — immediate but codec-wide and in-memory."""
960
+
961
+ def __init__(self, codec, target_ds):
962
+ rk = getattr(codec, "render_kwargs", None)
963
+ super().__init__(rk if isinstance(rk, dict) else {})
964
+ self._codec = codec
965
+ self._target_ds = target_ds
966
+ # Per-FILE overlay: attributes previously attached to this element's
967
+ # file (Codec.update_file_meta → AppModel.file_meta_collection) read
968
+ # back over the codec-wide render_kwargs, so the row round-trips
969
+ # across saves. `order` is folder-tree bookkeeping, not a param.
970
+ entry = codec.file_meta_entry(target_ds)
971
+ if isinstance(entry, dict):
972
+ self.update({k: v for k, v in entry.items() if k != "order"})
973
+
974
+ def __setitem__(self, k, v):
975
+ # Per-file first: when the element resolves to a file, the attribute
976
+ # belongs to THAT file - the codec stamps it into the file-metadata
977
+ # object (persisted with the root; the folder tree re-applies it as
978
+ # row kwargs). Only when no file resolves does the write fall through to
979
+ # the codec-wide live render_kwargs.
980
+ if not self._codec.update_file_meta(self._target_ds, k, v):
981
+ rk = getattr(self._codec, "render_kwargs", None)
982
+ if not isinstance(rk, dict):
983
+ rk = {}
984
+ self._codec.render_kwargs = rk
985
+ rk[k] = v
986
+ super().__setitem__(k, v)
987
+ self._target_ds.invalidate_up(max_depth=6)
988
+ request_render()
989
+
990
+
991
+ class _ChildKwargsSource(dict):
992
+ """The child_kwargs row when the parent's class-level @defaults doesn't
993
+ (yet) carry child_kwargs in SOURCE: displays the live dict, and the first
994
+ write lazily creates `child_kwargs={...}` inside the class-level defaults
995
+ PARSE — a non-dunder key, so plain bubbling item-writes wrap the new dict
996
+ and dirty the host (no __overrides__-style special casing needed). The
997
+ save + the parent-class recompile stamp then make it code."""
998
+
999
+ def __init__(self, defaults_parse, live):
1000
+ super().__init__({k: v for k, v in (live or {}).items()
1001
+ if isinstance(k, str) and not k.startswith("__")})
1002
+ self._dp = defaults_parse
1003
+
1004
+ def __setitem__(self, k, v):
1005
+ ck = self._dp.get("child_kwargs")
1006
+ if not isinstance(ck, dict):
1007
+ self._dp["child_kwargs"] = {} # bubbling: wraps + dirties
1008
+ ck = self._dp["child_kwargs"]
1009
+ ck[k] = v
1010
+ super().__setitem__(k, v)
1011
+
1012
+
1013
+ class _DrawStateAttrSource(dict):
1014
+ """The 'draw state' source row: whitelisted params read from the target
1015
+ draw_state's own fields (ds.tint — the style cascade's last fallback,
1016
+ persisted with window state). The DEFAULT source: SourcePriority ranks it
1017
+ last, so it only ever drives when nothing else sets the param. Writes go
1018
+ setattr-on-the-ds, in place, plus the same subtree invalidation the
1019
+ instance adapter does."""
1020
+
1021
+ def __init__(self, target_ds):
1022
+ from meltygui.core.core_render import OBJ_ATTR_PARAMS
1023
+ super().__init__({p: getattr(target_ds, p) for p in OBJ_ATTR_PARAMS
1024
+ if getattr(target_ds, p, None) is not None})
1025
+ self._target_ds = target_ds
1026
+ if "view_func" in target_ds.auto_params:
1027
+ dict.__setitem__(self, "view_func", target_ds.auto_params["view_func"])
1028
+
1029
+ def __setitem__(self, k, v):
1030
+ if k == "view_func":
1031
+ self._target_ds.auto_params[k] = v
1032
+ else:
1033
+ setattr(self._target_ds, k, v)
1034
+ super().__setitem__(k, v)
1035
+ self._target_ds.invalidate_up(max_depth=6)
1036
+ request_render()
1037
+
1038
+
1039
+ class _LazyOverrideEntry(dict):
1040
+ """Stand-in '# [<key>]' source for a site with NO override comment yet.
1041
+ Reads as the empty entry dict; the FIRST write (the matrix's + button)
1042
+ materializes __overrides__['__<key>__'] in the owning parse and the save
1043
+ synthesizes the comment line (_patch_leading_override's creation branch).
1044
+ On the next walk the real entry exists and registers instead.
1045
+
1046
+ Bubbling treats `__…` keys as internal bookkeeping — writes to them are
1047
+ stored RAW (no wrap, no dirty mark), which is exactly right for parse
1048
+ bookkeeping and exactly wrong here: naive creation leaves plain dicts the
1049
+ host never hears about (tint shows — the marker reads the same tree — but
1050
+ nothing saves). So the new structure is installed onto the host's bubble
1051
+ root explicitly, and the LEAF write goes through the wrapped entry, whose
1052
+ non-internal key fires the standard notify → dirty → save path."""
1053
+
1054
+ def __init__(self, root, entry_key=None):
1055
+ # entry_key=None targets the root node's OWN __overrides__ - a
1056
+ # leading comment above a class/function def - instead of a
1057
+ # '__<key>__' field slot on its parent.
1058
+ super().__init__()
1059
+ self._root = root
1060
+ self._entry_key = entry_key
1061
+
1062
+ def __setitem__(self, k, v):
1063
+ from meltygui.core.conversion.bubbling import install_bubbling
1064
+ root_node = self._root
1065
+ ovs = root_node.get("__overrides__")
1066
+ if not isinstance(ovs, dict):
1067
+ ovs = {}
1068
+ if self._entry_key is not None and not isinstance(ovs.get(self._entry_key), dict):
1069
+ ovs[self._entry_key] = {}
1070
+ broot = getattr(root_node, "_bubble_root", None)
1071
+ if broot is not None:
1072
+ # Plain dicts can't reclass - install returns the wrapped copy,
1073
+ # so store THAT (raw store: internal key). Idempotent if ovs
1074
+ # already bubbles.
1075
+ ovs = install_bubbling(ovs, broot)
1076
+ root_node["__overrides__"] = ovs
1077
+ entry = ovs if self._entry_key is None else ovs[self._entry_key]
1078
+ entry[k] = v # non-internal key on the wrapped entry → notify → dirty
1079
+ super().__setitem__(k, v) # same-frame reads (row refresh) see it too
1080
+
1081
+
1082
+ def collect_input_sources(input_value, cm_state, class_to_show=None):
1083
+ """Every editable input source behind a view, collected WITHOUT drawing —
1084
+ the shared engine of draw_input_tab and set_anywhere. `input_value` is the
1085
+ TARGET draw_state; `cm_state` caches the code hosts across calls (pass the
1086
+ context menu's, or any per-target instance). Returns a dict:
1087
+ sources {source_name: parse dict} (placeholder {} when unparsed)
1088
+ tints {source_name: codec tint}
1089
+ locations {source_name: (file, line)} for jump buttons
1090
+ kinds {source_name: kind caption} ("signature" / "caller +N" / ...)
1091
+ writable source names whose dict is a REAL parse node (writes save)
1092
+ comment_owners {source_name: draw_state} separates inherited parameter
1093
+ sources from the target window’s own lifecycle overrides
1094
+ """
1095
+ # Per-frame memo: the input tab plus the tint tab's get_sources_for /
1096
+ # from_anywhere / set_anywhere all this for the same target within one
1097
+ # frame, and the parses can't change mid-frame - build once per
1098
+ # (frame, class_to_show) per cm_state. Profiled at avg 2.5ms / max 12.7ms
1099
+ # per build; tripling it per frame was the input-tab drag fps drop.
1100
+ _memo_key = (Core.melty.frame_count, class_to_show)
1101
+ if getattr(cm_state, "_collect_key", None) == _memo_key:
1102
+ return cm_state._collect_cache
1103
+
1104
+ # Source/cst hosts come from the process-wide code-host cache, keyed by the
1105
+ # live reference - every menu opened on the same render_func/class/call
1106
+ # site shares ONE host pair, so the code isn't re-loaded and re-parsed per
1107
+ # open (code_hosts_for in new_converters; earlier like this tab used to
1108
+ # build inline).
1109
+ host_key = (input_value._view_func, class_to_show)
1110
+ if cm_state.host_key != host_key:
1111
+ cm_state.host_key = host_key
1112
+ cm_state.render_func_str, cm_state.render_func_dict = code_hosts_for(input_value._view_func)
1113
+ cm_state.class_str, cm_state.class_dict = code_hosts_for(class_to_show)
1114
+ # The nav retargeted us at a different view; its call sites differ (and
1115
+ # may not be captured yet — see the lazy capture in draw_context_menu).
1116
+ cm_state.call_site_hosts = []
1117
+ cm_state.call_site_keys = None
1118
+ # The class's source location feeds the class-var/class-default jump
1119
+ # buttons. inspect.getsourcelines() on a CLASS AST-parses the ENTIRE
1120
+ # module file (CPython 3.9+ _ClassFinder) - ~34ms on a 7k-line file like
1121
+ # libcst_conversion.py (IntParse, the parent of a parsed int field) -
1122
+ # so it CANNOT run per frame. class_to_show is part of host_key, so the
1123
+ # location only changes on a rebuild: read it once, here.
1124
+ cm_state.class_loc = None
1125
+ if isinstance(class_to_show, type):
1126
+ try:
1127
+ cm_state.class_loc = (inspect.getsourcefile(class_to_show),
1128
+ inspect.getsourcelines(class_to_show)[1])
1129
+ except (TypeError, OSError):
1130
+ cm_state.class_loc = None
1131
+
1132
+ decoration_func = input_value._kwargs.get("_view_func_origin", input_value._view_func)
1133
+ if getattr(cm_state, "decoration_key", None) is not decoration_func:
1134
+ cm_state.decoration_key = decoration_func
1135
+ cm_state.decoration_str, cm_state.decoration_dict = code_hosts_for(decoration_func)
1136
+
1137
+ # The caller CHAIN - the direct caller, its caller, ... up to
1138
+ # Toggles.caller_walk_steps real (non-dispatch) frames, innermost-first.
1139
+ # Read off the cached _call_stack (never the live stack: a drag re-renders
1140
+ # with parents skipped, which would wipe every host). The stack can lag a
1141
+ # frame or two AFTER retargeting (lazy one-shot capture on the wrapper's
1142
+ # next render), so recompute every pass and rebuild the hosts only when the
1143
+ # resolved sites change.
1144
+ from meltygui.code.chain_converters import caller_chain
1145
+ walk_steps = max(1, int(Toggles.caller_walk_steps or 1))
1146
+ caller_frames = caller_chain(getattr(input_value, "_call_stack", None))[:walk_steps]
1147
+ caller_site_keys = tuple((f, ln) for f, ln, _ in caller_frames)
1148
+ # getattr: a cm_state created before this field existed (hotswap of an
1149
+ # already-open inputs tab) lacks caller_site_keys - treat as "needs rebuild".
1150
+ if caller_site_keys != getattr(cm_state, "call_site_keys", None):
1151
+ cm_state.call_site_keys = caller_site_keys
1152
+ cm_state.call_site_hosts = [code_hosts_for(CallSite(f, ln))
1153
+ for f, ln in caller_site_keys]
1154
+
1155
+ # The ACTIVE mode driving this view - the wrapper stamps it into the
1156
+ # view's kwargs when a mode config matches (kwargs['current_mode'],
1157
+ # core_render; 'mode' for the recursive variant), so it rides on the
1158
+ # target's _kwargs. The host is for the mode's ENUM CLASS (whose source
1159
+ # holds every member), keyed per class so retargeting at a view under a
1160
+ # different mode enum rebuilds; which member to show is re-read each pass.
1161
+ current_mode = input_value._kwargs.get('current_mode')
1162
+ mode_cls = current_mode.__class__ if current_mode is not None else None
1163
+ if cm_state.mode_key != mode_cls:
1164
+ cm_state.mode_key = mode_cls
1165
+ cm_state.mode_str, cm_state.mode_dict = (
1166
+ code_hosts_for(mode_cls) if mode_cls is not None else (None, None))
1167
+
1168
+ # ── Every input source, one parameter at a time ──────────────────────────
1169
+ # Collect each parsed source dict, match against the render function's
1170
+ # parameter list via param_source_matrix, and hand the matrix to
1171
+ # draw_param_matrix: a per-parameter SCREEN (dropdown within params)
1172
+ # listing every possible source - param default, caller, mode, class
1173
+ # default, function decoration - set or not. Sources register here even
1174
+ # when empty/unparsed (placeholder {}), so each screen always shows the
1175
+ # full source list; registration order is the screens' row order.
1176
+ # Rebuilt every frame from the live parses (cheap dict copy), so a
1177
+ # background parse landing or an edit in any source shows up immediately.
1178
+ # Edits to a cell write back into the SOURCE dict they came from
1179
+ # (apply_param_source_changes) - those dicts are the hosts'
1180
+ # bubbling-wrapped parse nodes, so the edit marks the owning host dirty
1181
+ # and rides its normal chain_out/save path. Placeholder rows are plain
1182
+ # empty dicts: they never grow cells, so no write-back can land in them.
1183
+ # Each source maps to the CODEC that owns its data - the codec's
1184
+ # render_kwargs tint IS the source color. The matrix cells are parse
1185
+ # fragments that can't adopt it naturally (type-based codec fragments), so
1186
+ # the tint map rides into draw_param_matrix, which applies it manually
1187
+ # and prominently per row.
1188
+ from meltygui.code.new_codecs import FunctionCodec
1189
+ from meltygui.code.new_codecs import CallerCodec
1190
+ from meltygui.code.new_codecs import DecorationsCodec
1191
+ from meltygui.code.new_codecs import TypeCodec
1192
+ from meltygui.code.new_codecs import ModeCodec
1193
+
1194
+ def _codec_tint(codec):
1195
+ return (getattr(codec, "render_kwargs", None) or {}).get("tint")
1196
+
1197
+ sources = {}
1198
+ source_tints = {}
1199
+ source_locations = {}
1200
+ source_kinds = {}
1201
+ comment_owners = {}
1202
+ writable_sources = []
1203
+
1204
+ def _add_source(sname, sdict, codec, location=None, kind=None, owner=None):
1205
+ # Register even when the source sets nothing or hasn't parsed yet -
1206
+ # the per-param screen draws EVERY source row, absent ones as "not
1207
+ # set". The placeholder is a fresh empty dict, never written to;
1208
+ # only REAL parse dicts (even empty ones) are writable, so the
1209
+ # matrix's +/× buttons know where a stamped cell can legally land.
1210
+ # `kind` is the row's caption (signature / caller / mode / ...);
1211
+ # sname stays the concrete spelling (def draw_x / Mode.WINDOW / ...).
1212
+ if isinstance(sdict, dict):
1213
+ writable_sources.append(sname)
1214
+ sources[sname] = sdict if isinstance(sdict, dict) else {}
1215
+ source_tints[sname] = _codec_tint(codec)
1216
+ if kind:
1217
+ source_kinds[sname] = kind
1218
+ if kind == "code comment":
1219
+ comment_owners[sname] = input_value if owner is None else owner
1220
+ if location is not None and location[0] is not None:
1221
+ source_locations[sname] = location
1222
+
1223
+ # Source names are the concrete spelling shown on the row button
1224
+ # (def draw_x / Mode.WINDOW / @render_func(draw_x), ...); the generic
1225
+ # origin goes into `kind`, shown as a caption above the button. Locations
1226
+ # feed the jump-to buttons.
1227
+ view_fn = inspect.unwrap(input_value._view_func)
1228
+ fn_name = getattr(view_fn, "__name__", "?")
1229
+ try:
1230
+ fn_file = inspect.getsourcefile(view_fn)
1231
+ except TypeError:
1232
+ fn_file = None
1233
+ fn_loc = (fn_file, getattr(getattr(view_fn, "__code__", None),
1234
+ "co_firstlineno", None))
1235
+
1236
+ # cls_name is cheap (__name__); cls_loc is cached on host key change above
1237
+ # (inspect.getsourcelines AST-parses the whole module file - never per frame).
1238
+ cls_name = class_to_show.__name__ if isinstance(class_to_show, type) else "None"
1239
+ # getattr: a cm_state created before this field existed (hotswap of an
1240
+ # already-open inputs tab) lacks the field - None just degrades the jump
1241
+ # location until the menu retargets and the host_key block recomputes it.
1242
+ cls_loc = getattr(cm_state, "class_loc", None)
1243
+
1244
+ # One caller source per real frame walked (caller, caller's caller, ...),
1245
+ # innermost-first; each its own editable CallSite host parsed above. The
1246
+ # captions read "caller", "caller +1", ...; the row button shows the
1247
+ # concrete frame name (disambiguated with a ^N suffix when two frames share a
1248
+ # name, since source names are dict keys). If the stack hasn't been
1249
+ # captured yet (no real callers), still register one empty "caller"
1250
+ # row so the source-list shape stays consistent.
1251
+ caller_rows = [] # (sname, sdict, location, kind)
1252
+ _seen_caller_names = set()
1253
+ for i, ((_str_host, dict_host), (filename, lineno, func_name)) in enumerate(
1254
+ zip(cm_state.call_site_hosts, caller_frames)):
1255
+ cdict = dict_host.deep.unwrap() if dict_host else None
1256
+ cname = func_name or "caller"
1257
+ if cname in _seen_caller_names:
1258
+ cname = f"{cname} ^{i}"
1259
+ _seen_caller_names.add(cname)
1260
+ kind = "caller" if i == 0 else f"caller +{i}"
1261
+ caller_rows.append((cname, cdict, (filename, lineno), kind))
1262
+ if not caller_rows:
1263
+ caller_rows.append(("caller", None, None, "caller"))
1264
+
1265
+ # MODE - the active mode is entry in its enum class source (e.g.
1266
+ # `NEW_CODE = {types...: ModeOverrides(kwargs={...})}` in mode.py), not as
1267
+ # the kwargs dict that entry stamps into this view. A mode can hold
1268
+ # SEVERAL type-keyed entries (TEXT_ONLY); the parse can't be type-matched
1269
+ # (its keys are unevaluated source), so pick the candidate whose keys best
1270
+ # overlap the LIVE matched config (get_config_for) - the entry that
1271
+ # actually drove THIS view. Edits merge into the mode_dict host's parse
1272
+ # and ride its normal chain_out/save back into the enum's source file.
1273
+ mode_label = str(current_mode) if current_mode is not None else "None"
1274
+ mode_kwargs, mode_loc = None, None
1275
+ if current_mode is not None and cm_state.mode_dict is not None:
1276
+ candidates = [c for c in
1277
+ cm_state.mode_dict.deep[current_mode.name].kwargs.all()
1278
+ if isinstance(c, dict)]
1279
+ live_keys = set()
1280
+ if isinstance(getattr(current_mode, "value", None), dict):
1281
+ live_cfg = current_mode.get_config_for(input_value._raw_input_value)
1282
+ if live_cfg is not None and live_cfg.kwargs:
1283
+ live_keys = set(live_cfg.kwargs)
1284
+ mode_kwargs = max(candidates,
1285
+ key=lambda c: len(live_keys & set(c)), default=None)
1286
+ # inspect.getsourcelines on a class AST-parses its WHOLE module (the
1287
+ # class_loc lesson) - cache the member's location per (class, member),
1288
+ # never recompute per frame.
1289
+ _ml_key = (mode_cls, current_mode.name)
1290
+ if getattr(cm_state, "_mode_loc_key", None) != _ml_key:
1291
+ cm_state._mode_loc_key = _ml_key
1292
+ cm_state._mode_loc = None
1293
+ try:
1294
+ cls_lines, cls_start = inspect.getsourcelines(mode_cls)
1295
+ member_off = next(
1296
+ (i for i, l in enumerate(cls_lines)
1297
+ if l.lstrip().startswith((f"{current_mode.name} =",
1298
+ f"{current_mode.name}="))), 0)
1299
+ cm_state._mode_loc = (inspect.getsourcefile(mode_cls),
1300
+ cls_start + member_off)
1301
+ except (TypeError, OSError):
1302
+ pass
1303
+ mode_loc = cm_state._mode_loc
1304
+
1305
+ # Registration order IS the per-param screen's row order: param default
1306
+ # (signature), caller, mode, class var, class default, function decoration.
1307
+ _add_source(f"def {fn_name}", cm_state.render_func_dict.deep.parameters(), FunctionCodec,
1308
+ location=fn_loc, kind="signature")
1309
+ for cname, cdict, cloc, ckind in caller_rows:
1310
+ from meltygui.core.input.view_selection import CallerViewSource
1311
+ if isinstance(cdict, dict):
1312
+ cdict = CallerViewSource(cdict, cloc[0] if cloc else None, allow_direct=ckind == "caller")
1313
+ _add_source(cname, cdict, CallerCodec, location=cloc, kind=ckind)
1314
+ _add_source(mode_label, mode_kwargs, ModeCodec, location=mode_loc, kind="mode")
1315
+ # CLASS VAR - the data class's body assignments (`tint = (...)` on the
1316
+ # class itself). The class parse IS that dict (fields + __cst__/comment
1317
+ # bookkeeping, filtered out by param_source_matrix), so a + here writes a
1318
+ # brand-new class var assignment and × removes one, via the same
1319
+ # bubbling/save path as every other source.
1320
+ _add_source(f"class {cls_name}",
1321
+ cm_state.class_dict.deep.unwrap() if cm_state.class_dict else None,
1322
+ TypeCodec, location=cls_loc, kind="class var")
1323
+ _add_source(f"@defaults({cls_name})", cm_state.class_dict.deep.decorators.defaults(),
1324
+ TypeCodec, location=cls_loc, kind="class default")
1325
+ # INSTANCE ATTR - the value instance's own whitelisted params (Lamp.tint):
1326
+ # what core_render's OBJ_ATTR_PARAMS injection reads. Skip-when-absent
1327
+ # like the other value-side rows; dicts/classes carry their config in
1328
+ # __overrides__ / class rows above.
1329
+ _raw_obj = getattr(input_value, "_raw_input_value", None)
1330
+ if _raw_obj is not None and not isinstance(_raw_obj, (dict, list, type)):
1331
+ _ia = _InstanceAttrSource(_raw_obj, target_ds=input_value)
1332
+ if _ia:
1333
+ _add_source(f"{type(_raw_obj).__name__} instance", _ia,
1334
+ TypeCodec, kind="instance attr")
1335
+ # CHILD KWARGS - a parent view can drive this view's params via
1336
+ # child_kwargs={...} (draw_collection merges it into every child call).
1337
+ # The dict itself can be attached to ANY of the PARENT'S own sources (its
1338
+ # caller, decorator, comment, instance attr...), so resolve it with the
1339
+ # registry machinery ON THE PARENT: from_anywhere("child_kwargs",
1340
+ # parent). Cheap gatekeeping: only parents whose live _kwargs actually
1341
+ # carry a child_kwargs dict pay the (per-frame-memoized) parent
1342
+ # check; the root's self-parent loop is excluded. Recursion up the
1343
+ # ancestry terminates the same way: it only walks through parents that
1344
+ # themselves receive child_kwargs.
1345
+ _pds_ck = getattr(input_value, "_parent", None)
1346
+ if _pds_ck is not None and _pds_ck is not input_value:
1347
+ _my_key = (input_value._kwargs or {}).get("key")
1348
+ _ck_live = (_pds_ck._kwargs or {}).get("child_kwargs")
1349
+ _ck_live = _ck_live if isinstance(_ck_live, dict) and _ck_live else None
1350
+ _mode_src = None # the parent's mode source name, set below
1351
+ if _ck_live is not None or isinstance(_my_key, str):
1352
+ # Ensure the PARENT's registry exists (fresh sessions have no
1353
+ # _sa_cm_state until something collects it) - memoized per frame,
1354
+ # and this path only runs when a menu/tab is open on a child.
1355
+ _psrcs = _sources_for(_pds_ck)
1356
+ _pcm = getattr(_pds_ck, "_sa_cm_state", None)
1357
+ _pdeco = (_pcm.class_dict.deep.decorators()
1358
+ if _pcm is not None and _pcm.class_dict is not None else None)
1359
+ _pcls = (_pcm.host_key or (None, None))[1] if _pcm is not None else None
1360
+ _pcls_name = getattr(_pcls, "__name__", "?")
1361
+ _cls_defaults = None # first non-attr @defaults parse entry
1362
+ if isinstance(_pdeco, dict):
1363
+ for _dk, _dv in _pdeco.items():
1364
+ if not (isinstance(_dk, str) and _dk.split("#", 1)[0] == "defaults"
1365
+ and isinstance(_dv, dict)):
1366
+ continue
1367
+ _dattr = _dv.get("attr") or _dv.get("attrib")
1368
+ if _dattr is None:
1369
+ if _cls_defaults is None:
1370
+ _cls_defaults = _dv
1371
+ elif str(_dattr).strip("'\"") == _my_key:
1372
+ # ATTR-TARGETED @defaults(attr="<this field>", ...):
1373
+ # its own source row on the field's view.
1374
+ _add_source(f"@defaults({_pcls_name}.{_my_key})", _dv,
1375
+ TypeCodec, kind="attr default")
1376
+ # CHILD KWARGS (MODE) - the parent's MODE entry can carry its own
1377
+ # child_kwargs={...} (Modes.NEW_CODE stamps column_widths on every
1378
+ # child). It reaches this view through the same merge as any other
1379
+ # child_kwargs, but it lives in the mode ENUM's source, so it gets
1380
+ # its own row: an edit here writes mode.py, not the parent's
1381
+ # caller/@defaults. Absent a lazy-create adapter, but the first
1382
+ # write stamps `child_kwargs={...}` onto the mode entry's kwargs
1383
+ # parse (bubbling wraps + dirties; the mode host's code persists).
1384
+ _mode_src = next((s for s, k in _psrcs["kinds"].items()
1385
+ if k == "mode"), None)
1386
+ _mode_parse = _psrcs["sources"].get(_mode_src) if _mode_src else None
1387
+ if isinstance(_mode_parse, dict) and _mode_src in set(_psrcs["writable"]):
1388
+ _mck = _mode_parse.get("child_kwargs")
1389
+ if not isinstance(_mck, dict):
1390
+ _mck = _ChildKwargsSource(_mode_parse, None)
1391
+ _add_source("child_kwargs (mode)", _mck, ModeCodec,
1392
+ location=_psrcs["locations"].get(_mode_src),
1393
+ kind="mode child kwargs")
1394
+ if _ck_live is not None:
1395
+ # CHILD KWARGS — the dict driving this view from the parent.
1396
+ # Prefer the CODE-backed setter (the first source DRIVING
1397
+ # child_kwargs - from_anywhere's pick, inlined so the same target
1398
+ # also yields the row's jump location: the caller line /
1399
+ # @defaults class line the dict lives at). If no source sets it
1400
+ # yet, a lazy-create adapter over the class-level @defaults dict
1401
+ # makes the first write EDIT CODE (the live dict alone silently
1402
+ # kept writes in memory).
1403
+ # The MODE setter has its own row above (it outranks any other
1404
+ # parent sources, so it would otherwise always BE this row and the
1405
+ # non-mode setters would never be visible/editable): resolve this
1406
+ # row from the parent's sources MINUS mode.
1407
+ _psrcs_nm = _psrcs
1408
+ if _mode_src is not None:
1409
+ _psrcs_nm = dict(_psrcs, sources={k: v for k, v in _psrcs["sources"].items()
1410
+ if k != _mode_src})
1411
+ _ck_target = _driving_source(_psrcs_nm, "child_kwargs")
1412
+ _ck_dict = (_psrcs_nm["sources"][_ck_target].get("child_kwargs")
1413
+ if _ck_target is not None else None)
1414
+ _ck_loc = (_psrcs["locations"].get(_ck_target)
1415
+ if _ck_target is not None else None)
1416
+ if not (isinstance(_ck_dict, dict) and _ck_dict):
1417
+ _ck_dict = (_ChildKwargsSource(_cls_defaults, _ck_live)
1418
+ if isinstance(_cls_defaults, dict) else _ck_live)
1419
+ _ck_loc = getattr(_pcm, "class_loc", None) if _pcm is not None else None
1420
+ _add_source("child_kwargs", _ck_dict, TypeCodec,
1421
+ location=_ck_loc, kind="child kwargs")
1422
+ # CODEC - the active codec's render_kwargs (ds._codec, stashed by the
1423
+ # wrapper for every view): the lowest kwargs merge layer and the
1424
+ # provenance color (import views' green). Skip-when-absent like the
1425
+ # other value-side rows.
1426
+ _codec_obj = getattr(input_value, "_codec", None)
1427
+ if _codec_obj is not None:
1428
+ _cs = _CodecSource(_codec_obj, input_value)
1429
+ if _cs:
1430
+ _add_source(f"codec {getattr(_codec_obj, '__name__', type(_codec_obj).__name__)}",
1431
+ _cs, TypeCodec, kind="codec")
1432
+ # DRAW STATE - the ds's own kwargs (ds.tint): the lowest source, ranked
1433
+ # last, so windows tinted only by their persisted draw_state (the "blue
1434
+ # window no other claims" case) still resolve to a real, writable row.
1435
+ _dsa = _DrawStateAttrSource(input_value)
1436
+ if _dsa:
1437
+ _add_source("draw_state", _dsa, TypeCodec, kind="draw state")
1438
+
1439
+ # ── Sources parsed off the VALUE itself ──────────────────────────────────
1440
+ # The value flowing through the view can be (or sit inside) a parse node
1441
+ # of the owning window's's bubbling tree (e.g. a nested ClassParse in
1442
+ # the Toggles window). Kwargs carried by that parse are live inputs the
1443
+ # class_to_show rows (keyed on the value's runtime TYPE) miss, and since
1444
+ # the dicts are bubbling trees, a cell edit marks the host dirty and saves
1445
+ # through its normal path - no extra save need. Walk a few parents so a
1446
+ # primitive leaf (scroll_speed) still finds its owning class parse, same
1447
+ # as the class_to_show walk. Two such sources per parse:
1448
+ # @defaults(<name>) the parse's decorators.defaults dict - the wrapper
1449
+ # reads it directly (core_render's
1450
+ # input_value["decorators"] tint path)
1451
+ # # [<name>] the '# [tint=(...)]' override comment on the
1452
+ # value's source line: a leaf child carries it in
1453
+ # __overrides__; a primitive field's comment lives on
1454
+ # the PARENT parse under __<key>__ (fed to the
1455
+ # child's kwargs by draw_collection). Saves update
1456
+ # the comment via _patch_leading_override /
1457
+ # _patch_field_overrides.
1458
+ # Both skip when absent (same rule as @window below) - a placeholder row
1459
+ # on every parsed value would be permanent noise.
1460
+ def _root_file_loc(ds, span):
1461
+ # Map a span (relative to the root host parse's source) to a file
1462
+ # location: nested parses carry no file_path, so find the ancestor
1463
+ # parse that has file_path + line_offset. The window ds holds it as
1464
+ # _input_value (post-convert), so check both. None → no jump location.
1465
+ while span is not None and ds is not None:
1466
+ for _r in (getattr(ds, "_raw_input_value", None),
1467
+ getattr(ds, "_input_value", None)):
1468
+ if isinstance(_r, GeneralParse) and getattr(_r, "file_path", None):
1469
+ return (str(_r.file_path), _r.line_offset + span.start_line)
1470
+ if ds._parent is ds:
1471
+ break
1472
+ ds = ds._parent
1473
+ return None
1474
+
1475
+ # Explicit collection/key threading is always a comment source, even when
1476
+ # no ancestor view renders that value (standalone draw_text calls).
1477
+ slot_collection = input_value._kwargs.get("collection")
1478
+ slot_key = input_value._kwargs.get("key")
1479
+ if isinstance(slot_collection, dict) and isinstance(slot_key, str):
1480
+ overrides = slot_collection.get("__overrides__", {})
1481
+ entry = overrides.get(f"__{slot_key}__") if isinstance(overrides, dict) else None
1482
+ if isinstance(entry, dict) or isinstance(slot_collection, GeneralParse):
1483
+ if not isinstance(entry, dict):
1484
+ entry = _LazyOverrideEntry(slot_collection, f"__{slot_key}__")
1485
+ _add_source(f"# [{slot_key}]", entry, TypeCodec, kind="code comment")
1486
+ own_value = input_value._raw_input_value
1487
+ if isinstance(own_value, dict) and not isinstance(own_value, GeneralParse):
1488
+ overrides = own_value.get("__overrides__")
1489
+ if isinstance(overrides, dict):
1490
+ _add_source("# [value]", overrides, TypeCodec, kind="code comment")
1491
+
1492
+ _pds, _pwalk = input_value, 4
1493
+ while _pds is not None and _pwalk >= 0:
1494
+ _praw = getattr(_pds, "_raw_input_value", None)
1495
+ if isinstance(_praw, GeneralParse):
1496
+ _pdecos = _praw.get("decorators")
1497
+ _pdefaults = _pdecos.get("defaults") if isinstance(_pdecos, dict) else None
1498
+ _pname = (getattr(_praw, "def_name", None)
1499
+ or getattr(getattr(_praw.get("__cst__"), "name", None),
1500
+ "value", None))
1501
+ # The name-carry guard covers viewing ClassParse's own parse -
1502
+ # the class_to_show row already shows it there.
1503
+ if (isinstance(_pdefaults, dict) and _pdefaults and _pname
1504
+ and f"@defaults({_pname})" not in sources):
1505
+ _add_source(f"@defaults({_pname})", _pdefaults, TypeCodec,
1506
+ location=_root_file_loc(_pds, getattr(_praw, "span", None)),
1507
+ kind="code class default")
1508
+ _povs = _praw.get("__overrides__")
1509
+ if _pds is input_value:
1510
+ # The view targets the parse itself - its own leading comment.
1511
+ _ov_dict, _ov_name = _povs, _pname
1512
+ _ov_span = getattr(_praw, "span", None)
1513
+ else:
1514
+ # Primitive leaf - the parent parse holds its comment under
1515
+ # __<key>__; the leaf's parent key rides in its kwargs (the
1516
+ # 'key' entry draw_collection passes every child).
1517
+ _tkey = (input_value._kwargs or {}).get("key")
1518
+ _ov_dict = (_povs.get(f"__{_tkey}__")
1519
+ if isinstance(_povs, dict) and isinstance(_tkey, str)
1520
+ else None)
1521
+ _ov_name = _tkey
1522
+ # _child_spans maps field keys to their assignment's span
1523
+ # (_record_child) - jump lands on the field's line, the
1524
+ # comment's one above.
1525
+ _ov_span = (getattr(_praw, "_child_spans", None) or {}).get(_tkey)
1526
+ if _ov_name:
1527
+ if not (isinstance(_ov_dict, dict) and _ov_dict):
1528
+ # No comment yet - lazy row, same as the live-view path
1529
+ # below: the matrix's + materializes the entry, the
1530
+ # wrapper synthesizes the `# [...]` line. For the parse
1531
+ # itself (a class/function node) the entry is its OWN
1532
+ # __overrides__ (leading comment on the def); for a
1533
+ # leaf it's the parent's __<key>__ slot. Module parses
1534
+ # have no _pname, so they register nothing - no noise.
1535
+ _ov_dict = (_LazyOverrideEntry(_praw)
1536
+ if _pds is input_value
1537
+ else _LazyOverrideEntry(_praw, f"__{_ov_name}__"))
1538
+ _add_source(f"# [{_ov_name}]", _ov_dict, TypeCodec,
1539
+ location=_root_file_loc(_pds, _ov_span),
1540
+ kind="code comment")
1541
+ break
1542
+ # LIVE-VIEW windows/markers: the value flowing through them is a
1543
+ # runtime capture, not a parse node - but the marker stamps the owning
1544
+ # scope's parse dict on the draw_state (live_root/live_key, the same
1545
+ # data its comment-args splat reads), so the site's `# [<key>]`
1546
+ # comment registers as an input source exactly like a primitive
1547
+ # leaf's. Edits write to the editor host's bubbling tree and save
1548
+ # through its normal path.
1549
+ _lroot = getattr(_pds, "live_root", None)
1550
+ if isinstance(_lroot, dict):
1551
+ # Resolve against the editor's CURRENT root: the stamp is the
1552
+ # marker's last render, and a reparse since (the window's def
1553
+ # scrolled off the screen, say) orphaned it - a write into the
1554
+ # orphan shows in the replay but never reaches the save.
1555
+ from meltygui.editor.live_view_views import current_live_root
1556
+ _lroot = current_live_root(_pds)
1557
+ _lkey = getattr(_pds, "live_key", None)
1558
+ if isinstance(_lkey, str):
1559
+ _lovs = _lroot.get("__overrides__")
1560
+ _lov = (_lovs.get(f"__{_lkey}__")
1561
+ if isinstance(_lovs, dict) else None)
1562
+ if not isinstance(_lov, dict):
1563
+ # No override yet - register a lazy entry so the matrix's
1564
+ # + can create `# [tint=(...)]` the same way it stamps a
1565
+ # missing @defaults(row).
1566
+ _lov = _LazyOverrideEntry(_lroot, f"__{_lkey}__")
1567
+ _lspan = (getattr(_lroot, "_child_spans", None) or {}).get(_lkey)
1568
+ _add_source(f"# [{_lkey}]", _lov, TypeCodec,
1569
+ location=_root_file_loc(_pds, _lspan),
1570
+ kind="code comment", owner=_pds)
1571
+ break
1572
+ if _pds._parent is _pds:
1573
+ break
1574
+ _pds = _pds._parent
1575
+ _pwalk -= 1
1576
+ # @window on the class (e.g. `@window(tint=(0.11,0.12,0.14))` on Toggles) -
1577
+ # its kwargs drive the window rendering the value, so it's an input source.
1578
+ # Same skip-when-absent rule as the fn-side @window below.
1579
+ _cls_window_deco = (cm_state.class_dict.deep.decorators.window()
1580
+ if cm_state.class_dict else None)
1581
+ if isinstance(_cls_window_deco, dict) and _cls_window_deco:
1582
+ _add_source(f"@window({cls_name})", _cls_window_deco,
1583
+ DecorationsCodec, location=cls_loc, kind="class decoration")
1584
+ # @render_func(...) - the view func's decorator kwargs. Registered for
1585
+ # EVERY value (not just function values, the old gate): decorator kwargs
1586
+ # outrank signature defaults in the wrapper gauntlet, so a param set
1587
+ # there (fast_toggle's show_cache=False) is the TRUE driver and hiding
1588
+ # the row made provenance lie. The app-wide blast radius of an edit is
1589
+ # real but the row is only ever written by an explicit pick.
1590
+ decoration_name = decoration_func.__name__
1591
+ decoration_raw = inspect.unwrap(decoration_func)
1592
+ decoration_loc = (inspect.getsourcefile(decoration_raw), decoration_raw.__code__.co_firstlineno)
1593
+ _add_source(f"@render_func({decoration_name})",
1594
+ cm_state.decoration_dict.deep.decorators.render_func(),
1595
+ DecorationsCodec, location=decoration_loc, kind="decoration")
1596
+ # @window only exists as a source on actually-@window-decorated funcs -
1597
+ # a placeholder row here would be permanent noise on every other tab.
1598
+ from meltygui.core.input.view_selection import WindowViewSource
1599
+ _window_deco = cm_state.decoration_dict.deep.decorators.window()
1600
+ if isinstance(_window_deco, dict) and _window_deco:
1601
+ _add_source(f"@window({decoration_name})",
1602
+ WindowViewSource(_window_deco, decoration_func, decoration_loc[0]),
1603
+ DecorationsCodec, location=decoration_loc, kind="window decoration")
1604
+ # @glfw_window(...) on the view fn (`@glfw_window` over `@render_func`,
1605
+ # app.py): every kwarg past the OS window's own (title / size / window_id /
1606
+ # name) is the root VIEW's, handed to the view by app._draw_root - the OS
1607
+ # window's twin of @window, and it ranks with it. Same skip-when-absent
1608
+ # rule; a missing `@glfw_window` parses to a str and adds nothing.
1609
+ _glfw_deco = cm_state.decoration_dict.deep.decorators.glfw_window()
1610
+ if isinstance(_glfw_deco, dict) and _glfw_deco:
1611
+ _add_source(f"@glfw_window({decoration_name})",
1612
+ WindowViewSource(_glfw_deco, decoration_func, decoration_loc[0]),
1613
+ DecorationsCodec, location=decoration_loc, kind="glfw window decoration")
1614
+
1615
+ cm_state._collect_cache = {
1616
+ "sources": sources, "tints": source_tints,
1617
+ "locations": source_locations, "kinds": source_kinds,
1618
+ "comment_owners": comment_owners,
1619
+ "writable": tuple(writable_sources), "view_func": input_value._wrapper or input_value._view_func}
1620
+ cm_state._collect_key = _memo_key
1621
+ return cm_state._collect_cache
1622
+
1623
+
1624
+ def _ancestor_call_line(target_ds, ancestor_ds):
1625
+ """File-absolute line inside `ancestor_ds`'s view function whose statement
1626
+ (transitively) rendered `target_ds`'s element — e.g. inspecting a button
1627
+ drawn by draw_tab_bar and walking up one scope resolves the `button(...)`
1628
+ call line in draw_tab_bar. The func tab auto-selects it.
1629
+
1630
+ Scans the TARGET's cached _call_stack (innermost-first tuples, captured
1631
+ once on menu-open — never the live stack, see the capture note in
1632
+ core_render) for the nearest frame executing the ancestor's function; that
1633
+ frame's lineno is the call statement. A view rendered inside a deferred
1634
+ layer has a stack that bottoms out at the layer loop, so each deferred
1635
+ ancestor's queue-time _deferred_call_stack is appended to continue the
1636
+ chain outward. None when the ancestor's frame isn't in the chain (stack
1637
+ not captured yet, or the ancestor rendered from cache with parents
1638
+ skipped)."""
1639
+ view_func = getattr(ancestor_ds, "_view_func", None)
1640
+ if view_func is None:
1641
+ return None
1642
+ code = getattr(inspect.unwrap(view_func), "__code__", None)
1643
+ if code is None:
1644
+ return None
1645
+ stack = list(getattr(target_ds, "_call_stack", None) or ())
1646
+ node = target_ds
1647
+ for _ in range(64):
1648
+ if getattr(node, "_is_deferred_layer", False):
1649
+ stack.extend(getattr(node, "_deferred_call_stack", None) or ())
1650
+ parent = getattr(node, "_parent", None)
1651
+ if node is ancestor_ds or parent is None or parent is node:
1652
+ break
1653
+ node = parent
1654
+ for filename, lineno, func_name in stack:
1655
+ if func_name == code.co_name and filename == code.co_filename:
1656
+ return lineno
1657
+ return None
1658
+
1659
+
1660
+ def _deferred_ancestors(target_ds):
1661
+ """The draw_states on `target_ds`'s parent chain (itself included,
1662
+ nearest first) that were queued to a deferred layer at some point
1663
+ (_is_deferred_layer) — the ones whose queue-time stack a descendant's
1664
+ trace needs."""
1665
+ found = []
1666
+ node = target_ds
1667
+ for _ in range(64):
1668
+ if getattr(node, "_is_deferred_layer", False):
1669
+ found.append(node)
1670
+ parent = getattr(node, "_parent", None)
1671
+ if parent is None or parent is node:
1672
+ break
1673
+ node = parent
1674
+ return found
1675
+
1676
+
1677
+ def _request_deferred_stacks(target_ds):
1678
+ """Ask every deferred ancestor of `target_ds` that has no queue-time
1679
+ stack yet to capture one on its next inline (queue) pass — lazy and
1680
+ one-shot, the same shape as the up-arrow's _call_site_requested. The
1681
+ ancestor's PARENT has to re-run its body for the queue branch to fire,
1682
+ hence the invalidate_up."""
1683
+ for node in _deferred_ancestors(target_ds):
1684
+ if (node._deferred_call_stack_frames is None
1685
+ and not node._deferred_stack_requested):
1686
+ node._deferred_stack_requested = True
1687
+ if Core.melty.cache is not None:
1688
+ Core.melty.cache.invalidate_up(node._tile_id, max_depth=5)
1689
+ request_render()
1690
+
1691
+
1692
+ def _splice_deferred_stack(inline_frames, queued_frames, view_func):
1693
+ """Join a dispatch-bottomed stack onto the chain that queued its layer.
1694
+
1695
+ `inline_frames` (outermost first) were captured while the deferred
1696
+ layer was being drawn from Melty.end_frame's layer loop, so their head
1697
+ is the frame loop → Melty.draw → wrapper; `queued_frames` were captured
1698
+ at queue time inside that same wrapper, so their tail is the caller
1699
+ chain → wrapper. The result keeps the queued head up to the wrapper and
1700
+ the inline tail from the wrapper on: the trace of a direct call.
1701
+ Returns `inline_frames` unchanged when the layer loop isn't in it (the
1702
+ view rendered inline this time) or when the first view body after the
1703
+ dispatch isn't `view_func` (a stale deferred mark on another ancestor)."""
1704
+ from meltygui.code.chain_converters import _is_dispatch_frame
1705
+ def _base(path):
1706
+ return path.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
1707
+ loop = None
1708
+ for index, frame in enumerate(inline_frames):
1709
+ if _base(frame[0]) == "meltygui.py" and frame[2] == "end_frame":
1710
+ loop = index
1711
+ if loop is None:
1712
+ return inline_frames
1713
+ wrapper = None
1714
+ for index in range(loop + 1, len(inline_frames)):
1715
+ if _base(inline_frames[index][0]) == "core_render.py":
1716
+ wrapper = index
1717
+ break
1718
+ if wrapper is None:
1719
+ return inline_frames
1720
+ code = getattr(inspect.unwrap(view_func), "__code__", None) \
1721
+ if view_func is not None else None
1722
+ if code is not None:
1723
+ body = next((f for f in inline_frames[wrapper:]
1724
+ if not _is_dispatch_frame(f[0], f[2])), None)
1725
+ if body is None or body[0] != code.co_filename \
1726
+ or body[2] != code.co_name:
1727
+ return inline_frames
1728
+ head = list(queued_frames)
1729
+ while head and _base(head[-1][0]) == "core_render.py":
1730
+ head.pop()
1731
+ return head + list(inline_frames[wrapper:])
1732
+
1733
+
1734
+ def _merged_call_stack_frames(target_ds, menu_state):
1735
+ """The Code tab's stack: the target's menu-open capture with every
1736
+ deferred ancestor's queue-time stack spliced in, nearest layer first,
1737
+ so nested deferred windows chain outward to the real caller. Stops at
1738
+ the first ancestor whose queue-time stack hasn't landed yet (a partial
1739
+ splice past it would join the wrong layer). Memoized on the identities
1740
+ of the parts — draw_stack_trace rebuilds on the list's identity."""
1741
+ inline = getattr(target_ds, "_call_stack_frames", None)
1742
+ if not inline:
1743
+ return None
1744
+ ancestors = _deferred_ancestors(target_ds)
1745
+ key = (id(inline),) + tuple(
1746
+ id(node._deferred_call_stack_frames) for node in ancestors)
1747
+ if getattr(menu_state, "_merged_stack_key", None) == key:
1748
+ return menu_state._merged_stack
1749
+ merged = inline
1750
+ for node in ancestors:
1751
+ queued = node._deferred_call_stack_frames
1752
+ if not queued:
1753
+ break
1754
+ merged = _splice_deferred_stack(
1755
+ merged, queued, getattr(node, "_view_func", None))
1756
+ menu_state._merged_stack_key = key
1757
+ menu_state._merged_stack = merged
1758
+ return merged
1759
+
1760
+
1761
+ # Reported by draw_context_menu_items when its Inspect row was picked: the
1762
+ # wrapper (core_render's context-menu block) opens the inspector,
1763
+ # draw_context_menu, in the menu's place.
1764
+ INSPECT = object()
1765
+
1766
+
1767
+ from meltygui.core.layout.dropdown_core import _ds_in_subtree
1768
+
1769
+
1770
+ from meltygui.core.layout.dropdown_core import _dd_set_cursor
1771
+
1772
+
1773
+ from meltygui.core.layout.dropdown_core import _dd_invalidate_rows
1774
+
1775
+
1776
+ from meltygui.core.layout.dropdown_core import _dd_scroll_cursor_into_view
1777
+
1778
+
1779
+ from meltygui.core.layout.dropdown_core import _dd_pick
1780
+
1781
+
1782
+ from meltygui.core.layout.dropdown_core import _dd_close
1783
+
1784
+
1785
+ from meltygui.core.layout.dropdown_core import _dd_handle_keys
1786
+
1787
+
1788
+ _DD_MENU_W = Toggles.Dropdown.menu_width
1789
+ _DD_ROW_H = Toggles.Dropdown.row_height
1790
+ # Root popover size bounds (draw_dropdown's content fit + the resize handle):
1791
+ # the wrapper's min_width / min_height for dd_menu and the old popup_size.
1792
+ _DD_MENU_MIN_W = Toggles.Dropdown.min_width
1793
+ _DD_MENU_MIN_H = Toggles.Dropdown.min_height
1794
+ _DD_MENU_MAX_H = Toggles.Dropdown.max_height
1795
+
1796
+
1797
+ from meltygui.core.layout.dropdown_core import _dd_update_menu_size
1798
+
1799
+
1800
+ # Limits on the scope-label column of code-preview rows (usage-jump picker):
1801
+ # the main code column clamps here, and longer labels ellipsize, so the
1802
+ # code keeps most of the row's width.
1803
+ _DD_CODE_LBL_MAX_W = Toggles.Dropdown.code_label_max_width
1804
+
1805
+
1806
+ # Row tags (the right-aligned dim column): default colour - the
1807
+ # autocomplete kind label's blue washed - and the gap between segments.
1808
+ _DD_TAG_COLOR = Toggles.Dropdown.tag_color
1809
+ _DD_TAG_GAP = Toggles.Dropdown.tag_gap
1810
+ # Per-row symbol tint wash alpha (autocomplete's definition colours) -
1811
+ # the tag mask re-composes it so the mask stays invisible on tinted rows.
1812
+ _DD_ROW_TINT_A = Toggles.Dropdown.row_tint_alpha
1813
+
1814
+
1815
+ from meltygui.view.control_view import draw_single
1816
+
1817
+
1818
+ # [tint=(0.75, 0.0, 0.0), show_tint=True]
1819
+ def draw_any(input_value: any = None, view_func=None, mode: any = None, chain=None, **kwargs):
1820
+ import inspect
1821
+ kwargs_view_func = view_func
1822
+ key = kwargs.get("key", None)
1823
+ real_type = kwargs.get("real_type", type(input_value))
1824
+ collection_type = kwargs.get("type_collection", type(kwargs.get("collection", None)))
1825
+
1826
+ if view_func is None:
1827
+ new_default = Core.melty.get_default_view_function(real_type=real_type, collection_type=collection_type,
1828
+ attrib_key=key, value=input_value)
1829
+ if new_default is None:
1830
+ new_default = draw_collection
1831
+ if view_func is None:
1832
+ view_func = new_default
1833
+
1834
+ if mode is None:
1835
+ mode = Core.melty.mode_stack[-1] if len(Core.melty.mode_stack) > 0 else None
1836
+
1837
+ if isinstance(mode, tuple) and len(mode) > 0:
1838
+ main_mode = mode[0]
1839
+ else:
1840
+ main_mode = mode
1841
+
1842
+ if main_mode is not None:
1843
+ # Loop over super types
1844
+ mode_config = main_mode.get_config_for(input_value)
1845
+ if mode_config is not None and mode_config.func is None and (
1846
+ mode_config.kwargs.get("convert", None) is not None
1847
+ or mode_config.kwargs.get("convert_in", None) is not None):
1848
+ convert_in = mode_config.kwargs.get("convert_in", None)
1849
+ if convert_in is not None:
1850
+ # Infer target type from the return annotation of the last converter
1851
+ import inspect
1852
+ last_fn = convert_in[-1]
1853
+ ret = inspect.signature(last_fn).return_annotation
1854
+ convert_to_type = ret if ret is not inspect.Parameter.empty else None
1855
+ else:
1856
+ convert_to_type = mode_config.kwargs["convert"][-1]
1857
+ mode_config = main_mode.get_config_for(the_type=convert_to_type) if convert_to_type is not None else None
1858
+ if mode_config is not None and mode_config.func is not None:
1859
+ view_func = draw_single
1860
+ kwargs_view_func = mode_config.func
1861
+
1862
+ elif mode_config is not None and mode_config.func is not None:
1863
+ if isinstance(mode_config.func, tuple):
1864
+ kwargs['chain'] = mode_config.func
1865
+ kwargs['route'] = mode_config.route
1866
+ view_func = run_chain
1867
+ else:
1868
+ view_func = mode_config.func
1869
+ kwargs_view_func = view_func
1870
+
1871
+ # kwargs['use_cache'] = True
1872
+ kwargs['mode'] = mode
1873
+ kwargs['view_func'] = kwargs_view_func
1874
+
1875
+ from meltygui.core.input.view_selection import configured_view_func
1876
+ try:
1877
+ selected = configured_view_func(input_value, kwargs)
1878
+ except ValueError as error:
1879
+ from meltygui.core.diagnostics.notifications import notify
1880
+ notify(str(error), tag="view_func")
1881
+ selected = None
1882
+ if selected is not None and view_func is not draw_single and view_func is not run_chain:
1883
+ view_func = selected
1884
+ if "view_func" not in inspect.signature(inspect.unwrap(view_func)).parameters:
1885
+ kwargs.pop("view_func", None)
1886
+ return_val = view_func(input_value, **kwargs)
1887
+
1888
+ return return_val
1889
+
1890
+
1891
+ from meltygui.editor.source_ui import _RowSpan, _row_code_hosts