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 @@
1
+ """Reusable feature render functions."""
@@ -0,0 +1,524 @@
1
+ """Account view functions and supporting definitions."""
2
+ from meltygui.core.services.account_core import _cleanup_accounts
3
+ from meltygui.core.melty import Melty
4
+ from meltygui.model.account_model import AccountStore
5
+ from meltygui.core.core_render import render_func
6
+ from meltygui.state.account_state import AccountsPanelState
7
+ import meltygui_imgui as imgui
8
+
9
+
10
+ @render_func(use_cache=True, selectable=False, show_add_delete=False,
11
+ on_cleanup=_cleanup_accounts,
12
+ is_tree=False, show_name=True, shadow=True,
13
+ is_default_for="AccountStore", tint=(0.888, 0.669, 0.443))
14
+ def draw_internet_accounts(
15
+ # [tint=(0.85, 0.75, 0.05)]
16
+ input_value: AccountStore,
17
+ draw_state, panel_state: AccountsPanelState = None, style_manager=None,
18
+ non_blocking_left_mouse_down=False, **kwargs):
19
+ from meltygui.accounts.internet_accounts import ACCOUNTS_PATH
20
+ from meltygui.accounts.internet_accounts import AnthropicKind
21
+ from meltygui.accounts.internet_accounts import Button
22
+ from meltygui.accounts.internet_accounts import CodexKind
23
+ from meltygui.accounts.internet_accounts import KINDS
24
+ from meltygui.accounts.internet_accounts import _color_u32
25
+ from meltygui.accounts.internet_accounts import _ellipsize
26
+ from meltygui.accounts.internet_accounts import _format_gb
27
+ from meltygui.accounts.internet_accounts import _mix
28
+ from meltygui.accounts.internet_accounts import _refresh_stale
29
+ from meltygui.accounts.internet_accounts import _wrap_usage_label
30
+ from meltygui.accounts.internet_accounts import is_default
31
+ from meltygui.core.windowing.glfw_utils import request_render
32
+ from meltygui.core.cache.tile_cache import add_shadow
33
+ import meltygui.accounts.internet_accounts
34
+
35
+ meltygui.accounts.internet_accounts._window_draw_state = draw_state
36
+ store = input_value
37
+ if not store.loaded:
38
+ store.load()
39
+ store.ensure_kinds() # adopt newly registered kinds after a hotswap
40
+ if panel_state is not None:
41
+ if "usage" not in panel_state.__dict__:
42
+ panel_state.usage = {} # a legacy instance from before the field existed (hotswap)
43
+ # A fresh account dict (boot / restart / reload) has no runtime
44
+ # panel states yet - take the persisted ones, and last session's
45
+ # usage numbers for the rows that can show them.
46
+ for account_entry in store.values():
47
+ for key in AccountsPanelState.PANEL_KEYS:
48
+ if key not in account_entry:
49
+ account_entry[key] = bool(panel_state.open.get(f"{account_entry['id']}{key}"))
50
+ kind = KINDS.get(account_entry.get("kind"))
51
+ if isinstance(kind, (AnthropicKind, CodexKind)):
52
+ kind.restore_usage(account_entry, panel_state.usage.get(account_entry["id"]))
53
+
54
+ # ---- styling (fast_dock recipe) ----
55
+ row_bg_value, row_text_value = 0.06, 0.95
56
+ factor, saturation = 0.90, 1.0
57
+ button_bg_value, button_text_value = 0.13, 1.25
58
+ primary_bg_value = 0.22
59
+ hover_bg_boost, hover_text_boost = 0.05, 0.5
60
+ text_saturation = 0.8
61
+
62
+ # Status lamp colours (and the status text that inherits them), by
63
+ # probe state — add a state here when a kind's probe grows one.
64
+ # [tint=(0.35, 0.9, 0.45)]
65
+ state_tints = {
66
+ "ready": (0.35, 0.85, 0.45),
67
+ "busy": (0.85, 0.75, 0.35),
68
+ "needs_login": (0.95, 0.65, 0.25),
69
+ "warning": (0.95, 0.7, 0.3),
70
+ "error": (0.95, 0.35, 0.35),
71
+ "unknown": (0.55, 0.58, 0.65),
72
+ }
73
+ # Usage-bar fill by the endpoint's severity (Claude subscription rows)
74
+ # — the lamp palette, so "warning" reads the same everywhere.
75
+ # [tint=(0.95, 0.7, 0.3)]
76
+ severity_tints = {
77
+ "normal": state_tints["ready"],
78
+ "warning": state_tints["warning"],
79
+ "critical": state_tints["error"],
80
+ "exceeded": state_tints["error"],
81
+ }
82
+
83
+ # ---- row geometry, authored at ui_scale 1.0 and scaled once per frame ----
84
+ px = Melty.px
85
+ # [tint=(0.939, 0.453, 0.245)]
86
+ row_height = px(34.0)
87
+ row_gap = px(6.0)
88
+ pad_x = px(10.0)
89
+ corner = px(6.0)
90
+ button_height = px(24.0)
91
+ button_pad_x = px(10.0)
92
+ button_gap = px(6.0)
93
+ sub_row_height = px(30.0) # secondary rows (field editors, device code card, model rows)
94
+ strip_line_height = button_height + px(6) # one wrapped line of buttons (row and sub-row height)
95
+ stamp_row_height = px(18.0) # the "as of 21:42:10" line under the usage bars
96
+ kind_header_height = px(26.0)
97
+ # Below this much text room the buttons wrap onto their own line inside
98
+ # the row (the row grows) — raise it and narrow windows wrap sooner.
99
+ # [tint=(0.994, 0.872, 0.0)]
100
+ min_text_width = px(150.0)
101
+ # [tint=(0.35, 0.85, 0.94)]
102
+ text_inset = px(30.0) # label x inset (after the status lamp)
103
+ text_nudge_y = px(-1.0)
104
+
105
+ if style_manager is None:
106
+ style_manager = Melty.style_manager
107
+ draw_list = imgui.get_window_draw_list()
108
+ origin_x, origin_y = imgui.get_cursor_screen_pos()
109
+ content_width = draw_state.content_width or (draw_state.width or 300)
110
+ mouse_x, mouse_y = imgui.get_mouse_pos()
111
+ hover_ok = draw_state._bounding_hovered
112
+ press = non_blocking_left_mouse_down
113
+ # [tint=(0.62, 0.47, 0.95)]
114
+ click = (press.x, press.y) if (press and hasattr(press, "x")) else None
115
+ clip = getattr(draw_state, "abs_clip_rect", None)
116
+ line_height = imgui.get_text_line_height()
117
+ row_left, row_right = origin_x + pad_x, origin_x + content_width - pad_x
118
+ # True whenever a button handler or a field edit mutated the store this
119
+ # frame - it is the view's `changed` return (style guide rule 10).
120
+ pressed = [False]
121
+
122
+ def visible(top, bottom):
123
+ return clip is None or not (bottom < clip[1] or top > clip[3])
124
+
125
+ def button_width(button):
126
+ if button.icon is not None and button.label is None:
127
+ return button_height
128
+ return imgui.calc_text_size(button.label)[0] + 2 * button_pad_x
129
+
130
+ def buttons_width(buttons):
131
+ return (sum(button_width(button) for button in buttons)
132
+ + button_gap * max(0, len(buttons) - 1))
133
+
134
+ def pack_buttons(buttons, max_width):
135
+ """The strip as right-aligned LINES, each no wider than max_width —
136
+ a narrow window gets two or three lines of buttons instead of a
137
+ strip running off the row's left edge. (A single button wider than
138
+ the row still takes a line of its own.)"""
139
+ lines, line, width = [], [], 0.0
140
+ for button in buttons:
141
+ extra = button_width(button) + (button_gap if line else 0.0)
142
+ if line and width + extra > max_width:
143
+ lines.append(line)
144
+ line, width = [button], button_width(button)
145
+ else:
146
+ line.append(button)
147
+ width += extra
148
+ if line:
149
+ lines.append(line)
150
+ return lines
151
+
152
+ def strip_layout(buttons, strip_max, text_avail, min_text):
153
+ """(lines, wrap) for a row whose buttons share the line with text:
154
+ wrap when the strip needs more than one line or would leave the
155
+ text less than `min_text`."""
156
+ lines = pack_buttons(buttons, strip_max)
157
+ wrap = len(lines) > 1 or text_avail < min_text
158
+ return lines, wrap
159
+
160
+ def draw_buttons(buttons, right, top, tint, account, hint_slot):
161
+ """Right-aligned button strip ending at `right`. Runs click handlers."""
162
+ strip_right = right
163
+ for button in reversed(buttons):
164
+ width = button_width(button)
165
+ left = strip_right - width
166
+ bottom = top + button_height
167
+ enabled = button.enabled and not (account or {}).get("_busy")
168
+ hovered = (enabled and hover_ok
169
+ and left <= mouse_x <= strip_right and top <= mouse_y <= bottom)
170
+ bg_value = ((primary_bg_value if button.primary else button_bg_value)
171
+ + (hover_bg_boost if hovered else 0.0))
172
+ button_tint = (0.85, 0.35, 0.35) if button.danger else tint
173
+ bg_color = _mix(style_manager, button_tint,
174
+ bg_value if enabled else button_bg_value * 0.5, factor, saturation)
175
+ text_color = _mix(style_manager, button_tint,
176
+ (button_text_value + (hover_text_boost if hovered else 0.0))
177
+ if enabled else 0.45,
178
+ factor, text_saturation)
179
+ if enabled and visible(top, bottom):
180
+ add_shadow((left, top, width, button_height), offset=8 if button.primary else 4,
181
+ corner_radius=corner, clip=clip)
182
+ draw_list.add_rect_filled(left, top, strip_right, bottom,
183
+ _color_u32(bg_color), rounding=corner)
184
+ if button.icon is not None and button.label is None:
185
+ icon_size = imgui.calc_text_size(button.icon)
186
+ draw_list.add_text(left + (width - icon_size[0]) / 2.0,
187
+ top + (button_height - icon_size[1]) / 2.0 + text_nudge_y,
188
+ _color_u32(text_color), button.icon)
189
+ else:
190
+ label_size = imgui.calc_text_size(button.label)
191
+ draw_list.add_text(left + button_pad_x,
192
+ top + (button_height - label_size[1]) / 2.0 + text_nudge_y,
193
+ _color_u32(text_color), button.label)
194
+ if hovered and button.tip:
195
+ hint_slot[0] = button.tip
196
+ if (enabled and click is not None
197
+ and left <= click[0] <= strip_right and top <= click[1] <= bottom):
198
+ try:
199
+ button.on_click(account)
200
+ except Exception as error:
201
+ if account is not None:
202
+ account["_status"] = ("error", str(error)[:90])
203
+ pressed[0] = True
204
+ request_render()
205
+ strip_right = left - button_gap
206
+ return strip_right + button_gap # left edge of the strip
207
+
208
+ # ---- layout pass ----
209
+ # (kind, account, y, row height, buttons, wrap, subs) - heights are final
210
+ # here so the scroll dummy and the hit-tests agree.
211
+ y = origin_y
212
+ # [tint=(0.989, 0.17, 0.497)]
213
+ layout = []
214
+ for kind in KINDS.values():
215
+ layout.append(("head", kind, None, y, kind_header_height, None, False, []))
216
+ y += kind_header_height + px(2)
217
+ for account_entry in store.of_kind(kind.name):
218
+ buttons = list(kind.actions(account_entry))
219
+ if store.removable(account_entry):
220
+ buttons.append(Button(None, lambda account: store.remove(account["id"]),
221
+ icon=f"", danger=True,
222
+ tip=("Remove account — the next row becomes the default"
223
+ if is_default(account_entry) else "Remove account")))
224
+ text_avail = ((row_right - px(6)) - (row_left + text_inset)
225
+ - buttons_width(buttons) - button_gap)
226
+ lines, wrap = strip_layout(buttons, (row_right - px(6)) - (row_left + px(6)),
227
+ text_avail, min_text_width)
228
+ height = row_height + (len(lines) * strip_line_height if wrap else 0)
229
+ subs = []
230
+ if account_entry.get("_edit"):
231
+ subs.extend(("field", field) for field in kind.fields if not field.hidden)
232
+ subs.extend(kind.sub_rows(account_entry))
233
+ # Sub-rows with a button strip (card, model) grow the same way:
234
+ # (sub, height, button lines, wrap) - the painter reads them.
235
+ sub_strip_max = (row_right - px(12)) - (row_left + text_inset + px(6))
236
+ sub_items = []
237
+ # Align bars to the longest full name in this account's usage rows.
238
+ usage_label_width = max([px(150)] + [imgui.calc_text_size(sub[1]["label"])[0]
239
+ for sub in subs if sub[0] == "usage"])
240
+ usage_available = row_right - row_left - text_inset - px(20)
241
+ for sub in subs:
242
+ sub_height, sub_lines, sub_wrap = sub_row_height, None, False
243
+ if sub[0] == "stamp":
244
+ sub_height = stamp_row_height
245
+ elif sub[0] == "usage":
246
+ percent_width = imgui.calc_text_size(f"{sub[1]['percent']:.0f}%")[0]
247
+ sub_wrap = usage_label_width + percent_width + px(70) > usage_available
248
+ sub_lines = (_wrap_usage_label(sub[1]["label"], usage_available)
249
+ if sub_wrap else usage_label_width)
250
+ if sub_wrap:
251
+ sub_height += len(sub_lines) * line_height + px(4)
252
+ elif sub[0] in ("card", "model"):
253
+ sub_buttons = (sub[1][1] if sub[0] == "card"
254
+ else kind.model_actions(account_entry, sub[1]))
255
+ sub_lines, sub_wrap = strip_layout(
256
+ sub_buttons, sub_strip_max,
257
+ sub_strip_max - buttons_width(sub_buttons) - button_gap, min_text_width)
258
+ if sub_wrap:
259
+ sub_height += len(sub_lines) * strip_line_height
260
+ sub_items.append((sub, sub_height, sub_lines, sub_wrap))
261
+ layout.append(("account", kind, account_entry, y, height, lines, wrap, sub_items))
262
+ # The account's card: its header line AND its sub-rows
263
+ # (px(4) gap above them, px(4) pad below) - one block per account.
264
+ y += height + sum(item[1] for item in sub_items) + (px(8) if sub_items else 0) + row_gap
265
+ y += px(4)
266
+ footer_y = y
267
+ total_height = (footer_y - origin_y) + row_height
268
+ top_inset = (origin_y + draw_state.scroll_offset[1]) - draw_state.abs_top
269
+ imgui.dummy(content_width, max(1.0, total_height + max(0.0, top_inset)))
270
+
271
+ # [tint=(0.939, 0.836, 0.595)]
272
+ hint = [None]
273
+
274
+ for item in layout:
275
+ what, kind, account_entry, row_top, height, lines, wrap, sub_items = item
276
+ tint = kind.tint
277
+ if what == "head":
278
+ if visible(row_top, row_top + height):
279
+ text_color = _mix(style_manager, tint, 1.0, factor, text_saturation)
280
+ draw_list.add_text(row_left + px(2), row_top + (height - line_height) / 2.0,
281
+ _color_u32(text_color, 0.85), f"{kind.icon} {kind.label}")
282
+ draw_buttons([Button(f" account",
283
+ lambda _account, kind=kind: store.add(kind.name))],
284
+ row_right, row_top + (height - button_height) / 2.0, tint, None, hint)
285
+ continue
286
+
287
+ _refresh_stale(account_entry)
288
+ row_bottom = row_top + height
289
+ block_bottom = row_bottom + (sum(item[1] for item in sub_items) + px(8) if sub_items else 0)
290
+ state, status_text = kind.status(account_entry)
291
+ if account_entry.get("_busy") or account_entry.get("_probing"):
292
+ state, status_text = "busy", (status_text if state != "unknown" else "…")
293
+ # Hoisted above the visibility gate: the model sub-rows below read
294
+ # these even when their parent row is scrolled offscreen.
295
+ lamp_color = state_tints.get(state, state_tints["unknown"])
296
+ text_color = _mix(style_manager, tint, row_text_value, factor, text_saturation)
297
+ if visible(row_top, block_bottom):
298
+ # the account's card: header line + its sub-rows, hover over all of it
299
+ row_hovered = (hover_ok and row_left <= mouse_x <= row_right
300
+ and row_top <= mouse_y <= block_bottom)
301
+ bg_color = _mix(style_manager, tint, row_bg_value + (0.02 if row_hovered else 0.0),
302
+ factor, saturation)
303
+ draw_list.add_rect_filled(row_left, row_top, row_right, block_bottom,
304
+ _color_u32(bg_color), rounding=corner)
305
+ if visible(row_top, row_bottom):
306
+ # status lamp, recessed
307
+ lamp_radius = px(4.5)
308
+ lamp_x, lamp_y = row_left + px(14), row_top + row_height / 2.0
309
+ add_shadow((lamp_x - lamp_radius, lamp_y - lamp_radius, 2 * lamp_radius, 2 * lamp_radius),
310
+ offset=-1, corner_radius=lamp_radius, clip=clip)
311
+ draw_list.add_circle_filled(lamp_x, lamp_y, lamp_radius, _color_u32(lamp_color), 16)
312
+ # buttons: on the row line, or wrapped onto their own line(s)
313
+ if wrap:
314
+ strip_left = row_right - px(6)
315
+ for index, line in enumerate(lines):
316
+ draw_buttons(line, row_right - px(6),
317
+ row_top + row_height + px(2) + index * strip_line_height,
318
+ tint, account_entry, hint)
319
+ else:
320
+ strip_left = draw_buttons(lines[0], row_right - px(6),
321
+ row_top + (row_height - button_height) / 2.0,
322
+ tint, account_entry, hint)
323
+ # status text (the account's email / host / user), fitted to the
324
+ # space left of the strip - the kind header above names the service.
325
+ text_x = row_left + text_inset
326
+ text_right = strip_left - button_gap - px(4)
327
+ text_y = row_top + (row_height - line_height) / 2.0 + text_nudge_y
328
+ status_fit = _ellipsize(status_text, text_right - text_x)
329
+ if status_fit:
330
+ draw_list.add_text(text_x, text_y, _color_u32(lamp_color, 0.9), status_fit)
331
+
332
+ # ---- sub rows ----
333
+ sub_top = row_bottom + px(4)
334
+ for sub, sub_height, sub_lines, sub_wrap in sub_items:
335
+ sub_bottom = sub_top + sub_height
336
+ sub_left, sub_right = row_left + text_inset, row_right - px(6)
337
+
338
+ def draw_sub_strip(sub_top=sub_top, sub_lines=sub_lines, sub_wrap=sub_wrap):
339
+ """The sub-row's buttons: beside its text, or on wrapped
340
+ lines under it. Returns the text's right limit."""
341
+ if sub_wrap:
342
+ for index, line in enumerate(sub_lines):
343
+ draw_buttons(line, sub_right - px(6),
344
+ sub_top + sub_row_height - px(2) + index * strip_line_height,
345
+ tint, account_entry, hint)
346
+ return sub_right - px(10)
347
+ return draw_buttons(sub_lines[0], sub_right - px(6),
348
+ sub_top + (sub_row_height - button_height) / 2.0,
349
+ tint, account_entry, hint) - button_gap
350
+
351
+ if sub[0] == "field":
352
+ field = sub[1]
353
+ if visible(sub_top, sub_bottom):
354
+ label_color = _mix(style_manager, tint, 0.7, factor, text_saturation)
355
+ # The label column shrinks with the row so the editor keeps a usable width.
356
+ label_width = min(px(110), max(px(50), (sub_right - sub_left) * 0.35))
357
+ draw_list.add_text(sub_left,
358
+ sub_top + (sub_row_height - line_height) / 2.0 + text_nudge_y,
359
+ _color_u32(label_color, 0.85),
360
+ _ellipsize(field.label, label_width - px(6)))
361
+ field_left = sub_left + label_width
362
+ field_width = max(px(60), sub_right - field_left)
363
+ if _draw_field(account_entry, field, field_left,
364
+ sub_top + (sub_row_height - px(26)) / 2.0, field_width, px(26), store=store):
365
+ pressed[0] = True
366
+ elif sub[0] == "card":
367
+ # A highlighted strip with its own buttons: the kind supplies
368
+ # (message, [buttons...]) - Copilot's device code, the Anthropic
369
+ # sign-in waiting for the browser.
370
+ message, card_buttons = sub[1]
371
+ if visible(sub_top, sub_bottom):
372
+ add_shadow((sub_left, sub_top, sub_right - sub_left, sub_height),
373
+ offset=11, corner_radius=corner, clip=clip)
374
+ card_bg_color = _mix(style_manager, tint, 0.16, factor, saturation)
375
+ draw_list.add_rect_filled(sub_left, sub_top, sub_right, sub_bottom,
376
+ _color_u32(card_bg_color), rounding=corner)
377
+ text_right = draw_sub_strip()
378
+ message_fit = _ellipsize(message, text_right - (sub_left + px(10)))
379
+ draw_list.add_text(sub_left + px(10),
380
+ sub_top + (sub_row_height - line_height) / 2.0 + text_nudge_y,
381
+ _color_u32((1.0, 0.95, 0.85)), message_fit)
382
+ elif sub[0] == "usage":
383
+ # One rate-limit window: label · bar (fill = severity tint,
384
+ # width = percent) · percent · reset countdown / spend detail.
385
+ # Label may sit beside the bar, or wrap above it in a narrow
386
+ # row. Layout uses the same label height used by this painter.
387
+ row = sub[1]
388
+ if visible(sub_top, sub_bottom):
389
+ from meltygui.completion.providers.claude_usage import reset_text
390
+ text_y = sub_top + (sub_row_height - line_height) / 2.0 + text_nudge_y
391
+ fill_color = severity_tints.get(row["severity"], state_tints["unknown"])
392
+ label_color = (text_color if row["active"]
393
+ else _mix(style_manager, tint, 0.75, factor, text_saturation))
394
+ label_x = sub_left + px(6)
395
+ available = (sub_right - px(8)) - label_x
396
+ bar_height = px(10)
397
+ percent_label = f"{row['percent']:.0f}%"
398
+ percent_width = imgui.calc_text_size(percent_label)[0]
399
+ right_text = row["detail"] or reset_text(row["resets_at"])
400
+ right_width = imgui.calc_text_size(right_text)[0] if right_text else 0.0
401
+ # [tint=(0.75, 0.55, 0.9)]
402
+ label_width = 0 if sub_wrap else sub_lines
403
+ label_gap = 0 if sub_wrap else px(10)
404
+ label_height = len(sub_lines) * line_height + px(4) if sub_wrap else 0
405
+ if sub_wrap:
406
+ for index, label_line in enumerate(sub_lines):
407
+ draw_list.add_text(label_x, sub_top + index * line_height + px(2),
408
+ _color_u32(label_color), label_line)
409
+ text_y += label_height
410
+ else:
411
+ draw_list.add_text(label_x, text_y, _color_u32(label_color), row["label"])
412
+ rest = available - label_width - label_gap - percent_width - px(8)
413
+ if right_text and rest < right_width + px(12):
414
+ right_text, right_width = "", 0.0
415
+ bar_left = label_x + label_width + label_gap
416
+ bar_right = (sub_right - px(8) - right_width - (px(12) if right_text else 0.0)
417
+ - percent_width - px(8))
418
+ bar_top = sub_top + label_height + (sub_row_height - bar_height) / 2.0
419
+ if bar_right - bar_left >= px(40):
420
+ track_color = _mix(style_manager, tint, 0.02, factor, saturation)
421
+ add_shadow((bar_left, bar_top, bar_right - bar_left, bar_height),
422
+ offset=-1, corner_radius=bar_height / 2.0, clip=clip)
423
+ draw_list.add_rect_filled(bar_left, bar_top, bar_right, bar_top + bar_height,
424
+ _color_u32(track_color), rounding=bar_height / 2.0)
425
+ fill_right = bar_left + (bar_right - bar_left) * min(100.0, row["percent"]) / 100.0
426
+ if fill_right > bar_left + px(2):
427
+ draw_list.add_rect_filled(bar_left, bar_top, fill_right, bar_top + bar_height,
428
+ _color_u32(fill_color, 0.95), rounding=bar_height / 2.0)
429
+ draw_list.add_text(bar_right + px(8), text_y, _color_u32(fill_color), percent_label)
430
+ else:
431
+ draw_list.add_text(bar_left, text_y, _color_u32(fill_color), percent_label)
432
+ if right_text:
433
+ draw_list.add_text(sub_right - px(8) - right_width, text_y,
434
+ _color_u32((0.72, 0.75, 0.82), 0.85), right_text)
435
+ elif sub[0] == "stamp":
436
+ if visible(sub_top, sub_bottom):
437
+ stamp_fit = _ellipsize(sub[1], (sub_right - px(8)) - (sub_left + px(6)))
438
+ stamp_width = imgui.calc_text_size(stamp_fit)[0]
439
+ draw_list.add_text(sub_right - px(8) - stamp_width,
440
+ sub_top + (stamp_row_height - line_height) / 2.0 + text_nudge_y,
441
+ _color_u32((0.6, 0.62, 0.7), 0.7), stamp_fit)
442
+ elif sub[0] == "note":
443
+ if visible(sub_top, sub_bottom):
444
+ draw_list.add_text(sub_left + px(6),
445
+ sub_top + (sub_row_height - line_height) / 2.0 + text_nudge_y,
446
+ _color_u32((0.7, 0.72, 0.8), 0.8),
447
+ _ellipsize(sub[1], (sub_right - px(6)) - (sub_left + px(6))))
448
+ elif sub[0] == "model":
449
+ model = sub[1]
450
+ if visible(sub_top, sub_bottom):
451
+ model_bg_color = _mix(style_manager, tint, 0.09 if model["loaded"] else 0.04,
452
+ factor, saturation)
453
+ add_shadow((sub_left, sub_top, sub_right - sub_left, sub_height),
454
+ offset=2 if model["loaded"] else 1, corner_radius=corner, clip=clip)
455
+ draw_list.add_rect_filled(sub_left, sub_top, sub_right, sub_bottom,
456
+ _color_u32(model_bg_color), rounding=corner)
457
+ strip_left = draw_sub_strip() + button_gap
458
+ model_lamp = state_tints["ready"] if model["loaded"] else state_tints["unknown"]
459
+ draw_list.add_circle_filled(sub_left + px(12), sub_top + sub_row_height / 2.0,
460
+ px(3.5), _color_u32(model_lamp), 12)
461
+ name_x = sub_left + px(24)
462
+ text_y = sub_top + (sub_row_height - line_height) / 2.0 + text_nudge_y
463
+ name_fit = _ellipsize(model["name"],
464
+ min(px(260), strip_left - button_gap - name_x))
465
+ draw_list.add_text(name_x, text_y, _color_u32(text_color), name_fit)
466
+ info_x = name_x + imgui.calc_text_size(name_fit)[0] + px(10)
467
+ info = _format_gb(model["size"])
468
+ if model["loaded"]:
469
+ info += f" · loaded on {model['where'] or '?'}"
470
+ info_fit = _ellipsize(info, strip_left - button_gap - px(4) - info_x)
471
+ if info_fit:
472
+ draw_list.add_text(info_x, text_y,
473
+ _color_u32((0.72, 0.75, 0.82), 0.85), info_fit)
474
+ sub_top = sub_bottom
475
+
476
+ # ---- footer: file hints / notes / errors ----
477
+ if visible(footer_y, footer_y + row_height):
478
+ note = hint[0] or f"{ACCOUNTS_PATH}"
479
+ if store.error:
480
+ note += f" · {store.error}"
481
+ draw_list.add_text(row_left, footer_y + (row_height - line_height) / 2.0,
482
+ _color_u32((0.6, 0.62, 0.7), 0.6),
483
+ _ellipsize(note, row_right - row_left))
484
+
485
+ # ---- persist the panel toggles ----
486
+ # The account dicts' runtime flags are this frame's truth (the kinds'
487
+ # buttons toggle them); mirror them into panel_state, which persists.
488
+ if panel_state is not None:
489
+ open_panels = {f"{account_entry['id']}{key}": True
490
+ for account_entry in store.values()
491
+ for key in AccountsPanelState.PANEL_KEYS if account_entry.get(key)}
492
+ if open_panels != panel_state.open:
493
+ panel_state.open = open_panels
494
+ # ... and the usage numbers, so next session starts from them.
495
+ usage_cache = {}
496
+ for account_entry in store.values():
497
+ kind = KINDS.get(account_entry.get("kind"))
498
+ if isinstance(kind, (AnthropicKind, CodexKind)):
499
+ entry = kind.usage_cache_entry(account_entry)
500
+ if entry is not None:
501
+ usage_cache[account_entry["id"]] = entry
502
+ if usage_cache != panel_state.usage:
503
+ panel_state.usage = usage_cache
504
+
505
+ return pressed[0], input_value
506
+
507
+
508
+ def _draw_field(input_value: dict, field, left, top, width, height, *, store: AccountStore):
509
+ """An editable field: a single-line draw_text row (the editor, so focus,
510
+ selection and paste all work). Returns True when the edit changed the
511
+ stored value."""
512
+ from meltygui.view.text_view import draw_text
513
+ key = f"acct_{input_value['id']}_{field.name}"
514
+ value = input_value.get(field.name) or ""
515
+ imgui.set_cursor_screen_pos((left, top))
516
+ changed, new_value = draw_text(value, name=key, single_line=True, width=width, height=height,
517
+ show_widgets=False, show_root_backgrounds=False,
518
+ show_header=False, show_file_header=False, show_jump_bar=False,
519
+ shadow=False, use_cache=True, temp=True, autocomplete=False,
520
+ syntax_highlight=False, line_numbers=False, fim="")
521
+ if changed and isinstance(new_value, str) and new_value != value:
522
+ store.set_field(input_value["id"], field.name, new_value.strip())
523
+ return True
524
+ return False
@@ -0,0 +1,73 @@
1
+ """Action view functions and supporting definitions."""
2
+ from meltygui.core.core_render import render_func
3
+ from meltygui.core.rendering.window_decoration import window
4
+ from meltygui.core.runtime.toggles import Actions
5
+ import meltygui_imgui as imgui
6
+
7
+
8
+ @window
9
+ @render_func(auto_resize=True)
10
+ def draw_actions(input_value=None, draw_state=None, **kwargs):
11
+ from meltygui.view.code_view import draw_type
12
+
13
+ draw_type(Actions, name="Actions")
14
+
15
+
16
+ @render_func(show_bg=True, use_cache=True, selectable=False, align_header=False,
17
+ with_header=None, auto_resize=True, temp=True)
18
+ def draw_action_runner(input_value, draw_state=None, enter_key_pressed=False, **kwargs):
19
+ """The parameter-input popup for one Actions function: an editable row per
20
+ parameter and a Run button. Enter (the auto-subscribed event param) runs
21
+ it too, so a value can be typed and fired without reaching for the mouse."""
22
+ from meltygui.editor.source_ui import _category_tint
23
+ from meltygui.view.control_view import draw_button
24
+ from meltygui.view.text_view import draw_text
25
+ from meltygui.core.rendering.render_dispatch import draw_any
26
+ from meltygui.core.automation.action_core import _action_funcs
27
+ from meltygui.core.automation.action_core import _close_action_runner
28
+ from meltygui.core.automation.action_core import _run_action
29
+
30
+ fn = input_value.action
31
+ if fn is None:
32
+ # Nothing selected (fresh boot) - stay hidden until a search hit
33
+ # points us at an action.
34
+ if draw_state is not None:
35
+ draw_state.closed = True
36
+ return False, input_value
37
+ tint = _category_tint("Actions")
38
+ icon = next((i for n, f, i in _action_funcs() if f is fn), None)
39
+ title = f"{icon} Actions.{fn.__name__}" if icon else f"Actions.{fn.__name__}"
40
+ imgui.text_colored(title, *tint, 1.0)
41
+ # Close X on the title row - the auto-fit window mode has no header, so
42
+ # the popup draws its own close affordance (Esc closes it too).
43
+ imgui.same_line(position=max(0.0, (draw_state.content_width or 200) - 26))
44
+ x_clicked, _ = draw_button("", label="", name="close_runner", tint=tint,
45
+ wrap=True, min_width=20, height=18,
46
+ show_header=False, show_name=False)
47
+ if x_clicked:
48
+ _close_action_runner()
49
+ return False, input_value
50
+ imgui.dummy(0, 4)
51
+ # Params render individually (not via draw_collection) so the first
52
+ # str param can take the one-shot focus grab - draw_text is the only
53
+ # widget with a focus param. select_all_on_focus: the seeded default is
54
+ # prefilled, so typing replaces it.
55
+ focus_pending = input_value._focus_requested
56
+ input_value._focus_requested = False
57
+ focus_target = next((k for k, v in input_value.params.items()
58
+ if isinstance(v, str)), None)
59
+ for pname, val in list(input_value.params.items()):
60
+ if isinstance(val, str):
61
+ focus = focus_pending and pname == focus_target
62
+ changed, new_val = draw_text(val, name=pname, single_line=True,
63
+ request_focus=focus,
64
+ select_all_on_focus=focus)
65
+ else:
66
+ changed, new_val = draw_any(val, name=pname)
67
+ if changed:
68
+ input_value.params[pname] = new_val
69
+ clicked, _ = draw_button(label="Run", name=f"run_{fn.__name__}", tint=tint,
70
+ show_header=False, show_name=False)
71
+ if clicked or enter_key_pressed:
72
+ _run_action(input_value)
73
+ return False, input_value