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.
- meltygui/__init__.py +107 -0
- meltygui/accounts/__init__.py +0 -0
- meltygui/accounts/internet_accounts.py +1355 -0
- meltygui/chat/__init__.py +91 -0
- meltygui/chat/activity.py +75 -0
- meltygui/chat/backends.py +36 -0
- meltygui/chat/chat_interface.py +732 -0
- meltygui/chat/chat_proxy.py +352 -0
- meltygui/chat/codex_proxy.py +592 -0
- meltygui/chat/codex_settings.py +100 -0
- meltygui/chat/codex_transport.py +60 -0
- meltygui/chat/command_parser.py +204 -0
- meltygui/chat/images.py +227 -0
- meltygui/chat/messages.py +417 -0
- meltygui/chat/metadata.py +139 -0
- meltygui/chat/writer_locks.py +64 -0
- meltygui/code/__init__.py +0 -0
- meltygui/code/basic_converters.py +533 -0
- meltygui/code/chain_converters.py +2111 -0
- meltygui/code/code_checks.py +2209 -0
- meltygui/code/core_syntax.py +1430 -0
- meltygui/code/file_converters.py +1933 -0
- meltygui/code/fileref.py +702 -0
- meltygui/code/hotswap_guard.py +144 -0
- meltygui/code/libcst_conversion.py +9724 -0
- meltygui/code/live_instrument.py +392 -0
- meltygui/code/live_view.py +2490 -0
- meltygui/code/melty_scan.py +2684 -0
- meltygui/code/new_codecs.py +1255 -0
- meltygui/code/new_converters.py +3017 -0
- meltygui/code/project_code.py +278 -0
- meltygui/code/source_context.py +63 -0
- meltygui/code/symbol_roster.py +1588 -0
- meltygui/code/syntax_check.py +34 -0
- meltygui/code/syntax_check_worker.py +114 -0
- meltygui/completion/__init__.py +0 -0
- meltygui/completion/fim.py +1232 -0
- meltygui/completion/fim_context.py +481 -0
- meltygui/completion/providers/__init__.py +0 -0
- meltygui/completion/providers/anthropic_oauth.py +446 -0
- meltygui/completion/providers/anthropic_requests.py +69 -0
- meltygui/completion/providers/claude.py +203 -0
- meltygui/completion/providers/claude_usage.py +499 -0
- meltygui/completion/providers/codex_accounts.py +180 -0
- meltygui/completion/providers/copilot.py +617 -0
- meltygui/completion/providers/oauth_popup.py +220 -0
- meltygui/completion/providers/ollama.py +320 -0
- meltygui/completion/providers/profiles.py +23 -0
- meltygui/core/README.md +88 -0
- meltygui/core/__init__.py +1 -0
- meltygui/core/automation/__init__.py +1 -0
- meltygui/core/automation/action_core.py +153 -0
- meltygui/core/automation/collection_action.py +45 -0
- meltygui/core/automation/mcp_eval.py +167 -0
- meltygui/core/automation/mcp_hotswap.py +107 -0
- meltygui/core/automation/mcp_query.py +552 -0
- meltygui/core/automation/mcp_server.py +657 -0
- meltygui/core/automation/orchestration_core.py +2636 -0
- meltygui/core/automation/query_core.py +124 -0
- meltygui/core/automation/search_core.py +26 -0
- meltygui/core/automation/selector_core.py +340 -0
- meltygui/core/automation/value_core.py +1752 -0
- meltygui/core/cache/__init__.py +1 -0
- meltygui/core/cache/cache_diagnostics.py +0 -0
- meltygui/core/cache/invalidation_decoration.py +153 -0
- meltygui/core/cache/invalidation_tracker.py +43 -0
- meltygui/core/cache/tile_cache.py +5968 -0
- meltygui/core/conversion/__init__.py +1 -0
- meltygui/core/conversion/bubbling.py +599 -0
- meltygui/core/conversion/cache_tree.py +188 -0
- meltygui/core/conversion/chain.py +113 -0
- meltygui/core/conversion/converter_register.py +145 -0
- meltygui/core/conversion/data_decoration.py +67 -0
- meltygui/core/conversion/dict_conversion.py +1815 -0
- meltygui/core/conversion/dict_conversion_util.py +177 -0
- meltygui/core/conversion/dynamic_obj.py +89 -0
- meltygui/core/conversion/graph_compare.py +183 -0
- meltygui/core/conversion/load_save_v2.py +1032 -0
- meltygui/core/conversion/missing_saved_class.py +39 -0
- meltygui/core/conversion/path_finder.py +606 -0
- meltygui/core/conversion/render_host.py +1083 -0
- meltygui/core/core_render.py +6537 -0
- meltygui/core/definition_hotswap.py +231 -0
- meltygui/core/diagnostics/__init__.py +1 -0
- meltygui/core/diagnostics/attribute_churn.py +38 -0
- meltygui/core/diagnostics/fps_counter.py +39 -0
- meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
- meltygui/core/diagnostics/inspection_core.py +169 -0
- meltygui/core/diagnostics/monitor_core.py +105 -0
- meltygui/core/diagnostics/notifications.py +706 -0
- meltygui/core/diagnostics/perf_trace.py +281 -0
- meltygui/core/diagnostics/profile_decoration.py +88 -0
- meltygui/core/diagnostics/resize_trace.py +62 -0
- meltygui/core/diagnostics/screenshot_core.py +247 -0
- meltygui/core/diagnostics/session_status.py +98 -0
- meltygui/core/diagnostics/trace_core.py +445 -0
- meltygui/core/files/__init__.py +1 -0
- meltygui/core/files/file_core.py +208 -0
- meltygui/core/files/file_explorer_core.py +104 -0
- meltygui/core/files/file_tree_core.py +198 -0
- meltygui/core/files/file_watch_core.py +43 -0
- meltygui/core/files/import_graph_core.py +43 -0
- meltygui/core/files/metadata_core.py +51 -0
- meltygui/core/graphics/__init__.py +1 -0
- meltygui/core/graphics/cuda_context_core.py +166 -0
- meltygui/core/graphics/cuda_interop_core.py +136 -0
- meltygui/core/graphics/cuda_kernel_core.py +91 -0
- meltygui/core/graphics/framebuffer_recorder.py +337 -0
- meltygui/core/graphics/gl_state.py +658 -0
- meltygui/core/graphics/lut_core.py +52 -0
- meltygui/core/graphics/overlay_renderer.py +984 -0
- meltygui/core/graphics/scene_target.py +180 -0
- meltygui/core/graphics/screenshot.py +439 -0
- meltygui/core/graphics/shader_func.py +478 -0
- meltygui/core/graphics/tensor_core.py +45 -0
- meltygui/core/graphics/text_texture.py +329 -0
- meltygui/core/graphics/wayland_color.py +635 -0
- meltygui/core/input/__init__.py +1 -0
- meltygui/core/input/collision.py +165 -0
- meltygui/core/input/drag_drop_core.py +1525 -0
- meltygui/core/input/hypr_left_drag.py +323 -0
- meltygui/core/input/input_core.py +245 -0
- meltygui/core/input/input_handler.py +1101 -0
- meltygui/core/input/mouse_cursor.py +355 -0
- meltygui/core/input/pynput_backend.py +1054 -0
- meltygui/core/input/space_mouse.py +338 -0
- meltygui/core/input/touchpad_backend.py +393 -0
- meltygui/core/input/view_selection.py +177 -0
- meltygui/core/layout/__init__.py +1 -0
- meltygui/core/layout/column_core.py +2153 -0
- meltygui/core/layout/cursor_core.py +161 -0
- meltygui/core/layout/dropdown_core.py +278 -0
- meltygui/core/layout/edge_constraints.py +155 -0
- meltygui/core/layout/grid_core.py +117 -0
- meltygui/core/layout/header_core.py +23 -0
- meltygui/core/layout/header_runtime.py +67 -0
- meltygui/core/layout/layout_core.py +87 -0
- meltygui/core/layout/tile_manager_core.py +591 -0
- meltygui/core/melty.py +6726 -0
- meltygui/core/module_map.json +896 -0
- meltygui/core/module_names.py +19 -0
- meltygui/core/rendering/__init__.py +1 -0
- meltygui/core/rendering/core_decoration.py +431 -0
- meltygui/core/rendering/core_render_helpers.py +328 -0
- meltygui/core/rendering/func_metadata.py +398 -0
- meltygui/core/rendering/mode.py +818 -0
- meltygui/core/rendering/mode_defaults.py +41 -0
- meltygui/core/rendering/modes.py +136 -0
- meltygui/core/rendering/parameter_core.py +1665 -0
- meltygui/core/rendering/render_dispatch.py +1891 -0
- meltygui/core/rendering/render_funcs.py +273 -0
- meltygui/core/rendering/shaped.py +312 -0
- meltygui/core/rendering/window_decoration.py +25 -0
- meltygui/core/runtime/__init__.py +1 -0
- meltygui/core/runtime/app.py +735 -0
- meltygui/core/runtime/app_session.py +140 -0
- meltygui/core/runtime/background.py +564 -0
- meltygui/core/runtime/extensions.py +53 -0
- meltygui/core/runtime/gc_manager.py +1163 -0
- meltygui/core/runtime/lifecycle.py +21 -0
- meltygui/core/runtime/paths.py +27 -0
- meltygui/core/runtime/settings.py +14 -0
- meltygui/core/runtime/singleton.py +16 -0
- meltygui/core/runtime/thread_safe_bool.py +24 -0
- meltygui/core/runtime/thread_signal.py +30 -0
- meltygui/core/runtime/toggles.py +3115 -0
- meltygui/core/services/__init__.py +1 -0
- meltygui/core/services/account_core.py +10 -0
- meltygui/core/services/chat_core.py +19 -0
- meltygui/core/services/claude_terminal_core.py +346 -0
- meltygui/core/services/terminal_core.py +458 -0
- meltygui/core/services/terminal_runtime.py +78 -0
- meltygui/core/styling/__init__.py +1 -0
- meltygui/core/styling/color_core.py +46 -0
- meltygui/core/styling/fonts.py +639 -0
- meltygui/core/styling/global_style.py +338 -0
- meltygui/core/styling/style.py +198 -0
- meltygui/core/styling/style_core.py +522 -0
- meltygui/core/styling/warm_start.py +149 -0
- meltygui/core/windowing/__init__.py +1 -0
- meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
- meltygui/core/windowing/backends/__init__.py +1 -0
- meltygui/core/windowing/backends/imgui_renderer.py +138 -0
- meltygui/core/windowing/backends/native_wayland.py +850 -0
- meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
- meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
- meltygui/core/windowing/backends/wayland_protocol.py +101 -0
- meltygui/core/windowing/dock_core.py +182 -0
- meltygui/core/windowing/frame_geometry.py +42 -0
- meltygui/core/windowing/geometry_feed.py +864 -0
- meltygui/core/windowing/glfw_utils.py +1343 -0
- meltygui/core/windowing/os_frame.py +1552 -0
- meltygui/core/windowing/surface.py +643 -0
- meltygui/core/windowing/titlebar.py +1560 -0
- meltygui/core/windowing/titlebar_buttons.py +281 -0
- meltygui/core/windowing/wayland_move.py +932 -0
- meltygui/core/windowing/window_api.py +62 -0
- meltygui/core/windowing/window_constants.py +339 -0
- meltygui/core/windowing/window_visibility.py +162 -0
- meltygui/debug/__init__.py +0 -0
- meltygui/debug/app_view_utils.py +9 -0
- meltygui/editor/__init__.py +0 -0
- meltygui/editor/bash_syntax.py +30 -0
- meltygui/editor/code_line_fast.py +152 -0
- meltygui/editor/diff.py +139 -0
- meltygui/editor/external_changes.py +159 -0
- meltygui/editor/file_header.py +41 -0
- meltygui/editor/live_usage.py +169 -0
- meltygui/editor/live_view_views.py +1803 -0
- meltygui/editor/pending_save.py +1351 -0
- meltygui/editor/roster_tints.py +547 -0
- meltygui/editor/source_preview.py +16 -0
- meltygui/editor/source_tools.py +10 -0
- meltygui/editor/source_ui.py +70 -0
- meltygui/editor/spell_check.py +77 -0
- meltygui/editor/text_editor.py +9076 -0
- meltygui/editor/usage_picker.py +538 -0
- meltygui/events/__init__.py +0 -0
- meltygui/events/example.py +118 -0
- meltygui/examples/__init__.py +0 -0
- meltygui/examples/columns_demo.py +33 -0
- meltygui/examples/columns_window_demo.py +60 -0
- meltygui/examples/context_menu_demo.py +24 -0
- meltygui/examples/context_menu_window_demo.py +49 -0
- meltygui/examples/gui_playground.py +127 -0
- meltygui/examples/live_view_playground.py +256 -0
- meltygui/examples/lora.py +43 -0
- meltygui/examples/lora_data.py +59 -0
- meltygui/examples/lora_policies.py +67 -0
- meltygui/examples/mode_demo.py +49 -0
- meltygui/examples/modifies_demo.py +119 -0
- meltygui/examples/scalar_policies.py +60 -0
- meltygui/examples/style_layouts.py +149 -0
- meltygui/examples/tile_manager_demo.py +74 -0
- meltygui/examples/tint_demo.py +160 -0
- meltygui/examples/tint_functions.py +56 -0
- meltygui/examples/trace_demo.py +88 -0
- meltygui/examples/two_windows.py +36 -0
- meltygui/files/__init__.py +0 -0
- meltygui/files/fast_file_explorer.py +439 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
- meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
- meltygui/graphics/__init__.py +6 -0
- meltygui/graphics/base.py +85 -0
- meltygui/graphics/examples.py +507 -0
- meltygui/graphics/executor.py +520 -0
- meltygui/graphics/filter.py +667 -0
- meltygui/graphics/filter.pyi +856 -0
- meltygui/graphics/generate_stubs.py +22 -0
- meltygui/graphics/registry.py +203 -0
- meltygui/graphics/shader_compiler.py +155 -0
- meltygui/graphics/shaders.py +1302 -0
- meltygui/graphics/stub_generator.py +250 -0
- meltygui/graphics/texture_manager.py +170 -0
- meltygui/graphics/texture_min_max.py +295 -0
- meltygui/hdr_color.py +757 -0
- meltygui/image_load.py +308 -0
- meltygui/model/__init__.py +1 -0
- meltygui/model/account_model.py +142 -0
- meltygui/model/camera_model.py +159 -0
- meltygui/model/chat_model.py +86 -0
- meltygui/model/code_model.py +62 -0
- meltygui/model/code_proxy_model.py +876 -0
- meltygui/model/collection_model.py +41 -0
- meltygui/model/color_model.py +127 -0
- meltygui/model/cuda_tensor_model.py +36 -0
- meltygui/model/cuda_texture_model.py +149 -0
- meltygui/model/dropdown_model.py +137 -0
- meltygui/model/file_metadata_model.py +73 -0
- meltygui/model/file_model.py +289 -0
- meltygui/model/format_model.py +390 -0
- meltygui/model/graph_model.py +98 -0
- meltygui/model/icon_model.py +1024 -0
- meltygui/model/import_graph_model.py +468 -0
- meltygui/model/layout_model.py +15 -0
- meltygui/model/lut_model.py +266 -0
- meltygui/model/search_model.py +173 -0
- meltygui/model/tensor_model.py +402 -0
- meltygui/model/terminal_model.py +329 -0
- meltygui/model/texture_model.py +146 -0
- meltygui/model/tile_model.py +24 -0
- meltygui/model/trace_model.py +198 -0
- meltygui/model/trace_report_model.py +146 -0
- meltygui/models/__init__.py +0 -0
- meltygui/models/file_meta.py +613 -0
- meltygui/models/function_console.py +184 -0
- meltygui/models/orchestration.py +48 -0
- meltygui/pbr.py +1576 -0
- meltygui/png_unfilter.c +49 -0
- meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
- meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
- meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
- meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
- meltygui/resources/dejavu/LICENSE.txt +78 -0
- meltygui/resources/fontawesome-LICENSE.txt +121 -0
- meltygui/resources/fontawesome-webfont.ttf +0 -0
- meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
- meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
- meltygui/resources/jetbrains-weights/OFL.txt +93 -0
- meltygui/resources/jetbrains-weights/README.md +2 -0
- meltygui/state/__init__.py +0 -0
- meltygui/state/account_state.py +22 -0
- meltygui/state/animation_state.py +97 -0
- meltygui/state/annotation_state.py +22 -0
- meltygui/state/chat_state.py +36 -0
- meltygui/state/code_state.py +10 -0
- meltygui/state/core_enums.py +59 -0
- meltygui/state/core_markers.py +115 -0
- meltygui/state/core_undo.py +1075 -0
- meltygui/state/file_state.py +75 -0
- meltygui/state/graph_state.py +26 -0
- meltygui/state/inspection_state.py +47 -0
- meltygui/state/menu_state.py +10 -0
- meltygui/state/model_enums.py +11 -0
- meltygui/state/new_core_model.py +2394 -0
- meltygui/state/orchestration_state.py +14 -0
- meltygui/state/query_state.py +24 -0
- meltygui/state/tensor_state.py +10 -0
- meltygui/state/terminal_state.py +23 -0
- meltygui/state/trace_state.py +32 -0
- meltygui/state/voxel_state.py +12 -0
- meltygui/text_index.py +816 -0
- meltygui/utils/__init__.py +0 -0
- meltygui/utils/jump_to_code.py +344 -0
- meltygui/utils/pkl_inspect.py +90 -0
- meltygui/utils/render_utils.py +1419 -0
- meltygui/view/__init__.py +1 -0
- meltygui/view/account_view.py +524 -0
- meltygui/view/action_view.py +73 -0
- meltygui/view/chat_decoration_view.py +112 -0
- meltygui/view/chat_view.py +1703 -0
- meltygui/view/code_view.py +2677 -0
- meltygui/view/collection_view.py +1185 -0
- meltygui/view/color_view.py +824 -0
- meltygui/view/control_view.py +559 -0
- meltygui/view/decoration_view.py +439 -0
- meltygui/view/diagnostic_view.py +177 -0
- meltygui/view/dropdown_view.py +1082 -0
- meltygui/view/file_view.py +1599 -0
- meltygui/view/graph_cuda_view.py +105 -0
- meltygui/view/graph_view.py +882 -0
- meltygui/view/header_view.py +848 -0
- meltygui/view/input_view.py +238 -0
- meltygui/view/inspection_view.py +1783 -0
- meltygui/view/layout_view.py +596 -0
- meltygui/view/lut_view.py +45 -0
- meltygui/view/menu_view.py +212 -0
- meltygui/view/orchestration_view.py +658 -0
- meltygui/view/query_view.py +118 -0
- meltygui/view/search_view.py +342 -0
- meltygui/view/tab_view.py +258 -0
- meltygui/view/tensor_view.py +430 -0
- meltygui/view/terminal_view.py +355 -0
- meltygui/view/text_view.py +8031 -0
- meltygui/view/texture_view.py +480 -0
- meltygui/view/tile_view.py +38 -0
- meltygui/view/trace_view.py +923 -0
- meltygui/view/voxel_cuda_view.py +824 -0
- meltygui/view/voxel_view.py +1860 -0
- meltygui/view/window_view.py +111 -0
- meltygui-0.1.0.dist-info/METADATA +143 -0
- meltygui-0.1.0.dist-info/RECORD +372 -0
- meltygui-0.1.0.dist-info/WHEEL +4 -0
- 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
|