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,522 @@
1
+ import meltygui_imgui as imgui
2
+ from meltygui.hdr_color import pack_color
3
+ from meltygui.hdr_color import unpack_color
4
+ from meltygui.hdr_color import style_color
5
+ from meltygui.hdr_color import scale_saturation
6
+ import colorsys
7
+
8
+ from meltygui.core.styling.global_style import GlobalStyle
9
+
10
+
11
+ def cap_brightness(r, g, b, max_b):
12
+ """Cap PERCEIVED brightness (0.299r + 0.587g + 0.114b) at max_b — the
13
+ legibility guard for widget fills that carry light text. Scales the
14
+ channels, so hue and saturation survive (the max-side of the editor's
15
+ _brightness_clamp; kept local because toggles.py imports this module).
16
+ max_b <= 0 disables."""
17
+ if max_b <= 0:
18
+ return r, g, b
19
+ lum = 0.299 * r + 0.587 * g + 0.114 * b
20
+ if lum > max_b:
21
+ k = max_b / lum
22
+ return r * k, g * k, b * k
23
+ return r, g, b
24
+
25
+
26
+ class ImGuiStyleManager:
27
+ def __init__(self):
28
+ self.saved_colors = None
29
+ self.saved_style = None
30
+ self.saved_rgb = None
31
+ self.current_rgb = (0.0, 0.0, 0.0)
32
+ self.hsv = (0.0, 0.0, 0.0)
33
+ self.root = None
34
+
35
+ # List of all color indices we need to save/restore
36
+ self.color_indices = [
37
+ imgui.COLOR_TEXT,
38
+ imgui.COLOR_TEXT_DISABLED,
39
+ imgui.COLOR_WINDOW_BACKGROUND,
40
+ imgui.COLOR_CHILD_BACKGROUND,
41
+ imgui.COLOR_POPUP_BACKGROUND,
42
+ imgui.COLOR_BORDER,
43
+ imgui.COLOR_BORDER_SHADOW,
44
+ imgui.COLOR_FRAME_BACKGROUND,
45
+ imgui.COLOR_FRAME_BACKGROUND_HOVERED,
46
+ imgui.COLOR_FRAME_BACKGROUND_ACTIVE,
47
+ imgui.COLOR_TITLE_BACKGROUND,
48
+ imgui.COLOR_TITLE_BACKGROUND_ACTIVE,
49
+ imgui.COLOR_TITLE_BACKGROUND_COLLAPSED,
50
+ imgui.COLOR_SCROLLBAR_BACKGROUND,
51
+ imgui.COLOR_SCROLLBAR_GRAB,
52
+ imgui.COLOR_SCROLLBAR_GRAB_HOVERED,
53
+ imgui.COLOR_SCROLLBAR_GRAB_ACTIVE,
54
+ imgui.COLOR_CHECK_MARK,
55
+ imgui.COLOR_SLIDER_GRAB,
56
+ imgui.COLOR_SLIDER_GRAB_ACTIVE,
57
+ imgui.COLOR_BUTTON,
58
+ imgui.COLOR_BUTTON_HOVERED,
59
+ imgui.COLOR_BUTTON_ACTIVE,
60
+ imgui.COLOR_HEADER,
61
+ imgui.COLOR_HEADER_HOVERED,
62
+ imgui.COLOR_HEADER_ACTIVE,
63
+ imgui.COLOR_SEPARATOR,
64
+ imgui.COLOR_SEPARATOR_HOVERED,
65
+ imgui.COLOR_SEPARATOR_ACTIVE,
66
+ imgui.COLOR_RESIZE_GRIP,
67
+ imgui.COLOR_RESIZE_GRIP_HOVERED,
68
+ imgui.COLOR_RESIZE_GRIP_ACTIVE,
69
+ imgui.COLOR_TAB,
70
+ imgui.COLOR_TAB_HOVERED,
71
+ imgui.COLOR_TAB_ACTIVE,
72
+ imgui.COLOR_TAB_UNFOCUSED,
73
+ imgui.COLOR_TAB_UNFOCUSED_ACTIVE,
74
+ imgui.COLOR_PLOT_LINES,
75
+ imgui.COLOR_PLOT_LINES_HOVERED,
76
+ imgui.COLOR_PLOT_HISTOGRAM,
77
+ imgui.COLOR_PLOT_HISTOGRAM_HOVERED,
78
+ imgui.COLOR_TEXT_SELECTED_BACKGROUND,
79
+ imgui.COLOR_DRAG_DROP_TARGET,
80
+ imgui.COLOR_NAV_HIGHLIGHT,
81
+ imgui.COLOR_NAV_WINDOWING_HIGHLIGHT,
82
+ imgui.COLOR_NAV_WINDOWING_DIM_BACKGROUND,
83
+ ]
84
+
85
+ def set_root(self, root):
86
+ self.root = root
87
+
88
+ @staticmethod
89
+ def _safe_rgb_to_hsv(r, g, b):
90
+ # EXTENDED sRGB HSV: colours are never clamped into [0, 1] anymore.
91
+ # colorsys is exact on extended values whenever max(r, g, b) > 0 -
92
+ # a channel above 1 lands in v (> 1 = brighter than SDR white), a
93
+ # negative channel in s (> 1 = outside the sRGB gamut, the P3 tints
94
+ # from hdr_color.p3), and hsv_to_rgb hands both straight back. The
95
+ # old per-channel clamp is why draw_bg never showed a tint's HDR
96
+ # headroom or wide gamut (09-07). The one hazard is a max channel
97
+ # ≤ 0 (a black tint like (0.0, -0.005, -0.009), or all-negative):
98
+ # colorsys divides by it, so the only sensible result is black.
99
+ def clean(c):
100
+ try:
101
+ c = float(c)
102
+ except (TypeError, ValueError):
103
+ return 0.0
104
+ return 0.0 if c != c else c # NaN → 0
105
+
106
+ r, g, b = clean(r), clean(g), clean(b)
107
+ if max(r, g, b) <= 0.0:
108
+ return 0.0, 0.0, 0.0
109
+ return colorsys.rgb_to_hsv(r, g, b)
110
+
111
+ def make_custom_styled(self, r, g, b, input, alpha=1.0, value=0.5, saturation=None):
112
+ h, s, v = self._safe_rgb_to_hsv(r, g, b)
113
+ value = input["value"] + value
114
+
115
+ if saturation is not None:
116
+ saturation_scale = saturation
117
+ else:
118
+ saturation_scale = input["saturation"]
119
+ value = (v * GlobalStyle.base_value) + value
120
+ if 'max_value' in input:
121
+ value = min(value, input["max_value"])
122
+
123
+ modified_rgb = colorsys.hsv_to_rgb(h, scale_saturation(s, saturation_scale), value)
124
+ return (modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha)
125
+
126
+ def make_custom(self, r, g, b, value, saturation_scale=1.0, alpha=1.0):
127
+ h, s, v = self._safe_rgb_to_hsv(r, g, b)
128
+ modified_rgb = colorsys.hsv_to_rgb(h, scale_saturation(s, saturation_scale), value)
129
+ imgui_color = pack_color(modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha)
130
+
131
+ return unpack_color(imgui_color)[:3]
132
+
133
+ def make_color(self, value, saturation_scale=1.0, alpha=1.0):
134
+ h, s, v = self.hsv
135
+ value = (v * GlobalStyle.secondary_value) + value
136
+
137
+ modified_rgb = colorsys.hsv_to_rgb(h, scale_saturation(s, saturation_scale), value)
138
+ imgui_color = pack_color(modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha)
139
+ return imgui_color
140
+
141
+ def make_color_rgb(self, r, g, b, saturation_scale=0.4, alpha=1.0, factor=0.3, value=0.5):
142
+ """
143
+ Create an ImGui color from RGB values with optional saturation and alpha adjustments.
144
+ Args:
145
+ r, g, b: RGB values between 0 and 1
146
+ saturation_scale: Scale for saturation (default is 1.0)
147
+ alpha: Alpha value (default is 1.0)
148
+ Returns:
149
+ Packed u32 color value
150
+ """
151
+
152
+ def mix(r1, g1, b1, r2, g2, b2, alpha):
153
+ """Mix two colors with alpha blending"""
154
+ return (
155
+ r1 * (1 - alpha) + r2 * alpha,
156
+ g1 * (1 - alpha) + g2 * alpha,
157
+ b1 * (1 - alpha) + b2 * alpha
158
+ )
159
+
160
+ # Pure function of the current tint and the arguments - memoize:
161
+ # flat_button / tabs / dock rows call this twice per widget per
162
+ # frame with a handful of distinct values (two hsv round trips each).
163
+ key = (self.current_rgb, r, g, b, saturation_scale, alpha, factor, value)
164
+ hit = _MAKE_COLOR_RGB_MEMO.get(key)
165
+ if hit is not None:
166
+ return hit
167
+
168
+ modified_rgb = self.make_custom(*self.current_rgb, value, saturation_scale=saturation_scale, alpha=alpha)
169
+ # Apply alpha blending with the original color
170
+ modified_rgb = mix(r, g, b, modified_rgb[0], modified_rgb[1], modified_rgb[2], factor)
171
+
172
+ result = (modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha)
173
+ if len(_MAKE_COLOR_RGB_MEMO) > 4096:
174
+ _MAKE_COLOR_RGB_MEMO.clear() # bounded: drags mint many times
175
+ _MAKE_COLOR_RGB_MEMO[key] = result
176
+ return result
177
+
178
+ def make_color_style_imgui(self, input, alpha=1.0):
179
+ """
180
+ Create an ImGui color from a dictionary input with saturation and alpha adjustments.
181
+ Args:
182
+ input: Dictionary containing 'value', 'saturation', 'alpha', and optional 'max_value'
183
+ Returns:
184
+ Packed u32 color value
185
+ """
186
+ color = self.make_color_style(input, alpha)
187
+ return pack_color(color[0], color[1], color[2], alpha)
188
+
189
+ def make_color_style_value_imgui(self, input, saturation=None, alpha=1.0, value=0.0):
190
+ """
191
+ Create an ImGui color from a dictionary input with saturation and alpha adjustments.
192
+ Args:
193
+ input: Dictionary containing 'value', 'saturation', 'alpha', and optional 'max_value'
194
+ Returns:
195
+ Packed u32 color value
196
+ """
197
+ color = self.make_color_style_value(input, alpha, saturation=saturation, value=value)
198
+ return pack_color(color[0], color[1], color[2], alpha)
199
+
200
+
201
+ def make_color_style_rgb(self, r, g, b, input,factor=0.6):
202
+ """
203
+ Create an ImGui color from RGB values with optional saturation and alpha adjustments.
204
+ Args:
205
+ r, g, b: RGB values between 0 and 1
206
+ saturation_scale: Scale for saturation (default is 1.0)
207
+ alpha: Alpha value (default is 1.0)
208
+ Returns:
209
+ Packed u32 color value
210
+ """
211
+ h, s, v = self.hsv
212
+
213
+ value = input["value"]
214
+
215
+ saturation_scale = input["saturation"]
216
+ value = (v * GlobalStyle.base_value) + value
217
+ if 'max_value' in input:
218
+ value = min(value, input["max_value"])
219
+ alpha = input["alpha"]
220
+
221
+ modified_rgb = colorsys.hsv_to_rgb(h, scale_saturation(s, saturation_scale), value)
222
+
223
+ def mix(r1, g1, b1, r2, g2, b2, alpha):
224
+ """Mix two colors with alpha blending"""
225
+ return (
226
+ r1 * (1 - alpha) + r2 * alpha,
227
+ g1 * (1 - alpha) + g2 * alpha,
228
+ b1 * (1 - alpha) + b2 * alpha
229
+ )
230
+
231
+ # Apply alpha blending with the original color
232
+ modified_rgb = mix(r, g, b, modified_rgb[0], modified_rgb[1], modified_rgb[2], factor)
233
+
234
+ return modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha
235
+
236
+ def make_color_style_value(self, input, alpha=1.0, value=0.5, saturation=None):
237
+ h, s, v = self.hsv
238
+
239
+ value = input["value"] + value
240
+
241
+ if saturation is not None:
242
+ saturation_scale = saturation
243
+ else:
244
+ saturation_scale = input["saturation"]
245
+ value = (v * GlobalStyle.base_value) + value
246
+ if 'max_value' in input:
247
+ value = min(value, input["max_value"])
248
+
249
+ modified_rgb = colorsys.hsv_to_rgb(h, scale_saturation(s, saturation_scale), value)
250
+ return (modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha)
251
+
252
+ def make_color_style(self, input, alpha=1.0):
253
+ h, s, v = self.hsv
254
+
255
+ value = input["value"]
256
+
257
+ saturation_scale = input["saturation"]
258
+ value = (v * GlobalStyle.base_value) + value
259
+ if 'max_value' in input:
260
+ value = min(value, input["max_value"])
261
+ alpha = input["alpha"]
262
+
263
+ modified_rgb = colorsys.hsv_to_rgb(h, scale_saturation(s, saturation_scale), value)
264
+ return (modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha)
265
+
266
+ def make(self, value, saturation_scale=1.0, alpha=1.0):
267
+ _unpack_color = unpack_color
268
+
269
+ color = self.make_color(value, saturation_scale, alpha)
270
+ unpacked = _unpack_color(color)
271
+ return (unpacked[0], unpacked[1], unpacked[2])
272
+
273
+ def make_color_unpacked(self, value, saturation_scale=1.0, alpha=1.0):
274
+ _unpack_color = unpack_color
275
+ color = self.make_color(value, saturation_scale, alpha)
276
+ return _unpack_color(color)
277
+
278
+ def save_style(self):
279
+ """Save the current ImGui style and colors"""
280
+ # style = imgui.get_style()
281
+ self.saved_rgb = self.current_rgb
282
+
283
+ return self.saved_rgb
284
+ # # Save colors by their indices
285
+ # self.saved_colors = {i: tuple(style.colors[i]) for i in self.color_indices}
286
+ #
287
+ # # Save other style variables
288
+ # self.saved_style = {
289
+ # 'alpha': style.alpha,
290
+ # 'window_padding': style.window_padding,
291
+ # 'window_rounding': style.window_rounding,
292
+ # 'frame_padding': style.frame_padding,
293
+ # 'frame_rounding': style.frame_rounding,
294
+ # 'item_spacing': style.item_spacing,
295
+ # 'item_inner_spacing': style.item_inner_spacing,
296
+ # 'touch_extra_padding': style.touch_extra_padding,
297
+ # 'indent_spacing': style.indent_spacing,
298
+ # 'scrollbar_size': style.scrollbar_size,
299
+ # 'grab_min_size': style.grab_min_size
300
+ # }
301
+
302
+ def restore(self, saved_rgb=None):
303
+ if saved_rgb is not None:
304
+ self.set_imgui_tint(*saved_rgb)
305
+ else:
306
+ self.set_imgui_tint(*self.saved_rgb)
307
+
308
+ def restore_style(self, saved_rgb=None):
309
+ """Restore the previously saved style and colors"""
310
+
311
+ if saved_rgb is not None:
312
+ self.set_imgui_tint(*saved_rgb)
313
+ else:
314
+ self.set_imgui_tint(*self.saved_rgb)
315
+ # if self.saved_colors is None or self.saved_style is None:
316
+ # print("Warning: No style saved to restore")
317
+ # # Print stack trace
318
+ # import traceback
319
+ # traceback.print_stack()
320
+ # return False
321
+ #
322
+ # style = imgui.get_style()
323
+ # # Restore colors
324
+ # for i, color in self.saved_colors.items():
325
+ # style.colors[i] = color
326
+ #
327
+ # # Restore other style variables
328
+ # style.alpha = self.saved_style['alpha']
329
+ # style.window_padding = self.saved_style['window_padding']
330
+ # style.window_rounding = self.saved_style['window_rounding']
331
+ # style.frame_padding = self.saved_style['frame_padding']
332
+ # style.frame_rounding = self.saved_style['frame_rounding']
333
+ # style.item_spacing = self.saved_style['item_spacing']
334
+ # style.item_inner_spacing = self.saved_style['item_inner_spacing']
335
+ # style.touch_extra_padding = self.saved_style['touch_extra_padding']
336
+ # style.indent_spacing = self.saved_style['indent_spacing']
337
+ # style.scrollbar_size = self.saved_style['scrollbar_size']
338
+ # style.grab_min_size = self.saved_style['grab_min_size']
339
+ # return True
340
+
341
+ def tint_gold(self):
342
+ self.save_style()
343
+ self.set_imgui_tint(0.6, 0.5, 0.3)
344
+
345
+ def tint_default(self):
346
+ self.save_style()
347
+ self.set_imgui_tint(0.3, 0.6, 0.7)
348
+
349
+
350
+ def get_tint(self):
351
+ """
352
+ Returns the current tint color as an RGB tuple.
353
+ Returns:
354
+ Tuple of (r, g, b) values in the range [0.0, 1.0]
355
+ """
356
+ return self.current_rgb
357
+
358
+ def push_tint_fields(self, r, g, b, a=1.0):
359
+ """set_imgui_tint's COLOUR MATH without the imgui table apply: sets
360
+ `current_rgb` / `hsv` (what make_color_* and draw_bg read) and
361
+ returns the previous pair for pop_tint_fields. For draw-list code
362
+ that only needs draw_bg to colour from a tint (the editor's inline
363
+ widgets, ~40 a frame) — the 35-entry imgui table isn't consulted
364
+ there, and applying it twice per widget was half the widget's cost."""
365
+ prev = (self.current_rgb, self.hsv)
366
+ if a < 1.0 and self.current_rgb is not None:
367
+ pr, pg, pb = self.current_rgb
368
+ r = r * a + pr * (1.0 - a)
369
+ g = g * a + pg * (1.0 - a)
370
+ b = b * a + pb * (1.0 - a)
371
+ self.current_rgb = (r, g, b)
372
+ self.hsv = self._safe_rgb_to_hsv(r, g, b)
373
+ return prev
374
+
375
+ def pop_tint_fields(self, prev):
376
+ self.current_rgb, self.hsv = prev
377
+
378
+ def set_imgui_tint(self, r, g, b, a=1.0):
379
+ """
380
+ Sets a global tint color for ImGui by adjusting all style colors based on a single RGB color.
381
+ Args:
382
+ r, g, b: RGB values between 0 and 1
383
+ """
384
+
385
+ # No boot gate here: the studio's `set_root` used to be the first
386
+ # thing that armed this, and a @glfw_window app never called that, so
387
+ # setting tint was a no-op and the whole app painted from the black
388
+ # default (09-12). The colour math needs nothing; only the imgui
389
+ # style table below needs a check.
390
+
391
+ # A 4-component tint carries an alpha that controls how much of the
392
+ # current (parent) tint bleeds through. a=1.0 -> use the new color
393
+ # outright; a=0.0 -> keep the parent color in full force. Because every
394
+ # nested layer re-applies its tint through here, a low alpha lets the
395
+ # parent tint accumulate down the layer stack instead of washing out.
396
+ if a < 1.0 and self.current_rgb is not None:
397
+ pr, pg, pb = self.current_rgb
398
+ r = r * a + pr * (1.0 - a)
399
+ g = g * a + pg * (1.0 - a)
400
+ b = b * a + pb * (1.0 - a)
401
+
402
+ self.current_rgb = (r, g, b)
403
+ h, s, v = self._safe_rgb_to_hsv(r, g, b)
404
+ self.hsv = (h, s, v)
405
+ if imgui.get_current_context() is None:
406
+ return
407
+ style = imgui.get_style()
408
+ # The 35-entry table below is a pure function of the final (r, g, b)
409
+ # and the widget brightness cap, so it's memoized per tint: the
410
+ # editor's inline widgets push + restore a tint each (60+ widgets a
411
+ # frame on a Toggles-like file = ~2.8 ms of hsv conversion per frame).
412
+ from meltygui.core.runtime.toggles import Toggles
413
+ widget_max_b = Toggles.Style.widget_max_brightness
414
+ real_colors = style.colors
415
+ cache_key = (round(r, 5), round(g, 5), round(b, 5), widget_max_b)
416
+ cached = _TINT_TABLE_CACHE.get(cache_key)
417
+ if cached is not None:
418
+ for idx, col in cached:
419
+ real_colors[idx] = col
420
+ return
421
+ # Entries are recorded as the packer's bytes / 255 (hdr_color.style_color):
422
+ # imgui converts a style float4 to u32 by itself, and that conversion
423
+ # must reproduce pack_color's layout (the SDR bit, the 7-bit alpha).
424
+ colors = _TintTableRecorder()
425
+
426
+ def make_color(input, alpha=1.0):
427
+ value = input["value"]
428
+ saturation_scale = input["saturation"]
429
+ GlobalStyle.base_value = 0.3
430
+ value = (v * GlobalStyle.base_value) + value
431
+ alpha = input["alpha"]
432
+
433
+ modified_rgb = colorsys.hsv_to_rgb(h, scale_saturation(s, saturation_scale), value)
434
+ return (modified_rgb[0], modified_rgb[1], modified_rgb[2], alpha)
435
+
436
+ # Widget fills (buttons, frame backgrounds = text edits + drag/slider
437
+ # tracks, check.)) sit under the light COLOR_TEXT, so their
438
+ # brightness is capped (Toggles.Style.widget_max_brightness): a bright
439
+ # tint otherwise lifts them to the text's brightness and the widgets
440
+ # go blank. Function-level import: toggles.py imports this module.
441
+ from meltygui.core.runtime.toggles import Toggles
442
+ widget_max_b = Toggles.Style.widget_max_brightness
443
+
444
+ def make_widget_color(input):
445
+ cr, cg, cb, ca = make_color(input)
446
+ cr, cg, cb = cap_brightness(cr, cg, cb, widget_max_b)
447
+ return (cr, cg, cb, ca)
448
+
449
+ glb_cst = GlobalStyle.main_const
450
+
451
+ colors[imgui.COLOR_TEXT] = make_color(glb_cst["widget"]["text"]) # Nearly white text
452
+ colors[imgui.COLOR_TEXT_DISABLED] = make_color(glb_cst["widget"]["text_disabled"]) # Grayed out text
453
+
454
+ colors[imgui.COLOR_WINDOW_BACKGROUND] = make_color(glb_cst["window"]["background"]) # Dark background
455
+ colors[imgui.COLOR_CHILD_BACKGROUND] = make_color(glb_cst["window"]["child_bg"])
456
+ colors[imgui.COLOR_POPUP_BACKGROUND] = make_color(glb_cst["window"]["popup_bg"])
457
+ colors[imgui.COLOR_BORDER] = make_color(glb_cst["window"]["border"])
458
+
459
+ # Title
460
+ colors[imgui.COLOR_TITLE_BACKGROUND] = make_color(glb_cst["window"]["title_bg"])
461
+ colors[imgui.COLOR_TITLE_BACKGROUND_ACTIVE] = make_color(glb_cst["window"]["title_bg_active"])
462
+ colors[imgui.COLOR_TITLE_BACKGROUND_COLLAPSED] = make_color(glb_cst["window"]["title_bg_collapsed"])
463
+
464
+ # Headers
465
+ colors[imgui.COLOR_HEADER] = make_color(glb_cst["window"]["header"])
466
+ colors[imgui.COLOR_HEADER_HOVERED] = make_color(glb_cst["window"]["header_hovered"])
467
+ colors[imgui.COLOR_HEADER_ACTIVE] = make_color(glb_cst["window"]["header_active"])
468
+
469
+ colors[imgui.COLOR_RESIZE_GRIP] = make_color(glb_cst["widget"]["resize_grip"])
470
+ colors[imgui.COLOR_RESIZE_GRIP_HOVERED] = make_color(glb_cst["widget"]["resize_hovered"])
471
+ colors[imgui.COLOR_RESIZE_GRIP_ACTIVE] = make_color(glb_cst["widget"]["resize_active"])
472
+
473
+ # Buttons
474
+ colors[imgui.COLOR_BUTTON] = make_widget_color(glb_cst["widget"]["button"])
475
+ colors[imgui.COLOR_BUTTON_HOVERED] = make_widget_color(glb_cst["widget"]["button_hovered"])
476
+ colors[imgui.COLOR_BUTTON_ACTIVE] = make_widget_color(glb_cst["widget"]["button_active"])
477
+
478
+ # Frame backgrounds
479
+ colors[imgui.COLOR_FRAME_BACKGROUND] = make_widget_color(glb_cst["frame"]["frame_bg"])
480
+ colors[imgui.COLOR_FRAME_BACKGROUND_HOVERED] = make_widget_color(glb_cst["frame"]["frame_hovered"])
481
+ colors[imgui.COLOR_FRAME_BACKGROUND_ACTIVE] = make_widget_color(glb_cst["frame"]["frame_active"])
482
+
483
+ colors[imgui.COLOR_CHECK_MARK] = make_color(glb_cst["widget"]["check_mark"])
484
+ colors[imgui.COLOR_TEXT_SELECTED_BACKGROUND] = make_color(glb_cst["widget"]["text_selected_bg"])
485
+ # Tabs
486
+ colors[imgui.COLOR_TAB] = make_color(glb_cst["tab"]["tab"])
487
+ colors[imgui.COLOR_TAB_HOVERED] = make_color(glb_cst["tab"]["tab_hovered"])
488
+ colors[imgui.COLOR_TAB_ACTIVE] = make_color(glb_cst["tab"]["tab_active"])
489
+
490
+ # Borders and separators
491
+ colors[imgui.COLOR_SEPARATOR] = make_color(glb_cst["frame"]["separator"])
492
+
493
+ # Sliders, scrollbars
494
+ colors[imgui.COLOR_SLIDER_GRAB] = make_widget_color(glb_cst["widget"]["slider_grab"])
495
+ colors[imgui.COLOR_SLIDER_GRAB_ACTIVE] = make_widget_color(glb_cst["widget"]["slider_grab_active"])
496
+ colors[imgui.COLOR_SCROLLBAR_GRAB] = make_color(glb_cst["widget"]["scrollbar_grab"])
497
+ colors[imgui.COLOR_SCROLLBAR_GRAB_HOVERED] = make_color(glb_cst["widget"]["scrollbar_grab_hovered"])
498
+ colors[imgui.COLOR_SCROLLBAR_GRAB_ACTIVE] = make_color(glb_cst["widget"]["scrollbar_grab_active"])
499
+
500
+ table = colors.entries
501
+ for idx, col in table:
502
+ real_colors[idx] = col
503
+ if len(_TINT_TABLE_CACHE) > 512:
504
+ _TINT_TABLE_CACHE.clear() # bounded: a slider will muck many tints
505
+ _TINT_TABLE_CACHE[cache_key] = table
506
+
507
+
508
+ class _TintTableRecorder:
509
+ """Stand-in for style.colors while set_imgui_tint computes a table:
510
+ records (index, color) so the result can be memoized and replayed."""
511
+ __slots__ = ("entries",)
512
+
513
+ def __init__(self):
514
+ self.entries = []
515
+
516
+ def __setitem__(self, idx, col):
517
+ self.entries.append((idx, style_color(*col)))
518
+
519
+
520
+ _TINT_TABLE_CACHE = globals().get("_TINT_TABLE_CACHE", {}) # (r, g, b, cap) → [(idx, color)]
521
+ # (current tint, r, g, b, sat, alpha, factor, cap) → rgba - make_color_rgb
522
+ _MAKE_COLOR_RGB_MEMO = globals().get("_MAKE_COLOR_RGB_MEMO", {})
@@ -0,0 +1,149 @@
1
+ """Start-up shortcuts for meltygui apps (app.py), each worth tens of
2
+ milliseconds on the first frame. All measured 2026-09-10 on Hyprland with an
3
+ NVIDIA GPU, originally in hdr-viewer's warm_start.py. MELTY_COLD=1 skips them.
4
+
5
+ prepare(cache) call BEFORE ``import glfw``
6
+ remember_glfw_library() call after it
7
+ cache_hinted_atlas(fm) wrap a meltygui FontManager before the renderer builds
8
+ """
9
+ import hashlib
10
+ import os
11
+ import pathlib
12
+
13
+ # --- glfw shared library -------------------------------------------------------
14
+ # PyGLFW walks ~50 candidate paths and dlopens several before settling on the
15
+ # library (48 ms). PYGLFW_LIBRARY short-circuits that, so cache what it found.
16
+
17
+ def _glfw_cache_file(cache):
18
+ return cache / 'glfw-library'
19
+
20
+
21
+ def _hint_glfw_library(cache):
22
+ if os.environ.get('PYGLFW_LIBRARY'):
23
+ return
24
+ try:
25
+ path = _glfw_cache_file(cache).read_text().strip()
26
+ except OSError:
27
+ return
28
+ if path and os.path.isfile(path):
29
+ os.environ['PYGLFW_LIBRARY'] = path
30
+
31
+
32
+ def remember_glfw_library(cache):
33
+ import meltygui.core.windowing.window_api as glfw
34
+ path = getattr(getattr(glfw, '_glfw', None), '_name', None)
35
+ if not path or not os.path.isabs(path):
36
+ return
37
+ f = _glfw_cache_file(cache)
38
+ try:
39
+ if f.read_text().strip() == path:
40
+ return
41
+ except OSError:
42
+ pass
43
+ _write_atomic(f, path.encode())
44
+
45
+
46
+ # --- cursor theme --------------------------------------------------------------
47
+ # libwayland-cursor parses EVERY file of the theme when glfw creates the window.
48
+ # Bibata's two animated cursors (wait, progress) are 33 KB each: 130 ms for
49
+ # something a viewer never shows. Point XCURSOR at a shadow theme that symlinks
50
+ # only the theme's small cursors (< 1 MB) and inherits nothing.
51
+
52
+ CURSOR_MAX_BYTES = 1_000_000
53
+ _DEFAULT_CURSOR_PATH = ('~/.icons', '~/.local/share/icons', '/usr/share/icons', '/usr/share/pixmaps')
54
+
55
+
56
+ def _cursor_theme_dir(name):
57
+ dirs = os.environ.get('XCURSOR_PATH', '').split(':') if os.environ.get('XCURSOR_PATH') else _DEFAULT_CURSOR_PATH
58
+ for d in dirs:
59
+ if not d:
60
+ continue
61
+ p = pathlib.Path(d).expanduser() / name / 'cursors'
62
+ if p.is_dir():
63
+ return p
64
+ return None
65
+
66
+
67
+ def _shadow_cursor_theme(cache):
68
+ name = os.environ.get('XCURSOR_THEME') or 'default'
69
+ src = _cursor_theme_dir(name)
70
+ if src is None:
71
+ return
72
+ shadow_name = f'meltygui-{name}'
73
+ root = cache / 'cursor-themes'
74
+ theme = root / shadow_name
75
+ cursors = theme / 'cursors'
76
+ try:
77
+ stale = not cursors.is_dir() or cursors.stat().st_mtime < src.stat().st_mtime
78
+ if stale:
79
+ tmp = root / f'{shadow_name}.{os.getpid()}.tmp'
80
+ (tmp / 'cursors').mkdir(parents=True)
81
+ for entry in src.iterdir():
82
+ try:
83
+ if entry.is_file() and entry.stat().st_size < CURSOR_MAX_BYTES:
84
+ (tmp / 'cursors' / entry.name).symlink_to(entry)
85
+ except OSError:
86
+ pass
87
+ (tmp / 'index.theme').write_text(f'[Icon Theme]\nName={shadow_name}\n')
88
+ if theme.exists():
89
+ import shutil
90
+ shutil.rmtree(theme, ignore_errors=True)
91
+ os.rename(tmp, theme)
92
+ except OSError:
93
+ return
94
+ old_path = os.environ.get('XCURSOR_PATH')
95
+ os.environ['XCURSOR_PATH'] = str(root) + (':' + old_path if old_path else ':' + ':'.join(_DEFAULT_CURSOR_PATH))
96
+ os.environ['XCURSOR_THEME'] = shadow_name
97
+
98
+
99
+ # --- export --------------------------------------------------------------------
100
+
101
+ def prepare(cache):
102
+ """Environment tweaks; must run before ``import glfw``. MELTY_COLD=1 skips them."""
103
+ if os.environ.get('MELTY_COLD'):
104
+ return
105
+ try:
106
+ cache.mkdir(parents=True, exist_ok=True)
107
+ except OSError:
108
+ return
109
+ _hint_glfw_library(cache)
110
+ _shadow_cursor_theme(cache)
111
+
112
+
113
+ def cache_hinted_atlas(font_mgr, cache):
114
+ """FontManager.hint_atlas (FreeType re-rasterising every UI glyph, ~30 ms)
115
+ is a pure function of the stb atlas it is handed, so cache its output on
116
+ disk keyed by those pixels (and meltygui's fonts.py, which holds the algorithm)."""
117
+ if os.environ.get('MELTY_COLD'):
118
+ return
119
+ real = font_mgr.hint_atlas
120
+ try:
121
+ algo = str(os.stat(pathlib.Path(__file__).with_name('fonts.py')).st_mtime_ns).encode()
122
+ except OSError:
123
+ algo = b''
124
+
125
+ def hint_atlas(width, height, pixels):
126
+ key = hashlib.sha1(algo + b'|' + pixels).hexdigest()[:16]
127
+ f = cache / f'atlas-{width}x{height}-{key}.rgba'
128
+ try:
129
+ data = f.read_bytes()
130
+ if len(data) == width * height * 4:
131
+ return data
132
+ except OSError:
133
+ pass
134
+ data = real(width, height, pixels)
135
+ if data is not None:
136
+ _write_atomic(f, data)
137
+ return data
138
+
139
+ font_mgr.hint_atlas = hint_atlas
140
+
141
+
142
+ def _write_atomic(path, data):
143
+ try:
144
+ path.parent.mkdir(parents=True, exist_ok=True)
145
+ tmp = path.with_name(f'{path.name}.{os.getpid()}.tmp')
146
+ tmp.write_bytes(data)
147
+ os.replace(tmp, path)
148
+ except OSError:
149
+ pass
@@ -0,0 +1 @@
1
+ """Shared framework windowing machinery."""