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 @@
1
+ """Shared framework conversion machinery."""
@@ -0,0 +1,599 @@
1
+ """
2
+ Bubbling proxies — make a nested mutation notify a root object.
3
+
4
+ A RenderHost only intercepts writes to its OWN keys. A mutation to a nested
5
+ container — `host['cfg']['rank'] = 16`, or editing a `GeneralParse` you grabbed a
6
+ reference to — runs through THAT child's methods, which the host never sees. So a
7
+ deep edit silently fails to mark the host dirty, and its file-IO / state handling
8
+ doesn't react.
9
+
10
+ `install_bubbling(value, root)` walks a value tree and upgrades every mutable
11
+ container so a mutation at ANY depth calls `root._mark_changed()`. With it, the
12
+ host reflects (and an editor like code_file_io saves) deep edits with no manual
13
+ signal — the stateful handling becomes invisible.
14
+
15
+ Two type-preserving upgrade paths:
16
+
17
+ • A dict / list SUBCLASS that carries a __dict__ (GeneralParse, Conditional, Loop,
18
+ CallParse, …) is upgraded IN PLACE via `__class__` reassignment to a generated
19
+ `(mixin, base)` subclass. Same object, same identity, same attributes, and
20
+ `isinstance(x, GeneralParse)` still holds — so a reference grabbed BEFORE
21
+ adoption bubbles too, and the converters / renderers (all isinstance/MRO based)
22
+ keep working.
23
+
24
+ • A PLAIN dict / list (no __dict__, so `__class__` can't be reassigned) is replaced
25
+ by a constructed bubbling copy, written back into its parent.
26
+
27
+ The framework resolves `@defaults` kwargs by EXACT type (not MRO — see
28
+ core_render.py's `default_kwargs_by_type[type(input_value)]`), so a generated
29
+ subclass would otherwise lose its base's tint / included / disable_scroll defaults.
30
+ Each generated subclass therefore mirrors the base's per-class registry entries.
31
+
32
+ Cycle- and idempotency-safe: re-installing an already-bubbling tree just re-points
33
+ the root and recurses into any fresh subtrees.
34
+ """
35
+
36
+ from collections import deque
37
+
38
+ from meltygui.core.melty import Melty
39
+
40
+
41
+ # ── Lazy deep attribute traversal ──────────────────────────────────────────────
42
+ #
43
+ # A converted value tree is full of redundant wrapper rungs:
44
+ #
45
+ # render_func_dict["value"]["draw_collection"]["decorators"]["render_func"]
46
+ #
47
+ # Only "decorators" and "render_func" carry meaning - "value" and "draw_collection"
48
+ # are bookkeeping the converter had to spell out and guard (`if "value" in d: ...`).
49
+ # The `.deep` accessor lets you name ONLY the rungs you care about and finds the rest:
50
+ #
51
+ # func_dict.deep.decorators.render_func() # == the line above, without guards
52
+ #
53
+ # It is reached via an EXPLICIT `.deep` property - NOT a blanket __getattr__ on
54
+ # the container. A blanket __getattr__ would answer EVERY missing-attribute probe the
55
+ # framework makes (`hasattr(d, 'children')`, `getattr(d, 'x', default)`, copy/pickle
56
+ # dunders) with a stand-in object instead of the AttributeError those call sites
57
+ # expect - so MISSING/proxy values leak everywhere. `.deep` is opt-in: normal
58
+ # attribute access on the container is still completely unaffected.
59
+ #
60
+ # `.deep.a.b` accumulates the path lazily; calling it (`()`) resolves the WHOLE path
61
+ # at once with backtracking, so a name that matches a dead-end branch (one lacking the
62
+ # next rung) doesn't terminate the lookup - it tries the next candidate. Resolution
63
+ # returns the real value (ready to hand to draw_collection), or MISSING.
64
+
65
+
66
+ class _Missing:
67
+ """The result of a deep lookup that found nothing.
68
+
69
+ A null object so a resolved chain is easy to test: it's falsy, iterates empty,
70
+ has len 0, and any further attr/index access returns itself. Underscore names
71
+ still raise AttributeError so it can't masquerade as having dunder/protocol
72
+ methods (copy/pickle/etc.)."""
73
+
74
+ __slots__ = ()
75
+
76
+ def __getattr__(self, name):
77
+ if name.startswith("_"):
78
+ raise AttributeError(name)
79
+ return self
80
+
81
+ def __getitem__(self, key):
82
+ return self
83
+
84
+ def __call__(self, *args, **kwargs):
85
+ return self
86
+
87
+ def __iter__(self):
88
+ return iter(())
89
+
90
+ def __len__(self):
91
+ return 0
92
+
93
+ def __bool__(self):
94
+ return False
95
+
96
+ def __contains__(self, item):
97
+ return False
98
+
99
+ def __repr__(self):
100
+ return "<missing>"
101
+
102
+
103
+ MISSING = _Missing()
104
+
105
+
106
+ def _iter_matches(node, name):
107
+ """Yield every value stored under key `name` at or below `node`, breadth-first
108
+ (shallowest first), descending through nested dicts/lists. Internal `__…` keys
109
+ are neither matched nor descended."""
110
+ queue = deque([node])
111
+ seen = set()
112
+ while queue:
113
+ cur = queue.popleft()
114
+ cid = id(cur)
115
+ if cid in seen:
116
+ continue
117
+ seen.add(cid)
118
+ if isinstance(cur, dict):
119
+ if name in cur and not _is_internal_key(name):
120
+ yield dict.__getitem__(cur, name)
121
+ for k, v in cur.items():
122
+ if not _is_internal_key(k) and isinstance(v, (dict, list)):
123
+ queue.append(v)
124
+ elif isinstance(cur, list):
125
+ for v in cur:
126
+ if isinstance(v, (dict, list)):
127
+ queue.append(v)
128
+
129
+
130
+ def _resolve_chain(node, names):
131
+ """Resolve `names` from `node` with backtracking. For each rung, try every place
132
+ the name matches (shallowest first); recurse for the rest, and only accept a
133
+ candidate whose subtree can satisfy the REMAINING rungs. So a repeated key on a
134
+ branch that lacks the next rung is skipped rather than dead-ending the lookup.
135
+ Returns the matched value, or MISSING when no full path exists."""
136
+ if not names:
137
+ return node
138
+ first, rest = names[0], names[1:]
139
+ for cand in _iter_matches(node, first):
140
+ result = _resolve_chain(cand, rest)
141
+ if result is not MISSING:
142
+ return result
143
+ return MISSING
144
+
145
+
146
+ def deep_get(container, *names):
147
+ """Resolve a path of `names` through `container`, skipping redundant wrapper rungs
148
+ and backtracking past dead-end branches (see `_resolve_chain`). The function form
149
+ of the `.deep` accessor: `deep_get(host, "decorators", "render_func")`."""
150
+ return _resolve_chain(container, names)
151
+
152
+
153
+ def _resolve_all(node, names, out, seen):
154
+ """Collect EVERY value reachable by `names` from `node` (all branches, not just the
155
+ first backtracked hit), deduped by identity. Empty `names` → `node` itself."""
156
+ if not names:
157
+ if id(node) not in seen:
158
+ seen.add(id(node))
159
+ out.append(node)
160
+ return
161
+ first, rest = names[0], names[1:]
162
+ for cand in _iter_matches(node, first):
163
+ _resolve_all(cand, rest, out, seen)
164
+
165
+
166
+ def _iter_leaves(node, prefix, seen):
167
+ """Yield `(path_tuple, value)` for every LEAF (non-dict/list) at or below `node`,
168
+ descending dicts/lists. Internal `__…` keys are skipped. Cycle-safe."""
169
+ nid = id(node)
170
+ if nid in seen:
171
+ return
172
+ if isinstance(node, dict):
173
+ seen.add(nid)
174
+ for k, v in node.items():
175
+ if _is_internal_key(k):
176
+ continue
177
+ if isinstance(v, (dict, list)):
178
+ yield from _iter_leaves(v, prefix + (k,), seen)
179
+ else:
180
+ yield prefix + (k,), v
181
+ elif isinstance(node, list):
182
+ seen.add(nid)
183
+ for i, v in enumerate(node):
184
+ if isinstance(v, (dict, list)):
185
+ yield from _iter_leaves(v, prefix + (i,), seen)
186
+ else:
187
+ yield prefix + (i,), v
188
+ else:
189
+ yield prefix, node
190
+
191
+
192
+ def deep_all(container, *names):
193
+ """Every value matching `names` across ALL branches (the multi-hit form of
194
+ `deep_get`). With no names → every leaf value in the whole tree. Function form of
195
+ `host.deep.all()` / `host.deep.<names>.all()`."""
196
+ if not names:
197
+ return [v for _, v in _iter_leaves(container, (), set())]
198
+ out = []
199
+ _resolve_all(container, names, out, set())
200
+ return out
201
+
202
+
203
+ class _DeepPath:
204
+ """Lazy, backtracking path builder returned by `.deep`. Each `.name` / `[name]`
205
+ appends a rung WITHOUT resolving; calling it (`path()`) resolves the whole chain
206
+ at once against the root, so backtracking can see the full path. Underscore names
207
+ raise AttributeError so it stays invisible to attribute/copy/pickle probes."""
208
+
209
+ __slots__ = ("_root", "_names")
210
+
211
+ def __init__(self, root, names=()):
212
+ object.__setattr__(self, "_root", root)
213
+ object.__setattr__(self, "_names", names)
214
+
215
+ def __getattr__(self, name):
216
+ if name.startswith("_"):
217
+ raise AttributeError(name)
218
+ return _DeepPath(self._root, self._names + (name,))
219
+
220
+ def __getitem__(self, key):
221
+ return _DeepPath(self._root, self._names + (key,))
222
+
223
+ def __call__(self):
224
+ return _resolve_chain(self._root, self._names)
225
+
226
+ def all(self):
227
+ """Every value matching the accumulated path, across ALL branches — the
228
+ multi-hit counterpart to `()` (which returns just the first). With NO path
229
+ (`host.deep.all()`) → every leaf value in the whole tree: "I don't know the
230
+ path, give me everything". `host.deep.params.all()` → every `params` anywhere."""
231
+ return deep_all(self._root, *self._names)
232
+
233
+ def unwrap(self):
234
+ """The structure-preserving "everything": descend past redundant single-entry
235
+ wrapper rungs (`value`, the fn-name level, …) and return the first MEANINGFUL
236
+ container — the dict/list that actually holds the content — with its KEYS
237
+ intact, ready for draw_collection. Use this (not `.all()`) to render a whole
238
+ subtree when you don't know the path: `.all()` flattens to a list of leaf
239
+ values (so a labelled `tint=(...)` becomes a positional list entry); `.unwrap()`
240
+ keeps it a dict. Stops at the first branch (>1 child) or leaf-bearing level."""
241
+ node = self.__call__()
242
+ seen = set()
243
+ while isinstance(node, dict) and id(node) not in seen:
244
+ seen.add(id(node))
245
+ keys = [k for k in node.keys() if not _is_internal_key(k)]
246
+ if len(keys) != 1:
247
+ break # branch or empty → this is the content
248
+ child = dict.__getitem__(node, keys[0])
249
+ if not isinstance(child, (dict, list)):
250
+ break # single child is a leaf → keep the dict
251
+ node = child
252
+ return node
253
+
254
+ def items(self):
255
+ """Every leaf as `(path_tuple, value)` so you can see WHERE each came from.
256
+ Spans all branches the path resolves to (the whole tree for an empty path)."""
257
+ roots = self.all() if self._names else [self._root]
258
+ out = []
259
+ for node in roots:
260
+ if node is MISSING:
261
+ continue
262
+ out.extend(_iter_leaves(node, (), set()))
263
+ return out
264
+
265
+ def __bool__(self):
266
+ # `if host.a.b:` resolves and tests the result - no explicit call needed. Also
267
+ # the safety net for a bad probe that leaks here: an unresolved/empty chain
268
+ # reads as falsy, so framework code treats a leaked proxy like None.
269
+ result = self.__call__()
270
+ return result is not MISSING and bool(result)
271
+
272
+ def __iter__(self):
273
+ result = self.__call__()
274
+ return iter(result) if result is not MISSING else iter(())
275
+
276
+ def __len__(self):
277
+ result = self.__call__()
278
+ try:
279
+ return len(result) if result is not MISSING else 0
280
+ except TypeError:
281
+ return 0
282
+
283
+ def __contains__(self, item):
284
+ result = self.__call__()
285
+ try:
286
+ return result is not MISSING and item in result
287
+ except TypeError:
288
+ return False
289
+
290
+ def __repr__(self):
291
+ return f"<deep {'.'.join(map(str, self._names))} -> {self.__call__()!r}>"
292
+
293
+
294
+ class _DeepAttrMixin:
295
+ """Gives a dict/list container a `.deep` entry point for safe path traversal.
296
+
297
+ `.deep` is a real attribute (a property), so it never interferes with normal
298
+ attribute lookup, `getattr(x, name, default)`, `hasattr`, or copy/pickle — those
299
+ all behave exactly as before. Only `host.deep.<names>()` runs the deep search."""
300
+
301
+ @property
302
+ def deep(self):
303
+ return _DeepPath(self)
304
+
305
+
306
+ # base type -> generated bubbling subclass (one per concrete type, cached)
307
+ _BUBBLING_DICT_CLASSES = {}
308
+ _BUBBLING_LIST_CLASSES = {}
309
+
310
+ # Melty registries looked up by EXACT class (no MRO lookup), so a bubbling subclass
311
+ # must inherit the base's entries or it loses the base's @defaults / annotations.
312
+ _CLASS_KEYED_REGISTRIES = (
313
+ "default_kwargs_by_type",
314
+ "default_kwargs_by_attrib_type",
315
+ "default_funcs_by_name_type",
316
+ )
317
+
318
+
319
+ def _mirror_registries(base, bubbling):
320
+ """Point a generated bubbling subclass at the SAME per-class registry entries as
321
+ its base, so exact-type lookups (@defaults kwargs, per-attr overrides) still
322
+ resolve. Share the references — base entries are import-time and don't change."""
323
+ for reg_name in _CLASS_KEYED_REGISTRIES:
324
+ reg = getattr(Melty, reg_name, None)
325
+ if reg is not None and base in reg: # `in` on a registry: no autocreate
326
+ reg[bubbling] = reg[base]
327
+
328
+
329
+ def base_of_bubbling(t):
330
+ """A generated bubbling subclass (`Bubbling_<Base>`, made by `type(...)` at runtime)
331
+ has NO source of its own, so `inspect.getfile`/`getsourcelines` on it raises "could
332
+ not find class definition". It's an implementation detail that should behave like its
333
+ base everywhere — the registry mirroring already does this for `@defaults`; this does
334
+ it for SOURCE resolution. Return the real base (the non-mixin entry of `__bases__`,
335
+ since the subclass is `(mixin, base)`); pass non-bubbling types through unchanged."""
336
+ if isinstance(t, type) and issubclass(t, (_BubblingDictMixin, _BubblingListMixin)):
337
+ for b in t.__bases__:
338
+ if not (isinstance(b, type) and issubclass(b, (_BubblingDictMixin, _BubblingListMixin))):
339
+ return b
340
+ return t
341
+
342
+
343
+ def _notify(node):
344
+ root = getattr(node, "_bubble_root", None)
345
+ if root is not None:
346
+ root._mark_changed()
347
+
348
+
349
+ # Immutable SCALAR leaf types. imgui widgets hand back a FRESH object each frame for
350
+ # these (a slider returns a new int/float; ints outside CPython's -5..256 cache aren't
351
+ # interned), so an identity (`is`) check sees a spurious "change" and re-fires notify -
352
+ # which, through the proxy round-trip, becomes a Tree↔String save feedback loop. An `==`
353
+ # on a scalar leaf is O(1) and BOUNDED (its own size, the rendering cost paid), so
354
+ # it's safe here - unlike a deep `==` over a whole container/GeneralParse tree, which is
355
+ # what doesn't scale. Containers fall through to identity (they're edited in place).
356
+ _SCALAR_TYPES = frozenset({int, float, complex, bool, str, bytes, type(None)})
357
+
358
+
359
+ def _unchanged_leaf(cur, value):
360
+ """True if writing `value` over `cur` is a no-op: the SAME object, or an equal
361
+ immutable scalar (a reconstructed-but-equal leaf). Containers → identity only."""
362
+ return cur is value or (type(value) in _SCALAR_TYPES and cur == value)
363
+
364
+
365
+ def _is_internal_key(key):
366
+ """`__…` keys are framework bookkeeping, NOT user data — the editor already
367
+ excludes them (`excluded=["__cst__"]`, draw skips `__`-prefixed). Writing them
368
+ must NOT mark the host dirty: dict_to_cst_module's `leave_ClassDef` caches the
369
+ rebuilt node as `edit_dict["__cst__"] = node` while SERIALIZING the tree, and if
370
+ that re-dirties the proxy, every save triggers a serialize that triggers another
371
+ save — an infinite loop. So bubbling sets these raw and never notifies."""
372
+ return isinstance(key, str) and key.startswith("__")
373
+
374
+
375
+ # ── Mixins (front of the MRO) - bubble every mutation, keep new children bubbling ──
376
+ class _BubblingDictMixin(_DeepAttrMixin):
377
+ _bubble_root = None
378
+
379
+ def __setitem__(self, key, value):
380
+ if _is_internal_key(key):
381
+ super().__setitem__(key, value) # bookkeeping write, no bubble
382
+ return
383
+ # Re-assigning an unchanged child is not an edit (draw_collection writes the
384
+ # rendered child back every frame) - store it, but don't notify / loop. Identity
385
+ # for containers (edited in place → same object), O(1) `==` for scalar leaves
386
+ # (imgui reconstructs them, so `is` would spuriously fire). See _unchanged_leaf.
387
+ unchanged = key in self and _unchanged_leaf(dict.__getitem__(self, key), value)
388
+ root = self._bubble_root
389
+ if root is not None:
390
+ value = install_bubbling(value, root)
391
+ super().__setitem__(key, value)
392
+ if not unchanged:
393
+ _notify(self)
394
+
395
+ def __delitem__(self, key):
396
+ print("DEL", key)
397
+ internal = _is_internal_key(key)
398
+ super().__delitem__(key)
399
+ if not internal:
400
+ _notify(self)
401
+
402
+ def update(self, *args, **kwargs):
403
+ super().update(*args, **kwargs)
404
+ root = self._bubble_root
405
+ if root is not None:
406
+ _reinstall_children(self, root)
407
+ _notify(self)
408
+
409
+ def setdefault(self, key, default=None):
410
+ if key in self:
411
+ return super().__getitem__(key)
412
+ root = self._bubble_root
413
+ if root is not None:
414
+ default = install_bubbling(default, root)
415
+ super().__setitem__(key, default)
416
+ _notify(self)
417
+ return default
418
+
419
+ def pop(self, *args):
420
+ had = (not args) or args[0] in self
421
+ result = super().pop(*args)
422
+ if had:
423
+ _notify(self)
424
+ return result
425
+
426
+ def popitem(self):
427
+ result = super().popitem()
428
+ _notify(self)
429
+ return result
430
+
431
+ def clear(self):
432
+ had = bool(self)
433
+ super().clear()
434
+ if had:
435
+ _notify(self)
436
+
437
+
438
+ class _BubblingListMixin(_DeepAttrMixin):
439
+ _bubble_root = None
440
+
441
+ def __setitem__(self, idx, value):
442
+ # Unchanged element is not an edit → store but don't notify. Identity for containers,
443
+ # O(1) `==` for scalar leaves (imgui hands back a new int/float each frame, so a
444
+ # `is` check spuriously fires → the list-element feedback loop). See _unchanged_leaf.
445
+ unchanged = not isinstance(idx, slice) and _unchanged_leaf(list.__getitem__(self, idx), value)
446
+ root = self._bubble_root
447
+ if root is not None:
448
+ value = ([install_bubbling(v, root) for v in value]
449
+ if isinstance(idx, slice) else install_bubbling(value, root))
450
+ super().__setitem__(idx, value)
451
+ if not unchanged:
452
+ _notify(self)
453
+
454
+ def __delitem__(self, idx):
455
+ super().__delitem__(idx)
456
+ _notify(self)
457
+
458
+ def append(self, value):
459
+ root = self._bubble_root
460
+ if root is not None:
461
+ value = install_bubbling(value, root)
462
+ super().append(value)
463
+ _notify(self)
464
+
465
+ def extend(self, iterable):
466
+ root = self._bubble_root
467
+ if root is not None:
468
+ iterable = [install_bubbling(v, root) for v in iterable]
469
+ super().extend(iterable)
470
+ _notify(self)
471
+
472
+ def insert(self, idx, value):
473
+ root = self._bubble_root
474
+ if root is not None:
475
+ value = install_bubbling(value, root)
476
+ super().insert(idx, value)
477
+ _notify(self)
478
+
479
+ def pop(self, *args):
480
+ result = super().pop(*args)
481
+ _notify(self)
482
+ return result
483
+
484
+ def remove(self, value):
485
+ super().remove(value)
486
+ _notify(self)
487
+
488
+ def clear(self):
489
+ had = bool(self)
490
+ super().clear()
491
+ if had:
492
+ _notify(self)
493
+
494
+ def __iadd__(self, other):
495
+ self.extend(other)
496
+ return self
497
+
498
+
499
+ class _BubblingDict(_BubblingDictMixin, dict):
500
+ """Bubbling replacement for a PLAIN dict (which can't be upgraded in place)."""
501
+
502
+
503
+ class _BubblingList(_BubblingListMixin, list):
504
+ """Bubbling replacement for a PLAIN list."""
505
+
506
+
507
+ def _bubbling_dict_class_for(base):
508
+ cls = _BUBBLING_DICT_CLASSES.get(base)
509
+ if cls is None:
510
+ cls = type(f"Bubbling_{base.__name__}", (_BubblingDictMixin, base), {})
511
+ _BUBBLING_DICT_CLASSES[base] = cls
512
+ _mirror_registries(base, cls)
513
+ return cls
514
+
515
+
516
+ def _bubbling_list_class_for(base):
517
+ cls = _BUBBLING_LIST_CLASSES.get(base)
518
+ if cls is None:
519
+ cls = type(f"Bubbling_{base.__name__}", (_BubblingListMixin, base), {})
520
+ _BUBBLING_LIST_CLASSES[base] = cls
521
+ _mirror_registries(base, cls)
522
+ return cls
523
+
524
+
525
+ def _reinstall_children(node, root, _seen=None):
526
+ """Upgrade every child of an already-bubbling container, writing replacements
527
+ back through the BASE methods (raw dict/list ops) so we don't re-fire notify."""
528
+ if _seen is None:
529
+ _seen = set()
530
+ if isinstance(node, dict):
531
+ for k in list(dict.keys(node)):
532
+ child = dict.__getitem__(node, k)
533
+ new = install_bubbling(child, root, _seen)
534
+ if new is not child:
535
+ dict.__setitem__(node, k, new)
536
+ elif isinstance(node, list):
537
+ for i in range(len(node)):
538
+ child = list.__getitem__(node, i)
539
+ new = install_bubbling(child, root, _seen)
540
+ if new is not child:
541
+ list.__setitem__(node, i, new)
542
+
543
+
544
+ def install_bubbling(value, root, _seen=None):
545
+ """Upgrade `value` and everything nested under it to bubble mutations to `root`.
546
+
547
+ Returns the bubbling value: the SAME object when it can be upgraded in place (a
548
+ __dict__-bearing dict/list subclass), or a constructed copy for a plain
549
+ dict/list (whose parent then stores the replacement). Leaves immutable / opaque
550
+ leaves (str, int, tuple, libcst nodes, …) untouched. Idempotent and cycle-safe."""
551
+ if _seen is None:
552
+ _seen = set()
553
+ vid = id(value)
554
+
555
+ # Already bubbling - just (re)point the root and recurse for fresh subtrees.
556
+ if isinstance(value, (_BubblingDictMixin, _BubblingListMixin)):
557
+ value._bubble_root = root
558
+ if vid not in _seen:
559
+ _seen.add(vid)
560
+ _reinstall_children(value, root, _seen)
561
+ return value
562
+
563
+ if isinstance(value, dict):
564
+ if vid in _seen:
565
+ return value
566
+ _seen.add(vid)
567
+ if type(value) is dict: # plain dict: can't reclass, copy
568
+ target = _BubblingDict()
569
+ dict.update(target, value)
570
+ else: # subclass with __dict__: in-place
571
+ try:
572
+ value.__class__ = _bubbling_dict_class_for(type(value))
573
+ target = value
574
+ except TypeError: # exotic dict that refuses reclass
575
+ target = _BubblingDict()
576
+ dict.update(target, value)
577
+ target._bubble_root = root
578
+ _reinstall_children(target, root, _seen)
579
+ return target
580
+
581
+ if isinstance(value, list):
582
+ if vid in _seen:
583
+ return value
584
+ _seen.add(vid)
585
+ if type(value) is list:
586
+ target = _BubblingList()
587
+ list.extend(target, value)
588
+ else:
589
+ try:
590
+ value.__class__ = _bubbling_list_class_for(type(value))
591
+ target = value
592
+ except TypeError:
593
+ target = _BubblingList()
594
+ list.extend(target, value)
595
+ target._bubble_root = root
596
+ _reinstall_children(target, root, _seen)
597
+ return target
598
+
599
+ return value