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,984 @@
1
+ """GlfwRenderer variant that lets the foreground/overlay draw list be deferred.
2
+
3
+ Frame lifecycle:
4
+
5
+ imgui.new_frame()
6
+ # ... build UI; emit overlay primitives via get_foreground_draw_list() ...
7
+ end_frame()
8
+ renderer.begin_frame_split()
9
+ imgui.render()
10
+ draw_data = imgui.get_draw_data()
11
+ renderer.render_except_overlay(draw_data)
12
+ # ... custom GL work happens here, on top of windows but below overlay ...
13
+ renderer.render_overlay_only(draw_data)
14
+ """
15
+ from __future__ import absolute_import
16
+
17
+ import ctypes
18
+
19
+ from meltygui.core.styling.style import adjust_text_color
20
+
21
+ import OpenGL.GL as gl
22
+ import meltygui_imgui as imgui
23
+ import meltygui.core.windowing.window_api as glfw
24
+ from meltygui.core.windowing.backends.imgui_renderer import WindowRenderer
25
+ from meltygui_imgui.integrations.opengl import get_common_gl_state, restore_common_gl_state
26
+ from meltygui.hdr_color import GLSL_DECODE as _GLSL_DECODE
27
+ from meltygui.hdr_color import GLSL_UNPREMULTIPLY as _GLSL_UNPREMULTIPLY
28
+ from meltygui.hdr_color import GLSL_TEXT_CLAMP as _GLSL_TEXT_CLAMP
29
+ from meltygui.hdr_color import set_decode_uniforms
30
+
31
+
32
+ class _FakeWindowRect:
33
+ """Synthetic stand-in for a window draw_state, used to feed a fixed test
34
+ rect through the real masking path (_setup_channel_stencil)."""
35
+ closed = False
36
+ def __init__(self, layer, x, y, w, h):
37
+ self.layer = layer
38
+ self.abs_left = x
39
+ self.abs_top = y
40
+ self.width = w
41
+ self.height = h
42
+
43
+
44
+ class SplitOverlayRenderer(WindowRenderer):
45
+ debug_overlay_mask = False # one-shot diagnostics of window-mask overlay
46
+ # ISOLATION TEST: when set, inject a synthetic high-layer "window" with this
47
+ # fixed-screen rect into window_channels, so it flows through the SAME
48
+ # _setup_channel_stencil path as real windows. If sub-top overlays get cut
49
+ # inside this rect, the per-window masking path itself is correct and the
50
+ # bug is in the real windows' channel/coord data.
51
+ debug_static_mask = False
52
+ debug_static_rect = (400, 400, 400, 400) # screen-space (x, y, w, h)
53
+
54
+ # LCD subpixel text. The font atlas is 3x oversampled horizontally
55
+ # (fonts.py, FontSpec.oversample), so a glyph edge covers exactly three
56
+ # atlas texels per screen pixel and the texel at -1/0/+1 IS the coverage
57
+ # of the R/G/B sub's centre (stb's 3-wide box prefilter = FreeType's
58
+ # 'light' LCD filter). Dual-source blending applies that per channel:
59
+ # dst = src.rgb * cov.rgb + dst * (1 - cov.rgb)
60
+ # Everything else imgui draws keeps exact stock behaviour: solid
61
+ # geometry (rects, fills, AA fringes) uses the atlas white pixel with a
62
+ # CONSTANT uv -> fwidth(uv) == 0 exactly; thin AA lines sample the
63
+ # TexUvLines strip, which only varies in u -> the two-axis test keeps
64
+ # them grayscale; images/tiles are actual textures (Atlas == 0) and get
65
+ # uniform coverage == alpha, which with the same blend func is plain
66
+ # alpha blending. Proven byte-identical for non-glyph UI lists.
67
+ # Vertex colours are Melty's HDR/P3 packing (gl_gui/hdr_color.py): the
68
+ # vertex stage decodes the u32 into LINEAR scRGB and alpha, so the
69
+ # blend func happens in linear light on the fp16 scene target and a
70
+ # colour above 1.0 (white(4)) or outside sRGB (p3(1, 0, 0)) arrives
71
+ # intact. The two curve uniforms are set per draw (_bind_text_mode).
72
+ # The result is PREMULTIPLIED (melty_decode_premultiplied): imgui's AA
73
+ # feather vertices carry a zero alpha byte, which is not an SDR colour,
74
+ # and should not bleed their rgb to the edge.
75
+ VERTEX_SHADER_SRC = """
76
+ #version 330
77
+
78
+ uniform mat4 ProjMtx;
79
+ in vec2 Position;
80
+ in vec2 UV;
81
+ in vec4 Color;
82
+ out vec2 Frag_UV;
83
+ out vec4 Frag_Color;
84
+ """ + _GLSL_DECODE + """
85
+ void main() {
86
+ Frag_UV = UV;
87
+ Frag_Color = melty_decode_premultiplied(Color);
88
+ gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
89
+ }
90
+ """
91
+
92
+ FRAGMENT_SHADER_SRC = """
93
+ #version 330
94
+
95
+ uniform sampler2D Texture;
96
+ uniform vec2 TexelSize; // 1 / font atlas size
97
+ uniform int Atlas; // 1: this command samples the font atlas
98
+ uniform int Lcd; // Toggles.Fonts.lcd_subpixel
99
+ uniform int Bgr; // Toggles.Fonts.lcd_bgr
100
+ uniform float Gamma; // Toggles.Fonts.text_gamma
101
+ in vec2 Frag_UV;
102
+ in vec4 Frag_Color; // premultiplied (see VERTEX_SHADER_SRC)
103
+ layout(location = 0, index = 0) out vec4 Out_Color;
104
+ layout(location = 0, index = 1) out vec4 Out_Cov;
105
+ """ + _GLSL_UNPREMULTIPLY + _GLSL_TEXT_CLAMP + """
106
+ /* dynamic text policy */
107
+ void main() {
108
+ vec4 color = melty_unpremultiply(Frag_Color);
109
+ vec4 t = texture(Texture, Frag_UV.st);
110
+ vec3 cov = vec3(t.a);
111
+ if (Atlas == 1) {
112
+ vec2 fw = fwidth(Frag_UV);
113
+ if (fw.x > 0.0 && fw.y > 0.0) {
114
+ // Glyph pixels only (solid geometry samples the atlas's white
115
+ // texel with a flat UV): cap HDR text at Toggles.HDR.text_max_stops.
116
+ color.rgb = melty_clamp_text(color.rgb);
117
+ if (DynamicStyles == 1) color.rgb = adjust_text_color(color.rgb);
118
+ if (Lcd == 1) {
119
+ float l = texture(Texture, Frag_UV.st - vec2(TexelSize.x, 0.0)).a;
120
+ float r = texture(Texture, Frag_UV.st + vec2(TexelSize.x, 0.0)).a;
121
+ cov = (Bgr == 1) ? vec3(r, t.a, l) : vec3(l, t.a, r);
122
+ }
123
+ cov = pow(cov, vec3(1.0 / Gamma));
124
+ }
125
+ }
126
+ float a = color.a;
127
+ Out_Color = vec4(color.rgb * t.rgb, a * t.a);
128
+ Out_Cov = vec4(cov * a, a * t.a);
129
+ }
130
+ """
131
+
132
+ _STOCK_FRAGMENT_SHADER_SRC = WindowRenderer.FRAGMENT_SHADER_SRC
133
+
134
+ def __init__(self, window, attach_callbacks: bool = True):
135
+ # Set before super().__init__: it builds the device objects (shader)
136
+ # and the font texture, both of which the overrides below stamp onto
137
+ # these slots.
138
+ self._lcd_ok = False
139
+ self._loc_texel = self._loc_atlas = self._loc_lcd = self._loc_bgr = self._loc_gamma = -1
140
+ self._atlas_texel = (0.0, 0.0)
141
+ super().__init__(window, attach_callbacks=attach_callbacks)
142
+ self._has_overlay = False
143
+ self._scaled_this_frame = False
144
+ self._mask_debug_logged = False
145
+
146
+ def process_inputs(self):
147
+ super().process_inputs()
148
+ io = imgui.get_io()
149
+ # The stock backend reports the pointer as (-1, -1) whenever the OS
150
+ # window is not FOCUSED - but mouse hovers an unfocused window just
151
+ # fine, and a pointer at (-1, -1) reads as the top-left corner to
152
+ # titlebar._edge_at (the ↘ resize shape over the whole unfocused
153
+ # studio). While the pointer is over the window (GLFW's HOVERED
154
+ # flag) report its real position, so hover cursors and highlights
155
+ # resolve exactly as they do focused; off the window it stays the
156
+ # stock off-window sentinel.
157
+ if not glfw.get_window_attrib(self.window, glfw.FOCUSED) and \
158
+ glfw.get_window_attrib(self.window, glfw.HOVERED):
159
+ io.mouse_pos = glfw.get_cursor_pos(self.window)
160
+ # Shadow margin (titlebar.window_inset): imgui's display is the
161
+ # CONTENT and the real framebuffer is wider by the inset on every
162
+ # side; the render methods paint into that inset viewport and the
163
+ # pointer shifts back by it. Melty.frame_inset / framebuffer_size
164
+ # carry the two numbers to the masks, tiles, etc.
165
+ from meltygui.core.windowing.titlebar import window_inset
166
+ from meltygui.core.windowing.titlebar import content_origin
167
+ from meltygui.core.melty import Melty
168
+ inset = int(window_inset())
169
+ ox, oy = (int(v) for v in content_origin())
170
+ w, h = io.display_size
171
+ Melty.framebuffer_size = (int(w * io.display_fb_scale[0]), int(h * io.display_fb_scale[1]))
172
+ Melty.frame_inset = inset
173
+ Melty.frame_origin = (ox, oy)
174
+ if inset or ox or oy:
175
+ io.display_size = (max(1.0, w - ox - inset), max(1.0, h - oy - inset))
176
+ mx, my = io.mouse_pos
177
+ if mx > -1e6 and my > -1e6: # -FLT_MAX / (-1, -1) = off-window stays as is
178
+ io.mouse_pos = (mx - ox, my - oy)
179
+ self._cancel_surface_slide(io)
180
+ # A compositor move/resize grab (gl_gui/wayland_move.py) swallowed a
181
+ # button release, so GLFW's level state - what the stock poll above
182
+ # copies into io.mouse_down - stays PRESS until its next real event.
183
+ import meltygui.core.windowing.wayland_move as wayland_move
184
+ masked = wayland_move.masked_buttons()
185
+ if masked:
186
+ io = imgui.get_io()
187
+ for button in masked:
188
+ if 0 <= button < 3:
189
+ io.mouse_down[button] = False
190
+ # Orchestrator record/replay: inject the next slice of a replaying
191
+ # orchestration into the SAME handler the stock backend feeds - HERE,
192
+ # before imgui.new_frame, where stock input enters too (GLFW
193
+ # callbacks → handler, this poll → io). An injected event therefore
194
+ # lands in the handler and, through stamp_io right below, in imgui's
195
+ # io in the same frame, and process_frame dispatches it this frame at
196
+ # exactly a real press's moment. (It used to pump from
197
+ # Melty.begin_frame, inside the imgui frame: the handler saw a whole
198
+ # empty frame before imgui did, and on a behind press that
199
+ # handler-only frame raised the window and started the drag on a
200
+ # move handle before the widget under the press was active - the
201
+ # window dragged instead of the value, 09-01.) Then, while a replay
202
+ # or directed task drives, the VIRTUAL cursor / buttons / buttons /
203
+ # chars override the real ones for imgui (the handler side is handled
204
+ # through the input_tap funnel). No-ops otherwise.
205
+ from meltygui.core.automation.orchestration_core import Orchestrator
206
+ Orchestrator.pump() # engine errors surface if never swallowed here
207
+ try:
208
+ Orchestrator.stamp_io(imgui.get_io())
209
+ except Exception:
210
+ pass
211
+
212
+ def _create_device_objects(self):
213
+ """Build the LCD program; if the driver can't link it (no dual-source
214
+ blending), fall back to the stock shader + stock blending so text
215
+ still renders, just grayscale."""
216
+ self._text_style_source = adjust_text_color()
217
+ self.FRAGMENT_SHADER_SRC = type(self).FRAGMENT_SHADER_SRC.replace(
218
+ "/* dynamic text policy */", self._text_style_source)
219
+ try:
220
+ super()._create_device_objects()
221
+ if not gl.glGetProgramiv(self._shader_handle, gl.GL_LINK_STATUS):
222
+ raise RuntimeError(gl.glGetProgramInfoLog(self._shader_handle))
223
+ self._loc_texel = gl.glGetUniformLocation(self._shader_handle, "TexelSize")
224
+ self._loc_atlas = gl.glGetUniformLocation(self._shader_handle, "Atlas")
225
+ self._loc_lcd = gl.glGetUniformLocation(self._shader_handle, "Lcd")
226
+ self._loc_bgr = gl.glGetUniformLocation(self._shader_handle, "Bgr")
227
+ self._loc_gamma = gl.glGetUniformLocation(self._shader_handle, "Gamma")
228
+ self._loc_dynamic_styles = gl.glGetUniformLocation(self._shader_handle, "DynamicStyles")
229
+ self._loc_style_context = gl.glGetUniformLocation(self._shader_handle, "StyleContext")
230
+ self._loc_text_contrast = gl.glGetUniformLocation(self._shader_handle, "TextContrast")
231
+ self._lcd_ok = min(self._loc_texel, self._loc_atlas, self._loc_lcd,
232
+ self._loc_bgr, self._loc_gamma) >= 0
233
+ except Exception as e:
234
+ print(f"SplitOverlayRenderer: LCD text shader unavailable ({e}); "
235
+ f"falling back to grayscale text")
236
+ self._lcd_ok = False
237
+ if not self._lcd_ok:
238
+ gl.glDeleteProgram(self._shader_handle)
239
+ gl.glDeleteVertexArrays(1, [self._vao_handle])
240
+ gl.glDeleteBuffers(2, [self._vbo_handle, self._elements_handle])
241
+ self.FRAGMENT_SHADER_SRC = self._STOCK_FRAGMENT_SHADER_SRC
242
+ super()._create_device_objects()
243
+
244
+ def refresh_font_texture(self):
245
+ """Stock upload, with the atlas routed through FontManager.hint_atlas
246
+ (FreeType light-hinted LCD glyphs) when enabled. Same GL state
247
+ handling as the base: save/restore the bound texture, delete the
248
+ previous atlas texture, clear imgui's CPU copy after upload."""
249
+ last_texture = gl.glGetIntegerv(gl.GL_TEXTURE_BINDING_2D)
250
+ width, height, pixels = self.io.fonts.get_tex_data_as_rgba32()
251
+ pixels = self._hinted_atlas(width, height, pixels) or pixels
252
+
253
+ if self._font_texture is not None:
254
+ gl.glDeleteTextures([self._font_texture])
255
+ self._font_texture = gl.glGenTextures(1)
256
+ gl.glBindTexture(gl.GL_TEXTURE_2D, self._font_texture)
257
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
258
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
259
+ gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0,
260
+ gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, pixels)
261
+ self.io.fonts.texture_id = self._font_texture
262
+ gl.glBindTexture(gl.GL_TEXTURE_2D, last_texture)
263
+ self.io.fonts.clear_tex_data()
264
+ self._atlas_texel = (1.0 / max(1, width), 1.0 / max(1, height))
265
+
266
+ def _hinted_atlas(self, width, height, pixels):
267
+ """FreeType-hinted texels for the atlas imgui just built, or None to
268
+ upload stb's. Only a FontManager with live handles (it baked the
269
+ current atlas) may probe it. NB: pyimgui returns a fresh wrapper
270
+ from every get_io(), so io identity can't be compared."""
271
+ from meltygui.core.runtime.toggles import Toggles
272
+ if not Toggles.Fonts.freetype_hinting:
273
+ return None
274
+ from meltygui.core.melty import Melty
275
+ fm = getattr(Melty, "font_mgr", None)
276
+ if fm is None or not fm._handles:
277
+ return None
278
+ try:
279
+ return fm.hint_atlas(width, height, pixels)
280
+ except Exception as e: # never let the atlas upload fail over hinting
281
+ print(f"SplitOverlayRenderer: FreeType hinting pass failed ({e!r}); "
282
+ f"uploading stb atlas")
283
+ return None
284
+
285
+ def _bind_text_mode(self) -> int:
286
+ """Blend func + per-frame LCD uniforms for the bound program. Returns
287
+ the font texture id the per-command Atlas uniform compares against,
288
+ or -1 when running the stock shader."""
289
+ # Alpha channel: ONE / ONE_MINUS_SRC_ALPHA (coverage union), never
290
+ # SRC_ALPHA / ONE_MINUS_SRC_ALPHA - that gave dst_a = a² + dst_a-(1-a),
291
+ # so every translucent draw (a texture with alpha, an AA edge, glyph
292
+ # coverage) punched alpha OUT of an opaque framebuffer and, on an
293
+ # ARGB buffer (the frame's Wayland surface, an X11 compositor
294
+ # handing out an ARGB buffer), the desktop showed through. Tiles
295
+ # rendered offscreen start from alpha 0, but with the union rule
296
+ # their alpha is the true coverage, so a blitted alpha lands solid.
297
+ set_decode_uniforms(self._shader_handle) # Toggles.HLS curve, live
298
+ if not self._lcd_ok:
299
+ gl.glBlendFuncSeparate(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA,
300
+ gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA)
301
+ return -1
302
+ from meltygui.core.runtime.toggles import Toggles
303
+ gl.glBlendFuncSeparate(gl.GL_SRC1_COLOR, gl.GL_ONE_MINUS_SRC1_COLOR,
304
+ gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA)
305
+ # The shared atlas can be resized by another surface's lazy font load.
306
+ # Its live dimensions, just like its live texture ID, are authoritative:
307
+ # stale texel widths move the LCD taps off their subpixel centres.
308
+ self._atlas_texel = (1.0 / max(1, self.io.fonts.texture_width),
309
+ 1.0 / max(1, self.io.fonts.texture_height))
310
+ gl.glUniform2f(self._loc_texel, *self._atlas_texel)
311
+ gl.glUniform1i(self._loc_dynamic_styles, int(Toggles.dynamic_styles))
312
+ gl.glUniform1i(self._loc_style_context, 1)
313
+ gl.glUniform1f(self._loc_text_contrast, Toggles.dynamic_text_contrast)
314
+ gl.glUniform1i(self._loc_lcd, 1 if Toggles.Fonts.lcd_subpixel else 0)
315
+ gl.glUniform1i(self._loc_bgr, 1 if Toggles.Fonts.lcd_bgr else 0)
316
+ gamma = float(Toggles.Fonts.text_gamma)
317
+ gl.glUniform1f(self._loc_gamma, gamma if gamma > 0.0 else 1.0)
318
+ # Surfaces share an atlas: any renderer can replace its texture.
319
+ # Commands use the atlas's live ID, not this renderer's last upload.
320
+ texture = self.io.fonts.texture_id
321
+ return int(texture) if texture is not None else -1
322
+
323
+ def begin_frame_split(self) -> None:
324
+ """Snapshot whether the foreground list has content. Call after the UI
325
+ is fully built and before ``imgui.render()``."""
326
+ self._has_overlay = imgui.get_foreground_draw_list().vtx_buffer_size > 0
327
+ self._scaled_this_frame = False
328
+
329
+ def render_except_overlay(self, draw_data) -> None:
330
+ self._scale_clip_rects_once(draw_data)
331
+ lists = draw_data.commands_lists
332
+ if self._has_overlay and lists:
333
+ lists = lists[:-1]
334
+ self._render_command_lists(draw_data, lists)
335
+
336
+ def render_overlay_only(self, draw_data) -> None:
337
+ if not self._has_overlay:
338
+ return
339
+ self._scale_clip_rects_once(draw_data)
340
+ lists = draw_data.commands_lists
341
+ if not lists:
342
+ self._has_overlay = False
343
+ return
344
+
345
+ # If Melty channel-split the foreground list, render each channel
346
+ # with a per-layer stencil mask. Otherwise fall back to a regular draw.
347
+ from meltygui.core.melty import Melty
348
+ ranges = getattr(Melty, "_overlay_channel_ranges", None)
349
+ if ranges:
350
+ self._render_overlay_channels(draw_data, lists[-1], ranges, Melty)
351
+ else:
352
+ self._render_command_lists(draw_data, lists[-1:])
353
+ self._has_overlay = False
354
+
355
+ # Pointer slide correction: (surface pointer, relative-motion total) at
356
+ # mouse press, None if held.
357
+ _slide_base = None
358
+ # Last frame's (surface pointer, relative-motion total), and the per-axis
359
+ # relative motion the cursor did NOT follow (clamped at the screen
360
+ # edge) since the press - excluded from the slide.
361
+ _slide_last = None
362
+ _slide_clamped = (0.0, 0.0)
363
+ _slide_screen_origin = None
364
+ _slide_native_sample = None
365
+ SLIDE_DEADBAND = 1.5
366
+
367
+ def _cancel_surface_slide(self, io):
368
+ """While a mouse button is held, subtract the SURFACE's own motion
369
+ from the pointer. The compositor slides the studio to keep it on
370
+ screen when its surface grows past the workarea (the OS-edge push,
371
+ os_frame); the surface-relative pointer then jumps by the slide
372
+ while the hand did not move — a drag reading that as motion grows
373
+ the window more and is slid again, a feedback snap. The slide is
374
+ the difference between the surface-relative pointer's travel and
375
+ the relative-pointer (screen-space) travel since the press; below
376
+ SLIDE_DEADBAND it is rounding, not a slide. Resets on release, so
377
+ hover after a gesture reads the true pointer again."""
378
+ import meltygui.core.windowing.wayland_move as wayland_move
379
+ down = any(io.mouse_down[i] for i in range(3))
380
+ if not down or not wayland_move.relative_motion_available():
381
+ self._slide_base = None
382
+ self._slide_last = None
383
+ self._slide_clamped = (0.0, 0.0)
384
+ if not down:
385
+ self._slide_screen_origin = None
386
+ self._slide_native_sample = None
387
+ return
388
+ # Native seats deliver only surface-local motion. The geometry feed
389
+ # still tells us how far the surface moved: subtract that change
390
+ # from the drag, without treating a stationary relative pointer as a
391
+ # stationary hand. Keep this baseline local to the window/gesture.
392
+ window = getattr(self, 'window', None)
393
+ generation = (window.cursor_motion_generation
394
+ if glfw.is_native_window(window) else None)
395
+ sample = self._slide_native_sample
396
+ if generation is not None and sample is not None and sample[0] == generation:
397
+ # Native Wayland retains the last surface-local sample until
398
+ # wl_pointer.motion. A configure/feed update alone must not
399
+ # combine that OLD sample with a NEW surface origin: doing so
400
+ # turns the window's own translation into another phantom slide.
401
+ io.mouse_pos = sample[1]
402
+ return
403
+ import meltygui.core.windowing.geometry_feed as geometry_feed
404
+ rect = geometry_feed.frame_rect()
405
+ if rect is None:
406
+ self._slide_screen_origin = None
407
+ return
408
+ origin = rect[:2]
409
+ if self._slide_screen_origin is None:
410
+ self._slide_screen_origin = origin
411
+ mx, my = io.mouse_pos
412
+ if mx > -1e6 and my > -1e6:
413
+ ox, oy = self._slide_screen_origin
414
+ io.mouse_pos = (mx + origin[0] - ox, my + origin[1] - oy)
415
+ if generation is not None:
416
+ self._slide_native_sample = (generation, tuple(io.mouse_pos))
417
+ return
418
+ self._slide_screen_origin = None
419
+ self._slide_native_sample = None
420
+ mx, my = io.mouse_pos
421
+ if mx < -1e6 or my < -1e6:
422
+ return
423
+ rel = wayland_move.relative_motion_total()
424
+ if self._slide_base is None:
425
+ self._slide_base = ((mx, my), rel)
426
+ self._slide_last = ((mx, my), rel)
427
+ self._slide_clamped = (0.0, 0.0)
428
+ return
429
+ # The cursor CLAMPED at the screen edge: the hand (relative pointer)
430
+ # keeps moving on an axis while the surface pointer does not move
431
+ # at all on it. That is not the surface sliding — read as one it
432
+ # re-based every root window by the phantom slide each frame (the
433
+ # meltygui windows "slided closed" once the studio filled the
434
+ # display) and pushed the pointer past the edge, so the drag kept
435
+ # going. Excluded from the slide, permanently: the cursor does
436
+ # not owe that motion back.
437
+ if self._slide_last is None: # hotswapped mid-gesture: start the frame diff here
438
+ self._slide_last = ((mx, my), rel)
439
+ (lx, ly), (lrx, lry) = self._slide_last
440
+ d_rel = (rel[0] - lrx, rel[1] - lry)
441
+ cx, cy = self._slide_clamped
442
+ if d_rel[0] and mx == lx:
443
+ cx += d_rel[0]
444
+ if d_rel[1] and my == ly:
445
+ cy += d_rel[1]
446
+ self._slide_clamped = (cx, cy)
447
+ self._slide_last = ((mx, my), rel)
448
+ (bx, by), (rx0, ry0) = self._slide_base
449
+ slide_x = (mx - bx) - (rel[0] - rx0 - cx)
450
+ slide_y = (my - by) - (rel[1] - ry0 - cy)
451
+ if abs(slide_x) < self.SLIDE_DEADBAND:
452
+ slide_x = 0.0
453
+ if abs(slide_y) < self.SLIDE_DEADBAND:
454
+ slide_y = 0.0
455
+ if slide_x or slide_y:
456
+ io.mouse_pos = (mx - slide_x, my - slide_y)
457
+
458
+ @staticmethod
459
+ def _frame_origin():
460
+ """Framebuffer origin of imgui's display: (inset, inset) while the
461
+ frameless window carries its shadow margin, else (0, 0). Viewports
462
+ move by it, scissors add it."""
463
+ from meltygui.core.melty import Melty
464
+ ox, oy = getattr(Melty, "frame_origin", None) or (0, 0)
465
+ return int(ox), int(oy)
466
+
467
+ def _render_overlay_channels(self, draw_data, overlay_list, ranges, Melty) -> None:
468
+ """Render the foreground draw list one channel at a time. Channel index
469
+ equals layer_channel(draw_state.layer); the top channel is the unmasked
470
+ global overlay. Each channel renders with stencil set to exclude any
471
+ window whose layer_channel is greater than this channel — so the overlay
472
+ appears 'underneath' higher windows."""
473
+ io = self.io
474
+ display_width, display_height = io.display_size
475
+ fb_scale_x, fb_scale_y = io.display_fb_scale
476
+ fb_width = int(display_width * fb_scale_x)
477
+ fb_height = int(display_height * fb_scale_y)
478
+
479
+ if fb_width == 0 or fb_height == 0:
480
+ return
481
+
482
+ self._refresh_style_shader()
483
+ common_gl_state_tuple = get_common_gl_state()
484
+ last_program = gl.glGetIntegerv(gl.GL_CURRENT_PROGRAM)
485
+ last_active_texture = gl.glGetIntegerv(gl.GL_ACTIVE_TEXTURE)
486
+ last_array_buffer = gl.glGetIntegerv(gl.GL_ARRAY_BUFFER_BINDING)
487
+ last_element_array_buffer = gl.glGetIntegerv(gl.GL_ELEMENT_ARRAY_BUFFER_BINDING)
488
+ last_vertex_array = gl.glGetIntegerv(gl.GL_VERTEX_ARRAY_BINDING)
489
+ last_stencil_test = gl.glIsEnabled(gl.GL_STENCIL_TEST)
490
+
491
+ gl.glEnable(gl.GL_BLEND)
492
+ gl.glBlendEquation(gl.GL_FUNC_ADD)
493
+ gl.glDisable(gl.GL_CULL_FACE)
494
+ gl.glDisable(gl.GL_DEPTH_TEST)
495
+ gl.glEnable(gl.GL_SCISSOR_TEST)
496
+ gl.glActiveTexture(gl.GL_TEXTURE0)
497
+ gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
498
+ ox, oy = self._frame_origin()
499
+ gl.glViewport(ox, oy, fb_width, fb_height)
500
+
501
+ ortho_projection = (ctypes.c_float * 16)(
502
+ 2.0 / display_width, 0.0, 0.0, 0.0,
503
+ 0.0, 2.0 / -display_height, 0.0, 0.0,
504
+ 0.0, 0.0, -1.0, 0.0,
505
+ -1.0, 1.0, 0.0, 1.0,
506
+ )
507
+
508
+ from meltygui.core.runtime.toggles import Toggles
509
+ style_context = 0
510
+ if self._lcd_ok and Toggles.dynamic_styles:
511
+ style_context = self._ensure_style_context(fb_width, fb_height, fb_scale_x, fb_scale_y, ox, oy)
512
+ previous_context = self._bind_style_context(style_context)
513
+ gl.glUseProgram(self._shader_handle)
514
+ gl.glUniform1i(self._attrib_location_tex, 0)
515
+ gl.glUniformMatrix4fv(self._attrib_proj_mtx, 1, gl.GL_FALSE, ortho_projection)
516
+ font_tex = self._bind_text_mode()
517
+ gl.glBindVertexArray(self._vao_handle)
518
+
519
+ # Upload the merged foreground vtx/idx buffers once for all channels.
520
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._vbo_handle)
521
+ gl.glBufferData(
522
+ gl.GL_ARRAY_BUFFER,
523
+ overlay_list.vtx_buffer_size * imgui.VERTEX_SIZE,
524
+ ctypes.c_void_p(overlay_list.vtx_buffer_data),
525
+ gl.GL_STREAM_DRAW,
526
+ )
527
+ gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self._elements_handle)
528
+ gl.glBufferData(
529
+ gl.GL_ELEMENT_ARRAY_BUFFER,
530
+ overlay_list.idx_buffer_size * imgui.INDEX_SIZE,
531
+ ctypes.c_void_p(overlay_list.idx_buffer_data),
532
+ gl.GL_STREAM_DRAW,
533
+ )
534
+
535
+ gltype = gl.GL_UNSIGNED_SHORT if imgui.INDEX_SIZE == 2 else gl.GL_UNSIGNED_INT
536
+
537
+ # Each command's (idx_lo, idx_hi) span in the merged index buffer. The
538
+ # channel ranges are also in index space, so we render the intersection
539
+ # of each command with each channel - splitting fused commands at
540
+ # channel boundaries (a command's texture/clip are uniform over its span,
541
+ # so any sub-range is valid to draw).
542
+ commands = list(overlay_list.commands)
543
+ cmd_spans = []
544
+ running = 0
545
+ for cmd in commands:
546
+ cmd_spans.append((running, running + cmd.elem_count, cmd))
547
+ running += cmd.elem_count
548
+
549
+ # (channel, draw_state) for every registered window with a laid-out
550
+ # rect - used to punch holes for windows below each channel. Channels
551
+ # go through Melty.overlay_window_channel - the SAME dense rank map the
552
+ # views routed their overlays with - so the "higher than" comparison
553
+ # below stays exact instead of collapsing at the max_layer clamp.
554
+ top_channel = Melty.max_layer - 1 # global overlay is above all windows
555
+ window_channels = []
556
+ for w in Melty.registered_windows.values():
557
+ ds = getattr(w, "draw_state", None)
558
+ if ds is None or ds.closed:
559
+ continue
560
+ if ds.abs_left is None or ds.abs_top is None or ds.width is None or ds.height is None:
561
+ continue
562
+ window_channels.append((Melty.overlay_channel_for(ds), ds))
563
+
564
+ for r in Melty.root_draw_states.values():
565
+ for ds in r:
566
+ if ds is None or ds.closed:
567
+ continue
568
+ # A window hidden because its spawner scrolled out of view
569
+ # isn't dispatched (no pixels this frame), so its stale rect
570
+ # does not punch stencil holes in lower channels.
571
+ if getattr(ds, "_hidden_offscreen", False):
572
+ continue
573
+ if ds.abs_left is None or ds.abs_top is None or ds.width is None or ds.height is None:
574
+ continue
575
+ window_channels.append((Melty.overlay_channel_for(ds), ds))
576
+
577
+ if self.debug_static_mask:
578
+ sx, sy, sw, sh = self.debug_static_rect
579
+ # Inject at a non-TOP channel so it can mask the controlled debug
580
+ # content drawn on channel 10 (see Melty.end_frame() rect).
581
+ fake_channel = 20
582
+ fake = _FakeWindowRect(fake_channel, sx, sy, sw, sh)
583
+ window_channels.append((fake_channel, fake))
584
+
585
+ if self.debug_overlay_mask and not self._mask_debug_logged:
586
+ self._mask_debug_logged = False
587
+ fbo = gl.glGetIntegerv(gl.GL_DRAW_FRAMEBUFFER_BINDING)
588
+ # GL_STENCIL_BITS is removed in coreGL; query the bound
589
+ # framebuffer's stencil attachment size instead.
590
+ attachment = gl.GL_STENCIL if fbo == 0 else gl.GL_STENCIL_ATTACHMENT
591
+ try:
592
+ stencil_bits = gl.glGetFramebufferAttachmentParameteriv(
593
+ gl.GL_DRAW_FRAMEBUFFER, attachment,
594
+ gl.GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE)
595
+ except Exception as e:
596
+ stencil_bits = f"query-failed({e})"
597
+ non_empty = [(c, s, e) for (c, s, e) in ranges if e > s]
598
+
599
+ for channel_idx, idx_lo, idx_hi in ranges:
600
+ if idx_hi <= idx_lo:
601
+ continue
602
+ self._setup_channel_stencil(channel_idx, top_channel, window_channels,
603
+ fb_height, fb_scale_x, fb_scale_y, ox, oy)
604
+
605
+ for c_lo, c_hi, cmd in cmd_spans:
606
+ seg_lo = max(idx_lo, c_lo)
607
+ seg_hi = min(idx_hi, c_hi)
608
+ if seg_hi <= seg_lo:
609
+ continue
610
+ gl.glBindTexture(gl.GL_TEXTURE_2D, cmd.texture_id)
611
+ if font_tex >= 0:
612
+ gl.glUniform1i(self._loc_atlas, 1 if int(cmd.texture_id) == font_tex else 0)
613
+ x, y, z, w = cmd.clip_rect
614
+ gl.glScissor(int(x) + ox, int(fb_height - w) + oy, int(z - x), int(w - y))
615
+ self._draw_style_elements(overlay_list, cmd.texture_id, seg_lo, seg_hi, gltype)
616
+
617
+ if self.debug_overlay_mask:
618
+ self._debug_draw_window_rects(window_channels, fb_height,
619
+ fb_scale_x, fb_scale_y)
620
+
621
+ if last_stencil_test:
622
+ gl.glEnable(gl.GL_STENCIL_TEST)
623
+ else:
624
+ gl.glDisable(gl.GL_STENCIL_TEST)
625
+
626
+ self._unbind_style_context(previous_context)
627
+ restore_common_gl_state(common_gl_state_tuple)
628
+ gl.glUseProgram(last_program)
629
+ gl.glActiveTexture(last_active_texture)
630
+ gl.glBindVertexArray(last_vertex_array)
631
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, last_array_buffer)
632
+ gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer)
633
+
634
+ def _debug_draw_window_rects(self, window_channels, fb_height,
635
+ fb_scale_x, fb_scale_y) -> None:
636
+ """Visual debug: draw a bright border around each window rect using the
637
+ EXACT transform _setup_channel_stencil uses to punch stencil holes. If
638
+ these borders line up with the on-screen windows, the stencil holes are
639
+ positioned correctly. Magenta border; thickness scales with channel."""
640
+ gl.glDisable(gl.GL_STENCIL_TEST)
641
+ gl.glEnable(gl.GL_SCISSOR_TEST)
642
+ t = 2
643
+ for win_channel, ds in window_channels:
644
+ x = int(ds.abs_left * fb_scale_x)
645
+ y = int(fb_height - (ds.abs_top + ds.height) * fb_scale_y)
646
+ sw = int(ds.width * fb_scale_x)
647
+ sh = int(ds.height * fb_scale_y)
648
+ if sw <= 0 or sh <= 0:
649
+ continue
650
+ # Color-code by channel so overlapping windows are distinguishable.
651
+ r = 0.2 + 0.8 * ((win_channel * 37) % 100) / 100.0
652
+ g = 0.2 + 0.8 * ((win_channel * 71) % 100) / 100.0
653
+ b = 0.2 + 0.8 * ((win_channel * 13) % 100) / 100.0
654
+ gl.glClearColor(r, g, b, 1.0)
655
+ for bx, by, bw, bh in (
656
+ (x, y + sh - t, sw, t), # top
657
+ (x, y, sw, t), # bottom
658
+ (x, y, t, sh), # left
659
+ (x + sw - t, y, t, sh), # right
660
+ ):
661
+ gl.glScissor(bx, by, bw, bh)
662
+ gl.glClear(gl.GL_COLOR_BUFFER_BIT)
663
+
664
+ def _setup_channel_stencil(self, channel_idx, top_channel, window_channels,
665
+ fb_height, fb_scale_x, fb_scale_y, ox=0, oy=0) -> None:
666
+ """Top channel: no stencil (global overlay, never masked). Otherwise
667
+ stencil=1 everywhere, then punched to 0 inside the rect of every window
668
+ whose layer_channel is greater than this channel. The stencil test then
669
+ accepts only non-zero pixels, hiding the overlay where a higher window
670
+ covers it."""
671
+ higher = [(c, ds) for (c, ds) in window_channels if c > channel_idx]
672
+ if channel_idx >= top_channel or not higher:
673
+ gl.glDisable(gl.GL_STENCIL_TEST)
674
+ return
675
+
676
+ gl.glEnable(gl.GL_STENCIL_TEST)
677
+ gl.glStencilMask(0xFF)
678
+
679
+ gl.glDisable(gl.GL_SCISSOR_TEST)
680
+ gl.glClearStencil(1)
681
+ gl.glClear(gl.GL_STENCIL_BUFFER_BIT)
682
+
683
+ gl.glEnable(gl.GL_SCISSOR_TEST)
684
+ gl.glClearStencil(0)
685
+ for win_channel, ds in higher:
686
+ x = int(ds.abs_left * fb_scale_x) + ox
687
+ y = int(fb_height - (ds.abs_top + ds.height) * fb_scale_y) + oy
688
+ sw = int(ds.width * fb_scale_x)
689
+ sh = int(ds.height * fb_scale_y)
690
+ if sw <= 0 or sh <= 0:
691
+ continue
692
+ gl.glScissor(x, y, sw, sh)
693
+ gl.glClear(gl.GL_STENCIL_BUFFER_BIT)
694
+
695
+ gl.glStencilFunc(gl.GL_NOTEQUAL, 0, 0xFF)
696
+ gl.glStencilOp(gl.GL_KEEP, gl.GL_KEEP, gl.GL_KEEP)
697
+
698
+ def render(self, draw_data) -> None:
699
+ """Drop-in replacement matching the upstream signature; renders all
700
+ lists in a single pass (no split)."""
701
+ self._scale_clip_rects_once(draw_data)
702
+ self._render_command_lists(draw_data, draw_data.commands_lists)
703
+ self._has_overlay = False
704
+
705
+ def _scale_clip_rects_once(self, draw_data) -> None:
706
+ if self._scaled_this_frame:
707
+ return
708
+ io = self.io
709
+ draw_data.scale_clip_rects(*io.display_fb_scale)
710
+ self._scaled_this_frame = True
711
+
712
+ def _refresh_style_shader(self):
713
+ """A hotswapped text policy replaces the program, retaining the atlas."""
714
+ from meltygui.core.runtime.toggles import Toggles
715
+ initialized = hasattr(self, '_text_style_source')
716
+ if not self._lcd_ok or (initialized and not Toggles.dynamic_styles):
717
+ return
718
+ source = adjust_text_color()
719
+ if (initialized and source == self._text_style_source
720
+ or source == getattr(self, "_failed_text_style_source", None)):
721
+ return
722
+ previous = self.__dict__.copy()
723
+ self._create_device_objects()
724
+ if self._lcd_ok:
725
+ retired = previous
726
+ self._failed_text_style_source = None
727
+ else:
728
+ retired = self.__dict__.copy()
729
+ self.__dict__.update(previous)
730
+ # Keep the last good program, retry only after another source edit.
731
+ self._failed_text_style_source = source
732
+ gl.glDeleteProgram(retired['_shader_handle'])
733
+ gl.glDeleteVertexArrays(1, [retired['_vao_handle']])
734
+ gl.glDeleteBuffers(2, [retired['_vbo_handle'], retired['_elements_handle']])
735
+
736
+ def shutdown(self):
737
+ if hasattr(self, '_style_gl'):
738
+ self._style_gl.release()
739
+ super().shutdown()
740
+
741
+ _STYLE_CONTEXT_VERT = """
742
+ #version 330 core
743
+ layout(location = 0) in vec4 rect; // framebuffer px: x0, y0, x1, y1
744
+ layout(location = 1) in vec4 clip; // framebuffer px: x0, y0, x1, y1
745
+ layout(location = 2) in vec2 extra; // corner radius px, palette index
746
+ uniform vec2 size;
747
+ out vec4 v_rect; out vec4 v_clip; out vec2 v_extra;
748
+ const vec2 corners[6] = vec2[6](vec2(0, 0), vec2(1, 0), vec2(0, 1),
749
+ vec2(1, 0), vec2(1, 1), vec2(0, 1));
750
+ void main() {
751
+ vec2 p = mix(rect.xy, rect.zw, corners[gl_VertexID]);
752
+ gl_Position = vec4(p / size * 2.0 - 1.0, 0.0, 1.0);
753
+ v_rect = rect; v_clip = clip; v_extra = extra;
754
+ }
755
+ """
756
+
757
+ _STYLE_CONTEXT_FRAG = """
758
+ #version 330 core
759
+ in vec4 v_rect; in vec4 v_clip; in vec2 v_extra;
760
+ uniform sampler2D palette;
761
+ out vec4 frag;
762
+ void main() {
763
+ vec2 p = gl_FragCoord.xy;
764
+ if (p.x < v_clip.x || p.y < v_clip.y || p.x > v_clip.z || p.y > v_clip.w) discard;
765
+ vec2 half_size = (v_rect.zw - v_rect.xy) * 0.5;
766
+ float r = min(v_extra.x, min(half_size.x, half_size.y));
767
+ vec2 q = abs(p - (v_rect.xy + v_rect.zw) * 0.5) - (half_size - vec2(r));
768
+ float d = length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;
769
+ float coverage = clamp(0.5 - d, 0.0, 1.0);
770
+ if (coverage <= 0.0) discard;
771
+ frag = vec4(texelFetch(palette, ivec2(int(v_extra.y + 0.5), 0), 0).rgb, coverage);
772
+ }
773
+ """
774
+
775
+ def _ensure_style_context(self, fb_width, fb_height, fb_scale_x, fb_scale_y, ox, oy):
776
+ """The text-context layer: every dynamic background this frame,
777
+ painted from the resolved palette (Melty.draw_backgrounds) in
778
+ submission order over the root colour, rounded and clipped as it
779
+ was submitted. Built ONCE per frame (Melty.background_gen); the
780
+ text shader reads it at gl_FragCoord, so it is sized like the
781
+ framebuffer including the viewport origin. Replaces the per-glyph-run
782
+ framebuffer copy (5 GL queries + glCopyTexSubImage2D per line of
783
+ text: 20 ms on a LoRA tree, 09-13). Returns the texture id, 0 when
784
+ there is nothing to read."""
785
+ import numpy as np
786
+ from meltygui.core.graphics.gl_state import GLState
787
+ from meltygui.core.graphics.gl_state import gl_limits
788
+ from meltygui.core.melty import Melty
789
+ gen = Melty.background_gen
790
+ if Melty.dynamic_style_gl is None or not Melty.backgrounds:
791
+ self._style_context_gen = gen
792
+ return 0
793
+ if not hasattr(self, '_style_gl'):
794
+ self._style_gl = GLState()
795
+ width, height = int(ox + fb_width), int(oy + fb_height)
796
+ context = self._style_gl.fbo('text_context', width, height)
797
+ if getattr(self, '_style_context_gen', None) == gen and \
798
+ getattr(self, '_style_context_size', None) == (width, height):
799
+ return context.texture_id
800
+ self._style_context_gen, self._style_context_size = gen, (width, height)
801
+
802
+ # Instance rows, framebuffer coordinates (y up, viewport origin in).
803
+ rows = []
804
+ for index, entry in enumerate(Melty.background_rects):
805
+ if entry is None:
806
+ continue
807
+ left, top, w, h, (cl, ct, cr, cb), radius = entry
808
+ rows.append((left * fb_scale_x + ox, fb_height - (top + h) * fb_scale_y + oy,
809
+ (left + w) * fb_scale_x + ox, fb_height - top * fb_scale_y + oy,
810
+ cl * fb_scale_x + ox, fb_height - cb * fb_scale_y + oy,
811
+ cr * fb_scale_x + ox, fb_height - ct * fb_scale_y + oy,
812
+ radius * fb_scale_x, float(index)))
813
+ instances = np.asarray(rows, dtype=np.float32).reshape(-1, 10)
814
+
815
+ def build_program():
816
+ from OpenGL.GL import shaders
817
+ return shaders.compileProgram(
818
+ shaders.compileShader(self._STYLE_CONTEXT_VERT, gl.GL_VERTEX_SHADER),
819
+ shaders.compileShader(self._STYLE_CONTEXT_FRAG, gl.GL_FRAGMENT_SHADER))
820
+ program = self._style_gl.get('style_context_program', build_program, gl.glDeleteProgram)
821
+
822
+ def build_vao():
823
+ vbo = int(gl.glGenBuffers(1))
824
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo)
825
+ stride = 10 * 4
826
+ for location, count, offset in ((0, 4, 0), (1, 4, 16), (2, 2, 32)):
827
+ gl.glEnableVertexAttribArray(location)
828
+ gl.glVertexAttribPointer(location, count, gl.GL_FLOAT, gl.GL_FALSE, stride,
829
+ ctypes.c_void_p(offset))
830
+ gl.glVertexAttribDivisor(location, 1)
831
+ self._style_context_vbo = vbo
832
+ return (vbo,)
833
+ vao = self._style_gl.vao('style_context_vao', build_vao)
834
+ palette = Melty.dynamic_style_gl.fbo('background_palette', gl_limits()['max_2d'], 1)
835
+
836
+ last_program = gl.glGetIntegerv(gl.GL_CURRENT_PROGRAM)
837
+ last_vao = gl.glGetIntegerv(gl.GL_VERTEX_ARRAY_BINDING)
838
+ last_array_buffer = gl.glGetIntegerv(gl.GL_ARRAY_BUFFER_BINDING)
839
+ last_texture = gl.glGetIntegerv(gl.GL_TEXTURE_BINDING_2D)
840
+ last_scissor = gl.glIsEnabled(gl.GL_SCISSOR_TEST)
841
+ last_blend = gl.glIsEnabled(gl.GL_BLEND)
842
+ with context: # binds the FBO + viewport, restores both on exit
843
+ gl.glDisable(gl.GL_SCISSOR_TEST)
844
+ gl.glClearColor(*Melty.style_context_root)
845
+ gl.glClear(gl.GL_COLOR_BUFFER_BIT)
846
+ if len(instances):
847
+ gl.glEnable(gl.GL_BLEND)
848
+ gl.glBlendFuncSeparate(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA,
849
+ gl.GL_ONE, gl.GL_ONE_MINUS_SRC_ALPHA)
850
+ gl.glUseProgram(program)
851
+ gl.glUniform2f(gl.glGetUniformLocation(program, 'size'), float(width), float(height))
852
+ gl.glUniform1i(gl.glGetUniformLocation(program, 'palette'), 0)
853
+ gl.glBindTexture(gl.GL_TEXTURE_2D, palette.texture_id)
854
+ gl.glBindVertexArray(vao)
855
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._style_context_vbo)
856
+ gl.glBufferData(gl.GL_ARRAY_BUFFER, instances.nbytes, instances, gl.GL_STREAM_DRAW)
857
+ gl.glDrawArraysInstanced(gl.GL_TRIANGLES, 0, 6, len(instances))
858
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, last_array_buffer)
859
+ gl.glBindVertexArray(last_vao)
860
+ gl.glUseProgram(last_program)
861
+ gl.glBindTexture(gl.GL_TEXTURE_2D, last_texture)
862
+ (gl.glEnable if last_scissor else gl.glDisable)(gl.GL_SCISSOR_TEST)
863
+ (gl.glEnable if last_blend else gl.glDisable)(gl.GL_BLEND)
864
+ return context.texture_id
865
+
866
+ def _bind_style_context(self, texture_id):
867
+ """Texture unit 1 = StyleContext for the whole command render."""
868
+ gl.glActiveTexture(gl.GL_TEXTURE1)
869
+ previous = int(gl.glGetIntegerv(gl.GL_TEXTURE_BINDING_2D))
870
+ gl.glBindTexture(gl.GL_TEXTURE_2D, int(texture_id))
871
+ gl.glActiveTexture(gl.GL_TEXTURE0)
872
+ return previous
873
+
874
+ @staticmethod
875
+ def _unbind_style_context(previous):
876
+ gl.glActiveTexture(gl.GL_TEXTURE1)
877
+ gl.glBindTexture(gl.GL_TEXTURE_2D, previous)
878
+ gl.glActiveTexture(gl.GL_TEXTURE0)
879
+
880
+ def _draw_style_elements(self, draw_list, texture_id, start, end, index_type):
881
+ """One command, one draw. The text shader samples the context layer
882
+ (_ensure_style_context) at gl_FragCoord for what is behind a glyph."""
883
+ gl.glDrawElements(gl.GL_TRIANGLES, end - start, index_type,
884
+ ctypes.c_void_p(start * imgui.INDEX_SIZE))
885
+
886
+ def _render_command_lists(self, draw_data, command_lists) -> None:
887
+ """Body of ProgrammablePipelineRenderer.render, parameterized over
888
+ which command lists to iterate. GL state save/restore is self-contained
889
+ so this is safe to call multiple times per frame."""
890
+ io = self.io
891
+
892
+ display_width, display_height = io.display_size
893
+ fb_scale_x, fb_scale_y = io.display_fb_scale
894
+ fb_width = int(display_width * fb_scale_x)
895
+ fb_height = int(display_height * fb_scale_y)
896
+
897
+ if fb_width == 0 or fb_height == 0:
898
+ return
899
+ if not command_lists:
900
+ return
901
+
902
+ self._refresh_style_shader()
903
+ common_gl_state_tuple = get_common_gl_state()
904
+ last_program = gl.glGetIntegerv(gl.GL_CURRENT_PROGRAM)
905
+ last_active_texture = gl.glGetIntegerv(gl.GL_ACTIVE_TEXTURE)
906
+ last_array_buffer = gl.glGetIntegerv(gl.GL_ARRAY_BUFFER_BINDING)
907
+ last_element_array_buffer = gl.glGetIntegerv(gl.GL_ELEMENT_ARRAY_BUFFER_BINDING)
908
+ last_vertex_array = gl.glGetIntegerv(gl.GL_VERTEX_ARRAY_BINDING)
909
+
910
+ gl.glEnable(gl.GL_BLEND)
911
+ gl.glBlendEquation(gl.GL_FUNC_ADD)
912
+ gl.glDisable(gl.GL_CULL_FACE)
913
+ gl.glDisable(gl.GL_DEPTH_TEST)
914
+ gl.glEnable(gl.GL_SCISSOR_TEST)
915
+ gl.glActiveTexture(gl.GL_TEXTURE0)
916
+ gl.glPolygonMode(gl.GL_FRONT_AND_BACK, gl.GL_FILL)
917
+
918
+ ox, oy = self._frame_origin()
919
+ gl.glViewport(ox, oy, fb_width, fb_height)
920
+
921
+ ortho_projection = (ctypes.c_float * 16)(
922
+ 2.0 / display_width, 0.0, 0.0, 0.0,
923
+ 0.0, 2.0 / -display_height, 0.0, 0.0,
924
+ 0.0, 0.0, -1.0, 0.0,
925
+ -1.0, 1.0, 0.0, 1.0,
926
+ )
927
+
928
+ from meltygui.core.runtime.toggles import Toggles
929
+ style_context = 0
930
+ if self._lcd_ok and Toggles.dynamic_styles:
931
+ style_context = self._ensure_style_context(fb_width, fb_height, fb_scale_x, fb_scale_y, ox, oy)
932
+ previous_context = self._bind_style_context(style_context)
933
+ gl.glUseProgram(self._shader_handle)
934
+ gl.glUniform1i(self._attrib_location_tex, 0)
935
+ gl.glUniformMatrix4fv(self._attrib_proj_mtx, 1, gl.GL_FALSE, ortho_projection)
936
+ font_tex = self._bind_text_mode()
937
+ gl.glBindVertexArray(self._vao_handle)
938
+
939
+ for commands in command_lists:
940
+ idx_buffer_offset = 0
941
+
942
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self._vbo_handle)
943
+ gl.glBufferData(
944
+ gl.GL_ARRAY_BUFFER,
945
+ commands.vtx_buffer_size * imgui.VERTEX_SIZE,
946
+ ctypes.c_void_p(commands.vtx_buffer_data),
947
+ gl.GL_STREAM_DRAW,
948
+ )
949
+
950
+ gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self._elements_handle)
951
+ gl.glBufferData(
952
+ gl.GL_ELEMENT_ARRAY_BUFFER,
953
+ commands.idx_buffer_size * imgui.INDEX_SIZE,
954
+ ctypes.c_void_p(commands.idx_buffer_data),
955
+ gl.GL_STREAM_DRAW,
956
+ )
957
+
958
+ for command in commands.commands:
959
+ gl.glBindTexture(gl.GL_TEXTURE_2D, command.texture_id)
960
+ if font_tex >= 0:
961
+ gl.glUniform1i(self._loc_atlas, 1 if int(command.texture_id) == font_tex else 0)
962
+
963
+ x, y, z, w = command.clip_rect
964
+ gl.glScissor(int(x) + ox, int(fb_height - w) + oy, int(z - x), int(w - y))
965
+
966
+ if imgui.INDEX_SIZE == 2:
967
+ gltype = gl.GL_UNSIGNED_SHORT
968
+ else:
969
+ gltype = gl.GL_UNSIGNED_INT
970
+
971
+ self._draw_style_elements(commands, command.texture_id,
972
+ idx_buffer_offset // imgui.INDEX_SIZE,
973
+ idx_buffer_offset // imgui.INDEX_SIZE + command.elem_count, gltype)
974
+
975
+ idx_buffer_offset += command.elem_count * imgui.INDEX_SIZE
976
+
977
+ self._unbind_style_context(previous_context)
978
+ restore_common_gl_state(common_gl_state_tuple)
979
+
980
+ gl.glUseProgram(last_program)
981
+ gl.glActiveTexture(last_active_texture)
982
+ gl.glBindVertexArray(last_vertex_array)
983
+ gl.glBindBuffer(gl.GL_ARRAY_BUFFER, last_array_buffer)
984
+ gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, last_element_array_buffer)