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,1891 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import traceback
|
|
8
|
+
import types
|
|
9
|
+
from collections import deque, defaultdict, namedtuple
|
|
10
|
+
from collections.abc import MutableMapping
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from inspect import Parameter
|
|
13
|
+
from math import sqrt
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from types import NoneType
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import OpenGL.GL as gl
|
|
19
|
+
import meltygui.core.windowing.window_api as glfw
|
|
20
|
+
import math
|
|
21
|
+
import numpy
|
|
22
|
+
from meltygui_imgui.core import _DrawList
|
|
23
|
+
|
|
24
|
+
from meltygui.core.styling.fonts import Font
|
|
25
|
+
from meltygui.core.styling.global_style import GlobalStyle
|
|
26
|
+
from meltygui.core.melty import Melty
|
|
27
|
+
from meltygui.core.melty import CollectionAction
|
|
28
|
+
from meltygui.core.melty import ManagedWindow
|
|
29
|
+
from meltygui.core.melty import SearchTerm
|
|
30
|
+
from meltygui.core.conversion.render_host import RenderHost
|
|
31
|
+
from meltygui.core.rendering.shaped import Shaped
|
|
32
|
+
from meltygui.core.conversion.dict_conversion import DictConversion
|
|
33
|
+
from meltygui.core.rendering.modes import Modes
|
|
34
|
+
from meltygui.core.diagnostics.notifications import display
|
|
35
|
+
from meltygui.core.rendering.render_funcs import RenderFuncs
|
|
36
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
37
|
+
from meltygui.core.runtime.toggles import Tint
|
|
38
|
+
from meltygui.core.runtime.toggles import mix
|
|
39
|
+
from meltygui.core.runtime.toggles import rgb_to_hsv
|
|
40
|
+
from meltygui.core.runtime.toggles import hsv_to_rgb
|
|
41
|
+
from meltygui.core.graphics.gl_state import GLState
|
|
42
|
+
from meltygui.utils.render_utils import print_colored_traceback
|
|
43
|
+
from meltygui.utils.render_utils import push_style_var
|
|
44
|
+
from meltygui.utils.render_utils import pop_style_var
|
|
45
|
+
from meltygui.utils.render_utils import end
|
|
46
|
+
from meltygui.utils.render_utils import begin
|
|
47
|
+
from meltygui.core.windowing.glfw_utils import print_stack_trace
|
|
48
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
49
|
+
from meltygui.core.conversion.bubbling import _BubblingDict
|
|
50
|
+
from meltygui.core.conversion.bubbling import _DeepPath
|
|
51
|
+
from meltygui.core.conversion.cache_tree import UNSET_VALUE
|
|
52
|
+
from meltygui.code.libcst_conversion import Comment
|
|
53
|
+
from meltygui.code.libcst_conversion import GeneralParse
|
|
54
|
+
from meltygui.code.libcst_conversion import UsageRef
|
|
55
|
+
from meltygui.code.libcst_conversion import CallParse
|
|
56
|
+
from meltygui.code.libcst_conversion import ClassParse
|
|
57
|
+
from meltygui.code.libcst_conversion import EnumParse
|
|
58
|
+
from meltygui.code.libcst_conversion import FunctionParse
|
|
59
|
+
from meltygui.code.libcst_conversion import SymbolUsage
|
|
60
|
+
from meltygui.code.libcst_conversion import cst_module_to_dict
|
|
61
|
+
from meltygui.code.libcst_conversion import dict_to_cst_module
|
|
62
|
+
from meltygui.code.new_codecs import CallSite
|
|
63
|
+
from meltygui.code.new_converters import code_file_io
|
|
64
|
+
from meltygui.code.new_converters import convert_in_and_out_value
|
|
65
|
+
from meltygui.code.new_converters import cst_module_to_string
|
|
66
|
+
from meltygui.code.new_converters import string_to_cst_module
|
|
67
|
+
from meltygui.code.new_converters import code_hosts_for
|
|
68
|
+
from meltygui.code.new_converters import host_code_state
|
|
69
|
+
from meltygui.code.new_converters import recompile_button
|
|
70
|
+
from meltygui.code.new_converters import recompile_status
|
|
71
|
+
from meltygui.code.new_converters import run_recompile
|
|
72
|
+
from meltygui.core.conversion.path_finder import Pending
|
|
73
|
+
from meltygui.core.layout.cursor_core import same_line
|
|
74
|
+
from meltygui.core.cache.tile_cache import snap_int
|
|
75
|
+
from meltygui.core.cache.tile_cache import add_shadow
|
|
76
|
+
from meltygui.core.core_render import render_func
|
|
77
|
+
from meltygui.core.core_render import render_func_kwarg_names
|
|
78
|
+
from meltygui.core.core_render import SCROLL_BAR_WIDTH_DEFAULT
|
|
79
|
+
from meltygui.core.core_render import SCROLLBAR_MARGIN
|
|
80
|
+
from meltygui.core.rendering.parameter_core import SourcePriority
|
|
81
|
+
from meltygui.core.rendering.parameter_core import _source_priority
|
|
82
|
+
from meltygui.core.rendering.parameter_core import _sources_for
|
|
83
|
+
from meltygui.core.rendering.parameter_core import _driving_source
|
|
84
|
+
from meltygui.core.rendering.parameter_core import _setting_source
|
|
85
|
+
from meltygui.core.rendering.parameter_core import default_write_source
|
|
86
|
+
from meltygui.core.rendering.parameter_core import get_value_for_source
|
|
87
|
+
from meltygui.core.rendering.parameter_core import get_source_for
|
|
88
|
+
from meltygui.core.rendering.parameter_core import from_anywhere
|
|
89
|
+
from meltygui.core.rendering.parameter_core import anywhere_value
|
|
90
|
+
from meltygui.core.rendering.parameter_core import set_anywhere
|
|
91
|
+
from meltygui.core.rendering.parameter_core import flush_deferred_writes
|
|
92
|
+
from meltygui.core.rendering.parameter_core import SET_ANYWHERE_PARAMS
|
|
93
|
+
# Module import (not "from ... import DragDrop`) so hotswaps rebind cleanly.
|
|
94
|
+
import meltygui.core.input.drag_drop_core as _drag_drop
|
|
95
|
+
from meltygui.model.code_proxy_model import *
|
|
96
|
+
from meltygui.core.rendering.core_decoration import hotkey
|
|
97
|
+
from meltygui.core.rendering.core_decoration import Core
|
|
98
|
+
from meltygui.core.cache.invalidation_decoration import live
|
|
99
|
+
from meltygui.core.rendering.window_decoration import window
|
|
100
|
+
from meltygui.view.header_view import draw_header
|
|
101
|
+
from meltygui.core.diagnostics.inspection_core import set_fn_defaults
|
|
102
|
+
from meltygui.view.text_view import draw_text
|
|
103
|
+
from meltygui.editor.text_editor import _scroll_into_view
|
|
104
|
+
from meltygui.graphics.texture_manager import PendingTexture
|
|
105
|
+
from meltygui.core.rendering.core_decoration import defaults
|
|
106
|
+
from meltygui.code.symbol_roster import pass_scope
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def some_text(input_value: str, draw_state, **kwargs):
|
|
110
|
+
imgui.text(f"Text: {input_value}")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# --- Word-aware fuzzy matcher (global search) ---------------------------------
|
|
114
|
+
# Identifiers are WORDS ("draw_any" -> draw, any; "TextEditor" -> text,
|
|
115
|
+
# editor). A query matches when its words each claim a DISTINCT target word:
|
|
116
|
+
# * a query word claims a target word it PREFIXES exactly ("dr" -> draw),
|
|
117
|
+
# or is an exact mid-word substring of when >= 2 chars ("raw" -> draw,
|
|
118
|
+
# "ny" -> any -- never a lone char: "a" must START a word, so "draw_a"
|
|
119
|
+
# never lands on draw_int / draw_collection via the "a" in draw), or
|
|
120
|
+
# fuzzily matches when its FIRST character matches ("amy" -> any: 1 edit)
|
|
121
|
+
# -- fuzz budget is only spent where the word start agrees;
|
|
122
|
+
# * words are unordered ("any_draw" -> draw_any);
|
|
123
|
+
# * a query without separators ("anydraw", "drawany", "rawany", "dra") is
|
|
124
|
+
# tried as one word, then SEGMENTED into pieces that each claim a word by
|
|
125
|
+
# the same rules ("any" + "draw").
|
|
126
|
+
# _word_match() returns the total edit cost (0 = exact) or None.
|
|
127
|
+
_WORD_RE = re.compile(r"[A-Z]+(?![a-z])|[A-Z]?[a-z]+|[0-9]+")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def print_hello():
|
|
131
|
+
print("Hello, world!")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
from meltygui.core.automation.search_core import search_activate_target
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
from meltygui.view.collection_view import draw_collection
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def main_header(input_value, name, **kwargs):
|
|
141
|
+
imgui.text("Main Header")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# value = input_value.fget(input_value)
|
|
145
|
+
# draw_any(value, name="Value", show_bg=True, draw_state=draw_state)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@render_func(is_lens_for=(type), skip_draw=True)
|
|
149
|
+
def type_lens(input_value, view_func, child_kwargs, **kwargs):
|
|
150
|
+
changed, value = view_func(**child_kwargs)
|
|
151
|
+
if changed:
|
|
152
|
+
for k, v in value.items():
|
|
153
|
+
if hasattr(input_value, k):
|
|
154
|
+
if k.startswith("_"):
|
|
155
|
+
continue
|
|
156
|
+
try:
|
|
157
|
+
setattr(input_value, k, v)
|
|
158
|
+
except Exception as e:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
return changed, value
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@render_func()
|
|
165
|
+
def class_to_var_dict(input_value: type, changed, draw_state, **kwargs):
|
|
166
|
+
class_vars = {**{k: getattr(input_value, k) for k in vars(input_value)}}
|
|
167
|
+
class_vars["__original__"] = input_value
|
|
168
|
+
|
|
169
|
+
return changed, class_vars
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@render_func()
|
|
173
|
+
def var_dict_to_class(input_value, changed, **kwargs):
|
|
174
|
+
original_class = input_value.get("__original__", None)
|
|
175
|
+
if original_class is None:
|
|
176
|
+
imgui.text_colored("Error: No original class found in dict", 1.0, 0.0, 0.0, 1.0)
|
|
177
|
+
return False, input_value
|
|
178
|
+
|
|
179
|
+
if changed:
|
|
180
|
+
for k, v in input_value.items():
|
|
181
|
+
if k.startswith("_"):
|
|
182
|
+
continue
|
|
183
|
+
try:
|
|
184
|
+
imgui.text(f"Setting attribute {k} to value {v} on class {original_class.__name__}")
|
|
185
|
+
setattr(original_class, k, v)
|
|
186
|
+
except Exception as e:
|
|
187
|
+
imgui.text(f"Error setting attribute {k} on class {original_class.__name__}: {e}")
|
|
188
|
+
|
|
189
|
+
return False, input_value
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
some_float = [0.0]
|
|
193
|
+
cst_dict = {}
|
|
194
|
+
test_code = None
|
|
195
|
+
selected_tabs = ["Alpha"]
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@render_func(show_bg=True, with_header=draw_header)
|
|
199
|
+
def test_columns():
|
|
200
|
+
draw_str("Column 1", name="col1", column=0)
|
|
201
|
+
draw_int(123, name="col2", column=1)
|
|
202
|
+
draw_float(0.5, name="col3", column=2)
|
|
203
|
+
draw_float(0.5, name="test_5", column=5)
|
|
204
|
+
draw_float(0.5, name="test_5_b", column=5)
|
|
205
|
+
|
|
206
|
+
for i in range(10):
|
|
207
|
+
draw_float(0.4, name=f"float_{i}", column=2)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@render_func(use_cache=False, shadow=False, show_bg=False, disable_scroll=False, selectable=False)
|
|
211
|
+
def run_chain(input_value, chain=None, draw_state=None, route=None,
|
|
212
|
+
s_key_pressed=False, enter_key_pressed=False, unique=None, debug=False, **kwargs):
|
|
213
|
+
"""Debug render function: executes a chain step by step with imgui output.
|
|
214
|
+
|
|
215
|
+
Shows function name, changed flag, output type, and a value preview
|
|
216
|
+
at each stage. Color coded: green=changed, gray=cached, yellow=pending.
|
|
217
|
+
"""
|
|
218
|
+
if chain is None:
|
|
219
|
+
imgui.text("No chain provided")
|
|
220
|
+
return False, input_value
|
|
221
|
+
|
|
222
|
+
value = input_value
|
|
223
|
+
changed = False
|
|
224
|
+
|
|
225
|
+
if debug:
|
|
226
|
+
imgui.text(f"Chain: {len(chain)} nodes")
|
|
227
|
+
imgui.text(f"Input: {type(input_value).__name__}")
|
|
228
|
+
imgui.separator()
|
|
229
|
+
|
|
230
|
+
cache_tree = draw_state._chain_stack
|
|
231
|
+
cache_tree.begin()
|
|
232
|
+
|
|
233
|
+
mode_cache = chain[0][1].get("mode_cache", False) if isinstance(chain[0], dict) else False
|
|
234
|
+
if mode_cache:
|
|
235
|
+
value = cache_tree.step(changed, value)
|
|
236
|
+
|
|
237
|
+
to_route = {}
|
|
238
|
+
|
|
239
|
+
for i, func in enumerate(chain):
|
|
240
|
+
if isinstance(func, tuple):
|
|
241
|
+
func, func_kwargs = func[0], func[1]
|
|
242
|
+
else:
|
|
243
|
+
func_kwargs = {}
|
|
244
|
+
|
|
245
|
+
if debug:
|
|
246
|
+
if not changed:
|
|
247
|
+
name = getattr(func, '__name__', repr(func))
|
|
248
|
+
imgui.text(f" [{i}] {name} — (no change)")
|
|
249
|
+
|
|
250
|
+
func_kwargs['name'] = f"{func.__name__}{i}{kwargs.get('name', f'')}{unique}"
|
|
251
|
+
func_kwargs['shadow'] = False
|
|
252
|
+
func_kwargs['changed'] = changed
|
|
253
|
+
func_kwargs['show_header'] = False
|
|
254
|
+
func_kwargs['s_key_pressed'] = s_key_pressed
|
|
255
|
+
func_kwargs['enter_key_pressed'] = enter_key_pressed
|
|
256
|
+
func_kwargs['draw'] = True
|
|
257
|
+
func_kwargs['real_type'] = type(input_value)
|
|
258
|
+
for arg_name, arg_val in to_route.values():
|
|
259
|
+
func_kwargs[arg_name] = arg_val
|
|
260
|
+
|
|
261
|
+
next_cached = cache_tree.peek()
|
|
262
|
+
if isinstance(value, str):
|
|
263
|
+
imgui.text(f" [{i}] {func.__name__} — str: '{value[:30]}'")
|
|
264
|
+
|
|
265
|
+
imgui.begin_group()
|
|
266
|
+
changed, value = func(input_value=value, reference=next_cached, **func_kwargs)
|
|
267
|
+
imgui.end_group()
|
|
268
|
+
#
|
|
269
|
+
# if not changed:
|
|
270
|
+
# value = None
|
|
271
|
+
|
|
272
|
+
if isinstance(value, Pending):
|
|
273
|
+
changed = False
|
|
274
|
+
value = None
|
|
275
|
+
|
|
276
|
+
mode_cache = func_kwargs.get('mode_cache', True)
|
|
277
|
+
if mode_cache:
|
|
278
|
+
value = cache_tree.step(changed, value)
|
|
279
|
+
|
|
280
|
+
if route is not None:
|
|
281
|
+
if func in route:
|
|
282
|
+
arg_name = route[func]
|
|
283
|
+
to_route[arg_name] = arg_name, value
|
|
284
|
+
|
|
285
|
+
cache_tree.end()
|
|
286
|
+
|
|
287
|
+
return changed, value
|
|
288
|
+
|
|
289
|
+
# Nested sample data for the recursive dropdown demo.
|
|
290
|
+
dropdown_demo_data = {
|
|
291
|
+
"small": 12,
|
|
292
|
+
"medium": 16,
|
|
293
|
+
"large": 24,
|
|
294
|
+
"color": {
|
|
295
|
+
"rgb": {"red": (1.0, 0.0, 0.0), "green": (0.0, 1.0, 0.0)},
|
|
296
|
+
"named": {"steel": "#4682b4", "teal": "#008080"},
|
|
297
|
+
},
|
|
298
|
+
"alignment": ["left", "center", "right"],
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
drop_down_selection = None
|
|
303
|
+
# draw_main logs its section split to the perf log for any call slower than
|
|
304
|
+
# this (ms) - the root handler's frame-by-frame spikes were untraceable
|
|
305
|
+
# otherwise. [tint=(0.95, 0.55, 0.15)]
|
|
306
|
+
_DM_TRACE_MS = 1.5
|
|
307
|
+
# hey there
|
|
308
|
+
|
|
309
|
+
@render_func
|
|
310
|
+
def test_widget(input_value, name, unique, **kwargs):
|
|
311
|
+
imgui.text("Test Widget")
|
|
312
|
+
draw_text("Editable Text", name="editable_text", show_bg=True)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
source = "x = foo(val=1)\nprint(x)\nsome_list=[0, 1, 2, 3]\n"
|
|
316
|
+
module = cst.parse_module(source)
|
|
317
|
+
proxy = cst_wrap(module)
|
|
318
|
+
name_edits = {}
|
|
319
|
+
code_export_str = "Test"
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# Main draw function, called by the GUI framework
|
|
323
|
+
|
|
324
|
+
@live
|
|
325
|
+
class TestObj:
|
|
326
|
+
def __init__(self):
|
|
327
|
+
self.test_val = 0.0
|
|
328
|
+
self.test_list = [1, 2, 3, 4, 5]
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
test_obj = TestObj()
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def draw(vis):
|
|
335
|
+
draw_melty_windows(vis)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def export_code(test_param_2: int = 5):
|
|
339
|
+
# print(f"hello {test_param_2}")
|
|
340
|
+
global code_export_str
|
|
341
|
+
code_export_str = proxy.node.code
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@hotkey(glfw.KEY_O)
|
|
345
|
+
def toggle_offscreen():
|
|
346
|
+
if Core.melty.cache.enabled:
|
|
347
|
+
Core.melty.cache.set_enabled(False)
|
|
348
|
+
else:
|
|
349
|
+
Core.melty.cache.set_enabled(True)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
import meltygui_imgui as imgui
|
|
353
|
+
from meltygui.hdr_color import pack_color
|
|
354
|
+
from meltygui.hdr_color import scale_saturation
|
|
355
|
+
# new comment
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
bg_style_default = {
|
|
359
|
+
"value": 0.01,
|
|
360
|
+
"saturation": 1.2,
|
|
361
|
+
"alpha": 1.0,
|
|
362
|
+
'max_value': 1.0
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def get_bg_color(depth, rounding, style_manager, auto_resize):
|
|
367
|
+
depth_factor = GlobalStyle.get_global_constant("depth_factor", default=1.0, folder="bg_styles") * 0.95
|
|
368
|
+
depth_offset = GlobalStyle.get_global_constant("depth_offset", default=0.0, folder="bg_styles") - 1.3
|
|
369
|
+
dynamic_value = max(0, (float(depth + depth_offset) * depth_factor))
|
|
370
|
+
|
|
371
|
+
hovered_offset = 0.0
|
|
372
|
+
|
|
373
|
+
def mix_colors(c1, c2, fac):
|
|
374
|
+
return (c1[0] * (1 - fac) + c2[0] * fac,
|
|
375
|
+
c1[1] * (1 - fac) + c2[1] * fac,
|
|
376
|
+
c1[2] * (1 - fac) + c2[2] * fac)
|
|
377
|
+
|
|
378
|
+
global bg_style_default
|
|
379
|
+
bg_style = GlobalStyle.get_global_constant("bg_style", default=bg_style_default, folder="bg_styles")
|
|
380
|
+
outline_factor = GlobalStyle.get_global_constant("outline_factor", default=1.0, folder="bg_styles") * 1.4
|
|
381
|
+
|
|
382
|
+
if not auto_resize:
|
|
383
|
+
outline_factor *= 1.3
|
|
384
|
+
|
|
385
|
+
if auto_resize:
|
|
386
|
+
bleed_factor = 0.2
|
|
387
|
+
else:
|
|
388
|
+
bleed_factor = 0.0
|
|
389
|
+
bg_bleed = Core.melty.get_bg_color(-1)
|
|
390
|
+
bg_bleed = style_manager.make_custom_styled(*bg_bleed, input=bg_style,
|
|
391
|
+
value=0.6,
|
|
392
|
+
alpha=1.0, saturation=1.8)
|
|
393
|
+
bg_color = (style_manager.
|
|
394
|
+
make_color_style_value(input=bg_style, value=max(0, dynamic_value) + hovered_offset))
|
|
395
|
+
bg_color = mix_colors(bg_color, bg_bleed, bleed_factor)
|
|
396
|
+
return bg_color
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def seperator(height):
|
|
400
|
+
imgui.dummy(0, snap_int(height / 2))
|
|
401
|
+
imgui.separator()
|
|
402
|
+
imgui.dummy(0, snap_int(height / 2))
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def test_func():
|
|
406
|
+
# Some comment
|
|
407
|
+
# Comment here
|
|
408
|
+
some_val = 1.706
|
|
409
|
+
some_dict = {"some_key": -0.02,
|
|
410
|
+
"key": False,
|
|
411
|
+
"key_2": 2.421
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
from meltygui.model.color_model import _clamp_bg_value
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def compute_bg_color(bg_offset=0, tint=None, nested_bg=False, max_bg_depth=None, max_bg_value=None):
|
|
419
|
+
depth_wrap = 34
|
|
420
|
+
depth_scale = 1.629
|
|
421
|
+
intensity_factor = 0.021
|
|
422
|
+
intensity_offset = -0.336
|
|
423
|
+
outline_depth_mul = 0.786
|
|
424
|
+
# More text
|
|
425
|
+
bleed_style = {'value': -0.111, 'alpha': 1.12, 'saturation': 7.045}
|
|
426
|
+
|
|
427
|
+
bg_style = {
|
|
428
|
+
'value': -0.004, 'saturation': 1.101,
|
|
429
|
+
'alpha': 0.504, 'max_value': 1.8,
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
def mix_colors(color_a, color_b, factor):
|
|
433
|
+
return (
|
|
434
|
+
color_a[0] * (1 - factor) + color_b[0] * factor,
|
|
435
|
+
color_a[1] * (1 - factor) + color_b[1] * factor,
|
|
436
|
+
color_a[2] * (1 - factor) + color_b[2] * factor,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
# -- Depth calculation -------------------
|
|
440
|
+
max_depth = 15
|
|
441
|
+
bg_depth = Core.melty.bg_depth if Core.melty.bg_depth is not None else 0
|
|
442
|
+
bg_offset = bg_offset if bg_offset is not None else 0
|
|
443
|
+
wrapped_depth = min(max_depth, (bg_depth % depth_wrap) + bg_offset)
|
|
444
|
+
# Caller-supplied ceiling on the effective depth: past this step the color
|
|
445
|
+
# keeps the palette value for max_bg_depth instead of getting lighter.
|
|
446
|
+
if max_bg_depth is not None:
|
|
447
|
+
wrapped_depth = min(wrapped_depth, max_bg_depth)
|
|
448
|
+
scaled_depth = wrapped_depth * depth_scale
|
|
449
|
+
depth_intensity = (scaled_depth + intensity_offset) * intensity_factor
|
|
450
|
+
max_depth_intensity = 0.652
|
|
451
|
+
depth_intensity = min(depth_intensity, max_depth_intensity)
|
|
452
|
+
|
|
453
|
+
# ── Outline color ──────────────────────────────────────────
|
|
454
|
+
depth_mul = outline_depth_mul
|
|
455
|
+
if not nested_bg:
|
|
456
|
+
depth_mul *= 1.00
|
|
457
|
+
|
|
458
|
+
# ── Background bleed color ─────────────────────────────────
|
|
459
|
+
bleed_factor = 0.501 if nested_bg else 0.446
|
|
460
|
+
|
|
461
|
+
bleed_base = Core.melty.get_bg_color(-1)
|
|
462
|
+
bleed_color = Melty.style_manager.make_custom_styled(
|
|
463
|
+
*bleed_base, input=bg_style, **bleed_style,
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
# ── Fill rendering ─────────────────────────────────────────
|
|
467
|
+
bg_color = Melty.style_manager.make_color_style_value(input=bg_style, value=max(0.0, depth_intensity))
|
|
468
|
+
bg_color = mix_colors(bg_color, bleed_color, bleed_factor)
|
|
469
|
+
|
|
470
|
+
return _clamp_bg_value(bg_color, max_bg_value)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# (style hsv, bg colour −2, bg colour −1, outline value, sat) → (bleed, outline)
|
|
474
|
+
_DRAW_BG_COLOUR_MEMO = globals().get("_DRAW_BG_COLOUR_MEMO", {})
|
|
475
|
+
_DRAW_BG_FILL_MEMO = globals().get("_DRAW_BG_FILL_MEMO", {}) # (colour key, sat, depth, bleed) → fill rgb
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
from meltygui.view.control_view import text
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
from meltygui.view.control_view import draw_str
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def sort_dict_alphabetically(input_value, **kwargs):
|
|
485
|
+
changed = False
|
|
486
|
+
attr_name = "name"
|
|
487
|
+
first_item = next(iter(input_value.items()), None)[1]
|
|
488
|
+
if hasattr(first_item, attr_name):
|
|
489
|
+
sorted_dict = dict(sorted(input_value.items(), key=lambda item: str(getattr(item[1], attr_name)).lower()))
|
|
490
|
+
return changed, sorted_dict
|
|
491
|
+
else:
|
|
492
|
+
imgui.text("Cannot sort: items do not have 'name' attribute")
|
|
493
|
+
return False, input_value
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@render_func()
|
|
497
|
+
def unsort_dict_alphabetically(input_value, ref=None, changed=False):
|
|
498
|
+
if ref is None:
|
|
499
|
+
imgui.text("Original order not available")
|
|
500
|
+
return False, input_value
|
|
501
|
+
else:
|
|
502
|
+
# Ref is the original dict
|
|
503
|
+
ref.update(input_value)
|
|
504
|
+
return changed, ref
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def param_source_matrix(input_value, keys=None, func=None, include_unmatched=False, **kwargs):
|
|
508
|
+
"""Pivot a render function's possible INPUTS into a parameter × source
|
|
509
|
+
table — the inputs-tab aggregator. `input_value` is the collected sources,
|
|
510
|
+
a {source_name: {param: value}} mapping (caller kwargs, @defaults on the
|
|
511
|
+
model class, mode kwargs, @render_func decorator kwargs, signature
|
|
512
|
+
defaults, …); `keys` is the function's parameter-name list (rows) — pass it
|
|
513
|
+
directly, or pass `func` and they're derived via inspect (unwrapped, minus
|
|
514
|
+
the catch-all params). Returns {param: {source_name: value}}:
|
|
515
|
+
|
|
516
|
+
rows one per parameter, in parameter order — an EMPTY row means no
|
|
517
|
+
source sets it (still shown: the point is mapping the full
|
|
518
|
+
input surface in one spot)
|
|
519
|
+
columns one per source that sets the param, in source order
|
|
520
|
+
|
|
521
|
+
Cells alias the source values (no copies). With include_unmatched, keys a
|
|
522
|
+
source sets that are NOT parameters append as extra rows at the end —
|
|
523
|
+
typos and **kwargs ride-throughs stay visible instead of vanishing.
|
|
524
|
+
Shaped like sort_dict_alphabetically: a plain (changed, value) chain node;
|
|
525
|
+
apply_param_source_matrix below is the unsort-style reverse.
|
|
526
|
+
Source COLOR-CODING is not this function's job: codecs carry a tint
|
|
527
|
+
(new_codecs.render_kwargs) that core_render merges in as the lowest
|
|
528
|
+
kwargs layer, so codec-backed values color themselves wherever drawn."""
|
|
529
|
+
changed = False
|
|
530
|
+
if isinstance(input_value, dict):
|
|
531
|
+
items = list(input_value.items())
|
|
532
|
+
else:
|
|
533
|
+
items = [(f"source_{i}", s) for i, s in enumerate(input_value or ())]
|
|
534
|
+
items = [(str(n), s) for n, s in items if isinstance(s, dict)]
|
|
535
|
+
|
|
536
|
+
if keys is None and func is not None:
|
|
537
|
+
try:
|
|
538
|
+
keys = [p for p in inspect.signature(inspect.unwrap(func)).parameters
|
|
539
|
+
if p not in ("args", "kwargs", "o_kwargs", "next_kwargs")]
|
|
540
|
+
except (TypeError, ValueError):
|
|
541
|
+
keys = []
|
|
542
|
+
# A render func's input surface is its signature PLUS the kwargs the
|
|
543
|
+
# @render_func machinery itself consumes (width/height/tint/shadow and
|
|
544
|
+
# the flag zoo) - shared by every render func, loaded once per process
|
|
545
|
+
# by re-scanning the decorator source (render_func_kwarg_names).
|
|
546
|
+
if getattr(func, "__render_func__", False):
|
|
547
|
+
_seen = set(keys)
|
|
548
|
+
keys += [k for k in render_func_kwarg_names() if k not in _seen]
|
|
549
|
+
keys = list(keys or [])
|
|
550
|
+
|
|
551
|
+
matrix = {}
|
|
552
|
+
for k in keys:
|
|
553
|
+
row = {}
|
|
554
|
+
for sname, sdict in items:
|
|
555
|
+
if k in sdict:
|
|
556
|
+
row[sname] = sdict[k]
|
|
557
|
+
matrix[k] = row
|
|
558
|
+
if include_unmatched:
|
|
559
|
+
for sname, sdict in items:
|
|
560
|
+
for k in sdict:
|
|
561
|
+
# Parse metadata is NOT an input: skip non-plain-str keys
|
|
562
|
+
# (Comment objects keying their own line in a class-body
|
|
563
|
+
# parse), dunders/underscored bookkeeping, and the parse's
|
|
564
|
+
# section keys - only real attribute names become rows.
|
|
565
|
+
if (type(k) is not str or k.startswith('_')
|
|
566
|
+
or k in _PARSE_SECTION_KEYS):
|
|
567
|
+
continue
|
|
568
|
+
if k not in matrix or (k not in keys and sname not in matrix[k]):
|
|
569
|
+
matrix.setdefault(k, {})[sname] = sdict[k]
|
|
570
|
+
return changed, matrix
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
# Attributes ALWAYS in the inputs-tab param list (pinned right after the view
|
|
574
|
+
# function's own signature params), even if no source sets them organically -
|
|
575
|
+
# they come from the @render_func machinery, not the view function's signature.
|
|
576
|
+
MATRIX_DEFAULT_PRIORITY = ("view_func", "width", "height", "min_height", "min_width", "tint")
|
|
577
|
+
|
|
578
|
+
# Structural sections of a GeneralParse dict - never attribute names, so
|
|
579
|
+
# param_source_matrix's include_unmatched must not turn them into rows when a
|
|
580
|
+
# a class/function parse is registered as a source (the class-var source).
|
|
581
|
+
_PARSE_SECTION_KEYS = frozenset({"decorators", "parameters", "locals"})
|
|
582
|
+
|
|
583
|
+
# Framework-injected parameters: present in most view-function signatures but
|
|
584
|
+
# never user-tunable, so they don't belong in the signature section.
|
|
585
|
+
_MATRIX_FRAMEWORK_PARAMS = {"input_value", "draw_state", "args", "kwargs",
|
|
586
|
+
"o_kwargs", "next_kwargs", "meta", "viewstate",
|
|
587
|
+
"self", "unique", "changed"}
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def signature_param_names(func):
|
|
591
|
+
"""The view function's OWN tunable parameters (unwrapped signature minus
|
|
592
|
+
the framework-injected names) plus MATRIX_DEFAULT_PRIORITY — the params
|
|
593
|
+
pinned to the front of the inputs-tab list. For draw_float that's
|
|
594
|
+
min_value/max_value/speed/…; width/height/min_height/min_width/tint ride
|
|
595
|
+
along from the default list."""
|
|
596
|
+
try:
|
|
597
|
+
params = inspect.signature(inspect.unwrap(func)).parameters
|
|
598
|
+
except (TypeError, ValueError):
|
|
599
|
+
return []
|
|
600
|
+
names = [p for p in params if p not in _MATRIX_FRAMEWORK_PARAMS]
|
|
601
|
+
names += [k for k in MATRIX_DEFAULT_PRIORITY if k not in names]
|
|
602
|
+
return names
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
@render_func()
|
|
606
|
+
def apply_param_source_matrix(input_value, ref=None, changed=False):
|
|
607
|
+
"""Reverse of param_source_matrix — the unsort_dict_alphabetically analog.
|
|
608
|
+
`ref` is the ORIGINAL {source_name: dict} sources mapping; every edited
|
|
609
|
+
cell writes back into the source dict it came from (a tint edited under
|
|
610
|
+
the 'caller' column lands in the caller-kwargs dict), so each source's own
|
|
611
|
+
save path can persist it. Sources absent from ref are left untouched."""
|
|
612
|
+
if ref is None:
|
|
613
|
+
imgui.text("Original sources not available")
|
|
614
|
+
return False, input_value
|
|
615
|
+
for param, row in input_value.items():
|
|
616
|
+
if not isinstance(row, dict):
|
|
617
|
+
continue
|
|
618
|
+
for sname, val in row.items():
|
|
619
|
+
src = ref.get(sname) if isinstance(ref, dict) else None
|
|
620
|
+
# Skip already-equal cells: bubbling-wrapped source dicts mark
|
|
621
|
+
# their host dirty on each write, so only real edits write back.
|
|
622
|
+
if isinstance(src, dict) and (param not in src or src[param] is not val):
|
|
623
|
+
src[param] = val
|
|
624
|
+
return changed, ref
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
# Picker layout shared by the popover callers (draw_tuple, draw_tuple_fast,
|
|
628
|
+
# the editor's swatches) — they size the fixed popover window from it.
|
|
629
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
630
|
+
PICKER_SQUARE = Toggles.ColorPicker.square_size
|
|
631
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
632
|
+
PICKER_TABS_HEIGHT = Toggles.ColorPicker.tabs_height
|
|
633
|
+
# The sRGB+ tab's wide-gamut extension, to the RIGHT of the classic square
|
|
634
|
+
# (same height as the square; its width is what the tab adds to the popover).
|
|
635
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
636
|
+
PICKER_EXTENSION = Toggles.ColorPicker.extension_width
|
|
637
|
+
# The sRGB+ tab's exposure band ABOVE the square (and its extension): white
|
|
638
|
+
# at the seam up to 2^Toggles.HDR.picker_max_stops at the top. Every tab
|
|
639
|
+
# reserves this height above its square, so the classic square sits at the
|
|
640
|
+
# same screen spot whichever tab is showing.
|
|
641
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
642
|
+
PICKER_EXPOSURE_BAND = Toggles.ColorPicker.exposure_band_height
|
|
643
|
+
# Gap between the swatch's bottom edge and the popover's top.
|
|
644
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
645
|
+
PICKER_ANCHOR_GAP = Toggles.ColorPicker.anchor_gap
|
|
646
|
+
# The view-offset rows (draw_view_offsets_fast) under the colour tabs when
|
|
647
|
+
# the picker edits a view: a separator + one drag row per offset.
|
|
648
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
649
|
+
PICKER_OFFSETS_HEIGHT = Toggles.ColorPicker.offsets_height
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
from meltygui.core.styling.color_core import _style_policy_source
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
from meltygui.core.styling.color_core import _add_style_policy
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
# The view params the picker edits beside the colour, with their drag rows:
|
|
659
|
+
# (param, label, drag speed). Both are ints read and written through the
|
|
660
|
+
# owner's `locate_<param>` — the wrapper's `bg_offset` (palette depth the
|
|
661
|
+
# view's background is sampled at) and `z_offset` (its shadow / paint depth).
|
|
662
|
+
# Add a row here to expose another wrapper kwarg.
|
|
663
|
+
# [tint=(0.85, 0.75, 0.05)]
|
|
664
|
+
VIEW_OFFSET_ROWS = Toggles.ColorPicker.view_offset_rows
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
class TestClass(DictConversion):
|
|
668
|
+
def __init__(self):
|
|
669
|
+
super().__init__()
|
|
670
|
+
self.value = 2
|
|
671
|
+
self.str_val = "Test"
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
from meltygui.view.control_view import draw_float
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+
@render_func(wraps=render_func, show_add_delete=False, with_header=draw_header)
|
|
678
|
+
def eval_function(input_value, draw_state):
|
|
679
|
+
signature = inspect.signature(input_value)
|
|
680
|
+
params = signature.parameters
|
|
681
|
+
changed, new_val = draw_any(params, name="Parameters", show_add_delete=False)
|
|
682
|
+
if changed:
|
|
683
|
+
set_fn_defaults(input_value, new_val)
|
|
684
|
+
|
|
685
|
+
push_style_var(imgui.STYLE_ITEM_SPACING, (2, 4))
|
|
686
|
+
push_style_var(imgui.STYLE_FRAME_PADDING, (8, 6))
|
|
687
|
+
push_style_var(imgui.STYLE_FRAME_ROUNDING, 6)
|
|
688
|
+
|
|
689
|
+
function_args = inspect.signature(input_value).parameters
|
|
690
|
+
kwargs = {}
|
|
691
|
+
for name, param in function_args.items():
|
|
692
|
+
if param.default is not inspect.Parameter.empty:
|
|
693
|
+
kwargs[name] = param.default
|
|
694
|
+
else:
|
|
695
|
+
kwargs[name] = None
|
|
696
|
+
try:
|
|
697
|
+
result = input_value(**kwargs)
|
|
698
|
+
draw_any(result, name="Result", show_header=True, show_add_delete=False)
|
|
699
|
+
if draw_state._result != result:
|
|
700
|
+
Core.melty.cache.invalidate_all()
|
|
701
|
+
draw_state._result = result
|
|
702
|
+
|
|
703
|
+
except Exception as e:
|
|
704
|
+
print(f"Error calling function '{input_value.__name__}': {e}")
|
|
705
|
+
print_colored_traceback(*sys.exc_info())
|
|
706
|
+
|
|
707
|
+
pop_style_var(3)
|
|
708
|
+
|
|
709
|
+
return changed, input_value
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def _format_run_error(exc):
|
|
713
|
+
"""One compact, UI-ready error: `Type: message`, then the deepest
|
|
714
|
+
traceback frame in PROJECT code (site-packages/stdlib frames are where
|
|
715
|
+
the error SURFACED, not where it's fixable) with its source line."""
|
|
716
|
+
import traceback
|
|
717
|
+
frames = traceback.extract_tb(exc.__traceback__)
|
|
718
|
+
target = None
|
|
719
|
+
for fr in reversed(frames):
|
|
720
|
+
if "site-packages" not in fr.filename and "/lib/python" not in fr.filename:
|
|
721
|
+
target = fr
|
|
722
|
+
break
|
|
723
|
+
if target is None and frames:
|
|
724
|
+
target = frames[-1]
|
|
725
|
+
text = f"{type(exc).__name__}: {exc}"
|
|
726
|
+
if target is not None:
|
|
727
|
+
text += f"\n{Path(target.filename).name}:{target.lineno} in {target.name}"
|
|
728
|
+
if target.line:
|
|
729
|
+
text += f"\n {target.line}"
|
|
730
|
+
return text
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
# Runner draw states holding a run's `result` - what the CUDA-OOM responder
|
|
734
|
+
# frees (a forward pass result is typically the largest single live object).
|
|
735
|
+
_RUN_RESULT_HOLDERS = globals().get("_RUN_RESULT_HOLDERS")
|
|
736
|
+
if _RUN_RESULT_HOLDERS is None:
|
|
737
|
+
import weakref as _weakref
|
|
738
|
+
_RUN_RESULT_HOLDERS = _weakref.WeakSet()
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
# Runner draw_states with a threaded run IN FLIGHT - draw_function's
|
|
742
|
+
# single-flight policy. Process-living and NOT serialized: this latch used
|
|
743
|
+
# to be `draw_state.misc["_run_busy"]`, and it rides in custom.pkl with
|
|
744
|
+
# the draw_state, so a restart while a run was in flight (2026-08-23: the
|
|
745
|
+
# Pending Saves recompile) reloaded the runner as "busy" in every later
|
|
746
|
+
# session - locked forever, every new run refused as "already running".
|
|
747
|
+
_RUN_BUSY = globals().get("_RUN_BUSY")
|
|
748
|
+
if _RUN_BUSY is None:
|
|
749
|
+
import weakref as _weakref
|
|
750
|
+
_RUN_BUSY = _weakref.WeakSet()
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def is_run_busy(draw_state) -> bool:
|
|
754
|
+
return draw_state in _RUN_BUSY
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def run_busy_begin(draw_state) -> bool:
|
|
758
|
+
"""Claim the runner for a run. False if one is already in flight."""
|
|
759
|
+
if draw_state in _RUN_BUSY:
|
|
760
|
+
return False
|
|
761
|
+
_RUN_BUSY.add(draw_state)
|
|
762
|
+
return True
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def run_busy_end(draw_state) -> None:
|
|
766
|
+
_RUN_BUSY.discard(draw_state)
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
def _release_run_results():
|
|
770
|
+
for ds in list(_RUN_RESULT_HOLDERS):
|
|
771
|
+
try:
|
|
772
|
+
ds.result = None
|
|
773
|
+
ds.misc.pop("_result_frame", None)
|
|
774
|
+
except Exception:
|
|
775
|
+
pass
|
|
776
|
+
_RUN_RESULT_HOLDERS.clear()
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def _respond_to_cuda_oom(exc, where):
|
|
780
|
+
try:
|
|
781
|
+
from meltygui.core.runtime.gc_manager import respond_to_cuda_oom
|
|
782
|
+
from meltygui.core.runtime.gc_manager import OOM_RELEASE_HOOKS
|
|
783
|
+
if not any(getattr(h, "__name__", None) == "_release_run_results"
|
|
784
|
+
for h in OOM_RELEASE_HOOKS):
|
|
785
|
+
OOM_RELEASE_HOOKS.append(_release_run_results)
|
|
786
|
+
respond_to_cuda_oom(exc, where=where)
|
|
787
|
+
except Exception:
|
|
788
|
+
pass
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
from meltygui.view.control_view import draw_int
|
|
792
|
+
|
|
793
|
+
|
|
794
|
+
def eval_input_scope(draw_state):
|
|
795
|
+
"""{param: value} for every input the context menu's INPUTS tab lists on
|
|
796
|
+
`draw_state`'s view: the view function's own params, the header
|
|
797
|
+
function's params (the resolved `with_header`), and the kwargs the
|
|
798
|
+
@render_func wrapper itself consumes (show_bg, width, tint, ...). Each
|
|
799
|
+
value is what the inputs tab DISPLAYS for it — anywhere_value (the
|
|
800
|
+
wrapper-resolved `_kwargs`, the ds field fallback, an in-flight
|
|
801
|
+
set-anywhere value), else the declared signature default — so typing
|
|
802
|
+
`show_bg` in the Eval tab answers with the value actually driving the
|
|
803
|
+
view. Layered UNDER the call-time locals by run_scoped_eval: a name the
|
|
804
|
+
view function receives keeps its exact argument."""
|
|
805
|
+
from meltygui.core.rendering.parameter_core import anywhere_value
|
|
806
|
+
from meltygui.core.rendering.parameter_core import header_param_names
|
|
807
|
+
from meltygui.core.rendering.parameter_core import signature_default_for
|
|
808
|
+
from meltygui.core.rendering.parameter_core import view_param_names
|
|
809
|
+
from meltygui.core.core_render import render_func_kwarg_names
|
|
810
|
+
names = []
|
|
811
|
+
seen = set()
|
|
812
|
+
for group in (view_param_names(draw_state), header_param_names(draw_state),
|
|
813
|
+
render_func_kwarg_names()):
|
|
814
|
+
for name in group:
|
|
815
|
+
if name not in seen and name.isidentifier():
|
|
816
|
+
seen.add(name)
|
|
817
|
+
names.append(name)
|
|
818
|
+
scope = {}
|
|
819
|
+
for name in names:
|
|
820
|
+
try:
|
|
821
|
+
value = anywhere_value(name, draw_state)
|
|
822
|
+
if value is None:
|
|
823
|
+
value = signature_default_for(name, draw_state)
|
|
824
|
+
except Exception:
|
|
825
|
+
value = None
|
|
826
|
+
scope[name] = value
|
|
827
|
+
return scope
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def run_scoped_eval(code, view_func, draw_state, local_vars):
|
|
831
|
+
"""Run `code` with the view function's ACTUAL call-time locals in scope.
|
|
832
|
+
|
|
833
|
+
Called from the render wrapper (core_render) right before it invokes the
|
|
834
|
+
view function, so `local_vars` is the exact set of arguments the function is
|
|
835
|
+
about to receive -- its initial locals (`input_value`, `draw_state`, and
|
|
836
|
+
every kwarg by name). On top of those we layer the function's module globals
|
|
837
|
+
(so the snippet resolves the same free names the body would) plus the `value`
|
|
838
|
+
and `ds` aliases. Locals win over globals, mirroring normal scoping.
|
|
839
|
+
|
|
840
|
+
Delegates to the same eval/exec + stdout-capture machinery the MCP
|
|
841
|
+
`eval_python` tool uses, so a trailing expression's repr comes back alongside
|
|
842
|
+
any printed output. The namespace is a fresh dict layered over a *copy* of
|
|
843
|
+
the module globals, so assignments in the snippet don't leak back into the
|
|
844
|
+
module.
|
|
845
|
+
"""
|
|
846
|
+
from meltygui.core.automation.mcp_eval import _run_code
|
|
847
|
+
ns = {}
|
|
848
|
+
if view_func is not None:
|
|
849
|
+
# Same free names the function body resolves.
|
|
850
|
+
ns.update(getattr(view_func, "__globals__", {}))
|
|
851
|
+
# Every input the eval tab lists (view / header / wrapper params) at its
|
|
852
|
+
# displayed value; the call-time locals below override the ones the
|
|
853
|
+
# function actually receives.
|
|
854
|
+
try:
|
|
855
|
+
ns.update(eval_input_scope(draw_state))
|
|
856
|
+
except Exception:
|
|
857
|
+
pass
|
|
858
|
+
if local_vars:
|
|
859
|
+
ns.update(local_vars) # the view function's call-time locals
|
|
860
|
+
ns.setdefault("draw_state", draw_state)
|
|
861
|
+
ns.setdefault("ds", ns.get("draw_state"))
|
|
862
|
+
ns.setdefault("value", ns.get("input_value"))
|
|
863
|
+
|
|
864
|
+
# Record the EXACT eval scope (the function's call-time locals + the ds/value
|
|
865
|
+
# aliases) into the per-function metadata cache, so the eval tab's autocomplete
|
|
866
|
+
# gives type-accurate suggestions on the next load. Keyed on the wrapper
|
|
867
|
+
# (draw_state._view_func) to match what the eval tab looks up. Globals aren't
|
|
868
|
+
# recorded -- they're resolved live from the function's __globals__ at
|
|
869
|
+
# completion time. Best-effort; a hiccup here must never fail the eval.
|
|
870
|
+
try:
|
|
871
|
+
from meltygui.core.rendering.func_metadata import FuncsMetadata
|
|
872
|
+
cache_key = getattr(draw_state, "_view_func", None) or view_func
|
|
873
|
+
scope = eval_input_scope(draw_state)
|
|
874
|
+
scope.update(local_vars or {})
|
|
875
|
+
for alias in ("draw_state", "ds", "value", "input_value"):
|
|
876
|
+
scope[alias] = ns.get(alias)
|
|
877
|
+
FuncsMetadata.record(cache_key, scope)
|
|
878
|
+
except Exception:
|
|
879
|
+
pass
|
|
880
|
+
|
|
881
|
+
out, result, error = _run_code(code, ns)
|
|
882
|
+
parts = []
|
|
883
|
+
if out.strip():
|
|
884
|
+
parts.append(out.rstrip())
|
|
885
|
+
if result is not None:
|
|
886
|
+
parts.append("=> " + result)
|
|
887
|
+
if error:
|
|
888
|
+
parts.append(error.rstrip())
|
|
889
|
+
return "\n".join(parts) if parts else "(no output)"
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
# ── Context menu tabs ────────────────────────────────────────────────────────
|
|
893
|
+
# Each tab body is its own render_func. draw_context_menu places one per selected
|
|
894
|
+
# column by calling it with column=t_idx; the tab then owns a single-column
|
|
895
|
+
# region, so the inner views inside it no longer pass column themselves.
|
|
896
|
+
|
|
897
|
+
class _SourceItem(str):
|
|
898
|
+
"""A source name as a dropdown value. `.tint` colors its row and the
|
|
899
|
+
trigger via _dd_obj_tint — yellow marks the source actively driving
|
|
900
|
+
the param."""
|
|
901
|
+
|
|
902
|
+
def __new__(cls, name, tint=None):
|
|
903
|
+
self = str.__new__(cls, name)
|
|
904
|
+
self.tint = tint
|
|
905
|
+
return self
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
_ACTIVE_SRC_TINT = (0.9, 0.8, 0.2)
|
|
909
|
+
|
|
910
|
+
# Multi-key child kwargs carried by the collection dict itself (it's
|
|
911
|
+
# __overrides__ path): the info tab's 'header' group starts collapsed —
|
|
912
|
+
# `initial` only applies on the child's first frames, so the chevron still
|
|
913
|
+
# works afterwards. The dunder key never renders (underscore-skipped).
|
|
914
|
+
_INFO_GROUP_OVERRIDES = {"__header__": {"initial": {"expanded": False}}}
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
from meltygui.core.conversion.render_host import RenderHost
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
class _InstanceAttrSource(dict):
|
|
921
|
+
"""The 'instance attr' source row: the value object's own whitelisted
|
|
922
|
+
params (core_render.OBJ_ATTR_PARAMS — the attrs the wrapper injects, e.g.
|
|
923
|
+
Loras.tint). Reads snapshot at collection time; a write goes straight to
|
|
924
|
+
setattr on the LIVE object — in place and immediate, no code round trip
|
|
925
|
+
(the same storage the instance_attr lens edits). Unlike the host-backed
|
|
926
|
+
sources, a plain setattr triggers NOTHING — so the write also invalidates
|
|
927
|
+
the target view's subtree (the wrapper re-injects the attr on the next
|
|
928
|
+
render, which is also what clears the anywhere in-flight cache)."""
|
|
929
|
+
|
|
930
|
+
def __init__(self, obj, target_ds=None):
|
|
931
|
+
from meltygui.core.core_render import OBJ_ATTR_PARAMS
|
|
932
|
+
super().__init__({p: getattr(obj, p) for p in OBJ_ATTR_PARAMS
|
|
933
|
+
if getattr(obj, p, None) is not None
|
|
934
|
+
and (p != "view_func" or p in getattr(obj, "__dict__", {}))})
|
|
935
|
+
self._obj = obj
|
|
936
|
+
self._target_ds = target_ds
|
|
937
|
+
|
|
938
|
+
def __setitem__(self, k, v):
|
|
939
|
+
setattr(self._obj, k, v)
|
|
940
|
+
super().__setitem__(k, v)
|
|
941
|
+
ds = self._target_ds
|
|
942
|
+
if ds is not None:
|
|
943
|
+
# invalidate_up: the attr (tint) paints the whole subtree's bg -
|
|
944
|
+
# cached middle tiles would blit-skip dirty grandchildren.
|
|
945
|
+
ds.invalidate_up(max_depth=6)
|
|
946
|
+
request_render()
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
class _CodecSource(dict):
|
|
950
|
+
"""The 'codec' source row: the ACTIVE codec's render_kwargs — the
|
|
951
|
+
wrapper's lowest kwargs merge layer and the provenance color (the green
|
|
952
|
+
on import views etc.). The codec rides every draw_state as ds._codec.
|
|
953
|
+
|
|
954
|
+
Writes are PER-FILE when the element resolves to one: the codec stamps
|
|
955
|
+
the attribute into that file's entry in AppModel.file_meta_collection
|
|
956
|
+
(Codec.update_file_meta), which persists with the root save and feeds the
|
|
957
|
+
folder tree's row kwargs. Reads overlay that entry back over the
|
|
958
|
+
codec-wide render_kwargs. Only when no file resolves does a write mutate
|
|
959
|
+
the LIVE class attr in place — immediate but codec-wide and in-memory."""
|
|
960
|
+
|
|
961
|
+
def __init__(self, codec, target_ds):
|
|
962
|
+
rk = getattr(codec, "render_kwargs", None)
|
|
963
|
+
super().__init__(rk if isinstance(rk, dict) else {})
|
|
964
|
+
self._codec = codec
|
|
965
|
+
self._target_ds = target_ds
|
|
966
|
+
# Per-FILE overlay: attributes previously attached to this element's
|
|
967
|
+
# file (Codec.update_file_meta → AppModel.file_meta_collection) read
|
|
968
|
+
# back over the codec-wide render_kwargs, so the row round-trips
|
|
969
|
+
# across saves. `order` is folder-tree bookkeeping, not a param.
|
|
970
|
+
entry = codec.file_meta_entry(target_ds)
|
|
971
|
+
if isinstance(entry, dict):
|
|
972
|
+
self.update({k: v for k, v in entry.items() if k != "order"})
|
|
973
|
+
|
|
974
|
+
def __setitem__(self, k, v):
|
|
975
|
+
# Per-file first: when the element resolves to a file, the attribute
|
|
976
|
+
# belongs to THAT file - the codec stamps it into the file-metadata
|
|
977
|
+
# object (persisted with the root; the folder tree re-applies it as
|
|
978
|
+
# row kwargs). Only when no file resolves does the write fall through to
|
|
979
|
+
# the codec-wide live render_kwargs.
|
|
980
|
+
if not self._codec.update_file_meta(self._target_ds, k, v):
|
|
981
|
+
rk = getattr(self._codec, "render_kwargs", None)
|
|
982
|
+
if not isinstance(rk, dict):
|
|
983
|
+
rk = {}
|
|
984
|
+
self._codec.render_kwargs = rk
|
|
985
|
+
rk[k] = v
|
|
986
|
+
super().__setitem__(k, v)
|
|
987
|
+
self._target_ds.invalidate_up(max_depth=6)
|
|
988
|
+
request_render()
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
class _ChildKwargsSource(dict):
|
|
992
|
+
"""The child_kwargs row when the parent's class-level @defaults doesn't
|
|
993
|
+
(yet) carry child_kwargs in SOURCE: displays the live dict, and the first
|
|
994
|
+
write lazily creates `child_kwargs={...}` inside the class-level defaults
|
|
995
|
+
PARSE — a non-dunder key, so plain bubbling item-writes wrap the new dict
|
|
996
|
+
and dirty the host (no __overrides__-style special casing needed). The
|
|
997
|
+
save + the parent-class recompile stamp then make it code."""
|
|
998
|
+
|
|
999
|
+
def __init__(self, defaults_parse, live):
|
|
1000
|
+
super().__init__({k: v for k, v in (live or {}).items()
|
|
1001
|
+
if isinstance(k, str) and not k.startswith("__")})
|
|
1002
|
+
self._dp = defaults_parse
|
|
1003
|
+
|
|
1004
|
+
def __setitem__(self, k, v):
|
|
1005
|
+
ck = self._dp.get("child_kwargs")
|
|
1006
|
+
if not isinstance(ck, dict):
|
|
1007
|
+
self._dp["child_kwargs"] = {} # bubbling: wraps + dirties
|
|
1008
|
+
ck = self._dp["child_kwargs"]
|
|
1009
|
+
ck[k] = v
|
|
1010
|
+
super().__setitem__(k, v)
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
class _DrawStateAttrSource(dict):
|
|
1014
|
+
"""The 'draw state' source row: whitelisted params read from the target
|
|
1015
|
+
draw_state's own fields (ds.tint — the style cascade's last fallback,
|
|
1016
|
+
persisted with window state). The DEFAULT source: SourcePriority ranks it
|
|
1017
|
+
last, so it only ever drives when nothing else sets the param. Writes go
|
|
1018
|
+
setattr-on-the-ds, in place, plus the same subtree invalidation the
|
|
1019
|
+
instance adapter does."""
|
|
1020
|
+
|
|
1021
|
+
def __init__(self, target_ds):
|
|
1022
|
+
from meltygui.core.core_render import OBJ_ATTR_PARAMS
|
|
1023
|
+
super().__init__({p: getattr(target_ds, p) for p in OBJ_ATTR_PARAMS
|
|
1024
|
+
if getattr(target_ds, p, None) is not None})
|
|
1025
|
+
self._target_ds = target_ds
|
|
1026
|
+
if "view_func" in target_ds.auto_params:
|
|
1027
|
+
dict.__setitem__(self, "view_func", target_ds.auto_params["view_func"])
|
|
1028
|
+
|
|
1029
|
+
def __setitem__(self, k, v):
|
|
1030
|
+
if k == "view_func":
|
|
1031
|
+
self._target_ds.auto_params[k] = v
|
|
1032
|
+
else:
|
|
1033
|
+
setattr(self._target_ds, k, v)
|
|
1034
|
+
super().__setitem__(k, v)
|
|
1035
|
+
self._target_ds.invalidate_up(max_depth=6)
|
|
1036
|
+
request_render()
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
class _LazyOverrideEntry(dict):
|
|
1040
|
+
"""Stand-in '# [<key>]' source for a site with NO override comment yet.
|
|
1041
|
+
Reads as the empty entry dict; the FIRST write (the matrix's + button)
|
|
1042
|
+
materializes __overrides__['__<key>__'] in the owning parse and the save
|
|
1043
|
+
synthesizes the comment line (_patch_leading_override's creation branch).
|
|
1044
|
+
On the next walk the real entry exists and registers instead.
|
|
1045
|
+
|
|
1046
|
+
Bubbling treats `__…` keys as internal bookkeeping — writes to them are
|
|
1047
|
+
stored RAW (no wrap, no dirty mark), which is exactly right for parse
|
|
1048
|
+
bookkeeping and exactly wrong here: naive creation leaves plain dicts the
|
|
1049
|
+
host never hears about (tint shows — the marker reads the same tree — but
|
|
1050
|
+
nothing saves). So the new structure is installed onto the host's bubble
|
|
1051
|
+
root explicitly, and the LEAF write goes through the wrapped entry, whose
|
|
1052
|
+
non-internal key fires the standard notify → dirty → save path."""
|
|
1053
|
+
|
|
1054
|
+
def __init__(self, root, entry_key=None):
|
|
1055
|
+
# entry_key=None targets the root node's OWN __overrides__ - a
|
|
1056
|
+
# leading comment above a class/function def - instead of a
|
|
1057
|
+
# '__<key>__' field slot on its parent.
|
|
1058
|
+
super().__init__()
|
|
1059
|
+
self._root = root
|
|
1060
|
+
self._entry_key = entry_key
|
|
1061
|
+
|
|
1062
|
+
def __setitem__(self, k, v):
|
|
1063
|
+
from meltygui.core.conversion.bubbling import install_bubbling
|
|
1064
|
+
root_node = self._root
|
|
1065
|
+
ovs = root_node.get("__overrides__")
|
|
1066
|
+
if not isinstance(ovs, dict):
|
|
1067
|
+
ovs = {}
|
|
1068
|
+
if self._entry_key is not None and not isinstance(ovs.get(self._entry_key), dict):
|
|
1069
|
+
ovs[self._entry_key] = {}
|
|
1070
|
+
broot = getattr(root_node, "_bubble_root", None)
|
|
1071
|
+
if broot is not None:
|
|
1072
|
+
# Plain dicts can't reclass - install returns the wrapped copy,
|
|
1073
|
+
# so store THAT (raw store: internal key). Idempotent if ovs
|
|
1074
|
+
# already bubbles.
|
|
1075
|
+
ovs = install_bubbling(ovs, broot)
|
|
1076
|
+
root_node["__overrides__"] = ovs
|
|
1077
|
+
entry = ovs if self._entry_key is None else ovs[self._entry_key]
|
|
1078
|
+
entry[k] = v # non-internal key on the wrapped entry → notify → dirty
|
|
1079
|
+
super().__setitem__(k, v) # same-frame reads (row refresh) see it too
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def collect_input_sources(input_value, cm_state, class_to_show=None):
|
|
1083
|
+
"""Every editable input source behind a view, collected WITHOUT drawing —
|
|
1084
|
+
the shared engine of draw_input_tab and set_anywhere. `input_value` is the
|
|
1085
|
+
TARGET draw_state; `cm_state` caches the code hosts across calls (pass the
|
|
1086
|
+
context menu's, or any per-target instance). Returns a dict:
|
|
1087
|
+
sources {source_name: parse dict} (placeholder {} when unparsed)
|
|
1088
|
+
tints {source_name: codec tint}
|
|
1089
|
+
locations {source_name: (file, line)} for jump buttons
|
|
1090
|
+
kinds {source_name: kind caption} ("signature" / "caller +N" / ...)
|
|
1091
|
+
writable source names whose dict is a REAL parse node (writes save)
|
|
1092
|
+
comment_owners {source_name: draw_state} separates inherited parameter
|
|
1093
|
+
sources from the target window’s own lifecycle overrides
|
|
1094
|
+
"""
|
|
1095
|
+
# Per-frame memo: the input tab plus the tint tab's get_sources_for /
|
|
1096
|
+
# from_anywhere / set_anywhere all this for the same target within one
|
|
1097
|
+
# frame, and the parses can't change mid-frame - build once per
|
|
1098
|
+
# (frame, class_to_show) per cm_state. Profiled at avg 2.5ms / max 12.7ms
|
|
1099
|
+
# per build; tripling it per frame was the input-tab drag fps drop.
|
|
1100
|
+
_memo_key = (Core.melty.frame_count, class_to_show)
|
|
1101
|
+
if getattr(cm_state, "_collect_key", None) == _memo_key:
|
|
1102
|
+
return cm_state._collect_cache
|
|
1103
|
+
|
|
1104
|
+
# Source/cst hosts come from the process-wide code-host cache, keyed by the
|
|
1105
|
+
# live reference - every menu opened on the same render_func/class/call
|
|
1106
|
+
# site shares ONE host pair, so the code isn't re-loaded and re-parsed per
|
|
1107
|
+
# open (code_hosts_for in new_converters; earlier like this tab used to
|
|
1108
|
+
# build inline).
|
|
1109
|
+
host_key = (input_value._view_func, class_to_show)
|
|
1110
|
+
if cm_state.host_key != host_key:
|
|
1111
|
+
cm_state.host_key = host_key
|
|
1112
|
+
cm_state.render_func_str, cm_state.render_func_dict = code_hosts_for(input_value._view_func)
|
|
1113
|
+
cm_state.class_str, cm_state.class_dict = code_hosts_for(class_to_show)
|
|
1114
|
+
# The nav retargeted us at a different view; its call sites differ (and
|
|
1115
|
+
# may not be captured yet — see the lazy capture in draw_context_menu).
|
|
1116
|
+
cm_state.call_site_hosts = []
|
|
1117
|
+
cm_state.call_site_keys = None
|
|
1118
|
+
# The class's source location feeds the class-var/class-default jump
|
|
1119
|
+
# buttons. inspect.getsourcelines() on a CLASS AST-parses the ENTIRE
|
|
1120
|
+
# module file (CPython 3.9+ _ClassFinder) - ~34ms on a 7k-line file like
|
|
1121
|
+
# libcst_conversion.py (IntParse, the parent of a parsed int field) -
|
|
1122
|
+
# so it CANNOT run per frame. class_to_show is part of host_key, so the
|
|
1123
|
+
# location only changes on a rebuild: read it once, here.
|
|
1124
|
+
cm_state.class_loc = None
|
|
1125
|
+
if isinstance(class_to_show, type):
|
|
1126
|
+
try:
|
|
1127
|
+
cm_state.class_loc = (inspect.getsourcefile(class_to_show),
|
|
1128
|
+
inspect.getsourcelines(class_to_show)[1])
|
|
1129
|
+
except (TypeError, OSError):
|
|
1130
|
+
cm_state.class_loc = None
|
|
1131
|
+
|
|
1132
|
+
decoration_func = input_value._kwargs.get("_view_func_origin", input_value._view_func)
|
|
1133
|
+
if getattr(cm_state, "decoration_key", None) is not decoration_func:
|
|
1134
|
+
cm_state.decoration_key = decoration_func
|
|
1135
|
+
cm_state.decoration_str, cm_state.decoration_dict = code_hosts_for(decoration_func)
|
|
1136
|
+
|
|
1137
|
+
# The caller CHAIN - the direct caller, its caller, ... up to
|
|
1138
|
+
# Toggles.caller_walk_steps real (non-dispatch) frames, innermost-first.
|
|
1139
|
+
# Read off the cached _call_stack (never the live stack: a drag re-renders
|
|
1140
|
+
# with parents skipped, which would wipe every host). The stack can lag a
|
|
1141
|
+
# frame or two AFTER retargeting (lazy one-shot capture on the wrapper's
|
|
1142
|
+
# next render), so recompute every pass and rebuild the hosts only when the
|
|
1143
|
+
# resolved sites change.
|
|
1144
|
+
from meltygui.code.chain_converters import caller_chain
|
|
1145
|
+
walk_steps = max(1, int(Toggles.caller_walk_steps or 1))
|
|
1146
|
+
caller_frames = caller_chain(getattr(input_value, "_call_stack", None))[:walk_steps]
|
|
1147
|
+
caller_site_keys = tuple((f, ln) for f, ln, _ in caller_frames)
|
|
1148
|
+
# getattr: a cm_state created before this field existed (hotswap of an
|
|
1149
|
+
# already-open inputs tab) lacks caller_site_keys - treat as "needs rebuild".
|
|
1150
|
+
if caller_site_keys != getattr(cm_state, "call_site_keys", None):
|
|
1151
|
+
cm_state.call_site_keys = caller_site_keys
|
|
1152
|
+
cm_state.call_site_hosts = [code_hosts_for(CallSite(f, ln))
|
|
1153
|
+
for f, ln in caller_site_keys]
|
|
1154
|
+
|
|
1155
|
+
# The ACTIVE mode driving this view - the wrapper stamps it into the
|
|
1156
|
+
# view's kwargs when a mode config matches (kwargs['current_mode'],
|
|
1157
|
+
# core_render; 'mode' for the recursive variant), so it rides on the
|
|
1158
|
+
# target's _kwargs. The host is for the mode's ENUM CLASS (whose source
|
|
1159
|
+
# holds every member), keyed per class so retargeting at a view under a
|
|
1160
|
+
# different mode enum rebuilds; which member to show is re-read each pass.
|
|
1161
|
+
current_mode = input_value._kwargs.get('current_mode')
|
|
1162
|
+
mode_cls = current_mode.__class__ if current_mode is not None else None
|
|
1163
|
+
if cm_state.mode_key != mode_cls:
|
|
1164
|
+
cm_state.mode_key = mode_cls
|
|
1165
|
+
cm_state.mode_str, cm_state.mode_dict = (
|
|
1166
|
+
code_hosts_for(mode_cls) if mode_cls is not None else (None, None))
|
|
1167
|
+
|
|
1168
|
+
# ── Every input source, one parameter at a time ──────────────────────────
|
|
1169
|
+
# Collect each parsed source dict, match against the render function's
|
|
1170
|
+
# parameter list via param_source_matrix, and hand the matrix to
|
|
1171
|
+
# draw_param_matrix: a per-parameter SCREEN (dropdown within params)
|
|
1172
|
+
# listing every possible source - param default, caller, mode, class
|
|
1173
|
+
# default, function decoration - set or not. Sources register here even
|
|
1174
|
+
# when empty/unparsed (placeholder {}), so each screen always shows the
|
|
1175
|
+
# full source list; registration order is the screens' row order.
|
|
1176
|
+
# Rebuilt every frame from the live parses (cheap dict copy), so a
|
|
1177
|
+
# background parse landing or an edit in any source shows up immediately.
|
|
1178
|
+
# Edits to a cell write back into the SOURCE dict they came from
|
|
1179
|
+
# (apply_param_source_changes) - those dicts are the hosts'
|
|
1180
|
+
# bubbling-wrapped parse nodes, so the edit marks the owning host dirty
|
|
1181
|
+
# and rides its normal chain_out/save path. Placeholder rows are plain
|
|
1182
|
+
# empty dicts: they never grow cells, so no write-back can land in them.
|
|
1183
|
+
# Each source maps to the CODEC that owns its data - the codec's
|
|
1184
|
+
# render_kwargs tint IS the source color. The matrix cells are parse
|
|
1185
|
+
# fragments that can't adopt it naturally (type-based codec fragments), so
|
|
1186
|
+
# the tint map rides into draw_param_matrix, which applies it manually
|
|
1187
|
+
# and prominently per row.
|
|
1188
|
+
from meltygui.code.new_codecs import FunctionCodec
|
|
1189
|
+
from meltygui.code.new_codecs import CallerCodec
|
|
1190
|
+
from meltygui.code.new_codecs import DecorationsCodec
|
|
1191
|
+
from meltygui.code.new_codecs import TypeCodec
|
|
1192
|
+
from meltygui.code.new_codecs import ModeCodec
|
|
1193
|
+
|
|
1194
|
+
def _codec_tint(codec):
|
|
1195
|
+
return (getattr(codec, "render_kwargs", None) or {}).get("tint")
|
|
1196
|
+
|
|
1197
|
+
sources = {}
|
|
1198
|
+
source_tints = {}
|
|
1199
|
+
source_locations = {}
|
|
1200
|
+
source_kinds = {}
|
|
1201
|
+
comment_owners = {}
|
|
1202
|
+
writable_sources = []
|
|
1203
|
+
|
|
1204
|
+
def _add_source(sname, sdict, codec, location=None, kind=None, owner=None):
|
|
1205
|
+
# Register even when the source sets nothing or hasn't parsed yet -
|
|
1206
|
+
# the per-param screen draws EVERY source row, absent ones as "not
|
|
1207
|
+
# set". The placeholder is a fresh empty dict, never written to;
|
|
1208
|
+
# only REAL parse dicts (even empty ones) are writable, so the
|
|
1209
|
+
# matrix's +/× buttons know where a stamped cell can legally land.
|
|
1210
|
+
# `kind` is the row's caption (signature / caller / mode / ...);
|
|
1211
|
+
# sname stays the concrete spelling (def draw_x / Mode.WINDOW / ...).
|
|
1212
|
+
if isinstance(sdict, dict):
|
|
1213
|
+
writable_sources.append(sname)
|
|
1214
|
+
sources[sname] = sdict if isinstance(sdict, dict) else {}
|
|
1215
|
+
source_tints[sname] = _codec_tint(codec)
|
|
1216
|
+
if kind:
|
|
1217
|
+
source_kinds[sname] = kind
|
|
1218
|
+
if kind == "code comment":
|
|
1219
|
+
comment_owners[sname] = input_value if owner is None else owner
|
|
1220
|
+
if location is not None and location[0] is not None:
|
|
1221
|
+
source_locations[sname] = location
|
|
1222
|
+
|
|
1223
|
+
# Source names are the concrete spelling shown on the row button
|
|
1224
|
+
# (def draw_x / Mode.WINDOW / @render_func(draw_x), ...); the generic
|
|
1225
|
+
# origin goes into `kind`, shown as a caption above the button. Locations
|
|
1226
|
+
# feed the jump-to buttons.
|
|
1227
|
+
view_fn = inspect.unwrap(input_value._view_func)
|
|
1228
|
+
fn_name = getattr(view_fn, "__name__", "?")
|
|
1229
|
+
try:
|
|
1230
|
+
fn_file = inspect.getsourcefile(view_fn)
|
|
1231
|
+
except TypeError:
|
|
1232
|
+
fn_file = None
|
|
1233
|
+
fn_loc = (fn_file, getattr(getattr(view_fn, "__code__", None),
|
|
1234
|
+
"co_firstlineno", None))
|
|
1235
|
+
|
|
1236
|
+
# cls_name is cheap (__name__); cls_loc is cached on host key change above
|
|
1237
|
+
# (inspect.getsourcelines AST-parses the whole module file - never per frame).
|
|
1238
|
+
cls_name = class_to_show.__name__ if isinstance(class_to_show, type) else "None"
|
|
1239
|
+
# getattr: a cm_state created before this field existed (hotswap of an
|
|
1240
|
+
# already-open inputs tab) lacks the field - None just degrades the jump
|
|
1241
|
+
# location until the menu retargets and the host_key block recomputes it.
|
|
1242
|
+
cls_loc = getattr(cm_state, "class_loc", None)
|
|
1243
|
+
|
|
1244
|
+
# One caller source per real frame walked (caller, caller's caller, ...),
|
|
1245
|
+
# innermost-first; each its own editable CallSite host parsed above. The
|
|
1246
|
+
# captions read "caller", "caller +1", ...; the row button shows the
|
|
1247
|
+
# concrete frame name (disambiguated with a ^N suffix when two frames share a
|
|
1248
|
+
# name, since source names are dict keys). If the stack hasn't been
|
|
1249
|
+
# captured yet (no real callers), still register one empty "caller"
|
|
1250
|
+
# row so the source-list shape stays consistent.
|
|
1251
|
+
caller_rows = [] # (sname, sdict, location, kind)
|
|
1252
|
+
_seen_caller_names = set()
|
|
1253
|
+
for i, ((_str_host, dict_host), (filename, lineno, func_name)) in enumerate(
|
|
1254
|
+
zip(cm_state.call_site_hosts, caller_frames)):
|
|
1255
|
+
cdict = dict_host.deep.unwrap() if dict_host else None
|
|
1256
|
+
cname = func_name or "caller"
|
|
1257
|
+
if cname in _seen_caller_names:
|
|
1258
|
+
cname = f"{cname} ^{i}"
|
|
1259
|
+
_seen_caller_names.add(cname)
|
|
1260
|
+
kind = "caller" if i == 0 else f"caller +{i}"
|
|
1261
|
+
caller_rows.append((cname, cdict, (filename, lineno), kind))
|
|
1262
|
+
if not caller_rows:
|
|
1263
|
+
caller_rows.append(("caller", None, None, "caller"))
|
|
1264
|
+
|
|
1265
|
+
# MODE - the active mode is entry in its enum class source (e.g.
|
|
1266
|
+
# `NEW_CODE = {types...: ModeOverrides(kwargs={...})}` in mode.py), not as
|
|
1267
|
+
# the kwargs dict that entry stamps into this view. A mode can hold
|
|
1268
|
+
# SEVERAL type-keyed entries (TEXT_ONLY); the parse can't be type-matched
|
|
1269
|
+
# (its keys are unevaluated source), so pick the candidate whose keys best
|
|
1270
|
+
# overlap the LIVE matched config (get_config_for) - the entry that
|
|
1271
|
+
# actually drove THIS view. Edits merge into the mode_dict host's parse
|
|
1272
|
+
# and ride its normal chain_out/save back into the enum's source file.
|
|
1273
|
+
mode_label = str(current_mode) if current_mode is not None else "None"
|
|
1274
|
+
mode_kwargs, mode_loc = None, None
|
|
1275
|
+
if current_mode is not None and cm_state.mode_dict is not None:
|
|
1276
|
+
candidates = [c for c in
|
|
1277
|
+
cm_state.mode_dict.deep[current_mode.name].kwargs.all()
|
|
1278
|
+
if isinstance(c, dict)]
|
|
1279
|
+
live_keys = set()
|
|
1280
|
+
if isinstance(getattr(current_mode, "value", None), dict):
|
|
1281
|
+
live_cfg = current_mode.get_config_for(input_value._raw_input_value)
|
|
1282
|
+
if live_cfg is not None and live_cfg.kwargs:
|
|
1283
|
+
live_keys = set(live_cfg.kwargs)
|
|
1284
|
+
mode_kwargs = max(candidates,
|
|
1285
|
+
key=lambda c: len(live_keys & set(c)), default=None)
|
|
1286
|
+
# inspect.getsourcelines on a class AST-parses its WHOLE module (the
|
|
1287
|
+
# class_loc lesson) - cache the member's location per (class, member),
|
|
1288
|
+
# never recompute per frame.
|
|
1289
|
+
_ml_key = (mode_cls, current_mode.name)
|
|
1290
|
+
if getattr(cm_state, "_mode_loc_key", None) != _ml_key:
|
|
1291
|
+
cm_state._mode_loc_key = _ml_key
|
|
1292
|
+
cm_state._mode_loc = None
|
|
1293
|
+
try:
|
|
1294
|
+
cls_lines, cls_start = inspect.getsourcelines(mode_cls)
|
|
1295
|
+
member_off = next(
|
|
1296
|
+
(i for i, l in enumerate(cls_lines)
|
|
1297
|
+
if l.lstrip().startswith((f"{current_mode.name} =",
|
|
1298
|
+
f"{current_mode.name}="))), 0)
|
|
1299
|
+
cm_state._mode_loc = (inspect.getsourcefile(mode_cls),
|
|
1300
|
+
cls_start + member_off)
|
|
1301
|
+
except (TypeError, OSError):
|
|
1302
|
+
pass
|
|
1303
|
+
mode_loc = cm_state._mode_loc
|
|
1304
|
+
|
|
1305
|
+
# Registration order IS the per-param screen's row order: param default
|
|
1306
|
+
# (signature), caller, mode, class var, class default, function decoration.
|
|
1307
|
+
_add_source(f"def {fn_name}", cm_state.render_func_dict.deep.parameters(), FunctionCodec,
|
|
1308
|
+
location=fn_loc, kind="signature")
|
|
1309
|
+
for cname, cdict, cloc, ckind in caller_rows:
|
|
1310
|
+
from meltygui.core.input.view_selection import CallerViewSource
|
|
1311
|
+
if isinstance(cdict, dict):
|
|
1312
|
+
cdict = CallerViewSource(cdict, cloc[0] if cloc else None, allow_direct=ckind == "caller")
|
|
1313
|
+
_add_source(cname, cdict, CallerCodec, location=cloc, kind=ckind)
|
|
1314
|
+
_add_source(mode_label, mode_kwargs, ModeCodec, location=mode_loc, kind="mode")
|
|
1315
|
+
# CLASS VAR - the data class's body assignments (`tint = (...)` on the
|
|
1316
|
+
# class itself). The class parse IS that dict (fields + __cst__/comment
|
|
1317
|
+
# bookkeeping, filtered out by param_source_matrix), so a + here writes a
|
|
1318
|
+
# brand-new class var assignment and × removes one, via the same
|
|
1319
|
+
# bubbling/save path as every other source.
|
|
1320
|
+
_add_source(f"class {cls_name}",
|
|
1321
|
+
cm_state.class_dict.deep.unwrap() if cm_state.class_dict else None,
|
|
1322
|
+
TypeCodec, location=cls_loc, kind="class var")
|
|
1323
|
+
_add_source(f"@defaults({cls_name})", cm_state.class_dict.deep.decorators.defaults(),
|
|
1324
|
+
TypeCodec, location=cls_loc, kind="class default")
|
|
1325
|
+
# INSTANCE ATTR - the value instance's own whitelisted params (Lamp.tint):
|
|
1326
|
+
# what core_render's OBJ_ATTR_PARAMS injection reads. Skip-when-absent
|
|
1327
|
+
# like the other value-side rows; dicts/classes carry their config in
|
|
1328
|
+
# __overrides__ / class rows above.
|
|
1329
|
+
_raw_obj = getattr(input_value, "_raw_input_value", None)
|
|
1330
|
+
if _raw_obj is not None and not isinstance(_raw_obj, (dict, list, type)):
|
|
1331
|
+
_ia = _InstanceAttrSource(_raw_obj, target_ds=input_value)
|
|
1332
|
+
if _ia:
|
|
1333
|
+
_add_source(f"{type(_raw_obj).__name__} instance", _ia,
|
|
1334
|
+
TypeCodec, kind="instance attr")
|
|
1335
|
+
# CHILD KWARGS - a parent view can drive this view's params via
|
|
1336
|
+
# child_kwargs={...} (draw_collection merges it into every child call).
|
|
1337
|
+
# The dict itself can be attached to ANY of the PARENT'S own sources (its
|
|
1338
|
+
# caller, decorator, comment, instance attr...), so resolve it with the
|
|
1339
|
+
# registry machinery ON THE PARENT: from_anywhere("child_kwargs",
|
|
1340
|
+
# parent). Cheap gatekeeping: only parents whose live _kwargs actually
|
|
1341
|
+
# carry a child_kwargs dict pay the (per-frame-memoized) parent
|
|
1342
|
+
# check; the root's self-parent loop is excluded. Recursion up the
|
|
1343
|
+
# ancestry terminates the same way: it only walks through parents that
|
|
1344
|
+
# themselves receive child_kwargs.
|
|
1345
|
+
_pds_ck = getattr(input_value, "_parent", None)
|
|
1346
|
+
if _pds_ck is not None and _pds_ck is not input_value:
|
|
1347
|
+
_my_key = (input_value._kwargs or {}).get("key")
|
|
1348
|
+
_ck_live = (_pds_ck._kwargs or {}).get("child_kwargs")
|
|
1349
|
+
_ck_live = _ck_live if isinstance(_ck_live, dict) and _ck_live else None
|
|
1350
|
+
_mode_src = None # the parent's mode source name, set below
|
|
1351
|
+
if _ck_live is not None or isinstance(_my_key, str):
|
|
1352
|
+
# Ensure the PARENT's registry exists (fresh sessions have no
|
|
1353
|
+
# _sa_cm_state until something collects it) - memoized per frame,
|
|
1354
|
+
# and this path only runs when a menu/tab is open on a child.
|
|
1355
|
+
_psrcs = _sources_for(_pds_ck)
|
|
1356
|
+
_pcm = getattr(_pds_ck, "_sa_cm_state", None)
|
|
1357
|
+
_pdeco = (_pcm.class_dict.deep.decorators()
|
|
1358
|
+
if _pcm is not None and _pcm.class_dict is not None else None)
|
|
1359
|
+
_pcls = (_pcm.host_key or (None, None))[1] if _pcm is not None else None
|
|
1360
|
+
_pcls_name = getattr(_pcls, "__name__", "?")
|
|
1361
|
+
_cls_defaults = None # first non-attr @defaults parse entry
|
|
1362
|
+
if isinstance(_pdeco, dict):
|
|
1363
|
+
for _dk, _dv in _pdeco.items():
|
|
1364
|
+
if not (isinstance(_dk, str) and _dk.split("#", 1)[0] == "defaults"
|
|
1365
|
+
and isinstance(_dv, dict)):
|
|
1366
|
+
continue
|
|
1367
|
+
_dattr = _dv.get("attr") or _dv.get("attrib")
|
|
1368
|
+
if _dattr is None:
|
|
1369
|
+
if _cls_defaults is None:
|
|
1370
|
+
_cls_defaults = _dv
|
|
1371
|
+
elif str(_dattr).strip("'\"") == _my_key:
|
|
1372
|
+
# ATTR-TARGETED @defaults(attr="<this field>", ...):
|
|
1373
|
+
# its own source row on the field's view.
|
|
1374
|
+
_add_source(f"@defaults({_pcls_name}.{_my_key})", _dv,
|
|
1375
|
+
TypeCodec, kind="attr default")
|
|
1376
|
+
# CHILD KWARGS (MODE) - the parent's MODE entry can carry its own
|
|
1377
|
+
# child_kwargs={...} (Modes.NEW_CODE stamps column_widths on every
|
|
1378
|
+
# child). It reaches this view through the same merge as any other
|
|
1379
|
+
# child_kwargs, but it lives in the mode ENUM's source, so it gets
|
|
1380
|
+
# its own row: an edit here writes mode.py, not the parent's
|
|
1381
|
+
# caller/@defaults. Absent a lazy-create adapter, but the first
|
|
1382
|
+
# write stamps `child_kwargs={...}` onto the mode entry's kwargs
|
|
1383
|
+
# parse (bubbling wraps + dirties; the mode host's code persists).
|
|
1384
|
+
_mode_src = next((s for s, k in _psrcs["kinds"].items()
|
|
1385
|
+
if k == "mode"), None)
|
|
1386
|
+
_mode_parse = _psrcs["sources"].get(_mode_src) if _mode_src else None
|
|
1387
|
+
if isinstance(_mode_parse, dict) and _mode_src in set(_psrcs["writable"]):
|
|
1388
|
+
_mck = _mode_parse.get("child_kwargs")
|
|
1389
|
+
if not isinstance(_mck, dict):
|
|
1390
|
+
_mck = _ChildKwargsSource(_mode_parse, None)
|
|
1391
|
+
_add_source("child_kwargs (mode)", _mck, ModeCodec,
|
|
1392
|
+
location=_psrcs["locations"].get(_mode_src),
|
|
1393
|
+
kind="mode child kwargs")
|
|
1394
|
+
if _ck_live is not None:
|
|
1395
|
+
# CHILD KWARGS — the dict driving this view from the parent.
|
|
1396
|
+
# Prefer the CODE-backed setter (the first source DRIVING
|
|
1397
|
+
# child_kwargs - from_anywhere's pick, inlined so the same target
|
|
1398
|
+
# also yields the row's jump location: the caller line /
|
|
1399
|
+
# @defaults class line the dict lives at). If no source sets it
|
|
1400
|
+
# yet, a lazy-create adapter over the class-level @defaults dict
|
|
1401
|
+
# makes the first write EDIT CODE (the live dict alone silently
|
|
1402
|
+
# kept writes in memory).
|
|
1403
|
+
# The MODE setter has its own row above (it outranks any other
|
|
1404
|
+
# parent sources, so it would otherwise always BE this row and the
|
|
1405
|
+
# non-mode setters would never be visible/editable): resolve this
|
|
1406
|
+
# row from the parent's sources MINUS mode.
|
|
1407
|
+
_psrcs_nm = _psrcs
|
|
1408
|
+
if _mode_src is not None:
|
|
1409
|
+
_psrcs_nm = dict(_psrcs, sources={k: v for k, v in _psrcs["sources"].items()
|
|
1410
|
+
if k != _mode_src})
|
|
1411
|
+
_ck_target = _driving_source(_psrcs_nm, "child_kwargs")
|
|
1412
|
+
_ck_dict = (_psrcs_nm["sources"][_ck_target].get("child_kwargs")
|
|
1413
|
+
if _ck_target is not None else None)
|
|
1414
|
+
_ck_loc = (_psrcs["locations"].get(_ck_target)
|
|
1415
|
+
if _ck_target is not None else None)
|
|
1416
|
+
if not (isinstance(_ck_dict, dict) and _ck_dict):
|
|
1417
|
+
_ck_dict = (_ChildKwargsSource(_cls_defaults, _ck_live)
|
|
1418
|
+
if isinstance(_cls_defaults, dict) else _ck_live)
|
|
1419
|
+
_ck_loc = getattr(_pcm, "class_loc", None) if _pcm is not None else None
|
|
1420
|
+
_add_source("child_kwargs", _ck_dict, TypeCodec,
|
|
1421
|
+
location=_ck_loc, kind="child kwargs")
|
|
1422
|
+
# CODEC - the active codec's render_kwargs (ds._codec, stashed by the
|
|
1423
|
+
# wrapper for every view): the lowest kwargs merge layer and the
|
|
1424
|
+
# provenance color (import views' green). Skip-when-absent like the
|
|
1425
|
+
# other value-side rows.
|
|
1426
|
+
_codec_obj = getattr(input_value, "_codec", None)
|
|
1427
|
+
if _codec_obj is not None:
|
|
1428
|
+
_cs = _CodecSource(_codec_obj, input_value)
|
|
1429
|
+
if _cs:
|
|
1430
|
+
_add_source(f"codec {getattr(_codec_obj, '__name__', type(_codec_obj).__name__)}",
|
|
1431
|
+
_cs, TypeCodec, kind="codec")
|
|
1432
|
+
# DRAW STATE - the ds's own kwargs (ds.tint): the lowest source, ranked
|
|
1433
|
+
# last, so windows tinted only by their persisted draw_state (the "blue
|
|
1434
|
+
# window no other claims" case) still resolve to a real, writable row.
|
|
1435
|
+
_dsa = _DrawStateAttrSource(input_value)
|
|
1436
|
+
if _dsa:
|
|
1437
|
+
_add_source("draw_state", _dsa, TypeCodec, kind="draw state")
|
|
1438
|
+
|
|
1439
|
+
# ── Sources parsed off the VALUE itself ──────────────────────────────────
|
|
1440
|
+
# The value flowing through the view can be (or sit inside) a parse node
|
|
1441
|
+
# of the owning window's's bubbling tree (e.g. a nested ClassParse in
|
|
1442
|
+
# the Toggles window). Kwargs carried by that parse are live inputs the
|
|
1443
|
+
# class_to_show rows (keyed on the value's runtime TYPE) miss, and since
|
|
1444
|
+
# the dicts are bubbling trees, a cell edit marks the host dirty and saves
|
|
1445
|
+
# through its normal path - no extra save need. Walk a few parents so a
|
|
1446
|
+
# primitive leaf (scroll_speed) still finds its owning class parse, same
|
|
1447
|
+
# as the class_to_show walk. Two such sources per parse:
|
|
1448
|
+
# @defaults(<name>) the parse's decorators.defaults dict - the wrapper
|
|
1449
|
+
# reads it directly (core_render's
|
|
1450
|
+
# input_value["decorators"] tint path)
|
|
1451
|
+
# # [<name>] the '# [tint=(...)]' override comment on the
|
|
1452
|
+
# value's source line: a leaf child carries it in
|
|
1453
|
+
# __overrides__; a primitive field's comment lives on
|
|
1454
|
+
# the PARENT parse under __<key>__ (fed to the
|
|
1455
|
+
# child's kwargs by draw_collection). Saves update
|
|
1456
|
+
# the comment via _patch_leading_override /
|
|
1457
|
+
# _patch_field_overrides.
|
|
1458
|
+
# Both skip when absent (same rule as @window below) - a placeholder row
|
|
1459
|
+
# on every parsed value would be permanent noise.
|
|
1460
|
+
def _root_file_loc(ds, span):
|
|
1461
|
+
# Map a span (relative to the root host parse's source) to a file
|
|
1462
|
+
# location: nested parses carry no file_path, so find the ancestor
|
|
1463
|
+
# parse that has file_path + line_offset. The window ds holds it as
|
|
1464
|
+
# _input_value (post-convert), so check both. None → no jump location.
|
|
1465
|
+
while span is not None and ds is not None:
|
|
1466
|
+
for _r in (getattr(ds, "_raw_input_value", None),
|
|
1467
|
+
getattr(ds, "_input_value", None)):
|
|
1468
|
+
if isinstance(_r, GeneralParse) and getattr(_r, "file_path", None):
|
|
1469
|
+
return (str(_r.file_path), _r.line_offset + span.start_line)
|
|
1470
|
+
if ds._parent is ds:
|
|
1471
|
+
break
|
|
1472
|
+
ds = ds._parent
|
|
1473
|
+
return None
|
|
1474
|
+
|
|
1475
|
+
# Explicit collection/key threading is always a comment source, even when
|
|
1476
|
+
# no ancestor view renders that value (standalone draw_text calls).
|
|
1477
|
+
slot_collection = input_value._kwargs.get("collection")
|
|
1478
|
+
slot_key = input_value._kwargs.get("key")
|
|
1479
|
+
if isinstance(slot_collection, dict) and isinstance(slot_key, str):
|
|
1480
|
+
overrides = slot_collection.get("__overrides__", {})
|
|
1481
|
+
entry = overrides.get(f"__{slot_key}__") if isinstance(overrides, dict) else None
|
|
1482
|
+
if isinstance(entry, dict) or isinstance(slot_collection, GeneralParse):
|
|
1483
|
+
if not isinstance(entry, dict):
|
|
1484
|
+
entry = _LazyOverrideEntry(slot_collection, f"__{slot_key}__")
|
|
1485
|
+
_add_source(f"# [{slot_key}]", entry, TypeCodec, kind="code comment")
|
|
1486
|
+
own_value = input_value._raw_input_value
|
|
1487
|
+
if isinstance(own_value, dict) and not isinstance(own_value, GeneralParse):
|
|
1488
|
+
overrides = own_value.get("__overrides__")
|
|
1489
|
+
if isinstance(overrides, dict):
|
|
1490
|
+
_add_source("# [value]", overrides, TypeCodec, kind="code comment")
|
|
1491
|
+
|
|
1492
|
+
_pds, _pwalk = input_value, 4
|
|
1493
|
+
while _pds is not None and _pwalk >= 0:
|
|
1494
|
+
_praw = getattr(_pds, "_raw_input_value", None)
|
|
1495
|
+
if isinstance(_praw, GeneralParse):
|
|
1496
|
+
_pdecos = _praw.get("decorators")
|
|
1497
|
+
_pdefaults = _pdecos.get("defaults") if isinstance(_pdecos, dict) else None
|
|
1498
|
+
_pname = (getattr(_praw, "def_name", None)
|
|
1499
|
+
or getattr(getattr(_praw.get("__cst__"), "name", None),
|
|
1500
|
+
"value", None))
|
|
1501
|
+
# The name-carry guard covers viewing ClassParse's own parse -
|
|
1502
|
+
# the class_to_show row already shows it there.
|
|
1503
|
+
if (isinstance(_pdefaults, dict) and _pdefaults and _pname
|
|
1504
|
+
and f"@defaults({_pname})" not in sources):
|
|
1505
|
+
_add_source(f"@defaults({_pname})", _pdefaults, TypeCodec,
|
|
1506
|
+
location=_root_file_loc(_pds, getattr(_praw, "span", None)),
|
|
1507
|
+
kind="code class default")
|
|
1508
|
+
_povs = _praw.get("__overrides__")
|
|
1509
|
+
if _pds is input_value:
|
|
1510
|
+
# The view targets the parse itself - its own leading comment.
|
|
1511
|
+
_ov_dict, _ov_name = _povs, _pname
|
|
1512
|
+
_ov_span = getattr(_praw, "span", None)
|
|
1513
|
+
else:
|
|
1514
|
+
# Primitive leaf - the parent parse holds its comment under
|
|
1515
|
+
# __<key>__; the leaf's parent key rides in its kwargs (the
|
|
1516
|
+
# 'key' entry draw_collection passes every child).
|
|
1517
|
+
_tkey = (input_value._kwargs or {}).get("key")
|
|
1518
|
+
_ov_dict = (_povs.get(f"__{_tkey}__")
|
|
1519
|
+
if isinstance(_povs, dict) and isinstance(_tkey, str)
|
|
1520
|
+
else None)
|
|
1521
|
+
_ov_name = _tkey
|
|
1522
|
+
# _child_spans maps field keys to their assignment's span
|
|
1523
|
+
# (_record_child) - jump lands on the field's line, the
|
|
1524
|
+
# comment's one above.
|
|
1525
|
+
_ov_span = (getattr(_praw, "_child_spans", None) or {}).get(_tkey)
|
|
1526
|
+
if _ov_name:
|
|
1527
|
+
if not (isinstance(_ov_dict, dict) and _ov_dict):
|
|
1528
|
+
# No comment yet - lazy row, same as the live-view path
|
|
1529
|
+
# below: the matrix's + materializes the entry, the
|
|
1530
|
+
# wrapper synthesizes the `# [...]` line. For the parse
|
|
1531
|
+
# itself (a class/function node) the entry is its OWN
|
|
1532
|
+
# __overrides__ (leading comment on the def); for a
|
|
1533
|
+
# leaf it's the parent's __<key>__ slot. Module parses
|
|
1534
|
+
# have no _pname, so they register nothing - no noise.
|
|
1535
|
+
_ov_dict = (_LazyOverrideEntry(_praw)
|
|
1536
|
+
if _pds is input_value
|
|
1537
|
+
else _LazyOverrideEntry(_praw, f"__{_ov_name}__"))
|
|
1538
|
+
_add_source(f"# [{_ov_name}]", _ov_dict, TypeCodec,
|
|
1539
|
+
location=_root_file_loc(_pds, _ov_span),
|
|
1540
|
+
kind="code comment")
|
|
1541
|
+
break
|
|
1542
|
+
# LIVE-VIEW windows/markers: the value flowing through them is a
|
|
1543
|
+
# runtime capture, not a parse node - but the marker stamps the owning
|
|
1544
|
+
# scope's parse dict on the draw_state (live_root/live_key, the same
|
|
1545
|
+
# data its comment-args splat reads), so the site's `# [<key>]`
|
|
1546
|
+
# comment registers as an input source exactly like a primitive
|
|
1547
|
+
# leaf's. Edits write to the editor host's bubbling tree and save
|
|
1548
|
+
# through its normal path.
|
|
1549
|
+
_lroot = getattr(_pds, "live_root", None)
|
|
1550
|
+
if isinstance(_lroot, dict):
|
|
1551
|
+
# Resolve against the editor's CURRENT root: the stamp is the
|
|
1552
|
+
# marker's last render, and a reparse since (the window's def
|
|
1553
|
+
# scrolled off the screen, say) orphaned it - a write into the
|
|
1554
|
+
# orphan shows in the replay but never reaches the save.
|
|
1555
|
+
from meltygui.editor.live_view_views import current_live_root
|
|
1556
|
+
_lroot = current_live_root(_pds)
|
|
1557
|
+
_lkey = getattr(_pds, "live_key", None)
|
|
1558
|
+
if isinstance(_lkey, str):
|
|
1559
|
+
_lovs = _lroot.get("__overrides__")
|
|
1560
|
+
_lov = (_lovs.get(f"__{_lkey}__")
|
|
1561
|
+
if isinstance(_lovs, dict) else None)
|
|
1562
|
+
if not isinstance(_lov, dict):
|
|
1563
|
+
# No override yet - register a lazy entry so the matrix's
|
|
1564
|
+
# + can create `# [tint=(...)]` the same way it stamps a
|
|
1565
|
+
# missing @defaults(row).
|
|
1566
|
+
_lov = _LazyOverrideEntry(_lroot, f"__{_lkey}__")
|
|
1567
|
+
_lspan = (getattr(_lroot, "_child_spans", None) or {}).get(_lkey)
|
|
1568
|
+
_add_source(f"# [{_lkey}]", _lov, TypeCodec,
|
|
1569
|
+
location=_root_file_loc(_pds, _lspan),
|
|
1570
|
+
kind="code comment", owner=_pds)
|
|
1571
|
+
break
|
|
1572
|
+
if _pds._parent is _pds:
|
|
1573
|
+
break
|
|
1574
|
+
_pds = _pds._parent
|
|
1575
|
+
_pwalk -= 1
|
|
1576
|
+
# @window on the class (e.g. `@window(tint=(0.11,0.12,0.14))` on Toggles) -
|
|
1577
|
+
# its kwargs drive the window rendering the value, so it's an input source.
|
|
1578
|
+
# Same skip-when-absent rule as the fn-side @window below.
|
|
1579
|
+
_cls_window_deco = (cm_state.class_dict.deep.decorators.window()
|
|
1580
|
+
if cm_state.class_dict else None)
|
|
1581
|
+
if isinstance(_cls_window_deco, dict) and _cls_window_deco:
|
|
1582
|
+
_add_source(f"@window({cls_name})", _cls_window_deco,
|
|
1583
|
+
DecorationsCodec, location=cls_loc, kind="class decoration")
|
|
1584
|
+
# @render_func(...) - the view func's decorator kwargs. Registered for
|
|
1585
|
+
# EVERY value (not just function values, the old gate): decorator kwargs
|
|
1586
|
+
# outrank signature defaults in the wrapper gauntlet, so a param set
|
|
1587
|
+
# there (fast_toggle's show_cache=False) is the TRUE driver and hiding
|
|
1588
|
+
# the row made provenance lie. The app-wide blast radius of an edit is
|
|
1589
|
+
# real but the row is only ever written by an explicit pick.
|
|
1590
|
+
decoration_name = decoration_func.__name__
|
|
1591
|
+
decoration_raw = inspect.unwrap(decoration_func)
|
|
1592
|
+
decoration_loc = (inspect.getsourcefile(decoration_raw), decoration_raw.__code__.co_firstlineno)
|
|
1593
|
+
_add_source(f"@render_func({decoration_name})",
|
|
1594
|
+
cm_state.decoration_dict.deep.decorators.render_func(),
|
|
1595
|
+
DecorationsCodec, location=decoration_loc, kind="decoration")
|
|
1596
|
+
# @window only exists as a source on actually-@window-decorated funcs -
|
|
1597
|
+
# a placeholder row here would be permanent noise on every other tab.
|
|
1598
|
+
from meltygui.core.input.view_selection import WindowViewSource
|
|
1599
|
+
_window_deco = cm_state.decoration_dict.deep.decorators.window()
|
|
1600
|
+
if isinstance(_window_deco, dict) and _window_deco:
|
|
1601
|
+
_add_source(f"@window({decoration_name})",
|
|
1602
|
+
WindowViewSource(_window_deco, decoration_func, decoration_loc[0]),
|
|
1603
|
+
DecorationsCodec, location=decoration_loc, kind="window decoration")
|
|
1604
|
+
# @glfw_window(...) on the view fn (`@glfw_window` over `@render_func`,
|
|
1605
|
+
# app.py): every kwarg past the OS window's own (title / size / window_id /
|
|
1606
|
+
# name) is the root VIEW's, handed to the view by app._draw_root - the OS
|
|
1607
|
+
# window's twin of @window, and it ranks with it. Same skip-when-absent
|
|
1608
|
+
# rule; a missing `@glfw_window` parses to a str and adds nothing.
|
|
1609
|
+
_glfw_deco = cm_state.decoration_dict.deep.decorators.glfw_window()
|
|
1610
|
+
if isinstance(_glfw_deco, dict) and _glfw_deco:
|
|
1611
|
+
_add_source(f"@glfw_window({decoration_name})",
|
|
1612
|
+
WindowViewSource(_glfw_deco, decoration_func, decoration_loc[0]),
|
|
1613
|
+
DecorationsCodec, location=decoration_loc, kind="glfw window decoration")
|
|
1614
|
+
|
|
1615
|
+
cm_state._collect_cache = {
|
|
1616
|
+
"sources": sources, "tints": source_tints,
|
|
1617
|
+
"locations": source_locations, "kinds": source_kinds,
|
|
1618
|
+
"comment_owners": comment_owners,
|
|
1619
|
+
"writable": tuple(writable_sources), "view_func": input_value._wrapper or input_value._view_func}
|
|
1620
|
+
cm_state._collect_key = _memo_key
|
|
1621
|
+
return cm_state._collect_cache
|
|
1622
|
+
|
|
1623
|
+
|
|
1624
|
+
def _ancestor_call_line(target_ds, ancestor_ds):
|
|
1625
|
+
"""File-absolute line inside `ancestor_ds`'s view function whose statement
|
|
1626
|
+
(transitively) rendered `target_ds`'s element — e.g. inspecting a button
|
|
1627
|
+
drawn by draw_tab_bar and walking up one scope resolves the `button(...)`
|
|
1628
|
+
call line in draw_tab_bar. The func tab auto-selects it.
|
|
1629
|
+
|
|
1630
|
+
Scans the TARGET's cached _call_stack (innermost-first tuples, captured
|
|
1631
|
+
once on menu-open — never the live stack, see the capture note in
|
|
1632
|
+
core_render) for the nearest frame executing the ancestor's function; that
|
|
1633
|
+
frame's lineno is the call statement. A view rendered inside a deferred
|
|
1634
|
+
layer has a stack that bottoms out at the layer loop, so each deferred
|
|
1635
|
+
ancestor's queue-time _deferred_call_stack is appended to continue the
|
|
1636
|
+
chain outward. None when the ancestor's frame isn't in the chain (stack
|
|
1637
|
+
not captured yet, or the ancestor rendered from cache with parents
|
|
1638
|
+
skipped)."""
|
|
1639
|
+
view_func = getattr(ancestor_ds, "_view_func", None)
|
|
1640
|
+
if view_func is None:
|
|
1641
|
+
return None
|
|
1642
|
+
code = getattr(inspect.unwrap(view_func), "__code__", None)
|
|
1643
|
+
if code is None:
|
|
1644
|
+
return None
|
|
1645
|
+
stack = list(getattr(target_ds, "_call_stack", None) or ())
|
|
1646
|
+
node = target_ds
|
|
1647
|
+
for _ in range(64):
|
|
1648
|
+
if getattr(node, "_is_deferred_layer", False):
|
|
1649
|
+
stack.extend(getattr(node, "_deferred_call_stack", None) or ())
|
|
1650
|
+
parent = getattr(node, "_parent", None)
|
|
1651
|
+
if node is ancestor_ds or parent is None or parent is node:
|
|
1652
|
+
break
|
|
1653
|
+
node = parent
|
|
1654
|
+
for filename, lineno, func_name in stack:
|
|
1655
|
+
if func_name == code.co_name and filename == code.co_filename:
|
|
1656
|
+
return lineno
|
|
1657
|
+
return None
|
|
1658
|
+
|
|
1659
|
+
|
|
1660
|
+
def _deferred_ancestors(target_ds):
|
|
1661
|
+
"""The draw_states on `target_ds`'s parent chain (itself included,
|
|
1662
|
+
nearest first) that were queued to a deferred layer at some point
|
|
1663
|
+
(_is_deferred_layer) — the ones whose queue-time stack a descendant's
|
|
1664
|
+
trace needs."""
|
|
1665
|
+
found = []
|
|
1666
|
+
node = target_ds
|
|
1667
|
+
for _ in range(64):
|
|
1668
|
+
if getattr(node, "_is_deferred_layer", False):
|
|
1669
|
+
found.append(node)
|
|
1670
|
+
parent = getattr(node, "_parent", None)
|
|
1671
|
+
if parent is None or parent is node:
|
|
1672
|
+
break
|
|
1673
|
+
node = parent
|
|
1674
|
+
return found
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
def _request_deferred_stacks(target_ds):
|
|
1678
|
+
"""Ask every deferred ancestor of `target_ds` that has no queue-time
|
|
1679
|
+
stack yet to capture one on its next inline (queue) pass — lazy and
|
|
1680
|
+
one-shot, the same shape as the up-arrow's _call_site_requested. The
|
|
1681
|
+
ancestor's PARENT has to re-run its body for the queue branch to fire,
|
|
1682
|
+
hence the invalidate_up."""
|
|
1683
|
+
for node in _deferred_ancestors(target_ds):
|
|
1684
|
+
if (node._deferred_call_stack_frames is None
|
|
1685
|
+
and not node._deferred_stack_requested):
|
|
1686
|
+
node._deferred_stack_requested = True
|
|
1687
|
+
if Core.melty.cache is not None:
|
|
1688
|
+
Core.melty.cache.invalidate_up(node._tile_id, max_depth=5)
|
|
1689
|
+
request_render()
|
|
1690
|
+
|
|
1691
|
+
|
|
1692
|
+
def _splice_deferred_stack(inline_frames, queued_frames, view_func):
|
|
1693
|
+
"""Join a dispatch-bottomed stack onto the chain that queued its layer.
|
|
1694
|
+
|
|
1695
|
+
`inline_frames` (outermost first) were captured while the deferred
|
|
1696
|
+
layer was being drawn from Melty.end_frame's layer loop, so their head
|
|
1697
|
+
is the frame loop → Melty.draw → wrapper; `queued_frames` were captured
|
|
1698
|
+
at queue time inside that same wrapper, so their tail is the caller
|
|
1699
|
+
chain → wrapper. The result keeps the queued head up to the wrapper and
|
|
1700
|
+
the inline tail from the wrapper on: the trace of a direct call.
|
|
1701
|
+
Returns `inline_frames` unchanged when the layer loop isn't in it (the
|
|
1702
|
+
view rendered inline this time) or when the first view body after the
|
|
1703
|
+
dispatch isn't `view_func` (a stale deferred mark on another ancestor)."""
|
|
1704
|
+
from meltygui.code.chain_converters import _is_dispatch_frame
|
|
1705
|
+
def _base(path):
|
|
1706
|
+
return path.rsplit("/", 1)[-1].rsplit("\\", 1)[-1]
|
|
1707
|
+
loop = None
|
|
1708
|
+
for index, frame in enumerate(inline_frames):
|
|
1709
|
+
if _base(frame[0]) == "meltygui.py" and frame[2] == "end_frame":
|
|
1710
|
+
loop = index
|
|
1711
|
+
if loop is None:
|
|
1712
|
+
return inline_frames
|
|
1713
|
+
wrapper = None
|
|
1714
|
+
for index in range(loop + 1, len(inline_frames)):
|
|
1715
|
+
if _base(inline_frames[index][0]) == "core_render.py":
|
|
1716
|
+
wrapper = index
|
|
1717
|
+
break
|
|
1718
|
+
if wrapper is None:
|
|
1719
|
+
return inline_frames
|
|
1720
|
+
code = getattr(inspect.unwrap(view_func), "__code__", None) \
|
|
1721
|
+
if view_func is not None else None
|
|
1722
|
+
if code is not None:
|
|
1723
|
+
body = next((f for f in inline_frames[wrapper:]
|
|
1724
|
+
if not _is_dispatch_frame(f[0], f[2])), None)
|
|
1725
|
+
if body is None or body[0] != code.co_filename \
|
|
1726
|
+
or body[2] != code.co_name:
|
|
1727
|
+
return inline_frames
|
|
1728
|
+
head = list(queued_frames)
|
|
1729
|
+
while head and _base(head[-1][0]) == "core_render.py":
|
|
1730
|
+
head.pop()
|
|
1731
|
+
return head + list(inline_frames[wrapper:])
|
|
1732
|
+
|
|
1733
|
+
|
|
1734
|
+
def _merged_call_stack_frames(target_ds, menu_state):
|
|
1735
|
+
"""The Code tab's stack: the target's menu-open capture with every
|
|
1736
|
+
deferred ancestor's queue-time stack spliced in, nearest layer first,
|
|
1737
|
+
so nested deferred windows chain outward to the real caller. Stops at
|
|
1738
|
+
the first ancestor whose queue-time stack hasn't landed yet (a partial
|
|
1739
|
+
splice past it would join the wrong layer). Memoized on the identities
|
|
1740
|
+
of the parts — draw_stack_trace rebuilds on the list's identity."""
|
|
1741
|
+
inline = getattr(target_ds, "_call_stack_frames", None)
|
|
1742
|
+
if not inline:
|
|
1743
|
+
return None
|
|
1744
|
+
ancestors = _deferred_ancestors(target_ds)
|
|
1745
|
+
key = (id(inline),) + tuple(
|
|
1746
|
+
id(node._deferred_call_stack_frames) for node in ancestors)
|
|
1747
|
+
if getattr(menu_state, "_merged_stack_key", None) == key:
|
|
1748
|
+
return menu_state._merged_stack
|
|
1749
|
+
merged = inline
|
|
1750
|
+
for node in ancestors:
|
|
1751
|
+
queued = node._deferred_call_stack_frames
|
|
1752
|
+
if not queued:
|
|
1753
|
+
break
|
|
1754
|
+
merged = _splice_deferred_stack(
|
|
1755
|
+
merged, queued, getattr(node, "_view_func", None))
|
|
1756
|
+
menu_state._merged_stack_key = key
|
|
1757
|
+
menu_state._merged_stack = merged
|
|
1758
|
+
return merged
|
|
1759
|
+
|
|
1760
|
+
|
|
1761
|
+
# Reported by draw_context_menu_items when its Inspect row was picked: the
|
|
1762
|
+
# wrapper (core_render's context-menu block) opens the inspector,
|
|
1763
|
+
# draw_context_menu, in the menu's place.
|
|
1764
|
+
INSPECT = object()
|
|
1765
|
+
|
|
1766
|
+
|
|
1767
|
+
from meltygui.core.layout.dropdown_core import _ds_in_subtree
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
from meltygui.core.layout.dropdown_core import _dd_set_cursor
|
|
1771
|
+
|
|
1772
|
+
|
|
1773
|
+
from meltygui.core.layout.dropdown_core import _dd_invalidate_rows
|
|
1774
|
+
|
|
1775
|
+
|
|
1776
|
+
from meltygui.core.layout.dropdown_core import _dd_scroll_cursor_into_view
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
from meltygui.core.layout.dropdown_core import _dd_pick
|
|
1780
|
+
|
|
1781
|
+
|
|
1782
|
+
from meltygui.core.layout.dropdown_core import _dd_close
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
from meltygui.core.layout.dropdown_core import _dd_handle_keys
|
|
1786
|
+
|
|
1787
|
+
|
|
1788
|
+
_DD_MENU_W = Toggles.Dropdown.menu_width
|
|
1789
|
+
_DD_ROW_H = Toggles.Dropdown.row_height
|
|
1790
|
+
# Root popover size bounds (draw_dropdown's content fit + the resize handle):
|
|
1791
|
+
# the wrapper's min_width / min_height for dd_menu and the old popup_size.
|
|
1792
|
+
_DD_MENU_MIN_W = Toggles.Dropdown.min_width
|
|
1793
|
+
_DD_MENU_MIN_H = Toggles.Dropdown.min_height
|
|
1794
|
+
_DD_MENU_MAX_H = Toggles.Dropdown.max_height
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
from meltygui.core.layout.dropdown_core import _dd_update_menu_size
|
|
1798
|
+
|
|
1799
|
+
|
|
1800
|
+
# Limits on the scope-label column of code-preview rows (usage-jump picker):
|
|
1801
|
+
# the main code column clamps here, and longer labels ellipsize, so the
|
|
1802
|
+
# code keeps most of the row's width.
|
|
1803
|
+
_DD_CODE_LBL_MAX_W = Toggles.Dropdown.code_label_max_width
|
|
1804
|
+
|
|
1805
|
+
|
|
1806
|
+
# Row tags (the right-aligned dim column): default colour - the
|
|
1807
|
+
# autocomplete kind label's blue washed - and the gap between segments.
|
|
1808
|
+
_DD_TAG_COLOR = Toggles.Dropdown.tag_color
|
|
1809
|
+
_DD_TAG_GAP = Toggles.Dropdown.tag_gap
|
|
1810
|
+
# Per-row symbol tint wash alpha (autocomplete's definition colours) -
|
|
1811
|
+
# the tag mask re-composes it so the mask stays invisible on tinted rows.
|
|
1812
|
+
_DD_ROW_TINT_A = Toggles.Dropdown.row_tint_alpha
|
|
1813
|
+
|
|
1814
|
+
|
|
1815
|
+
from meltygui.view.control_view import draw_single
|
|
1816
|
+
|
|
1817
|
+
|
|
1818
|
+
# [tint=(0.75, 0.0, 0.0), show_tint=True]
|
|
1819
|
+
def draw_any(input_value: any = None, view_func=None, mode: any = None, chain=None, **kwargs):
|
|
1820
|
+
import inspect
|
|
1821
|
+
kwargs_view_func = view_func
|
|
1822
|
+
key = kwargs.get("key", None)
|
|
1823
|
+
real_type = kwargs.get("real_type", type(input_value))
|
|
1824
|
+
collection_type = kwargs.get("type_collection", type(kwargs.get("collection", None)))
|
|
1825
|
+
|
|
1826
|
+
if view_func is None:
|
|
1827
|
+
new_default = Core.melty.get_default_view_function(real_type=real_type, collection_type=collection_type,
|
|
1828
|
+
attrib_key=key, value=input_value)
|
|
1829
|
+
if new_default is None:
|
|
1830
|
+
new_default = draw_collection
|
|
1831
|
+
if view_func is None:
|
|
1832
|
+
view_func = new_default
|
|
1833
|
+
|
|
1834
|
+
if mode is None:
|
|
1835
|
+
mode = Core.melty.mode_stack[-1] if len(Core.melty.mode_stack) > 0 else None
|
|
1836
|
+
|
|
1837
|
+
if isinstance(mode, tuple) and len(mode) > 0:
|
|
1838
|
+
main_mode = mode[0]
|
|
1839
|
+
else:
|
|
1840
|
+
main_mode = mode
|
|
1841
|
+
|
|
1842
|
+
if main_mode is not None:
|
|
1843
|
+
# Loop over super types
|
|
1844
|
+
mode_config = main_mode.get_config_for(input_value)
|
|
1845
|
+
if mode_config is not None and mode_config.func is None and (
|
|
1846
|
+
mode_config.kwargs.get("convert", None) is not None
|
|
1847
|
+
or mode_config.kwargs.get("convert_in", None) is not None):
|
|
1848
|
+
convert_in = mode_config.kwargs.get("convert_in", None)
|
|
1849
|
+
if convert_in is not None:
|
|
1850
|
+
# Infer target type from the return annotation of the last converter
|
|
1851
|
+
import inspect
|
|
1852
|
+
last_fn = convert_in[-1]
|
|
1853
|
+
ret = inspect.signature(last_fn).return_annotation
|
|
1854
|
+
convert_to_type = ret if ret is not inspect.Parameter.empty else None
|
|
1855
|
+
else:
|
|
1856
|
+
convert_to_type = mode_config.kwargs["convert"][-1]
|
|
1857
|
+
mode_config = main_mode.get_config_for(the_type=convert_to_type) if convert_to_type is not None else None
|
|
1858
|
+
if mode_config is not None and mode_config.func is not None:
|
|
1859
|
+
view_func = draw_single
|
|
1860
|
+
kwargs_view_func = mode_config.func
|
|
1861
|
+
|
|
1862
|
+
elif mode_config is not None and mode_config.func is not None:
|
|
1863
|
+
if isinstance(mode_config.func, tuple):
|
|
1864
|
+
kwargs['chain'] = mode_config.func
|
|
1865
|
+
kwargs['route'] = mode_config.route
|
|
1866
|
+
view_func = run_chain
|
|
1867
|
+
else:
|
|
1868
|
+
view_func = mode_config.func
|
|
1869
|
+
kwargs_view_func = view_func
|
|
1870
|
+
|
|
1871
|
+
# kwargs['use_cache'] = True
|
|
1872
|
+
kwargs['mode'] = mode
|
|
1873
|
+
kwargs['view_func'] = kwargs_view_func
|
|
1874
|
+
|
|
1875
|
+
from meltygui.core.input.view_selection import configured_view_func
|
|
1876
|
+
try:
|
|
1877
|
+
selected = configured_view_func(input_value, kwargs)
|
|
1878
|
+
except ValueError as error:
|
|
1879
|
+
from meltygui.core.diagnostics.notifications import notify
|
|
1880
|
+
notify(str(error), tag="view_func")
|
|
1881
|
+
selected = None
|
|
1882
|
+
if selected is not None and view_func is not draw_single and view_func is not run_chain:
|
|
1883
|
+
view_func = selected
|
|
1884
|
+
if "view_func" not in inspect.signature(inspect.unwrap(view_func)).parameters:
|
|
1885
|
+
kwargs.pop("view_func", None)
|
|
1886
|
+
return_val = view_func(input_value, **kwargs)
|
|
1887
|
+
|
|
1888
|
+
return return_val
|
|
1889
|
+
|
|
1890
|
+
|
|
1891
|
+
from meltygui.editor.source_ui import _RowSpan, _row_code_hosts
|