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,735 @@
1
+ """meltygui apps: ``@glfw_window`` turns a draw function into an OS window.
2
+
3
+ from meltygui import glfw_window, draw_text, pressed
4
+
5
+ @glfw_window
6
+ def editor():
7
+ changed, new = draw_text(text)
8
+ ...
9
+
10
+ The first decoration boots meltygui: the start-up shortcuts (warm_start.py),
11
+ glfw.init and a hidden owner window (the GL share group's root and the imgui
12
+ context that owns the font atlas) on the calling thread, and meltygui's heavy
13
+ imports on a background thread — the two overlap, as hdr-viewer measured.
14
+ Every decoration registers its function; the loop starts when the main
15
+ module's top level finishes (a trace hook on that frame's return — atexit
16
+ is too late: threading is already shut down), or explicitly with ``run()``.
17
+
18
+ Each window is a Surface (surface.py): frameless with meltygui's own title bar,
19
+ window controls, corner cut and shadow unless Toggles.Melty.wayland_show_frame
20
+ asks for the compositor's frame. Windows are peers: closing one closes it
21
+ alone, the loop ends when the last is gone. Child windows come from
22
+ ``draw_something(glfw_window=True)`` inside a body (the render wrapper) and
23
+ follow their parent like nested meltygui windows.
24
+
25
+ Every launch appends a phase timing table to ~/.cache/<app_id>/startup.log;
26
+ MELTY_BENCH=1 also prints it and exits after the first frame.
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import inspect
31
+ import os
32
+ import pathlib
33
+ import sys
34
+ import threading
35
+ import traceback
36
+ import time
37
+
38
+ _T0 = float(os.environ.get('MELTY_T0') or time.time())
39
+ _MARKS = [('launcher exec', _T0), ('interpreter + stdlib', time.time())]
40
+ _ROOTS: list = [] # (fn, kwargs) in decoration order
41
+ _state = dict(booted=False, ran=False, app_id=None, cache=None, imports=None,
42
+ import_error=None, switch_interval=None, failed=False)
43
+
44
+
45
+ def mark(label):
46
+ _MARKS.append((label, time.time()))
47
+
48
+
49
+ def _write_startup_log(app_id, subject):
50
+ lines = [f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(_T0))} {subject} (pid {os.getpid()})",
51
+ ' +ms dms phase']
52
+ prev = _T0
53
+ for label, t in sorted(_MARKS, key=lambda m: m[1]):
54
+ lines.append(f' {(t - _T0) * 1000:6.0f} {(t - prev) * 1000:6.0f} {label}')
55
+ prev = t
56
+ lines.append(f' total {(_MARKS[-1][1] - _T0) * 1000:.0f} ms')
57
+ text = '\n'.join(lines) + '\n\n'
58
+ path = _state['cache'] / 'startup.log'
59
+ try:
60
+ path.parent.mkdir(parents=True, exist_ok=True)
61
+ with open(path, 'a') as f:
62
+ f.write(text)
63
+ except OSError as e:
64
+ print(f'{app_id}: cannot write {path}: {e}', file=sys.stderr)
65
+ if os.environ.get('MELTY_BENCH'):
66
+ print(text, end='', file=sys.stderr, flush=True)
67
+
68
+
69
+ # --- boot -------------------------------------------------------------------------
70
+ def _default_app_id():
71
+ main = sys.modules.get('__main__')
72
+ path = getattr(main, '__file__', None)
73
+ return pathlib.Path(path).stem.replace('_', '-') if path else 'meltygui-app'
74
+
75
+
76
+ def _utf8_output():
77
+ """Trace groups and stack reports draw bars with box characters. A stream
78
+ that encodes with a legacy code page (Windows redirects stdout as cp1252)
79
+ raises UnicodeEncodeError on them in the middle of a frame, which leaves
80
+ the imgui ID stack unbalanced; such a stream writes UTF-8 instead."""
81
+ for stream in (sys.stdout, sys.stderr):
82
+ encoding = (getattr(stream, 'encoding', None) or '').lower().replace('-', '')
83
+ if encoding != 'utf8' and hasattr(stream, 'reconfigure'):
84
+ stream.reconfigure(encoding='utf-8', errors='replace')
85
+
86
+
87
+ def boot(app_id=None):
88
+ """Start meltygui: shortcuts, glfw, the owner window, the import thread.
89
+ Idempotent; the first @glfw_window calls it."""
90
+ if _state['booted']:
91
+ if app_id and app_id != _state['app_id']:
92
+ print(f"meltygui: app_id {app_id!r} ignored — already booted as {_state['app_id']!r} "
93
+ f"(the first boot names the session and cache directories)", file=sys.stderr)
94
+ return
95
+ _state['booted'] = True
96
+ _utf8_output()
97
+ _state['app_id'] = app_id or _default_app_id()
98
+ cache = pathlib.Path(os.environ.get('XDG_CACHE_HOME') or pathlib.Path.home() / '.cache') / _state['app_id']
99
+ _state['cache'] = cache
100
+ _register_editable(getattr(sys.modules.get('__main__'), '__file__', None))
101
+ if sys.platform.startswith('linux'):
102
+ os.environ.setdefault('GDK_BACKEND', 'wayland')
103
+ import meltygui.core.styling.warm_start as warm_start
104
+ warm_start.prepare(cache)
105
+ import meltygui.core.windowing.window_api as glfw
106
+ from meltygui.core.runtime.toggles import Toggles
107
+ backend = glfw.select_backend(Toggles.windows.native_os_windows)
108
+ if backend == 'wayland':
109
+ sys._lsd_wayland_libdecor_disabled = True
110
+ # While the import thread holds the GIL, each of pyGLFW's Python-side
111
+ # steps waits up to a switch interval for it (5 ms default: window
112
+ # creation went 80 -> 150 ms). Shorten it until the imports are done.
113
+ _state['switch_interval'] = sys.getswitchinterval()
114
+ sys.setswitchinterval(0.0002)
115
+ thread = threading.Thread(target=_run_imports, name='meltygui-imports', daemon=True)
116
+ thread.start()
117
+ _state['imports'] = thread
118
+ mark(f'{backend} window API ready')
119
+ # libdecor loads its GTK plugin at init (~60 ms) and the frameless window
120
+ # never shows it: meltygui's own hint (Toggles.Melty.wayland_native_frame)
121
+ # disables it and records that for titlebar.py, whose chrome only runs
122
+ # on the native frame. MELTY_LIBDECOR=1 keeps libdecor (compositors
123
+ # without xdg-decoration), which also means the compositor's frame.
124
+ if backend == 'glfw' and os.environ.get('MELTY_LIBDECOR'):
125
+ sys._lsd_wayland_libdecor_disabled = False
126
+ from meltygui.core.windowing.glfw_utils import apply_wayland_frame_hint
127
+ apply_wayland_frame_hint()
128
+ if not glfw.init():
129
+ raise SystemExit('glfw.init failed')
130
+ mark(f'{backend}.init')
131
+ if backend == 'glfw':
132
+ warm_start.remember_glfw_library(cache)
133
+ glfw.window_hint(glfw.VISIBLE, False)
134
+ glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
135
+ glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
136
+ glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
137
+ owner = glfw.create_window(1, 1, 'meltygui owner', None, None)
138
+ if not owner:
139
+ raise SystemExit('glfw.create_window (owner) failed')
140
+ glfw.default_window_hints()
141
+ _state['owner'] = owner
142
+ mark('owner window created')
143
+ # The first eglMakeCurrent loads driver state (~30 ms on NVIDIA). Do it
144
+ # while the import worker is still running, before joining the worker.
145
+ glfw.make_context_current(owner)
146
+ mark('owner context current')
147
+
148
+
149
+ def _run_imports():
150
+ try:
151
+ # PyOpenGL only imports numpy during the first renderer call,
152
+ # after the driver work could have overlapped it. No GL calls here.
153
+ from OpenGL.arrays import numpymodule # noqa: F401
154
+ import meltygui_imgui as imgui # noqa: F401
155
+ import OpenGL.GL # noqa: F401
156
+ mark('imgui/numpy/GL imported (bg)')
157
+ import meltygui.core.melty as runtime # noqa: F401
158
+ import meltygui.core.windowing.surface as surface # noqa: F401
159
+ import meltygui.editor.text_editor as text_editor
160
+ import meltygui.view.texture_view as texture_view # noqa: F401
161
+ mark('meltygui imported (bg)')
162
+ except BaseException as e: # re-raised on the main thread
163
+ _state['import_error'] = e
164
+
165
+
166
+ def _wait_imports():
167
+ thread = _state['imports']
168
+ if thread is not None:
169
+ thread.join()
170
+ _state['imports'] = None
171
+ if _state['switch_interval'] is not None:
172
+ sys.setswitchinterval(_state['switch_interval'])
173
+ if _state['import_error'] is not None:
174
+ raise _state['import_error']
175
+
176
+
177
+ def _init_melty():
178
+ """Once, after the imports: the owner imgui context with the font atlas,
179
+ the global style manager, meltygui's flags."""
180
+ import meltygui.core.windowing.window_api as glfw
181
+ import meltygui_imgui as imgui
182
+ from meltygui.core.melty import Melty
183
+ from meltygui.core.styling.fonts import FontManager
184
+ from meltygui.core.windowing.surface import Surface
185
+ from meltygui.core.runtime.toggles import Toggles
186
+ from meltygui.core.styling.style_core import ImGuiStyleManager
187
+ import meltygui.core.styling.warm_start as warm_start
188
+ owner = _state['owner']
189
+ glfw.make_context_current(owner)
190
+ Surface.owner_window = owner
191
+ Surface.owner_context = imgui.create_context()
192
+ imgui.get_io().ini_file_name = None
193
+ imgui.get_io().display_size = (1.0, 1.0) # the atlas hinting pass frames on it
194
+ Melty.glfw_window = owner
195
+ Melty.font_mgr = FontManager(imgui.get_io(), Melty.resolve_ui_scale())
196
+ Melty.font_mgr.prewarm()
197
+ warm_start.cache_hinted_atlas(Melty.font_mgr, _state['cache'])
198
+ mark('fonts loaded')
199
+ Melty.style_manager = ImGuiStyleManager()
200
+ Melty.global_attrs['style_manager'] = Melty.style_manager
201
+ # The app's persisted draw states (app_session.py): loaded on the
202
+ # first Surface so every surface's `Melty.vis.root` is the session and
203
+ # get_draw_state / note_window_seen / the window z-order read and write
204
+ # the stores that run() saves on exit.
205
+ session = _load_session()
206
+ _register_projects()
207
+ Melty.draw_state_registry = session.draw_state_registry
208
+ Melty.adopt_registered_windows(session)
209
+ Surface.session = session
210
+ mark('session loaded')
211
+ # meltygui boots in "annotation mode" (view calls return carriers, nothing
212
+ # renders) until the studio's Melty.init() clears it. That init also
213
+ # starts file watchers and a jedi worker we do not need.
214
+ Melty.annotation_mode = False
215
+ Toggles.show_fps = False
216
+ if os.environ.get('MELTY_NO_OS_FRAME'):
217
+ Toggles.Melty.push_os_window_edges = False
218
+ Surface.app_id = _state['app_id']
219
+ import meltygui.core.windowing.geometry_feed as geometry_feed
220
+ geometry_feed.start() # for rects: the size fit, child placement, os_frame
221
+ mark('meltygui configured')
222
+
223
+
224
+ def _load_session():
225
+ """The app's AppSession (app_session.load), read once: by the first
226
+ `persisted` call or by _init_melty, whichever comes first. Needs the
227
+ meltygui imports (the pickled classes), so it waits for them."""
228
+ session = _state.get('session')
229
+ if session is None:
230
+ _wait_imports()
231
+ import meltygui.core.runtime.app_session as app_session
232
+ session = app_session.load(_state['app_id'])
233
+ _state['session'] = session
234
+ mark('session loaded')
235
+ return session
236
+
237
+
238
+ def persisted(name, factory, *, app_id=None):
239
+ """An object the app keeps between runs: last run's saved `name`, or a
240
+ fresh `factory()` when there is none (or the saved one is not an
241
+ instance of `factory`, a class). It rides the app's session
242
+ (app_session.AppSession.app_state) and is saved with the draw states
243
+ when the loop exits, so it must be a DictConversion — its public,
244
+ non-@no_save fields persist, exactly as a studio model field does.
245
+
246
+ open_files = meltygui.persisted('open_files', OpenFiles, app_id='meltygui-code-editor')
247
+
248
+ `app_id` names the session file when this runs before the first
249
+ `@glfw_window` (the usual place — the object feeds the window's body);
250
+ the decorator's later `app_id` must match it."""
251
+ boot(app_id)
252
+ session = _load_session()
253
+ saved = session.app_state.get(name)
254
+ if saved is None or (isinstance(factory, type) and not isinstance(saved, factory)):
255
+ saved = factory()
256
+ session.app_state[name] = saved
257
+ return saved
258
+
259
+
260
+ # --- the decorator ------------------------------------------------------------------
261
+ def _register_editable(file):
262
+ """Make the project holding `file` editable source (address.add_editable_root):
263
+ the app's own code loads into the studio's code hosts, so its decorators
264
+ are input sources (the inputs tab, the header's tint chip, `locate_<param>`)
265
+ and its files hotswap, exactly like the checkout's."""
266
+ if not file:
267
+ return
268
+ from meltygui.code.fileref import add_editable_root
269
+ add_editable_root(file)
270
+
271
+
272
+ def _register_projects():
273
+ """Every folder marked as a project in the shared file-meta store
274
+ (file_meta.mark_project — machine-wide, so a project marked in the
275
+ studio or another app counts here) is editable source too, like the
276
+ app's own tree. The store is a small pickle; read once at init."""
277
+ try:
278
+ from meltygui.core.runtime.extensions import source_folders as project_roots
279
+ from meltygui.code.fileref import add_editable_root
280
+ for root in project_roots():
281
+ add_editable_root(root)
282
+ except Exception:
283
+ traceback.print_exc()
284
+
285
+
286
+ def glfw_window(fn=None, *, name=None, width=1280, height=800, app_id=None, on_close=None, **view_kwargs):
287
+ """Register ``fn`` as an OS window. ``fn()`` draws the window's content
288
+ each frame; views it draws at root level fill the window.
289
+
290
+ ``name`` (default: the function's name) is the window's name AND its
291
+ OS title — one per window; ``width`` / ``height`` its content size.
292
+ ``on_close(surface)`` is asked when the window is told to close (the
293
+ title bar's ×, the compositor, `glfw.set_window_should_close`): return
294
+ False to keep it (hide it, say — a chat app with a turn streaming).
295
+ These are the universal meltygui names (`@window`, every view's kwargs),
296
+ never `title=` or `size=` (Lukas 09-12).
297
+
298
+ Every other keyword argument is the root VIEW's, exactly as `@window`'s
299
+ are the studio window's (`tint=`, `disable_scroll=`, `value=`,
300
+ `with_header=`, `show_name=`, ...): a render-func body is drawn with
301
+ them as the window's root view; a plain-function body runs under the
302
+ `tint` (the style tint its filling view colours from). Like `@window`
303
+ the decorator is an INPUT SOURCE of the view (`@glfw_window(<fn>)` in
304
+ the inputs tab, read and written by `locate_<param>`): its file's
305
+ project becomes editable source, and a hotswap that re-runs the
306
+ decorator (an edited kwarg lands as a recompile of the def) updates
307
+ the registered window's config IN PLACE — same name, same window,
308
+ the new kwargs on the next frame — instead of registering a second
309
+ root."""
310
+ def wrap(fn):
311
+ boot(app_id)
312
+ try:
313
+ source = inspect.getsourcefile(inspect.unwrap(fn))
314
+ except TypeError:
315
+ source = None
316
+ _register_editable(source)
317
+ config = dict(name=name or fn.__name__, width=int(width), height=int(height), on_close=on_close,
318
+ view_kwargs=view_kwargs)
319
+ for index, (registered, existing) in enumerate(_ROOTS):
320
+ if existing['name'] == config['name']:
321
+ existing.update(config) # the live config object: the body reads it
322
+ if not _state['ran']:
323
+ _ROOTS[index] = (fn, existing)
324
+ break
325
+ else:
326
+ _ROOTS.append((fn, config))
327
+ if not _state.get('hooked'):
328
+ _state['hooked'] = True
329
+ _hook_main_return()
330
+ return fn
331
+ return wrap(fn) if fn is not None else wrap
332
+
333
+
334
+ def _root_body(fn, name, view_kwargs=None, config=None):
335
+ """The window's body. A plain function draws inline into the surface
336
+ root (its filling view sizes itself to the window). A RENDER FUNC (the
337
+ @window playgrounds: `@glfw_window` over `@render_func`, the direct
338
+ swap) is drawn as the window's root view the way the studio draws a
339
+ @window: a meltygui view filling the window, with the background and
340
+ layout context its children (draw_rows, draw_any, fields) expect —
341
+ minus the closable chrome, which the OS window provides.
342
+
343
+ ``config`` is the root's LIVE registration (the dict in _ROOTS): a
344
+ render-func body reads its `view_kwargs` on every frame, so a
345
+ re-decoration (a hotswapped `@glfw_window(tint=...)` edit) reaches the
346
+ open window; without it `view_kwargs` is fixed."""
347
+ def current_kwargs():
348
+ source = config.get('view_kwargs') if config is not None else view_kwargs
349
+ return dict(source or {})
350
+ if hasattr(fn, '__render_func__'):
351
+ return lambda surface: _draw_root(fn, name, **current_kwargs())
352
+ if current_kwargs().get('tint') is None and config is None:
353
+ return lambda surface: fn()
354
+
355
+ def tinted(surface):
356
+ # The body runs under the decorator's tint (what draw_bg and the
357
+ # style colours read), and after - the same push/restore the
358
+ # wrapper does around a tinted view. Read per frame: a live tint
359
+ # edit (anywhere.live_apply) or a re-decoration changes the config.
360
+ tint = current_kwargs().get('tint')
361
+ if tint is None:
362
+ fn()
363
+ return
364
+ from meltygui.core.melty import Melty
365
+ previous = Melty.style_manager.get_tint()
366
+ Melty.style_manager.set_imgui_tint(*tint[:4])
367
+ try:
368
+ fn()
369
+ finally:
370
+ Melty.style_manager.set_imgui_tint(*previous)
371
+ return tinted
372
+
373
+
374
+ def _searchable_body(body):
375
+ """The root body plus the app's global search (app_search.draw: a no-op
376
+ unless meltygui.global_search enabled it and this is its window), drawn
377
+ AFTER the body so the search window floats above the root."""
378
+ def searchable(surface):
379
+ body(surface)
380
+ from meltygui.core.runtime.extensions import call
381
+ from meltygui.view.code_view import draw_pending_preview
382
+ draw_pending_preview()
383
+ call('root_draw', surface)
384
+ return searchable
385
+
386
+
387
+ def _draw_root(fn, name, value=None, **kwargs):
388
+ """Draw the render func ``fn`` as the window's root view, filling it:
389
+ what `@glfw_window` over `@render_func` does each frame. ``value`` is
390
+ the view's input value (None: the view owns its state); ``kwargs`` are
391
+ the decorator's view kwargs. ``with_header=draw_header`` puts the meltygui
392
+ header in the chrome row beside the window controls
393
+ (surface.root_view_kwargs)."""
394
+ from meltygui.core.windowing.surface import root_view_kwargs
395
+ # A closable meltygui window (the studio's Mode.MODE_WINDOW), pinned to
396
+ # the surface: layouts (draw_rows / draw_columns) register their
397
+ # children on the enclosing view, so the root must be one.
398
+ return fn(value, **root_view_kwargs(name or fn.__name__, **kwargs))
399
+
400
+
401
+ def _hook_main_return():
402
+ """Start the loop when the main module's top level returns, so a script
403
+ is just decorated functions. A local trace function on that frame sees
404
+ its 'return' event (tracing has to be enabled globally for local trace
405
+ functions to fire; the global one declines every other frame, and line
406
+ events are off, so the cost is one call per function call until the
407
+ module ends). Declines when a debugger already traces; ``run()`` then
408
+ has to be called explicitly."""
409
+ if sys.gettrace() is not None:
410
+ return
411
+ frame = sys._getframe(1)
412
+ while frame is not None and frame.f_globals.get('__name__') != '__main__':
413
+ frame = frame.f_back
414
+ if frame is None or frame.f_code.co_name != '<module>':
415
+ return
416
+ state = {'failed': False}
417
+
418
+ def local(fr, event, arg):
419
+ if event == 'exception':
420
+ state['failed'] = True
421
+ elif event == 'return':
422
+ sys.settrace(None)
423
+ fr.f_trace = None
424
+ _state['hooked'] = False
425
+ if not state['failed']:
426
+ run()
427
+ return local
428
+
429
+ _state['tracer'] = lambda fr, event, arg: None
430
+ sys.settrace(_state['tracer'])
431
+ frame.f_trace_lines = False
432
+ frame.f_trace = local
433
+
434
+
435
+ # --- the loop -----------------------------------------------------------------------
436
+ def _unhook_main_return():
437
+ """Drop the implicit-start trace hook. It needs GLOBAL tracing on to
438
+ fire its local 'return' hook, and global tracing costs a callback per
439
+ Python call — fine for the moment between the decorators and the
440
+ module's return, ruinous for a whole session: a script that calls
441
+ run() itself never returns from its module while the loop runs, and
442
+ every frame ran under the tracer (draw_text 26 ms for an empty line,
443
+ the chat window at 7 fps, 09-12)."""
444
+ if not _state.get('hooked'):
445
+ return
446
+ if sys.gettrace() is not _state.get('tracer'):
447
+ return # a debugger's tracer, not ours: leave it
448
+ sys.settrace(None)
449
+ frame = sys._getframe(1)
450
+ while frame is not None:
451
+ if frame.f_globals.get('__name__') == '__main__' and frame.f_code.co_name == '<module>':
452
+ frame.f_trace = None
453
+ frame = frame.f_back
454
+ _state['hooked'] = False
455
+
456
+
457
+ def run():
458
+ """Open every registered window and run until the last one closes."""
459
+ if _state['ran']:
460
+ return
461
+ _unhook_main_return()
462
+ _state['ran'] = True
463
+ if not _state['booted']:
464
+ boot()
465
+ import meltygui.core.windowing.window_api as glfw
466
+ _wait_imports()
467
+ _init_melty()
468
+ from meltygui.core.melty import Melty
469
+ from meltygui.core.windowing.surface import Surface
470
+ from meltygui.core.runtime.extensions import call
471
+ if _ROOTS:
472
+ call('root_ready', _ROOTS[0][1]['name'])
473
+ for fn, kw in _ROOTS:
474
+ view_kwargs = kw.get('view_kwargs') or {}
475
+ # A plain-function body draws straight onto the surface, so its
476
+ # decorator tint is the surface's; a render-func body gets its
477
+ # kwargs from _draw_root and sits on the default ground.
478
+ ground_tint = None if hasattr(fn, '__render_func__') else view_kwargs.get('tint')
479
+ Surface(kw['name'], _searchable_body(_root_body(fn, kw['name'], view_kwargs, config=kw)),
480
+ width=kw['width'], height=kw['height'], tint=ground_tint, on_close=kw.get('on_close'))
481
+ mark(f'{len(Surface.all)} window(s) created')
482
+ import meltygui.core.windowing.glfw_utils as glfw_utils
483
+ bench = os.environ.get('MELTY_BENCH')
484
+ first = True
485
+ frames = 0
486
+ frametime = [] if os.environ.get('MELTY_FRAMETIME') else None
487
+ glfw_utils.request_render()
488
+ try:
489
+ while Surface.all:
490
+ glfw.poll_events()
491
+ _open_requested_children()
492
+ # A frame only on a request (request_render: the input layer's
493
+ # callbacks, Surface's focus/resize/close hooks, animations via
494
+ # frames_left, post-render rendering, a window / white flag), as in
495
+ # the studio's loop; an idle app blocks in wait_events. The flag
496
+ # is cleared BEFORE the frame so a request made mid-frame
497
+ # remains for the next iteration. app_tick advances only with a
498
+ # frame: a child not drawn in a TICK closes (_close_stale_children).
499
+ if glfw_utils._needs_render.is_set():
500
+ glfw_utils._needs_render.clear()
501
+ Melty.app_tick += 1
502
+ frames += 1
503
+ started = time.perf_counter() if frametime is not None else 0.0
504
+ for surface in list(Surface.all):
505
+ try:
506
+ surface.frame()
507
+ except Exception:
508
+ _state['failed'] = True
509
+ raise
510
+ if frametime is not None:
511
+ # MELTY_FRAMETIME=1 prints a line per frame with the render
512
+ # thread's time for it (the budget for 120 fps is 8.3 ms).
513
+ spent = (time.perf_counter() - started) * 1000
514
+ print(f'meltygui: frame {frames} {spent:.1f} ms', flush=True)
515
+ _close_stale_children()
516
+ for surface in list(Surface.all):
517
+ _present_children(surface)
518
+ for surface in list(Surface.all):
519
+ if surface.closed:
520
+ _note_closed(surface)
521
+ surface.destroy()
522
+ if first and Surface.all:
523
+ first = False
524
+ mark('first frame presented')
525
+ _write_startup_log(_state['app_id'], ' '.join(s.name for s in Surface.all))
526
+ if bench:
527
+ break
528
+ if glfw_utils._needs_render.is_set():
529
+ continue
530
+ # glfw.wait_events also returns for events not asked for (on
531
+ # Hyprland the NVIDIA EGL driver's per-swap wl_buffer teardown is
532
+ # posted on the event queue), hence the gate above. Children
533
+ # follow their parent through the geometry feed, a child that
534
+ # requests nothing: poll while any exist. The idle wait is
535
+ # bounded so a signal (Ctrl+C) gets a turn: Python runs its
536
+ # handler between bytecodes, never inside a blocked OS call.
537
+ glfw.wait_events_timeout(1 / 60 if any(s.children for s in Surface.all) else 1.0)
538
+ finally:
539
+ _debug(f'{frames} frames rendered')
540
+ if not _state['failed']:
541
+ _flush_pending_saves()
542
+ _save_session()
543
+ for surface in list(Surface.all):
544
+ surface.destroy()
545
+ glfw.terminate()
546
+
547
+
548
+ def _flush_pending_saves():
549
+ """Write the edits the file hosts hold. A code_file_io host's save is
550
+ the studio's deferred model: each edit queues into PendingSave (in
551
+ memory) and the disk write happens at apply_all_saves, which the studio
552
+ runs from Melty.shutdown. An app that draws such hosts (the code editor)
553
+ exits through here, so flush here too — before the surfaces go, while
554
+ the imgui context the codecs' notifications expect is still alive. A
555
+ failed frame skips it: nothing written from a broken state."""
556
+ from meltygui.editor.pending_save import PendingSave
557
+ if not PendingSave.pending_saves:
558
+ return
559
+ _debug(f'flushing {len(PendingSave.pending_saves)} pending save(s)')
560
+ PendingSave.apply_all_saves()
561
+
562
+
563
+ def _save_session():
564
+ """Persist the draw states (app_session.save) — the studio's exit save,
565
+ for an app. Skipped after a failed frame like the pending saves: a
566
+ broken state must not replace the last good session."""
567
+ session = _state.get('session')
568
+ if session is None:
569
+ return
570
+ import meltygui.core.runtime.app_session as app_session
571
+ path = app_session.save(session, _state['app_id'])
572
+ _debug(f'session saved to {path}' if path else 'session save failed')
573
+
574
+
575
+ def _open_requested_children():
576
+ """Child surfaces the render wrapper asked for (glfw_window=True) since
577
+ the last tick: Melty.surface_requests, filled by surface_window_request."""
578
+ from meltygui.core.melty import Melty
579
+ from meltygui.core.windowing.surface import Surface
580
+ requests = Melty.surface_requests
581
+ while requests:
582
+ req = requests.pop(0)
583
+ parent = req.parent_surface
584
+ if parent is None or parent not in Surface.all or req.surface is not None or req.closed:
585
+ continue
586
+ ds = req.draw_state
587
+ size = req.window_size
588
+ child = Surface(req.name, _child_body(req), width=size[0], height=size[1],
589
+ parent=parent, draw_state=ds)
590
+ child.request = req
591
+ req.surface = child
592
+ _debug(f'child surface {child.title!r} of {parent.title!r} size={size}')
593
+
594
+
595
+ def _child_body(req):
596
+ def body(surface):
597
+ from meltygui.core.melty import Melty
598
+ Melty.draw_surface_root(req, surface)
599
+ return body
600
+
601
+
602
+ def _close_stale_children():
603
+ """Immediate mode: a child whose glfw_window=True call was not made
604
+ this tick closes (its parent stopped drawing it); the next call
605
+ reopens it."""
606
+ from meltygui.core.melty import Melty
607
+ for req in list(Melty.surface_windows.values()):
608
+ child = req.surface
609
+ if child is not None and req.tick != Melty.app_tick:
610
+ child.closed = True
611
+ child.stale = True
612
+
613
+
614
+ def _note_closed(surface, *, user_closed=True):
615
+ """A surface on its way out. An OS close of a child (the title bar's
616
+ X, the compositor) leaves its request CLOSED: the parent's calls
617
+ return (False, None) from then on, as a closed meltygui window's do. A
618
+ stale child (not drawn this tick) just drops its surface."""
619
+ req = surface.request
620
+ if req is not None:
621
+ req.surface = None
622
+ if not getattr(surface, 'stale', False):
623
+ was_closed = req.closed
624
+ req.closed = True
625
+ if req.draw_state is not None:
626
+ from meltygui.core.windowing.window_visibility import native_user_window_closed
627
+ if user_closed and not was_closed:
628
+ native_user_window_closed(req.draw_state, True)
629
+ req.draw_state.closed = True
630
+ for child in list(surface.children):
631
+ _note_closed(child, user_closed=False)
632
+
633
+
634
+ ACK_TIMEOUT_S = 0.5
635
+
636
+
637
+ def _debug(msg):
638
+ if os.environ.get('MELTY_DEBUG'):
639
+ print(f'[app] {msg}', flush=True)
640
+
641
+
642
+ def _present_children(parent):
643
+ """Children follow their parent exactly as nested meltygui windows do:
644
+ the request's window_pos is the parent-relative offset. Each tick the
645
+ child's target rect = the parent's screen rect (geometry feed) + the
646
+ offset, sent to the compositor when it differs from what was last
647
+ sent (Hyprland). A child the USER dragged — its feed rect moved while
648
+ the parent's did not, once our own last placement was acknowledged —
649
+ adopts the new offset, unless window_pos= pinned it. GNOME: rects are
650
+ read but never sent."""
651
+ if not parent.children:
652
+ return
653
+ import meltygui.core.windowing.window_api as glfw
654
+ import meltygui.core.windowing.geometry_feed as geometry_feed
655
+ import meltygui.core.windowing.titlebar as titlebar
656
+ import meltygui.core.windowing.wayland_move as wayland_move
657
+ prect = geometry_feed.surface_rect(parent.title)
658
+ if prect is None:
659
+ return
660
+ now = time.monotonic()
661
+ parent_moved = parent.seen_rect is not None and tuple(prect[:2]) != tuple(parent.seen_rect[:2])
662
+ parent.seen_rect = prect
663
+ for child in list(parent.children):
664
+ req = child.request
665
+ if req is None or child.window is None:
666
+ continue
667
+ # Retry a missing relationship, including surfaces created before
668
+ # the set_parent fix was hotswapped into the running app.
669
+ if (getattr(child, 'toplevel', None) and parent.toplevel
670
+ and not getattr(child, 'parent_linked', False)):
671
+ child.parent_linked = wayland_move.set_parent(child.toplevel, parent.toplevel)
672
+ crect = geometry_feed.surface_rect(child.title)
673
+ child_moved = (crect is not None and child.seen_rect is not None
674
+ and tuple(crect[:2]) != tuple(child.seen_rect[:2]))
675
+ child.seen_rect = crect
676
+ if child.await_ack and child.last_sent_rect is not None:
677
+ if (crect is not None and tuple(crect[:2]) == tuple(child.last_sent_rect[:2])) \
678
+ or now - child.sent_at > ACK_TIMEOUT_S:
679
+ child.await_ack = False
680
+ child_moved = False # that was our placement landing
681
+ if (child_moved and not parent_moved and not req.pinned and not child.await_ack
682
+ and child.last_sent_rect is not None):
683
+ req.window_pos = (crect[0] - prect[0], crect[1] - prect[1])
684
+ from meltygui.core.windowing.window_visibility import native_user_window_position
685
+ native_user_window_position(req.draw_state, req.window_pos)
686
+ _debug(f'child {child.title!r} dragged: offset now {req.window_pos}')
687
+ pos = req.window_pos
688
+ width, height = glfw.get_window_size(child.window)
689
+ if geometry_feed.hypr_honors_geometry():
690
+ # The box is the CONTENT: the surface less the shadow inset.
691
+ child.activate()
692
+ inset = int(titlebar.window_inset())
693
+ width, height = max(1, width - 2 * inset), max(1, height - 2 * inset)
694
+ target = (int(prect[0] + pos[0]), int(prect[1] + pos[1]), int(width), int(height))
695
+ # Following a parent's POSITION only. Reissuing GLFW's size
696
+ # through the compositor races pending edge-solver commits and
697
+ # turns an ordinary resize into a second, foreign resize.
698
+ if child.last_sent_rect is None or target[:2] != child.last_sent_rect[:2]:
699
+ if crect is not None and tuple(crect[:2]) == target[:2]:
700
+ child.last_sent_rect = target # already there
701
+ elif geometry_feed.place_window(child.title, target, resize=False):
702
+ _debug(f'place {child.title!r} at {target} (parent {prect[:2]} + {pos})')
703
+ child.last_sent_rect = target
704
+ child.await_ack = True
705
+ child.sent_at = now
706
+
707
+
708
+ # Window input -----------------------------------------------------------------------------
709
+ _MODS = {'ctrl': 2, 'control': 2, 'shift': 1, 'alt': 4, 'super': 8, 'meta': 8}
710
+
711
+
712
+ def pressed(combo):
713
+ """Edge-triggered ``'ctrl+s'``-style check against this frame's key
714
+ events of the active window (GLFW press + repeat, so a held chord
715
+ repeats). Modifiers must match exactly."""
716
+ import meltygui.core.windowing.window_api as glfw
717
+ from meltygui.core.melty import Melty
718
+ parts = [p.strip().lower() for p in combo.split('+') if p.strip()]
719
+ mods = 0
720
+ key = None
721
+ for p in parts:
722
+ if p in _MODS:
723
+ mods |= _MODS[p]
724
+ else:
725
+ key = getattr(glfw, 'KEY_' + p.upper(), None)
726
+ if key is None:
727
+ raise ValueError(f'unknown key in {combo!r}: {p}')
728
+ mask = 0xF
729
+ return any(k == key and (m & mask) == mods for k, m in Melty.frame_key_events)
730
+
731
+
732
+ def content_size():
733
+ """The (width, height) a root-level view fills in the active window."""
734
+ from meltygui.core.melty import Melty
735
+ return Melty.root_fill