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,258 @@
|
|
|
1
|
+
"""Tab view functions and supporting definitions."""
|
|
2
|
+
from meltygui.core.melty import Melty
|
|
3
|
+
from meltygui.core.core_render import render_func
|
|
4
|
+
from meltygui.core.rendering.core_decoration import Core
|
|
5
|
+
from meltygui.state.new_core_model import TabState
|
|
6
|
+
from meltygui.view.header_view import draw_header
|
|
7
|
+
import meltygui_imgui as imgui
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@render_func(is_tree=False, show_bg=True, shadow=False, use_cache=False, header_same_line=True,
|
|
11
|
+
disable_scroll=True,
|
|
12
|
+
indent_size=0, show_add_delete=False,
|
|
13
|
+
show_name=False, selectable=False, parent_show_add_delete=False,
|
|
14
|
+
with_header=draw_header)
|
|
15
|
+
def draw_tab_bar(input_value: list, tab_height=30, names=None, tint_value=0.235, tint_saturation=0.372, unique=None,
|
|
16
|
+
collection=None, as_toggles=False, tints=None, icons=None, excluded=None, width=None,
|
|
17
|
+
dnd_collection_ds=None, dnd_keys=None,
|
|
18
|
+
draw_state=None):
|
|
19
|
+
"""Tab bar with multi-select via shift-click. input_value is the list of selected items, collection is all available tabs.
|
|
20
|
+
Tabs wrap onto a new row when the cumulative width would exceed draw_state.content_width.
|
|
21
|
+
|
|
22
|
+
tints: optional list of (r, g, b) tint colors, one per tab in `collection`. Entries that are
|
|
23
|
+
None (or beyond the list) fall back to the neutral grey. (Defaults to None rather than [] to
|
|
24
|
+
avoid the mutable-default-arg pitfall; behaves identically to an empty list.)
|
|
25
|
+
|
|
26
|
+
dnd_collection_ds + dnd_keys: opt tabs into the framework DragDrop. Each
|
|
27
|
+
tab button registers as a whole-rect drag item of `dnd_collection_ds` (the
|
|
28
|
+
draw_state whose input_value is the collection being reordered — the
|
|
29
|
+
Reorder/Insert lands on ITS return via Melty.dnd_requests). dnd_keys is a
|
|
30
|
+
list parallel to `collection`: (key, collection_index) per tab, or None
|
|
31
|
+
for tabs that aren't draggable (e.g. a synthetic General tab). The owner
|
|
32
|
+
must stamp _dnd_drop_target/_dnd_horizontal on dnd_collection_ds so slot
|
|
33
|
+
lines render vertically between the tabs."""
|
|
34
|
+
from meltygui.view.control_view import button
|
|
35
|
+
from meltygui.view.header_view import flat_button
|
|
36
|
+
from meltygui.core.layout.cursor_core import same_line
|
|
37
|
+
import meltygui.core.input.drag_drop_core as _drag_drop
|
|
38
|
+
|
|
39
|
+
if collection is None:
|
|
40
|
+
return False, input_value
|
|
41
|
+
|
|
42
|
+
if excluded is None:
|
|
43
|
+
excluded = set()
|
|
44
|
+
# Content-left edge, captured before the dummy/same_line/-10 shift below.
|
|
45
|
+
# The wrap limit is measured from here so it lines up with content_width.
|
|
46
|
+
origin_x = imgui.get_cursor_screen_pos()[0]
|
|
47
|
+
|
|
48
|
+
imgui.dummy(0, 0)
|
|
49
|
+
imgui.same_line()
|
|
50
|
+
|
|
51
|
+
# [tint=(0.894, 0.568, 0.204, 1.0), show_tint=True]
|
|
52
|
+
io = imgui.get_io()
|
|
53
|
+
changed = False
|
|
54
|
+
selected = list(input_value)
|
|
55
|
+
if selected is None:
|
|
56
|
+
selected = []
|
|
57
|
+
if names is None and hasattr(input_value, 'keys') and hasattr(input_value, 'values'):
|
|
58
|
+
input_value = list(input_value.values())
|
|
59
|
+
names = list(input_value.keys())
|
|
60
|
+
|
|
61
|
+
imgui.set_cursor_screen_pos((imgui.get_cursor_screen_pos()[0] - 10, imgui.get_cursor_screen_pos()[1]))
|
|
62
|
+
# First-row start x after the -10 shift; wrapped rows realign to this
|
|
63
|
+
# (new_line() alone resets to the window content x, which is ~10px right).
|
|
64
|
+
row_start_x = imgui.get_cursor_screen_pos()[0]
|
|
65
|
+
|
|
66
|
+
# Mirror button()'s sizing: width = calc_text_size(label_text).x + text_pad (15).
|
|
67
|
+
# Scaled like button() scales its text_pad, so the wrap measurement below
|
|
68
|
+
# matches the width the buttons actually take.
|
|
69
|
+
# [tint=(0.124, 0.65, 0.087, 1.0), show_tint=True]
|
|
70
|
+
button_padding = Melty.px(15)
|
|
71
|
+
# tab_height arrives as an authored-at-1.0 constant (callers pass 30/40),
|
|
72
|
+
# so it scales here - once, up front, so the button, the click test and
|
|
73
|
+
# the drag placeholder below all agree on one height. The drag path
|
|
74
|
+
# deliberately overrides this with the MEASURED pickup height, which is
|
|
75
|
+
# already in real pixels.
|
|
76
|
+
tab_height = Melty.px(tab_height)
|
|
77
|
+
content_width = draw_state.content_width if draw_state is not None else 0
|
|
78
|
+
x_limit = origin_x + content_width if content_width > 0 else None
|
|
79
|
+
|
|
80
|
+
# An explicitly passed width is a real constraint content_width knows
|
|
81
|
+
# nothing about (the context menu passes width=content_width-282) — clamp
|
|
82
|
+
# to it. draw_state.width and abs_clip_rect are NOT constraints here: with
|
|
83
|
+
# wrap=True the wrapper writes the measured content extent back into
|
|
84
|
+
# draw_state.width (and the clip rect derives from it), so clamping to
|
|
85
|
+
# them ratchets the bar narrower on every reflow until each tab fits on
|
|
86
|
+
# its own row.
|
|
87
|
+
if width and draw_state is not None:
|
|
88
|
+
view_right = draw_state._abs_left() + width - 3
|
|
89
|
+
x_limit = view_right if x_limit is None else min(x_limit, view_right)
|
|
90
|
+
|
|
91
|
+
def _tab_text(t):
|
|
92
|
+
return t.name if hasattr(t, 'name') else str(t)
|
|
93
|
+
|
|
94
|
+
for i, tab in enumerate(collection):
|
|
95
|
+
raw = _tab_text(tab)
|
|
96
|
+
# label_text = raw.replace("_", " ")
|
|
97
|
+
|
|
98
|
+
if i in excluded:
|
|
99
|
+
continue
|
|
100
|
+
label = f"{raw}"
|
|
101
|
+
if names is not None and i < len(names):
|
|
102
|
+
label = f"{names[i]}"
|
|
103
|
+
# Icons parallels `collection` like tints; prefix before the## so the
|
|
104
|
+
# imgui id (and click identity) stays keyed on the bare tab name.
|
|
105
|
+
if icons is not None and i < len(icons) and icons[i]:
|
|
106
|
+
label = f"{icons[i]} {label}"
|
|
107
|
+
active = tab in selected
|
|
108
|
+
|
|
109
|
+
tab_color = (0.5, 0.5, 0.5)
|
|
110
|
+
tinted = tints is not None and i < len(tints) and tints[i] is not None
|
|
111
|
+
if tinted:
|
|
112
|
+
tab_color = tints[i]
|
|
113
|
+
|
|
114
|
+
new_value = 0.15 if not tinted else 0.1
|
|
115
|
+
|
|
116
|
+
# make_color_rgb mixes `color` toward the theme color by `factor`; factor=1.0 (button's
|
|
117
|
+
# default) discards `color` entirely. Drop factor for tinted tabs so the tint shows, and
|
|
118
|
+
# give inactive tinted tabs a faint fill (the default alpha=0.0 draws no rect at all).
|
|
119
|
+
tab_factor = 0.30 if tinted else 1.2
|
|
120
|
+
|
|
121
|
+
tab_width = imgui.calc_text_size(label.split("##")[0]).x + button_padding
|
|
122
|
+
|
|
123
|
+
# Wrap before drawing: the previous iteration's same_line() left the
|
|
124
|
+
# cursor at this tab's real start (with item spacing included), so
|
|
125
|
+
# comparing live cursor + width against the content right edge needs
|
|
126
|
+
# no estimated spacing or fudge factor.
|
|
127
|
+
if i > 0 and x_limit is not None and imgui.get_cursor_screen_pos()[0] + tab_width > x_limit:
|
|
128
|
+
imgui.new_line()
|
|
129
|
+
imgui.set_cursor_screen_pos((row_start_x, imgui.get_cursor_screen_pos()[1]))
|
|
130
|
+
|
|
131
|
+
# Framework drag-and-drop: this button acts as a whole-rect drag
|
|
132
|
+
# item of dnd_collection_ds (key= is what DragDrop._begin picks up).
|
|
133
|
+
# While IT is the dragged child it renders with the floating-window
|
|
134
|
+
# kwargs (detached, fixed to pickup size, glued to the cursor) and a
|
|
135
|
+
# plain dummy holds its slot open in the flow.
|
|
136
|
+
dnd = None
|
|
137
|
+
dragged = False
|
|
138
|
+
dnd_extra = {}
|
|
139
|
+
if dnd_collection_ds is not None and dnd_keys is not None and i < len(dnd_keys):
|
|
140
|
+
dnd = dnd_keys[i]
|
|
141
|
+
_tx, _ty = imgui.get_cursor_screen_pos()
|
|
142
|
+
btn_height = tab_height
|
|
143
|
+
if dnd is not None:
|
|
144
|
+
dnd_extra = {"key": dnd[0], "dnd_handle": True, "return_extras": True}
|
|
145
|
+
# Same (collection, key) can be carried by two views - this button
|
|
146
|
+
# and the tab's stacked content view (draw_collection_as_tabs
|
|
147
|
+
# multi-select). Only the view actually picked up detaches, so
|
|
148
|
+
# require the dragged item to BE this button's draw state.
|
|
149
|
+
dragged = (_drag_drop.DragDrop.is_dragged_child(dnd_collection_ds, dnd[0])
|
|
150
|
+
and _drag_drop.DragDrop.item_ds is dnd_collection_ds._children.get(dnd[1]))
|
|
151
|
+
if dragged:
|
|
152
|
+
dnd_extra.update(_drag_drop.DragDrop.dragged_item_kwargs())
|
|
153
|
+
# dragged_item_kwargs pins width/height to the pickup size -
|
|
154
|
+
# height would collide with the explicit height= below, so
|
|
155
|
+
# route it through btn_height (the pinned button size).
|
|
156
|
+
btn_height = dnd_extra.pop("height", btn_height)
|
|
157
|
+
|
|
158
|
+
if dnd is not None:
|
|
159
|
+
# dnd: tabs - legacy @render_func buttons: DragDrop needs a
|
|
160
|
+
# per-tab draw_state to register as the drag child (pickup,
|
|
161
|
+
# floating window, slot placeholder). Only the context menu's
|
|
162
|
+
# reorderable tab bar takes this branch.
|
|
163
|
+
if active:
|
|
164
|
+
selected_value = 0.23
|
|
165
|
+
btn_res = button(label, z_offset=2, name=f"tab_{i}_{unique}",
|
|
166
|
+
height=btn_height, tint_value=new_value + selected_value - 0.03,
|
|
167
|
+
color=tab_color, factor=tab_factor, draw=True, **dnd_extra)
|
|
168
|
+
else:
|
|
169
|
+
saturation = 1.0 if tinted else 0.3
|
|
170
|
+
btn_res = button(label, indent_size=0, height=btn_height, draw=True, z_offset=0.0,
|
|
171
|
+
alpha=0.0 if tinted else 0.0, tint_value=new_value if not tinted else 0.1,
|
|
172
|
+
saturation=saturation,
|
|
173
|
+
name=f"tab_{i}_{unique}_deactivated", color=tab_color, factor=tab_factor,
|
|
174
|
+
text_value=1.0 if not tinted else 0.9,
|
|
175
|
+
shadow=False, **dnd_extra)
|
|
176
|
+
# A draggable tab must not change the selection on mouse-DOWN (a
|
|
177
|
+
# drag pickup would eat a multi-select). Override the button's
|
|
178
|
+
# down-click and select on CLICKED instead: the input handler only
|
|
179
|
+
# passes it for a release within MOVE_MAX_DISTANCE of the press,
|
|
180
|
+
# so a press that becomes a drag never selects.
|
|
181
|
+
clicked = False
|
|
182
|
+
if not dragged and draw_state is not None:
|
|
183
|
+
clicked = draw_state.on_action(
|
|
184
|
+
"left_mouse_clicked", view_id=f"tab_click_{i}",
|
|
185
|
+
rect=(_tx, _ty, _tx + tab_width, _ty + tab_height),
|
|
186
|
+
priority_delta=2) is not None
|
|
187
|
+
btn_ds = btn_res[2] if len(btn_res) == 3 else None
|
|
188
|
+
if btn_ds is not None:
|
|
189
|
+
# Fulfill the drop-collection contract on the OWNER's ds (see
|
|
190
|
+
# DragDrop._is_drop_collection): children keyed by collection
|
|
191
|
+
# index, backlinked for register_item/_begin.
|
|
192
|
+
btn_ds._collection_draw_state = dnd_collection_ds
|
|
193
|
+
if dnd_collection_ds._children is None:
|
|
194
|
+
dnd_collection_ds._children = {}
|
|
195
|
+
dnd_collection_ds._children[dnd[1]] = btn_ds
|
|
196
|
+
if dragged:
|
|
197
|
+
# The dragged button deferred to a floating window and drew
|
|
198
|
+
# nothing inline - hold its slot open at the current flow
|
|
199
|
+
# position so the bar doesn't reflow mid-drag.
|
|
200
|
+
imgui.dummy(tab_width, tab_height)
|
|
201
|
+
clicked = False
|
|
202
|
+
else:
|
|
203
|
+
# Plain tabs: draw-list rendering (flat_button - the fast-dock
|
|
204
|
+
# model). The per-tab @render_func buttons cost ~0.7ms each in
|
|
205
|
+
# wrapper machinery alone; on the editor's 8-tab strip that was
|
|
206
|
+
# the largest single slice of the hovered frame. Visual parity
|
|
207
|
+
# with the old button params: active tabs get the filled rect,
|
|
208
|
+
# inactive draw label-only (alpha=0), hover brightens the text
|
|
209
|
+
# like the button's hovered text_value boost. Framework
|
|
210
|
+
# shadows/z-offset on the active tab are gone (no draw_state) -
|
|
211
|
+
# the fast-dock tradeoff.
|
|
212
|
+
if active:
|
|
213
|
+
# flat_button applies its own shadow when it draws a bg (the
|
|
214
|
+
# inactive alpha=0 tabs stay flat).
|
|
215
|
+
clicked = flat_button(
|
|
216
|
+
label, draw_state, view_id=f"tab_{i}",
|
|
217
|
+
width=tab_width, height=btn_height,
|
|
218
|
+
color=tab_color, factor=tab_factor,
|
|
219
|
+
tint_value=new_value + 0.23 - 0.03)
|
|
220
|
+
else:
|
|
221
|
+
clicked = flat_button(
|
|
222
|
+
label, draw_state, view_id=f"tab_{i}",
|
|
223
|
+
width=tab_width, height=btn_height,
|
|
224
|
+
color=tab_color, factor=tab_factor, alpha=0.0,
|
|
225
|
+
tint_value=new_value if not tinted else 0.1,
|
|
226
|
+
saturation=1.0 if tinted else 0.3,
|
|
227
|
+
text_value=1.0 if not tinted else 0.9)
|
|
228
|
+
|
|
229
|
+
if clicked:
|
|
230
|
+
changed = True
|
|
231
|
+
if io.key_shift or as_toggles:
|
|
232
|
+
if active:
|
|
233
|
+
selected.remove(tab)
|
|
234
|
+
else:
|
|
235
|
+
selected.append(tab)
|
|
236
|
+
else:
|
|
237
|
+
selected = [tab]
|
|
238
|
+
|
|
239
|
+
same_line()
|
|
240
|
+
|
|
241
|
+
imgui.dummy(0, 0)
|
|
242
|
+
|
|
243
|
+
if changed:
|
|
244
|
+
Core.melty.refresh_nested_windows(draw_state)
|
|
245
|
+
|
|
246
|
+
return changed, selected
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@render_func(with_header=draw_header, is_tree=False, shadow=False)
|
|
250
|
+
def draw_enum_tabs(input_value: type, tab_state: TabState):
|
|
251
|
+
enum_states = list(input_value)
|
|
252
|
+
selected = tab_state.selected_tabs
|
|
253
|
+
|
|
254
|
+
changed, new_selected = draw_tab_bar(selected, collection=enum_states)
|
|
255
|
+
if changed:
|
|
256
|
+
tab_state.selected_tabs = new_selected
|
|
257
|
+
|
|
258
|
+
return False, input_value
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"""Tensor view functions and supporting definitions."""
|
|
2
|
+
from meltygui.hdr_color import pack_color
|
|
3
|
+
from meltygui.state.tensor_state import TensorErrorState
|
|
4
|
+
from meltygui.model.tensor_model import TensorDim
|
|
5
|
+
from meltygui.model.tensor_model import TensorDims
|
|
6
|
+
from meltygui.core.core_render import render_func
|
|
7
|
+
from meltygui.core.rendering.render_funcs import RenderFuncs
|
|
8
|
+
from meltygui.view.header_view import draw_header
|
|
9
|
+
import math
|
|
10
|
+
import meltygui_imgui as imgui
|
|
11
|
+
from meltygui.view.header_view import flat_button
|
|
12
|
+
from meltygui.view.decoration_view import draw_bg
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@render_func(tint=(0.23, 0.49, 0.62), use_cache=True, show_bg=False,
|
|
16
|
+
with_header=None, show_header=False, shadow=False, auto_resize=True, wrap=False)
|
|
17
|
+
def draw_tensor_slices(input_value: tuple, slider_dims=(), dim_names=(),
|
|
18
|
+
source_shape=(), draw_state=None):
|
|
19
|
+
"""Edit unmapped tensor dimensions through ordinary integer controls."""
|
|
20
|
+
from meltygui.view.control_view import draw_int_slider
|
|
21
|
+
values = list(input_value) + [0] * max(0, len(source_shape) - len(input_value))
|
|
22
|
+
changed = False
|
|
23
|
+
for dimension in slider_dims:
|
|
24
|
+
label = dim_names[dimension] if dimension < len(dim_names) else f"dim{dimension}"
|
|
25
|
+
upper_bound = max(0, source_shape[dimension] - 1)
|
|
26
|
+
current = max(0, min(int(values[dimension]), upper_bound))
|
|
27
|
+
edited, value = draw_int_slider(current, name=f"{label}##slice{dimension}",
|
|
28
|
+
min_value=0, max_value=upper_bound,
|
|
29
|
+
width=draw_state.content_width, height=22)
|
|
30
|
+
if edited and value != current:
|
|
31
|
+
values[dimension] = value
|
|
32
|
+
changed = True
|
|
33
|
+
return changed, tuple(values) if changed else input_value
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _draw_slice_sliders(draw_state, slider_dims, dim_names, slices, source_shape, width):
|
|
37
|
+
changed, value = draw_tensor_slices(slices, slider_dims=slider_dims,
|
|
38
|
+
dim_names=dim_names, source_shape=source_shape,
|
|
39
|
+
name="Slice positions", width=width)
|
|
40
|
+
if changed:
|
|
41
|
+
draw_state.locate_slices = value
|
|
42
|
+
return changed, value
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@render_func(tint=(0.68, 0.22, 0.20), use_cache=True, show_bg=False,
|
|
46
|
+
with_header=None, show_header=False, shadow=False, auto_resize=False)
|
|
47
|
+
def draw_tensor_error(input_value: str, draw_state=None,
|
|
48
|
+
error_state: TensorErrorState = None, who="draw_voxels"):
|
|
49
|
+
"""An error card with the same footprint as a tensor or graph view."""
|
|
50
|
+
import textwrap
|
|
51
|
+
left, top, right, bottom = draw_state.get_content_rect()
|
|
52
|
+
width, height = right - left, bottom - top
|
|
53
|
+
padding = 12.0
|
|
54
|
+
draw_list = imgui.get_window_draw_list()
|
|
55
|
+
draw_list.add_rect_filled(left, top, left + width, top + height,
|
|
56
|
+
pack_color(0.09, 0.05, 0.05, 1.0), 6.0)
|
|
57
|
+
draw_list.add_rect(left, top, left + width, top + height,
|
|
58
|
+
pack_color(0.75, 0.25, 0.25, 0.9), 6.0, thickness=1.5)
|
|
59
|
+
draw_list.add_text(left + padding, top + padding,
|
|
60
|
+
pack_color(1.0, 0.55, 0.55, 1.0),
|
|
61
|
+
f"{who} can't display this tensor")
|
|
62
|
+
line_height = imgui.get_font_size() + 2
|
|
63
|
+
line_top = top + padding + line_height + 4
|
|
64
|
+
columns = max(1, int((width - padding * 2) / max(1, imgui.calc_text_size("M")[0])))
|
|
65
|
+
draw_list.push_clip_rect(left + padding, top + padding, right - padding, bottom - padding)
|
|
66
|
+
for paragraph in input_value.splitlines():
|
|
67
|
+
for line in textwrap.wrap(paragraph, width=columns) or ['']:
|
|
68
|
+
draw_list.add_text(left + padding, line_top, pack_color(1.0, 0.85, 0.85, 1.0), line)
|
|
69
|
+
line_top += line_height
|
|
70
|
+
draw_list.pop_clip_rect()
|
|
71
|
+
imgui.dummy(width, height)
|
|
72
|
+
if error_state.last_error != input_value:
|
|
73
|
+
error_state.last_error = input_value
|
|
74
|
+
print(f"[{who}] {draw_state.name}: {input_value}")
|
|
75
|
+
return False, input_value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _draw_voxel_error(draw_state, message, who="draw_voxels"):
|
|
79
|
+
width, height = _view_size(draw_state)
|
|
80
|
+
return draw_tensor_error(message, name=f"{who} error", who=who,
|
|
81
|
+
width=width, height=height)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@render_func(is_default_for=("TensorDim", "TensorDims"), show_bg=False, is_tree=False,
|
|
85
|
+
header_same_line=True, with_header=draw_header)
|
|
86
|
+
def draw_tensor_dim(input_value: TensorDim | TensorDims = None, draw_state=None,
|
|
87
|
+
unique=0, ui_scale=1.0, style_manager=None, **kwargs):
|
|
88
|
+
"""THE dim picker — one TAB per dim NAME instead of a bare number field,
|
|
89
|
+
shared by every dim-typed param. A TensorDim renders single-select with
|
|
90
|
+
a leading "off" tab that maps to -1 (unset: sort disabled, nf/axis dims
|
|
91
|
+
derived), so sort_dim and nf_chop/nf_along reuse it as-is. A TensorDims
|
|
92
|
+
renders the same tabs multi-select (mean_dims). The tabs are this view's
|
|
93
|
+
own flat_buttons (`_draw_dim_tabs`), not a nested draw_tab_bar. Picking
|
|
94
|
+
a dim a sibling AXIS row already holds swaps the two (`_swap_sibling_dim`,
|
|
95
|
+
SWAP_DIM_KEYS). The names
|
|
96
|
+
come from the sibling `dim_names` entry of the collection this row
|
|
97
|
+
renders in (the params panel's locate_params proxy); with no names in
|
|
98
|
+
reach it falls back to a plain int edit. Returns the SAME type it was
|
|
99
|
+
given so the value keeps routing here (a plain int/tuple would drop back
|
|
100
|
+
to the generic renderer next frame)."""
|
|
101
|
+
from meltygui.model.tensor_model import TensorDim
|
|
102
|
+
from meltygui.model.tensor_model import TensorDims
|
|
103
|
+
from meltygui.model.tensor_model import _collection_dim_labels
|
|
104
|
+
|
|
105
|
+
multi = isinstance(input_value, tuple)
|
|
106
|
+
col = _row_collection(draw_state, kwargs)
|
|
107
|
+
labels = _collection_dim_labels(col)
|
|
108
|
+
if not labels:
|
|
109
|
+
if multi:
|
|
110
|
+
imgui.text(f"dims: {tuple(int(v) for v in input_value)}")
|
|
111
|
+
return False, input_value
|
|
112
|
+
changed, v = RenderFuncs.draw_int(
|
|
113
|
+
0 if input_value is None else int(input_value),
|
|
114
|
+
name=f"dim##{unique}")
|
|
115
|
+
if changed and v is not None:
|
|
116
|
+
return True, TensorDim(int(v))
|
|
117
|
+
return False, input_value
|
|
118
|
+
n = len(labels)
|
|
119
|
+
if multi:
|
|
120
|
+
cur = [int(v) for v in input_value if isinstance(v, int)]
|
|
121
|
+
changed, selected = _draw_dim_tabs(
|
|
122
|
+
draw_state, list(range(n)), labels,
|
|
123
|
+
[d for d in cur if 0 <= d < n], multi=True,
|
|
124
|
+
ui_scale=ui_scale, style_manager=style_manager)
|
|
125
|
+
if changed:
|
|
126
|
+
return True, TensorDims(sorted(int(s) for s in selected))
|
|
127
|
+
return False, input_value
|
|
128
|
+
# Single-select: a leading "off" tab maps to -1 (unset - sort disabled,
|
|
129
|
+
# nf/axis dims derived), so unsetting doesn't rely on double-click.
|
|
130
|
+
cur = int(input_value) if input_value is not None else -1
|
|
131
|
+
if not (0 <= cur < n):
|
|
132
|
+
cur = -1
|
|
133
|
+
changed, selected = _draw_dim_tabs(
|
|
134
|
+
draw_state, [-1] + list(range(n)), ["off"] + labels, [cur], multi=False,
|
|
135
|
+
ui_scale=ui_scale, style_manager=style_manager)
|
|
136
|
+
if changed:
|
|
137
|
+
new = int(selected[0]) if selected else -1
|
|
138
|
+
_swap_sibling_dim(draw_state, kwargs, cur, new)
|
|
139
|
+
return True, TensorDim(new)
|
|
140
|
+
return False, input_value
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _describe_tensor(t):
|
|
144
|
+
"""'Tensor[32, 1, 570, 4096] float16 cuda:0' — for error cards."""
|
|
145
|
+
try:
|
|
146
|
+
dev = getattr(t, "device", None)
|
|
147
|
+
dev = f" {dev}" if dev is not None and str(dev) != "cpu" else ""
|
|
148
|
+
return f"{type(t).__name__}{list(t.shape)} {str(t.dtype).replace('torch.', '')}{dev}"
|
|
149
|
+
except Exception:
|
|
150
|
+
return repr(t)[:80]
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _view_size(draw_state):
|
|
154
|
+
"""(width, height) the 3-D image occupies — shared by the render path
|
|
155
|
+
and the error card so the card holds the view's footprint (no layout
|
|
156
|
+
jump between a good frame and a failed one). Sized from the OWNING
|
|
157
|
+
WINDOW, not this view's own content rect — a nested view's rect derives
|
|
158
|
+
from what it rendered last frame (self-referential), while the window's
|
|
159
|
+
height is the user-dragged size. The owning window IS the draw_state
|
|
160
|
+
when draw_voxels is itself a window (closable); parent_window for a
|
|
161
|
+
closable voxel grabbed an ANCESTOR that doesn't move with it."""
|
|
162
|
+
win = draw_state if draw_state.closable else (draw_state.parent_window or draw_state)
|
|
163
|
+
width = max(64, int(draw_state.content_width or win.content_width or 0))
|
|
164
|
+
# Vertical reserve: the actual header band (0 if hidden) plus a little
|
|
165
|
+
# slack for the footer/status margin (the old hardcoded 30 was the
|
|
166
|
+
# 23px header + this slack).
|
|
167
|
+
_reserve = int(draw_state.header_height or 0) + 7
|
|
168
|
+
if draw_state.closable:
|
|
169
|
+
height = max(100, draw_state.height - _reserve)
|
|
170
|
+
else:
|
|
171
|
+
# When in a parent's flow, draw_state.height is only trustworthy
|
|
172
|
+
# when something explicit wrote it - a user drag ("initial
|
|
173
|
+
# window size"), a passed height kwarg, fill_height. The auto_resize
|
|
174
|
+
# measurement path ("... item_rect[1]") is what this view drew last
|
|
175
|
+
# frame - sizing the image from it is a feedback loop that sustains
|
|
176
|
+
# any spike forever (image = height-30 → measures back ≈ height →
|
|
177
|
+
# committed again); fall back to the design height (min_height,
|
|
178
|
+
# overridable per call site) for that case, and a bad committed
|
|
179
|
+
# height self-heals on the next live render.
|
|
180
|
+
_h_src = str(draw_state._source.get("height", ""))
|
|
181
|
+
if draw_state.height and "item_rect" not in _h_src:
|
|
182
|
+
height = max(100, int(draw_state.height) - _reserve)
|
|
183
|
+
else:
|
|
184
|
+
height = max(100, int(draw_state.min_height or 293) - _reserve)
|
|
185
|
+
return width, height
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _draw_image_notice(img_pos, width, text):
|
|
189
|
+
"""Small wrapped caption on the image's top-left (clamp notices):
|
|
190
|
+
a translucent plate under wrapped imgui text, cursor restored after."""
|
|
191
|
+
x, y = img_pos
|
|
192
|
+
pad = 6.0
|
|
193
|
+
wrap_w = max(40.0, width - 2 * pad - 4)
|
|
194
|
+
tw, th = imgui.calc_text_size(text, wrap_width=wrap_w)
|
|
195
|
+
imgui.get_window_draw_list().add_rect_filled(
|
|
196
|
+
x + 2, y + 2, x + min(tw, wrap_w) + 2 * pad + 2, y + th + 2 * pad + 2,
|
|
197
|
+
pack_color(0.0, 0.0, 0.0, 0.6), 4.0)
|
|
198
|
+
cur = imgui.get_cursor_screen_pos()
|
|
199
|
+
imgui.set_cursor_screen_pos((x + pad + 2, y + pad + 2))
|
|
200
|
+
imgui.push_text_wrap_pos(x + pad + 2 + wrap_w)
|
|
201
|
+
imgui.push_style_color(imgui.COLOR_TEXT, 1.0, 0.8, 0.4, 1.0)
|
|
202
|
+
imgui.text_wrapped(text)
|
|
203
|
+
imgui.pop_style_color()
|
|
204
|
+
imgui.pop_text_wrap_pos()
|
|
205
|
+
imgui.set_cursor_screen_pos(cur)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def format_bytes(n):
|
|
209
|
+
"""Tensor byte count → 'KB' / 'MB' / 'GB' string (the old volume
|
|
210
|
+
renderer's 2-dp formatting) plus its size tint: green ≤10 MB, yellow
|
|
211
|
+
≤100 MB, red above."""
|
|
212
|
+
n = int(n or 0)
|
|
213
|
+
if n > 1024 ** 3:
|
|
214
|
+
txt = f"{n / 1024 ** 3:.2f} GB"
|
|
215
|
+
elif n > 1024 ** 2:
|
|
216
|
+
txt = f"{n / 1024 ** 2:.2f} MB"
|
|
217
|
+
else:
|
|
218
|
+
txt = f"{n / 1024:.2f} KB"
|
|
219
|
+
if n > 100 * 1024 ** 2:
|
|
220
|
+
tint = (1.0, 0.45, 0.4, 1.0)
|
|
221
|
+
elif n > 10 * 1024 ** 2:
|
|
222
|
+
tint = (1.0, 0.85, 0.35, 1.0)
|
|
223
|
+
else:
|
|
224
|
+
tint = (0.5, 0.9, 0.5, 1.0)
|
|
225
|
+
return txt, tint
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _draw_tensor_meta(img_pos, height, t):
|
|
229
|
+
"""Bottom-left caption over the image: shape · dtype · device · bytes
|
|
230
|
+
(size tinted by magnitude), read straight off the source tensor."""
|
|
231
|
+
try:
|
|
232
|
+
shape = "×".join(str(int(d)) for d in t.shape)
|
|
233
|
+
dtype = str(t.dtype).replace("torch.", "")
|
|
234
|
+
nbytes = int(t.numel()) * int(t.element_size())
|
|
235
|
+
except Exception:
|
|
236
|
+
return
|
|
237
|
+
dev = str(getattr(t, "device", "cpu"))
|
|
238
|
+
head = " ".join(p for p in (shape, dtype, dev if dev != "cpu" else "") if p)
|
|
239
|
+
size_txt, size_tint = format_bytes(nbytes)
|
|
240
|
+
pad, gap = 5.0, 8.0
|
|
241
|
+
hw, hh = imgui.calc_text_size(head)
|
|
242
|
+
sw, sh = imgui.calc_text_size(size_txt)
|
|
243
|
+
tw, th = hw + gap + sw, max(hh, sh)
|
|
244
|
+
x, y = img_pos
|
|
245
|
+
x0, y0 = x + 2, y + height - th - 2 * pad - 2
|
|
246
|
+
dl = imgui.get_window_draw_list()
|
|
247
|
+
dl.add_rect_filled(x0, y0, x0 + tw + 2 * pad, y0 + th + 2 * pad,
|
|
248
|
+
pack_color(0.0, 0.0, 0.0, 0.55), 4.0)
|
|
249
|
+
dl.add_text(x0 + pad, y0 + pad, pack_color(0.85, 0.85, 0.85, 1.0), head)
|
|
250
|
+
dl.add_text(x0 + pad + hw + gap, y0 + pad, pack_color(*size_tint), size_txt)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
DIM_TAB_HEIGHT = 26
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
DIM_TAB_TEXT_PAD = 11
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
DIM_TAB_GAP = 3
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
DIM_TAB_BAND_PAD = 3
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
DIM_TAB_COLOR = (0.5, 0.5, 0.5)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
SWAP_DIM_KEYS = frozenset({"x_dim", "y_dim", "z_dim", "line_dim"})
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _row_collection(draw_state, kwargs):
|
|
272
|
+
"""The collection this row renders in (the params panel's locate_params
|
|
273
|
+
proxy) — sibling params like dim_names / x_dim live there."""
|
|
274
|
+
col = kwargs.get("collection")
|
|
275
|
+
if not isinstance(col, dict):
|
|
276
|
+
col = getattr(draw_state, "_collection", None)
|
|
277
|
+
return col if isinstance(col, dict) else None
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _draw_dim_tabs(draw_state, options, labels, selected, multi, *,
|
|
281
|
+
ui_scale=1.0, style_manager=None):
|
|
282
|
+
"""The dim tab strip: one flat_button per option, laid out by hand
|
|
283
|
+
(wrapping at draw_state.content_width) over the same draw_bg band
|
|
284
|
+
draw_tab_bar's wrapper painted (bg_offset=-3, rounding 5), with the tab
|
|
285
|
+
bar's look — active = filled rect + shadow, inactive = label only, hover
|
|
286
|
+
brightens — but DIM_TAB_* geometry. No nested render_func: the buttons
|
|
287
|
+
paint straight into this view's draw list and claim their clicks through
|
|
288
|
+
this view's draw_state.on_action, so the row costs one wrapper instead
|
|
289
|
+
of two. Returns (changed, selected) with `selected` a list of option
|
|
290
|
+
values; multi toggles, single replaces."""
|
|
291
|
+
tab_h = DIM_TAB_HEIGHT * ui_scale
|
|
292
|
+
pad = DIM_TAB_TEXT_PAD * ui_scale
|
|
293
|
+
gap = DIM_TAB_GAP * ui_scale
|
|
294
|
+
band_pad = DIM_TAB_BAND_PAD * ui_scale
|
|
295
|
+
x0, y0 = imgui.get_cursor_screen_pos()
|
|
296
|
+
content_width = draw_state.content_width if draw_state is not None else 0
|
|
297
|
+
x_limit = x0 + content_width if content_width > 0 else None
|
|
298
|
+
# Layout first (pure math over the label widths) so the band can be
|
|
299
|
+
# painted UNDER the buttons without carrying last frame's extent on the
|
|
300
|
+
# draw_state the way the tab / show_bg path does.
|
|
301
|
+
rects = []
|
|
302
|
+
x, y = x0 + band_pad, y0 + band_pad
|
|
303
|
+
for label in labels:
|
|
304
|
+
w = imgui.calc_text_size(label).x + pad
|
|
305
|
+
if rects and x_limit is not None and x + w + band_pad > x_limit:
|
|
306
|
+
x, y = x0 + band_pad, y + tab_h + gap
|
|
307
|
+
rects.append((x, y, w))
|
|
308
|
+
x += w + gap
|
|
309
|
+
right = max(rx + rw for rx, _, rw in rects) + band_pad
|
|
310
|
+
bottom = rects[-1][1] + tab_h + band_pad
|
|
311
|
+
draw_bg(left=x0, top=y0, width=right - x0, height=bottom - y0,
|
|
312
|
+
rounding=5, bg_offset=-3, depth=draw_state.depth_and_layer[0], opacity=1.0,
|
|
313
|
+
selected=False, pressed=False, nested_bg=False,
|
|
314
|
+
style_manager=style_manager)
|
|
315
|
+
changed = False
|
|
316
|
+
selected = list(selected)
|
|
317
|
+
for i, (opt, label, (x, y, w)) in enumerate(zip(options, labels, rects)):
|
|
318
|
+
imgui.set_cursor_screen_pos((x, y))
|
|
319
|
+
active = opt in selected
|
|
320
|
+
# Same params draw_tab_bar hands flat_button for an untinted tab
|
|
321
|
+
# (new_value=0.15, factor=1.2): active keeps flat_button's full
|
|
322
|
+
# text/saturation, inactive is alpha=0 with the muted text.
|
|
323
|
+
if active:
|
|
324
|
+
clicked = flat_button(label, draw_state, view_id=f"dim_tab_{i}",
|
|
325
|
+
width=w, height=tab_h, color=DIM_TAB_COLOR,
|
|
326
|
+
factor=1.2, tint_value=0.15 + 0.23 - 0.03)
|
|
327
|
+
else:
|
|
328
|
+
clicked = flat_button(label, draw_state, view_id=f"dim_tab_{i}",
|
|
329
|
+
width=w, height=tab_h, color=DIM_TAB_COLOR,
|
|
330
|
+
factor=1.2, alpha=0.0, tint_value=0.15,
|
|
331
|
+
saturation=0.3, text_value=1.0)
|
|
332
|
+
if clicked:
|
|
333
|
+
changed = True
|
|
334
|
+
if multi:
|
|
335
|
+
if active:
|
|
336
|
+
selected.remove(opt)
|
|
337
|
+
else:
|
|
338
|
+
selected.append(opt)
|
|
339
|
+
else:
|
|
340
|
+
selected = [opt]
|
|
341
|
+
# Register the whole band (pads included) with the layout so the row's
|
|
342
|
+
# measured extent covers it, and leave the cursor below it.
|
|
343
|
+
imgui.set_cursor_screen_pos((x0, y0))
|
|
344
|
+
imgui.dummy(right - x0, bottom - y0)
|
|
345
|
+
return changed, selected
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _sibling_dim_keys(draw_state):
|
|
349
|
+
"""Keys of the OTHER draw_tensor_dim rows in the panel this row renders
|
|
350
|
+
in, found by walking the render tree (the parent's _view_children index,
|
|
351
|
+
where every rendered child self-registers) rather than the collection:
|
|
352
|
+
what is actually rendering as a dim picker right now, whatever the
|
|
353
|
+
collection stores. Read-only over the siblings — no value ever moves
|
|
354
|
+
through another row's draw_state."""
|
|
355
|
+
parent = draw_state._parent if draw_state is not None else None
|
|
356
|
+
if parent is None or parent is draw_state: # a root ds parents itself
|
|
357
|
+
return []
|
|
358
|
+
keys = []
|
|
359
|
+
for sib in parent._view_children.values():
|
|
360
|
+
if sib is None or sib is draw_state or sib._parent is not parent:
|
|
361
|
+
continue
|
|
362
|
+
# Recognize the renderer by its stable name, including rows restored
|
|
363
|
+
# from older sessions or decorated through another call path.
|
|
364
|
+
if getattr(sib._view_func, "__name__", None) != "draw_tensor_dim":
|
|
365
|
+
continue
|
|
366
|
+
key = (sib._kwargs or {}).get("key")
|
|
367
|
+
if key is not None:
|
|
368
|
+
keys.append(key)
|
|
369
|
+
return keys
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _swap_sibling_dim(draw_state, kwargs, old, new):
|
|
373
|
+
"""This row just moved from `old` to `new`: if a sibling AXIS row holds
|
|
374
|
+
`new`, hand it `old` so the two axes swap. The hand-off is the same
|
|
375
|
+
write draw_collection performs when that row returns changed —
|
|
376
|
+
`collection[key] = value` on the panel's ParamProxy (→ set_anywhere) —
|
|
377
|
+
so the sibling's value takes the normal route whether or not that row
|
|
378
|
+
renders this frame, and nothing is posted on its draw_state."""
|
|
379
|
+
if new < 0:
|
|
380
|
+
return # "off" can be shared freely
|
|
381
|
+
key = (draw_state._kwargs or {}).get("key") if draw_state is not None else None
|
|
382
|
+
if key not in SWAP_DIM_KEYS:
|
|
383
|
+
return
|
|
384
|
+
col = _row_collection(draw_state, kwargs)
|
|
385
|
+
if col is None:
|
|
386
|
+
return
|
|
387
|
+
for sib_key in _sibling_dim_keys(draw_state):
|
|
388
|
+
if sib_key not in SWAP_DIM_KEYS or sib_key not in col:
|
|
389
|
+
continue
|
|
390
|
+
v = col.get(sib_key)
|
|
391
|
+
if isinstance(v, int) and not isinstance(v, bool) and int(v) == new:
|
|
392
|
+
col[sib_key] = TensorDim(old)
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _tick_values(lo, hi, px_per_idx, num_px, spacing=1.6):
|
|
397
|
+
"""Integer tick positions for the VISIBLE [lo, hi] index span of one
|
|
398
|
+
edge: EVERY integer when the labels fit, else the smallest 1-2-5·10ᵏ
|
|
399
|
+
step whose rotated labels keep clear of each other (footprint ≈ the
|
|
400
|
+
widest label's text width along the edge, in projected PIXELS — so
|
|
401
|
+
zooming in fits more ticks). `spacing` is the minimum gap between tick
|
|
402
|
+
centers in widest-label widths. The span's end values always show —
|
|
403
|
+
0/max on an unclipped edge, the boundary indices (a scrollbar-like
|
|
404
|
+
readout of where you are along the axis) on a clipped one; interior
|
|
405
|
+
step multiples stay GLOBAL multiples (they don't jitter as the clip
|
|
406
|
+
end moves) and yield when they would crowd an end."""
|
|
407
|
+
e0, e1 = int(math.ceil(lo - 1e-9)), int(math.floor(hi + 1e-9))
|
|
408
|
+
if e1 < e0:
|
|
409
|
+
return []
|
|
410
|
+
if e1 == e0:
|
|
411
|
+
return [e0]
|
|
412
|
+
widest = max(1, len(str(e1))) * 0.62 * num_px # ~max glyph aspect
|
|
413
|
+
min_px = widest * spacing
|
|
414
|
+
step, k = None, 1
|
|
415
|
+
while step is None and k <= 10 ** 9:
|
|
416
|
+
for s in (1, 2, 5):
|
|
417
|
+
if s * k * px_per_idx >= min_px:
|
|
418
|
+
step = s * k
|
|
419
|
+
break
|
|
420
|
+
else:
|
|
421
|
+
k *= 10
|
|
422
|
+
if step is None or step > e1 - e0:
|
|
423
|
+
return [e0, e1]
|
|
424
|
+
ticks = [e0]
|
|
425
|
+
m = int(math.ceil((e0 + 0.6 * step) / step)) * step
|
|
426
|
+
while m <= e1 - 0.6 * step:
|
|
427
|
+
ticks.append(m)
|
|
428
|
+
m += step
|
|
429
|
+
ticks.append(e1)
|
|
430
|
+
return ticks
|