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,643 @@
1
+ """OS-window surfaces: one meltygui frame per GLFW window (app.py's @glfw_window).
2
+
3
+ A Surface owns everything that belongs to ONE OS window: the GLFW window and
4
+ its GL context (all surfaces share one GL share group, rooted at app.py's
5
+ hidden owner window), an imgui context sharing the owner's font atlas, the
6
+ SplitOverlayRenderer, the GLFW-callback input backend and its InputHandler,
7
+ the tile cache (FBO-bound), the fp16 scene target, and the frameless-window
8
+ chrome state (titlebar.py, os_frame.py).
9
+
10
+ meltygui was written for one window: that per-window state lives in `Melty`
11
+ class attributes and in module globals of titlebar / os_frame / scene_target
12
+ / wayland_move / wayland_color / mouse_cursor. Surfaces render strictly one
13
+ after another, so instead of threading a window through thousands of call
14
+ sites, `activate()` swaps that state: the previously active surface stashes
15
+ the live values, this one restores its own. Everything rebuilt inside
16
+ Melty.begin_frame / end_frame (layers, stacks, events) needs no swapping —
17
+ see the catalog in app.py's history. The studio, which never creates a
18
+ Surface, is untouched: with no surface active nothing is ever swapped.
19
+
20
+ Frame (``frame()``): the studio's root-window ritual (new_core_view.
21
+ draw_melty_windows) with the meltygui title bar, window controls, corner cut
22
+ and drop shadow when the window is frameless (Toggles.Melty.
23
+ wayland_show_frame off), and the body drawn inline into the root. Views the
24
+ body draws at root level fill the window (Melty.root_fill, consumed by the
25
+ render wrapper) so a one-view body needs no width/height.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ import copy
30
+ import time
31
+ from collections import defaultdict
32
+ from types import SimpleNamespace
33
+
34
+ import meltygui.core.windowing.window_api as glfw
35
+ import meltygui_imgui as imgui
36
+ import OpenGL.GL as gl
37
+
38
+ from meltygui.core.melty import Melty
39
+ import meltygui.core.input.mouse_cursor as mouse_cursor
40
+ import meltygui.core.windowing.os_frame as os_frame
41
+ import meltygui.core.graphics.scene_target as scene_target
42
+ import meltygui.core.windowing.titlebar as titlebar
43
+ import meltygui.core.windowing.titlebar_buttons as titlebar_buttons
44
+ import meltygui.core.graphics.wayland_color as wayland_color
45
+ import meltygui.core.windowing.wayland_move as wayland_move
46
+ import meltygui.core.input.input_handler as input_handler
47
+ from meltygui.core.runtime.toggles import Toggles
48
+ from meltygui.core.diagnostics.fps_counter import FpsCounter
49
+ import meltygui.utils.render_utils as views
50
+ from meltygui.core.windowing.glfw_utils import request_render
51
+
52
+ # --- Per-window state -----------------------------------------------------------
53
+ # Melty class attributes that persist across frames and belong to one window.
54
+ MELTY_ATTRS = ('glfw_window', 'vis', 'framebuffer_size', 'frame_inset', 'frame_origin',
55
+ 'dynamic_style_gl',
56
+ 'root_draw_states', 'root_draw_states_by_layer', 'cache', 'backend',
57
+ 'event_handler', 'frame_key_events', 'hovered_ds', 'imgui_main_window_hovered',
58
+ 'glfw_close_requested', 'any_window_hovered', 'any_window_hovered_pending',
59
+ 'filter', # its executor's VAO is per GL context
60
+ # The hit-test tree and the focus slots are per OS window: boxes
61
+ # are window-local coordinates, so one shared tree had window B's
62
+ # views answering window A's hit test at the same local point (and
63
+ # a closed window's boxes out of sync - the 09-12 two-roots repro).
64
+ '_bvh', '_bvh_id_to_ds', '_bvh_gen', '_bvh_query_cache', '_bvh_query_cache_gen',
65
+ 'bvh_hover_ids', 'focused_ds', 'text_focused_ds', 'popover_focused_ds')
66
+ # Module globals that assume one window (titlebar/os_frame resolve "the window"
67
+ # through Melty.glfw_window, so with the swap they see this window's).
68
+ MODULE_GLOBALS = {
69
+ titlebar: ('_pressed_button', '_wm_move_started', '_rdrag', '_corner_gl',
70
+ '_input_rect_applied', '_geometry_applied', '_self_resize',
71
+ '_pending_surface_size', '_pending_surface_offset', '_frame_surface_offset',
72
+ '_pending_surface_fit', '_pending_surface_wait', '_last_surface_size'),
73
+ os_frame: ('_STATE',),
74
+ scene_target: ('_STATE',),
75
+ wayland_move: ('_STATE',),
76
+ wayland_color: ('_STATE',),
77
+ mouse_cursor: ('_applied', '_applied_immediate', '_requested', '_external'),
78
+ input_handler: ('_BUTTON_PROBE',),
79
+ }
80
+ _DEFAULTS = None # Default module globals, captured before the first surface
81
+ _DEBUG = bool(__import__('os').environ.get('MELTY_DEBUG'))
82
+
83
+
84
+ def _capture_defaults():
85
+ global _DEFAULTS
86
+ if _DEFAULTS is not None:
87
+ return
88
+ _DEFAULTS = {}
89
+ for mod, names in MODULE_GLOBALS.items():
90
+ for name in names:
91
+ value = mod.__dict__[name]
92
+ try:
93
+ _DEFAULTS[(mod, name)] = copy.deepcopy(value)
94
+ except Exception:
95
+ _DEFAULTS[(mod, name)] = value
96
+
97
+
98
+ ROOT_FLAGS = (imgui.WINDOW_NO_BACKGROUND | imgui.WINDOW_NO_TITLE_BAR | imgui.WINDOW_NO_RESIZE
99
+ | imgui.WINDOW_NO_MOVE | imgui.WINDOW_NO_SCROLLBAR | imgui.WINDOW_NO_NAV_FOCUS
100
+ | imgui.WINDOW_NO_BRING_TO_FRONT_ON_FOCUS | imgui.WINDOW_NO_NAV_INPUTS
101
+ | imgui.WINDOW_NO_NAV | imgui.WINDOW_NO_COLLAPSE | imgui.WINDOW_NO_SAVED_SETTINGS
102
+ | imgui.WINDOW_NO_SCROLL_WITH_MOUSE)
103
+ ROOT_MAX_BG_VALUE = 0.130 # draw_main's max_bg_value: the ground's brightness cap
104
+
105
+
106
+ class Surface:
107
+ all: list = [] # open surfaces in creation order
108
+ active = None
109
+ owner_window = None # app.py's hidden share-group root
110
+ owner_context = None # the imgui context that owns the font atlas
111
+ app_id = 'meltygui'
112
+ session = None # the AppSession app.py loaded (app_session.py), if any
113
+
114
+ def __init__(self, name, body, *, width=1280, height=800, parent=None,
115
+ draw_state=None, tint=None, on_close=None):
116
+ """``name`` is the window's name AND its OS title (a child's `##suffix`
117
+ is stripped from the title); ``width`` / ``height`` the content size —
118
+ the universal meltygui names, as on `@window` and every view.
119
+ ``body(surface)`` draws the window's content inline into the root.
120
+ ``parent``: the Surface this one is a child of (glfw_window=True calls);
121
+ ``draw_state``: the child's root draw_state (its window_pos/size are the
122
+ parent-relative geometry, exactly as for a closable meltygui window);
123
+ ``tint``: the root ground's tint (None: Toggles.Melty.app_root_tint)."""
124
+ _capture_defaults()
125
+ self.name, self.body, self.parent, self.draw_state = name, body, parent, draw_state
126
+ self.tint = tint
127
+ self.on_close = on_close # asked when an OS close lands; False keeps the window
128
+ width, height = int(width), int(height)
129
+ self.title = _unique_title(name.split('##')[0])
130
+ self.request = None # the melty.surface_children entry of a child
131
+ self.toplevel = None # xdg_toplevel proxy (wayland_move), for set_parent
132
+ self.parent_linked = False
133
+ self.await_ack = False # a rect sent, the parent yet to show it
134
+ self.sent_at = 0.0
135
+ self.seen_rect = None # the feed's rect last tick (present_children)
136
+ self.stale = False # closing because its call stopped, not an OS close
137
+ self.children: list = []
138
+ self.frames = 0
139
+ self.fps_counter = FpsCounter()
140
+ self.closed = False
141
+ self.last_sent_rect = None
142
+ self.chrome = not titlebar.wants_os_decoration()
143
+ transparent = titlebar.wants_transparent_framebuffer()
144
+ self.transparent = transparent
145
+
146
+ glfw.default_window_hints()
147
+ glfw.window_hint(glfw.VISIBLE, True)
148
+ glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
149
+ glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
150
+ glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
151
+ glfw.window_hint(glfw.TRANSPARENT_FRAMEBUFFER, transparent)
152
+ glfw.window_hint(glfw.ALPHA_BITS, 8 if transparent else 0)
153
+ glfw.window_hint(glfw.DECORATED, glfw.FALSE if self.chrome else glfw.TRUE)
154
+ glfw.window_hint(glfw.STENCIL_BITS, 8)
155
+ glfw.window_hint(glfw.RESIZABLE, True)
156
+ try:
157
+ glfw.window_hint_string(glfw.WAYLAND_APP_ID, Surface.app_id)
158
+ glfw.window_hint_string(glfw.X11_CLASS_NAME, Surface.app_id)
159
+ except Exception:
160
+ pass
161
+ share = Surface.owner_window
162
+ self.window = glfw.create_window(width, height, self.title, None, share)
163
+ if not self.window:
164
+ raise SystemExit('glfw.create_window failed')
165
+
166
+ # Fresh per-window state: module defaults + a clean Melty set.
167
+ self._mods = {key: _fresh(value) for key, value in _DEFAULTS.items()}
168
+ self.root_windows = {} # live collision roots belonging to this surface window
169
+ # vis.root is what Melty reads and stores off (draw_state_registry,
170
+ # surface_windows): the user's persisted session (app_session.py)
171
+ # when app.py loaded one, else a stand-in around the shared registry.
172
+ root = Surface.session
173
+ if root is None:
174
+ registry = Melty.draw_state_registry
175
+ if registry is None:
176
+ registry = Melty.draw_state_registry = {}
177
+ root = SimpleNamespace(draw_state_registry=registry)
178
+ self._melty = dict(
179
+ glfw_window=self.window,
180
+ vis=SimpleNamespace(root=root,
181
+ window=self.window, tracked_keys=[], first_frame_keys=set(),
182
+ fa_font=None),
183
+ framebuffer_size=None, frame_inset=0, frame_origin=(0, 0),
184
+ root_draw_states=defaultdict(list), root_draw_states_by_layer=defaultdict(list),
185
+ cache=None, backend=None, dynamic_style_gl=None, event_handler=input_handler.InputHandler(),
186
+ frame_key_events=[], hovered_ds=None, imgui_main_window_hovered=False,
187
+ glfw_close_requested=False, any_window_hovered=False, any_window_hovered_pending=False,
188
+ filter=type(Melty.filter)(),
189
+ _bvh=type(Melty._bvh)(), _bvh_id_to_ds={}, _bvh_gen=0, _bvh_query_cache={},
190
+ _bvh_query_cache_gen=-1, bvh_hover_ids=set(),
191
+ focused_ds=None, text_focused_ds=None, popover_focused_ds=None)
192
+
193
+ # imgui context sharing the owner's atlas (the owner context OWNS it,
194
+ # so this surface can die without taking the atlas with it).
195
+ owner_io = _owner_io()
196
+ self.ctx = imgui.create_context(shared_font_atlas=owner_io.fonts)
197
+ # create_context only makes the new context current when NONE is
198
+ # (the owner's is), so select ours before touching its io.
199
+ imgui.set_current_context(self.ctx)
200
+ imgui.get_io().ini_file_name = None
201
+ # The renderer uploads the atlas in its constructor, BEFORE it stamps
202
+ # display_size: the FreeType hinting pass (fonts.hint_atlas) frames on
203
+ # the current context and dies on the (-1, -1) default.
204
+ imgui.get_io().display_size = (float(width), float(height))
205
+ self.activate()
206
+ titlebar.note_surface_size(self.window)
207
+ glfw.swap_interval(1)
208
+ from meltygui.core.graphics.overlay_renderer import SplitOverlayRenderer
209
+ from meltygui.core.cache.tile_cache import TileCacheMasked
210
+ self.impl = SplitOverlayRenderer(self.window)
211
+ Melty.init_input_backend(self.window)
212
+ Melty.cache = TileCacheMasked()
213
+ # The blit cache starts OFF and resize() switches it on after the
214
+ # second frame, the studio's schedule (lsd_studio: frame_count 2).
215
+ # Off, nothing is ever tile-cached: every view, every RenderHost
216
+ # body and draw_text reverts on every frame and freeze_resize
217
+ # never engages (09-13, melty_code_editor: 18 ms hover frames).
218
+ # The popover artifacts that kept it off (row tiles blank, a black
219
+ # block under a context menu, 09-12) were captures of a menu
220
+ # opened off the display's right edge served after it was pinned
221
+ # back in - TileCacheMasked._fill_unfilled now re-renders those.
222
+ Melty.cache.enabled = False
223
+ if glfw.get_platform() == glfw.PLATFORM_WAYLAND:
224
+ # The tag is the window's stable identity for the compositor
225
+ # (remembered floating state key on app id + tag), the name
226
+ # before its uniqueness suffix.
227
+ wayland_move.attach(self.window, tag=name.split('##')[0])
228
+ self.toplevel = wayland_move.toplevel_proxy()
229
+ self.hdr_tagged = wayland_color.attach_window(self.window)
230
+ else:
231
+ self.hdr_tagged = False
232
+ if parent is not None:
233
+ parent.children.append(self)
234
+ if self.toplevel and parent.toplevel:
235
+ self.parent_linked = wayland_move.set_parent(self.toplevel, parent.toplevel)
236
+ self._hook_callbacks()
237
+ Surface.all.append(self)
238
+ # Install the shadow margin BEFORE the first buffer is committed. A
239
+ # deferred IPC resize overwrites Hyprland's restored content size and
240
+ # first maps a content box smaller by twice the inset. Grow only the
241
+ # native surface here; subsequent compositor configures become the
242
+ # authority for the content size (on_surface_resized adds the margin).
243
+ inset = int(titlebar.window_inset()) if self.chrome else 0
244
+ if inset > 0:
245
+ content_width, content_height = glfw.get_window_size(self.window)
246
+ titlebar.set_surface_size(self.window, content_width + 2 * inset,
247
+ content_height + 2 * inset, box=False)
248
+ titlebar.sync_window_geometry(self.window)
249
+
250
+ # --- activation ----------------------------------------------------------------
251
+ def _stash(self):
252
+ for name in MELTY_ATTRS:
253
+ self._melty[name] = getattr(Melty, name)
254
+ for (mod, name) in self._mods:
255
+ self._mods[(mod, name)] = mod.__dict__[name]
256
+
257
+ def _restore(self):
258
+ # A surface created before the dynamic-style code was hotswapped in
259
+ # must not inherit another surface's GL resources.
260
+ self._melty.setdefault('dynamic_style_gl', None)
261
+ for name, value in self._melty.items():
262
+ setattr(Melty, name, value)
263
+ for (mod, name), value in self._mods.items():
264
+ mod.__dict__[name] = value
265
+
266
+ def activate(self):
267
+ """Make this surface's window, GL context, imgui context and per-window
268
+ state current. Cheap when already active."""
269
+ if Surface.active is not self:
270
+ if _DEBUG:
271
+ print(f'[surface] activate {getattr(Surface.active, "title", None)!r} -> {self.title!r}', flush=True)
272
+ if Surface.active is not None:
273
+ Surface.active._stash()
274
+ Surface.active = self
275
+ self._restore()
276
+ glfw.make_context_current(self.window)
277
+ imgui.set_current_context(self.ctx)
278
+
279
+ def _hook_callbacks(self):
280
+ """GLFW callbacks fire inside poll_events — and inside any GLFW call
281
+ that dispatches Wayland events (set_window_size, a roundtrip), i.e.
282
+ possibly while ANOTHER surface is mid-frame. The input backend
283
+ appends to Melty.frame_key_events and titlebar's resize hook reads
284
+ its globals, so chain a wrapper in front of every callback that
285
+ loads OUR per-window state for the duration of the callback and
286
+ puts the interrupted surface's back afterwards. Never the GL or
287
+ imgui context: the interrupted frame keeps rendering into its own."""
288
+ def hook(setter, extra=None):
289
+ prev = None
290
+
291
+ def wrapper(window, *args):
292
+ interrupted = Surface.active
293
+ if interrupted is not self:
294
+ if interrupted is not None:
295
+ interrupted._stash()
296
+ Surface.active = self
297
+ self._restore()
298
+ try:
299
+ if extra is not None:
300
+ extra(*args)
301
+ if prev is not None:
302
+ prev(window, *args)
303
+ finally:
304
+ if interrupted is not self:
305
+ self._stash()
306
+ Surface.active = interrupted
307
+ if interrupted is not None:
308
+ interrupted._restore()
309
+ prev = setter(self.window, wrapper)
310
+
311
+ hook(glfw.set_key_callback)
312
+ hook(glfw.set_char_callback)
313
+ hook(glfw.set_mouse_button_callback)
314
+ hook(glfw.set_scroll_callback)
315
+ hook(glfw.set_cursor_pos_callback)
316
+ hook(glfw.set_cursor_enter_callback)
317
+ hook(glfw.set_window_size_callback)
318
+ hook(glfw.set_window_focus_callback, self._on_focus)
319
+ hook(glfw.set_framebuffer_size_callback, self._on_framebuffer_size)
320
+ hook(glfw.set_window_close_callback, lambda *_: request_render())
321
+
322
+ def _on_focus(self, focused):
323
+ request_render()
324
+ if focused:
325
+ # The user may have changed the desktop's title-bar buttons in
326
+ # the meantime (titlebar_buttons: rate-limited re-read).
327
+ titlebar_buttons.refresh()
328
+ # ... or flipped left-drag move in the desktop's Settings.
329
+ import meltygui.core.input.hypr_left_drag as hypr_left_drag
330
+ hypr_left_drag.refresh()
331
+
332
+ def _on_framebuffer_size(self, width, height):
333
+ request_render()
334
+ applied = titlebar.on_surface_resized(self.window, width, height)
335
+ if _DEBUG:
336
+ print(f'[surface {self.title}] framebuffer {width}x{height} '
337
+ f'self_resize={titlebar._self_resize} regrow={applied}', flush=True)
338
+
339
+ # --- render ---------------------------------------------------------------------
340
+ def _draw_render_hosts(self):
341
+ """The studio's draw_main draws every registered RenderHost once a
342
+ frame (RenderHost.draw_all); that is what loads a code_file_io host's
343
+ file, reparses it after an edit and auto-saves it. A meltygui app's body
344
+ is a plain draw function, so the surface runs the pump for it, after
345
+ the body and inside the imgui frame. Hosts are process-global: once
346
+ per app tick, however many surfaces draw in it."""
347
+ if not Melty.render_hosts or Melty.render_hosts_tick == Melty.app_tick:
348
+ return
349
+ Melty.render_hosts_tick = Melty.app_tick
350
+ from meltygui.core.conversion.render_host import RenderHost
351
+ RenderHost.draw_all()
352
+
353
+ def frame(self):
354
+ self.activate()
355
+ frame_started = self.fps_counter.frame_started()
356
+ if glfw.window_should_close(self.window):
357
+ # The window's ×, the compositor's close and set_window_should_close
358
+ # all land here; a window's on_close may decline (and hide instead).
359
+ if self.on_close is not None and self.on_close(self) is False:
360
+ glfw.set_window_should_close(self.window, False)
361
+ else:
362
+ self.closed = True
363
+ return
364
+ titlebar.apply_pending_surface_size(self.window)
365
+ self.impl.process_inputs()
366
+ fb_w, fb_h = glfw.get_framebuffer_size(self.window)
367
+ if fb_w == 0 or fb_h == 0:
368
+ return
369
+ scene_target.begin(fb_w, fb_h)
370
+ transparent = titlebar._frame_transparent(self.window)
371
+ gl.glClearColor(0.0, 0.0, 0.0, 0.0 if transparent else 1.0)
372
+ gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT | gl.GL_STENCIL_BUFFER_BIT)
373
+ Melty.apply_ui_scale(self.impl)
374
+ views.new_frame()
375
+ io = imgui.get_io()
376
+ disp_w, disp_h = io.display_size
377
+ imgui.set_next_window_position(0, 0)
378
+ imgui.set_next_window_size(disp_w, disp_h)
379
+ imgui.push_style_var(imgui.STYLE_WINDOW_PADDING, (0.0, 0.0))
380
+ imgui.push_style_var(imgui.STYLE_WINDOW_BORDERSIZE, 0.0)
381
+ views.begin('main##window_melty', closable=False, flags=ROOT_FLAGS)
382
+ imgui.pop_style_var(2)
383
+ # meltygui only routes pointer events to apps while the root imgui
384
+ # window is hovered (draw_state.hover_eligible).
385
+ Melty.imgui_main_window_hovered = imgui.is_window_hovered()
386
+ Melty.begin_frame()
387
+ # Standalone apps do not run the studio's draw_main. Commit source
388
+ # edits deferred while a picker/slider held the pointer here too.
389
+ from meltygui.core.rendering.parameter_core import flush_deferred_writes
390
+ flush_deferred_writes()
391
+ if self.chrome:
392
+ os_frame.begin_frame()
393
+ titlebar.poll_os_window_drag()
394
+ os_frame.solve()
395
+ imgui.set_cursor_screen_pos((0, 0))
396
+ imgui.set_item_allow_overlap()
397
+ draw_list = imgui.get_window_draw_list()
398
+ draw_list.channels_split(Melty.max_depth)
399
+ Melty.channels_split = True
400
+ Melty.window_stack.append((self.name, True))
401
+
402
+ radius = titlebar.frame_corner_radius() if transparent else 0.0
403
+ previous_tint, bg_color = self._root_background(disp_w, disp_h, radius)
404
+ if transparent and Toggles.Melty.window_shadow_lift > 0:
405
+ from meltygui.core.cache.tile_cache import add_shadow
406
+ add_shadow((0, 0, disp_w, disp_h), offset=0.5, corner_radius=radius, clip=False)
407
+
408
+ top = titlebar.top_inset() if self.chrome else 0.0
409
+ imgui.set_cursor_screen_pos((0, top))
410
+ # The root fills the OS MODEL's size (os_frame.content_size): equal
411
+ # to the display except while our own resize is still landing.
412
+ fill_w, fill_h = os_frame.content_size((disp_w, disp_h)) if self.chrome else (disp_w, disp_h)
413
+ Melty.root_fill = (float(fill_w), float(fill_h) - top, float(top)) # (w, h below the chrome, top y)
414
+ Melty.root_fill_used = False
415
+ # The body runs INSIDE the ground, as the studio's windows run inside
416
+ # draw_main's show_bg: one bg depth down, the ground's tint and colour
417
+ # on the stacks draw_bg reads for the bleed (the wrapper's own fill).
418
+ Melty.bg_depth += 1
419
+ Melty.bg_stack.append(Melty.style_manager.get_tint())
420
+ Melty.bg_color_stack.append(bg_color)
421
+ try:
422
+ self.body(self)
423
+ self._draw_render_hosts()
424
+ finally:
425
+ Melty.root_fill = None
426
+ Melty.bg_depth -= 1
427
+ Melty.bg_stack.pop()
428
+ Melty.bg_color_stack.pop()
429
+ Melty.style_manager.set_imgui_tint(*previous_tint)
430
+ if self.chrome:
431
+ titlebar.paint_window_controls(draw_list)
432
+ titlebar.note_fps_label(self.fps_counter.label(), self.chrome)
433
+ Melty.end_frame()
434
+ if self.request is not None:
435
+ Melty.finish_surface_root(self.request, self)
436
+ if self.chrome:
437
+ os_frame.flush()
438
+ Melty.window_stack.pop()
439
+ draw_list.channels_merge()
440
+ Melty.channels_split = False
441
+ views.end()
442
+ if self.chrome:
443
+ titlebar.draw_titlebar(self.window)
444
+ views.end_frame()
445
+ try:
446
+ Melty.post_frame(self.impl, self.window) # imgui render, shadow, corner cut, PQ, swap
447
+ except Exception:
448
+ if _DEBUG:
449
+ import ctypes
450
+ cur = glfw.get_current_context()
451
+ cg = titlebar._corner_gl
452
+ print(f'[surface {self.title}] post_frame FAILED ctx={ctypes.cast(cur, ctypes.c_void_p).value:#x} '
453
+ f'mine={ctypes.cast(self.window, ctypes.c_void_p).value:#x} corner_gl={id(cg):#x} '
454
+ f'corner_cache={getattr(cg, "_cache", None) and list(getattr(cg, "_cache").keys())} '
455
+ f'mods_corner={id(self._mods[(titlebar, "_corner_gl")]):#x} active={Surface.active.title!r}',
456
+ flush=True)
457
+ raise
458
+ self.fps_counter.frame_finished(frame_started)
459
+ self.frames += 1
460
+ if self.frames == 2 and not Melty.cache.enabled:
461
+ Melty.cache.set_enabled(True)
462
+
463
+ def _root_background(self, w, h, radius):
464
+ """The window's ground: what the studio's Main Window paints under
465
+ its windows (draw_main's show_bg — draw_bg at depth 0 under the root
466
+ tint, capped at the same max_bg_value), edge to edge with the alpha
467
+ cut's corner radius and no outline stroke. Sets the style tint for
468
+ the body; returns (the tint to restore, the ground's colour)."""
469
+ from meltygui.view.decoration_view import draw_bg
470
+ style_manager = Melty.style_manager
471
+ previous_tint = style_manager.get_tint()
472
+ tint = self.tint if self.tint is not None else Toggles.Melty.app_root_tint
473
+ style_manager.set_imgui_tint(*tint[:4])
474
+ _, bg_color = draw_bg(bypass=True, left=0, top=0, width=w, height=h, rounding=radius,
475
+ outline=False, opacity=1.0, max_bg_value=ROOT_MAX_BG_VALUE,
476
+ depth=Melty.shadow_depth, style_manager=style_manager)
477
+ return previous_tint, bg_color
478
+
479
+ # --- geometry (children) ---------------------------------------------------------
480
+ def content_size(self):
481
+ return glfw.get_framebuffer_size(self.window)
482
+
483
+ # --- teardown --------------------------------------------------------------------
484
+ def destroy(self):
485
+ # A parent destroys its children recursively. The app shutdown loop
486
+ # may still hold those children in its snapshot of Surface.all.
487
+ if self not in Surface.all:
488
+ return
489
+ if _DEBUG:
490
+ print(f'[surface] destroy {self.title!r} (active={getattr(Surface.active, "title", None)!r})', flush=True)
491
+ for child in list(self.children):
492
+ child.destroy()
493
+ if self.parent is not None and self in self.parent.children:
494
+ self.parent.children.remove(self)
495
+ self.activate()
496
+ from meltygui.core.graphics.gl_state import GLState
497
+ from meltygui.core.graphics.gl_state import current_context
498
+ context = current_context()
499
+ try:
500
+ if len(Surface.all) > 1:
501
+ # Other surfaces live on: the renderer's shutdown would
502
+ # delete the shader program and the font texture, SHARED
503
+ # across the GL share group. Drop only this context's own
504
+ # vertex objects.
505
+ impl = self.impl
506
+ if getattr(impl, '_vao_handle', -1) > 0:
507
+ gl.glDeleteVertexArrays(1, [impl._vao_handle])
508
+ for name in ('_vbo_handle', '_elements_handle'):
509
+ handle = getattr(impl, name, -1)
510
+ if handle > 0:
511
+ gl.glDeleteBuffers(1, [handle])
512
+ impl._vao_handle = impl._vbo_handle = impl._elements_handle = 0
513
+ else:
514
+ self.impl.shutdown()
515
+ except Exception:
516
+ pass
517
+ # This surface's GL objects, deleted while it's still current: the
518
+ # scene target, the chrome's GLState and the shader filter. Whatever
519
+ # else is queued for it later (the GLState updates after the window
520
+ # is gone) is discarded below - its names die with the context.
521
+ GLState.release_context(context)
522
+ scene_target.shutdown()
523
+ for state in (titlebar._corner_gl, scene_target._STATE.get('gl'), Melty.dynamic_style_gl):
524
+ if state is not None:
525
+ try:
526
+ state.release()
527
+ except Exception:
528
+ pass
529
+ try:
530
+ Melty.filter.cleanup()
531
+ except Exception:
532
+ pass
533
+ try:
534
+ GLState.flush_deletes()
535
+ except Exception:
536
+ pass
537
+ # This window's Wayland objects, released while its proxies are alive
538
+ # (glfw.destroy_window takes the wl_surface): the HDR buffer / feedback
539
+ # / description, and wayland_move's per-window references. The
540
+ # connection-wide seat, pointer and managers stay for the others.
541
+ if self.hdr_tagged or glfw.get_platform() == glfw.PLATFORM_WAYLAND:
542
+ try:
543
+ wayland_color.detach()
544
+ except Exception:
545
+ pass
546
+ try:
547
+ wayland_move.detach(self.window)
548
+ except Exception:
549
+ pass
550
+ if self in Surface.all:
551
+ Surface.all.remove(self)
552
+ Surface.active = None
553
+ # The imgui context must not be current when destroyed.
554
+ imgui.set_current_context(Surface.owner_context)
555
+ imgui.destroy_context(self.ctx)
556
+ glfw.destroy_window(self.window)
557
+ self.window = None
558
+ GLState.discard_context(context)
559
+ if Surface.all:
560
+ Surface.all[0].activate()
561
+ elif Surface.owner_window is not None:
562
+ glfw.make_context_current(Surface.owner_window)
563
+
564
+
565
+ def root_view_kwargs(name, /, **kwargs):
566
+ """The kwargs that draw a render func as the active surface's ROOT meltygui
567
+ window: a closable window pinned to the OS window (layouts — draw_rows /
568
+ draw_columns — register their edges on the enclosing WINDOW, so the
569
+ root must be one), sized to it, never dragged, its own close and shadow
570
+ off (the OS window has the chrome). Shared by app._draw_root (a
571
+ `@glfw_window` over a render func) and Melty.draw_surface_root (a
572
+ `glfw_window=True` child).
573
+
574
+ ``with_header=`` (the decorator's, the call's) puts the MELTY HEADER in
575
+ the chrome row — the same header a studio window wears, beside the OS
576
+ window's controls: `show_name` names it after the OS window's title
577
+ (`display_name`, live through a retitle), the tint chip edits the
578
+ view's tint, and the geometry keeps clear of the controls on both sides
579
+ — `header_indent` past a left-side group, and a `with_header_end` that
580
+ only claims the right group's width (titlebar.draw_header_controls), so
581
+ the wrapper right-aligns and clips the header exactly as it does
582
+ around a header's close button. No collapse arrow (`is_tree=False`): an
583
+ OS window does not fold to its header. Without a header the body
584
+ starts under the control row (titlebar.top_inset).
585
+
586
+ Caller kwargs win over every pinned value (``show_header=False`` hides
587
+ a passed header, ``disable_scroll=False`` scrolls the root, a ``name=``
588
+ the fallback ``name`` — positional-only, so a child's own kwargs pass)."""
589
+ width, height, top = Melty.root_fill
590
+ header = kwargs.get('with_header') is not None
591
+ if header:
592
+ surface = Surface.active
593
+ chrome = surface is not None and surface.chrome
594
+ left_inset, right_inset = titlebar.chrome_insets() if chrome else (0.0, 0.0)
595
+ # The header row IS the chrome row: the view starts at the very
596
+ # top and the body is immediately under the header.
597
+ height, top = height + top, 0.0
598
+ kwargs.setdefault('is_tree', False)
599
+ kwargs.setdefault('show_tint', True)
600
+ kwargs.setdefault('display_name', surface.title if surface is not None else name)
601
+ kwargs.setdefault('header_indent', left_inset)
602
+ kwargs.setdefault('with_header_end', titlebar.draw_header_controls if right_inset > 0 else None)
603
+ else:
604
+ kwargs.setdefault('with_header_end', None)
605
+ # OS bodies orchestrate render calls and shortcuts every requested frame.
606
+ # Descendant bodies have their own caches; the app still sleeps when idle.
607
+ pinned = dict(name=name, closable=True, draggable=False, frame_pinned=True,
608
+ window_pos=(0, top), width=width, height=height,
609
+ auto_resize=False, show_header=header, with_footer=None, shadow=False, show_bg=True,
610
+ selectable=False, use_cache=False, disable_scroll=True, indent_size=5,
611
+ initial={'width': width, 'height': height, 'window_pos': (0, top)})
612
+ return pinned | kwargs
613
+
614
+
615
+ def _fresh(value):
616
+ try:
617
+ return copy.deepcopy(value)
618
+ except Exception:
619
+ return value
620
+
621
+
622
+ def _owner_io():
623
+ if Surface.owner_context is None:
624
+ raise RuntimeError('Surface.owner_context is not set (app.py creates it)')
625
+ prev = imgui.get_current_context()
626
+ imgui.set_current_context(Surface.owner_context)
627
+ try:
628
+ return imgui.get_io()
629
+ finally:
630
+ if prev is not None:
631
+ imgui.set_current_context(prev)
632
+
633
+
634
+ def _unique_title(title):
635
+ """Window titles double as the geometry feed's per-window key (several
636
+ windows share our pid and class), so no two surfaces share one."""
637
+ taken = {s.title for s in Surface.all}
638
+ if title not in taken:
639
+ return title
640
+ n = 2
641
+ while f'{title} ({n})' in taken:
642
+ n += 1
643
+ return f'{title} ({n})'