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,824 @@
1
+ """Color view functions and supporting definitions."""
2
+ from meltygui.core.graphics.gl_state import GLState
3
+ from meltygui.core.core_render import render_func
4
+ from meltygui.state.new_core_model import ColorPickerState
5
+ from meltygui.state.new_core_model import DrawState
6
+ from meltygui.state.new_core_model import TabState
7
+ from meltygui.core.runtime.toggles import Toggles
8
+ import meltygui_imgui as imgui
9
+ import types
10
+ from meltygui.hdr_color import pack_color
11
+ from meltygui.core.runtime.toggles import Tint
12
+
13
+
14
+ def draw_style_policy_fast(owner, draw_state):
15
+ """One shared code-host editor, only for the selected effective policy."""
16
+ from meltygui.code.new_converters import code_hosts_for
17
+ from meltygui.code.new_converters import host_code_state
18
+ from meltygui.core.styling.fonts import Font
19
+ from meltygui.core.windowing.glfw_utils import request_render
20
+ from meltygui.view.text_view import draw_text
21
+ from meltygui.core.styling.color_core import _add_style_policy
22
+ from meltygui.core.styling.color_core import _style_policy_source
23
+
24
+ fields = ("tint_fn", "font_size_fn", "font_weight_fn", "shadow_fn")
25
+ selected = draw_state.misc.get("style_policy_index", 0)
26
+ imgui.push_item_width(180)
27
+ imgui.push_style_color(imgui.COLOR_FRAME_BACKGROUND, 0.0, 0.0, 0.0, 0.0)
28
+ changed, selected = imgui.combo("Parent policy", selected,
29
+ ["Tint", "Font size", "Font weight", "Shadow"])
30
+ imgui.pop_style_color()
31
+ imgui.pop_item_width()
32
+ if changed:
33
+ draw_state.misc["style_policy_index"] = selected
34
+ request_render()
35
+ fn, source = _style_policy_source(owner, fields[selected])
36
+ if source is not owner:
37
+ from meltygui.view.header_view import flat_button
38
+ if flat_button("+ Add policy here", draw_state,
39
+ view_id="add_style_policy_" + fields[selected], shadow=False):
40
+ _add_style_policy(owner, fields[selected])
41
+ fn, source = _style_policy_source(owner, fields[selected])
42
+ origin = "default" if source is None else ("this view" if source is owner else str(source.name).split("##")[0])
43
+ imgui.text("From: " + origin)
44
+ imgui.text_disabled("Shared function: edits apply to its users.")
45
+ if not isinstance(fn, types.FunctionType) or fn.__name__ == "<lambda>":
46
+ imgui.text_wrapped("This policy has no standalone function definition. Edit its containing source in Inputs.")
47
+ return
48
+ # Identical shared editor to Inputs; no inspect/getsource/exec in this UI.
49
+ str_host, dict_host = code_hosts_for(fn)
50
+ str_host.notify_on_change(draw_state)
51
+ dict_host.notify_on_change(draw_state)
52
+ text = str_host.get(str_host.value_key)
53
+ if not isinstance(text, str):
54
+ imgui.text_disabled("Loading policy…")
55
+ return
56
+ code_state = host_code_state(str_host)
57
+ changed, updated = draw_text(
58
+ text, name="style_policy_" + fields[selected],
59
+ width=480, height=180, font=Font.JETBRAINS_MONO_13,
60
+ is_tree=False, show_header=False, editable=True,
61
+ code_dict=dict_host._held(),
62
+ jump_to=code_state.address if code_state is not None else None)
63
+ if changed and updated != text:
64
+ str_host[str_host.value_key] = updated
65
+
66
+
67
+ def draw_style_residuals_fast(owner, draw_state=None):
68
+ """Popover-only controls; locate reads/writes the source driving the view."""
69
+ from meltygui.core.windowing.glfw_utils import request_render
70
+
71
+ from meltygui.core.styling.style import Style
72
+ from meltygui.state.core_undo import UndoManager
73
+
74
+ value = owner.locate_style
75
+ attr = "locate_style" if value is not None else "locate_tint"
76
+ if value is None:
77
+ value = owner.locate_tint
78
+ original = value
79
+ if not isinstance(value, Style):
80
+ value = Style(value or (0, 0, 0), absolute=value is not None)
81
+ rgb = list(value[:3])
82
+ metadata = value.__getnewargs_ex__()[1]
83
+ alpha = value[3] if len(value) == 4 else 1.0
84
+ imgui.text("Edit " + attr.removeprefix("locate_") + " at its source")
85
+ imgui.push_style_var(imgui.STYLE_FRAME_BORDERSIZE, 1.0)
86
+ changed, metadata['absolute'] = imgui.checkbox("Absolute values", value.absolute)
87
+ imgui.text_disabled("Signed shifts follow parent policy.")
88
+ imgui.separator()
89
+ # Let the existing background context show through the native controls.
90
+ for slot in (imgui.COLOR_FRAME_BACKGROUND, imgui.COLOR_FRAME_BACKGROUND_HOVERED,
91
+ imgui.COLOR_FRAME_BACKGROUND_ACTIVE):
92
+ imgui.push_style_color(slot, 0.0, 0.0, 0.0, 0.0)
93
+ imgui.push_item_width(120)
94
+ for i, label in enumerate(("Red", "Green", "Blue")):
95
+ edited, rgb[i] = imgui.drag_float(label, rgb[i], change_speed=0.005, format="%.3f")
96
+ changed |= edited
97
+ edited, alpha = imgui.slider_float("Opacity", alpha, 0.0, 1.0, format="%.2f")
98
+ changed |= edited
99
+ imgui.separator()
100
+ for name, label, speed in (("font_size", "Size (px)", 0.1),
101
+ ("font_weight", "Weight", 5.0),
102
+ ("shadow_offset", "Shadow", 0.1)):
103
+ current = metadata[name]
104
+ edited, enabled = imgui.checkbox("##enable_" + name, current is not None)
105
+ if edited:
106
+ metadata[name] = 0.0 if enabled else None
107
+ changed = True
108
+ imgui.same_line()
109
+ if enabled:
110
+ edited, number = imgui.drag_float(label, metadata[name], change_speed=speed, format="%.2f")
111
+ if edited:
112
+ metadata[name] = number
113
+ changed = True
114
+ else:
115
+ imgui.text_disabled(label + " (inherit)")
116
+ imgui.pop_item_width()
117
+ imgui.pop_style_color(3)
118
+ imgui.pop_style_var()
119
+ imgui.separator()
120
+ imgui.text_disabled("Unchecked = inherit.")
121
+ if changed:
122
+ updated = Style(tuple(rgb) + ((alpha,) if len(value) == 4 or alpha != 1.0 else ()), **metadata)
123
+ setter = lambda new: setattr(owner, attr, new)
124
+ UndoManager.record(owner, original, updated, setter=setter,
125
+ key="header_style_residuals", label="Style residuals")
126
+ setter(updated)
127
+ owner.invalidate()
128
+ request_render()
129
+ if draw_state is not None:
130
+ imgui.separator()
131
+ draw_style_policy_fast(owner, draw_state)
132
+
133
+
134
+ def draw_view_offsets_fast(owner, draw_state=None):
135
+ """Popover-only rows under the colour tabs: the OWNER view's `bg_offset`
136
+ and `z_offset` (VIEW_OFFSET_ROWS), read through `owner.locate_<param>`
137
+ and written back the same way — set_anywhere picks the source that
138
+ drives the param (a `@window(bg_offset=…)`, a caller kwarg, a `# [ ]`
139
+ comment) and falls back to the owner's own draw_state. Every change is
140
+ a SetterChange on the undo stack (like the colour chip's) and invalidates
141
+ the owner; the wrapper reads both offsets on its next run."""
142
+ from meltygui.core.windowing.glfw_utils import request_render
143
+
144
+ from meltygui.state.core_undo import UndoManager
145
+ # [tint=(0.85, 0.75, 0.05)]
146
+ row_width = 120
147
+ imgui.separator()
148
+ for slot in (imgui.COLOR_FRAME_BACKGROUND, imgui.COLOR_FRAME_BACKGROUND_HOVERED,
149
+ imgui.COLOR_FRAME_BACKGROUND_ACTIVE):
150
+ imgui.push_style_color(slot, 0.0, 0.0, 0.0, 0.0)
151
+ imgui.push_item_width(row_width)
152
+ for param, label, speed in Toggles.ColorPicker.view_offset_rows:
153
+ current = getattr(owner, "locate_" + param)
154
+ current = int(current) if isinstance(current, (int, float)) else 0
155
+ edited, value = imgui.drag_int(f"{label}##view_{param}", current, speed)
156
+ if edited and value != current:
157
+ def setter(new, _owner=owner, _attr="locate_" + param):
158
+ setattr(_owner, _attr, new)
159
+ _owner.invalidate()
160
+ UndoManager.record(owner, current, value, setter=setter,
161
+ key="view_" + param, label=label)
162
+ setter(value)
163
+ request_render()
164
+ imgui.pop_item_width()
165
+ imgui.pop_style_color(3)
166
+
167
+
168
+ @render_func(use_cache=False, show_bg=True, shadow=False, selectable=False, with_header=None)
169
+ def draw_color_picker(input_value, wrap=True, draw_state=None, info=None,
170
+ picker_state: ColorPickerState = None, gl_state: GLState = None, owner=None, **kwargs):
171
+ """The colour picker popover, three tabs: **Wide** (default) — a
172
+ Display-P3 hue/saturation/value square whose value axis runs on above
173
+ white (`Toggles.HDR.picker_max_stops`), rendered as an fp16 texture so
174
+ every texel is the real HDR/P3 colour, with the sRGB-reachable region
175
+ outlined — **sRGB**, the classic square — and **sRGB+**, the classic
176
+ square kept at its size with a wide-gamut extension to its right
177
+ (`_draw_extended_picker`). All take and return extended-sRGB tuples
178
+ (hdr_color.py), so a colour picked on one tab reads back on the others
179
+ (an out-of-sRGB value shows clipped on the sRGB tab)."""
180
+ from meltygui.core.windowing.glfw_utils import request_render
181
+
182
+ # The tab strip: draw-list flat_buttons (no wrapper per tab), neutral
183
+ # grey like draw_tabs' untinted strip — the active tab gets the filled
184
+ # rect, the others draw label-only.
185
+ # [tint=(0.85, 0.75, 0.05)]
186
+ tab_color = (0.5, 0.5, 0.5)
187
+ # [tint=(0.85, 0.75, 0.05)]
188
+ tab_height = 21
189
+ from meltygui.view.header_view import flat_button
190
+ imgui.dummy(0, 2)
191
+ tabs = [("Wide", "wide"), ("sRGB", "srgb"), ("sRGB+", "extended")]
192
+ if owner is not None and Toggles.dynamic_styles:
193
+ tabs.append(("Residuals", "residuals"))
194
+ for label, key in tabs:
195
+ selected = picker_state.tab == key
196
+ if flat_button(label, draw_state, view_id=f"cp_tab_{key}", height=tab_height,
197
+ color=tab_color, factor=1.2, corner_radius=4,
198
+ tint_value=0.35 if selected else 0.15, saturation=0.3,
199
+ alpha=1.0 if selected else 0.0, text_value=1.0,
200
+ event="left_mouse_down"):
201
+ picker_state.tab = key
202
+ request_render()
203
+ imgui.same_line(spacing=4)
204
+ imgui.new_line()
205
+ if picker_state.tab == "residuals" and owner is not None and Toggles.dynamic_styles:
206
+ draw_style_residuals_fast(owner, draw_state)
207
+ return False, input_value
208
+ if picker_state.tab == "extended":
209
+ result = _draw_extended_picker(input_value, draw_state, gl_state, info)
210
+ else:
211
+ # The band's room stays reserved: the square lands at the same pos on
212
+ # every tab.
213
+ imgui.dummy(0, Toggles.ColorPicker.exposure_band_height)
214
+ if picker_state.tab == "srgb":
215
+ result = _draw_srgb_picker(input_value, draw_state, info)
216
+ else:
217
+ result = _draw_wide_picker(input_value, draw_state, gl_state, info)
218
+ if owner is not None:
219
+ # Editing a VIEW's colour: its other paint knobs ride along under
220
+ # the colour tabs (color_picker_height reserves two rows).
221
+ draw_view_offsets_fast(owner, draw_state)
222
+ return result
223
+
224
+
225
+ @render_func(use_cache=True, show_bg=True, selectable=False)
226
+ def draw_tint_context(input_value: DrawState, tab_state: TabState = None, **kwargs):
227
+ from meltygui.view.collection_view import draw_tuple
228
+ from meltygui.core.rendering.parameter_core import anywhere_value
229
+ from meltygui.core.rendering.parameter_core import get_source_for
230
+ from meltygui.core.rendering.parameter_core import set_anywhere
231
+
232
+ if Toggles.debug_set_anywhere:
233
+ source_name = get_source_for("tint", input_value)
234
+ imgui.text(f"Tint source: {source_name}")
235
+
236
+ # The live framework-resolved tint - except mid-round-trip, when the
237
+ # pending UI value shows so the widget doesn't snap back while the
238
+ # write→save→hotswap frames play out (anywhere_value).
239
+ tint_changed, tint_value = draw_tuple(anywhere_value("tint", input_value), name="Tint: set anywhere", height=30,
240
+ width=40,
241
+ show_name=False, show_header=False)
242
+ if tint_changed:
243
+ set_anywhere("tint", tint_value, input_value)
244
+
245
+ return False, None
246
+
247
+
248
+
249
+ def color_picker_height(n_channels: int, has_info: bool = False,
250
+ has_owner: bool = False) -> int:
251
+ """Height of draw_color_picker's popover: tab row + exposure band +
252
+ square + the channel rows + the readout line (+ the info caption) (+ the
253
+ view-offset rows an owner draw_state adds, `draw_view_offsets_fast`)."""
254
+
255
+ return (Toggles.ColorPicker.tabs_height + Toggles.ColorPicker.exposure_band_height + Toggles.ColorPicker.square_size + 14
256
+ + n_channels * 26 + 26 + (22 if has_info else 0)
257
+ + (Toggles.ColorPicker.offsets_height if has_owner else 0))
258
+
259
+
260
+ def color_picker_top_offset(gap: int = Toggles.ColorPicker.anchor_gap) -> int:
261
+ """The popover's y offset below its anchor (the cursor under the
262
+ swatch): the popover HANGS under the swatch, tabs first, and the
263
+ exposure band pushes the square down. It used to return `gap` less the
264
+ band so the window grew upward and the square kept its band-less spot —
265
+ that parked the tab row above the swatch, over the host's header row
266
+ (Lukas 09-10: too high)."""
267
+ return gap
268
+
269
+
270
+ def color_picker_width() -> int:
271
+ """Width of draw_color_picker's popover: the square + hue bar row of
272
+ the widest tab (sRGB+, whose extension sits between square and hue bar)
273
+ plus the window margins. One size for every tab — the popover is a
274
+ fixed-size closable window and the tab is the picker's own state."""
275
+
276
+ return 216 + Toggles.ColorPicker.extension_width
277
+
278
+
279
+ def _draw_wide_picker(input_value, draw_state, gl_state, info):
280
+ """The Wide tab: P3 HSV + exposure (hdr_color.p3_hsv_from_extended). The
281
+ square's X is P3 saturation, its top `picker_top_fraction` is exposure
282
+ (2^max_stops at the top, white at the seam), the rest the classic value
283
+ axis. The sRGB outline is the gamut edge for the current hue
284
+ (hdr_color.srgb_region_outline). `draw_state._cpw_precise` echoes our own
285
+ edits back like the sRGB tab's `_cp_precise`."""
286
+ from meltygui.model.color_model import _wide_marker
287
+ from meltygui.model.color_model import _wide_pick
288
+ from meltygui.core.windowing.glfw_utils import request_render
289
+ from meltygui.view.control_view import button
290
+
291
+ import meltygui.hdr_color as hdr_color
292
+ # [tint=(0.85, 0.75, 0.05)]
293
+ outline_color = (1.0, 1.0, 1.0, 0.8)
294
+ # [tint=(0.85, 0.75, 0.05)]
295
+ outline_shadow = (0.0, 0.0, 0.0, 0.6)
296
+ SQ, BAR_W, GAP = Toggles.ColorPicker.square_size, 18, 8
297
+ max_stops = float(Toggles.HDR.picker_max_stops)
298
+ top_fraction = float(Toggles.HDR.picker_top_fraction)
299
+ imgui.dummy(0, 3)
300
+ vals = list(input_value)
301
+ has_alpha = len(vals) >= 4
302
+ r, g, b = float(vals[0]), float(vals[1]), float(vals[2])
303
+ a = float(vals[3]) if has_alpha else 1.0
304
+ in_r, in_g, in_b, in_a = r, g, b, a
305
+
306
+ ECHO_TOL = 0.002
307
+ _prec = getattr(draw_state, '_cpw_precise', None)
308
+ is_echo = _prec is not None and all(abs(pc - c) <= ECHO_TOL for pc, c in zip(_prec[0], (r, g, b, a)))
309
+ if is_echo:
310
+ r, g, b, a = _prec[0]
311
+ h, s, v, exposure = _prec[1]
312
+ else:
313
+ h, s, v, exposure = hdr_color.p3_hsv_from_extended(r, g, b)
314
+ if _prec is not None:
315
+ if s <= 0.0 or v <= 0.0:
316
+ h = _prec[1][0]
317
+ if v <= 0.0:
318
+ s = _prec[1][1]
319
+
320
+ dl = imgui.get_window_draw_list()
321
+ white = pack_color(1, 1, 1, 1)
322
+ black = pack_color(0, 0, 0, 1)
323
+ changed = False
324
+ hsv_changed = False
325
+
326
+ # --- the square: an fp16 texture of the hue's slice (re-baked per hue) ---
327
+ sx0, sy0 = imgui.get_cursor_screen_pos()
328
+ tex = _wide_square_texture(gl_state, h, SQ, top_fraction, max_stops)
329
+ if tex is not None:
330
+ dl.add_image(tex.texture_id, (sx0, sy0), (sx0 + SQ, sy0 + SQ))
331
+ imgui.invisible_button("##wsv", SQ, SQ)
332
+ if imgui.is_item_active():
333
+ mx, my = imgui.get_mouse_pos()
334
+ s, v, exposure = _wide_pick((mx - sx0) / SQ, (my - sy0) / SQ, top_fraction, max_stops)
335
+ hsv_changed = True
336
+
337
+ # --- hue bar: P3 hues at full saturation ---
338
+ imgui.same_line(spacing=GAP)
339
+ hx0, hy0 = imgui.get_cursor_screen_pos()
340
+ for i in range(12):
341
+ t0, t1 = i / 12.0, (i + 1) / 12.0
342
+ c0 = pack_color(*hdr_color.p3(*imgui.color_convert_hsv_to_rgb(t0, 1, 1)), 1)
343
+ c1 = pack_color(*hdr_color.p3(*imgui.color_convert_hsv_to_rgb(t1, 1, 1)), 1)
344
+ dl.add_rect_filled_multicolor(hx0, hy0 + SQ * t0, hx0 + BAR_W, hy0 + SQ * t1, c0, c0, c1, c1)
345
+ imgui.invisible_button("##whue", BAR_W, SQ)
346
+ if imgui.is_item_active():
347
+ h = min(max((imgui.get_mouse_pos()[1] - hy0) / SQ, 0.0), 1.0)
348
+ hsv_changed = True
349
+
350
+ # --- sRGB region outline + the white seam ---
351
+ pts = [(sx0 + px * SQ, sy0 + py * SQ) for px, py in hdr_color.srgb_region_outline(h, top_fraction)]
352
+ dl.add_polyline(pts, pack_color(*outline_shadow), False, 3.0)
353
+ dl.add_polyline(pts, pack_color(*outline_color), False, 1.0)
354
+ seam_y = sy0 + top_fraction * SQ
355
+ dl.add_line(sx0, seam_y, sx0 + SQ, seam_y, pack_color(1, 1, 1, 0.25), 1.0)
356
+
357
+ # --- markers ---
358
+ mx_, my_ = _wide_marker(s, v, exposure, top_fraction, max_stops)
359
+ cx, cy = sx0 + mx_ * SQ, sy0 + my_ * SQ
360
+ dl.add_circle(cx, cy, 6, black, thickness=1.0)
361
+ dl.add_circle(cx, cy, 5, white, thickness=1.5)
362
+ hmy = hy0 + h * SQ
363
+ dl.add_rect(hx0 - 1, hmy - 2, hx0 + BAR_W + 1, hmy + 2, white, thickness=1.5)
364
+
365
+ if hsv_changed:
366
+ r, g, b = hdr_color.extended_from_p3_hsv(h, s, v, exposure)
367
+ changed = True
368
+
369
+ # --- channel rows: extended sRGB, so they read past 1 and below 0 ---
370
+ imgui.dummy(0, 4)
371
+ imgui.push_item_width(SQ + GAP + BAR_W)
372
+ out, edited = [], []
373
+ lo, hi = -1.0, hdr_color.linear_to_srgb(2.0 ** max_stops)
374
+ for lbl, cur in ([("R", r), ("G", g), ("B", b)] + ([("A", a)] if has_alpha else [])):
375
+ imgui.set_next_item_width(draw_state.content_width - 30)
376
+ if lbl == "A":
377
+ ch, nv = imgui.drag_float(f"{lbl}##cpw_{lbl}", cur, 0.004, 0.0, 1.0, "%.3f")
378
+ else:
379
+ ch, nv = imgui.drag_float(f"{lbl}##cpw_{lbl}", cur, 0.006, lo, hi, "%.3f")
380
+ if ch:
381
+ changed = True
382
+ edited.append(ch)
383
+ out.append(nv if ch else cur)
384
+ imgui.dummy(0, 1)
385
+ imgui.pop_item_width()
386
+ r, g, b = out[0], out[1], out[2]
387
+ if has_alpha:
388
+ a = out[3]
389
+
390
+ # --- readout: exposure + gamut ---
391
+ # Remove the colour: returns None (the tuple popover unsets its tuple).
392
+ if button("\uf1f8", tint=(1, 0, 0, 0.5), height=21, shadow=True, use_cache=True,
393
+ name="delete_color##", text_value=1.6)[0]:
394
+ request_render()
395
+ return True, None
396
+ imgui.same_line()
397
+ gamut = "sRGB" if all(0.0 <= c <= 1.0 for c in (r, g, b)) else ("P3" if exposure <= 1.0 else "P3 HDR")
398
+ imgui.text_colored(f"{exposure:.2f}× white · {gamut}", *Tint.subtle_text())
399
+ if info:
400
+ imgui.dummy(0, 2)
401
+ imgui.text_colored(str(info), 1.0, 1.0, 1.0, 0.45)
402
+ if changed:
403
+ if has_alpha and not edited[3]:
404
+ a = in_a
405
+ if not hsv_changed:
406
+ if not edited[0]:
407
+ r = in_r
408
+ if not edited[1]:
409
+ g = in_g
410
+ if not edited[2]:
411
+ b = in_b
412
+ nh, ns, nv, ne = hdr_color.p3_hsv_from_extended(r, g, b)
413
+ if ns <= 0.0 or nv <= 0.0:
414
+ nh = h
415
+ if nv <= 0.0:
416
+ ns = s
417
+ h, s, v, exposure = nh, ns, nv, ne
418
+ draw_state._cpw_precise = ((r, g, b, a), (h, s, v, exposure))
419
+ request_render()
420
+ return True, ((r, g, b, a) if has_alpha else (r, g, b))
421
+ return False, input_value
422
+
423
+
424
+ def _wide_square_texture(gl_state, hue, size, top_fraction, max_stops):
425
+ """The hue slice as an RGBA16F GLTexture, cached on the picker's GLState
426
+ and re-baked when the hue (or the layout toggles) change."""
427
+ if gl_state is None:
428
+ return None
429
+ import meltygui.hdr_color as hdr_color
430
+ import OpenGL.GL as gl
431
+ from meltygui.core.graphics.gl_state import GLTexture
432
+ from meltygui.core.graphics.gl_state import _scalar
433
+
434
+ def create():
435
+ data = hdr_color.wide_square_linear(hue, size, top_fraction, max_stops)
436
+ tex_id = _scalar(gl.glGenTextures(1))
437
+ gl.glBindTexture(gl.GL_TEXTURE_2D, tex_id)
438
+ gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA16F, size, size, 0, gl.GL_RGBA, gl.GL_FLOAT, data)
439
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
440
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
441
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
442
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
443
+ gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
444
+ return GLTexture(tex_id, gl.GL_TEXTURE_2D, (size, size), gl.GL_RGBA16F)
445
+
446
+ return gl_state.get("wide_square", create, lambda t: gl.glDeleteTextures([t.texture_id]),
447
+ deps=(round(float(hue), 4), int(size), round(top_fraction, 4), round(max_stops, 4)))
448
+
449
+
450
+ def _draw_extended_picker(input_value, draw_state, gl_state, info):
451
+ """The sRGB+ tab: the classic sRGB HSV square at its usual size and
452
+ place, extended on two sides — to its RIGHT a PICKER_EXTENSION-wide
453
+ strip that carries every row on past the sRGB gamut edge into Display
454
+ P3 (colour-continuous across the seam, the top row ending in the pure
455
+ P3 primary), and ABOVE square and strip a PICKER_EXPOSURE_BAND-tall
456
+ exposure band that lifts the top row from white at the seam to
457
+ 2^Toggles.HDR.picker_max_stops at the top. The whole area is one fp16
458
+ texture per hue (hdr_color.srgb_plus_linear) so every texel is the real
459
+ wide / HDR colour. One drag runs across all of it (_srgb_plus_pick):
460
+ inside the square the classic (s, v); past the right seam s = 1 and x
461
+ is the depth into P3; above the top seam v = 1 and the height is
462
+ exposure. The hue bar is sRGB hue (the square's). Below black there is
463
+ nothing to extend into, so the square's bottom stays the bottom.
464
+ `draw_state._cpx_precise` echoes our own edits back like the other
465
+ tabs' caches; `_cpx_coords` memoizes the inverse for an external value
466
+ (a bisection, not a per-frame cost)."""
467
+ from meltygui.model.color_model import _srgb_plus_marker
468
+ from meltygui.model.color_model import _srgb_plus_pick
469
+ from meltygui.core.windowing.glfw_utils import request_render
470
+ from meltygui.view.control_view import button
471
+
472
+ import meltygui.hdr_color as hdr_color
473
+ # [tint=(0.85, 0.75, 0.05)]
474
+ seam_color = (1.0, 1.0, 1.0, 0.35)
475
+ SQ, EXT, BAND, BAR_W, GAP = Toggles.ColorPicker.square_size, Toggles.ColorPicker.extension_width, Toggles.ColorPicker.exposure_band_height, 18, 8
476
+ max_stops = float(Toggles.HDR.picker_max_stops)
477
+ imgui.dummy(0, 3)
478
+ vals = list(input_value)
479
+ has_alpha = len(vals) >= 4
480
+ r, g, b = float(vals[0]), float(vals[1]), float(vals[2])
481
+ a = float(vals[3]) if has_alpha else 1.0
482
+ in_r, in_g, in_b, in_a = r, g, b, a
483
+
484
+ ECHO_TOL = 0.002
485
+ _prec = getattr(draw_state, '_cpx_precise', None)
486
+ is_echo = _prec is not None and all(abs(pc - c) <= ECHO_TOL for pc, c in zip(_prec[0], (r, g, b, a)))
487
+ if is_echo:
488
+ r, g, b, a = _prec[0]
489
+ h, s, v, x, exposure = _prec[1]
490
+ else:
491
+ memo = getattr(draw_state, '_cpx_coords', None)
492
+ if memo is not None and memo[0] == (r, g, b):
493
+ h, s, v, x, exposure = memo[1]
494
+ else:
495
+ hint = _prec[1][0] if _prec is not None else None
496
+ h, s, v, x, exposure = hdr_color.srgb_extension_coords(r, g, b, hue_hint=hint)
497
+ if _prec is not None:
498
+ if s <= 0.0 or v <= 0.0:
499
+ h = _prec[1][0]
500
+ if v <= 0.0:
501
+ s = _prec[1][1]
502
+ draw_state._cpx_coords = ((r, g, b), (h, s, v, x, exposure))
503
+
504
+ dl = imgui.get_window_draw_list()
505
+ white = pack_color(1, 1, 1, 1)
506
+ black = pack_color(0, 0, 0, 1)
507
+ changed = False
508
+ hsv_changed = False
509
+
510
+ # --- the picking area: band over square + strip, the texture ---
511
+ ax0, ay0 = imgui.get_cursor_screen_pos() # the area's top-left
512
+ sx0, sy0 = ax0, ay0 + BAND # the square's top-left
513
+ tex = _srgb_plus_texture(gl_state, h, SQ, EXT, BAND, max_stops)
514
+ if tex is not None:
515
+ dl.add_image(tex.texture_id, (ax0, ay0), (ax0 + SQ + EXT, ay0 + BAND + SQ))
516
+ seam = pack_color(*seam_color)
517
+ dl.add_line(sx0 + SQ, ay0, sx0 + SQ, sy0 + SQ, seam, 1.0) # sRGB | P3
518
+ dl.add_line(ax0, sy0, ax0 + SQ + EXT, sy0, seam, 1.0) # exposure | SDR
519
+ imgui.invisible_button("##xsv", SQ + EXT, BAND + SQ)
520
+ if imgui.is_item_active():
521
+ mx, my = imgui.get_mouse_pos()
522
+ s, x, v, exposure = _srgb_plus_pick(mx - sx0, my - sy0, SQ, EXT, BAND, max_stops)
523
+ hsv_changed = True
524
+
525
+ # --- hue bar: sRGB hue (the square's axis), the full height ---
526
+ imgui.same_line(spacing=GAP)
527
+ hx0, hy0 = imgui.get_cursor_screen_pos()
528
+ bar_h = BAND + SQ
529
+ for i in range(6):
530
+ t0, t1 = i / 6.0, (i + 1) / 6.0
531
+ c0 = pack_color(*imgui.color_convert_hsv_to_rgb(t0, 1, 1), 1)
532
+ c1 = pack_color(*imgui.color_convert_hsv_to_rgb(t1, 1, 1), 1)
533
+ dl.add_rect_filled_multicolor(hx0, hy0 + bar_h * t0, hx0 + BAR_W, hy0 + bar_h * t1, c0, c0, c1, c1)
534
+ imgui.invisible_button("##xhue", BAR_W, bar_h)
535
+ if imgui.is_item_active():
536
+ h = min(max((imgui.get_mouse_pos()[1] - hy0) / bar_h, 0.0), 1.0)
537
+ hsv_changed = True
538
+
539
+ # --- markers ---
540
+ px, py = _srgb_plus_marker(s, x, v, exposure, SQ, EXT, BAND, max_stops)
541
+ cx, cy = sx0 + px, sy0 + py
542
+ dl.add_circle(cx, cy, 6, black, thickness=1.0)
543
+ dl.add_circle(cx, cy, 5, white, thickness=1.5)
544
+ hmy = hy0 + h * bar_h
545
+ dl.add_rect(hx0 - 1, hmy - 2, hx0 + BAR_W + 1, hmy + 2, white, thickness=1.5)
546
+
547
+ if hsv_changed:
548
+ r, g, b = hdr_color.extended_from_srgb_extension(h, s, v, x, exposure)
549
+ changed = True
550
+
551
+ # --- channel rows: extended sRGB, so they read past 1 and below 0 ---
552
+ imgui.dummy(0, 4)
553
+ imgui.push_item_width(SQ + EXT + GAP + BAR_W)
554
+ out, edited = [], []
555
+ lo, hi = -1.0, hdr_color.linear_to_srgb(2.0 ** max_stops)
556
+ for lbl, cur in ([("R", r), ("G", g), ("B", b)] + ([("A", a)] if has_alpha else [])):
557
+ imgui.set_next_item_width(draw_state.content_width - 30)
558
+ if lbl == "A":
559
+ ch, nv = imgui.drag_float(f"{lbl}##cpx_{lbl}", cur, 0.004, 0.0, 1.0, "%.3f")
560
+ else:
561
+ ch, nv = imgui.drag_float(f"{lbl}##cpx_{lbl}", cur, 0.006, lo, hi, "%.3f")
562
+ if ch:
563
+ changed = True
564
+ edited.append(ch)
565
+ out.append(nv if ch else cur)
566
+ imgui.dummy(0, 1)
567
+ imgui.pop_item_width()
568
+ r, g, b = out[0], out[1], out[2]
569
+ if has_alpha:
570
+ a = out[3]
571
+
572
+ # --- readout: hex inside sRGB, else the gamut + exposure ---
573
+ # Remove the colour: return None (the tuple popover unsets the value).
574
+ if button("\uf1f8", tint=(1, 0, 0, 0.5), height=21, shadow=True, use_cache=True,
575
+ name="delete_color##", text_value=1.6)[0]:
576
+ request_render()
577
+ return True, None
578
+ imgui.same_line()
579
+ if all(0.0 <= c <= 1.0 for c in (r, g, b)):
580
+ ri, gi, bi = (int(round(c * 255)) for c in (r, g, b))
581
+ readout = (f"#{ri:02x}{gi:02x}{bi:02x}{int(round(a * 255)):02x}"
582
+ if has_alpha else f"#{ri:02x}{gi:02x}{bi:02x}")
583
+ else:
584
+ gamut = "sRGB" if x <= 0.0 else "P3"
585
+ readout = f"{exposure:.2f}× white · " + (gamut if exposure <= 1.0 else gamut + " HDR")
586
+ imgui.text_colored(readout, *Tint.subtle_text())
587
+ if info:
588
+ imgui.dummy(0, 2)
589
+ imgui.text_colored(str(info), 1.0, 1.0, 1.0, 0.45)
590
+ if changed:
591
+ if has_alpha and not edited[3]:
592
+ a = in_a
593
+ if not hsv_changed:
594
+ if not edited[0]:
595
+ r = in_r
596
+ if not edited[1]:
597
+ g = in_g
598
+ if not edited[2]:
599
+ b = in_b
600
+ nh, ns, nv, nx, ne = hdr_color.srgb_extension_coords(r, g, b, hue_hint=h)
601
+ if ns <= 0.0 or nv <= 0.0:
602
+ nh = h
603
+ if nv <= 0.0:
604
+ ns = s
605
+ h, s, v, x, exposure = nh, ns, nv, nx, ne
606
+ draw_state._cpx_precise = ((r, g, b, a), (h, s, v, x, exposure))
607
+ request_render()
608
+ return True, ((r, g, b, a) if has_alpha else (r, g, b))
609
+ return False, input_value
610
+
611
+
612
+ def _srgb_plus_texture(gl_state, hue, square, ext, band, max_stops):
613
+ """The sRGB+ picking area (band + square + strip) as an RGBA16F
614
+ GLTexture, cached on the picker's GLState and re-baked when the hue (or
615
+ the layout) changes."""
616
+ if gl_state is None:
617
+ return None
618
+ import meltygui.hdr_color as hdr_color
619
+ import OpenGL.GL as gl
620
+ from meltygui.core.graphics.gl_state import GLTexture
621
+ from meltygui.core.graphics.gl_state import _scalar
622
+ cols, rows = int(square + ext), int(band + square)
623
+
624
+ def create():
625
+ data = hdr_color.srgb_plus_linear(hue, square, ext, band, max_stops)
626
+ tex_id = _scalar(gl.glGenTextures(1))
627
+ gl.glBindTexture(gl.GL_TEXTURE_2D, tex_id)
628
+ gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA16F, cols, rows, 0, gl.GL_RGBA, gl.GL_FLOAT, data)
629
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
630
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
631
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
632
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
633
+ gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
634
+ return GLTexture(tex_id, gl.GL_TEXTURE_2D, (cols, rows), gl.GL_RGBA16F)
635
+
636
+ return gl_state.get("srgb_plus", create, lambda t: gl.glDeleteTextures([t.texture_id]),
637
+ deps=(round(float(hue), 4), int(square), int(ext), int(band), round(max_stops, 4)))
638
+
639
+
640
+ def _draw_srgb_picker(input_value, draw_state, info):
641
+ """The sRGB tab: the classic HSV picker (a saturation/value square plus a
642
+ hue bar. `input_value` is a 3- or 4-float RGB(A) tuple in 0..1; returns
643
+ (changed, new_tuple). HSV is derived from the value each frame and the edit
644
+ written straight back (imgui's own is_item_active tracks the drag), so it
645
+ can be dropped anywhere; draw_tuple wraps it in Mode.POPOVER. The one piece
646
+ of state is `draw_state._cp_precise`: the last full-precision RGBA we
647
+ emitted plus its HSV. RGB→HSV loses hue at black/gray (h collapses to 0)
648
+ and a driven value can echo back quantized (save/parse round trip, %.3f
649
+ drag rounding) — so when the incoming value is just an echo of our own
650
+ edit, we resume from the cache instead of re-deriving."""
651
+ from meltygui.core.windowing.glfw_utils import request_render
652
+ from meltygui.view.control_view import button
653
+
654
+ imgui.dummy(0, 3)
655
+ vals = list(input_value)
656
+ has_alpha = len(vals) >= 4
657
+ r, g, b = float(vals[0]), float(vals[1]), float(vals[2])
658
+ a = float(vals[3]) if has_alpha else 1.0
659
+ # Exact incoming channels - untouched channels are emitted as these, so
660
+ # an edit to one channel doesn't override the others' cached/rounded
661
+ # working copies.
662
+ in_r, in_g, in_b, in_a = r, g, b, a
663
+
664
+ # Tolerance for "this is our own value coming back"; covers %.3f drag
665
+ # rounding (±0.0005) and 1/255 hex quantization (±0.002).
666
+ ECHO_TOL = 0.002
667
+ _prec = getattr(draw_state, '_cp_precise', None)
668
+ is_echo = False
669
+ if _prec is not None:
670
+ p_rgba, p_hsv = _prec # p_rgba is always stored as a 4-tuple
671
+ is_echo = (abs(p_rgba[0] - r) <= ECHO_TOL and
672
+ abs(p_rgba[1] - g) <= ECHO_TOL and
673
+ abs(p_rgba[2] - b) <= ECHO_TOL and
674
+ abs(p_rgba[3] - a) <= ECHO_TOL)
675
+
676
+ if is_echo:
677
+ # Echo of our own edit - resume the full-precision working value so
678
+ # our SV/hue markers don't jump on quantization noise, and the cached
679
+ # hue survives even when the colour is currently black/gray.
680
+ r, g, b = p_rgba[0], p_rgba[1], p_rgba[2]
681
+ if has_alpha:
682
+ a = p_rgba[3]
683
+ h, s, v = p_hsv
684
+ else:
685
+ h, s, v = imgui.color_convert_rgb_to_hsv(r, g, b)
686
+ if _prec is not None:
687
+ # External change to black/gray: hue (and at black, saturation)
688
+ # are undefined in the new value - keep the cached ones for
689
+ # continuity rather than snapping the markers to red/top-left.
690
+ if s <= 0.0 or v <= 0.0:
691
+ h = _prec[1][0]
692
+ if v <= 0.0:
693
+ s = _prec[1][1]
694
+
695
+ SQ, BAR_W, GAP = 180, 18, 8
696
+ dl = imgui.get_window_draw_list()
697
+ white = pack_color(1, 1, 1, 1)
698
+ black = pack_color(0, 0, 0, 1)
699
+ trans = pack_color(0, 0, 0, 0)
700
+ changed = False
701
+ hsv_changed = False # only convert HSV->RGB when the square/hue is actually changed
702
+
703
+ # Everything renders in NATURAL imgui flow (invisible_button advances the
704
+ # cursor, same_line for the hue bar, plain widgets below). That keeps imgui's
705
+ # content-size tracking honest so the auto-resized popover grows to match the
706
+ # square + channel rows + hex.
707
+
708
+ # --- SV square: white->hue across, transparent->black down ---
709
+ sx0, sy0 = imgui.get_cursor_screen_pos()
710
+ hr, hg, hb = imgui.color_convert_hsv_to_rgb(h, 1.0, 1.0)
711
+ hue = pack_color(hr, hg, hb, 1)
712
+ dl.add_rect_filled_multicolor(sx0, sy0, sx0 + SQ, sy0 + SQ, white, hue, hue, white)
713
+ dl.add_rect_filled_multicolor(sx0, sy0, sx0 + SQ, sy0 + SQ, trans, trans, black, black)
714
+ imgui.invisible_button("##sv", SQ, SQ)
715
+ if imgui.is_item_active():
716
+ mx, my = imgui.get_mouse_pos()
717
+ s = min(max((mx - sx0) / SQ, 0.0), 1.0)
718
+ v = 1.0 - min(max((my - sy0) / SQ, 0.0), 1.0)
719
+ hsv_changed = True
720
+
721
+ # --- Hue bar to its right, 6 gradient segments ---
722
+ imgui.same_line(spacing=GAP)
723
+ hx0, hy0 = imgui.get_cursor_screen_pos()
724
+ for i in range(6):
725
+ t0, t1 = i / 6.0, (i + 1) / 6.0
726
+ r0, g0, b0 = imgui.color_convert_hsv_to_rgb(t0, 1, 1)
727
+ r1, g1, b1 = imgui.color_convert_hsv_to_rgb(t1, 1, 1)
728
+ c0 = pack_color(r0, g0, b0, 1)
729
+ c1 = pack_color(r1, g1, b1, 1)
730
+ dl.add_rect_filled_multicolor(hx0, hy0 + SQ * t0, hx0 + BAR_W, hy0 + SQ * t1, c0, c0, c1, c1)
731
+ imgui.invisible_button("##hue", BAR_W, SQ)
732
+ if imgui.is_item_active():
733
+ h = min(max((imgui.get_mouse_pos()[1] - hy0) / SQ, 0.0), 1.0)
734
+ hsv_changed = True
735
+
736
+ # --- Markers (after input, at the current position) ---
737
+ cx, cy = sx0 + s * SQ, sy0 + (1.0 - v) * SQ
738
+ dl.add_circle(cx, cy, 6, black, thickness=1.0)
739
+ dl.add_circle(cx, cy, 5, white, thickness=1.5)
740
+ hmy = hy0 + h * SQ
741
+ dl.add_rect(hx0 - 1, hmy - 2, hx0 + BAR_W + 1, hmy + 2, white, thickness=1.5)
742
+
743
+ # Fold the SV/hue edit back to RGB only when the square or hue bar was just
744
+ # dragged. Otherwise keep the original input RGB untouched - round-tripping
745
+ # RGB->HSV->RGB every frame accumulates conversion error and feeds it back as
746
+ # next frame's input, which is what made the RGB sliders jitter.
747
+ if hsv_changed:
748
+ r, g, b = imgui.color_convert_hsv_to_rgb(h, s, v)
749
+ changed = True
750
+
751
+ # --- RGBA drag floats (natural flow, below the square row). Dragging a
752
+ # channel edits RGB directly, overriding the HSV-derived value this frame. ---
753
+ imgui.dummy(0, 4)
754
+ imgui.push_item_width(SQ + GAP + BAR_W)
755
+ out, edited = [], []
756
+ for lbl, cur in ([("R", r), ("G", g), ("B", b)] + ([("A", a)] if has_alpha else [])):
757
+ imgui.set_next_item_width(draw_state.content_width - 30)
758
+ ch, nv = imgui.drag_float(f"{lbl}##cp_{lbl}", cur, 0.004, 0.0, 1.0, "%.3f")
759
+ if ch:
760
+ changed = True
761
+ edited.append(ch)
762
+ # Keep `cur` verbatim unless this row was actually dragged - the
763
+ # returned value can differ from format rounding even if untouched.
764
+ out.append(min(max(nv, 0.0), 1.0) if ch else cur)
765
+ imgui.dummy(0, 1)
766
+ imgui.pop_item_width()
767
+ r, g, b = out[0], out[1], out[2]
768
+ if has_alpha:
769
+ a = out[3]
770
+
771
+ # --- Hex code label (#rrggbb, +a with alpha) ---
772
+ ri, gi, bi = (int(round(c * 255)) for c in (r, g, b))
773
+ hex_str = (f"#{ri:02x}{gi:02x}{bi:02x}{int(round(a * 255)):02x}"
774
+ if has_alpha else f"#{ri:02x}{gi:02x}{bi:02x}")
775
+
776
+
777
+ if button("\uf1f8", tint=(1, 0, 0, 0.5), height=21, shadow=True, use_cache=True,
778
+ name="delete_color##", text_value=1.6)[0]:
779
+ request_render()
780
+ return True, None
781
+ imgui.same_line()
782
+ imgui.text_colored(hex_str, *Tint.subtle_text())
783
+ if info:
784
+ # General-purpose caption (the tuple widget's info callback) - bottom
785
+ # line, under the hex readout.
786
+ imgui.dummy(0, 2)
787
+ imgui.text_colored(str(info), 1.0, 1.0, 1.0, 0.45)
788
+ if changed:
789
+ # Untouched channels emit the EXACT incoming value - the working
790
+ # copies may be truncated/rounded and must never overwrite precise
791
+ # channels the user didn't edit. An SV/hue drag rewrites RGB
792
+ # wholesale (that edit scope is all three channels); alpha only
793
+ # changes when its own row was dragged.
794
+ if has_alpha and not edited[3]:
795
+ a = in_a
796
+ if not hsv_changed:
797
+ if not edited[0]:
798
+ r = in_r
799
+ if not edited[1]:
800
+ g = in_g
801
+ if not edited[2]:
802
+ b = in_b
803
+ # RGB drags edit the colour directly - refresh the cached HSV,
804
+ # keeping hue/saturation where the new value leaves them undefined.
805
+ nh, ns, nv = imgui.color_convert_rgb_to_hsv(r, g, b)
806
+ if ns <= 0.0 or nv <= 0.0:
807
+ nh = h
808
+ if nv <= 0.0:
809
+ ns = s
810
+ h, s, v = nh, ns, nv
811
+ draw_state._cp_precise = ((r, g, b, a), (h, s, v))
812
+ request_render()
813
+ return True, ((r, g, b, a) if has_alpha else (r, g, b))
814
+ return False, input_value
815
+
816
+
817
+ def _popover_anchor(draw_state):
818
+ """The window a header-row popover (draw_tuple_fast's picker) is nested
819
+ under: the host itself when it IS a meltygui window, else the host's
820
+ enclosing window. A popover parented to a plain draw_state inherits its
821
+ `expanded` through abs_closed, so collapsing the host discarded it."""
822
+ if draw_state.closable or draw_state.parent_window is None:
823
+ return draw_state
824
+ return draw_state.parent_window