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,1082 @@
|
|
|
1
|
+
"""Dropdown view functions and supporting definitions."""
|
|
2
|
+
from meltygui.core.runtime.paths import debug_log_path
|
|
3
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
4
|
+
from meltygui.hdr_color import pack_color
|
|
5
|
+
from meltygui.core.melty import Melty
|
|
6
|
+
from meltygui.core.core_render import render_func
|
|
7
|
+
from meltygui.core.rendering.core_decoration import Core
|
|
8
|
+
from meltygui.core.rendering.window_decoration import window
|
|
9
|
+
from meltygui.state.new_core_model import DropDownState
|
|
10
|
+
from meltygui.core.runtime.toggles import Tint
|
|
11
|
+
from meltygui.view.header_view import draw_header
|
|
12
|
+
from meltygui.view.header_view import flat_button
|
|
13
|
+
import meltygui_imgui as imgui
|
|
14
|
+
from meltygui.core.core_render import SCROLLBAR_MARGIN
|
|
15
|
+
from meltygui.core.core_render import SCROLL_BAR_WIDTH_DEFAULT
|
|
16
|
+
import traceback
|
|
17
|
+
|
|
18
|
+
# Inset of a left/right aligned trigger label from the trigger edge, in pixels.
|
|
19
|
+
# Raise it to move the caret and label further from the edge.
|
|
20
|
+
TRIGGER_TEXT_INSET = 5
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@render_func(use_cache=True, temp=True)
|
|
24
|
+
def draw_drop_down_item(input_value, name="", unique=0, shadow=False, draw_state=None, **kwargs):
|
|
25
|
+
from meltygui.view.control_view import button
|
|
26
|
+
|
|
27
|
+
hovered = draw_state._bounding_hovered
|
|
28
|
+
clicked, _ = button(name, name=f"{unique}{name}_dd_item", show_bg=True, height=25,
|
|
29
|
+
shadow=False, hovered=hovered)
|
|
30
|
+
|
|
31
|
+
# Hover highlight: paint a translucent wash over this item's own box (its
|
|
32
|
+
# draw_state is the row, so abs_left/abs_top + width/height frame it exactly)
|
|
33
|
+
# straight onto the window draw list so it sits over the button fill.
|
|
34
|
+
if hovered:
|
|
35
|
+
dl = imgui.get_window_draw_list()
|
|
36
|
+
dl.add_rect_filled(draw_state.abs_left, draw_state.abs_top,
|
|
37
|
+
draw_state.abs_left + draw_state.width,
|
|
38
|
+
draw_state.abs_top + draw_state.height,
|
|
39
|
+
pack_color(1, 1, 1, 0.16),
|
|
40
|
+
rounding=getattr(draw_state, 'corner_radius', 6))
|
|
41
|
+
|
|
42
|
+
if clicked:
|
|
43
|
+
return True, name
|
|
44
|
+
|
|
45
|
+
return False, input_value
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@render_func(use_cache=True, show_bg=False, selectable=False,
|
|
49
|
+
tint=(0.083, 0.10, 0.144),
|
|
50
|
+
is_tree=False, show_name=True, with_header=draw_header)
|
|
51
|
+
@window
|
|
52
|
+
def draw_dropdown(input_value, collection, name, draw_state, unique, drop_down_state: DropDownState, shadow=True,
|
|
53
|
+
text_align="left", open_upwards=None, menu_min_width=None, display_label=None, **kwargs):
|
|
54
|
+
"""Root of a recursive dropdown. Renders a trigger button showing the current
|
|
55
|
+
selection; clicking it opens the (click-o-open) root popover. Nested dict
|
|
56
|
+
rows inside the popover open their own sub-menus on hover. Returns
|
|
57
|
+
(changed, selected_leaf) when the user picks a value. `open_upwards`
|
|
58
|
+
defaults to choosing the side with room. True forces above, False below;
|
|
59
|
+
`menu_min_width` sets its width floor.
|
|
60
|
+
|
|
61
|
+
Open/closed is a single global slot -- ``Melty.popover_focused_ds`` holds the
|
|
62
|
+
draw_state of whichever dropdown's popover is currently shown. Each dropdown
|
|
63
|
+
decides it's open by identity (``popover_focused_ds is draw_state``), so
|
|
64
|
+
opening one popover implicitly closes every other (they all fail the test).
|
|
65
|
+
``drop_down_state`` is the per-view scratch object the framework re-injects
|
|
66
|
+
every frame; we stash the last picked leaf on it for the trigger label."""
|
|
67
|
+
from meltygui.core.conversion.cache_tree import UNSET_VALUE
|
|
68
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
69
|
+
from meltygui.model.dropdown_model import _dd_as_tuple
|
|
70
|
+
from meltygui.core.layout.dropdown_core import _dd_close
|
|
71
|
+
from meltygui.core.layout.dropdown_core import _dd_handle_keys
|
|
72
|
+
from meltygui.model.dropdown_model import _dd_label_for_path
|
|
73
|
+
from meltygui.model.dropdown_model import _dd_path_for_value
|
|
74
|
+
from meltygui.model.dropdown_model import _dd_walk
|
|
75
|
+
import meltygui.core.windowing.window_api as glfw
|
|
76
|
+
|
|
77
|
+
from meltygui.core.rendering.mode import Mode
|
|
78
|
+
|
|
79
|
+
# DEBUG
|
|
80
|
+
# Is THIS dropdown the one whose popover is showing?
|
|
81
|
+
is_open = Melty.popover_focused_ds is draw_state
|
|
82
|
+
_DD_DBG = False # TEMP: default False for dropdown-close investigation
|
|
83
|
+
if _DD_DBG:
|
|
84
|
+
_pf = Melty.popover_focused_ds
|
|
85
|
+
if is_open or _pf is not None:
|
|
86
|
+
try:
|
|
87
|
+
_mx, _my = imgui.get_mouse_pos()
|
|
88
|
+
_clk = imgui.is_mouse_clicked(0);
|
|
89
|
+
_dn = imgui.is_mouse_down(0)
|
|
90
|
+
except Exception:
|
|
91
|
+
_mx = _my = -1.0;
|
|
92
|
+
_clk = _dn = None
|
|
93
|
+
with open(debug_log_path("dd_debug.log"), "a") as _f:
|
|
94
|
+
_f.write(f"[DD-DBG] f={Melty.frame_count} name={name!r} ds={id(draw_state)} "
|
|
95
|
+
f"pf={id(_pf) if _pf is not None else None} is_open={is_open} "
|
|
96
|
+
f"mouse=({_mx:.0f},{_my:.0f}) clk={_clk} down={_dn} "
|
|
97
|
+
f"sel_path={getattr(drop_down_state, 'selected_path', ())} "
|
|
98
|
+
f"open_frame={getattr(Melty, '_popover_open_frame', None)}\n")
|
|
99
|
+
|
|
100
|
+
# Label shows the last pick (sticky across frames via drop_down_state),
|
|
101
|
+
# falling back to the raw input value.
|
|
102
|
+
# Title shows the LABEL/key of the current selection (e.g. "red"), not the raw
|
|
103
|
+
# value (which may be a tuple/number); selected_label is stamped at pick-time.
|
|
104
|
+
# The caller's input_value is the selection of record: when that MOVES
|
|
105
|
+
# (the caller adopted a pick, a nav-undo replay, a selection carried
|
|
106
|
+
# over from elsewhere), the text follows it - the label/path of the
|
|
107
|
+
# leaf holding that value - instead of whatever selected_label was last
|
|
108
|
+
# stamped. The stamp stays put while input_value holds still (callers
|
|
109
|
+
# passing a static value rely on the last pick showing). Callers used to
|
|
110
|
+
# re-stamp selected_label themselves after the draw: on the pick frame
|
|
111
|
+
# their input was still the OLD value, so the old label went back into
|
|
112
|
+
# the state, and with this body frozen the wrong text sat until a
|
|
113
|
+
# hover repaint. Arriving here runs on the frame the input changed -
|
|
114
|
+
# which is a frame before body runs (the input is the tile's hash).
|
|
115
|
+
if input_value != getattr(drop_down_state, "_dd_last_input", UNSET_VALUE):
|
|
116
|
+
drop_down_state._dd_last_input = input_value
|
|
117
|
+
_in_path = _dd_path_for_value(collection, input_value)
|
|
118
|
+
if _in_path is not None:
|
|
119
|
+
drop_down_state.selected_path = _in_path
|
|
120
|
+
drop_down_state.selected_label = _dd_label_for_path(collection, _in_path)
|
|
121
|
+
_sel_label = getattr(drop_down_state, "selected_label", "") or ""
|
|
122
|
+
current = display_label if display_label is not None else (_sel_label if _sel_label else (str(input_value) if input_value is not None else ""))
|
|
123
|
+
caret = "" if is_open else "" # fa-chevron-down / fa-chevron-right
|
|
124
|
+
|
|
125
|
+
# Compact mode: in a very narrow slot (e.g. an inline table cell) there's no room
|
|
126
|
+
# for the caret + button chrome, so show NOTHING but the selected value. Still a
|
|
127
|
+
# real (bg-less) button, so it stays clickable to open the popover. Threshold is
|
|
128
|
+
# tunable via compact_below (px).
|
|
129
|
+
#
|
|
130
|
+
# The trigger's slot width: an explicit caller `width` wins, only without one
|
|
131
|
+
# do we fall back to the measured content_width. Measurement must never feed
|
|
132
|
+
# back into the trigger size - an open popover inflates content_width, which
|
|
133
|
+
# would flip compact mode off and balloon the trigger (~240px), wrapping the
|
|
134
|
+
# header row it sits in: the disagreement between the drawn size and the
|
|
135
|
+
# measured item rect is exactly what reads as animation jitter.
|
|
136
|
+
_slot_w = kwargs.get("width") or draw_state.content_width
|
|
137
|
+
compact = False
|
|
138
|
+
|
|
139
|
+
# A compact trigger hugs its glyph: minimal text pad and a centered label,
|
|
140
|
+
# so a small chevron/icon cell doesn't balloon to full text label width.
|
|
141
|
+
trigger_pad = kwargs.get("text_pad", 6 if compact else 15)
|
|
142
|
+
trigger_align = "center" if compact else text_align
|
|
143
|
+
trigger_w = max(15 if compact else 18, _slot_w)
|
|
144
|
+
|
|
145
|
+
# The label must FIT the fixed trigger width in pixels - a long label
|
|
146
|
+
# overflows the button rect and the measured item rect disagrees with
|
|
147
|
+
# the drawn size, the same disagreement the width comment above calls
|
|
148
|
+
# out as height jitter. Ellipsis-trim via a text width, not a
|
|
149
|
+
# character count (glyph widths vary wildly with font/monospace).
|
|
150
|
+
_label_px = max(4.0, trigger_w - trigger_pad * 2)
|
|
151
|
+
if compact:
|
|
152
|
+
# Show the VALUE itself (not the label/key). For a name->glyph dropdown
|
|
153
|
+
# the trigger must hug the glyph, not become the picked value's name.
|
|
154
|
+
drop_down_display_str = _dd_fit_label(
|
|
155
|
+
str(input_value if input_value is not None else current), _label_px)
|
|
156
|
+
else:
|
|
157
|
+
drop_down_display_str = _dd_fit_label(f"{caret} {str(current)}", _label_px)
|
|
158
|
+
|
|
159
|
+
# A caller's trigger_height taller than the 25 px slot also moves the popover anchor down.
|
|
160
|
+
trigger_h = (getattr(draw_state, "content_height", 0) or 25) if compact else max(25, kwargs.get("trigger_height", 25))
|
|
161
|
+
# Quiet text dimming (text_toward_bg - the info tab's info rows): a
|
|
162
|
+
# selection WITHOUT an embedded tint fades toward the background, so a
|
|
163
|
+
# tinted one (the active source in yellow) is what draws the eye.
|
|
164
|
+
_ttb = kwargs.get("text_toward_bg", 0.0)
|
|
165
|
+
trigger_text_value = 1.023
|
|
166
|
+
if _ttb and getattr(input_value, "tint", None) is None:
|
|
167
|
+
trigger_text_value = 1.023 * (1.0 - min(max(float(_ttb), 0.0), 1.0))
|
|
168
|
+
trigger_left, trigger_top = imgui.get_cursor_screen_pos()
|
|
169
|
+
# Draw-list trigger: the click is claimed through this dropdown's own
|
|
170
|
+
# draw_state (no nested render_func). It fires on press, as the old button did.
|
|
171
|
+
# An embedded tint colours the trigger; otherwise flat_button's default hue applies.
|
|
172
|
+
embedded_tint = _dd_obj_tint(input_value)
|
|
173
|
+
trigger_color = {} if embedded_tint is None else {"color": embedded_tint}
|
|
174
|
+
label_width = imgui.calc_text_size(drop_down_display_str.split("##")[0]).x
|
|
175
|
+
if trigger_align == "left":
|
|
176
|
+
trigger_text_offset = TRIGGER_TEXT_INSET
|
|
177
|
+
elif trigger_align == "right":
|
|
178
|
+
trigger_text_offset = trigger_w - label_width - TRIGGER_TEXT_INSET
|
|
179
|
+
else:
|
|
180
|
+
trigger_text_offset = None
|
|
181
|
+
clicked = flat_button(drop_down_display_str, draw_state, view_id=f"{name}_dd_trigger{unique}",
|
|
182
|
+
width=trigger_w, height=trigger_h,
|
|
183
|
+
alpha=1.0 if kwargs.get("show_button_bg", True) else 0.0,
|
|
184
|
+
shadow=shadow, text_value=trigger_text_value, text_pad=trigger_pad,
|
|
185
|
+
text_offset_x=trigger_text_offset, event="left_mouse_down", **trigger_color)
|
|
186
|
+
|
|
187
|
+
if clicked:
|
|
188
|
+
|
|
189
|
+
was_open = is_open
|
|
190
|
+
if _DD_DBG:
|
|
191
|
+
with open(debug_log_path("dd_debug.log"), "a") as _f:
|
|
192
|
+
_f.write(f"[DD-DBG] CLICK toggle f={Melty.frame_count} name={name!r} was_open={was_open}\n")
|
|
193
|
+
Melty.popover_focused_ds = None if is_open else draw_state
|
|
194
|
+
is_open = Melty.popover_focused_ds is draw_state
|
|
195
|
+
if is_open and not was_open:
|
|
196
|
+
Melty._popover_open_frame = Melty.frame_count # grace the opening click
|
|
197
|
+
# Fresh open: start with an empty query and give the search box a few
|
|
198
|
+
# frames to grab text focus so the user can type to filter immediately.
|
|
199
|
+
drop_down_state.search_query = ""
|
|
200
|
+
drop_down_state.search = ""
|
|
201
|
+
drop_down_state._focus_search = 8 if len(collection) > 4 else 0
|
|
202
|
+
if len(collection) <= 4:
|
|
203
|
+
Melty.clear_focus(not_this=draw_state)
|
|
204
|
+
# Start the highlight on the last-selected item (expanded to it) rather
|
|
205
|
+
# than the top, so re-opening starts where you left off.
|
|
206
|
+
_sp = _dd_as_tuple(getattr(drop_down_state, "selected_path", ()))
|
|
207
|
+
drop_down_state.cursor_path = _sp
|
|
208
|
+
drop_down_state.open_path = _sp[:-1] if _sp else ()
|
|
209
|
+
drop_down_state._kbd_mode = True
|
|
210
|
+
drop_down_state._last_mouse = None
|
|
211
|
+
drop_down_state._had_focus = False
|
|
212
|
+
if not is_open:
|
|
213
|
+
_dd_close(drop_down_state)
|
|
214
|
+
draw_state.invalidate()
|
|
215
|
+
|
|
216
|
+
request_render()
|
|
217
|
+
|
|
218
|
+
# The popover is a latching window -- the first call registers it and it
|
|
219
|
+
# stays alive, so we always call it and toggle visibility with `closed`
|
|
220
|
+
# rather than skipping the call (skipping would leave the last-open frame
|
|
221
|
+
# painted). closed=True hides the whole subtree. window_pos pins it just
|
|
222
|
+
# under the trigger (relative to this dropdown window) so it doesn't drift
|
|
223
|
+
# off as a free-floating draggable; temp keeps it ephemeral. root_state
|
|
224
|
+
# carries the single open-path the recursion expands; path_prefix starts
|
|
225
|
+
# empty at the root. A pick bubbles back as (changed, value).
|
|
226
|
+
if is_open:
|
|
227
|
+
# A mouse move switches back to hover mode so the highlight follows the
|
|
228
|
+
# pointer again (until the next arrow key locks keyboard mode).
|
|
229
|
+
_mp = imgui.get_mouse_pos()
|
|
230
|
+
_lm = getattr(drop_down_state, "_last_mouse", None)
|
|
231
|
+
if _lm is not None and (abs(_mp[0] - _lm[0]) > 0.5 or abs(_mp[1] - _lm[1]) > 0.5):
|
|
232
|
+
drop_down_state._kbd_mode = False
|
|
233
|
+
drop_down_state._last_mouse = (_mp[0], _mp[1])
|
|
234
|
+
|
|
235
|
+
# Size from the current rows, never the previous window's clipped bounds.
|
|
236
|
+
# The model catalog may arrive after opening, and a search may temporarily
|
|
237
|
+
# leave the items. Neither may become the next menu's permanent size.
|
|
238
|
+
menu_width, menu_height, menu_top = _dd_popup_geometry(
|
|
239
|
+
collection, getattr(drop_down_state, "search_query", ""),
|
|
240
|
+
trigger_top, trigger_h, open_upwards, max(trigger_w, menu_min_width or 0))
|
|
241
|
+
menu_left = max(0, min(trigger_left, imgui.get_io().display_size[0] - menu_width))
|
|
242
|
+
changed, new_item, menu_ds = draw_dd_menu(
|
|
243
|
+
collection, tint=draw_state.tint,
|
|
244
|
+
name=f"{unique}_menu",
|
|
245
|
+
closed=not is_open, temp=True, shadow=False, auto_resize=False,
|
|
246
|
+
window_pos=(menu_left - imgui.get_cursor_screen_pos()[0], menu_top - imgui.get_cursor_screen_pos()[1]),
|
|
247
|
+
width=menu_width, height=menu_height,
|
|
248
|
+
parent_window=draw_state, swoosh=False, disable_scroll=False,
|
|
249
|
+
row_tags=kwargs.get("row_tags"),
|
|
250
|
+
row_tints=kwargs.get("row_tints"),
|
|
251
|
+
row_actions=kwargs.get("row_actions"),
|
|
252
|
+
text_toward_bg=kwargs.get("text_toward_bg", 0.0),
|
|
253
|
+
root_state=drop_down_state, path_prefix=(), return_extras=True)
|
|
254
|
+
drop_down_state._menu_ds = menu_ds
|
|
255
|
+
if is_open:
|
|
256
|
+
pending = getattr(drop_down_state, "_pending_pick", None)
|
|
257
|
+
if pending is not None:
|
|
258
|
+
drop_down_state._pending_pick = None
|
|
259
|
+
drop_down_state._picked_path = _dd_as_tuple(pending)
|
|
260
|
+
changed, new_item = True, _dd_walk(collection, _dd_as_tuple(pending))
|
|
261
|
+
if changed:
|
|
262
|
+
_p = _dd_as_tuple(getattr(drop_down_state, "_picked_path", ()))
|
|
263
|
+
drop_down_state.selected_path = _p
|
|
264
|
+
drop_down_state.selected_label = _dd_label_for_path(collection, _p)
|
|
265
|
+
if _DD_DBG:
|
|
266
|
+
with open(debug_log_path("dd_debug.log"), "a") as _f:
|
|
267
|
+
_f.write(f"[DD-DBG] CLOSE via menu-pick f={Melty.frame_count} name={name!r} picked={_p}\n")
|
|
268
|
+
Melty.popover_focused_ds = None # picking dismisses the popover
|
|
269
|
+
_dd_close(drop_down_state)
|
|
270
|
+
draw_state.invalidate()
|
|
271
|
+
request_render()
|
|
272
|
+
return True, new_item
|
|
273
|
+
|
|
274
|
+
# The dropdown owns the keyboard while open: its search box holds text
|
|
275
|
+
# focus (taken on open, released on close), so OTHER text editors gate off
|
|
276
|
+
# (they all check Melty.text_focused_ds) and never act on the same keys. We
|
|
277
|
+
# only handle nav keys while we actually hold that focus - that's what
|
|
278
|
+
# keeps the arrow/Enter/Esc collisions with whatever editor was active.
|
|
279
|
+
box_tile = getattr(drop_down_state, "_search_box_tile", None)
|
|
280
|
+
text_focused = (Melty.text_focused_ds is not None and box_tile is not None
|
|
281
|
+
and getattr(Melty.text_focused_ds, "_tile_id", None) == box_tile)
|
|
282
|
+
if len(collection) <= 4:
|
|
283
|
+
text_focused = True
|
|
284
|
+
|
|
285
|
+
# Esc dismisses the open dropdown (and releases its text focus via
|
|
286
|
+
# _dd_close). Ungated: the global text box Esc handler may have already
|
|
287
|
+
# cleared the box's focus this same frame, so we don't require it here.
|
|
288
|
+
if any(k == glfw.KEY_ESCAPE for k, _ in Core.melty.frame_key_events):
|
|
289
|
+
if _DD_DBG:
|
|
290
|
+
with open(debug_log_path("dd_debug.log"), "a") as _f:
|
|
291
|
+
_f.write(f"[DD-DBG] CLOSE via Esc f={Melty.frame_count} name={name!r}\n")
|
|
292
|
+
Melty.popover_focused_ds = None
|
|
293
|
+
_dd_close(drop_down_state)
|
|
294
|
+
draw_state.invalidate()
|
|
295
|
+
request_render()
|
|
296
|
+
return False, input_value
|
|
297
|
+
|
|
298
|
+
# Arrows / Enter only while we own the keyboard, so they don't also drive
|
|
299
|
+
# whatever editor was active when the dropdown opened.
|
|
300
|
+
if text_focused:
|
|
301
|
+
search = getattr(drop_down_state, "search", "") or ""
|
|
302
|
+
picked = _dd_handle_keys(collection, drop_down_state, search=search,
|
|
303
|
+
text_focused=text_focused)
|
|
304
|
+
if picked is not UNSET_VALUE:
|
|
305
|
+
_p = _dd_as_tuple(getattr(drop_down_state, "_picked_path", ()))
|
|
306
|
+
drop_down_state.selected_path = _p
|
|
307
|
+
drop_down_state.selected_label = _dd_label_for_path(collection, _p)
|
|
308
|
+
Melty.popover_focused_ds = None
|
|
309
|
+
_dd_close(drop_down_state)
|
|
310
|
+
draw_state.invalidate()
|
|
311
|
+
request_render()
|
|
312
|
+
return True, picked
|
|
313
|
+
|
|
314
|
+
# Window press dispatch owns click-away dismissal. It uses the captured
|
|
315
|
+
# press position and both parent links; a second post-imgui check here
|
|
316
|
+
# races deferred menu rendering and mistakes nested menu clicks for exits.
|
|
317
|
+
|
|
318
|
+
# Focus settle (bounded, NOT a permanent repaint loop): right after open
|
|
319
|
+
# the search box asks for text focus, but the opening click's
|
|
320
|
+
# request_focus can race it the same frame. While the box hasn't
|
|
321
|
+
# confirmed focus and we're still within the small retry budget, re-run
|
|
322
|
+
# so it asks again; it gets within a frame or two and this stops.
|
|
323
|
+
# Steady-state open repaints nothing - mouse changes invalidate via
|
|
324
|
+
# _dd_set_cursor, keys via begin_frame. The retry must invalidate the
|
|
325
|
+
# BOX's own chain (invalidate_up from its tile): the menu is a separate
|
|
326
|
+
# cached window subtree, so invalidating just this trigger view never
|
|
327
|
+
# re-rendered the box on a reopen and request_focus never re-fired.
|
|
328
|
+
if getattr(drop_down_state, "_focus_search", 0) > 0:
|
|
329
|
+
box_tile = getattr(drop_down_state, "_search_box_tile", None)
|
|
330
|
+
if box_tile is not None:
|
|
331
|
+
Melty.cache.invalidate_up(box_tile, force=True)
|
|
332
|
+
Melty.cache.invalidate(draw_state._tile_id, force=True)
|
|
333
|
+
request_render()
|
|
334
|
+
|
|
335
|
+
return False, input_value
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@render_func(use_cache=True, show_bg=True, shadow=True, selectable=False, temp=True,
|
|
339
|
+
closable=True, popover=True, melty_window=False, auto_resize=True, with_header=None,
|
|
340
|
+
max_height=420, min_width=300, swoosh=False, min_height=33, keep_in_view=True)
|
|
341
|
+
def draw_dd_menu(input_value, draw_state, root_state=None, unique=0, path_prefix=(), tint=None,
|
|
342
|
+
show_search=None, text_align="right", row_tags=None, row_tints=None,
|
|
343
|
+
row_suffixes=None, row_actions=None, text_toward_bg=0.0,
|
|
344
|
+
full_render=False, row_code=None, **kwargs):
|
|
345
|
+
"""One level of the dropdown, drawn as its own temp popover window. Iterates
|
|
346
|
+
the level's entries and renders each as a row (`_dd_menu_row`); a leaf click
|
|
347
|
+
or a pick inside a nested sub-menu bubbles back up as (changed, value).
|
|
348
|
+
|
|
349
|
+
Expansion is driven by `root_state.open_path` (a single chain of keys, see
|
|
350
|
+
DropDownState) — NOT by each row testing its own hover. A row renders its
|
|
351
|
+
sub-menu only when `open_path` runs through it, so at most one sub-menu is
|
|
352
|
+
open per level and hidden siblings can never resurface. `path_prefix` is this
|
|
353
|
+
level's key chain from the root; each row's full path is prefix + its key.
|
|
354
|
+
|
|
355
|
+
`show_search` draws the root-level filter box. Autocomplete (the code editor's
|
|
356
|
+
suggestion popup) passes False: the editor itself owns text focus and the
|
|
357
|
+
half-typed identifier IS the filter, so a second focus-stealing search box
|
|
358
|
+
would fight it. The caller pre-filters the rows in that case.
|
|
359
|
+
|
|
360
|
+
The popover and its rows are CACHED tiles with NO kwargs cache key —
|
|
361
|
+
a clean row tile blit-skips even when its cursor/open path inputs changed.
|
|
362
|
+
Repaints are therefore driven by explicit invalidation only: an open_path
|
|
363
|
+
change cascades invalidate_up from the menu tile inside _dd_set_cursor
|
|
364
|
+
(this is what re-runs a stale branch row so it stamps its submenu window
|
|
365
|
+
closed — see the leak note there), keys via the begin_frame popover hook
|
|
366
|
+
(or the code editor's per-event invalidate_up) — invalidate_up
|
|
367
|
+
specifically, since it cascades to the row tiles; a plain invalidate
|
|
368
|
+
leaves the inner dd_rows collection clean and it blit-skips."""
|
|
369
|
+
from meltygui.core.conversion.cache_tree import UNSET_VALUE
|
|
370
|
+
from meltygui.core.styling.fonts import Font
|
|
371
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
372
|
+
from meltygui.view.text_view import draw_text
|
|
373
|
+
from meltygui.model.dropdown_model import _dd_as_tuple
|
|
374
|
+
from meltygui.model.dropdown_model import _dd_first_match_leaf
|
|
375
|
+
from meltygui.model.dropdown_model import _dd_row_lookup
|
|
376
|
+
from meltygui.core.layout.dropdown_core import _dd_set_cursor
|
|
377
|
+
from meltygui.model.dropdown_model import _dd_visible_entries
|
|
378
|
+
|
|
379
|
+
if show_search is None:
|
|
380
|
+
show_search = len(input_value) > 4
|
|
381
|
+
if show_search and not path_prefix and root_state is not None:
|
|
382
|
+
# Root owns the search box. Single-line so Up/Down/Enter pass through to
|
|
383
|
+
# menu nav; it auto-focuses once when the menu opens (_focus_search).
|
|
384
|
+
q = getattr(root_state, "search_query", "") or ""
|
|
385
|
+
box = draw_text(q, name=f"dd_search{unique}", show_name=False, searchable=False,
|
|
386
|
+
single_line=True, is_search_box=True, is_tree=False,
|
|
387
|
+
with_header=None, with_footer=None, show_bg=True, shadow=False,
|
|
388
|
+
request_focus=getattr(root_state, "_focus_search", 0) > 0,
|
|
389
|
+
font=Font.JETBRAINS_MONO_19,
|
|
390
|
+
tint=tint, return_extras=True)
|
|
391
|
+
q_changed, new_q = box[0], box[1]
|
|
392
|
+
box_ds = box[2] if len(box) > 2 else None
|
|
393
|
+
if box_ds is not None:
|
|
394
|
+
root_state._search_box_tile = box_ds._tile_id
|
|
395
|
+
# Focus retry is a bounded countdown: the first popover's
|
|
396
|
+
# move-to-front clears text focus the frame the box first grabs it
|
|
397
|
+
# (apply_move_to_front(): parent != front window), so a one-shot
|
|
398
|
+
# request is lost. Re-request for a few frames until it lands, then
|
|
399
|
+
# clear (0). Draw_dropdown drives the re-runs while this is > 0.
|
|
400
|
+
if Melty.text_focused_ds is box_ds:
|
|
401
|
+
root_state._focus_search = 0
|
|
402
|
+
elif getattr(root_state, "_focus_search", 0) > 0:
|
|
403
|
+
root_state._focus_search -= 1
|
|
404
|
+
new_search = str(new_q or "").strip().lower()
|
|
405
|
+
if q_changed:
|
|
406
|
+
root_state.search_query = new_q
|
|
407
|
+
# Jump the cursor onto the first matching leaf, auto-expanding all
|
|
408
|
+
# branches above it, so the leaf is visible and one Enter selects it
|
|
409
|
+
# (instead of Enter-to-open-then-Enter-to-pick).
|
|
410
|
+
if new_search:
|
|
411
|
+
leaf = _dd_first_match_leaf(input_value, new_search)
|
|
412
|
+
if leaf is not None:
|
|
413
|
+
_dd_set_cursor(root_state, leaf, False, menu_ds=draw_state)
|
|
414
|
+
else:
|
|
415
|
+
_dd_set_cursor(root_state, (), False, menu_ds=draw_state)
|
|
416
|
+
else:
|
|
417
|
+
_dd_set_cursor(root_state, (), False, menu_ds=draw_state)
|
|
418
|
+
root_state.search = new_search
|
|
419
|
+
|
|
420
|
+
search = str(getattr(root_state, "search", "") or "")
|
|
421
|
+
# Cursor/open paths are passed down as ROW INPUTS (not read by the row's
|
|
422
|
+
# root_state): a row is a use_cache=True tile, so its highlight only repaints
|
|
423
|
+
# when an input changes. Throwing the paths through makes keyboard nav and the
|
|
424
|
+
# default-selection-on-open repaint (hover repaints via a separate hook).
|
|
425
|
+
open_path = _dd_as_tuple(getattr(root_state, "open_path", ()))
|
|
426
|
+
cursor_path = _dd_as_tuple(getattr(root_state, "cursor_path", ()))
|
|
427
|
+
|
|
428
|
+
# Stale-submenu sweep (root level only - the registry is keyed by full row
|
|
429
|
+
# path, so one sweep covers every depth): a branch row stamps its submenu
|
|
430
|
+
# `closed` only when the row body runs, and an active search can filter
|
|
431
|
+
# the row itself out of `rows` while its submenu is open - the submenu
|
|
432
|
+
# window then floats on with nothing left to close it. dd_menu_row
|
|
433
|
+
# registers its submenu draw_state in root_state._dd_submenu_ds; the
|
|
434
|
+
# root body re-runs on every query/open_path change, so close anything
|
|
435
|
+
# here that's no longer in the open path. The row re-registers/reopens it
|
|
436
|
+
# if it ever comes back on-path.
|
|
437
|
+
if not path_prefix and root_state is not None:
|
|
438
|
+
for _sp, _sds in list(getattr(root_state, "_dd_submenu_ds", {}).items()):
|
|
439
|
+
if (_sds is not None and open_path[:len(_sp)] != _sp
|
|
440
|
+
and not getattr(_sds, "closed", True)):
|
|
441
|
+
_sds.closed = True
|
|
442
|
+
request_render()
|
|
443
|
+
|
|
444
|
+
ancestor_matched = bool(search) and any(search in str(k).lower() for k in path_prefix)
|
|
445
|
+
rows = _dd_visible_entries(input_value, "" if ancestor_matched else search)
|
|
446
|
+
# if not rows and search:
|
|
447
|
+
# imgui.dummy(180, 6)
|
|
448
|
+
# text(" No matches", width=180, height=_DD_ROW_H, name="dd_nomatch",
|
|
449
|
+
# text_colour=(1, 1, 1))
|
|
450
|
+
# return False, None
|
|
451
|
+
|
|
452
|
+
# Per-row kwargs shared by both render paths. Each row is a
|
|
453
|
+
# (key, value, label, is_branch) tuple handed to dd_menu_row, which computes
|
|
454
|
+
# its own path/cursor/open state from path_prefix + root_state. full_render is
|
|
455
|
+
# threaded down so nested sub-menus inherit the same render path.
|
|
456
|
+
row_kwargs = dict(show_bg=False, shadow=False, path_prefix=tuple(path_prefix),
|
|
457
|
+
root_state=root_state, tint=tint, text_align=text_align, z_offset=0,
|
|
458
|
+
row_tags=row_tags, row_tints=row_tints, row_suffixes=row_suffixes,
|
|
459
|
+
cursor_path=cursor_path,
|
|
460
|
+
open_path=open_path, full_render=full_render)
|
|
461
|
+
|
|
462
|
+
# Manual row loop (the dd_collection full_render path is gone - its
|
|
463
|
+
# per-row render_func tiles cost more than they saved; virtualization is
|
|
464
|
+
# done directly below instead). Branch rows still go through the
|
|
465
|
+
# dd_menu_row render_func (they own a nested submenu + a real draw_state);
|
|
466
|
+
# leaf rows - the bulk of a big list - are drawn inline by _dd_leaf_row
|
|
467
|
+
# with raw imgui, skipping the per-row wrapper overhead that made long
|
|
468
|
+
# menus crawl while interacting.
|
|
469
|
+
result = (False, input_value)
|
|
470
|
+
|
|
471
|
+
# Viewport culling for big flat menus (the 967-icon picker): rows are a
|
|
472
|
+
# fixed _DD_ROW_H pitch, so a leaf row scrolled outside the window band
|
|
473
|
+
# skips its draw entirely and reserves the space with a dummy. The width
|
|
474
|
+
# is the cached widest label width so the auto_resize width doesn't
|
|
475
|
+
# jitter with the visible set (the cache recomputes once per search
|
|
476
|
+
# change, not per frame). Branch rows are never culled: a parent stamps
|
|
477
|
+
# `closed` on their submenu window each run (see the leak note in
|
|
478
|
+
# _dd_set_cursor). Gated on a known height - the first frame draws
|
|
479
|
+
# everything once - and on row count so small menus keep the simple path.
|
|
480
|
+
_cull_top = _cull_bot = None
|
|
481
|
+
if root_state is not None and len(rows) >= 40 and draw_state.height:
|
|
482
|
+
_wt = draw_state._abs_top()
|
|
483
|
+
_cull_top, _cull_bot = _wt, _wt + draw_state.height
|
|
484
|
+
_wkey = (len(rows), search)
|
|
485
|
+
if getattr(root_state, "_row_w_key", None) != _wkey:
|
|
486
|
+
_mw = 0.0
|
|
487
|
+
for (_k, _v, _lbl, _b) in rows:
|
|
488
|
+
_sfx = _dd_row_lookup(row_suffixes, _v) if row_suffixes else None
|
|
489
|
+
_tag = _dd_row_lookup(row_tags, _v) if row_tags else None
|
|
490
|
+
_mw = max(_mw, imgui.calc_text_size(str(_lbl) + (_sfx or ""))[0]
|
|
491
|
+
+ (_dd_tag_width(_tag) + 16.0 if _tag else 0.0))
|
|
492
|
+
root_state._row_w_key = _wkey
|
|
493
|
+
root_state._row_w = _mw + 24.0
|
|
494
|
+
# Shared label column for code rows: every row's code preview starts at
|
|
495
|
+
# the same x (the widest label, capped - a long scope name does not eat
|
|
496
|
+
# the code's width), so the embedded editors are up line to line -
|
|
497
|
+
# per-row label widths made each row's code start jagged.
|
|
498
|
+
code_label_w = None
|
|
499
|
+
if row_code:
|
|
500
|
+
_ws = [imgui.calc_text_size(str(_l).rstrip())[0]
|
|
501
|
+
for (_k, _v, _l, _b) in rows
|
|
502
|
+
if not _b and _dd_row_lookup(row_code, _v) is not None]
|
|
503
|
+
if _ws:
|
|
504
|
+
code_label_w = min(max(_ws), Toggles.Dropdown.code_label_max_width)
|
|
505
|
+
row_width = _dd_row_width(draw_state)
|
|
506
|
+
for idx, row in enumerate(rows):
|
|
507
|
+
key, value, label, is_branch = row
|
|
508
|
+
if is_branch:
|
|
509
|
+
changed, picked = dd_menu_row(row, name=f"ddrow_{idx}_{key}", **row_kwargs)
|
|
510
|
+
if changed:
|
|
511
|
+
result = (True, picked)
|
|
512
|
+
else:
|
|
513
|
+
if _cull_top is not None:
|
|
514
|
+
_cx0, _cy0 = imgui.get_cursor_screen_pos()
|
|
515
|
+
if _cy0 + Toggles.Dropdown.row_height <= _cull_top or _cy0 >= _cull_bot:
|
|
516
|
+
# Offscreen leaf: reserve its draw rect (width from the
|
|
517
|
+
# cached widest label so measure/auto-resize stay stable)
|
|
518
|
+
# and pin the cursor a row down, like a hidden row does.
|
|
519
|
+
imgui.dummy(getattr(root_state, "_row_w", 1.0), Toggles.Dropdown.row_height)
|
|
520
|
+
imgui.set_cursor_screen_pos((_cx0, _cy0 + Toggles.Dropdown.row_height))
|
|
521
|
+
continue
|
|
522
|
+
picked = _dd_leaf_row(key, value, label, draw_state, root_state,
|
|
523
|
+
tuple(path_prefix), cursor_path, tint=tint,
|
|
524
|
+
row_tags=row_tags, row_tints=row_tints,
|
|
525
|
+
row_suffixes=row_suffixes,
|
|
526
|
+
row_actions=row_actions,
|
|
527
|
+
text_toward_bg=text_toward_bg,
|
|
528
|
+
row_code=row_code,
|
|
529
|
+
code_label_w=code_label_w,
|
|
530
|
+
row_width=row_width)
|
|
531
|
+
if picked is not UNSET_VALUE:
|
|
532
|
+
result = (True, picked)
|
|
533
|
+
return result
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@render_func(use_cache=True, show_bg=False, shadow=False, selectable=False, temp=True, show_add_delete=False,
|
|
537
|
+
with_header=None, disable_scroll=True, min_width=300, swoosh=False, z_offset=-3)
|
|
538
|
+
def dd_menu_row(input_value, draw_state, text_align="right", path_prefix=(),
|
|
539
|
+
root_state=None, tint=None, row_tags=None, row_tints=None,
|
|
540
|
+
cursor_path=(), open_path=(), full_render=True, **kwargs):
|
|
541
|
+
"""A single BRANCH menu row (leaves go through the raw _dd_leaf_row).
|
|
542
|
+
`input_value` is the row TUPLE (key, value, label, is_branch) handed in by
|
|
543
|
+
draw_dd_menu's manual loop. `cursor_path` / `open_path` are
|
|
544
|
+
passed IN (not read from root_state) so they're cache-key inputs: a row is a
|
|
545
|
+
use_cache=True tile, so its keyboard highlight / open chevron only repaint when
|
|
546
|
+
an input changes. Leaves are a button returning the VALUE on click; branch rows
|
|
547
|
+
show a chevron and own a nested `draw_dd_menu` to their right, shown only while
|
|
548
|
+
on the open path. Hovering points the shared cursor here; the highlight is
|
|
549
|
+
painted over the row box when hovered / keyboard-current.
|
|
550
|
+
|
|
551
|
+
`row_tags` (optional) maps a value -> short dim string drawn right-aligned —
|
|
552
|
+
the code editor's completion popup uses it for the kind label (func/class/…)."""
|
|
553
|
+
from meltygui.view.control_view import button
|
|
554
|
+
from meltygui.model.dropdown_model import _dd_as_tuple
|
|
555
|
+
from meltygui.core.layout.dropdown_core import _dd_invalidate_rows
|
|
556
|
+
from meltygui.core.layout.dropdown_core import _dd_pick
|
|
557
|
+
from meltygui.model.dropdown_model import _dd_row_lookup
|
|
558
|
+
from meltygui.core.layout.dropdown_core import _dd_set_cursor
|
|
559
|
+
|
|
560
|
+
key, value, label, is_branch = input_value
|
|
561
|
+
row_path = tuple(path_prefix) + (key,)
|
|
562
|
+
open_path = _dd_as_tuple(open_path)
|
|
563
|
+
cursor_path = _dd_as_tuple(cursor_path)
|
|
564
|
+
sub_open = open_path[:len(row_path)] == row_path
|
|
565
|
+
is_cursor = cursor_path == row_path
|
|
566
|
+
tag = _dd_row_lookup(row_tags, value)
|
|
567
|
+
|
|
568
|
+
hovered = draw_state._bounding_hovered
|
|
569
|
+
# Colour the row by its value's embedded tint (e.g. a Lora's .tint), falling
|
|
570
|
+
# back to the per-row override (autocomplete's symbol tints), then the menu
|
|
571
|
+
# tint for plain values.
|
|
572
|
+
row_tint = _dd_row_lookup(row_tints, value)
|
|
573
|
+
tint = _dd_obj_tint(value, row_tint or tint)
|
|
574
|
+
fa_chrevron_right = f"\uf054"
|
|
575
|
+
|
|
576
|
+
kbd_mode = getattr(root_state, "_kbd_mode", True)
|
|
577
|
+
if hovered and not kbd_mode:
|
|
578
|
+
_dd_set_cursor(root_state, row_path, is_branch,
|
|
579
|
+
menu_ds=draw_state.parent_window)
|
|
580
|
+
|
|
581
|
+
active = is_cursor if kbd_mode else hovered
|
|
582
|
+
if active:
|
|
583
|
+
dl = imgui.get_window_draw_list()
|
|
584
|
+
dl.add_rect_filled(draw_state.abs_left, draw_state.abs_top,
|
|
585
|
+
draw_state.abs_left + draw_state.width,
|
|
586
|
+
draw_state.abs_top + draw_state.height,
|
|
587
|
+
pack_color(1, 1, 1, 0.16),
|
|
588
|
+
rounding=getattr(draw_state, 'corner_radius', 6))
|
|
589
|
+
|
|
590
|
+
chevron = f" {fa_chrevron_right}" if is_branch else " " # fa-chevron-right
|
|
591
|
+
if not full_render:
|
|
592
|
+
# Lightweight row: draw the label with raw imgui.text() instead of the
|
|
593
|
+
# full button/draw_text render_func. There's no click report from
|
|
594
|
+
# imgui.text(), so derive one from this row's hover (the caller's
|
|
595
|
+
# bounding-box hover) plus a fresh left mouse-down.
|
|
596
|
+
imgui.text(f"{label}{chevron}")
|
|
597
|
+
clicked = hovered and imgui.is_mouse_clicked(0)
|
|
598
|
+
elif is_branch:
|
|
599
|
+
# +0.9 value on the active row lifts the label over the white@0.16 wash
|
|
600
|
+
# (button's own +1.5 hover boost keys off `hovered`, which is False for
|
|
601
|
+
# the keyboard-cursor row); hue is retained, only brightness moves.
|
|
602
|
+
clicked, _ = button(f"{label}{chevron}", name=f"{label}_ddrow", width=draw_state.content_width - 10,
|
|
603
|
+
height=Toggles.Dropdown.row_height, hovered=hovered, text_color=Tint.dd_text(requested_tint=tint),
|
|
604
|
+
text_saturation=1.349, shadow=False,
|
|
605
|
+
rounding=0, show_button_bg=False, show_bg=False, use_cache=True,
|
|
606
|
+
text_align=text_align, tint=tint)
|
|
607
|
+
else:
|
|
608
|
+
clicked, _ = button(f"{label}{chevron}", name=f"{label}_ddrow", show_button_bg=False,
|
|
609
|
+
width=draw_state.content_width - 10, height=Toggles.Dropdown.row_height, hovered=hovered,
|
|
610
|
+
text_saturation=0.716, z_offset=0, shadow=False, show_bg=False, use_cache=True,
|
|
611
|
+
text_align=text_align, tint=tint)
|
|
612
|
+
|
|
613
|
+
# In keyboard-select mode the arrow keys paint the highlight; hover neither
|
|
614
|
+
# moves the cursor nor paints, until the mouse moves (draw_dropdown clears it).
|
|
615
|
+
|
|
616
|
+
# ONE highlight, framed to this row's box. Mouse mode keys off the live hover;
|
|
617
|
+
# keyboard mode keys off the cursor. Using a separate source per mode (rather
|
|
618
|
+
# than hover OR cursor) avoids briefly painting both the stale-cursor row and
|
|
619
|
+
# the freshly-hovered row, which doubled the wash and looked inconsistent.
|
|
620
|
+
|
|
621
|
+
# Dim kind tag, right-aligned over the row (drawn last so it sits above the
|
|
622
|
+
# highlight). The name is left-aligned by the caller's text_align. A long
|
|
623
|
+
# label could run under a long tag, so the tag gets an opaque backing rect
|
|
624
|
+
# first: the parent window's actual bg fill (bg_color_stack top = nearest
|
|
625
|
+
# show_bg ancestor) with the active-row wash (white @ 0.16, see above)
|
|
626
|
+
# re-composed in, so the mask is invisible on both active and highlighted
|
|
627
|
+
# rows while still cutting off the label.
|
|
628
|
+
if tag:
|
|
629
|
+
_dd_paint_tag(imgui.get_window_draw_list(),
|
|
630
|
+
draw_state.abs_left + draw_state.width - 10,
|
|
631
|
+
draw_state.abs_top, draw_state.height, tag, active)
|
|
632
|
+
|
|
633
|
+
if is_branch:
|
|
634
|
+
# Always call the sub-menu (so off-path ones stay registered but hidden
|
|
635
|
+
# via closed=True, never leaving a stale painted frame); only the on-path
|
|
636
|
+
# branch actually draws. Pinned to the right of this row with window_pos.
|
|
637
|
+
search = str(getattr(root_state, "search", "") or "")
|
|
638
|
+
if any(search in str(key).lower() for key in row_path):
|
|
639
|
+
search = ""
|
|
640
|
+
popup_width, popup_height, _ = _dd_popup_geometry(value, search, 0, 0, False)
|
|
641
|
+
popup_left, popup_top = _dd_submenu_position(
|
|
642
|
+
draw_state.abs_left, draw_state.abs_top, draw_state.width,
|
|
643
|
+
popup_width, popup_height, *imgui.get_io().display_size)
|
|
644
|
+
cursor_x, cursor_y = imgui.get_cursor_screen_pos()
|
|
645
|
+
changed, picked, _sub_ds = draw_dd_menu(value, name=f"{label}_submenu", tint=tint,
|
|
646
|
+
closed=not sub_open, temp=True, use_cache=False,
|
|
647
|
+
window_pos=(popup_left - cursor_x, popup_top - cursor_y),
|
|
648
|
+
width=popup_width, height=popup_height, auto_resize=False,
|
|
649
|
+
show_add_delete=False,
|
|
650
|
+
parent_window=draw_state, disable_scroll=False,
|
|
651
|
+
full_render=full_render, row_tints=row_tints,
|
|
652
|
+
root_state=root_state, path_prefix=row_path,
|
|
653
|
+
return_extras=True)
|
|
654
|
+
# Register the submenu window on the shared root state so the ROOT
|
|
655
|
+
# menu body can sweep it close even when THIS row stops rendering -
|
|
656
|
+
# a search query can filter the row itself out of this level while its
|
|
657
|
+
# submenu is stamped open, and a row that doesn't render can never
|
|
658
|
+
# restamp `closed` (the nested-window leak, one level down).
|
|
659
|
+
if root_state is not None and _sub_ds is not None:
|
|
660
|
+
_reg = getattr(root_state, "_dd_submenu_ds", None)
|
|
661
|
+
if _reg is None:
|
|
662
|
+
_reg = root_state._dd_submenu_ds = {}
|
|
663
|
+
_reg[row_path] = _sub_ds
|
|
664
|
+
if _sub_ds is not None:
|
|
665
|
+
# The closed -> open EDGE: the submenu's row tiles are cached and
|
|
666
|
+
# blit-skip while clean, so a reopened submenu came up as a blank
|
|
667
|
+
# panel until the pointer entered it (a meltygui app's menu bar,
|
|
668
|
+
# 09-14). Cascade-invalidate its rows the frame it opens.
|
|
669
|
+
was_open = getattr(_sub_ds, "_dd_was_open", False)
|
|
670
|
+
if sub_open and not was_open:
|
|
671
|
+
_dd_invalidate_rows(root_state, _sub_ds)
|
|
672
|
+
_sub_ds._dd_was_open = sub_open
|
|
673
|
+
if changed:
|
|
674
|
+
return True, picked
|
|
675
|
+
elif clicked:
|
|
676
|
+
_dd_pick(root_state, row_path)
|
|
677
|
+
return True, value
|
|
678
|
+
|
|
679
|
+
return False, value
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def _dd_fit_label(s, px):
|
|
684
|
+
"""`s` ellipsis-trimmed to render within `px` (imgui text metrics), so a
|
|
685
|
+
fixed-width trigger never draws wider than its own button rect."""
|
|
686
|
+
if px <= 0:
|
|
687
|
+
return ""
|
|
688
|
+
if imgui.calc_text_size(s)[0] <= px:
|
|
689
|
+
return s
|
|
690
|
+
while s and imgui.calc_text_size(s + "...")[0] > px:
|
|
691
|
+
s = s[:-1]
|
|
692
|
+
return s + "..."
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
def _dd_obj_tint(obj, fallback=None):
|
|
696
|
+
"""An object's embedded tint (a 3+-tuple `.tint`, e.g. on a Lora), else
|
|
697
|
+
`fallback`. Used to colour each row by its value and the trigger by the
|
|
698
|
+
selected value."""
|
|
699
|
+
t = getattr(obj, "tint", None)
|
|
700
|
+
if isinstance(t, (tuple, list)) and len(t) >= 3:
|
|
701
|
+
return tuple(t)
|
|
702
|
+
return fallback
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def _dd_submenu_position(left, top, row_width, width, height, display_w, display_h):
|
|
706
|
+
right = left + row_width
|
|
707
|
+
x = right if right + width <= display_w else left - width
|
|
708
|
+
return max(0, min(x, display_w - width)), max(0, min(top, display_h - height))
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
def _dd_popup_geometry(collection, search, trigger_top, trigger_height,
|
|
712
|
+
open_upwards=None, min_width=None):
|
|
713
|
+
"""Content-sized popup contained in the display, independent of old bounds."""
|
|
714
|
+
from meltygui.model.dropdown_model import _dd_visible_entries
|
|
715
|
+
from meltygui.core.cache.tile_cache import snap_int
|
|
716
|
+
|
|
717
|
+
rows = _dd_visible_entries(collection, (search or "").strip().lower())
|
|
718
|
+
display_w, display_h = imgui.get_io().display_size
|
|
719
|
+
natural_height = (len(rows) + (2 if len(collection) > 4 else 1)) * Toggles.Dropdown.row_height
|
|
720
|
+
above = max(0, trigger_top)
|
|
721
|
+
below = max(0, display_h - trigger_top - trigger_height)
|
|
722
|
+
upwards = (below < min(natural_height, Toggles.Dropdown.max_height) and above > below
|
|
723
|
+
if open_upwards is None else open_upwards)
|
|
724
|
+
height = min(natural_height, Toggles.Dropdown.max_height, above if upwards else below)
|
|
725
|
+
width = min(display_w, max(
|
|
726
|
+
Toggles.Dropdown.min_width if min_width is None else min_width,
|
|
727
|
+
max((imgui.calc_text_size(row[2])[0] + 48 for row in rows), default=0)))
|
|
728
|
+
top = trigger_top - height if upwards else trigger_top + trigger_height
|
|
729
|
+
return snap_int(width), snap_int(height), snap_int(top)
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def _dd_menu_fit(menu_ds, min_width=None, max_height=None):
|
|
733
|
+
"""The popover size that fits its content — what the wrapper's
|
|
734
|
+
auto_resize computed for a closable window: the measured UNCLIPPED
|
|
735
|
+
group rect (_content_rect), width floored at min_width, height capped
|
|
736
|
+
at max_height and the display (rows scroll past it). None until the
|
|
737
|
+
body has measured."""
|
|
738
|
+
from meltygui.core.cache.tile_cache import snap_int
|
|
739
|
+
|
|
740
|
+
rect = getattr(menu_ds, "_content_rect", None)
|
|
741
|
+
if not rect or rect[0] <= 0 or rect[1] <= 0:
|
|
742
|
+
return None
|
|
743
|
+
display_w, display_h = imgui.get_io().display_size
|
|
744
|
+
fit_w = snap_int(max(min(rect[0], display_w), Toggles.Dropdown.min_width if min_width is None else min_width))
|
|
745
|
+
fit_h = snap_int(max(min(rect[1], display_h, Toggles.Dropdown.max_height if max_height is None else max_height), Toggles.Dropdown.min_height))
|
|
746
|
+
return (fit_w, fit_h)
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
def _dd_tag_segments(tag):
|
|
750
|
+
"""A row tag as [(text, rgba)] segments. A plain str is ONE segment in
|
|
751
|
+
the default dim colour; a sequence mixes str items with (text, color)
|
|
752
|
+
pairs that keep their own colour — a 3-tuple colour gets the default
|
|
753
|
+
alpha, a None colour the default colour. Empty texts drop out."""
|
|
754
|
+
|
|
755
|
+
if not tag:
|
|
756
|
+
return []
|
|
757
|
+
if isinstance(tag, str):
|
|
758
|
+
return [(tag, Toggles.Dropdown.tag_color)]
|
|
759
|
+
out = []
|
|
760
|
+
for item in tag:
|
|
761
|
+
if isinstance(item, str):
|
|
762
|
+
text, color = item, None
|
|
763
|
+
else:
|
|
764
|
+
text, color = item
|
|
765
|
+
color = Toggles.Dropdown.tag_color if color is None else tuple(color)
|
|
766
|
+
if len(color) == 3:
|
|
767
|
+
color = color + (Toggles.Dropdown.tag_color[3],)
|
|
768
|
+
if text:
|
|
769
|
+
out.append((str(text), color))
|
|
770
|
+
return out
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
def _dd_tag_width(tag):
|
|
774
|
+
"""Painted width of a row tag (all segments + gaps), 0 for none."""
|
|
775
|
+
|
|
776
|
+
segments = _dd_tag_segments(tag)
|
|
777
|
+
if not segments:
|
|
778
|
+
return 0.0
|
|
779
|
+
return (sum(imgui.calc_text_size(text)[0] for text, _c in segments)
|
|
780
|
+
+ Toggles.Dropdown.tag_gap * (len(segments) - 1))
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def _dd_paint_tag(draw_list, right, top, height, tag, active,
|
|
784
|
+
row_tint=None, fade=0.0):
|
|
785
|
+
"""The dim tag column, right-aligned to `right` inside a row box
|
|
786
|
+
[top, top + height). Opaque backing rect first so a long label can't
|
|
787
|
+
run under it: the menu's actual painted fill (bg_color_stack top),
|
|
788
|
+
with the row's tint wash and the active-row wash (white @ 0.16)
|
|
789
|
+
re-composed in, so the mask is invisible on plain, tinted and
|
|
790
|
+
highlighted rows alike. `fade` (0..1) pulls every segment's colour
|
|
791
|
+
toward that fill — a faded row's tag fades with its label."""
|
|
792
|
+
|
|
793
|
+
segments = _dd_tag_segments(tag)
|
|
794
|
+
if not segments:
|
|
795
|
+
return
|
|
796
|
+
line_h = imgui.get_text_line_height()
|
|
797
|
+
tag_w = _dd_tag_width(tag)
|
|
798
|
+
tag_x = right - tag_w
|
|
799
|
+
tag_y = top + (height - line_h) * 0.5
|
|
800
|
+
bg = Melty.bg_color_stack[-1][:3] if Melty.bg_color_stack else None
|
|
801
|
+
if bg is not None:
|
|
802
|
+
r, g, b = bg
|
|
803
|
+
if row_tint is not None:
|
|
804
|
+
r = r * (1 - Toggles.Dropdown.row_tint_alpha) + row_tint[0] * Toggles.Dropdown.row_tint_alpha
|
|
805
|
+
g = g * (1 - Toggles.Dropdown.row_tint_alpha) + row_tint[1] * Toggles.Dropdown.row_tint_alpha
|
|
806
|
+
b = b * (1 - Toggles.Dropdown.row_tint_alpha) + row_tint[2] * Toggles.Dropdown.row_tint_alpha
|
|
807
|
+
if active:
|
|
808
|
+
r, g, b = r * 0.84 + 0.16, g * 0.84 + 0.16, b * 0.84 + 0.16
|
|
809
|
+
mask = pack_color(min(max(r, 0.0), 1.0),
|
|
810
|
+
min(max(g, 0.0), 1.0),
|
|
811
|
+
min(max(b, 0.0), 1.0), 1.0)
|
|
812
|
+
draw_list.add_rect_filled(tag_x - 6, top + 1, tag_x + tag_w + 6,
|
|
813
|
+
top + height - 1, mask)
|
|
814
|
+
fade = min(max(float(fade or 0.0), 0.0), 1.0)
|
|
815
|
+
x = tag_x
|
|
816
|
+
for text, color in segments:
|
|
817
|
+
red, green, blue, alpha = color
|
|
818
|
+
if fade and bg is not None:
|
|
819
|
+
red = red * (1 - fade) + bg[0] * fade
|
|
820
|
+
green = green * (1 - fade) + bg[1] * fade
|
|
821
|
+
blue = blue * (1 - fade) + bg[2] * fade
|
|
822
|
+
draw_list.add_text(x, tag_y,
|
|
823
|
+
pack_color(red, green, blue, alpha),
|
|
824
|
+
text)
|
|
825
|
+
x += imgui.calc_text_size(text)[0] + Toggles.Dropdown.tag_gap
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
def _dd_row_width(draw_state):
|
|
829
|
+
"""A menu row's width: the popover WINDOW's own width less the scrollbar
|
|
830
|
+
reserve. NOT content_width — the wrapper derives that from the PARENT's
|
|
831
|
+
available width (the trigger's 300 px slot) and pins it at min_width,
|
|
832
|
+
so a popover grown to fit long labels painted its tags and hit-tested
|
|
833
|
+
its rows at 300 px while the window was 600+ wide (the tag masked the
|
|
834
|
+
label's tail). The max keeps the old figure where it was the larger."""
|
|
835
|
+
window_width = draw_state.width or 0
|
|
836
|
+
return max(draw_state.content_width or 0,
|
|
837
|
+
window_width - (SCROLL_BAR_WIDTH_DEFAULT + SCROLLBAR_MARGIN))
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
def _dd_leaf_row(key, value, label, draw_state, root_state, path_prefix,
|
|
841
|
+
cursor_path, tint=None, row_tags=None, row_tints=None,
|
|
842
|
+
row_suffixes=None, row_actions=None, left_pad=10,
|
|
843
|
+
text_toward_bg=0.0, row_code=None, code_label_w=None,
|
|
844
|
+
row_width=None):
|
|
845
|
+
"""Render ONE leaf menu row inline with raw imgui — NO per-row render_func.
|
|
846
|
+
Leaves are the bulk of a big menu, so skipping the dd_menu_row wrapper (its
|
|
847
|
+
own draw_state / cache / BVH / hover machinery, tens of µs each) is the whole
|
|
848
|
+
point: it's what made the 967-icon list crawl while hovering/scrolling. The
|
|
849
|
+
parent draw_dd_menu tile already re-renders every frame it's bounding-hovered
|
|
850
|
+
(core_render hover invalidation), so this row's hover highlight / click stay
|
|
851
|
+
live without a tile of its own. Branch rows still go through dd_menu_row — they
|
|
852
|
+
own a nested submenu and a real draw_state. Returns the picked value when
|
|
853
|
+
clicked, else UNSET_VALUE.
|
|
854
|
+
|
|
855
|
+
`draw_state` is the MENU window's draw_state (the level), not a per-row one."""
|
|
856
|
+
from meltygui.core.conversion.cache_tree import UNSET_VALUE
|
|
857
|
+
from meltygui.core.layout.dropdown_core import _dd_pick
|
|
858
|
+
from meltygui.core.layout.dropdown_core import _dd_set_cursor
|
|
859
|
+
from meltygui.editor.source_ui import _RowSpan
|
|
860
|
+
from meltygui.editor.source_ui import _row_code_hosts
|
|
861
|
+
from meltygui.view.text_view import draw_text
|
|
862
|
+
from meltygui.model.dropdown_model import _dd_as_tuple
|
|
863
|
+
from meltygui.model.dropdown_model import _dd_row_lookup
|
|
864
|
+
from meltygui.core.windowing.glfw_utils import request_render
|
|
865
|
+
|
|
866
|
+
row_path = tuple(path_prefix) + (key,)
|
|
867
|
+
is_cursor = _dd_as_tuple(cursor_path) == row_path
|
|
868
|
+
kbd_mode = getattr(root_state, "_kbd_mode", True)
|
|
869
|
+
row_fade = float(text_toward_bg or 0.0)
|
|
870
|
+
|
|
871
|
+
pos = imgui.get_cursor_screen_pos()
|
|
872
|
+
x, y = pos[0], pos[1]
|
|
873
|
+
w = row_width if row_width else _dd_row_width(draw_state)
|
|
874
|
+
h = Toggles.Dropdown.row_height
|
|
875
|
+
mp = imgui.get_mouse_pos()
|
|
876
|
+
hovered = (x <= mp[0] < x + w) and (y <= mp[1] < y + h)
|
|
877
|
+
if hovered:
|
|
878
|
+
# Clamp hover to the menu window's visible band: rows laid out above the
|
|
879
|
+
# window bounds (scrolled/clipped away) are invisible but this raw imgui
|
|
880
|
+
# math would still hit them - a click on editor text under the popup
|
|
881
|
+
# could pick an unseen row.
|
|
882
|
+
_wt = draw_state._abs_top()
|
|
883
|
+
if not (_wt <= mp[1] < _wt + (draw_state.height or 0)):
|
|
884
|
+
hovered = False
|
|
885
|
+
if hovered and not kbd_mode:
|
|
886
|
+
_dd_set_cursor(root_state, row_path, False, menu_ds=draw_state)
|
|
887
|
+
|
|
888
|
+
active = is_cursor if kbd_mode else hovered
|
|
889
|
+
dl = imgui.get_window_draw_list()
|
|
890
|
+
line_h = imgui.get_text_line_height()
|
|
891
|
+
# Per-row symbol tint (the autocomplete popup's background colors): drawn
|
|
892
|
+
# as a colored BACKGROUND wash with near-white text over it - the editor's
|
|
893
|
+
# wash styling - rather than colored text. Under the active highlight so
|
|
894
|
+
# keyboard/hover selection still reads on tinted rows.
|
|
895
|
+
row_tint = _dd_row_lookup(row_tints, value)
|
|
896
|
+
_ROW_TINT_A = Toggles.Dropdown.row_tint_alpha
|
|
897
|
+
_ROW_TINT_V_CAP = 0.55 # max RGB component - near-white text must stay legible
|
|
898
|
+
_ROW_TINT_S_BOOST = 1.25 # saturation bump on capped tints — keeps hue vivid
|
|
899
|
+
if row_tint is not None:
|
|
900
|
+
r, g, b = row_tint[:3]
|
|
901
|
+
v = max(r, g, b)
|
|
902
|
+
if v > _ROW_TINT_V_CAP:
|
|
903
|
+
k = _ROW_TINT_V_CAP / v
|
|
904
|
+
r, g, b = r * k, g * k, b * k
|
|
905
|
+
# Boost saturation by pulling the lower channels up from the max
|
|
906
|
+
# (raw channel arithmetic - hue is the channel ORDER, matters).
|
|
907
|
+
m = max(r, g, b)
|
|
908
|
+
r = max(0.0, m - (m - r) * _ROW_TINT_S_BOOST)
|
|
909
|
+
g = max(0.0, m - (m - g) * _ROW_TINT_S_BOOST)
|
|
910
|
+
b = max(0.0, m - (m - b) * _ROW_TINT_S_BOOST)
|
|
911
|
+
dl.add_rect_filled(x, y + 1, x + w, y + h - 1,
|
|
912
|
+
pack_color(r, g, b, _ROW_TINT_A),
|
|
913
|
+
rounding=getattr(draw_state, 'corner_radius', 6))
|
|
914
|
+
if active:
|
|
915
|
+
dl.add_rect_filled(x, y, x + w, y + h,
|
|
916
|
+
pack_color(1, 1, 1, 0.16),
|
|
917
|
+
rounding=getattr(draw_state, 'corner_radius', 6))
|
|
918
|
+
|
|
919
|
+
# Raw imgui.text_colored() for the label, coloured by the value's embedded
|
|
920
|
+
# tint (e.g. a Lora's .tint) falling back to the menu tint - same source as
|
|
921
|
+
# dd_menu_row. Vertically centred in the fixed-height row: set_cursor pins the
|
|
922
|
+
# next row exactly h below (matches the full menu's item_spacing.y=0), so
|
|
923
|
+
# leaves and dd_menu_row branches line up.
|
|
924
|
+
if row_tint is not None:
|
|
925
|
+
# White nudged toward the wash color: legible on the tinted bar while
|
|
926
|
+
# still reading as that symbol's hue.
|
|
927
|
+
color = tuple(min(1.0, c * 0.25 + 0.75) for c in row_tint[:3])
|
|
928
|
+
else:
|
|
929
|
+
color = _dd_obj_tint(value, tint)
|
|
930
|
+
color = Tint.dd_text(requested_tint=color)
|
|
931
|
+
# Quiet-row dimming: pull the label toward the menu bg so tinted
|
|
932
|
+
# rows (the info tab's active-source yellow) stand out - menu-wide
|
|
933
|
+
# (text_toward_bg). Skipped for washed rows above - their label is
|
|
934
|
+
# already contrast-managed.
|
|
935
|
+
if row_fade and Melty.bg_color_stack:
|
|
936
|
+
_bg = Melty.bg_color_stack[-1]
|
|
937
|
+
_k = min(max(row_fade, 0.0), 1.0)
|
|
938
|
+
color = tuple(c * (1 - _k) + _b * _k
|
|
939
|
+
for c, _b in zip(color[:3], _bg[:3]))
|
|
940
|
+
imgui.set_cursor_screen_pos((x + left_pad, y + (h - line_h) * 0.5))
|
|
941
|
+
|
|
942
|
+
if active:
|
|
943
|
+
# Active-row label: lerp toward white so it clears the white@0.16 wash
|
|
944
|
+
# (the wash lightens the row fill while the label is its plain text
|
|
945
|
+
# value - light-on-light, 2.8:1 in a dark-tinted editor). Keeping 40%
|
|
946
|
+
# of the original chroma leaves per-item tints recognizable.
|
|
947
|
+
color = tuple(min(1.0, c * 0.4 + 0.6) for c in color[:3])
|
|
948
|
+
color = *(color[:3]), 1.0
|
|
949
|
+
|
|
950
|
+
tag = _dd_row_lookup(row_tags, value)
|
|
951
|
+
code_row = _dd_row_lookup(row_code, value)
|
|
952
|
+
if code_row is not None:
|
|
953
|
+
# GlobalSearch-mirrored code row (the quick-jump popup): the row
|
|
954
|
+
# label as a dim prefix, then the ACTUAL code line rendered through the
|
|
955
|
+
# real editor - draw_text with the file's live cst-dict parse and a
|
|
956
|
+
# jump_to line-offset shim, so token colors and definition washes are
|
|
957
|
+
# pixel-identical to the jump window; the file line shown in the
|
|
958
|
+
# editor's own gutter (line_numbers=[...]) and the editor's text
|
|
959
|
+
# right-aligns like every other row (width reserved below). Same
|
|
960
|
+
# embed recipe as draw_global_search's code_row block, including
|
|
961
|
+
# use_cache=False (the layer-band masking note there).
|
|
962
|
+
_cp, _cl, _ccode = code_row
|
|
963
|
+
_ty = y + (h - line_h) * 0.5
|
|
964
|
+
# rstrip: dedup keys may carry invisible trailing whitespace - never a
|
|
965
|
+
# visible counter. Elipsize past the
|
|
966
|
+
# label cap so a deep scope name can't eat the code column.
|
|
967
|
+
_lbl = str(label).rstrip()
|
|
968
|
+
if imgui.calc_text_size(_lbl)[0] > Toggles.Dropdown.code_label_max_width:
|
|
969
|
+
while _lbl and imgui.calc_text_size(_lbl + "…")[0] > Toggles.Dropdown.code_label_max_width:
|
|
970
|
+
_lbl = _lbl[:-1]
|
|
971
|
+
_lbl += "…"
|
|
972
|
+
dl.add_text(x + left_pad, _ty,
|
|
973
|
+
pack_color(color[0], color[1], color[2], 0.9),
|
|
974
|
+
_lbl)
|
|
975
|
+
# Shared column (widest label in the menu, capped, precomputed by
|
|
976
|
+
# draw_dd_menu) so every row's editor starts at the same x - with the
|
|
977
|
+
# static 5-digit gutter inside draw_text, code aligns line to line.
|
|
978
|
+
_lw = (code_label_w if code_label_w is not None
|
|
979
|
+
else min(imgui.calc_text_size(_lbl)[0], Toggles.Dropdown.code_label_max_width))
|
|
980
|
+
_cx = x + left_pad + _lw + 14.0
|
|
981
|
+
_tw_r = (_dd_tag_width(tag) + 22.0) if tag else 10.0
|
|
982
|
+
_cw = max(60.0, x + w - _tw_r - _cx)
|
|
983
|
+
# Offscreen rows (scrolled past the menu's visible band): skip the
|
|
984
|
+
# draw_text embed - the one-line editor body takes real wrapper
|
|
985
|
+
# time, and a tall menu lays out every row each frame - and reserve
|
|
986
|
+
# the space with a dummy so layout/scroll stay constant.
|
|
987
|
+
_wt = draw_state._abs_top()
|
|
988
|
+
_wb = _wt + (draw_state.height or 0)
|
|
989
|
+
if y + h <= _wt or y >= _wb:
|
|
990
|
+
imgui.set_cursor_screen_pos((_cx, y))
|
|
991
|
+
imgui.dummy(_cw, h)
|
|
992
|
+
else:
|
|
993
|
+
_cdict, _chost = _row_code_hosts(_cp)
|
|
994
|
+
imgui.set_cursor_screen_pos((_cx, y))
|
|
995
|
+
try:
|
|
996
|
+
# use_cache=True: the menu re-renders every bounding-hovered
|
|
997
|
+
# frame, and an uncached editor body per row makes big pickers
|
|
998
|
+
# crawl - cached editor tiles blit-skip when clean. Same
|
|
999
|
+
# masking-cliff caveat as the GlobalSearch code rows.
|
|
1000
|
+
_res = draw_text(_ccode, name=f"dd_code_{key}", show_header=False,
|
|
1001
|
+
show_bg=False, shadow=True, single_line=True,
|
|
1002
|
+
width=_cw, height=h, use_cache=False, bg_offset=-2,
|
|
1003
|
+
jump_to=_RowSpan(_cl - 1, _cp), is_search_box=True,
|
|
1004
|
+
is_tree=False, z_offset=2,
|
|
1005
|
+
show_jump_bar=False, line_numbers=[_cl],
|
|
1006
|
+
tint=row_tint, selectable=False,
|
|
1007
|
+
roster_live_hold=False, # single-only preview
|
|
1008
|
+
return_extras=True)
|
|
1009
|
+
if _chost is not None:
|
|
1010
|
+
# Repaint when the background parse lands (washes pop in) but
|
|
1011
|
+
# the ROW's own cached tile, not the menu window (the window
|
|
1012
|
+
# invalidation leaves clean row tiles blit-skipped).
|
|
1013
|
+
_row_ds = _res[2] if len(_res) > 2 else None
|
|
1014
|
+
_chost.notify_on_change(_row_ds if _row_ds is not None
|
|
1015
|
+
else draw_state)
|
|
1016
|
+
except Exception:
|
|
1017
|
+
traceback.print_exc()
|
|
1018
|
+
# The embedded editor's caret sub consumes clicks over the code
|
|
1019
|
+
# area - reclaim them with a higher-priority sub on exactly that
|
|
1020
|
+
# rect (draw_global_search's code rows use the same technique) so
|
|
1021
|
+
# the click still picks the row. Label/tag/action areas keep the
|
|
1022
|
+
# raw imgui click path below.
|
|
1023
|
+
if draw_state.on_action("left_mouse_down",
|
|
1024
|
+
view_id=f"dd_code_act_{key}",
|
|
1025
|
+
rect=(_cx, y, _cx + _cw, y + h),
|
|
1026
|
+
priority_delta=4) is not None:
|
|
1027
|
+
_dd_pick(root_state, row_path)
|
|
1028
|
+
imgui.set_cursor_screen_pos((x, y + h))
|
|
1029
|
+
return value
|
|
1030
|
+
imgui.set_cursor_screen_pos((x, y + h))
|
|
1031
|
+
else:
|
|
1032
|
+
imgui.text_colored(str(label), *color)
|
|
1033
|
+
|
|
1034
|
+
# Dim '(param, param2)' suffix right after the callable's name (autocomplete
|
|
1035
|
+
# rows): same hue as the label at reduced alpha, so it stays subtle on
|
|
1036
|
+
# plain, tinted and active rows alike. imgui text (not raw dl.add_text) so
|
|
1037
|
+
# the row's measured width includes it and auto-resizes with the popup.
|
|
1038
|
+
sfx = _dd_row_lookup(row_suffixes, value)
|
|
1039
|
+
if sfx:
|
|
1040
|
+
imgui.same_line(spacing=0)
|
|
1041
|
+
imgui.text_colored(sfx, color[0], color[1], color[2], 0.45)
|
|
1042
|
+
# Claim the tag column's width as row content too, so the menu's
|
|
1043
|
+
# auto-resize fits label AND tag side by side instead of the tag
|
|
1044
|
+
# masking the label's tail (a wide tag - commit date - hid most of a
|
|
1045
|
+
# long commit subject label).
|
|
1046
|
+
if tag:
|
|
1047
|
+
imgui.same_line(spacing=0)
|
|
1048
|
+
imgui.dummy(_dd_tag_width(tag) + 16.0, 1)
|
|
1049
|
+
|
|
1050
|
+
imgui.set_cursor_screen_pos((x, y + h))
|
|
1051
|
+
|
|
1052
|
+
# Dim kind tag, right-aligned (autocomplete's func/class/… label; the
|
|
1053
|
+
# Compare With rows' date) - _dd_paint_tag masks the label under it and
|
|
1054
|
+
# fades the hovered row.
|
|
1055
|
+
if tag:
|
|
1056
|
+
_dd_paint_tag(dl, x + w - 10, y, h, tag, active, row_tint=row_tint,
|
|
1057
|
+
fade=row_fade)
|
|
1058
|
+
|
|
1059
|
+
# Per-row ACTION (`row_actions`: dict value→callable, or single callable for
|
|
1060
|
+
# every row): a right-aligned trash icon whose click runs the action and
|
|
1061
|
+
# CONSUMES the click - no pick, popover stays open, so several rows can
|
|
1062
|
+
# be acted on in one visit (the info tab's clear-at-action).
|
|
1063
|
+
_act = None
|
|
1064
|
+
if row_actions is not None:
|
|
1065
|
+
_act = (_dd_row_lookup(row_actions, value)
|
|
1066
|
+
if isinstance(row_actions, dict) else row_actions)
|
|
1067
|
+
if _act is not None:
|
|
1068
|
+
_ax = x + w - 24
|
|
1069
|
+
_over_act = hovered and mp[0] >= _ax - 4
|
|
1070
|
+
dl.add_text(_ax, y + (h - line_h) * 0.5,
|
|
1071
|
+
pack_color(0.85, 0.32, 0.28,
|
|
1072
|
+
0.95 if _over_act else 0.4),
|
|
1073
|
+
"")
|
|
1074
|
+
if _over_act and imgui.is_mouse_clicked(0):
|
|
1075
|
+
_act(value)
|
|
1076
|
+
request_render()
|
|
1077
|
+
return UNSET_VALUE
|
|
1078
|
+
|
|
1079
|
+
if hovered and imgui.is_mouse_clicked(0):
|
|
1080
|
+
_dd_pick(root_state, row_path)
|
|
1081
|
+
return value
|
|
1082
|
+
return UNSET_VALUE
|