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,658 @@
|
|
|
1
|
+
"""Orchestration view functions and supporting definitions."""
|
|
2
|
+
from meltygui.hdr_color import pack_color
|
|
3
|
+
from meltygui.core.melty import Melty
|
|
4
|
+
from meltygui.core.core_render import render_func
|
|
5
|
+
from meltygui.state.orchestration_state import OrchestratorPanelState
|
|
6
|
+
import meltygui_imgui as imgui
|
|
7
|
+
import time
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@render_func(use_cache=True, selectable=False, show_add_delete=False,
|
|
11
|
+
is_tree=False, show_name=True, shadow=True, tint=(0.719, 0.478, 0.208))
|
|
12
|
+
def draw_orchestrator(input_value=None, draw_state=None, style_manager=None,
|
|
13
|
+
panel_state: OrchestratorPanelState = None,
|
|
14
|
+
renaming_key="", rename_text="",
|
|
15
|
+
left_mouse_down=False, left_mouse_double_clicked=False, **kwargs):
|
|
16
|
+
# NOTE the click param names: "double_left_mouse_down" canonicalises
|
|
17
|
+
# to the SAME (input_id, DOWN) key as left_mouse_down (DOWN has no double
|
|
18
|
+
# promotion) but the per-frame name cache keeps one name per key - it
|
|
19
|
+
# silently drops every plain press. DOUBLE_CLICKED is its own key.
|
|
20
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
21
|
+
from meltygui.core.cache.tile_cache import add_shadow
|
|
22
|
+
from meltygui.core.automation.orchestration_core import Orchestrator
|
|
23
|
+
from meltygui.core.automation.orchestration_core import _format_value
|
|
24
|
+
from meltygui.core.automation.orchestration_core import _wrap_text
|
|
25
|
+
from meltygui.core.automation.orchestration_core import cue_gesture
|
|
26
|
+
from meltygui.core.automation.orchestration_core import cue_get
|
|
27
|
+
from meltygui.core.automation.orchestration_core import cue_has_target
|
|
28
|
+
from meltygui.core.automation.orchestration_core import cue_press_frac
|
|
29
|
+
from meltygui.core.automation.orchestration_core import failure_report
|
|
30
|
+
from meltygui.core.automation.orchestration_core import generalized_commands
|
|
31
|
+
from meltygui.core.automation.orchestration_core import group_events
|
|
32
|
+
from meltygui.core.automation.orchestration_core import parse_argument
|
|
33
|
+
import meltygui.core.automation.orchestration_core
|
|
34
|
+
|
|
35
|
+
meltygui.core.automation.orchestration_core._window_draw_state = draw_state
|
|
36
|
+
Orchestrator._precondition_watch = set() # re-declared by the command rows below
|
|
37
|
+
root = getattr(Melty.vis, "root", None)
|
|
38
|
+
store = getattr(root, "orchestrations", None)
|
|
39
|
+
if store is None:
|
|
40
|
+
imgui.text("model not loaded")
|
|
41
|
+
return False, input_value
|
|
42
|
+
orchestrations = store.orchestrations
|
|
43
|
+
|
|
44
|
+
# ---- icons (glyph literals - the editor renders them as a font) ----
|
|
45
|
+
record_icon = f""
|
|
46
|
+
stop_icon = f""
|
|
47
|
+
play_icon = f""
|
|
48
|
+
plus_icon = f""
|
|
49
|
+
delete_icon = f""
|
|
50
|
+
restore_icon = f""
|
|
51
|
+
chevron_right_icon = f""
|
|
52
|
+
chevron_down_icon = f""
|
|
53
|
+
rename_icon = f""
|
|
54
|
+
check_icon = f"" # precondition rows: "target hittable"
|
|
55
|
+
|
|
56
|
+
# ---- styling (fast_dock recipe) ----
|
|
57
|
+
row_bg_value, row_text_value = 0.06, 0.95
|
|
58
|
+
factor, saturation = 0.85, 1.0
|
|
59
|
+
button_bg_value, button_text_value = 0.13, 1.25
|
|
60
|
+
hover_bg_boost, hover_text_boost = 0.05, 0.5
|
|
61
|
+
text_saturation = 0.8
|
|
62
|
+
dim_text_value = 0.45
|
|
63
|
+
record_tint = (0.85, 0.25, 0.25) # armed recording button / pulse
|
|
64
|
+
restore_on_tint = (0.4, 0.85, 0.5) # restore chip when checked
|
|
65
|
+
|
|
66
|
+
# ---- geometry, authored at ui_scale 1.0 and scaled once per frame ----
|
|
67
|
+
px = Melty.px
|
|
68
|
+
# [tint=(0.939, 0.453, 0.245)]
|
|
69
|
+
row_height = px(28.0)
|
|
70
|
+
row_gap = px(3.0)
|
|
71
|
+
toolbar_height = px(26.0)
|
|
72
|
+
toolbar_gap = px(8.0)
|
|
73
|
+
button_pad_x = px(10.0)
|
|
74
|
+
button_gap = px(6.0)
|
|
75
|
+
chip_width = px(24.0) # per-row icon buttons (play/restore/delete)
|
|
76
|
+
corner = px(6.0)
|
|
77
|
+
pad_x = px(8.0)
|
|
78
|
+
text_nudge_y = px(-1.0)
|
|
79
|
+
status_height = px(20.0)
|
|
80
|
+
|
|
81
|
+
if style_manager is None:
|
|
82
|
+
style_manager = Melty.style_manager
|
|
83
|
+
draw_list = imgui.get_window_draw_list()
|
|
84
|
+
origin_x, origin_y = imgui.get_cursor_screen_pos()
|
|
85
|
+
content_width = draw_state.content_width or (draw_state.width or 300)
|
|
86
|
+
mouse_x, mouse_y = imgui.get_mouse_pos()
|
|
87
|
+
hover_ok = draw_state._bounding_hovered
|
|
88
|
+
press = left_mouse_down
|
|
89
|
+
# [tint=(0.62, 0.47, 0.95)]
|
|
90
|
+
click = (press.x, press.y) if (press and hasattr(press, "x")) else None
|
|
91
|
+
clip = getattr(draw_state, "abs_clip_rect", None)
|
|
92
|
+
changed = False
|
|
93
|
+
|
|
94
|
+
# The engine trims the stop click off a take's tail with this rect.
|
|
95
|
+
Orchestrator.window_rect = (draw_state.abs_left, draw_state.abs_top,
|
|
96
|
+
draw_state.abs_left + (draw_state.width or content_width),
|
|
97
|
+
draw_state.abs_top + (draw_state.height or 200))
|
|
98
|
+
|
|
99
|
+
def _mix(tint, value, mix_factor, mix_saturation):
|
|
100
|
+
color = tint if (isinstance(tint, tuple) and len(tint) >= 3) else (0.5, 0.5, 0.5)
|
|
101
|
+
return style_manager.make_color_rgb(color[0], color[1], color[2], value=value,
|
|
102
|
+
factor=mix_factor, saturation_scale=mix_saturation)
|
|
103
|
+
|
|
104
|
+
def _u32(color, alpha=1.0):
|
|
105
|
+
return pack_color(color[0], color[1], color[2], alpha)
|
|
106
|
+
|
|
107
|
+
def _in(rect, x, y):
|
|
108
|
+
return rect[0] <= x <= rect[2] and rect[1] <= y <= rect[3]
|
|
109
|
+
|
|
110
|
+
def _button(x, y, width, height, label, tint, active=False, icon_only=False):
|
|
111
|
+
"""Draw a toolbar/chip button, return (clicked, right_edge)."""
|
|
112
|
+
rect = (x, y, x + width, y + height)
|
|
113
|
+
hovered = hover_ok and _in(rect, mouse_x, mouse_y)
|
|
114
|
+
bg_value = button_bg_value + (0.08 if active else 0.0) \
|
|
115
|
+
+ (hover_bg_boost if hovered else 0.0)
|
|
116
|
+
background = _mix(tint, bg_value, factor, saturation)
|
|
117
|
+
text_color = _mix(tint, button_text_value + (hover_text_boost if hovered else 0.0),
|
|
118
|
+
factor, text_saturation)
|
|
119
|
+
if active or hovered or not icon_only:
|
|
120
|
+
add_shadow((x, y, width, height), corner_radius=corner, clip=clip)
|
|
121
|
+
draw_list.add_rect_filled(rect[0], rect[1], rect[2], rect[3],
|
|
122
|
+
_u32(background), rounding=corner)
|
|
123
|
+
label_size = imgui.calc_text_size(label)
|
|
124
|
+
draw_list.add_text(x + (width - label_size[0]) / 2.0,
|
|
125
|
+
y + (height - label_size[1]) / 2.0 + text_nudge_y,
|
|
126
|
+
_u32(text_color), label)
|
|
127
|
+
clicked = click is not None and _in(rect, click[0], click[1])
|
|
128
|
+
return clicked, rect[2]
|
|
129
|
+
|
|
130
|
+
# ---- toolbar: Record/Stop - Play/Abort - + New ----
|
|
131
|
+
toolbar_x = origin_x + pad_x
|
|
132
|
+
toolbar_y = origin_y
|
|
133
|
+
engine_busy = (Orchestrator.replaying is not None or bool(Orchestrator._restore_steps)
|
|
134
|
+
or Orchestrator._task is not None)
|
|
135
|
+
is_recording = Orchestrator.recording is not None
|
|
136
|
+
|
|
137
|
+
# Record New ALWAYS creates a fresh orchestration and records into it;
|
|
138
|
+
# re-recording an existing row use that row's own record chip.
|
|
139
|
+
record_label = f"{stop_icon} Stop" if is_recording \
|
|
140
|
+
else f"{record_icon} Record New"
|
|
141
|
+
record_width = imgui.calc_text_size(record_label)[0] + 2 * button_pad_x
|
|
142
|
+
record_clicked, edge = _button(toolbar_x, toolbar_y, record_width, toolbar_height,
|
|
143
|
+
record_label, record_tint if is_recording else draw_state.tint,
|
|
144
|
+
active=is_recording)
|
|
145
|
+
if record_clicked and not engine_busy:
|
|
146
|
+
if is_recording:
|
|
147
|
+
Orchestrator.stop_recording()
|
|
148
|
+
else:
|
|
149
|
+
from meltygui.models.orchestration import Orchestration
|
|
150
|
+
target = Orchestration()
|
|
151
|
+
target.name = f"Orchestration {len(orchestrations) + 1}"
|
|
152
|
+
orchestrations[target.id] = target
|
|
153
|
+
Orchestrator.start_recording(target)
|
|
154
|
+
changed = True
|
|
155
|
+
|
|
156
|
+
if engine_busy:
|
|
157
|
+
abort_label = f"{stop_icon} Abort"
|
|
158
|
+
abort_width = imgui.calc_text_size(abort_label)[0] + 2 * button_pad_x
|
|
159
|
+
abort_clicked, edge = _button(edge + button_gap, toolbar_y, abort_width,
|
|
160
|
+
toolbar_height, abort_label, record_tint, active=True)
|
|
161
|
+
if abort_clicked:
|
|
162
|
+
Orchestrator.abort("stop button")
|
|
163
|
+
|
|
164
|
+
# ---- rows (one per orchestration, each expands to its detail tabs) ----
|
|
165
|
+
rows_top = toolbar_y + toolbar_height + toolbar_gap
|
|
166
|
+
row_left = origin_x + pad_x
|
|
167
|
+
row_right = origin_x + content_width - pad_x
|
|
168
|
+
row_stride = row_height + row_gap
|
|
169
|
+
row_y = rows_top
|
|
170
|
+
detail_row_height = px(18.0)
|
|
171
|
+
detail_indent = px(34.0)
|
|
172
|
+
tab_height = px(20.0)
|
|
173
|
+
tab_pad_x = px(8.0)
|
|
174
|
+
precondition_indent = detail_indent + px(26.0) # precondition rows sit under their command
|
|
175
|
+
cue_tint = (0.85, 0.65, 0.2) # cue / effect rows (undo anchors)
|
|
176
|
+
fail_tint = (0.92, 0.32, 0.3) # failed row / error bar
|
|
177
|
+
success_tint = (0.32, 0.78, 0.42) # success row wash / ok tag
|
|
178
|
+
dim_color = _mix(draw_state.tint, dim_text_value, factor, text_saturation)
|
|
179
|
+
fail_color = _mix(fail_tint, row_text_value + 0.2, 0.35, 1.2)
|
|
180
|
+
success_color = _mix(success_tint, row_text_value + 0.1, 0.35, 1.2)
|
|
181
|
+
expanded_map = panel_state.expanded if panel_state is not None else {}
|
|
182
|
+
tab_map = panel_state.tab if panel_state is not None else {}
|
|
183
|
+
dbl = (left_mouse_double_clicked.x, left_mouse_double_clicked.y) \
|
|
184
|
+
if (left_mouse_double_clicked and hasattr(left_mouse_double_clicked, "x")) else None
|
|
185
|
+
|
|
186
|
+
def _start_rename(key, orchestration):
|
|
187
|
+
draw_state.renaming_key = key
|
|
188
|
+
draw_state.rename_text = orchestration.name
|
|
189
|
+
if panel_state is not None:
|
|
190
|
+
panel_state._rename_focus = True
|
|
191
|
+
panel_state._rename_was_active = False
|
|
192
|
+
request_render()
|
|
193
|
+
|
|
194
|
+
def _commit_rename(orchestration):
|
|
195
|
+
text = (draw_state.rename_text or "").strip()
|
|
196
|
+
if text and text != orchestration.name:
|
|
197
|
+
orchestration.name = text
|
|
198
|
+
orchestration.custom_name = True # auto-name never overwrites this
|
|
199
|
+
request_render()
|
|
200
|
+
committed = True
|
|
201
|
+
else:
|
|
202
|
+
committed = False
|
|
203
|
+
draw_state.renaming_key = ""
|
|
204
|
+
return committed
|
|
205
|
+
|
|
206
|
+
def _detail_rows(rows_list, playing, fail_at=None):
|
|
207
|
+
"""Both tabs render through here — same replay-progress highlight:
|
|
208
|
+
the row being replayed brightens, finished rows dim, effect rows
|
|
209
|
+
(cues / commands) wear the amber, the FAILED row (the cue that
|
|
210
|
+
never verified / the event the replay stopped at) reads red."""
|
|
211
|
+
nonlocal row_y
|
|
212
|
+
for row in rows_list:
|
|
213
|
+
start_index, end_index, row_kind, label, tag = row[:5]
|
|
214
|
+
if clip is None or not (row_y + detail_row_height < clip[1] or row_y > clip[3]):
|
|
215
|
+
running = playing and start_index <= Orchestrator._replay_index \
|
|
216
|
+
< max(end_index, start_index + 1)
|
|
217
|
+
if row_kind == "cue": # cue rows sit AT their index
|
|
218
|
+
failed_here = (fail_at is not None and not playing
|
|
219
|
+
and start_index == fail_at)
|
|
220
|
+
else:
|
|
221
|
+
failed_here = (fail_at is not None and not playing
|
|
222
|
+
and start_index <= fail_at
|
|
223
|
+
< max(end_index, start_index + 1))
|
|
224
|
+
if failed_here:
|
|
225
|
+
label_color = fail_color
|
|
226
|
+
elif running:
|
|
227
|
+
label_color = _mix(draw_state.tint, row_text_value + 0.4,
|
|
228
|
+
factor, text_saturation) # being replayed
|
|
229
|
+
elif playing and max(end_index, start_index + 1) <= Orchestrator._replay_index:
|
|
230
|
+
label_color = dim_color # already replayed
|
|
231
|
+
elif row_kind in ("cue", "expand", "window", "move", "goto", "set"):
|
|
232
|
+
label_color = _mix(cue_tint, row_text_value, 0.4, 1.2)
|
|
233
|
+
else:
|
|
234
|
+
label_color = _mix(draw_state.tint, row_text_value * 0.75,
|
|
235
|
+
factor, text_saturation)
|
|
236
|
+
draw_list.add_text(row_left + detail_indent, row_y + text_nudge_y,
|
|
237
|
+
_u32(label_color), label)
|
|
238
|
+
if tag:
|
|
239
|
+
tag_width = imgui.calc_text_size(tag)[0]
|
|
240
|
+
draw_list.add_text(row_right - px(4) - tag_width,
|
|
241
|
+
row_y + text_nudge_y, _u32(dim_color), tag)
|
|
242
|
+
row_y += detail_row_height
|
|
243
|
+
|
|
244
|
+
def _command_rows(key, orchestration, tint, rows_list, playing_raw, cmd_cursor,
|
|
245
|
+
fail_cue_index=None):
|
|
246
|
+
"""Commands tab: each leaf-edit command's ARGUMENT is an inline edit
|
|
247
|
+
box — the recorded value pre-filled (defaults reproduce the take
|
|
248
|
+
exactly), an edit stores an override (amber text, × resets) and
|
|
249
|
+
routes play through change_value with the edited value. Highlight:
|
|
250
|
+
raw replay by event span, command playback by cue ordinal."""
|
|
251
|
+
nonlocal row_y, changed
|
|
252
|
+
overrides = getattr(orchestration, "overrides", None)
|
|
253
|
+
if overrides is None:
|
|
254
|
+
overrides = orchestration.overrides = {}
|
|
255
|
+
for ordinal, row in enumerate(rows_list):
|
|
256
|
+
start_index, end_index, row_kind, label, tag = row[:5]
|
|
257
|
+
arg = row[5] if len(row) > 5 else None
|
|
258
|
+
row_visible = clip is None or not (row_y + detail_row_height < clip[1]
|
|
259
|
+
or row_y > clip[3])
|
|
260
|
+
if row_visible:
|
|
261
|
+
if cmd_cursor is not None:
|
|
262
|
+
running = ordinal == cmd_cursor
|
|
263
|
+
finished = ordinal < cmd_cursor
|
|
264
|
+
else:
|
|
265
|
+
running = playing_raw and start_index <= Orchestrator._replay_index \
|
|
266
|
+
< max(end_index, start_index + 1)
|
|
267
|
+
finished = playing_raw \
|
|
268
|
+
and max(end_index, start_index + 1) <= Orchestrator._replay_index
|
|
269
|
+
if fail_cue_index is not None and ordinal == fail_cue_index \
|
|
270
|
+
and not running:
|
|
271
|
+
label_color = fail_color
|
|
272
|
+
elif running:
|
|
273
|
+
label_color = _mix(draw_state.tint, row_text_value + 0.4,
|
|
274
|
+
factor, text_saturation)
|
|
275
|
+
elif finished:
|
|
276
|
+
label_color = dim_color
|
|
277
|
+
elif arg is None:
|
|
278
|
+
label_color = _mix(cue_tint, row_text_value, 0.4, 1.2)
|
|
279
|
+
else:
|
|
280
|
+
label_color = _mix(draw_state.tint, row_text_value * 0.75,
|
|
281
|
+
factor, text_saturation)
|
|
282
|
+
# per-action run chip (the indent gutter): replays just THIS
|
|
283
|
+
# command's event span - overrides remap its gesture as in
|
|
284
|
+
# a full play - its cue verifying at the main
|
|
285
|
+
run_clicked, _ = _button(row_left + px(10), row_y, px(18.0),
|
|
286
|
+
px(16.0), play_icon, tint, icon_only=True)
|
|
287
|
+
if run_clicked and not is_recording and not engine_busy:
|
|
288
|
+
Orchestrator.play(orchestration, start=start_index,
|
|
289
|
+
end=max(end_index, start_index), generalize=True)
|
|
290
|
+
changed = True
|
|
291
|
+
draw_list.add_text(row_left + detail_indent, row_y + text_nudge_y,
|
|
292
|
+
_u32(label_color), label)
|
|
293
|
+
if arg is not None:
|
|
294
|
+
override_key = str(ordinal)
|
|
295
|
+
modified = override_key in overrides
|
|
296
|
+
value = overrides.get(override_key, arg)
|
|
297
|
+
value_text = value if isinstance(value, str) else _format_value(value)
|
|
298
|
+
input_width = px(150.0) if isinstance(arg, str) else px(72.0)
|
|
299
|
+
input_x = (row_left + detail_indent
|
|
300
|
+
+ imgui.calc_text_size(label)[0] + px(6))
|
|
301
|
+
imgui.set_cursor_screen_pos((input_x, row_y - px(1)))
|
|
302
|
+
imgui.push_item_width(input_width)
|
|
303
|
+
if modified:
|
|
304
|
+
imgui.push_style_color(imgui.COLOR_TEXT, 1.0, 0.75, 0.3, 1.0)
|
|
305
|
+
entered, text = imgui.input_text(
|
|
306
|
+
f"##orch-arg-{key}-{ordinal}", value_text, 128)
|
|
307
|
+
imgui.set_item_allow_overlap()
|
|
308
|
+
if modified:
|
|
309
|
+
imgui.pop_style_color()
|
|
310
|
+
imgui.pop_item_width()
|
|
311
|
+
if entered:
|
|
312
|
+
ok, parsed = parse_argument(text, arg)
|
|
313
|
+
if ok:
|
|
314
|
+
if parsed == arg:
|
|
315
|
+
overrides.pop(override_key, None) # back to default
|
|
316
|
+
else:
|
|
317
|
+
overrides[override_key] = parsed
|
|
318
|
+
changed = True
|
|
319
|
+
request_render()
|
|
320
|
+
close_x = input_x + input_width + px(4)
|
|
321
|
+
draw_list.add_text(close_x, row_y + text_nudge_y,
|
|
322
|
+
_u32(label_color), ")")
|
|
323
|
+
if modified:
|
|
324
|
+
reset_clicked, _ = _button(close_x + px(14), row_y, px(18.0),
|
|
325
|
+
px(16.0), delete_icon, tint,
|
|
326
|
+
icon_only=True)
|
|
327
|
+
if reset_clicked:
|
|
328
|
+
overrides.pop(override_key, None)
|
|
329
|
+
changed = True
|
|
330
|
+
request_render()
|
|
331
|
+
if tag and arg is None:
|
|
332
|
+
tag_width = imgui.calc_text_size(tag)[0]
|
|
333
|
+
draw_list.add_text(row_right - px(4) - tag_width,
|
|
334
|
+
row_y + text_nudge_y, _u32(dim_color), tag)
|
|
335
|
+
row_y += detail_row_height
|
|
336
|
+
if not cue_has_target(orchestration.cues[ordinal]):
|
|
337
|
+
continue
|
|
338
|
+
# ---- preconditions for this command (any cue with a target): one indented row per
|
|
339
|
+
# unmet precondition (outermost first - the order the solver
|
|
340
|
+
# takes them), the cheapest fix as its tag, a run chip that
|
|
341
|
+
# satisfies preconditions up to and including THIS one ----
|
|
342
|
+
Orchestrator._precondition_watch.add((key, ordinal))
|
|
343
|
+
preconditions = Orchestrator._preconditions.get((key, ordinal))
|
|
344
|
+
if preconditions is None:
|
|
345
|
+
continue # not listed yet (next sync)
|
|
346
|
+
if not preconditions:
|
|
347
|
+
if row_visible:
|
|
348
|
+
draw_list.add_text(row_left + precondition_indent, row_y + text_nudge_y,
|
|
349
|
+
_u32(dim_color), f"{check_icon} target hittable")
|
|
350
|
+
row_y += detail_row_height
|
|
351
|
+
continue
|
|
352
|
+
for precondition in preconditions:
|
|
353
|
+
if clip is None or not (row_y + detail_row_height < clip[1] or row_y > clip[3]):
|
|
354
|
+
runnable = precondition["kind"] not in ("unresolved", "error", "hittable")
|
|
355
|
+
run_pre = False
|
|
356
|
+
if runnable:
|
|
357
|
+
run_pre, _ = _button(row_left + precondition_indent - px(22), row_y,
|
|
358
|
+
px(18.0), px(16.0), play_icon, tint, icon_only=True)
|
|
359
|
+
if run_pre and not is_recording and not engine_busy:
|
|
360
|
+
from meltygui.core.automation.value_core import precondition_task
|
|
361
|
+
cue = orchestration.cues[ordinal]
|
|
362
|
+
path = tuple(cue_get(cue, "chain") or [cue_get(cue, "name") or "?"])
|
|
363
|
+
Orchestrator.submit(precondition_task(
|
|
364
|
+
path, precondition["key"], orchestration=orchestration,
|
|
365
|
+
gesture=cue_gesture(cue), press_frac=cue_press_frac(cue)))
|
|
366
|
+
changed = True
|
|
367
|
+
pre_color = (fail_color if precondition["kind"] in ("unresolved", "error")
|
|
368
|
+
else dim_color if precondition["kind"] == "hittable"
|
|
369
|
+
else _mix(cue_tint, row_text_value, 0.4, 1.2))
|
|
370
|
+
label_text = precondition["label"]
|
|
371
|
+
if precondition["kind"] == "hittable":
|
|
372
|
+
label_text = f"{check_icon} {label_text}"
|
|
373
|
+
draw_list.add_text(row_left + precondition_indent, row_y + text_nudge_y,
|
|
374
|
+
_u32(pre_color), label_text)
|
|
375
|
+
fixes = precondition.get("fixes") or []
|
|
376
|
+
if fixes:
|
|
377
|
+
fix_tag = f"fix: {fixes[0][1]}" + (f" (+{len(fixes) - 1})" if len(fixes) > 1 else "")
|
|
378
|
+
fix_width = imgui.calc_text_size(fix_tag)[0]
|
|
379
|
+
draw_list.add_text(row_right - px(4) - fix_width,
|
|
380
|
+
row_y + text_nudge_y, _u32(dim_color), fix_tag)
|
|
381
|
+
row_y += detail_row_height
|
|
382
|
+
|
|
383
|
+
for key, orchestration in list(orchestrations.items()):
|
|
384
|
+
row_rect = (row_left, row_y, row_right, row_y + row_height)
|
|
385
|
+
header_visible = clip is None or not (row_rect[3] < clip[1]
|
|
386
|
+
or row_rect[1] > clip[3])
|
|
387
|
+
tint = orchestration.tint if isinstance(orchestration.tint, tuple) else draw_state.tint
|
|
388
|
+
is_playing = Orchestrator.replaying is orchestration
|
|
389
|
+
take_failure = (Orchestrator.last_failure
|
|
390
|
+
if (Orchestrator.last_failure is not None
|
|
391
|
+
and Orchestrator.last_failure.orchestration is orchestration)
|
|
392
|
+
else None)
|
|
393
|
+
take_success = (Orchestrator.last_success
|
|
394
|
+
if (Orchestrator.last_success is not None
|
|
395
|
+
and Orchestrator.last_success.orchestration is orchestration
|
|
396
|
+
and take_failure is None)
|
|
397
|
+
else None)
|
|
398
|
+
is_take = Orchestrator.recording is orchestration
|
|
399
|
+
is_expanded = bool(expanded_map.get(key))
|
|
400
|
+
renaming = renaming_key == key
|
|
401
|
+
row_hovered = hover_ok and _in(row_rect, mouse_x, mouse_y)
|
|
402
|
+
chip_y = row_y + (row_height - px(22)) / 2.0
|
|
403
|
+
deleted = False
|
|
404
|
+
|
|
405
|
+
if header_visible:
|
|
406
|
+
bg_value = row_bg_value + (hover_bg_boost if row_hovered else 0.0)
|
|
407
|
+
# every row floats on a soft drop shadow; a succeeded take's
|
|
408
|
+
# row washes green (failure red wins - checked above)
|
|
409
|
+
add_shadow((row_rect[0], row_rect[1], row_right - row_left, row_height),
|
|
410
|
+
corner_radius=corner, clip=clip)
|
|
411
|
+
bg_tint = success_tint if take_success is not None else tint
|
|
412
|
+
draw_list.add_rect_filled(row_rect[0], row_rect[1], row_rect[2], row_rect[3],
|
|
413
|
+
_u32(_mix(bg_tint, bg_value + (0.03 if take_success else 0.0),
|
|
414
|
+
factor, saturation)),
|
|
415
|
+
rounding=corner)
|
|
416
|
+
|
|
417
|
+
# chevron (expand/collapse) + play chips
|
|
418
|
+
chevron_clicked, chip_edge = _button(
|
|
419
|
+
row_left + px(4), chip_y, chip_width, px(22),
|
|
420
|
+
chevron_down_icon if is_expanded else chevron_right_icon,
|
|
421
|
+
tint, icon_only=True)
|
|
422
|
+
if chevron_clicked:
|
|
423
|
+
if is_expanded:
|
|
424
|
+
expanded_map.pop(key, None)
|
|
425
|
+
else:
|
|
426
|
+
expanded_map[key] = True
|
|
427
|
+
is_expanded = not is_expanded
|
|
428
|
+
request_render()
|
|
429
|
+
# play = every command through change_value to its override or
|
|
430
|
+
# its original value (a verbatim replay had no button worth
|
|
431
|
+
# keeping — Lukas 09-01; `play(generalize=False)` still exists
|
|
432
|
+
# for the tests and tooling)
|
|
433
|
+
play_clicked, chip_edge = _button(chip_edge + px(2), chip_y, chip_width, px(22),
|
|
434
|
+
play_icon, tint, icon_only=True)
|
|
435
|
+
if play_clicked and not is_recording and not engine_busy:
|
|
436
|
+
Orchestrator.play(orchestration, generalize=True)
|
|
437
|
+
changed = True
|
|
438
|
+
# per-row record chip: re-record INTO this orchestration (events
|
|
439
|
+
# and cues replaced; the auto-name refreshes too, unless the
|
|
440
|
+
# name was set by hand). Shows stop while THIS take records.
|
|
441
|
+
record_chip_clicked, chip_edge = _button(
|
|
442
|
+
chip_edge + px(2), chip_y, chip_width, px(22),
|
|
443
|
+
stop_icon if is_take else record_icon,
|
|
444
|
+
record_tint if is_take else tint,
|
|
445
|
+
active=is_take, icon_only=True)
|
|
446
|
+
if record_chip_clicked:
|
|
447
|
+
if is_take:
|
|
448
|
+
Orchestrator.stop_recording()
|
|
449
|
+
changed = True
|
|
450
|
+
elif not is_recording and not engine_busy:
|
|
451
|
+
Orchestrator.start_recording(orchestration)
|
|
452
|
+
changed = True
|
|
453
|
+
|
|
454
|
+
# right side, outermost in: delete · restore · rename · tag
|
|
455
|
+
delete_left = row_right - px(4) - chip_width
|
|
456
|
+
if row_hovered and not is_take and not is_playing and not renaming:
|
|
457
|
+
delete_clicked, _ = _button(delete_left, chip_y, chip_width, px(22),
|
|
458
|
+
delete_icon, tint, icon_only=True)
|
|
459
|
+
if delete_clicked:
|
|
460
|
+
orchestrations.pop(key, None)
|
|
461
|
+
expanded_map.pop(key, None)
|
|
462
|
+
tab_map.pop(key, None)
|
|
463
|
+
changed = True
|
|
464
|
+
deleted = True
|
|
465
|
+
restore_left = delete_left - button_gap - chip_width
|
|
466
|
+
restore_tint = restore_on_tint if orchestration.restore_on_finish else tint
|
|
467
|
+
restore_clicked, _ = _button(restore_left, chip_y, chip_width, px(22),
|
|
468
|
+
restore_icon, restore_tint,
|
|
469
|
+
active=orchestration.restore_on_finish,
|
|
470
|
+
icon_only=True)
|
|
471
|
+
if restore_clicked:
|
|
472
|
+
orchestration.restore_on_finish = not orchestration.restore_on_finish
|
|
473
|
+
changed = True
|
|
474
|
+
rename_left = restore_left - button_gap - chip_width
|
|
475
|
+
if row_hovered and not renaming:
|
|
476
|
+
rename_clicked, _ = _button(rename_left, chip_y, chip_width, px(22),
|
|
477
|
+
rename_icon, tint, icon_only=True)
|
|
478
|
+
if rename_clicked:
|
|
479
|
+
_start_rename(key, orchestration)
|
|
480
|
+
renaming = True
|
|
481
|
+
|
|
482
|
+
if is_playing:
|
|
483
|
+
tag = f"{Orchestrator._replay_index}/{len(orchestration.events)}"
|
|
484
|
+
else:
|
|
485
|
+
override_count = len(getattr(orchestration, "overrides", None) or {})
|
|
486
|
+
tag = f"{orchestration.duration:g}s · {len(orchestration.events)} ev" \
|
|
487
|
+
+ (f" · {override_count} edited" if override_count else "")
|
|
488
|
+
if take_failure is not None:
|
|
489
|
+
tag += " · failed"
|
|
490
|
+
elif take_success is not None:
|
|
491
|
+
tag += " · ok"
|
|
492
|
+
tag_width = imgui.calc_text_size(tag)[0]
|
|
493
|
+
tag_left = rename_left - button_gap - tag_width
|
|
494
|
+
if is_playing:
|
|
495
|
+
tag_color = _mix(tint, dim_text_value, factor, text_saturation)
|
|
496
|
+
elif take_failure is not None:
|
|
497
|
+
tag_color = fail_color
|
|
498
|
+
elif take_success is not None:
|
|
499
|
+
tag_color = success_color
|
|
500
|
+
else:
|
|
501
|
+
tag_color = _mix(tint, dim_text_value, factor, text_saturation)
|
|
502
|
+
draw_list.add_text(tag_left,
|
|
503
|
+
row_y + (row_height - imgui.get_text_line_height()) / 2.0
|
|
504
|
+
+ text_nudge_y, _u32(tag_color), tag)
|
|
505
|
+
|
|
506
|
+
# name: inline rename input, or the label (double-click to rename)
|
|
507
|
+
name_left = chip_edge + px(8)
|
|
508
|
+
if deleted:
|
|
509
|
+
pass
|
|
510
|
+
elif renaming:
|
|
511
|
+
imgui.set_cursor_screen_pos((name_left, row_y + px(3)))
|
|
512
|
+
imgui.push_item_width(max(px(90), min(px(240),
|
|
513
|
+
tag_left - name_left - px(10))))
|
|
514
|
+
if panel_state is not None and getattr(panel_state, "_rename_focus", False):
|
|
515
|
+
imgui.set_keyboard_focus_here()
|
|
516
|
+
panel_state._rename_focus = False
|
|
517
|
+
entered, text = imgui.input_text(f"##orch-rename-{key}",
|
|
518
|
+
draw_state.rename_text or "", 128,
|
|
519
|
+
imgui.INPUT_TEXT_ENTER_RETURNS_TRUE)
|
|
520
|
+
imgui.pop_item_width()
|
|
521
|
+
draw_state.rename_text = text
|
|
522
|
+
item_active = imgui.is_item_active()
|
|
523
|
+
was_active = (getattr(panel_state, "_rename_was_active", False)
|
|
524
|
+
if panel_state is not None else True)
|
|
525
|
+
if entered or (was_active and not item_active):
|
|
526
|
+
changed = _commit_rename(orchestration) or changed
|
|
527
|
+
elif panel_state is not None:
|
|
528
|
+
panel_state._rename_was_active = item_active
|
|
529
|
+
else:
|
|
530
|
+
text_value = row_text_value + (hover_text_boost if row_hovered else 0.0)
|
|
531
|
+
name_color = _mix((record_tint if is_take else tint), text_value,
|
|
532
|
+
factor, text_saturation)
|
|
533
|
+
label = orchestration.name + (" (recording)" if is_take else "")
|
|
534
|
+
draw_list.add_text(name_left,
|
|
535
|
+
row_y + (row_height - imgui.get_text_line_height()) / 2.0
|
|
536
|
+
+ text_nudge_y, _u32(name_color), label)
|
|
537
|
+
if dbl is not None and _in((name_left, row_y, tag_left, row_y + row_height),
|
|
538
|
+
dbl[0], dbl[1]):
|
|
539
|
+
_start_rename(key, orchestration)
|
|
540
|
+
|
|
541
|
+
row_y += row_stride
|
|
542
|
+
if deleted:
|
|
543
|
+
continue
|
|
544
|
+
|
|
545
|
+
# ---- expanded detail: Commands (generalized) | Events (raw) ----
|
|
546
|
+
if is_expanded:
|
|
547
|
+
current_tab = tab_map.get(key, "commands")
|
|
548
|
+
tab_x = row_left + detail_indent
|
|
549
|
+
for tab_key, tab_label in (("commands", "Commands"), ("events", "Events")):
|
|
550
|
+
width = imgui.calc_text_size(tab_label)[0] + 2 * tab_pad_x
|
|
551
|
+
tab_clicked, tab_edge = _button(tab_x, row_y, width, tab_height,
|
|
552
|
+
tab_label, tint,
|
|
553
|
+
active=current_tab == tab_key)
|
|
554
|
+
if tab_clicked and current_tab != tab_key:
|
|
555
|
+
tab_map[key] = tab_key
|
|
556
|
+
current_tab = tab_key
|
|
557
|
+
request_render()
|
|
558
|
+
tab_x = tab_edge + button_gap
|
|
559
|
+
row_y += tab_height + px(4)
|
|
560
|
+
fail_at = None
|
|
561
|
+
if take_failure is not None:
|
|
562
|
+
if (take_failure.cue_index is not None
|
|
563
|
+
and take_failure.cue_index < len(orchestration.cues)):
|
|
564
|
+
fail_at = cue_get(orchestration.cues[take_failure.cue_index],
|
|
565
|
+
"at", 0)
|
|
566
|
+
else:
|
|
567
|
+
fail_at = take_failure.event_index
|
|
568
|
+
if current_tab == "events":
|
|
569
|
+
detail = group_events(orchestration.events, orchestration.cues)
|
|
570
|
+
if not detail:
|
|
571
|
+
detail = [(0, 0, "", "no events yet — press Record", "")]
|
|
572
|
+
_detail_rows(detail, is_playing, fail_at=fail_at)
|
|
573
|
+
else:
|
|
574
|
+
detail = generalized_commands(orchestration)
|
|
575
|
+
if detail:
|
|
576
|
+
_command_rows(key, orchestration, tint, detail, is_playing, None,
|
|
577
|
+
fail_cue_index=(take_failure.cue_index
|
|
578
|
+
if take_failure is not None else None))
|
|
579
|
+
else:
|
|
580
|
+
_detail_rows([(0, 0, "", "no commands yet — effects appear "
|
|
581
|
+
"as recording touches the undo stacks", "")],
|
|
582
|
+
False)
|
|
583
|
+
row_y += px(4)
|
|
584
|
+
|
|
585
|
+
if not orchestrations:
|
|
586
|
+
empty_color = _mix(draw_state.tint, dim_text_value, factor, text_saturation)
|
|
587
|
+
draw_list.add_text(row_left + px(4), rows_top + px(4), _u32(empty_color),
|
|
588
|
+
"No orchestrations — press Record to capture one.")
|
|
589
|
+
row_y += row_stride
|
|
590
|
+
|
|
591
|
+
# ---- status footer ----
|
|
592
|
+
if Orchestrator.status:
|
|
593
|
+
status_color = _mix(record_tint if is_recording else draw_state.tint,
|
|
594
|
+
row_text_value, factor, text_saturation)
|
|
595
|
+
draw_list.add_text(row_left + px(4), row_y + px(2), _u32(status_color),
|
|
596
|
+
Orchestrator.status)
|
|
597
|
+
row_y += status_height
|
|
598
|
+
|
|
599
|
+
# ---- error bar: the full reason for the most recent failure ----
|
|
600
|
+
failure = Orchestrator.last_failure
|
|
601
|
+
if failure is not None:
|
|
602
|
+
bar_top = row_y + px(4)
|
|
603
|
+
bar_line_height = imgui.get_text_line_height()
|
|
604
|
+
bar_pad_y = px(5.0)
|
|
605
|
+
dismiss_reserve = px(4) + px(18.0) + button_gap
|
|
606
|
+
message_wrap_width = max(px(60), (row_right - row_left) - px(16) - dismiss_reserve)
|
|
607
|
+
failed_name = str(getattr(failure.orchestration, "name", None) or "task")
|
|
608
|
+
where = ""
|
|
609
|
+
if failure.cue_index is not None:
|
|
610
|
+
where = f" (command {failure.cue_index + 1})"
|
|
611
|
+
elif failure.event_index is not None:
|
|
612
|
+
where = f" (event {failure.event_index})"
|
|
613
|
+
message = f"{failed_name}{where}: {failure.reason}"
|
|
614
|
+
message_lines = _wrap_text(message, message_wrap_width)
|
|
615
|
+
bar_height = max(px(24.0), bar_pad_y * 2 + bar_line_height * len(message_lines))
|
|
616
|
+
add_shadow((row_left, bar_top, row_right - row_left, bar_height),
|
|
617
|
+
corner_radius=corner, clip=clip)
|
|
618
|
+
draw_list.add_rect_filled(row_left, bar_top, row_right, bar_top + bar_height,
|
|
619
|
+
_u32(_mix(fail_tint, 0.045, 0.55, 1.1)),
|
|
620
|
+
rounding=corner)
|
|
621
|
+
message_rect = (row_left, bar_top, row_right - dismiss_reserve, bar_top + bar_height)
|
|
622
|
+
message_hovered = hover_ok and _in(message_rect, mouse_x, mouse_y)
|
|
623
|
+
# click anywhere on the message copies it (whole line, not just the
|
|
624
|
+
# visible part); "copied" shows in place for a beat
|
|
625
|
+
just_copied = (getattr(failure, "copied_at", None) is not None
|
|
626
|
+
and time.time() - failure.copied_at < 1.2)
|
|
627
|
+
if click is not None and _in(message_rect, click[0], click[1]):
|
|
628
|
+
try:
|
|
629
|
+
imgui.set_clipboard_text(failure_report(failure))
|
|
630
|
+
except Exception:
|
|
631
|
+
pass
|
|
632
|
+
failure.copied_at = time.time()
|
|
633
|
+
just_copied = True
|
|
634
|
+
request_render()
|
|
635
|
+
if message_hovered:
|
|
636
|
+
draw_list.add_rect_filled(message_rect[0], message_rect[1], message_rect[2],
|
|
637
|
+
message_rect[3], _u32(_mix(fail_tint, 0.09, 0.55, 1.1)),
|
|
638
|
+
rounding=corner)
|
|
639
|
+
# the whole reason, WRAPPED to the bar (a solver failure lists every
|
|
640
|
+
# fix it tried - one line clipped the part that mattered)
|
|
641
|
+
shown_lines = (["debug report copied to clipboard"] if just_copied else message_lines)
|
|
642
|
+
for line_index, line in enumerate(shown_lines):
|
|
643
|
+
draw_list.add_text(row_left + px(8),
|
|
644
|
+
bar_top + bar_pad_y + line_index * bar_line_height + text_nudge_y,
|
|
645
|
+
_u32(success_color if just_copied else fail_color), line)
|
|
646
|
+
if just_copied:
|
|
647
|
+
request_render() # let the confirmation fade
|
|
648
|
+
dismiss_clicked, _ = _button(row_right - px(4) - px(18.0),
|
|
649
|
+
bar_top + (bar_height - px(16.0)) / 2.0,
|
|
650
|
+
px(18.0), px(16.0), delete_icon, fail_tint,
|
|
651
|
+
icon_only=True)
|
|
652
|
+
if dismiss_clicked:
|
|
653
|
+
Orchestrator.last_failure = None
|
|
654
|
+
request_render()
|
|
655
|
+
row_y = bar_top + bar_height + px(2)
|
|
656
|
+
|
|
657
|
+
imgui.dummy(content_width, max(1.0, (row_y - origin_y) + row_gap))
|
|
658
|
+
return changed, input_value
|