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,848 @@
|
|
|
1
|
+
"""Header view functions and supporting definitions."""
|
|
2
|
+
from meltygui.hdr_color import pack_color
|
|
3
|
+
from meltygui.core.melty import Melty
|
|
4
|
+
from meltygui.core.melty import add_to_collection
|
|
5
|
+
from meltygui.core.rendering.render_funcs import RenderFuncs
|
|
6
|
+
from meltygui.state.core_enums import ProfileMode
|
|
7
|
+
from meltygui.state.new_core_model import TileMode
|
|
8
|
+
from meltygui.core.runtime.toggles import Tint
|
|
9
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
10
|
+
from meltygui_imgui.core import _DrawList
|
|
11
|
+
from types import NoneType
|
|
12
|
+
import colorsys
|
|
13
|
+
import meltygui_imgui as imgui
|
|
14
|
+
import types
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def render_search(search_ds, draw_state, unique=None, width=None, regrab_focus=True):
|
|
18
|
+
"""Render the find UI for the searchable view whose state lives on
|
|
19
|
+
`search_ds`: the search input, match count, prev/next nav, and close.
|
|
20
|
+
|
|
21
|
+
Shared by draw_header (inline, when the view has a header) and draw_search
|
|
22
|
+
(a floating window, when it doesn't). All state — search_text, match count,
|
|
23
|
+
current index — lives on `search_ds`, the owning view's draw_state, so both
|
|
24
|
+
presentations drive the same search.
|
|
25
|
+
|
|
26
|
+
`regrab_focus=False` limits the focus claim to first open (plus the Ctrl+F
|
|
27
|
+
one-shot): an always-visible box (the input tab's filter) must not pull
|
|
28
|
+
focus back whenever nothing holds text focus, or other fields on the same
|
|
29
|
+
tab become untypeable once focus clears.
|
|
30
|
+
"""
|
|
31
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
32
|
+
import meltygui.core.windowing.window_api as glfw
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
from meltygui.view.text_view import draw_text
|
|
36
|
+
# Grab focus on first open, and re-grab whenever nothing holds text focus.
|
|
37
|
+
# Window focus management (move-to-front / window activation) clears
|
|
38
|
+
# text_focused_ds when a window comes forward that doesn't contain the
|
|
39
|
+
# focused field. The floating search box lives in its own window, so
|
|
40
|
+
# without re-grabbing it would drop focus after the first keypress. The
|
|
41
|
+
# re-grab runs before draw_text's key handling, so no keystroke is lost,
|
|
42
|
+
# and it won't steal focus from a deliberate click into the editor (which
|
|
43
|
+
# leaves text_focused_ds non-None).
|
|
44
|
+
# `_search_focus_pending` is the one-shot set when Ctrl+F opened the search:
|
|
45
|
+
# claim focus this frame regardless of who holds text focus (the underlying
|
|
46
|
+
# searchable view can reclaim meltygui text focus before this box renders, so
|
|
47
|
+
# "text_focused_ds is None" alone misses the just-opened case). Consume it so
|
|
48
|
+
# later frames fall back to the gentle re-grab and don't fight a deliberate
|
|
49
|
+
# click into the editor.
|
|
50
|
+
# The re-grab (reclaim focus when nothing holds text focus) is gated on this
|
|
51
|
+
# search still being the active one (focused_ds is the owner). A deliberate
|
|
52
|
+
# click away runs clear_focus, which clears focused_ds, so the box releases
|
|
53
|
+
# focus and stays open-but-unfocused. A spurious clear during typing leaves
|
|
54
|
+
# focused_ds intact, so the box reclaims focus and no keystroke is lost.
|
|
55
|
+
# First-open and the Ctrl+F one-shot grab regardless.
|
|
56
|
+
focus_search = ((not search_ds._search_was_active)
|
|
57
|
+
or (regrab_focus and Melty.text_focused_ds is None
|
|
58
|
+
and Melty.focused_ds is search_ds)
|
|
59
|
+
or search_ds._search_focus_pending)
|
|
60
|
+
# One-shot open/Ctrl+F frame (NOT the spurious-clear regrab): select the
|
|
61
|
+
# whole term so typing replaces it and a single delete clears it.
|
|
62
|
+
focus_fresh = ((not search_ds._search_was_active)
|
|
63
|
+
or search_ds._search_focus_pending)
|
|
64
|
+
search_ds._search_focus_pending = False
|
|
65
|
+
search_ds._search_was_active = True
|
|
66
|
+
search_icon = ""
|
|
67
|
+
imgui.align_text_to_frame_padding()
|
|
68
|
+
imgui.text(search_icon)
|
|
69
|
+
imgui.same_line()
|
|
70
|
+
|
|
71
|
+
if width is None:
|
|
72
|
+
width = draw_state.content_width
|
|
73
|
+
_box = draw_text(search_ds.search_text, searchable=False,
|
|
74
|
+
is_search_box=True,
|
|
75
|
+
shadow=False, max_height=40,
|
|
76
|
+
name=search_icon + str(unique), with_header=None,
|
|
77
|
+
with_header_end=None, max_width=width - 50,
|
|
78
|
+
with_footer=None, header_same_line=True, tint=search_ds.tint,
|
|
79
|
+
show_name=False, show_header=False, single_line=True,
|
|
80
|
+
request_focus=focus_search, select_all_on_focus=focus_fresh,
|
|
81
|
+
return_extras=True)
|
|
82
|
+
search_change, new_search = _box[0], _box[1]
|
|
83
|
+
_box_ds = _box[2] if len(_box) > 2 else None
|
|
84
|
+
# While the find box holds text focus, mark this search as the active one so
|
|
85
|
+
# Enter/arrow nav routes here — including after clicking back into the box,
|
|
86
|
+
# where the click restores text focus but not focused_ds.
|
|
87
|
+
if _box_ds is not None and Melty.text_focused_ds is _box_ds:
|
|
88
|
+
Melty.focused_ds = search_ds
|
|
89
|
+
if search_change:
|
|
90
|
+
search_ds.search_text = new_search
|
|
91
|
+
# Re-render the owner's whole subtree so every child view recomputes its
|
|
92
|
+
# matches against the new term and the combined count stays in sync.
|
|
93
|
+
Melty.cache.invalidate_up(search_ds._tile_id, force=True, max_depth=12)
|
|
94
|
+
request_render()
|
|
95
|
+
# initial use
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Match count + prev/next navigation. The count and current index are
|
|
99
|
+
# populated by the searchable view's body (e.g. the text editor); the
|
|
100
|
+
# arrows step the active match and ask the body to scroll it into view.
|
|
101
|
+
|
|
102
|
+
imgui.same_line()
|
|
103
|
+
from meltygui.view.control_view import button
|
|
104
|
+
fa_x_icon = ""
|
|
105
|
+
|
|
106
|
+
imgui.set_cursor_screen_pos((draw_state.abs_left + width-25, imgui.get_cursor_screen_pos()[1]))
|
|
107
|
+
# imgui.set_cursor_screen_pos((imgui.get_cursor_screen_pos()[0], imgui.get_cursor_screen_pos()[1] + 2))
|
|
108
|
+
if button(fa_x_icon, name=f"{unique}##fa_x_icon", show_bg=False,
|
|
109
|
+
use_cache=True, height=23, shadow=True, z_offset=3, max_height=40,
|
|
110
|
+
tile_mode=TileMode.MAX, color=(9, 1, 1, 0))[0]:
|
|
111
|
+
search_ds.search_active = False
|
|
112
|
+
search_ds._search_was_active = False
|
|
113
|
+
# Keep search_text so reopening the find bar restores the last query.
|
|
114
|
+
Melty.text_focused_ds = None
|
|
115
|
+
|
|
116
|
+
total = search_ds.text_search_count
|
|
117
|
+
if total > 0:
|
|
118
|
+
imgui.align_text_to_frame_padding()
|
|
119
|
+
imgui.text_colored(f"{search_ds.text_search_current + 1}/{total}",
|
|
120
|
+
0.66, 0.74, 0.82, 1.0)
|
|
121
|
+
imgui.same_line(spacing=2)
|
|
122
|
+
nav = 0
|
|
123
|
+
if imgui.small_button(f"##search_prev{unique}"):
|
|
124
|
+
nav = -1
|
|
125
|
+
imgui.same_line(spacing=2)
|
|
126
|
+
if imgui.small_button(f"##search_next{unique}"):
|
|
127
|
+
nav = 1
|
|
128
|
+
# Enter / Down = find next, Shift+Enter / Up = find prev, Ctrl+Enter =
|
|
129
|
+
# "click" the selected result — but only while the FIND BOX (not the
|
|
130
|
+
# underlying editor) holds text focus, so Enter still inserts newlines
|
|
131
|
+
# when you click into the editor. We gate on the box holding text focus
|
|
132
|
+
# directly rather than on `focused_ds is search_ds`: clicking back into
|
|
133
|
+
# the box runs clear_focus, which nulls focused_ds (the searched view
|
|
134
|
+
# isn't under the click to be protected), so keying off focused_ds
|
|
135
|
+
# silently dropped Enter-nav after a mouse refocus. text_focused_ds is
|
|
136
|
+
# set straight by the box's own click handler, so it survives that.
|
|
137
|
+
# The find box is single-line, so Up/Down don't move its cursor and are
|
|
138
|
+
# free for stepping matches. Drained from the GLFW-callback key queue
|
|
139
|
+
# (not imgui.is_key_pressed) so it isn't dropped on slow frames.
|
|
140
|
+
if _box_ds is not None and Melty.text_focused_ds is _box_ds:
|
|
141
|
+
if any(k == glfw.KEY_DOWN for k, _ in Melty.frame_key_events):
|
|
142
|
+
nav = 1
|
|
143
|
+
elif any(k == glfw.KEY_UP for k, _ in Melty.frame_key_events):
|
|
144
|
+
nav = -1
|
|
145
|
+
# Enter steps to the next match, Shift+Enter to the previous, and
|
|
146
|
+
# HOLDING Enter rapid-fires — imgui's synthesized auto-repeat
|
|
147
|
+
# (io.key_repeat_delay/rate) is read here because GLFW's REPEAT events
|
|
148
|
+
# don't reach the key queue on every platform (Wayland); keep the loop
|
|
149
|
+
# rendering while Enter is held so that cadence is sampled. Ctrl+Enter
|
|
150
|
+
# "clicks" the selected result and stays single-shot (from the queue).
|
|
151
|
+
_enter = [m for k, m in Melty.frame_key_events
|
|
152
|
+
if k in (glfw.KEY_ENTER, glfw.KEY_KP_ENTER)]
|
|
153
|
+
_enter_repeat = (imgui.is_key_pressed(glfw.KEY_ENTER, repeat=True)
|
|
154
|
+
or imgui.is_key_pressed(glfw.KEY_KP_ENTER, repeat=True))
|
|
155
|
+
if imgui.is_key_down(glfw.KEY_ENTER) or imgui.is_key_down(glfw.KEY_KP_ENTER):
|
|
156
|
+
request_render()
|
|
157
|
+
if _enter or _enter_repeat:
|
|
158
|
+
if _enter and (_enter[-1] & glfw.MOD_CONTROL):
|
|
159
|
+
# "Click" the selected result: hit-test the BVH at the center
|
|
160
|
+
# of the selected match's rect and send a mouse-down to the
|
|
161
|
+
# front-most view there (the actual clickable, e.g. a managed
|
|
162
|
+
# window's name button) — exactly what a real click resolves
|
|
163
|
+
# to. The view's own click handling does the rest (toggle a
|
|
164
|
+
# window, focus an input, …). Queued + the tile invalidated so
|
|
165
|
+
# it re-renders and reads the click next frame (the find UI
|
|
166
|
+
# renders too late to inject for this frame).
|
|
167
|
+
from meltygui.core.automation.search_core import search_activate_target
|
|
168
|
+
from meltygui.core.input.input_handler import InputEvent
|
|
169
|
+
_target = search_activate_target(Melty.search_current_node)
|
|
170
|
+
if _target is not None and _target.width and _target.height:
|
|
171
|
+
_cx = _target.abs_left + _target.width / 2.0
|
|
172
|
+
_cy = _target.abs_top + _target.height / 2.0
|
|
173
|
+
_hits = [h for h in Melty.bvh_query(_cx, _cy)
|
|
174
|
+
if h._tile_id is not None and not h.just_shadow]
|
|
175
|
+
_top = _hits[0] if _hits else _target
|
|
176
|
+
Melty.search_click_pending = (
|
|
177
|
+
_top._tile_id,
|
|
178
|
+
InputEvent("left_mouse", "down", tile_id=_top._tile_id, x=_cx, y=_cy))
|
|
179
|
+
# Re-render the owner's subtree next frame (same as nav) so
|
|
180
|
+
# the clicked view actually re-runs and reads the injection.
|
|
181
|
+
Melty.cache.invalidate_up(search_ds._tile_id, force=True, max_depth=12)
|
|
182
|
+
request_render()
|
|
183
|
+
elif not imgui.get_io().key_ctrl:
|
|
184
|
+
nav = -1 if imgui.get_io().key_shift else 1
|
|
185
|
+
|
|
186
|
+
if nav != 0:
|
|
187
|
+
# total is the combined count across all views; stepping wraps over
|
|
188
|
+
# the whole result set. Flag a scroll and invalidate the owner's
|
|
189
|
+
# subtree so every child view recomputes and the one holding the new
|
|
190
|
+
# global-current match scrolls to it.
|
|
191
|
+
search_ds.text_search_current = (search_ds.text_search_current + nav) % total
|
|
192
|
+
search_ds._search_nav_pending = True
|
|
193
|
+
Melty.cache.invalidate_up(search_ds._tile_id, force=True, max_depth=12)
|
|
194
|
+
request_render()
|
|
195
|
+
elif search_ds.search_text:
|
|
196
|
+
imgui.align_text_to_frame_padding()
|
|
197
|
+
imgui.text_colored("No results", 0.74, 0.5, 0.5, 1.0)
|
|
198
|
+
# Enter with the find box focused force-recomputes the result set. "No
|
|
199
|
+
# results" can be stale — the searched views may have been rebuilt since
|
|
200
|
+
# the count was last taken (e.g. a fresh load from disk) — so re-run the
|
|
201
|
+
# cross-view walk on demand rather than leaving it stuck at zero. Flagging
|
|
202
|
+
# _search_nav_pending makes the owner's pre-body walk re-count next frame
|
|
203
|
+
# (same key source + box-focus gate as the nav block above).
|
|
204
|
+
if _box_ds is not None and Melty.text_focused_ds is _box_ds:
|
|
205
|
+
if any(k in (glfw.KEY_ENTER, glfw.KEY_KP_ENTER)
|
|
206
|
+
for k, _ in Melty.frame_key_events):
|
|
207
|
+
search_ds._search_nav_pending = True
|
|
208
|
+
Melty.cache.invalidate_up(search_ds._tile_id, force=True, max_depth=12)
|
|
209
|
+
request_render()
|
|
210
|
+
else:
|
|
211
|
+
imgui.align_text_to_frame_padding()
|
|
212
|
+
imgui.text_colored("", 0.74, 0.5, 0.5, 1.0)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def flat_button(label, draw_state, view_id, width=None, height=None,
|
|
216
|
+
color=(0.533, 0.068, 0.5), tint_value=0.16, text_value=1.023,
|
|
217
|
+
factor=1.0, saturation=1.2, text_saturation=0.8, alpha=1.0,
|
|
218
|
+
corner_radius=6.0, text_pad=15, hover_boost=0.05,
|
|
219
|
+
hover_text_boost=2.2, max_bg_brightness=0.25,
|
|
220
|
+
event="left_mouse_clicked", text_offset_x=None,
|
|
221
|
+
style_manager=None, layout=True, draw_list=None,
|
|
222
|
+
shadow=True, shadow_offset=2.0, text_color=None, pos=None,
|
|
223
|
+
hovered=None, style=None, **kwargs):
|
|
224
|
+
"""Draw-list button — the fast-dock interaction model instead of a
|
|
225
|
+
@render_func widget (~0.7ms of wrapper per call, measured): a rounded
|
|
226
|
+
rect + centered label straight to the draw list, hover from the live
|
|
227
|
+
mouse position (the owning tile repaints every frame while
|
|
228
|
+
bounding-hovered, so the highlight tracks), and the click claimed
|
|
229
|
+
through the OWNING view's draw_state.on_action rect — the same routing
|
|
230
|
+
the tab bar's dnd clicks already used, so blit-cache event delivery
|
|
231
|
+
holds. Advances the flow like an inline item of the same size (dummy).
|
|
232
|
+
Styling mirrors `button`'s make_color_rgb + brightness-clamp pipeline so
|
|
233
|
+
converted call sites keep their look. alpha=0 draws no bg (label-only
|
|
234
|
+
buttons, e.g. inactive tabs). Returns True on click.
|
|
235
|
+
|
|
236
|
+
pos=(x, y) / hovered=: draw-only callers outside any view (the OS-window
|
|
237
|
+
controls in titlebar.py, on the overlay list with their own hit logic)
|
|
238
|
+
place the button and drive its hover state themselves instead of the
|
|
239
|
+
cursor position and the owning draw_state."""
|
|
240
|
+
from meltygui.core.styling.style import Style
|
|
241
|
+
from meltygui.core.cache.tile_cache import add_shadow
|
|
242
|
+
from meltygui.core.layout.header_runtime import _TEXT_COLOR_MEMO
|
|
243
|
+
from meltygui.model.color_model import _brightness_clamp
|
|
244
|
+
import meltygui.core.input.mouse_cursor as mouse_cursor
|
|
245
|
+
|
|
246
|
+
if style_manager is None:
|
|
247
|
+
style_manager = Melty.style_manager
|
|
248
|
+
scale = Melty.ui_scale # Melty.px inlined: ~170 calls a frame
|
|
249
|
+
text = str(label).split("##")[0]
|
|
250
|
+
ts = imgui.calc_text_size(text)
|
|
251
|
+
w = width if width is not None else ts.x + text_pad * scale
|
|
252
|
+
h = height if height is not None else ts.y + 8.0 * scale
|
|
253
|
+
x, y = pos if pos is not None else imgui.get_cursor_screen_pos()
|
|
254
|
+
mx, my = imgui.get_mouse_pos()
|
|
255
|
+
if hovered is None:
|
|
256
|
+
hovered = (draw_state is not None
|
|
257
|
+
and draw_state._bounding_hovered
|
|
258
|
+
and not Melty.on_drag
|
|
259
|
+
and x <= mx < x + w and y <= my < y + h)
|
|
260
|
+
# draw_list: paint into a caller-provided list instead of the window's
|
|
261
|
+
# (e.g. the overlay list a DragDrop ghost rides) — pairs with layout=False.
|
|
262
|
+
dl = draw_list if draw_list is not None else imgui.get_window_draw_list()
|
|
263
|
+
if alpha > 0.0 and color is not None:
|
|
264
|
+
# Shadow under any button that draws a bg — a standalone depth mark
|
|
265
|
+
# (no draw_state for the compositor to shadow; the default offset +2
|
|
266
|
+
# mirrors the legacy active-button z_offset lift). Label-only buttons
|
|
267
|
+
# (alpha=0, e.g. inactive tabs) cast nothing, matching the old
|
|
268
|
+
# per-call-site marks. layout=False draw-only ghosts skip it too —
|
|
269
|
+
# they ride an overlay list outside the mark's snapshotted clip.
|
|
270
|
+
# shadow_offset = the lift (depth delta); a smaller one sits the
|
|
271
|
+
# button lower, so e.g. inactive tabs stay under the active tab.
|
|
272
|
+
rounding = corner_radius * scale
|
|
273
|
+
if shadow and layout:
|
|
274
|
+
add_shadow((x, y, w, h), offset=shadow_offset,
|
|
275
|
+
corner_radius=rounding)
|
|
276
|
+
if Toggles.dynamic_styles:
|
|
277
|
+
# Legacy callers supply a hue; its strength becomes a residual.
|
|
278
|
+
# Explicit Style values can supply signed shifts or absolutes.
|
|
279
|
+
bg_style = style
|
|
280
|
+
if bg_style is None:
|
|
281
|
+
strength = tint_value + (hover_boost if hovered else 0.0)
|
|
282
|
+
bg_style = Style(tuple(c * strength for c in color[:3]) + (alpha,))
|
|
283
|
+
Melty.add_background(bg_style, rect=(x, y, w, h),
|
|
284
|
+
corner_radius=rounding, draw_list=dl)
|
|
285
|
+
else:
|
|
286
|
+
clamp = _brightness_clamp
|
|
287
|
+
bg = style_manager.make_color_rgb(
|
|
288
|
+
color[0], color[1], color[2],
|
|
289
|
+
value=tint_value + (hover_boost if hovered else 0.0),
|
|
290
|
+
factor=factor, saturation_scale=saturation, alpha=1.0)
|
|
291
|
+
bg = clamp(bg[0], bg[1], bg[2], 0.0, max_bg_brightness)
|
|
292
|
+
dl.add_rect_filled(x, y, x + w, y + h,
|
|
293
|
+
pack_color(bg[0], bg[1], bg[2], alpha),
|
|
294
|
+
rounding=rounding)
|
|
295
|
+
# text_color: use this exact rgb for the label instead of the theme-mix
|
|
296
|
+
# pipeline below — that pipeline only lets text_value/text_saturation
|
|
297
|
+
# touch `factor` worth of the final color (the rest is the raw `color`),
|
|
298
|
+
# so callers needing FULL-range text control (the editor tabs' hsv
|
|
299
|
+
# knobs) pre-compute the color and pass it here. Hover still brightens.
|
|
300
|
+
if text_color is not None:
|
|
301
|
+
tc_key = (text_color[0], text_color[1], text_color[2], hovered)
|
|
302
|
+
tc = _TEXT_COLOR_MEMO.get(tc_key)
|
|
303
|
+
if tc is None:
|
|
304
|
+
th, tsat, tv = colorsys.rgb_to_hsv(*text_color[:3])
|
|
305
|
+
if hovered:
|
|
306
|
+
tv = min(1.0, tv + 0.25)
|
|
307
|
+
tc = colorsys.hsv_to_rgb(th, tsat, tv)
|
|
308
|
+
if len(_TEXT_COLOR_MEMO) > 2048:
|
|
309
|
+
_TEXT_COLOR_MEMO.clear()
|
|
310
|
+
_TEXT_COLOR_MEMO[tc_key] = tc
|
|
311
|
+
else:
|
|
312
|
+
tc = style_manager.make_color_rgb(
|
|
313
|
+
color[0], color[1], color[2],
|
|
314
|
+
value=text_value + (hover_text_boost if hovered else 0.0),
|
|
315
|
+
factor=factor, saturation_scale=text_saturation, alpha=1.0)
|
|
316
|
+
# text_offset_x: left-align the label at a fixed inset instead of
|
|
317
|
+
# centering — for buttons whose left edge hosts another element (the
|
|
318
|
+
# editor tabs' tint swatch) that centered text would overlap.
|
|
319
|
+
# Optical-centering nudges (same as the fast dock / `button`): glyphs sit
|
|
320
|
+
# low-left of their geometric cell, so shift right and up a hair.
|
|
321
|
+
tx = x + text_offset_x if text_offset_x is not None else x + (w - ts.x) * 0.5
|
|
322
|
+
dl.add_text(tx + 2.0 * scale, y + (h - ts.y) * 0.5 - scale,
|
|
323
|
+
pack_color(tc[0], tc[1], tc[2], 1.0), text)
|
|
324
|
+
# layout=False: draw-only — no dummy (nothing submitted to the window
|
|
325
|
+
# group, so an out-of-flow draw like a DragDrop ghost can't stretch the
|
|
326
|
+
# view's measured content) and no click subscription.
|
|
327
|
+
if not layout:
|
|
328
|
+
return False
|
|
329
|
+
imgui.dummy(w, h)
|
|
330
|
+
if draw_state is None:
|
|
331
|
+
return False
|
|
332
|
+
# `event` picks the trigger: the default full click, or "left_mouse_down"
|
|
333
|
+
# for press-reactive controls (tab switches) that should feel immediate.
|
|
334
|
+
# cursor=ARROW: a button always shows the plain pointer, whatever shape
|
|
335
|
+
# the view it sits in carries (inline buttons in draw_text sit inside
|
|
336
|
+
# the text body's I-beam rect). priority_delta=4 outranks the body's
|
|
337
|
+
# own cursor registrations (draw_text's is at 3), and being an
|
|
338
|
+
# on_action from the owner's body it is replayed on its cache hits.
|
|
339
|
+
fired = draw_state.on_action(event, view_id=view_id,
|
|
340
|
+
rect=(x, y, x + w, y + h),
|
|
341
|
+
priority_delta=4,
|
|
342
|
+
cursor=mouse_cursor.ARROW) is not None
|
|
343
|
+
# Effect ledger: a fired flat button is an observable effect with no
|
|
344
|
+
# undo record — the Orchestrator cues replays off it (view_id names the
|
|
345
|
+
# button; the rect gives the cue press-fraction geometry, so button
|
|
346
|
+
# clicks generalize like leaf-editor presses).
|
|
347
|
+
if fired and Melty.effect_hook is not None:
|
|
348
|
+
try:
|
|
349
|
+
Melty.effect_hook("button", str(view_id).split("##")[0], draw_state,
|
|
350
|
+
rect=(x, y, w, h))
|
|
351
|
+
except Exception:
|
|
352
|
+
pass
|
|
353
|
+
return fired
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def draw_header_arrow(expanded, color=None, alpha=0.071):
|
|
357
|
+
"""The header's transparent tree control, also usable by flat views.
|
|
358
|
+
Dimmed by Toggles.Melty.arrow_brightness (every arrow, everywhere)."""
|
|
359
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
360
|
+
dim = float(Toggles.Melty.arrow_brightness)
|
|
361
|
+
alpha = alpha * dim
|
|
362
|
+
if color is None:
|
|
363
|
+
depth = max(0.0, Melty.bg_depth)
|
|
364
|
+
color = Melty.style_manager.make_color_style_value(input={
|
|
365
|
+
"value": (7.788 + (depth - 30.0) * 0.05 * 0.332) * dim,
|
|
366
|
+
"saturation": 1.559 + (depth - 1.773) * -0.004,
|
|
367
|
+
"alpha": alpha, "max_value": 1.601})
|
|
368
|
+
else:
|
|
369
|
+
color = tuple(c * dim for c in color[:3]) + tuple(color[3:])
|
|
370
|
+
imgui.push_style_color(imgui.COLOR_TEXT, *color[:3], 1.0)
|
|
371
|
+
imgui.push_style_color(imgui.COLOR_BUTTON, 0.0, 0.0, 0.0, 0.0)
|
|
372
|
+
imgui.push_style_color(imgui.COLOR_BUTTON_HOVERED, 0.0, 0.0, 0.0, 0.0)
|
|
373
|
+
imgui.push_style_var(imgui.STYLE_ALPHA, alpha)
|
|
374
|
+
try:
|
|
375
|
+
imgui.set_item_allow_overlap()
|
|
376
|
+
return imgui.arrow_button("##tree", imgui.DIRECTION_DOWN if expanded else imgui.DIRECTION_RIGHT)
|
|
377
|
+
finally:
|
|
378
|
+
imgui.pop_style_var()
|
|
379
|
+
imgui.pop_style_color(3)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def draw_header(input_value=None, name="", key=None, meltygui=None, parent_show_add_delete=False, width=7, suffix="",
|
|
383
|
+
collection=None, icon=None, display_name=None, meta=None, unique=None, is_tree=True,
|
|
384
|
+
show_name=True, name_func=None, show_type=False, show_unique=False, name_color=None,
|
|
385
|
+
on_search=False, trigger_collapse=False, trigger_expand=False, header_same_line=False,
|
|
386
|
+
draw_state=None, show_tint=False, opacity=1.23, show_add_delete=True, new_item_type=types.NoneType,
|
|
387
|
+
show_add_types=None, on_drag=False, on_action=None, style_manager=None, font=None,
|
|
388
|
+
**kwargs):
|
|
389
|
+
# Constants
|
|
390
|
+
from meltygui.core.conversion.bubbling import _BubblingDict
|
|
391
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
392
|
+
from meltygui.utils.render_utils import pop_style_var
|
|
393
|
+
from meltygui.utils.render_utils import push_style_var
|
|
394
|
+
from meltygui.view.search_view import draw_search_highlight
|
|
395
|
+
from meltygui.core.layout.cursor_core import same_line
|
|
396
|
+
from meltygui.core.layout.header_core import _jump_to_view_source
|
|
397
|
+
from meltygui.model.collection_model import annotation_item_type
|
|
398
|
+
|
|
399
|
+
_font_pushed = False
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
if font is not None and Melty.font_mgr is not None:
|
|
403
|
+
_font_handle = Melty.font_mgr.get(font)
|
|
404
|
+
if _font_handle is not None:
|
|
405
|
+
imgui.push_font(_font_handle)
|
|
406
|
+
_font_pushed = True
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# Depth drives name brightness
|
|
410
|
+
depth_scale = 0.05
|
|
411
|
+
depth_offset = -30.0
|
|
412
|
+
# Depth drives text saturation falloff
|
|
413
|
+
sat_depth_factor = -0.004
|
|
414
|
+
sat_depth_offset = -1.773
|
|
415
|
+
|
|
416
|
+
draw_list = imgui.get_window_draw_list()
|
|
417
|
+
depth = max(0.0, Melty.bg_depth)
|
|
418
|
+
depth_intensity = float(depth + depth_offset) * depth_scale
|
|
419
|
+
|
|
420
|
+
sat_shift = float(depth + sat_depth_offset) * sat_depth_factor
|
|
421
|
+
# Name text (value is the base offset, updated by depth below)
|
|
422
|
+
name_style = {
|
|
423
|
+
'value': 1.188, 'saturation': 0.778,
|
|
424
|
+
'alpha': 0.174, 'max_value': 3.921,
|
|
425
|
+
'depth_factor': 0.729
|
|
426
|
+
}
|
|
427
|
+
name_rounding = 2.696
|
|
428
|
+
max_name_chars = 40
|
|
429
|
+
min_name_text_width = 62
|
|
430
|
+
|
|
431
|
+
# Type / unique label colors
|
|
432
|
+
type_label_tint = (3.672, 1.944, 2.861, 1.0)
|
|
433
|
+
unique_label_tint = (-1.535, 0.0, 0.9, 1.0)
|
|
434
|
+
|
|
435
|
+
# Depth-driven color computati
|
|
436
|
+
# on
|
|
437
|
+
name_style['value'] = depth_intensity * name_style['depth_factor'] + name_style['value']
|
|
438
|
+
name_style['saturation'] = name_style['saturation'] + sat_shift
|
|
439
|
+
|
|
440
|
+
if name_color is not None:
|
|
441
|
+
name_color = style_manager.make_color_style_rgb(*name_color, input=name_style, factor=0.1)
|
|
442
|
+
else:
|
|
443
|
+
name_color = style_manager.make_color_style_value(input=name_style, value=0.5)
|
|
444
|
+
arrow_style = {
|
|
445
|
+
'value': 7.788, 'saturation': 1.559,
|
|
446
|
+
'alpha': 0.071, 'max_value': 1.601,
|
|
447
|
+
'depth_factor': 0.332
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
# imgui.same_line(False) # does not work
|
|
452
|
+
# imgui.set_cursor_position(0,0) # does not work
|
|
453
|
+
|
|
454
|
+
# imgui.set_cursor_pos((0,0))
|
|
455
|
+
# def set_cursor(tuple_in):
|
|
456
|
+
# pass
|
|
457
|
+
|
|
458
|
+
# set_cursor((0,0)) # fine
|
|
459
|
+
# set_cursor(0,0) # This one works
|
|
460
|
+
# set_cursor(*(0,0)) # does not work
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
arrow_style['value'] = depth_intensity * arrow_style['depth_factor'] + arrow_style['value']
|
|
464
|
+
arrow_style['saturation'] = arrow_style['saturation'] + sat_shift
|
|
465
|
+
arrow_color = style_manager.make_color_style_value(input=arrow_style)
|
|
466
|
+
|
|
467
|
+
# ── Tree arrow ─────────────────────────────────────────────
|
|
468
|
+
imgui.dummy(5, 0)
|
|
469
|
+
start_x, start_y = imgui.get_cursor_screen_pos()
|
|
470
|
+
|
|
471
|
+
on_change = False
|
|
472
|
+
return_val = on_action
|
|
473
|
+
push_style_var(imgui.STYLE_ALPHA, opacity)
|
|
474
|
+
if display_name is not None:
|
|
475
|
+
name = display_name
|
|
476
|
+
imgui.align_text_to_frame_padding()
|
|
477
|
+
|
|
478
|
+
if is_tree:
|
|
479
|
+
imgui.set_cursor_screen_pos(imgui.get_cursor_screen_pos())
|
|
480
|
+
imgui.dummy(0, 0)
|
|
481
|
+
imgui.same_line(spacing=0)
|
|
482
|
+
|
|
483
|
+
if draw_header_arrow(draw_state.expanded, arrow_color, arrow_style['alpha']):
|
|
484
|
+
draw_state.expanded = not draw_state.expanded
|
|
485
|
+
# Effect ledger: expand/collapse is draw_state UI state — no
|
|
486
|
+
# undo record — but it IS the gate the orchestration machinery
|
|
487
|
+
# keys on (the field-behind-a-collapsed-parent story). The
|
|
488
|
+
# direction rides in the kind so replay verification catches a
|
|
489
|
+
# wrong toggle (an "expand" replayed onto an already-expanded
|
|
490
|
+
# view collapses it — the cue then fails honestly).
|
|
491
|
+
if Melty.effect_hook is not None:
|
|
492
|
+
try:
|
|
493
|
+
Melty.effect_hook(
|
|
494
|
+
"expand" if draw_state.expanded else "collapse",
|
|
495
|
+
str(getattr(draw_state, "name", "?")).split("##")[0],
|
|
496
|
+
draw_state)
|
|
497
|
+
except Exception:
|
|
498
|
+
pass
|
|
499
|
+
draw_state.content_height = 0
|
|
500
|
+
draw_state.invalid_content_height = True
|
|
501
|
+
request_render()
|
|
502
|
+
same_line()
|
|
503
|
+
else:
|
|
504
|
+
imgui.same_line(spacing=0)
|
|
505
|
+
|
|
506
|
+
# ── Type / unique labels ───────────────────────────────────
|
|
507
|
+
if show_type:
|
|
508
|
+
imgui.text_colored(f"({input_value.__class__.__name__})", *type_label_tint)
|
|
509
|
+
same_line()
|
|
510
|
+
if show_unique:
|
|
511
|
+
imgui.text_colored(f"({str(Melty.get_tile_id())})", *unique_label_tint)
|
|
512
|
+
same_line()
|
|
513
|
+
if show_name and name != "":
|
|
514
|
+
same_line(spacing=0)
|
|
515
|
+
imgui.set_item_allow_overlap()
|
|
516
|
+
|
|
517
|
+
# ── Tint widget ────────────────────────────────────────────
|
|
518
|
+
# A dict can carry its tint in __overrides__ (parsed from a `# [tint=(...)]`
|
|
519
|
+
# comment); edit that store directly so the change round-trips to source.
|
|
520
|
+
# The override comment is itself the opt-in, so this isn't gated on
|
|
521
|
+
# show_tint (which is only set for top-level windows, not nested classes).
|
|
522
|
+
# (Legacy per-storage tint chain removed — the anywhere swatch below IS
|
|
523
|
+
# the tint widget: one loop for every source, set_anywhere on write.)
|
|
524
|
+
_overrides = input_value.get("__overrides__") if isinstance(input_value, dict) else None
|
|
525
|
+
|
|
526
|
+
# ── Anywhere tint swatch (outlined) ── one attribute, both directions:
|
|
527
|
+
# `draw_state.locate_tint` READS the framework-resolved tint
|
|
528
|
+
# (draw_state._kwargs, in-flight cache included) and ASSIGNING it writes
|
|
529
|
+
# back to whichever source DRIVES it — code, comment, decoration,
|
|
530
|
+
# instance attr. (The property pair lives in anywhere.py; this is its
|
|
531
|
+
# first caller.) Outlined so the swatch reads apart from the labels.
|
|
532
|
+
from meltygui.core.rendering.parameter_core import get_source_for
|
|
533
|
+
_aw_attr = "tint"
|
|
534
|
+
_aw_tint = draw_state.locate_tint
|
|
535
|
+
if Toggles.dynamic_styles and draw_state.locate_style is not None:
|
|
536
|
+
_aw_attr = "style"
|
|
537
|
+
_aw_tint = draw_state.locate_style
|
|
538
|
+
|
|
539
|
+
# The caption re-resolves this often while it still reads the draw_state
|
|
540
|
+
# fallback: the view's code hosts load in the background, so the first
|
|
541
|
+
# popover open (which creates them) resolves before the source that
|
|
542
|
+
# really drives the tint has parsed — a meltygui app's `@glfw_window(tint=)`
|
|
543
|
+
# showed as "draw_state" until a write forced a fresh collection (09-12).
|
|
544
|
+
# [tint=(0.85, 0.55, 0.25)]
|
|
545
|
+
source_retry_frames = 30
|
|
546
|
+
|
|
547
|
+
def _aw_source_info(_ds=draw_state, _attr=_aw_attr):
|
|
548
|
+
# Popover caption: the LAST-KNOWN driving source. Reads the cache
|
|
549
|
+
# set_anywhere maintains; resolves (one collection) on first popover
|
|
550
|
+
# open, then caches on the ds — re-resolving only while the answer is
|
|
551
|
+
# the draw_state fallback (see source_retry_frames), never per frame.
|
|
552
|
+
_last = getattr(_ds, "_sa_last_source", None)
|
|
553
|
+
_src = _last.get(_attr) if _last else None
|
|
554
|
+
_frame = Melty.frame_count
|
|
555
|
+
_stale = (_src == "draw_state"
|
|
556
|
+
and _frame - getattr(_ds, "_sa_source_frame", -source_retry_frames) >= source_retry_frames)
|
|
557
|
+
if _src is None or _stale:
|
|
558
|
+
_src = get_source_for(_attr, _ds)
|
|
559
|
+
_ds._sa_source_frame = _frame
|
|
560
|
+
if _src is not None:
|
|
561
|
+
if _last is None:
|
|
562
|
+
_last = {}
|
|
563
|
+
_ds._sa_last_source = _last
|
|
564
|
+
_last[_attr] = _src
|
|
565
|
+
return f" {_src}" if _src else None # FA location-arrow
|
|
566
|
+
|
|
567
|
+
if _aw_tint is not None and (show_tint or (
|
|
568
|
+
isinstance(_overrides, dict) and _overrides.get("tint") is not None)):
|
|
569
|
+
draw_state._has_popup = True
|
|
570
|
+
# Named on the header's own `unique` — NEVER Melty.get_tile_id(),
|
|
571
|
+
# which reflects the current tile pass and shifts during edits
|
|
572
|
+
# (popover open, forced invalidations), re-keying the widget mid-drag
|
|
573
|
+
# so its fresh draw_state re-measures and the row wraps. The outline
|
|
574
|
+
# is drawn by draw_tuple around the chip itself (outline=True) — the
|
|
575
|
+
# widget's draw_state box is far wider than the swatch.
|
|
576
|
+
imgui.same_line(spacing=0)
|
|
577
|
+
# draw_tuple_fast, not the draw_tuple render_func: every window
|
|
578
|
+
# header paid a full wrapper call per frame for this 17 px chip
|
|
579
|
+
# (~0.19 ms each, use_cache=False). The chip claims its own 17×17
|
|
580
|
+
# footprint here since the fast path draws without layout.
|
|
581
|
+
from meltygui.view.collection_view import draw_tuple_fast
|
|
582
|
+
_aw_x, _aw_y = imgui.get_cursor_screen_pos()
|
|
583
|
+
_aw_ch, _aw_val = draw_tuple_fast(
|
|
584
|
+
_aw_tint, draw_state, view_id="aw_tint", x=_aw_x, y=_aw_y,
|
|
585
|
+
size=17, outline=True, info=_aw_source_info,
|
|
586
|
+
setter=lambda value, _ds=draw_state, _attr=_aw_attr: setattr(_ds, "locate_" + _attr, value),
|
|
587
|
+
view_owner=draw_state)
|
|
588
|
+
imgui.dummy(17, 17)
|
|
589
|
+
if _aw_ch:
|
|
590
|
+
setattr(draw_state, "locate_" + _aw_attr, _aw_val)
|
|
591
|
+
same_line()
|
|
592
|
+
|
|
593
|
+
# ── Add button ─────────────────────────────────────────────
|
|
594
|
+
if show_add_delete and (isinstance(input_value, (list, dict, _BubblingDict)) or hasattr(input_value, "__dict__")):
|
|
595
|
+
def _instantiate_and_add(item_type):
|
|
596
|
+
if item_type is NoneType:
|
|
597
|
+
item_type = annotation_item_type(kwargs.get("annotation")) or NoneType
|
|
598
|
+
try:
|
|
599
|
+
new_item = item_type()
|
|
600
|
+
except Exception as e:
|
|
601
|
+
print(f"Could not instantiate {item_type} for add: {e}")
|
|
602
|
+
new_item = None
|
|
603
|
+
add_to_collection(input_value, new_item)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
if show_add_delete:
|
|
607
|
+
if flat_button(f"\uf067##add{unique}", draw_state,
|
|
608
|
+
view_id=f"hdr_add{unique}"):
|
|
609
|
+
_instantiate_and_add(new_item_type)
|
|
610
|
+
on_change = True
|
|
611
|
+
return_val = input_value
|
|
612
|
+
same_line()
|
|
613
|
+
|
|
614
|
+
# ── Typed add button ───────────────────────────────────
|
|
615
|
+
# A second + button whose item type comes from show_add_types — a
|
|
616
|
+
# {display_name: type} dict (a bare list/tuple keys itself by
|
|
617
|
+
# __name__). With several entries a small chevron dropdown picks the
|
|
618
|
+
# type: the pick persists BY DISPLAY NAME on draw_state.misc (a plain
|
|
619
|
+
# str survives save/load and hotswap; the type is re-resolved from
|
|
620
|
+
# show_add_types each frame) and the + relabels to "+ <name>". A
|
|
621
|
+
# single entry needs no choice — no chevron, the + is pre-bound to
|
|
622
|
+
# it. Until a type is picked the + falls back to the same hint-based
|
|
623
|
+
# default as the plain add button.
|
|
624
|
+
if show_add_types:
|
|
625
|
+
if not isinstance(show_add_types, dict):
|
|
626
|
+
show_add_types = {t.__name__: t for t in show_add_types}
|
|
627
|
+
if len(show_add_types) == 1:
|
|
628
|
+
sel_name = next(iter(show_add_types))
|
|
629
|
+
else:
|
|
630
|
+
sel_name = draw_state.misc.get("add_type_name")
|
|
631
|
+
sel_type = show_add_types.get(sel_name)
|
|
632
|
+
add_label = f"\uf067 {sel_name}" if sel_type is not None else "\uf067"
|
|
633
|
+
if flat_button(f"{add_label}##add_typed{unique}", draw_state,
|
|
634
|
+
view_id=f"hdr_add_typed{unique}"):
|
|
635
|
+
_instantiate_and_add(sel_type if sel_type is not None else new_item_type)
|
|
636
|
+
on_change = True
|
|
637
|
+
return_val = input_value
|
|
638
|
+
if len(show_add_types) > 1:
|
|
639
|
+
same_line(spacing=0)
|
|
640
|
+
_dd_pos = imgui.get_cursor_screen_pos()
|
|
641
|
+
type_changed, picked = RenderFuncs.draw_dropdown(
|
|
642
|
+
"\uf078", collection=show_add_types, z_offset=-1,
|
|
643
|
+
name=f"add_type{unique}", width=20, show_header=False, show_bg=False, show_button_bg=False,
|
|
644
|
+
show_name=False, shadow=False)
|
|
645
|
+
if type_changed and any(v is picked for v in show_add_types.values()):
|
|
646
|
+
draw_state.misc["add_type_name"] = next(
|
|
647
|
+
k for k, v in show_add_types.items() if v is picked)
|
|
648
|
+
draw_state.invalidate()
|
|
649
|
+
request_render()
|
|
650
|
+
# The dropdown's (hidden) popover window leaves the imgui cursor on
|
|
651
|
+
# a new line; same_line() would chain off that, so restore the
|
|
652
|
+
# cursor to just right of the chevron for the name that follows.
|
|
653
|
+
# Width mirrors the trigger's own sizing (passed width, with the
|
|
654
|
+
# glyph + compact pad 6 as the floor) so the name never starts
|
|
655
|
+
# inside the button.
|
|
656
|
+
_chev_w = max(24, imgui.calc_text_size("\uf078")[0] + 6)
|
|
657
|
+
imgui.set_cursor_screen_pos((_dd_pos[0] + _chev_w + 3, _dd_pos[1]))
|
|
658
|
+
else:
|
|
659
|
+
same_line()
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
# ── Name label / edit ──────────────────────────────────────
|
|
663
|
+
has_visible_name = show_name and name not in ("", None, "None")
|
|
664
|
+
|
|
665
|
+
if has_visible_name:
|
|
666
|
+
clipped_name = name.split("##")[0][:max_name_chars] + " "
|
|
667
|
+
if header_same_line:
|
|
668
|
+
text_width = imgui.calc_text_size(clipped_name)[0] - 4
|
|
669
|
+
else:
|
|
670
|
+
text_width = imgui.calc_text_size(clipped_name)[0]
|
|
671
|
+
|
|
672
|
+
if icon is not None:
|
|
673
|
+
imgui.align_text_to_frame_padding()
|
|
674
|
+
# The icon glyph renders 2px below the name text baseline.
|
|
675
|
+
_ix, _iy = imgui.get_cursor_screen_pos()
|
|
676
|
+
imgui.set_cursor_screen_pos((_ix, _iy - 2))
|
|
677
|
+
imgui.text_colored(icon, *Tint.icon_tint())
|
|
678
|
+
imgui.same_line()
|
|
679
|
+
_nx, _ny = imgui.get_cursor_screen_pos()
|
|
680
|
+
imgui.set_cursor_screen_pos((_nx, _ny + 2))
|
|
681
|
+
|
|
682
|
+
push_style_var(imgui.STYLE_FRAME_ROUNDING, name_rounding)
|
|
683
|
+
|
|
684
|
+
if not draw_state._name_edit:
|
|
685
|
+
|
|
686
|
+
draw_list: _DrawList = imgui.get_window_draw_list()
|
|
687
|
+
cursor_pos = imgui.get_cursor_screen_pos()
|
|
688
|
+
# Search-match highlight behind the key name (drawn before the text
|
|
689
|
+
# so glyphs stay readable). Strong fill + outline for the active
|
|
690
|
+
# (global-current) match; a faint fill for the rest. The flags are
|
|
691
|
+
# set by draw_collection when this header's key matches the query.
|
|
692
|
+
# Lifted to a HIGHER depth channel (restore after, the
|
|
693
|
+
# DrawState.draw_rect idiom): the halo spills past this row's rect,
|
|
694
|
+
# and at the content channel a sibling row's background composites
|
|
695
|
+
# over the spill — cropped edges and bake-order flicker.
|
|
696
|
+
if kwargs.get("search_match", False):
|
|
697
|
+
hx0, hy0 = cursor_pos[0], cursor_pos[1]
|
|
698
|
+
hx1 = cursor_pos[0] + text_width
|
|
699
|
+
hy1 = cursor_pos[1] + imgui.get_text_line_height()
|
|
700
|
+
if Melty.channels_split:
|
|
701
|
+
draw_list.channels_set_current(
|
|
702
|
+
min(Melty.get_channel() + 2, Melty.max_depth - 1))
|
|
703
|
+
draw_search_highlight(draw_list, hx0, hy0, hx1, hy1,
|
|
704
|
+
current=kwargs.get("search_current", False))
|
|
705
|
+
if Melty.channels_split:
|
|
706
|
+
draw_list.channels_set_current(Melty.get_channel())
|
|
707
|
+
packed_name_color = pack_color(*name_color[:3], 1.0)
|
|
708
|
+
draw_list.add_text(cursor_pos[0], cursor_pos[1], packed_name_color, clipped_name)
|
|
709
|
+
imgui.dummy(text_width, imgui.get_frame_height())
|
|
710
|
+
pop_style_var(1)
|
|
711
|
+
else:
|
|
712
|
+
name_width = min(text_width, min_name_text_width)
|
|
713
|
+
imgui.set_next_item_width(name_width)
|
|
714
|
+
edit_flags = imgui.INPUT_TEXT_ENTER_RETURNS_TRUE | imgui.INPUT_TEXT_AUTO_SELECT_ALL
|
|
715
|
+
changed, new_name = imgui.input_text(f"##edit{name}_{unique}", name, flags=edit_flags)
|
|
716
|
+
pop_style_var(1)
|
|
717
|
+
|
|
718
|
+
if changed or imgui.is_key_pressed(imgui.KEY_ESCAPE) or not imgui.is_item_active():
|
|
719
|
+
draw_state._name_edit = False
|
|
720
|
+
|
|
721
|
+
same_line(spacing=0)
|
|
722
|
+
|
|
723
|
+
end_x = imgui.get_cursor_screen_pos()[0]
|
|
724
|
+
|
|
725
|
+
# ── Ctrl+B on the header → the view function's source ──────
|
|
726
|
+
# Same landing as a global-search Code hit (_jump_to_symbol_def): the
|
|
727
|
+
# editor opens on the `def` of whatever function renders this view.
|
|
728
|
+
# Hover-scoped to the header's own rect, so the editor's Ctrl+B (usage
|
|
729
|
+
# jump, hover-routed to the text body) is untouched.
|
|
730
|
+
header_rect = (start_x, start_y, end_x, start_y + imgui.get_frame_height())
|
|
731
|
+
if draw_state.on_action("ctrl_b_down", view_id=f"hdr_jump{unique}",
|
|
732
|
+
rect=header_rect) is not None:
|
|
733
|
+
# Fire-and-forget flash on the header itself so the jump reads as
|
|
734
|
+
# "from here" while the editor comes to front (same flash as the
|
|
735
|
+
# editor's own jump landing / merge Apply).
|
|
736
|
+
Melty.emphasize(f"hdr_jump {draw_state.name}",
|
|
737
|
+
(header_rect[0] - 3, header_rect[1] - 2,
|
|
738
|
+
header_rect[2] + 3, header_rect[3] + 2))
|
|
739
|
+
_jump_to_view_source(draw_state)
|
|
740
|
+
|
|
741
|
+
# ── Profiler ───────────────────────────────────────────────
|
|
742
|
+
is_profiling = Toggles.profile_mode == ProfileMode.ON
|
|
743
|
+
if is_profiling:
|
|
744
|
+
from meltygui.view.diagnostic_view import render_profiler_time
|
|
745
|
+
render_profiler_time(
|
|
746
|
+
input_value=draw_state.render_time, brief=True,
|
|
747
|
+
style_manager=style_manager,
|
|
748
|
+
)
|
|
749
|
+
same_line(spacing=3)
|
|
750
|
+
|
|
751
|
+
pop_style_var(1)
|
|
752
|
+
|
|
753
|
+
if _font_pushed:
|
|
754
|
+
imgui.pop_font()
|
|
755
|
+
# Record the natural (pre-pad) header width so core_render can fold it into
|
|
756
|
+
# the parent window's running max for the next frame.
|
|
757
|
+
draw_state.header_natural_width = end_x - start_x
|
|
758
|
+
if kwargs.get("align_header", True) and show_name:
|
|
759
|
+
# Pad to the widest header in this window (tracked per-window), with the
|
|
760
|
+
# preferred width as a floor. Falls back to the floor when no window.
|
|
761
|
+
parent_window = draw_state.parent_window
|
|
762
|
+
pad_target = Toggles.Collection.preferred_header_width
|
|
763
|
+
if parent_window is not None:
|
|
764
|
+
pad_target = max(pad_target, parent_window.max_header_width)
|
|
765
|
+
if end_x - start_x < pad_target:
|
|
766
|
+
imgui.dummy(pad_target - (end_x - start_x), 1)
|
|
767
|
+
imgui.same_line(8)
|
|
768
|
+
|
|
769
|
+
return on_change, return_val
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
def draw_footer(input_value=None, name="", key=None, meltygui=None, parent_show_add_delete=False, width=0, suffix="",
|
|
773
|
+
collection=None, display_name=None, meta=None, unique=None, is_tree=True,
|
|
774
|
+
show_name=True, name_func=None, show_type=False, show_unique=False,
|
|
775
|
+
on_search=False, trigger_collapse=False, trigger_expand=False,
|
|
776
|
+
draw_state=None, show_tint=False, opacity=1.0, show_add_delete=True,
|
|
777
|
+
on_drag=False, on_action=None, style_manager=None,
|
|
778
|
+
**kwargs):
|
|
779
|
+
|
|
780
|
+
# for key, pending in draw_state._all_pending.items():
|
|
781
|
+
# if pending is not None:
|
|
782
|
+
# if pending.state == PendingState.ERROR:
|
|
783
|
+
# from meltygui.core.rendering.render_dispatch import draw_pending
|
|
784
|
+
# draw_pending(pending, name=f"{key}", tint=(1, 0, 0))
|
|
785
|
+
|
|
786
|
+
imgui.dummy(1,1)
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def draw_header_end(input_value=None, name="", show_close=True, key=None, meltygui=None, parent_show_add_delete=False,
|
|
790
|
+
collection=None, draw_state=None, closable=False, style_manager=None,
|
|
791
|
+
unique=None, show_tint=False, **kwargs):
|
|
792
|
+
from meltygui.core.layout.cursor_core import same_line
|
|
793
|
+
|
|
794
|
+
close_icon = ""
|
|
795
|
+
# Frame-redraw indicator (left of the close button): alternates these
|
|
796
|
+
# two glyphs every frame, so a header that repaints continuously flickers.
|
|
797
|
+
redraw_icon_0 = ""
|
|
798
|
+
redraw_icon_1 = ""
|
|
799
|
+
# [tint=(1.0, 1.0, 1.0)]
|
|
800
|
+
redraw_alpha = 0.1
|
|
801
|
+
redraw_pad_x = 3
|
|
802
|
+
|
|
803
|
+
if closable:
|
|
804
|
+
if show_tint:
|
|
805
|
+
# Paint-only (no dummy / same_line): the glyph must not claim
|
|
806
|
+
# layout in the right-aligned end-header group, or it would
|
|
807
|
+
# push the close button left. It hangs off the button's left
|
|
808
|
+
# edge, centered on the row height flat_button gives the
|
|
809
|
+
# button (text + px(8)).
|
|
810
|
+
button_height = imgui.calc_text_size(close_icon)[1] + Melty.px(8.0)
|
|
811
|
+
redraw_icon = [redraw_icon_0, redraw_icon_1][Melty.frame_count % 2]
|
|
812
|
+
# One slot as wide as the wider glyph, each glyph centered in it —
|
|
813
|
+
# anchoring by the glyph's own width made the two alternate ~2px apart.
|
|
814
|
+
slot_width = max(imgui.calc_text_size(redraw_icon_0)[0],
|
|
815
|
+
imgui.calc_text_size(redraw_icon_1)[0])
|
|
816
|
+
icon_width, icon_height = imgui.calc_text_size(redraw_icon)
|
|
817
|
+
button_x, row_top = imgui.get_cursor_screen_pos()
|
|
818
|
+
icon_x = button_x - redraw_pad_x - slot_width + (slot_width - icon_width) / 2
|
|
819
|
+
icon_y = row_top + (button_height - icon_height) / 2
|
|
820
|
+
redraw_color = pack_color(1, 1, 1, redraw_alpha)
|
|
821
|
+
imgui.get_window_draw_list().add_text(icon_x, icon_y, redraw_color, redraw_icon)
|
|
822
|
+
if show_close:
|
|
823
|
+
if flat_button(f"{close_icon}##{unique}", draw_state,
|
|
824
|
+
view_id=f"hdr_close{unique}",
|
|
825
|
+
color=(9, 1, 1)):
|
|
826
|
+
_was_closed = draw_state.closed
|
|
827
|
+
from meltygui.core.windowing.window_visibility import native_user_window_closed
|
|
828
|
+
native_user_window_closed(draw_state, not draw_state.closed)
|
|
829
|
+
from meltygui.state.core_undo import NavUndo
|
|
830
|
+
from meltygui.core.windowing.window_visibility import window_edit_is_local
|
|
831
|
+
if window_edit_is_local(draw_state, 'closed'):
|
|
832
|
+
NavUndo.record_window(draw_state, _was_closed, draw_state.closed)
|
|
833
|
+
Melty.cache.invalidate_up_by_obj(Melty.registered_windows)
|
|
834
|
+
|
|
835
|
+
# draw_state._parent.invalidate_up()
|
|
836
|
+
if draw_state.parent_window is not None:
|
|
837
|
+
draw_state.parent_window.invalidate_up()
|
|
838
|
+
|
|
839
|
+
draw_state.dlt_count = 0
|
|
840
|
+
# if draw_state.parent_window is not None:
|
|
841
|
+
# Melty.cache.invalidate_up(draw_state.parent_window._tile_id, max_depth=10)
|
|
842
|
+
else:
|
|
843
|
+
if parent_show_add_delete and collection is not None and key is not None:
|
|
844
|
+
if flat_button(f"\uf1f8##del{unique}", draw_state,
|
|
845
|
+
view_id=f"hdr_del{unique}",
|
|
846
|
+
color=(0.12, 0.002037035, 0.002037035), alpha=0.4):
|
|
847
|
+
Melty.to_delete(key, collection)
|
|
848
|
+
same_line(spacing=0.0)
|