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,1783 @@
1
+ """Inspection view functions and supporting definitions."""
2
+ from enum import Enum
3
+ from meltygui.core.melty import Melty
4
+ from meltygui.core.melty import SearchTerm
5
+ from meltygui.core.rendering.modes import Modes
6
+ from meltygui.core.core_render import render_func
7
+ from meltygui.core.rendering.core_decoration import Core
8
+ from meltygui.core.rendering.window_decoration import window
9
+ from meltygui.core.rendering.render_funcs import RenderFuncs
10
+ from meltygui.state.inspection_state import ContextMenuState
11
+ from meltygui.state.inspection_state import _InfoRow
12
+ from meltygui.state.new_core_model import ContextMenuWindowState
13
+ from meltygui.state.new_core_model import TabState
14
+ from meltygui.core.runtime.toggles import Tint
15
+ from meltygui.core.runtime.toggles import Toggles
16
+ from meltygui.core.runtime.toggles import hsv_to_rgb
17
+ from meltygui.core.runtime.toggles import rgb_to_hsv
18
+ import inspect
19
+ import meltygui_imgui as imgui
20
+ import threading
21
+ import types
22
+
23
+
24
+ @render_func(use_cache=False, show_bg=False, disable_scroll=True, shadow=False, selectable=False)
25
+ def draw_with_modes(input_value, modes, tab_state: TabState = None, search_text="", draw_state=None, unique=0):
26
+ from meltygui.view.tab_view import draw_tab_bar
27
+ from meltygui.core.rendering.render_dispatch import draw_any
28
+ from meltygui.core.rendering.render_dispatch import input_tab_name
29
+ from meltygui.core.rendering.render_dispatch import tab_names
30
+
31
+ if not tab_state.selected_tabs:
32
+ tab_state.selected_tabs = [modes[0]]
33
+ imgui.dummy(0, 5)
34
+ tint_value = 0.0
35
+ tint_saturation = 0.688
36
+ tab_changed, new_tabs = draw_tab_bar(input_value=tab_state.selected_tabs,
37
+ tab_height=30, show_bg=False, bg_offset=1,
38
+ name=f"tab_bar{unique}", wrap=True,
39
+ collection=modes, as_toggles=False)
40
+ if tab_changed:
41
+ tab_state.selected_tabs = new_tabs
42
+ # The compact Info tab's initial fit leaves no room for source rows.
43
+ # Give Inputs a usable viewport when entering it from that small fit.
44
+ if tab_names.index(input_tab_name) in new_tabs:
45
+ minimum_height = min(700, int(imgui.get_io().display_size.y * 0.8))
46
+ if (draw_state.height or 0) < minimum_height:
47
+ draw_state.height = minimum_height
48
+ draw_state.invalidate()
49
+
50
+ imgui.dummy(0, 2)
51
+ changed = False
52
+ value = input_value
53
+ for idx, mode in enumerate(tab_state.selected_tabs):
54
+ mode_changed, value = draw_any(input_value, name=f"Mode: {mode} {unique}", mode=mode, selectable=False,
55
+ show_name=False,
56
+ with_header=None, show_header=False, disable_scroll=False,
57
+ indent_size=0, show_bg=False, use_cache=True, shadow=False, column=idx)
58
+ changed |= mode_changed
59
+
60
+ return changed, value
61
+
62
+
63
+ @render_func(tint=(0.18, 0.32, 0.55), use_cache=False, show_name=False, show_bg=False)
64
+ def draw_view_func_selector(input_value, search_text="", draw_state=None, **kwargs):
65
+ """Select a registered renderer; the caller owns applying the choice."""
66
+ from meltygui.view.dropdown_view import draw_dropdown
67
+ from meltygui.model.search_model import _fuzzy_key_match
68
+
69
+ from meltygui.core.input.view_selection import view_func_name
70
+ choices = {name: getattr(RenderFuncs, name)
71
+ for name in sorted(Melty.render_funcs_by_name)
72
+ if not search_text or _fuzzy_key_match(search_text.lower(), name.lower())}
73
+ options = dict(kwargs)
74
+ options.pop("view_func", None)
75
+ options["use_cache"] = False
76
+ options["display_label"] = view_func_name(input_value)
77
+ changed, selected = draw_dropdown(input_value, collection=choices, **options)
78
+ return changed, selected if changed else input_value
79
+
80
+
81
+ @render_func(use_cache=False, show_bg=False, shadow=False, with_header=None,
82
+ show_name=False, selectable=False, is_tree=True, temp=True, searchable=False)
83
+ def draw_param_matrix(input_value, wrap=True, search_text="", draw_state=None, source_tints=None, unique=None,
84
+ source_locations=None, priority_params=(), source_order=(), view_draw_state=None,
85
+ source_dicts=None, writable_sources=(), source_kinds=None, **kwargs):
86
+ """The inputs-tab parameter screen: ONE parameter at a time, EVERY source.
87
+ A dropdown at the top switches between all the parameters identified for
88
+ the view (its own signature params first, then the @render_func machinery
89
+ kwargs); below it, one row per possible input source — param default
90
+ (signature), caller, mode, class @defaults, function decoration — in
91
+ `source_order`, shown whether or not the source currently sets the value.
92
+ Sources that set the param show their editable value; the rest show a dim
93
+ "not set", so the parameter's full input surface is mapped in one glance.
94
+
95
+ Cells are parse FRAGMENTS (leaves pulled out of their codec's parse), so
96
+ they can't naturally adopt the codec tint the way a whole codec-typed
97
+ value does — this view is special: it looks the tint up per source (the
98
+ tab maps each row to its codec) and applies it MANUALLY, alpha-boosted,
99
+ so the data source is highly visible at a glance. Cell edits mutate the
100
+ row in place and report changed, for apply_param_source_matrix write-back.
101
+
102
+ `priority_params` (see signature_param_names) orders the view function's
103
+ own signature params to the front of the dropdown list.
104
+
105
+ `search_text` (the tab's Ctrl+F find bar) filters the dropdown's param
106
+ list and auto-switches the screen to the best match: exact substring for
107
+ short terms, the shared typo-tolerant matcher (_fuzzy_key_match) for
108
+ longer ones.
109
+
110
+ `source_dicts` (the live {source_name: parse dict} mapping) enables the
111
+ +/× buttons: + stamps the param into a source that doesn't set it (seeded
112
+ from the view's live resolved value), × pops it from one that does. Both
113
+ are PLAIN dict mutations — the bubbling wrapper marks the owning host
114
+ dirty and its normal chain_out/save path persists the change. Only
115
+ `writable_sources` (real parse dicts, not the absent-source placeholders)
116
+ get the buttons."""
117
+ from meltygui.core.conversion.cache_tree import UNSET_VALUE
118
+ from meltygui.core.styling.fonts import Font
119
+ from meltygui.core.windowing.glfw_utils import request_render
120
+ from meltygui.view.control_view import button
121
+ from meltygui.view.dropdown_view import draw_dropdown
122
+ from meltygui.view.text_view import draw_text
123
+ from meltygui.core.rendering.render_dispatch import _MATRIX_FRAMEWORK_PARAMS
124
+ from meltygui.model.search_model import _fuzzy_key_match
125
+ from meltygui.core.rendering.render_dispatch import draw_any
126
+
127
+ changed = False
128
+ tints = source_tints or {}
129
+ locations = source_locations or {}
130
+ plus_icon = "\uf067" # FA plus -- explicit escape, see jump_to.py
131
+ times_icon = "" # FA times -- explicit escape, see jump_to.py
132
+ folder_icon = "\uf07b" # FA folder -- explicit escape, see jump_to.py
133
+
134
+ # The dropdown's param list: the view function's own signature params
135
+ # first (plus the MATRIX_DEFAULT_PRIORITY pins, which are listed even
136
+ # when no source row exists for them - their screen just shows every
137
+ # source as not set / +), then the @render_func machinery kwargs.
138
+ # Injected/underscored params are never user inputs, so no screen.
139
+ prio_set = set(priority_params or ())
140
+ params = [p for p in (priority_params or ()) if not p.startswith('_')]
141
+ params += [p for p in input_value
142
+ if p not in prio_set and p not in _MATRIX_FRAMEWORK_PARAMS
143
+ and not p.startswith('_')]
144
+
145
+ search_q = str(search_text or "").strip().lower()
146
+ if search_q:
147
+ params = [p for p in params if _fuzzy_key_match(search_q, p.lower())]
148
+ if not params:
149
+ imgui.text_colored(f"no parameters match '{search_q}'", 1, 1, 1, 0.3)
150
+ return False, input_value
151
+
152
+ selected = getattr(draw_state, "_selected_param", None)
153
+ if selected not in params:
154
+ # Fresh screen (or stale selection) defaults to tint - the param this
155
+ # menu is reached for most - falling back to the first param if a
156
+ # search filter has excluded it.
157
+ selected = "tint" if "tint" in params else params[0]
158
+ draw_state._selected_param = selected
159
+
160
+ draw_text(f"def {view_draw_state._view_func.__name__}",
161
+ is_tree=False, editable=False, width=draw_state.content_width - 14,
162
+ font=Font.JETBRAINS_MONO_30)
163
+ imgui.dummy(0, 4)
164
+
165
+ # STABLE identity: the dropdown's name must not change with the selection -
166
+ # its popover is a latching child window, and a name change would orphan
167
+ # the existing popover. Sync the label as an ordinary input.
168
+ dd_res = draw_dropdown(
169
+ selected, collection={p: p for p in params},
170
+ name=f"param_pick##{unique}",
171
+ width=min(280, max(120, draw_state.content_width - 24)),
172
+ show_header=False, return_extras=True, display_label=selected)
173
+ picked_changed, picked = dd_res[0], dd_res[1]
174
+ if picked_changed and picked in params:
175
+ draw_state._selected_param = picked
176
+ selected = picked
177
+ draw_state.invalidate()
178
+ imgui.same_line()
179
+ origin = "renderer" if selected == "view_func" else ("signature" if selected in prio_set else "core_render.py")
180
+ imgui.text_colored(origin, 1, 1, 1, 0.25)
181
+ imgui.dummy(0, 6)
182
+
183
+ row = input_value.get(selected)
184
+ row = row if isinstance(row, dict) else {}
185
+ # Every registered source gets a row, set or not; sources present only in
186
+ # the row (unmatched leftovers) append after the canonical order.
187
+ order = [s for s in (source_order or ())]
188
+ order += [s for s in row if s not in order]
189
+
190
+ # Uniform label-button width across every row, so values align into a
191
+ # column no matter how long each source's name is.
192
+ btn_w = max((imgui.calc_text_size(f"{folder_icon} {sn}")[0] for sn in order),
193
+ default=0.0) + 15
194
+
195
+ def _stamp_value(param):
196
+ """Seed for a + click: the view's LIVE resolved value for the param
197
+ (its stamped kwargs, then the draw_state mirror), falling back to the
198
+ first set source's cell — adding a source changes nothing visually
199
+ until the user edits the new value. Deep-copied so the new source
200
+ never aliases another source's parse node; a copied dict's foreign
201
+ __cst__ would mis-anchor the save, so it's stripped."""
202
+ import copy as _copy
203
+ v = (getattr(view_draw_state, '_kwargs', None) or {}).get(param, UNSET_VALUE)
204
+ if v is UNSET_VALUE:
205
+ try:
206
+ v = getattr(view_draw_state, param, None)
207
+ except Exception:
208
+ v = None
209
+ if v is None:
210
+ v = next((row[sn] for sn in order if sn in row), None)
211
+ try:
212
+ v = _copy.deepcopy(v)
213
+ except Exception:
214
+ pass
215
+ if isinstance(v, dict):
216
+ v.pop('__cst__', None)
217
+ v.pop('__origin__', None)
218
+ return v
219
+
220
+ for sname in order:
221
+ tint = tints.get(sname)
222
+ is_set = sname in row
223
+ src = (source_dicts or {}).get(sname)
224
+ can_write = isinstance(src, dict) and sname in (writable_sources or ())
225
+
226
+ # The source KIND caption (signature / caller / mode / class default /
227
+ # decoration) sits on its own line above the row; the tinted button
228
+ # below displays the concrete name (def draw_voxels / Mode.WINDOW /
229
+ # @defaults(...)) and IS the jump button - clicking opens the source's
230
+ # file in the IDE; the value sits on the same line with
231
+ # show_name=False - so one element does both labeling and navigation.
232
+ kind = (source_kinds or {}).get(sname)
233
+ if selected == "view_func" and kind == "signature" and not is_set:
234
+ can_write = False # Adding a new parameter does not select a renderer.
235
+ if kind:
236
+ imgui.text_colored(kind, 1, 1, 1, 0.5)
237
+ tint_kwargs = {"alpha": 0.0, "tint": tint} if tint else {}
238
+ clicked = button(f"{folder_icon} {sname}", width=btn_w, height=22, shadow=False,
239
+ text_saturation=0.9, use_cache=True, text_align="left",
240
+ text_value=0.819,
241
+ name=f"jump_{sname}##{selected}_{unique}", show_button_bg=True,
242
+ **tint_kwargs)[0]
243
+ loc = locations.get(sname)
244
+ if clicked and loc:
245
+ from meltygui.utils.jump_to_code import open_in_intellij
246
+ threading.Thread(target=open_in_intellij, args=(str(loc[0]),),
247
+ kwargs={"line_number": loc[1]},
248
+ daemon=True).start()
249
+ imgui.same_line()
250
+
251
+ if is_set:
252
+ # × removes the param from this source's dict. A plain dict
253
+ # mutation: the bubbling invalidate notifies the owning object,
254
+ # which goes dirty and persists via its own chain_out/save.
255
+ if isinstance(src, dict) and not (selected == "view_func" and getattr(src, "direct", False)):
256
+ if button(times_icon, width=30, height=22, shadow=True, use_cache=True,
257
+ text_value=1.0, name=f"{sname}_delete##{selected}_{unique}",
258
+ show_button_bg=True, **tint_kwargs)[0]:
259
+ if selected == "view_func" and view_draw_state is not None:
260
+ from meltygui.core.rendering.parameter_core import clear_anywhere
261
+ clear_anywhere(selected, view_draw_state, source=sname)
262
+ else:
263
+ src.pop(selected, None)
264
+ row.pop(sname, None)
265
+ draw_state.invalidate()
266
+ request_render()
267
+ imgui.dummy(0, 3)
268
+ continue
269
+ imgui.same_line()
270
+
271
+ # key routes the cell by ATTRIBUTE name (a tint value gets the
272
+ # swatch/picker, not draw_tuple); the SOURCE stays in the
273
+ # identity via name while the button above displays it.
274
+ cell_view = draw_view_func_selector if selected == "view_func" else draw_any
275
+ ch, nv = cell_view(row[sname], name=f"{sname}##{selected}_{unique}",
276
+ key=selected,
277
+ tint=tint,
278
+ show_name=False, wrap=True,
279
+ bg_offset=2, z_offset=0, disable_scroll=True, width=167)
280
+ if ch:
281
+ if selected == "view_func" and view_draw_state is not None:
282
+ from meltygui.core.rendering.parameter_core import set_anywhere
283
+ set_anywhere(selected, nv, view_draw_state, source=sname)
284
+ else:
285
+ row[sname] = nv
286
+ changed = True
287
+ elif can_write:
288
+ # + stamps the param on this source - same plain-dict write the
289
+ # cell editors use, so the same host-dirty/save machinery runs.
290
+ if button(plus_icon, width=31, height=22, shadow=True, use_cache=True,
291
+ text_value=1.1, name=f"add_{sname}##{selected}_{unique}",
292
+ show_button_bg=True, **tint_kwargs)[0]:
293
+ if selected == "view_func" and view_draw_state is not None:
294
+ from meltygui.core.rendering.parameter_core import set_anywhere
295
+ from meltygui.core.rendering.parameter_core import anywhere_value
296
+ set_anywhere(selected, anywhere_value(selected, view_draw_state),
297
+ view_draw_state, source=sname)
298
+ else:
299
+ src[selected] = _stamp_value(selected)
300
+ row[sname] = src.get(selected)
301
+ draw_state.invalidate()
302
+ request_render()
303
+ imgui.same_line()
304
+ imgui.text_colored("not set", 1, 1, 1, 0.5)
305
+ else:
306
+ imgui.text_colored("not set", 1, 1, 1, 0.5)
307
+
308
+ imgui.dummy(0, 3)
309
+
310
+ return changed, input_value
311
+
312
+
313
+ def draw_lens(lens, draw_state):
314
+ """Render a single Lens against draw_state: resolve its root, then either
315
+ focus the live leaf in place (in-place kinds) or run its generated
316
+ parse→focus→save chain (code kinds). Returns (changed, _)."""
317
+ from meltygui.core.rendering.render_dispatch import draw_any
318
+
319
+ from meltygui.code.chain_converters import focus
320
+ root = lens.root(draw_state)
321
+ if root is None:
322
+ imgui.text_colored(f"{lens.kind or lens.label}: n/a here", 0.5, 0.5, 0.5)
323
+ return False, root
324
+ if lens.chain is None:
325
+ return focus(root, path=lens.path, default=lens.default, kind=lens.kind, name=lens.label + lens.name)
326
+ return draw_any(root, chain=lens.chain(root), name=lens.label)
327
+
328
+
329
+ @window
330
+ @render_func()
331
+ def context_menu_settings(input_value, draw_state):
332
+ from meltygui.code.new_converters import code_file_io
333
+
334
+ code_file_io(draw_context_menu, mode=Modes.NEW_CODE)
335
+
336
+
337
+ @render_func(use_cache=False, show_bg=False, show_header=False, show_name=False,
338
+ selectable=False, is_default_for=_InfoRow)
339
+ def draw_info_param(input_value, **kwargs):
340
+ """One info-tab row: a source dropdown for LOOKING at the different input
341
+ sources, plus the value stored AT the selected source — editable when
342
+ that source is writable, an inline + when it doesn't set the param there
343
+ yet, read-only text otherwise. Shared per-render state (source registry,
344
+ active map, dropdown option cache) arrives via _InfoRow.ctx; selection
345
+ lives on the TAB's draw_state (misc) — pure view state."""
346
+ from meltygui.core.windowing.glfw_utils import request_render
347
+ from meltygui.view.control_view import draw_button
348
+ from meltygui.view.control_view import text
349
+ from meltygui.view.dropdown_view import draw_dropdown
350
+ from meltygui.core.rendering.parameter_core import set_anywhere
351
+ from meltygui.core.rendering.render_dispatch import draw_any
352
+
353
+ from meltygui.core.rendering.parameter_core import _ABOVE_DRAW_STATE
354
+ row = input_value
355
+ ctx = row.ctx
356
+ if ctx is None or row.group is None:
357
+ # Cold hit before the tab stamped this row's context (shouldn't
358
+ # happen - rows only render from inside the tab body).
359
+ return False, input_value
360
+ param = row.param
361
+ target = ctx.target
362
+ # Key match/current flags come from draw_collection (it matched our name);
363
+ # forwarded to the value widget so the header carries the match, same as
364
+ # the non-collection rows did.
365
+ _search_kw = {"search_match": kwargs.get("search_match", False),
366
+ "search_current": kwargs.get("search_current", False)}
367
+
368
+ if ctx.parses_ready:
369
+ # The ACTIVE source: the highest-priority setter (precomputed in
370
+ # ctx.active_map, one registry pass) - unless a diverged auto_param
371
+ # outranks it at runtime (the ds replaces every setter not in
372
+ # _ABOVE_DRAW_STATE; the ds row only registers whitelisted
373
+ # attrs, so detect the divergence directly. A bare `param in
374
+ # target.__dict__` would be wrong: DrawState.__init__ sets its
375
+ # own param on every param).
376
+ setting = ctx.active_map.get(param)
377
+ ds_has = param in (getattr(target, "auto_params", None) or {})
378
+ if ds_has and (setting is None
379
+ or ctx.prio[setting][0] not in _ABOVE_DRAW_STATE):
380
+ active = "draw_state"
381
+ else:
382
+ active = setting
383
+ ctx.active_cache[param] = active
384
+ known = True
385
+ else:
386
+ # ACTIVE-SOURCE CACHE DISABLED (perf A/B): never serve cached
387
+ # picks - loading rows draw no dropdown until sources are live.
388
+ # Re-enable by restoring: known = param in ctx.active_cache;
389
+ # active = ctx.active_cache.get(param)
390
+ known = False
391
+ active = None
392
+
393
+ if known:
394
+ # Dropdown of ALL sources in SourcePriority order, active one
395
+ # tinted + row-ted - memoized per current active source
396
+ # (ctx.options_for), not rebuilt per param.
397
+ options, _row_tints = ctx.options_for(active)
398
+
399
+ # Selection is per-tab view state; default = the active source.
400
+ sel_key = f"src_sel::{param}"
401
+ sel = ctx.tab_ds.misc.get(sel_key)
402
+ if sel not in options:
403
+ if active is not None:
404
+ sel = active
405
+ elif ctx.parses_ready:
406
+ sel = ctx.default_source_once()
407
+ else:
408
+ sel = "draw_state"
409
+
410
+ # Subtle trigger: no button bg/shadow, short, narrow - it's a
411
+ # provenance label with a dropdown, not a primary action.
412
+ # Per-row trash INSIDE the dropdown row clears this param at THAT
413
+ # source without closing the list, so several sources can be
414
+ # cleared in one operation. Only rows that actually SET the param
415
+ # get one (ctx.setters_map, plus the ds when an auto_param diverged);
416
+ # codec rows are excluded - clear_anywhere can't reverse the
417
+ # codec's per-file/render_kwargs fanout yet.
418
+ def _clear_at(src, _p=param):
419
+ from meltygui.core.rendering.parameter_core import clear_anywhere
420
+ if clear_anywhere(_p, target, str(src)) is not None:
421
+ target.invalidate()
422
+ ctx.tab_ds.invalidate()
423
+
424
+ _row_actions = {s: _clear_at for s in ctx.setters_map.get(param, ())
425
+ if ctx.srcs["kinds"].get(s) != "codec"}
426
+ if param in (getattr(target, "auto_params", None) or {}):
427
+ _row_actions["draw_state"] = _clear_at
428
+ pick_changed, new_pick = draw_dropdown(
429
+ options.get(sel, sel), collection=options, width=181, z_offset=0,
430
+ shadow=False, show_button_bg=False, trigger_height=22, show_bg=False,
431
+ text_pad=3, row_tints=_row_tints, row_actions=_row_actions,
432
+ text_toward_bg=ctx.text_toward_bg,
433
+ name=f"src_{param}_dd", show_header=False)
434
+ if pick_changed and new_pick:
435
+ sel = str(new_pick)
436
+ ctx.tab_ds.misc[sel_key] = sel
437
+ else:
438
+ sel = None
439
+ imgui.dummy(181, 22) # hold the dropdown slot so no reflow when it appears
440
+
441
+ imgui.same_line()
442
+
443
+ any_changed = False
444
+ if not ctx.parses_ready:
445
+ # Sources still loading: stored-at-source reads would hit
446
+ # placeholders - bind the widget to the RESOLVED value and route
447
+ # edits through the automatic pick until the sources are real.
448
+ item_return = draw_any(row.group.get(param), name=param,
449
+ show_bg=False, show_header=True, **_search_kw)
450
+ item_changed, out_val = item_return[0], item_return[1]
451
+ if item_changed:
452
+ row.group[param] = out_val
453
+ any_changed = True
454
+ else:
455
+ # The value AT the selected source (not the resolved value) - that's
456
+ # what looking at a source means. draw_state reads the direct attr.
457
+ sdict = ctx.srcs["sources"].get(sel)
458
+ stored = sdict.get(param) if isinstance(sdict, dict) else None
459
+ if sel == "draw_state" and stored is None:
460
+ stored = (getattr(target, "auto_params", None) or {}).get(
461
+ param, target.__dict__.get(param))
462
+ sel_writable = sel in ctx.writable or sel == "draw_state"
463
+
464
+ # In-flight display cache (_sa_pending - the same one anywhere_value
465
+ # serves): a slow-source write, and EVERY write while a drag is underway
466
+ # (deferred), hasn't reached the source dict yet - a raw stored read
467
+ # snaps the slider back to the stale value next frame ("stuck").
468
+ # Serve the pending UI value while the trip is in flight, but only
469
+ # when this row's selected source is the one the write targeted.
470
+ # The tab's proxy refresh runs anywhere_value per param, which
471
+ # retires it once the stored value moves off its at-set baseline.
472
+ _pending = getattr(target, "_sa_pending", None)
473
+ if _pending and param in _pending:
474
+ _lastsrc = getattr(target, "_sa_last_source", None) or {}
475
+ if _lastsrc.get(param, sel) == sel:
476
+ stored = _pending[param][0]
477
+
478
+ if stored is None:
479
+ if sel_writable:
480
+ # Selected source doesn't set the param yet: + stamps a value
481
+ # into it (creating the entry); next frame the widget takes
482
+ # over. draw_button is the most-compatible in-line button
483
+ # (draw_float's shape) - same header chrome as widget rows.
484
+ clicked, _ = draw_button("+", name=f"+##add_{param}",
485
+ label="+", display_name=param,
486
+ show_name=True, show_bg=False,
487
+ wrap=True, min_width=24, **_search_kw)
488
+ if clicked:
489
+ # Resolved value when there is one; a None (header params
490
+ # nothing sets) stamps the DECLARED signature default -
491
+ # stamping None would create an entry that still reads
492
+ # as None ("the + does nothing" feel).
493
+ stamp = row.group.get(param)
494
+ if stamp is None:
495
+ from meltygui.core.rendering.parameter_core import signature_default_for
496
+ stamp = signature_default_for(param, target)
497
+ set_anywhere(param, stamp, target, allow_any=True,
498
+ ds_fallback=True, source=sel)
499
+ any_changed = True
500
+ else:
501
+ text(f"{param}: not set here", name=f"ro_{param}",
502
+ editable=False, **_search_kw)
503
+ elif sel_writable:
504
+ item_return = draw_any(stored, name=param,
505
+ show_bg=False, show_header=True, **_search_kw)
506
+ item_changed, out_val = item_return[0], item_return[1]
507
+ if item_changed:
508
+ set_anywhere(param, out_val, target, allow_any=True,
509
+ ds_fallback=True, source=sel)
510
+ any_changed = True
511
+ else:
512
+ text(f"{param}: {stored}", name=f"ro_{param}", editable=False,
513
+ **_search_kw)
514
+
515
+ if any_changed:
516
+ target.invalidate()
517
+ # The rows themselves live under the (cached) tab: re-render so the
518
+ # active tint and stored values reflect the write this frame
519
+ # (parse-dict writes are synchronous; the hosts' notify triggers the
520
+ # later save/hotswap).
521
+ ctx.tab_ds.invalidate()
522
+ request_render()
523
+ return False, input_value
524
+
525
+
526
+ @render_func(use_cache=True, show_bg=False, show_header=False, disable_scroll=False,
527
+ searchable=True, show_name=False, selectable=False)
528
+ @window
529
+ def draw_info_tab(input_value, search_text='', draw_state=None, unique=None, **kwargs):
530
+ """One row per view param: a source dropdown for LOOKING at the different
531
+ input sources, plus the value stored at the selected source — editable
532
+ when that source is writable, an inline + when it doesn't set the param
533
+ there yet. The dropdown defaults to the source actively driving the
534
+ param (yellow row/trigger); switching it never deletes or moves
535
+ anything, it just changes which source you're viewing/editing.
536
+ Selection lives on THIS tab's draw_state (misc) — pure view state.
537
+
538
+ The source machinery is debug-gated: by default the tab renders just the
539
+ grouped param values (cheap — no registry parses) and the Debug button
540
+ switches to the dropdown rows. The header group starts collapsed in
541
+ both modes."""
542
+ from meltygui.state.inspection_state import _InfoRow
543
+ from meltygui.core.windowing.glfw_utils import request_render
544
+ from meltygui.view.collection_view import draw_collection
545
+ from meltygui.view.control_view import draw_button
546
+ from meltygui.core.rendering.parameter_core import _source_priority
547
+ from meltygui.core.rendering.parameter_core import _sources_for
548
+ from meltygui.core.rendering.parameter_core import default_write_source
549
+ from meltygui.core.rendering.render_dispatch import _INFO_GROUP_OVERRIDES
550
+ from meltygui.core.rendering.render_dispatch import _SourceItem
551
+
552
+ if input_value is None:
553
+ return False, None
554
+ from meltygui.core.rendering.parameter_core import _unset_value
555
+ target = input_value
556
+ # locate_all_params: the view's own params PLUS the header's
557
+ # (with_header function inputs - icon, show_name, name_color, etc),
558
+ # deduped, view params first. Same read/write semantics.
559
+ proxy = target.locate_all_params
560
+
561
+ # ── Search - the rows render through draw_collection, so it owns key
562
+ # matching: count claims (its _search_matcher over the row keys = the
563
+ # param names), the match/current glow kwargs, and scroll-to-match.
564
+ # Just resolve what to FORWARD: a term/SearchTerm passed in search_text
565
+ # (the menu's search box / an ancestor session), else this tab's OWN
566
+ # find-bar session - the search_text kwarg is NEVER injected from an
567
+ # ancestor session, so a self-hosted Ctrl+F never arrives through it.
568
+ draw_state._search_matcher = None # pre-collection matcher (stale after hotswap)
569
+ _term = search_text or (draw_state.search_text if draw_state.search_active else "")
570
+ if isinstance(_term, SearchTerm):
571
+ child_search = _term
572
+ elif _term and draw_state.search_active and draw_state._search_session is not None:
573
+ # The session (a SearchTerm) carries term + current/scroll_to state.
574
+ child_search = draw_state._search_session
575
+ else:
576
+ child_search = ""
577
+
578
+ # ── Debug gate - the source registry is EXPENSIVE (_sources_for spawns
579
+ # render-func/class/call-site parses, and the codeCM guard below
580
+ # pulses re-renders until they land). Skip ALL of it until asked: by
581
+ # default the tab is just the grouped values (edits route through
582
+ # ParamProxy.__setitem__ → set_anywhere on the driving source); the
583
+ # Debug button fills in the per-param source dropdowns. The pick is
584
+ # per-tab view state, same slot as src_sel.
585
+ debug = bool(draw_state.misc.get("info_sources"))
586
+ clicked, _ = draw_button("Debug", name="dbg_btn", show_name=False,
587
+ label="Hide sources" if debug else "Debug",
588
+ min_width=110)
589
+ if clicked:
590
+ debug = not debug
591
+ draw_state.misc["info_sources"] = debug
592
+ draw_state.invalidate()
593
+ request_render()
594
+
595
+ if not debug:
596
+ # Same grouped shape as the debug mirror, but with the live
597
+ # ParamProxy groups themselves. A fresh outer dict, so the proxy
598
+ # never carries the __overrides__ entry (GroupedParamProxy.refresh
599
+ # would break on a non-proxy value).
600
+ outer = {}
601
+ for _gkey in ("params", "header"):
602
+ _group = proxy.get(_gkey)
603
+ if _group:
604
+ outer[_gkey] = _group
605
+ outer["__overrides__"] = _INFO_GROUP_OVERRIDES
606
+ draw_collection(outer, name="rows", use_cache=False, show_bg=False,
607
+ show_header=False, shadow=False, selectable=False,
608
+ item_spacing_y=2, child_kwargs={"show_system": True,
609
+ "initial":{"expanded":False}
610
+ },
611
+ search_text=child_search)
612
+ return False, input_value
613
+
614
+ srcs = _sources_for(target)
615
+ writable = set(srcs["writable"])
616
+
617
+ # ONE pass over the registry per render - the active source is simply
618
+ # the highest-priority writable source with a SET value. The old shape
619
+ # re-walked every source dict per PARAM (_setting_source per row, plus
620
+ # the default_write_source cue re-counting key overlaps), which is
621
+ # O(params × sources × keys) through lazy bubbling parse wrappers.
622
+ _prio = {s: _source_priority(srcs["kinds"].get(s)) for s in srcs["sources"]}
623
+ _ordered_all = sorted(srcs["sources"], key=_prio.get)
624
+ active_map = {} # param -> highest-priority source
625
+ setters_map = {} # param -> [every source setting it, priority order]
626
+ for _s in _ordered_all:
627
+ if _s not in writable:
628
+ continue
629
+ _sd = srcs["sources"][_s]
630
+ if not isinstance(_sd, dict):
631
+ continue
632
+ for _k, _v in _sd.items():
633
+ if _unset_value(_v):
634
+ continue
635
+ if _k not in active_map:
636
+ active_map[_k] = _s
637
+ setters_map.setdefault(_k, []).append(_s)
638
+
639
+ # The + default for params the source sets (the other-params cue) is
640
+ # param-independent to first order - compute at most once per render,
641
+ # not per row (its overlap counting walks every source dict).
642
+ _cue = []
643
+
644
+ def _default_source_once():
645
+ if not _cue:
646
+ _cue.append(default_write_source("", target, srcs=srcs))
647
+ return _cue[0]
648
+
649
+ # Dropdown styling from Toggles.ContextMenu (live-editable): the active
650
+ # source's yellow, and how far source-row text pulls toward the menu bg.
651
+ _active_tint = tuple(Toggles.ContextMenu.active_source_tint)
652
+ _text_toward_bg = float(Toggles.ContextMenu.source_text_toward_bg)
653
+
654
+ # Dropdown options/row-tints are IDENTICAL for every param sharing the
655
+ # same active source - and a view usually has only one or two distinct
656
+ # actives. Build once per distinct active, not per param (the per-row
657
+ # dict of _SourceItems was N_params × N_sources object churn per render).
658
+ _row_cache = {}
659
+
660
+ def _options_for(active):
661
+ hit = _row_cache.get(active)
662
+ if hit is None:
663
+ options = {s: _SourceItem(s, _active_tint if s == active else None)
664
+ for s in _ordered_all}
665
+ options.setdefault(
666
+ "draw_state",
667
+ _SourceItem("draw_state",
668
+ _active_tint if active == "draw_state" else None))
669
+ row_tints = ({str(active): _active_tint}
670
+ if active is not None else None)
671
+ hit = (options, row_tints)
672
+ _row_cache[active] = hit
673
+ return hit
674
+
675
+ # _sources_for's keep-alive registers the TARGET as the code hosts'
676
+ # consumer; this tab is cached separately, so register it too - a parse
677
+ # landing (an editor's save/hotswap + an external change) then invalidates
678
+ # these rows and the active-source cache tracks the live registry.
679
+ _cm = getattr(target, "_sa_cm_state", None)
680
+ if _cm is not None:
681
+ for _h in (_cm.render_func_dict, _cm.class_dict, _cm.mode_dict,
682
+ *[dh for (_sh, dh) in (_cm.call_site_hosts or [])]):
683
+ if _h is not None:
684
+ _h.notify_on_change(draw_state)
685
+
686
+ # Cold-session guard: while the render-func parse hasn't materialized
687
+ # (signature row still the unwritable placeholder), keep re-rendering.
688
+ # A cached tab stops pulsing the registry and its consumer stamp goes stale,
689
+ # and the parse-landing notify can miss it - the tab then shows "not
690
+ # set here" placeholders forever on a fresh session.
691
+ _sig = next((s for s, k in srcs["kinds"].items() if k == "signature"), None)
692
+ parses_ready = _sig in writable
693
+ if not parses_ready:
694
+ draw_state.invalidate()
695
+ request_render()
696
+
697
+ # Active-source cache on the TARGET ds: parses take a time on a cold
698
+ # open, and provenance shouldn't hide while they load. Ready registry →
699
+ # recompute and refresh the cache; loading → serve the cached pick, and
700
+ # a param with NO cached pick hides the dropdown until the registry is
701
+ # known (the value widget still draws, bound to the resolved value).
702
+ _active_cache = getattr(target, "_sa_active_src", None)
703
+ if _active_cache is None:
704
+ _active_cache = {}
705
+ target._sa_active_src = _active_cache
706
+
707
+ # Everything a row needs to draw its dropdown + stored-value widget,
708
+ # computed ONCE per tab render and shared by every row via _InfoRow.ctx.
709
+ ctx = types.SimpleNamespace(
710
+ target=target, tab_ds=draw_state, srcs=srcs, writable=writable,
711
+ prio=_prio, active_map=active_map, setters_map=setters_map,
712
+ options_for=_options_for, default_source_once=_default_source_once,
713
+ text_toward_bg=_text_toward_bg, parses_ready=parses_ready,
714
+ active_cache=_active_cache)
715
+
716
+ # Mirror the grouped proxy ({'params': {...}, 'header': {...}}) with
717
+ # stable _InfoRow leaves: one row object per param, kept across frames
718
+ # so the child draw_states maintain their identity, rebuilt in proxy order
719
+ # (in place; group dicts' identity is stable too) and pruned as params
720
+ # vanish.
721
+ rows_store = getattr(draw_state, "_info_rows", None)
722
+ if rows_store is None:
723
+ rows_store = {}
724
+ draw_state._info_rows = rows_store
725
+ for _gkey in ("params", "header"):
726
+ _group = proxy.get(_gkey)
727
+ if not _group:
728
+ rows_store.pop(_gkey, None)
729
+ continue
730
+ rows = rows_store.setdefault(_gkey, {})
731
+ _params = list(_group.keys())
732
+ if list(rows.keys()) != _params:
733
+ _prev = dict(rows)
734
+ rows.clear()
735
+ for _p in _params:
736
+ rows[_p] = _prev.get(_p) or _InfoRow(_p)
737
+ for _row in rows.values():
738
+ _row.group = _group
739
+ _row.ctx = ctx
740
+
741
+ # ONE draw_collection over the whole grouped dict - leaf groups render as
742
+ # nested tabs (the point of the grouped shape), leaf rows type-route to
743
+ # draw_info_param and own their writes (always returning changed=False,
744
+ # so nothing gets written back into the mirror). use_cache=False: the
745
+ # tab's own cache is the only gate, as before. Named apart from the
746
+ # edit-mode "rows" so each mode owns its own draw_state subtree.
747
+ rows_store["__overrides__"] = _INFO_GROUP_OVERRIDES # header starts collapsed
748
+ draw_collection(rows_store, name="src_rows", use_cache=False,
749
+ show_bg=False, show_header=False, shadow=False,
750
+ selectable=False, item_spacing_y=2,
751
+ child_kwargs={"show_system": True},
752
+ search_text=child_search)
753
+ return False, input_value
754
+
755
+
756
+ @render_func(use_cache=True, show_bg=False, show_header=False, show_name=False, selectable=False)
757
+ def draw_config_tab(input_value, **kwargs):
758
+ """List the inspected view function's configurable parameters and their
759
+ current values (kwarg override, else signature default)."""
760
+ from meltygui.view.control_view import text
761
+ from meltygui.view.text_view import draw_text
762
+ from meltygui.core.rendering.render_dispatch import draw_any
763
+
764
+ view_func = input_value._view_func
765
+ if view_func is None:
766
+ text("No view function")
767
+ return False, input_value
768
+ imgui.dummy(0, 0)
769
+
770
+ # Unwrap the @render_func wrapper to read the original signature.
771
+ raw_func = getattr(view_func, '__wrapped__', view_func)
772
+ sig = inspect.signature(raw_func)
773
+ ds_kwargs = input_value._kwargs or {}
774
+ # Framework-injected params the user doesn't configure.
775
+ skip_params = {"input_value", "draw_state", "args", "o_kwargs",
776
+ "kwargs", "meta", "viewstate", "self"}
777
+
778
+ for param_name, param in sig.parameters.items():
779
+ if param_name in skip_params:
780
+ if param_name in ds_kwargs:
781
+ param_value = ds_kwargs[param_name]
782
+ draw_text(f"{param.__class__.__name__}", name=param_name,
783
+ editable=False, tint=(0.8, 0.8, 0.2))
784
+ continue
785
+
786
+ if param.kind in (inspect.Parameter.VAR_POSITIONAL,
787
+ inspect.Parameter.VAR_KEYWORD):
788
+ continue
789
+
790
+ # Current value: kwarg override, else the signature default.
791
+ if param_name in ds_kwargs:
792
+ param_value = ds_kwargs[param_name]
793
+ elif param.default is not inspect.Parameter.empty:
794
+ param_value = object()
795
+ else:
796
+ param_value = None
797
+
798
+ if isinstance(param_value, (int, float, str, bool, Enum)):
799
+ draw_text(f"{param_value}", name=param_name, editable=False)
800
+ else:
801
+ draw_any(param_value, name=param_name,
802
+ show_name=True, show_header=True,
803
+ show_add_delete=False, draw=True)
804
+ return False, input_value
805
+
806
+
807
+ @render_func(use_cache=True, show_bg=False, live=False, mode=Modes.WINDOW, show_header=False, show_name=False,
808
+ selectable=False)
809
+ def draw_live_tab(input_value, **kwargs):
810
+ """List the inspected view function's configurable parameters and their
811
+ current values (kwarg override, else signature default)."""
812
+ from meltygui.view.control_view import text
813
+
814
+ imgui.text("Re-renders view frequently, bad for performance but good for debugging")
815
+
816
+ params_to_view = ["unique", ("abs_left", "left_offset"), ("abs_top", "top_offset"), "scroll_offset",
817
+ ("abs_top_true", "top_offset_true"), ("width", "height"), ("content_width", "content_height"),
818
+ "layer", "z_offset"]
819
+ for to_view in params_to_view:
820
+ if isinstance(to_view, str):
821
+ value = getattr(input_value, to_view, 'N/A')
822
+ text(f"{to_view}: {value}", name=to_view, editable=False)
823
+ else:
824
+ values = [getattr(input_value, attr, 'N/A') for attr in to_view]
825
+ text(f"{', '.join(to_view)}: {', '.join(str(v) for v in values)}", name=", ".join(to_view), editable=False)
826
+
827
+ if isinstance(input_value._raw_input_value, (dict, list, tuple)):
828
+ text(f"Length: {len(input_value._raw_input_value)}", name="raw_input_length", editable=False)
829
+
830
+ # imgui.text_colored(f"Unique {input_value.unique}", *(0.5, 0.01, 0.6))
831
+ # imgui.dummy(0,2)
832
+ #
833
+ # imgui.text_colored(f"Top, Left {input_value.abs_top}, {input_value.abs_left}", *(0.5, 0.5, 0.0))
834
+ # imgui.dummy(0, 2)
835
+ #
836
+ # #Width, height
837
+ # imgui.text_colored(f"Width, Height {input_value.width}, {input_value.height}", *(0.5, 0.01, 0.6))
838
+ # imgui.dummy(0, 2)
839
+ #
840
+ # imgui.text_colored(f"Unique {input_value.unique}", *(0.5, 0.01, 0.6))
841
+ # imgui.dummy(0, 2)
842
+
843
+ return False, input_value
844
+
845
+
846
+ def draw_func_tab(input_value, name=None, disable_scroll=True, width=None,
847
+ height=None, select_line=None, select_seq=0, **kwargs):
848
+ """Editable source of the inspected view function; hotswaps on save.
849
+ Routes through Mode.FILE_TREE — the same cache-backed code_file_io path a
850
+ folder-files leaf uses — so all editors share one code path.
851
+
852
+ A plain function, not a @render_func: the wrapper added a full pass +
853
+ tile layer around a single dispatch (the standing `draw_func_tab` line
854
+ in the frame profiles) and the editor child does its own caching. The
855
+ caller's `name` rides into the child as its `key` so two func tabs
856
+ showing the same function keep distinct draw_states — the wrapper's
857
+ per-tab name used to provide that separation.
858
+
859
+ code_file_io is called DIRECTLY with Mode.FILE_TREE's override kwargs
860
+ (chain idiom) instead of via draw_any(mode=FILE_TREE): the mode pins
861
+ disable_scroll=True and mode kwargs win over call kwargs, so the
862
+ caller's disable_scroll=False could never reach the editor — the old
863
+ wrapper was the scroll container, and removing it killed scrolling
864
+ until this bypass."""
865
+ from meltygui.view.control_view import draw_str
866
+
867
+ view_func = input_value._view_func
868
+ if view_func is not None:
869
+ from meltygui.code.new_converters import code_file_io
870
+ from meltygui.view.code_view import draw_text_from_code_cache
871
+ view_func_name = view_func.__name__ if hasattr(view_func, '__name__') else str(view_func)
872
+ _kw = {}
873
+ if width is not None:
874
+ _kw["width"] = width
875
+ if height is not None:
876
+ _kw["height"] = height
877
+ if name is not None:
878
+ _kw["key"] = name
879
+ if select_line is not None:
880
+ # File-absolute line to auto-select (scope-up nav) — rides through
881
+ # code_file_io's child_kwargs into draw_text_from_code_cache, which
882
+ # consumes it against the span buffer. select_seq (the key-press
883
+ # generation) keys the one-shot so each press re-selects.
884
+ _kw["child_kwargs"] = {"select_line": select_line,
885
+ "select_seq": select_seq}
886
+ code_file_io(view_func, auto_load_edits=True,
887
+ view_func=draw_text_from_code_cache,
888
+ disable_scroll=disable_scroll, show_name=False,
889
+ is_tree=False, name=view_func_name, **_kw)
890
+ else:
891
+ draw_str("No view function specified", name="View Function", editable=False)
892
+ return False, input_value
893
+
894
+
895
+ @render_func(use_cache=True, show_bg=False, show_header=False, show_name=False, selectable=False)
896
+ def draw_eval_tab(input_value, draw_state, unique=None, enter_key_down=None,
897
+ menu_draw_state=None, **kwargs):
898
+ """Arbitrary-code REPL scoped to the inspected view function. This tab is just
899
+ an editor + trigger: it stashes the snippet and a pending flag on the TARGET
900
+ widget's draw_state. The eval itself runs back in that widget's render wrapper
901
+ (core_render), right before it calls the view func -- so the snippet sees the
902
+ view function's real call-time locals. We read the result back off the same
903
+ draw_state."""
904
+ from meltygui.core.windowing.glfw_utils import request_render
905
+ from meltygui.view.control_view import button
906
+ from meltygui.view.text_view import draw_text
907
+ from meltygui.core.rendering.render_dispatch import eval_input_scope
908
+ import meltygui.core.windowing.window_api as glfw
909
+
910
+ target = input_value # (possibly walked-up) target's draw_state
911
+ eval_view_func = target._view_func
912
+
913
+ # Autocomplete scope. The exact call-time locals are only known once an eval
914
+ # actually fires (core_scoped_eval pulls them then). To give type-aware
915
+ # suggestions BEFORE the first eval, pre-record an initial scope from what
916
+ # the target draw_state already exposes -- input_value/value, draw_state/ds,
917
+ # and every explicit kwarg. core_scoped_eval refines this to exact on first eval.
918
+ from meltygui.core.rendering.func_metadata import FuncsMetadata
919
+ from meltygui.core.rendering.func_metadata import eval_completion_source
920
+ _scope = eval_input_scope(target)
921
+ if isinstance(target._kwargs, dict):
922
+ _scope.update(target._kwargs)
923
+ _scope.update({"input_value": target._raw_input_value, "value": target._raw_input_value,
924
+ "draw_state": target, "ds": target})
925
+ FuncsMetadata.record(eval_view_func, _scope)
926
+
927
+ # The snippet lives on the TARGET's draw_state as a persisted `eval_code`
928
+ # field (excluded from auto-invalidation: typing must not re-render the
929
+ # inspected view), so it reopens with what was last typed for this view.
930
+ code = target.eval_code
931
+ if code is None:
932
+ code = "input_value"
933
+ # Single-line editor: Enter never reaches the editor as a newline (its newline
934
+ # handler is gated on `not single_line`); instead the menu claims the
935
+ # enter-down event via its on_enter_key_down param and uses it to fire the
936
+ # eval below.
937
+ # return_extras gives the code box's draw_state so we can tell when it holds
938
+ # text focus (and thus when Enter should fire the eval -- see below).
939
+ box = draw_text(
940
+ code, name=f"eval_code##{unique}", padding_right=100,
941
+ single_line=True, show_bg=True, show_header=False,
942
+ completion_source=eval_completion_source(eval_view_func),
943
+ return_extras=True, tint=(0.05, 0.15, 0.08))
944
+ code_changed, new_code = box[0], box[1]
945
+ code_ds = box[2] if len(box) > 2 else None
946
+ if code_changed:
947
+ target.eval_code = new_code
948
+ code = new_code
949
+
950
+ def _fire_eval():
951
+ # Stash the snippet + arm the trigger, then force the target to actually
952
+ # re-render (bypassing its cache) so its wrapper runs func() -- and our
953
+ # eval hook -- this/next frame.
954
+ target.eval_code = code
955
+ target._eval_pending = True
956
+ target._eval_request_gen = getattr(target, '_eval_generation', 0) + 1
957
+ target.invalidate()
958
+ target._parent.invalidate_up(max_depth=5)
959
+ request_render()
960
+
961
+ run_clicked = button("Run", height=30, name=f"eval_run##{unique}",
962
+ color=(0.2, 0.7, 0.3), factor=0.8)[0]
963
+ # Enter fires the eval. Listen for it directly (the way draw_text reads keys
964
+ # off the frame queue) instead of relying on the menu to forward it: while the
965
+ # single-line code box holds text focus, Enter never reaches it as a newline,
966
+ # so we claim it here when that box is the focused editor. But when the
967
+ # autocomplete popup is open, Enter ACCEPTS the highlighted suggestion (the box
968
+ # consumes it + splices text),), so we must NOT also fire the eval that press.
969
+ # `code_changed` is the reliable gate: a "run" Enter (popup closed) leaves the
970
+ # single-line buffer untouched, while an accept always rewrites it. (Reading
971
+ # code_ds._popup_open here is too late; draw_text already cleared it on accept.)
972
+ enter_pressed = (code_ds is not None and not code_changed
973
+ and Core.melty.text_focused_ds is code_ds
974
+ and any(k in (glfw.KEY_ENTER, glfw.KEY_KP_ENTER)
975
+ for k, _ in Core.melty.frame_key_events))
976
+ if run_clicked or enter_pressed:
977
+ _fire_eval()
978
+
979
+ # The eval lands in the target's wrapper, a separate render pass. Until the
980
+ # requested generation is served, keep THIS tab (and the owning menu) live so
981
+ # we re-run and re-read the fresh result rather than serving a stale cache.
982
+ if getattr(target, '_eval_generation', 0) < getattr(target, '_eval_request_gen', 0):
983
+ draw_state.invalidate() # this tab's own draw_state
984
+ if menu_draw_state is not None:
985
+ menu_draw_state.invalidate() # the menu so it re-calls this tab
986
+ request_render()
987
+
988
+ eval_result = getattr(target, '_eval_result', None)
989
+ if eval_result:
990
+ # Titled with the snippet that produced it (stamped beside the result
991
+ # by the wrapper's eval hook), so a finished run is visible at a
992
+ # glance; no tree collapser - the result is always open.
993
+ result_title = getattr(target, '_eval_result_code', None) or "eval"
994
+ draw_text(eval_result, name=f"eval_result##{unique}",
995
+ display_name=result_title, is_tree=False,
996
+ show_bg=True, show_header=True,
997
+ show_name=True, editable=False, height=400, min_height=400,
998
+ wrap=True, bg_offset=-100, width=draw_state.content_width,
999
+ tint=(0.0, 0.0, 0.0))
1000
+ return False, input_value
1001
+
1002
+
1003
+ @render_func(use_cache=False, show_bg=False, show_header=False, show_name=False, selectable=False, disable_scroll=False,
1004
+ temp=True, searchable=True)
1005
+ def draw_input_tab(input_value, cm_state: ContextMenuState, draw_state, wrap=True, unique=None, class_to_show=None,
1006
+ enter_key_pressed=None, **kwargs):
1007
+ """The three editable sources behind this view, in dispatch order:
1008
+
1009
+ 1. RENDER FUNCTION — the render_func whose body produced the view, edited
1010
+ whole via FunctionCodec. It isn't on the captured stack (the capture runs
1011
+ in the wrapper BEFORE the body executes, so the innermost frame is the
1012
+ filtered wrapper), so it's read from `_view_func`.
1013
+ 2. CALLER — the direct `draw_x(...)` call site that invoked this view, edited
1014
+ via CallerCodec (spans just the call expression). `_call_site` is the
1015
+ nearest real caller (filename, lineno), captured once on menu-open with the
1016
+ render-dispatch machinery already filtered out (caller_site, core_render).
1017
+ 3. DECORATIONS — the `@...` block on the value's class, edited via
1018
+ DecorationsCodec. `class_to_show` is resolved by draw_context_menu (the
1019
+ value's own class, or the nearest parent with source for a primitive
1020
+ field); only classes carry decorations, so it's skipped otherwise.
1021
+ 4. MODE — the ACTIVE mode's entry kwargs in its enum class source
1022
+ (mode.py), edited via ModeCodec. Which member to show comes from the
1023
+ target's _kwargs ('current_mode', stamped by the wrapper when a mode
1024
+ config matched); skipped when no mode drove this view."""
1025
+ from meltygui.code.new_converters import host_code_state
1026
+ from meltygui.code.new_converters import recompile_button
1027
+ from meltygui.code.new_converters import recompile_status
1028
+ from meltygui.code.new_converters import run_recompile
1029
+ from meltygui.core.rendering.parameter_core import _source_priority
1030
+ from meltygui.core.rendering.render_dispatch import apply_param_source_matrix
1031
+ from meltygui.core.rendering.render_dispatch import collect_input_sources
1032
+ from meltygui.core.rendering.render_dispatch import param_source_matrix
1033
+ from meltygui.core.rendering.render_dispatch import signature_param_names
1034
+
1035
+
1036
+ srcs = collect_input_sources(input_value, cm_state, class_to_show)
1037
+ sources, source_tints = srcs["sources"], srcs["tints"]
1038
+ source_locations, source_kinds = srcs["locations"], srcs["kinds"]
1039
+ writable_sources = srcs["writable"]
1040
+
1041
+ # ── Recompile (hotswap) - the same Run path code_file_io draws on a file
1042
+ # leaf (folder_files / FILE_TREE). A matrix edit saves SOURCE to disk via
1043
+ # the hosts' chain_out, but the live view function keeps its old defaults
1044
+ # until a hotswap. The button/Run work against the view_host's own
1045
+ # CodeState, the Run compiles the host's live root (the edit already
1046
+ # merged in), not a possibly-stale disk copy. None until the lazy host has
1047
+ # drawn/updated - the button appears a beat after the menu opens.
1048
+ code_state = host_code_state(cm_state.render_func_str)
1049
+ if code_state is not None and code_state.address is not None:
1050
+ clicked = recompile_button(code_state, unique=unique)
1051
+ recompile_status(code_state, draw_state)
1052
+ # Alt+Enter (or the editor's usual Ctrl+Enter) while over the tab -
1053
+ # enter_key_pressed is the menu-subscribed Enter-down InputEvent, same
1054
+ # mechanism as code_file_io's hotkey; modifiers ride on the event.
1055
+ hotkey = bool(enter_key_pressed and (enter_key_pressed.alt or enter_key_pressed.ctrl))
1056
+ run_recompile(input_value._view_func, code_state, draw_state,
1057
+ start=clicked or hotkey, name=f"recompile{unique}")
1058
+
1059
+ # ── Search - the STANDARD searchable path, no special filter box. The tab
1060
+ # is searchable=True, so Ctrl+F on it opens the framework's floating
1061
+ # find bar (core_render's searchable block), which maintains the term on
1062
+ # this draw_state.search_text and invalidates this subtree per keystroke.
1063
+ # That same text hits draw_param_matrix's live param filter below (which
1064
+ # auto-switches the screen to the best match), and the search session on
1065
+ # Melty.search_stack reaches the matrix cells' strings for in-place
1066
+ # highlighting like any other searchable widget.
1067
+
1068
+ from meltygui.core.rendering.parameter_core import anywhere_value
1069
+ anywhere_value("view_func", input_value)
1070
+ if sources:
1071
+ _, matrix = param_source_matrix(sources, func=input_value._view_func,
1072
+ include_unmatched=True)
1073
+ changed, value = draw_param_matrix(matrix, source_tints=source_tints,
1074
+ source_locations=source_locations,
1075
+ # Rows in SourceKind order, highest
1076
+ # (the first candidates) on top;
1077
+ # registration order breaks ties
1078
+ # (sorted is stable).
1079
+ source_order=tuple(sorted(
1080
+ sources,
1081
+ key=lambda n: _source_priority(
1082
+ source_kinds.get(n)))),
1083
+ source_dicts=sources,
1084
+ writable_sources=tuple(writable_sources),
1085
+ source_kinds=source_kinds,
1086
+ view_draw_state=input_value, wrap=True,
1087
+ width=draw_state.content_width - 0,
1088
+ priority_params=tuple(signature_param_names(input_value._view_func)),
1089
+ search_text=str(draw_state.search_text or ""),
1090
+ name=f"{input_value._view_func.__name__} inputs##matrix{unique}",
1091
+ disable_scroll=True)
1092
+ if changed and isinstance(value, dict):
1093
+ # Pure write-back (no UI) - call the bare function, not this wrapper.
1094
+ apply_param_source_matrix.__wrapped__(value, ref=sources, changed=True)
1095
+
1096
+ # Each *_dict RenderHost parses its source on a background worker in its OWN draw
1097
+ # loop; this tab merely READS the materialized value (h.deep....) and draws it. When
1098
+ # a parse lands, the host's after_render() runs the loop but can't reach this
1099
+ # cached subtree - so register this tab's draw_state as a listener and the host
1100
+ # invalidates us when its value changes. Replaces the old "invalidate for the first
1101
+ # 10ms" guess, which expired before the ~400ms chain_in debounce, leaving the
1102
+ # dict blank until a manual mouse-over.
1103
+ caller_dict_hosts = [dh for (_sh, dh) in cm_state.call_site_hosts]
1104
+ for _h in (cm_state.render_func_dict, cm_state.decoration_dict, cm_state.class_dict,
1105
+ *caller_dict_hosts, cm_state.mode_dict):
1106
+ if _h is not None:
1107
+ _h.notify_on_change(draw_state)
1108
+
1109
+ # common = dict(mode=Modes.NEW_CODE, min_width=100, max_height=300, fill_height=False)
1110
+ #
1111
+ # view_func = getattr(input_value, "_view_func", None)
1112
+ # if inspect.isfunction(view_func):
1113
+ # draw_any(view_func, name=f"{view_func.__name__} (self)##self_{unique}", **common)
1114
+
1115
+ # call_site = getattr(input_value, "_call_site", None)
1116
+ # if call_site is not None:
1117
+ # filename, lineno = call_site
1118
+ # draw_any(CallSite(filename, lineno), name=f"caller:{lineno}##caller_{unique}", **common)
1119
+ #
1120
+ # if isinstance(class_to_show, type):
1121
+ # draw_any(Decorations(class_to_show),
1122
+ # name=f"{class_to_show.__name__} decorations##deco_{unique}", **common)
1123
+
1124
+ return False, input_value
1125
+
1126
+
1127
+ @render_func(use_cache=True, show_bg=False, show_header=False, disable_scroll=False, show_name=False, selectable=False)
1128
+ def draw_class_tab(input_value, class_to_show=None, class_is_parent=False, class_name='', **kwargs):
1129
+ """Editable class source. For a primitive field this is the parent object's
1130
+ class (e.g. Lora for a Lora.rank float) -- labelled so the source is clear.
1131
+ Routes through Mode.FILE_TREE — the same cache-backed code_file_io path a
1132
+ folder-files leaf uses — so all editors share one code path."""
1133
+ from meltygui.view.control_view import text
1134
+ from meltygui.core.rendering.render_dispatch import draw_any
1135
+
1136
+ from meltygui.core.rendering.mode import Mode
1137
+ if class_is_parent:
1138
+ text(f"Parent type of {class_name}", name="Source",
1139
+ editable=False, tint=(1.0, 0.64, 0.113))
1140
+ cls_change, new_cls = draw_any(class_to_show, mode=Mode.FILE_TREE,
1141
+ name=class_to_show.__name__)
1142
+ return False, input_value
1143
+
1144
+
1145
+ @render_func(use_cache=True, show_bg=False, show_header=False, show_name=False, selectable=False)
1146
+ def draw_mode_tab(input_value, draw_state, current_mode=None, **kwargs):
1147
+ """Show the current Mode's value string."""
1148
+ from meltygui.view.control_view import text
1149
+
1150
+ if current_mode is not None:
1151
+ mode_change, new_mode = text(str(current_mode.value), width=draw_state.content_width,
1152
+ name=str(current_mode))
1153
+ return False, input_value
1154
+
1155
+
1156
+ def draw_context_menu_items(draw_state, items, right_click, name, unique):
1157
+ """The `context_menu={label: callable}` popover of a view: the dropdown's
1158
+ own menu (draw_dd_menu — rows, hover, keys, click-away) opened AT THE
1159
+ POINTER by a right-click instead of under a trigger button. Called by
1160
+ the wrapper (core_render's context-menu block) on every body run of the
1161
+ view, `right_click` = a right-click landed on it this run.
1162
+
1163
+ Open/closed is the dropdowns' slot: the VIEW holds Melty.popover_focused_ds
1164
+ while its menu is open, exactly as a dropdown trigger does, so a click
1165
+ anywhere outside the menu hands the slot on (the wrapper's click-away
1166
+ clear_focus, which also re-runs this view) and the next call draws the
1167
+ menu closed. The menu is a latching window: it is called EVERY run with
1168
+ `closed=` so its window re-registers on each open (a window drawn only
1169
+ while its draw_state is already closed never registers — the reopen
1170
+ that died 09-10).
1171
+
1172
+ A picked row runs its callable here and closes the menu; the Inspect row
1173
+ at the bottom reports INSPECT so the wrapper opens the inspector in the
1174
+ menu's place. Returns INSPECT, the picked label, or None."""
1175
+ from meltygui.core.windowing.glfw_utils import request_render
1176
+ from meltygui.view.dropdown_view import draw_dd_menu
1177
+ from meltygui.core.rendering.render_dispatch import INSPECT
1178
+ from meltygui.model.dropdown_model import _dd_as_tuple
1179
+ from meltygui.core.layout.dropdown_core import _dd_close
1180
+ from meltygui.model.dropdown_model import _dd_label_for_path
1181
+ from meltygui.core.layout.dropdown_core import _dd_update_menu_size
1182
+ from meltygui.core.layout.dropdown_core import _ds_in_subtree
1183
+ import meltygui.core.windowing.window_api as glfw
1184
+
1185
+ from meltygui.state.new_core_model import ContextMenuItemsState
1186
+ # [tint=(0.85, 0.75, 0.05)]
1187
+ inspect_label = f" Inspect"
1188
+ inspect_tint = (0.42, 0.24, 0.06)
1189
+ state_key = "context_menu_items_state"
1190
+
1191
+ # The menu's state lives in the view's misc, where injected states go,
1192
+ # so it persists with the draw_state (DropDownState: cursor/open paths,
1193
+ # the drag-resized menu_size) and carries where the menu opened.
1194
+ state = draw_state.misc.get(state_key)
1195
+ if not isinstance(state, ContextMenuItemsState):
1196
+ state = draw_state.misc[state_key] = ContextMenuItemsState()
1197
+ draw_state.misc_used.add(state_key)
1198
+
1199
+ is_open = Melty.popover_focused_ds is draw_state
1200
+ if right_click:
1201
+ if is_open:
1202
+ Melty.popover_focused_ds = None
1203
+ _dd_close(state)
1204
+ else:
1205
+ Melty.popover_focused_ds = draw_state
1206
+ Melty._popover_open_frame = Melty.frame_count # grace the opening click
1207
+ mouse_x, mouse_y = imgui.get_mouse_pos()
1208
+ state.open_at = (mouse_x - draw_state.abs_left, mouse_y - draw_state.abs_top)
1209
+ _dd_close(state)
1210
+ state._kbd_mode = False
1211
+ state._last_mouse = None
1212
+ is_open = not is_open
1213
+ draw_state.invalidate()
1214
+ request_render()
1215
+
1216
+ collection = {str(label): action for label, action in items.items()}
1217
+ collection[inspect_label] = INSPECT
1218
+
1219
+ # Popover size: content-fit until the user drag-resizes it - the same
1220
+ # mechanism as draw_dropdown (see its popover-size comment): a forced
1221
+ # size goes on before the window begins, else a fit is stamped after
1222
+ # every open frame and a size that differs is the resize handle's work.
1223
+ menu_ds = state._menu_ds
1224
+ menu_size = state.menu_size
1225
+ if is_open and menu_ds is not None:
1226
+ opening = getattr(Melty, "_popover_open_frame", None) == Melty.frame_count
1227
+ if opening and menu_size is not None:
1228
+ menu_ds.width, menu_ds.height = menu_size
1229
+ state._menu_fit = tuple(menu_size)
1230
+ if menu_ds.width is None or menu_ds.width < 5:
1231
+ menu_ds.width = Toggles.Dropdown.min_width
1232
+ # The menu's top-left sits this far RIGHT of the pointer, so the pointer
1233
+ # rests on the first row rather than on the popover's left edge (the
1234
+ # wrapper's resize handle) — Lukas 09-10.
1235
+ # [tint=(0.939, 0.453, 0.245)]
1236
+ pointer_inset_x = 8
1237
+ # Below the pointer unless that runs past the display edge - then above it.
1238
+ open_x, open_y = state.open_at
1239
+ open_x += pointer_inset_x
1240
+ if menu_ds is not None and menu_ds.height:
1241
+ display_h = imgui.get_io().display_size[1]
1242
+ if draw_state.abs_top + open_y + menu_ds.height > display_h - 10:
1243
+ open_y -= menu_ds.height
1244
+ # The pointer is the anchor through the CURSOR (draw_tuple_fast's picker
1245
+ # does the same), never through window_pos: the wrapper folds a nested
1246
+ # window's window_pos offset into its content measure, so an offset
1247
+ # menu grew by that offset every frame. Restored after — the wrapper
1248
+ # reads the cursor right after this block for the view's own header.
1249
+ # Row labels are Tint.dd_text over the menu tint (its value × 2.16). A view
1250
+ # sitting in a dark-tinted window hands over a dark tint and the labels
1251
+ # came out dim against the popover (Lukas 09-10), so the tint's value is
1252
+ # floored here — raise the floor for brighter labels. The popover bg is
1253
+ # darkened by depth from the same tint and barely moves with it.
1254
+ # [tint=(0.994, 0.872, 0.0)]
1255
+ menu_tint_value_floor = 0.5
1256
+ menu_tint = draw_state.tint
1257
+ if menu_tint is not None:
1258
+ hue, saturation, value = rgb_to_hsv(*menu_tint[:3])
1259
+ if value < menu_tint_value_floor:
1260
+ menu_tint = hsv_to_rgb(hue, saturation, menu_tint_value_floor)
1261
+ cursor = imgui.get_cursor_screen_pos()
1262
+ imgui.set_cursor_screen_pos((draw_state.abs_left + open_x, draw_state.abs_top + open_y))
1263
+ changed, picked, menu_ds = draw_dd_menu(
1264
+ collection, tint=menu_tint,
1265
+ name=f"{name}##context_menu_items_{unique}",
1266
+ closed=not is_open, temp=True, shadow=False, auto_resize=False,
1267
+ window_pos=(0, 0), max_height=Toggles.Dropdown.max_height,
1268
+ parent_window=draw_state, swoosh=False, disable_scroll=False,
1269
+ show_search=False, row_tints={INSPECT: inspect_tint},
1270
+ root_state=state, path_prefix=(), return_extras=True)
1271
+ imgui.set_cursor_screen_pos(cursor)
1272
+ state._menu_ds = menu_ds
1273
+ if not is_open or menu_ds is None:
1274
+ return None
1275
+
1276
+ _dd_update_menu_size(state, menu_ds)
1277
+
1278
+ def _dismiss():
1279
+ Melty.popover_focused_ds = None
1280
+ _dd_close(state)
1281
+ draw_state.invalidate()
1282
+ request_render()
1283
+
1284
+ if changed:
1285
+ label = _dd_label_for_path(collection, _dd_as_tuple(state._picked_path))
1286
+ _dismiss()
1287
+ if picked is INSPECT:
1288
+ return INSPECT
1289
+ if callable(picked):
1290
+ picked()
1291
+ return label
1292
+
1293
+ if any(k == glfw.KEY_ESCAPE for k, _ in Core.melty.frame_key_events):
1294
+ _dismiss()
1295
+ return None
1296
+
1297
+ # Click-outside dismissal - the MENU is the inside (a click back to the
1298
+ # view header closes it too, as a native context menu does).
1299
+ if imgui.is_mouse_clicked(0):
1300
+ mouse_x, mouse_y = imgui.get_mouse_pos()
1301
+ under = Core.melty.bvh_query(mouse_x, mouse_y)
1302
+ if not any(_ds_in_subtree(ds, menu_ds) for ds in under):
1303
+ _dismiss()
1304
+ return None
1305
+
1306
+
1307
+ @render_func(use_cache=False, disable_scroll=True, show_header=False,
1308
+ header_same_line=False, show_tint=False, show_name=False, is_tree=False)
1309
+ def draw_context_menu(input_value, draw_state, cursor_hover_inverted, func, unique=None, search_text='',
1310
+ search_active=False,
1311
+ enter_key_down=None, tab_state: TabState = None,
1312
+ menu_state: ContextMenuWindowState = None, **kwargs):
1313
+ from meltygui.core.conversion.cache_tree import UNSET_VALUE
1314
+ from meltygui.core.windowing.glfw_utils import request_render
1315
+ from meltygui.view.color_view import draw_tint_context
1316
+ from meltygui.view.header_view import flat_button
1317
+ from meltygui.view.tab_view import draw_tab_bar
1318
+ from meltygui.core.rendering.render_dispatch import _ancestor_call_line
1319
+ from meltygui.core.rendering.render_dispatch import _deferred_ancestors
1320
+ from meltygui.core.rendering.render_dispatch import _merged_call_stack_frames
1321
+ from meltygui.core.rendering.render_dispatch import _request_deferred_stacks
1322
+
1323
+ if input_value is None:
1324
+ return False, None
1325
+ context_menu_offset = input_value.context_menu_offset
1326
+ # imgui.text(type(input_value._input_value).__name__)
1327
+ imgui.set_cursor_screen_pos((imgui.get_cursor_screen_pos()[0] - 1, imgui.get_cursor_screen_pos()[1] - 18))
1328
+ # if up_key_pressed:
1329
+ # print("Up key pressed")g
1330
+ fa_up_arrow = ""
1331
+ fa_down_arrow = ""
1332
+ # The toolbar buttons are flat_buttons (draw-list, no @render_func
1333
+ # wrapper) styled to match `button` exactly: same text_value /
1334
+ # text_saturation / hover boosts, and the shadow lift = button's
1335
+ # z_offset 3 + the wrapper's shadow +1. The fill hue comes from the
1336
+ # tint (button's factor=1.0 makes `color` moot), so each call pushes the
1337
+ # tint the old button carried — the decorator's blue for the arrows.
1338
+ # [tint=(0.0, 0.241, 0.556)]
1339
+ button_default_tint = (0.0, 0.241, 0.556)
1340
+
1341
+ def _menu_button(label, view_id, height, tint=button_default_tint):
1342
+ style_manager = Melty.style_manager
1343
+ previous = style_manager.push_tint_fields(*tint[:4])
1344
+ try:
1345
+ return flat_button(label, draw_state, view_id=view_id, height=height,
1346
+ text_value=0.694, text_saturation=1.2,
1347
+ hover_text_boost=1.5, shadow_offset=4.0,
1348
+ event="left_mouse_down", style_manager=style_manager)
1349
+ finally:
1350
+ style_manager.pop_tint_fields(previous)
1351
+
1352
+ if input_value._parent.id is not None:
1353
+ if _menu_button(fa_up_arrow, f"ctx_menu_up##{unique}", height=50):
1354
+ input_value.context_menu_offset += 1
1355
+ # Nav generation; rides into the func tab's select_line guard so
1356
+ # EVERY arrow press re-applies the auto-selection, even when
1357
+ # returning to a level whose line was selected before (the previous
1358
+ # ds persists, and its one-shot marker would otherwise skip it).
1359
+ input_value._scope_nav_seq = getattr(input_value, "_scope_nav_seq", 0) + 1
1360
+ Core.melty.cache.invalidate_up(draw_state._tile_id, max_depth=5)
1361
+ Core.melty.cache.invalidate_up(input_value._tile_id, max_depth=5)
1362
+
1363
+ imgui.same_line()
1364
+ if input_value.context_menu_offset > 0:
1365
+ if _menu_button(fa_down_arrow, f"ctx_menu_down##{unique}", height=50):
1366
+ input_value.context_menu_offset = max(0, input_value.context_menu_offset - 1)
1367
+ input_value._scope_nav_seq = getattr(input_value, "_scope_nav_seq", 0) + 1
1368
+ Core.melty.cache.invalidate_up(draw_state._tile_id, max_depth=5)
1369
+ Core.melty.cache.invalidate_up(input_value._tile_id, max_depth=5)
1370
+ else:
1371
+ imgui.dummy(30, 30)
1372
+
1373
+ imgui.same_line()
1374
+ imgui.text_colored(f"{context_menu_offset}", 1, 1, 1, 0.3)
1375
+ imgui.same_line()
1376
+
1377
+ # Screenshot this menu's parent view, top of the menu below the nav arrows.
1378
+ # Deferred so the menu isn't in the shot: front the owning window (so the
1379
+ # view is visible), queue the view capture, hide this menu (asking to reopen
1380
+ # it afterward), then let screenshot.process_take_screenshot_flags grab the
1381
+ # view's shot a few frames later, open the shot in nemo, and reopen the menu.
1382
+ if _menu_button(f" ", f"screenshot_window##{unique}", height=30, tint=(0, 0, 0, 1.0)):
1383
+ from meltygui.core.graphics.screenshot import request_view_capture
1384
+
1385
+ def _open_in_nemo(shot_path):
1386
+ # Full path + close_fds=False => posix_spawn, not fork (forking this
1387
+ # CUDA/GL process stalls the render thread).
1388
+ import shutil, subprocess
1389
+ nemo = shutil.which("nemo") or "/usr/bin/nemo"
1390
+ subprocess.Popen([nemo, shot_path], close_fds=False)
1391
+
1392
+ view_ds = input_value # the view this menu is for (offset-walked)
1393
+ Core.melty.move_window_to_front(view_ds.root_window)
1394
+ draw_state._reopen = True
1395
+ request_view_capture(view_ds, Core.melty.frame_count, reopen_menu_ds=draw_state,
1396
+ on_captured=_open_in_nemo)
1397
+ draw_state.closed = True
1398
+ request_render()
1399
+
1400
+ imgui.same_line()
1401
+
1402
+ # Same deferred screenshot, then hand it to Claude: once the shot lands,
1403
+ # boot a fresh claude-d session pre-typed (NOT sent) with the shot path +
1404
+ # the view's render function (the same function the Input tab edits), and
1405
+ # open the Claude Terminals window so the new session's terminal comes up.
1406
+ if _menu_button(f" claude", f"claude_session##{unique}", height=30, tint=(0, 0, 0, 1.0)):
1407
+ from meltygui.core.graphics.screenshot import request_view_capture
1408
+ view_ds = input_value
1409
+ # Resolve the menu's offset-walked target so the shot + function match
1410
+ # what the tabs will show (the walk proper happens above the buttons).
1411
+ for _ in range(context_menu_offset):
1412
+ if view_ds._parent is None or view_ds._parent is view_ds:
1413
+ break
1414
+ view_ds = view_ds._parent
1415
+ fn = inspect.unwrap(view_ds._view_func)
1416
+ fn_name = getattr(fn, "__name__", "?")
1417
+ try:
1418
+ fn_file = inspect.getsourcefile(fn)
1419
+ except Exception:
1420
+ fn_file = None
1421
+ fn_line = getattr(getattr(fn, "__code__", None), "co_firstlineno", None)
1422
+ loc = f"{fn_file}:{fn_line}" if fn_file else "unknown location"
1423
+
1424
+ def _start_claude(shot_path, _name=fn_name, _loc=loc):
1425
+ from meltygui.core.services.claude_terminal_core import launch_claude_session
1426
+ from meltygui.core.services.claude_terminal_core import open_claude_terminals_window
1427
+ launch_claude_session(
1428
+ f"Take a look at this screenshot of a view in the studio: {shot_path} "
1429
+ f"It is rendered by the function `{_name}` in {_loc}. ")
1430
+ open_claude_terminals_window()
1431
+
1432
+ Core.melty.move_window_to_front(view_ds.root_window)
1433
+ draw_state._reopen = True
1434
+ request_view_capture(view_ds, Core.melty.frame_count, reopen_menu_ds=draw_state,
1435
+ on_captured=_start_claude)
1436
+ draw_state.closed = True
1437
+ request_render()
1438
+
1439
+ imgui.same_line()
1440
+
1441
+ # Recapture the caller trace: the stack - and everything riding it
1442
+ # (caller f_locals types + stack-snapshot live values, the target's
1443
+ # in-scope publish, the one-shot body-locals capture) - is grabbed
1444
+ # ONCE per session and kept until restart. This button-arms the one-shot
1445
+ # gates so the target's next inline render captures fresh, and
1446
+ # invalidates up so the ancestors actually re-render: a cache-replayed
1447
+ # target renders without its parents on the stack, which would capture
1448
+ # a chain that bottoms out in dispatch machinery.
1449
+ bug_icon = "" # fa-bug - red as a debug affordance
1450
+ if _menu_button(f"{bug_icon}", f"recapture_trace##{unique}", height=30,
1451
+ tint=(0.42, 0.24, 0.06, 1.0)):
1452
+ _rc_target = input_value
1453
+ for _ in range(context_menu_offset):
1454
+ if _rc_target._parent is None or _rc_target._parent is _rc_target:
1455
+ break
1456
+ _rc_target = _rc_target._parent
1457
+ _rc_target._call_site_captured = False
1458
+ _rc_target._call_site_requested = True
1459
+ # Every deferred layer on the chain recaptures its queue-time stack
1460
+ # too, so the Code tab's splice is built from fresh parts.
1461
+ for _deferred in _deferred_ancestors(_rc_target):
1462
+ _deferred._deferred_call_stack_frames = None
1463
+ _deferred._deferred_stack_requested = True
1464
+ Core.melty.cache.invalidate_up(_deferred._tile_id, max_depth=5)
1465
+ Core.melty.cache.invalidate_up(_rc_target._tile_id, max_depth=5)
1466
+ request_render()
1467
+
1468
+ imgui.same_line()
1469
+
1470
+ offset_ds = input_value
1471
+ for i in range(context_menu_offset):
1472
+ if offset_ds._parent is None:
1473
+ break
1474
+ offset_ds = offset_ds._parent
1475
+
1476
+ # The menu walked up to an ancestor (offset > 0). That ancestor never had its
1477
+ # OWN context menu open, so the context_menu_open capture gate never ran for
1478
+ # it and its _call_site is None — caller lenses come up empty. Ask its next
1479
+ # inline render to capture the site (lazy, one-shot), and invalidate it so it
1480
+ # re-renders fresh rather than from cache (where the capture line is skipped).
1481
+ if (offset_ds is not input_value and not offset_ds._call_site_captured
1482
+ and not offset_ds._call_site_requested):
1483
+ offset_ds._call_site_requested = True
1484
+ if Core.melty.cache is not None:
1485
+ Core.melty.cache.invalidate_up(offset_ds._tile_id, max_depth=5)
1486
+ request_render()
1487
+ # Same lazy one-shot for the queue-time stacks of every deferred layers
1488
+ # above the target: the Code tab splices them onto the target's stack.
1489
+ _request_deferred_stacks(input_value)
1490
+
1491
+ # Scope-up auto-select: when the menu is walked up to an ancestor, resolve
1492
+ # the line inside the ancestor's view function that drew the ORIGINAL
1493
+ # element (from the original target's cached _call_stack, available
1494
+ # immediately, no waiting on the ancestor's lazy capture above), and have
1495
+ # the func tab select it.
1496
+ func_tab_select_line = None
1497
+ if offset_ds is not input_value:
1498
+ func_tab_select_line = _ancestor_call_line(input_value, offset_ds)
1499
+ # Arrow-press generation: part of the selection's one-shot key, so each
1500
+ # press re-selects but keeping the resolved line (and editor tab) changes.
1501
+ func_tab_select_seq = getattr(input_value, "_scope_nav_seq", 0)
1502
+
1503
+ input_value._offset_ds = offset_ds
1504
+ input_value = offset_ds
1505
+
1506
+ # Font awesome info icon unicode: \uf05a
1507
+ gear_icon = f"\uf013"
1508
+ config_icon_fa = f"{gear_icon} Config"
1509
+ info_icon_fa = " Info"
1510
+ view_func_name = offset_ds._view_func.__name__
1511
+ class_name = type(input_value._raw_input_value).__name__
1512
+ paint_brush_icon = f"\uf1fc"
1513
+ tint_tab_name = f"{paint_brush_icon} Tint"
1514
+ terminal_icon = f"\uf120" # fa-terminal
1515
+ eval_tab_name = f"{terminal_icon} Eval"
1516
+ keyboard_icon = f"\uf11c" # fa-keyboard
1517
+ input_tab_name = f"{keyboard_icon} Input"
1518
+ # Lightning bolt
1519
+ live_icon = f"\uf0e7"
1520
+ live_tab = f"{live_icon} Live"
1521
+ code_stack_icon = f"\uf121"
1522
+ code_stack_tab = f"{code_stack_icon} Code"
1523
+
1524
+ # Resolve which class's source to show in the class tab.
1525
+ # For a non-primitive value that's just the value's own class. For a
1526
+ # primitive field (e.g. a float `rank`) the value itself has no source,
1527
+ # so walk up the parent chain to the nearest object that does have source
1528
+ # code -- so e.g. Lora.rank still shows Lora's class, labelled as parent.
1529
+ raw_value = input_value._raw_input_value
1530
+ class_to_show = None
1531
+ class_is_parent = False
1532
+ # A bubbling tree node's type is a runtime-generated `Bubbling_<Base>` with no source
1533
+ # — resolve its real base (e.g. GeneralParse) so the Class/Decorations tabs resolve
1534
+ # rather than erroring.
1535
+ from meltygui.core.conversion.bubbling import base_of_bubbling
1536
+ # Use exact-type matching, not isinstance: a subclass of a primitive
1537
+ # (e.g. CodeLine(str)) DOES have its own source, so it should show its
1538
+ # own class tab rather than being treated as a bare primitive.
1539
+ if isinstance(raw_value, type):
1540
+ # The view is a CLASS itself (e.g. the Toggles window draws the
1541
+ # Toggles class via @window). The class whose source to show is the
1542
+ # value, not its metaclass - type(Toggles) is `type`, a builtin with
1543
+ # no source, which would blank every right-side source row (class
1544
+ # var / @defaults / @window on the class).
1545
+ class_to_show = base_of_bubbling(raw_value)
1546
+ elif type(raw_value) not in (int, float, str, bool):
1547
+ class_to_show = base_of_bubbling(type(raw_value))
1548
+ else:
1549
+ max_walk = 4
1550
+ ancestor = input_value._parent
1551
+ while ancestor is not None and max_walk > 0:
1552
+ a_raw = getattr(ancestor, '_raw_input_value', UNSET_VALUE)
1553
+ a_type = base_of_bubbling(type(a_raw)) if a_raw is not UNSET_VALUE else None
1554
+ if a_type is not None and getattr(a_type, '__module__', None) \
1555
+ not in (None, 'builtins', '_collections_abc'):
1556
+ class_to_show = a_type
1557
+ class_is_parent = True
1558
+ break
1559
+ ancestor = ancestor._parent
1560
+ max_walk -= 1
1561
+
1562
+ # Font awesome: fa-code () for the view function, fa-cube () for the class.
1563
+ func_tab = f" {view_func_name}"
1564
+ if class_to_show is not None:
1565
+ class_tab = f" {class_to_show.__name__}" + (" (parent)" if class_is_parent else "")
1566
+ else:
1567
+ class_tab = f" {class_name}"
1568
+
1569
+ # Static tint colors for the fixed Config / Info tabs; remaining tabs use the neutral grey.
1570
+ config_tint = (0.12, 0.38, 0.772)
1571
+ info_tint = (0.545, 0.469, 0.012)
1572
+
1573
+ tab_names = []
1574
+ tab_tints = []
1575
+ tab_names.append(info_icon_fa)
1576
+ tab_tints.append(info_tint)
1577
+ tab_names.append(config_icon_fa)
1578
+ tab_tints.append(config_tint)
1579
+ tab_names.append(func_tab)
1580
+ tab_tints.append(None)
1581
+ tab_names.append(eval_tab_name)
1582
+ tab_tints.append((0.2, 0.7, 0.3)) # green for the eval/REPL tab
1583
+ tab_names.append(input_tab_name)
1584
+ tab_tints.append((0.4, 0.2, 0.7)) # purple for the input tab
1585
+ tab_names.append(tint_tab_name)
1586
+ tab_tints.append(Core.melty._saturated_rgb(draw_state.tint)) # orange tint for the tint tab
1587
+ if class_to_show is not None:
1588
+ tab_names.append(class_tab)
1589
+ tab_tints.append(None)
1590
+
1591
+ tab_names.append(live_tab)
1592
+ tab_tints.append((0.7, 0.0, 0.0))
1593
+ tab_names.append(code_stack_tab)
1594
+ tab_tints.append((0.9, 0.35, 0.28)) # the code trace view's own tint
1595
+
1596
+ indices = list(range(len(tab_names)))
1597
+
1598
+ if not tab_state.selected_tabs:
1599
+ tab_state.selected_tabs = [indices[Toggles.ContextMenu.default_tab]]
1600
+
1601
+ current_mode = input_value._kwargs.get('mode', None)
1602
+ mode_tab = str(current_mode)
1603
+ if current_mode is not None:
1604
+ tab_names.append(mode_tab)
1605
+
1606
+ # Indices list
1607
+
1608
+ imgui.dummy(0, 1)
1609
+ imgui.same_line()
1610
+ tab_changed, new_tabs = draw_tab_bar(tab_state.selected_tabs, names=tab_names, wrap=True, tab_height=40,
1611
+ tint_value=0.7,
1612
+ # 282 = nav arrows + counter + shot +
1613
+ # claude; +46 for the + (trace
1614
+ # recapture) button.
1615
+ width=max(50, draw_state.content_width - 328),
1616
+ show_bg=True, name=f"tab_bar#{view_func_name}{unique}",
1617
+ z_offset=-0.5, bg_offset=-7, draw=True,
1618
+ collection=indices, tints=tab_tints, as_toggles=False)
1619
+ if tab_changed:
1620
+ tab_state.selected_tabs = new_tabs
1621
+ # The compact input tab's initial fit leaves no room for source rows.
1622
+ # Give Inputs a usable viewport when selecting it from that small fit.
1623
+ if tab_names.index(input_tab_name) in new_tabs:
1624
+ minimum_height = min(700, int(imgui.get_io().display_size.y * 0.8))
1625
+ if (draw_state.height or 0) < minimum_height:
1626
+ draw_state.height = minimum_height
1627
+ draw_state.invalidate()
1628
+
1629
+ imgui.dummy(0, 2)
1630
+
1631
+ # Columns stripped: render the selected tab(s) stacked at the menu's full
1632
+ # width - one all across the menu, several fill the height. No Column calls,
1633
+ # so nothing here triggers the window-frame edge system (the source of the
1634
+ n_sel = max(1, len(tab_state.selected_tabs))
1635
+ full_w = draw_state.content_width
1636
+ clip = draw_state.abs_clip_rect
1637
+ top_y = imgui.get_cursor_screen_pos()[1]
1638
+ # First-load auto-fit (same pattern as draw_global_search's height
1639
+ # auto-fit): until the menu has been fitted once, hand the tab bodies NO
1640
+ # height so they render at their natural extent - the usual tab_h derives
1641
+ # from the current window clip rect which is circular while we're still
1642
+ # choosing the window height. fit_done PERSISTS with the menu's
1643
+ # draw_state (ContextMenuWindowState): a menu restored open at boot
1644
+ # already had its size, and re-fitting it overwrote that size.
1645
+ fitting = not menu_state.fit_done
1646
+ tab_h = max(60.0, (clip[3] - top_y) / n_sel - 6) if (clip is not None and not fitting) else None
1647
+
1648
+ for t_idx, static_tab in enumerate(tab_state.selected_tabs):
1649
+ size_kw = {"width": full_w}
1650
+ if tab_h is not None:
1651
+ size_kw["height"] = tab_h
1652
+ if static_tab >= len(tab_names):
1653
+ # A persisted selection that outran the current tab set: fall back
1654
+ # to the func tab rather than indexing out of range.
1655
+ draw_func_tab(input_value, name=f"func_tab_{t_idx}##{unique}",
1656
+ disable_scroll=True, select_line=func_tab_select_line,
1657
+ select_seq=func_tab_select_seq, **size_kw)
1658
+ continue
1659
+ this_tab = tab_names[static_tab]
1660
+ if this_tab == tint_tab_name:
1661
+ draw_tint_context(input_value, name=f"Context Tint##{unique}", **size_kw)
1662
+
1663
+ elif this_tab == info_icon_fa:
1664
+ # Resolve the effective search term (menu kwarg, else its search box).
1665
+ info_search = search_text if search_text != "" else draw_state.search_text
1666
+ draw_info_tab(input_value, search_text=info_search, unique=unique,
1667
+ name=f"info_tab_{t_idx}##{unique}", **size_kw)
1668
+
1669
+ elif this_tab == config_icon_fa:
1670
+ draw_config_tab(input_value, name=f"config_tab_{t_idx}##{unique}", **size_kw)
1671
+
1672
+ elif this_tab == func_tab:
1673
+ draw_func_tab(input_value, name=f"func_tab_{t_idx}##{unique}",
1674
+ disable_scroll=False, select_line=func_tab_select_line,
1675
+ select_seq=func_tab_select_seq, **size_kw)
1676
+
1677
+ elif this_tab == eval_tab_name:
1678
+ draw_eval_tab(input_value, unique=unique, enter_key_down=enter_key_down,
1679
+ menu_draw_state=draw_state,
1680
+ name=f"eval_tab_{t_idx}##{unique}", **size_kw)
1681
+
1682
+ elif this_tab == input_tab_name:
1683
+ draw_input_tab(input_value, class_to_show=class_to_show,
1684
+ name=f"input_tab_{t_idx}##{unique}", wrap=False,
1685
+ disable_scroll=False, **size_kw)
1686
+
1687
+
1688
+ elif this_tab == class_tab:
1689
+ draw_class_tab(input_value, class_to_show=class_to_show, class_is_parent=class_is_parent,
1690
+ class_name=class_name, name=f"class_tab_{t_idx}##{unique}", **size_kw)
1691
+
1692
+ elif this_tab == mode_tab:
1693
+ draw_mode_tab(input_value, current_mode=current_mode,
1694
+ name=f"mode_tab_{t_idx}##{unique}", **size_kw)
1695
+
1696
+ elif this_tab == live_tab:
1697
+ draw_live_tab(input_value, name=f"live_tab_{t_idx}##{unique}", **size_kw)
1698
+
1699
+ elif this_tab == code_stack_tab:
1700
+ # The stack captured at menu open (core_render's one-shot grab -
1701
+ # `_call_stack_frames`: (path, lineno, func_name, locals) tuples,
1702
+ # outermost first) with its deferred ancestor's queue-time
1703
+ # stack spliced in (_merged_call_stack_frames), rendered as a
1704
+ # stack trace view. Values come from the frames' captured locals
1705
+ # through pane-LOCAL stores - nothing published, nothing global.
1706
+ # The debug (bug) button above recaptures a fresh stack.
1707
+ from meltygui.view.trace_view import draw_stack_trace
1708
+ captured_stack = _merged_call_stack_frames(input_value, menu_state)
1709
+ if captured_stack:
1710
+ # indent_views=False: the tab is narrow - panes slide left
1711
+ # instead of the inlined call-chain slide.
1712
+ draw_stack_trace(
1713
+ captured_stack, indent_views=False,
1714
+ hide_dispatch=Toggles.ContextMenu.code_tab_hide_dispatch,
1715
+ name=f"code_tab_{t_idx}##{unique}", **size_kw)
1716
+ else:
1717
+ RenderFuncs.draw_text(
1718
+ "No captured stack for this view yet — press the bug "
1719
+ "button above to recapture the trace.",
1720
+ name=f"code_tab_empty_{t_idx}##{unique}", show_bg=False,
1721
+ editable=False, tint=Tint.subtle_text())
1722
+ imgui.set_cursor_screen_pos((imgui.get_cursor_screen_pos()[0] - 1, imgui.get_cursor_screen_pos()[1] - 18))
1723
+ imgui.text(f"{input_value._raw_input_value.__class__.__name__}")
1724
+
1725
+ imgui.dummy(0, 30)
1726
+
1727
+ # First-load fit, two phases. Phase 0: set the default width - width
1728
+ # drives wrap, so a height measure is only honest once the content has
1729
+ # rendered AT that width; invalidate unconditionally (use_cache would
1730
+ # otherwise replay the tile and skip phase 1). Phase 1: the tab bodies
1731
+ # just rendered at no height, so the cursor bottom IS the content
1732
+ # extent. Write the size immediately - the wrapper's measured item_rect
1733
+ # can't shrink a fixed-width window - cap at most of the display, and fit
1734
+ # ONCE per menu draw_state: the ds persists across open/close, so reopens
1735
+ # and tab switches keep the user's size.
1736
+ if fitting:
1737
+ if menu_state.fit_phase == 0:
1738
+ menu_state.fit_phase = 1
1739
+ if (draw_state.width or 0) < 520:
1740
+ draw_state.width = 520
1741
+ draw_state._source["width"] = "context menu first-load default width"
1742
+ draw_state.invalidate()
1743
+ request_render()
1744
+ else:
1745
+ content_bottom = imgui.get_cursor_screen_pos()[1]
1746
+ new_h = max(100, int(content_bottom - draw_state._abs_top() + 10))
1747
+ disp_h = imgui.get_io().display_size.y
1748
+ if disp_h > 0:
1749
+ new_h = min(new_h, int(disp_h * 0.8))
1750
+ if draw_state.height is None or abs(draw_state.height - new_h) > 1:
1751
+ draw_state.height = new_h
1752
+ draw_state._source["height"] = "context menu first-load auto-fit"
1753
+ draw_state.invalidate()
1754
+ request_render()
1755
+ menu_state.fit_done = True
1756
+
1757
+ # Never open the menu partially off-display. While the window offset is
1758
+ # still the fresh default reset (0,0) - core_render does that on every
1759
+ # right-click open - shift window_pos (the additive offset in the pinned
1760
+ # branch of _abs_left/_abs_top) so the whole window fits inside the
1761
+ # display. A user drag writes window_pos and ends this clamping; blit
1762
+ # placement follows abs pos anyway, so no invalidate is needed for a move.
1763
+ wp = draw_state.window_pos or (0, 0)
1764
+ if not fitting and tuple(wp) == (0, 0):
1765
+ disp = imgui.get_io().display_size
1766
+ x0, y0 = draw_state._abs_left(), draw_state._abs_top()
1767
+ w = draw_state.width or draw_state.content_width or 0
1768
+ h = draw_state.height or 0
1769
+ dx = dy = 0.0
1770
+ if disp.x > 0:
1771
+ if x0 + w > disp.x:
1772
+ dx = disp.x - (x0 + w)
1773
+ if x0 + dx < 0:
1774
+ dx = -x0
1775
+ if disp.y > 0:
1776
+ if y0 + h > disp.y:
1777
+ dy = disp.y - (y0 + h)
1778
+ if y0 + dy < 0:
1779
+ dy = -y0
1780
+ if abs(dx) > 0.5 or abs(dy) > 0.5:
1781
+ draw_state.window_pos = (wp[0] + dx, wp[1] + dy)
1782
+ request_render()
1783
+ return False, input_value