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,538 @@
|
|
|
1
|
+
"""The Ctrl+B usage-jump picker — GlobalSearch's Code tab, as a popover.
|
|
2
|
+
|
|
3
|
+
Rows are a TREE, exactly the Code tab's: the file (depth 0, icon + name,
|
|
4
|
+
FileMeta tint), then every enclosing class / def of a hit as its own row
|
|
5
|
+
(the def line, painted like the editor paints it), then the usage line
|
|
6
|
+
itself. EVERY row is selectable — a scope row jumps to that definition, the
|
|
7
|
+
file row opens the file — and the keyboard cursor lands on the BEST match
|
|
8
|
+
(the first target the resolver returned) rather than row 0, so Enter still
|
|
9
|
+
does what the flat list did.
|
|
10
|
+
|
|
11
|
+
Rows are built ONCE when the picker opens (`build_usage_rows`: roster
|
|
12
|
+
tables for the scope chains, `CodeLineTints` for the washes) and painted
|
|
13
|
+
every frame with `draw_code_line_fast` — a few draw-list calls per row, no
|
|
14
|
+
per-row editor body. The window is `draw_usage_picker`, a latched popover
|
|
15
|
+
with the same shell as `draw_dd_menu` (called every frame with `closed=`
|
|
16
|
+
toggled by draw_text, which owns the keys and the focus gate).
|
|
17
|
+
"""
|
|
18
|
+
import os
|
|
19
|
+
import re
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
import meltygui_imgui as imgui
|
|
23
|
+
from meltygui.hdr_color import pack_color
|
|
24
|
+
|
|
25
|
+
from meltygui.core.melty import Melty
|
|
26
|
+
from meltygui.core.runtime.toggles import Tint
|
|
27
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
28
|
+
from meltygui.core.styling.fonts import Font
|
|
29
|
+
from meltygui.code.libcst_conversion import UsageRef
|
|
30
|
+
from meltygui.core.cache.tile_cache import add_shadow
|
|
31
|
+
from meltygui.editor.code_line_fast import CodeLineTints
|
|
32
|
+
from meltygui.view.code_view import draw_code_line_fast
|
|
33
|
+
from meltygui.editor.code_line_fast import pop_code_font
|
|
34
|
+
from meltygui.editor.code_line_fast import push_code_font
|
|
35
|
+
from meltygui.editor.code_line_fast import shift_spans
|
|
36
|
+
from meltygui.core.core_render import render_func
|
|
37
|
+
|
|
38
|
+
# Row pitch shared with the scroll-into-view helper — the Code tab's 24 + 2.
|
|
39
|
+
ROW_H = Toggles.UsagePicker.row_height
|
|
40
|
+
ROW_GAP = Toggles.UsagePicker.row_gap
|
|
41
|
+
ROW_PITCH = ROW_H + ROW_GAP
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class UsageRow:
|
|
45
|
+
"""One picker row. `kind` is "file" / "scope" / "site"; `ref` is the
|
|
46
|
+
UsageRef a pick jumps to (None for the file row — it opens the file),
|
|
47
|
+
`token` the symbol spelling the caret lands on; `text` is the display
|
|
48
|
+
line (stripped), `block_tint` / `spans` its washes in display columns;
|
|
49
|
+
`tint` the ROW's own tint (file: FileMeta, scope: the definition's)."""
|
|
50
|
+
__slots__ = ("kind", "depth", "path", "line", "text", "tint", "ref", "token",
|
|
51
|
+
"block_tint", "spans", "label", "emphasis", "indent")
|
|
52
|
+
|
|
53
|
+
def __init__(self, kind, depth, path, line, text, tint=None, ref=None,
|
|
54
|
+
token=None, block_tint=None, spans=(), label="", emphasis=None):
|
|
55
|
+
self.kind = kind
|
|
56
|
+
self.indent = 0.0
|
|
57
|
+
self.depth = depth
|
|
58
|
+
self.path = path
|
|
59
|
+
self.line = line
|
|
60
|
+
self.text = text
|
|
61
|
+
self.tint = tint
|
|
62
|
+
self.ref = ref
|
|
63
|
+
self.token = token
|
|
64
|
+
self.block_tint = block_tint
|
|
65
|
+
self.spans = spans
|
|
66
|
+
self.label = label
|
|
67
|
+
# Display columns of the searched symbol on a site row - painted at
|
|
68
|
+
# full brightness while the rest of the line is dimmed; () dims the
|
|
69
|
+
# whole line (a def scope row), None dims nothing.
|
|
70
|
+
self.emphasis = emphasis
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
from meltygui.model.code_model import UsagePickerModel
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ── row builder ─────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
class _Node:
|
|
79
|
+
__slots__ = ("entry", "children", "sites")
|
|
80
|
+
|
|
81
|
+
def __init__(self, entry):
|
|
82
|
+
self.entry = entry
|
|
83
|
+
self.children = {} # qualname -> _Node, first-appearance order
|
|
84
|
+
self.sites = [] # UsageRefs, first-appearance order
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _display_name(path):
|
|
88
|
+
"""The file's name relative to the project's src tree ("core_views/
|
|
89
|
+
text_editor.py"), the bare name when the path isn't under it."""
|
|
90
|
+
p = str(path)
|
|
91
|
+
marker = f"{os.sep}src{os.sep}"
|
|
92
|
+
i = p.rfind(marker)
|
|
93
|
+
return p[i + len(marker):] if i != -1 else os.path.basename(p)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _symbol_columns(disp, token, column=None):
|
|
97
|
+
"""Column spans of `token` (an identifier) in the display line `disp`:
|
|
98
|
+
the occurrence at `column` when one starts there, else every whole-word
|
|
99
|
+
occurrence. Empty when the token isn't on the line."""
|
|
100
|
+
if not token:
|
|
101
|
+
return []
|
|
102
|
+
if column is not None and 0 <= column < len(disp) and disp.startswith(token, column):
|
|
103
|
+
before = disp[column - 1] if column > 0 else ""
|
|
104
|
+
after = disp[column + len(token)] if column + len(token) < len(disp) else ""
|
|
105
|
+
if not (before.isalnum() or before == "_") and not (after.isalnum() or after == "_"):
|
|
106
|
+
return [(column, column + len(token))]
|
|
107
|
+
pat = re.compile(r"(?<![\w])" + re.escape(token) + r"(?![\w])")
|
|
108
|
+
return [(m.start(), m.end()) for m in pat.finditer(disp)]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def build_usage_rows(targets, names, tints=None, *, texts=None):
|
|
112
|
+
"""Rows for `targets` (UsageRefs, best first) grouped file → scope chain
|
|
113
|
+
→ usage line, in first-appearance order. `names` maps ref → the symbol
|
|
114
|
+
spelling for the caret. Returns (rows, best_index) — best_index is the
|
|
115
|
+
row of targets[0]. Optional `texts` supplies exact buffer snapshots,
|
|
116
|
+
keyed by path, so other code lists can share this tree and painter."""
|
|
117
|
+
import meltygui.code.symbol_roster as roster
|
|
118
|
+
from meltygui.editor.pending_save import PendingSave
|
|
119
|
+
from meltygui.editor.text_editor import _uj_file_tint
|
|
120
|
+
tints = tints if tints is not None else CodeLineTints()
|
|
121
|
+
files = {} # normalized path -> (path_str, text, lines, table, root _Node)
|
|
122
|
+
for ref in targets:
|
|
123
|
+
p = getattr(ref, "path", None)
|
|
124
|
+
if p is None:
|
|
125
|
+
continue
|
|
126
|
+
key = roster._norm(str(p))
|
|
127
|
+
ent = files.get(key)
|
|
128
|
+
if ent is None:
|
|
129
|
+
try:
|
|
130
|
+
text = (texts[str(p)] if texts is not None
|
|
131
|
+
else PendingSave.current_file_text(Path(str(p))))
|
|
132
|
+
except Exception:
|
|
133
|
+
text = None
|
|
134
|
+
text = text if isinstance(text, str) else ""
|
|
135
|
+
try:
|
|
136
|
+
table = (roster.detached_table(key, text) if texts is not None
|
|
137
|
+
else roster.table_for(key))
|
|
138
|
+
except Exception:
|
|
139
|
+
table = None
|
|
140
|
+
ent = files[key] = (str(p), text, text.split("\n"), table, _Node(None))
|
|
141
|
+
_path, _text, _lines, table, root = ent
|
|
142
|
+
node = root
|
|
143
|
+
if table is not None:
|
|
144
|
+
chain = []
|
|
145
|
+
scope = table.scope_at(ref.line)
|
|
146
|
+
while scope is not None:
|
|
147
|
+
chain.append(scope)
|
|
148
|
+
scope = (table.by_qualname.get(scope.parent)
|
|
149
|
+
if scope.parent else None)
|
|
150
|
+
for entry in reversed(chain):
|
|
151
|
+
child = node.children.get(entry.qualname)
|
|
152
|
+
if child is None:
|
|
153
|
+
child = node.children[entry.qualname] = _Node(entry)
|
|
154
|
+
node = child
|
|
155
|
+
node.sites.append(ref)
|
|
156
|
+
|
|
157
|
+
rows, best = [], 0
|
|
158
|
+
first = targets[0] if targets else None
|
|
159
|
+
|
|
160
|
+
def line_row(kind, depth, path, text, lines, line, ref, token, tint=None):
|
|
161
|
+
raw = lines[line - 1] if 0 < line <= len(lines) else ""
|
|
162
|
+
disp = raw.strip()
|
|
163
|
+
indent = len(raw) - len(raw.lstrip())
|
|
164
|
+
block, spans = tints.tints(path, text, line)
|
|
165
|
+
spans = shift_spans(spans, indent)
|
|
166
|
+
# A site row lights the caret symbol; a scope row is context.
|
|
167
|
+
if kind == "site":
|
|
168
|
+
col = getattr(ref, "column", None)
|
|
169
|
+
emphasis = _symbol_columns(disp, token,
|
|
170
|
+
None if col is None else col - indent)
|
|
171
|
+
else:
|
|
172
|
+
emphasis = ()
|
|
173
|
+
return UsageRow(kind, depth, path, line, disp, tint=tint, ref=ref,
|
|
174
|
+
token=token, block_tint=block, spans=spans, emphasis=emphasis)
|
|
175
|
+
|
|
176
|
+
def emit(node, depth, path, text, lines):
|
|
177
|
+
nonlocal best
|
|
178
|
+
entry = node.entry
|
|
179
|
+
site_lines = {getattr(r, "line", None) for r in node.sites}
|
|
180
|
+
if entry is not None:
|
|
181
|
+
# A target ON the def line IS this scope: one row, the site's ref.
|
|
182
|
+
own = next((r for r in node.sites if r.line == entry.line), None)
|
|
183
|
+
ref = own or UsageRef(Path(path), entry.line, entry.col,
|
|
184
|
+
scope=entry.parent or "<module>")
|
|
185
|
+
token = names.get(own) if own is not None else entry.name
|
|
186
|
+
if own is not None and own is first:
|
|
187
|
+
best = len(rows)
|
|
188
|
+
rows.append(line_row("site" if own is not None else "scope", depth,
|
|
189
|
+
path, text, lines, entry.line, ref,
|
|
190
|
+
token or entry.name, tint=entry.tint))
|
|
191
|
+
depth += 1
|
|
192
|
+
for ref in node.sites:
|
|
193
|
+
if entry is not None and ref.line == entry.line:
|
|
194
|
+
continue
|
|
195
|
+
if ref is first:
|
|
196
|
+
best = len(rows)
|
|
197
|
+
rows.append(line_row("site", depth, path, text, lines, ref.line,
|
|
198
|
+
ref, names.get(ref)))
|
|
199
|
+
for child in node.children.values():
|
|
200
|
+
emit(child, depth, path, text, lines)
|
|
201
|
+
|
|
202
|
+
for _key, (path, text, lines, _table, root) in files.items():
|
|
203
|
+
rows.append(UsageRow("file", 0, path, 1, _display_name(path),
|
|
204
|
+
tint=_uj_file_tint(path), label=path))
|
|
205
|
+
emit(root, 1, path, text, lines)
|
|
206
|
+
return rows, best
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ── picker window ───────────────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
def _same_tint(a, b):
|
|
212
|
+
"""True when two rgb tints are the same colour (None never matches)."""
|
|
213
|
+
if a is None or b is None:
|
|
214
|
+
return False
|
|
215
|
+
return all(abs(float(x) - float(y)) < 0.005 for x, y in zip(a[:3], b[:3]))
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# Popover size: fitted to the rows ONCE, on open (width = the remembered
|
|
219
|
+
# drag width, TextEditorState.usage_picker_width, when there is one); after
|
|
220
|
+
# that the handle resizes it freely, constrained only by the content height
|
|
221
|
+
# (every row + the "+ N more" row) measured in draw_text's call.
|
|
222
|
+
PICKER_MIN_W = Toggles.UsagePicker.min_width
|
|
223
|
+
PICKER_MIN_H = Toggles.UsagePicker.min_height
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _fresh_rect(menu_ds, model):
|
|
227
|
+
"""The wrapper's content rect, only when the body measured it THIS frame
|
|
228
|
+
— a latched popover keeps the rect of its last open (a longer list
|
|
229
|
+
sized a 4-row picker to 500 px, 09-04). A fresh rect also teaches the
|
|
230
|
+
model its chrome height."""
|
|
231
|
+
if menu_ds is None or model is None or model.measured is None:
|
|
232
|
+
return None
|
|
233
|
+
frame, rows_h = model.measured
|
|
234
|
+
if frame != Melty.frame_count:
|
|
235
|
+
return None
|
|
236
|
+
rect = getattr(menu_ds, "_content_rect", None)
|
|
237
|
+
if not rect or rect[0] <= 0 or rect[1] <= 0:
|
|
238
|
+
return None
|
|
239
|
+
if rect[1] >= rows_h:
|
|
240
|
+
model.chrome_h = rect[1] - rows_h
|
|
241
|
+
return rect
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def picker_content_height(menu_ds, model):
|
|
245
|
+
"""The popover's whole content: every row (the "+ N more" row included)
|
|
246
|
+
plus the window chrome — the ceiling of its height."""
|
|
247
|
+
rect = _fresh_rect(menu_ds, model)
|
|
248
|
+
if rect is not None:
|
|
249
|
+
return max(PICKER_MIN_H, rect[1])
|
|
250
|
+
n = len(model.rows) if model is not None else 0
|
|
251
|
+
chrome = model.chrome_h if model is not None else 8.0
|
|
252
|
+
return max(PICKER_MIN_H, n * ROW_PITCH + chrome)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def picker_fit(menu_ds, model):
|
|
256
|
+
"""The popover size that fits its rows, display-clamped: the fresh
|
|
257
|
+
content rect's width (else the minimum) and the content height."""
|
|
258
|
+
from meltygui.core.cache.tile_cache import snap_int
|
|
259
|
+
display_w, display_h = imgui.get_io().display_size
|
|
260
|
+
rect = _fresh_rect(menu_ds, model)
|
|
261
|
+
fit_w = snap_int(max(min(rect[0] if rect else PICKER_MIN_W, display_w), PICKER_MIN_W))
|
|
262
|
+
fit_h = snap_int(max(min(picker_content_height(menu_ds, model), display_h),
|
|
263
|
+
PICKER_MIN_H))
|
|
264
|
+
return (fit_w, fit_h)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def scroll_row_into_view(menu_ds, row_index):
|
|
268
|
+
"""Minimal scroll of the picker window so `row_index` is fully visible
|
|
269
|
+
(the dd-menu helper at this module's row pitch)."""
|
|
270
|
+
from meltygui.core.layout.dropdown_core import _dd_scroll_cursor_into_view
|
|
271
|
+
_dd_scroll_cursor_into_view(menu_ds, row_index, pitch=ROW_PITCH)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def paint_usage_rows(input_value, draw_state, *, width=None,
|
|
278
|
+
row_height=ROW_H, row_gap=ROW_GAP, tree_indent=16.0,
|
|
279
|
+
code_font=Font.FONTAWESOME_MONO_19, collapsible=False,
|
|
280
|
+
group_tint=None, change_kinds=None, event_prefix="uj",
|
|
281
|
+
line_numbers_left=False):
|
|
282
|
+
"""Paint into the caller's draw list and tile, like the usage search tree.
|
|
283
|
+
|
|
284
|
+
Background groups and code glyphs must share a capture owner. An
|
|
285
|
+
embedded list uses its column's draw_state for events and clipping;
|
|
286
|
+
the standalone picker supplies its own draw_state above.
|
|
287
|
+
"""
|
|
288
|
+
from meltygui.view.dropdown_view import _dd_row_width
|
|
289
|
+
# [tint=(0.9, 0.6, 0.2)] layout knobs — the Code tab's numbers
|
|
290
|
+
ICON_COL = 22.0
|
|
291
|
+
ICON_X = 4.0
|
|
292
|
+
TREE_INDENT = tree_indent
|
|
293
|
+
GROUP_INSET = ICON_COL - 4.0
|
|
294
|
+
file_icon = f""
|
|
295
|
+
collapsed_icon, expanded_icon = "", ""
|
|
296
|
+
row_pitch = row_height + row_gap
|
|
297
|
+
icon_alpha = 0.55
|
|
298
|
+
suffix_alpha = 0.55
|
|
299
|
+
row_bg_value, row_bg_hot = 0.045, 0.10
|
|
300
|
+
group_bg_value, group_bg_step = 0.028, 0.012
|
|
301
|
+
file_text_value, file_text_hot = 0.9, 1.5
|
|
302
|
+
more_tint = (0.55, 0.72, 0.85) # the Code tab's default blue
|
|
303
|
+
more_text_value, more_text_hot = 0.5, 1.0
|
|
304
|
+
text_saturation = 0.8
|
|
305
|
+
sel_color = pack_color(0.88, 0.93, 1.0, 1.0)
|
|
306
|
+
sel_thickness = 1.5
|
|
307
|
+
hot_bg_untinted = pack_color(1.0, 1.0, 1.0, 0.07)
|
|
308
|
+
suffix_color = pack_color(0.52, 0.55, 0.6, suffix_alpha)
|
|
309
|
+
|
|
310
|
+
model = input_value
|
|
311
|
+
rows = model.rows if model is not None else []
|
|
312
|
+
sm = Melty.style_manager
|
|
313
|
+
|
|
314
|
+
def _mix(tint, value, factor=0.8, sat=1.0, alpha=1.0):
|
|
315
|
+
c = tint if (isinstance(tint, tuple) and len(tint) >= 3) else (0.5, 0.5, 0.5)
|
|
316
|
+
col = sm.make_color_rgb(c[0], c[1], c[2], value=value, factor=factor,
|
|
317
|
+
saturation_scale=sat)
|
|
318
|
+
return pack_color(col[0], col[1], col[2], alpha)
|
|
319
|
+
|
|
320
|
+
draw_list = imgui.get_window_draw_list()
|
|
321
|
+
origin = imgui.get_cursor_screen_pos()
|
|
322
|
+
x0, y0 = origin[0], origin[1]
|
|
323
|
+
width = _dd_row_width(draw_state) if width is None else width
|
|
324
|
+
n = len(rows)
|
|
325
|
+
total_h = n * row_pitch
|
|
326
|
+
if model is not None:
|
|
327
|
+
model.measured = (Melty.frame_count, total_h if n else row_height)
|
|
328
|
+
if n == 0:
|
|
329
|
+
imgui.dummy(width, row_height)
|
|
330
|
+
return False, model
|
|
331
|
+
|
|
332
|
+
# Visible band of the (scrolling) window - rows outside it keep their
|
|
333
|
+
# geometry and skip their paint.
|
|
334
|
+
band_top = draw_state._abs_top()
|
|
335
|
+
band_bot = band_top + (draw_state.height or 0)
|
|
336
|
+
|
|
337
|
+
# ── hover vs keyboard highlight ──
|
|
338
|
+
mouse = imgui.get_mouse_pos()
|
|
339
|
+
over = (getattr(draw_state, "_bounding_hovered", False)
|
|
340
|
+
and x0 <= mouse[0] < x0 + width and band_top <= mouse[1] < band_bot)
|
|
341
|
+
last = model._last_mouse
|
|
342
|
+
moved = last is not None and (abs(mouse[0] - last[0]) > 0.5
|
|
343
|
+
or abs(mouse[1] - last[1]) > 0.5)
|
|
344
|
+
model._last_mouse = (mouse[0], mouse[1])
|
|
345
|
+
hover = None
|
|
346
|
+
if over:
|
|
347
|
+
hi = int((mouse[1] - y0) // row_pitch)
|
|
348
|
+
if 0 <= hi < n and (mouse[1] - y0) - hi * row_pitch < row_height:
|
|
349
|
+
hover = hi
|
|
350
|
+
if line_numbers_left:
|
|
351
|
+
# Embedded lists share a column: its bbox may still be hovered
|
|
352
|
+
# while the pointer has moved into a new file's usage list.
|
|
353
|
+
if hover is None and not model.kbd_mode:
|
|
354
|
+
model.index = -1
|
|
355
|
+
if moved:
|
|
356
|
+
model.kbd_mode = False
|
|
357
|
+
elif not over:
|
|
358
|
+
model.kbd_mode = True
|
|
359
|
+
elif moved and hover is not None:
|
|
360
|
+
model.kbd_mode = False
|
|
361
|
+
model.hover = hover if not model.kbd_mode else None
|
|
362
|
+
if not model.kbd_mode and hover is not None:
|
|
363
|
+
model.index = hover # Enter switches from the hovered row
|
|
364
|
+
|
|
365
|
+
pushed, char_w, line_h = push_code_font(code_font)
|
|
366
|
+
try:
|
|
367
|
+
gutter_width = (max(len(str(row.line)) for row in rows) * char_w + 8.0
|
|
368
|
+
if line_numbers_left else 0.0)
|
|
369
|
+
content_left = x0 + gutter_width
|
|
370
|
+
row_y = [y0 + i * row_pitch for i in range(n)]
|
|
371
|
+
depths = [r.depth for r in rows]
|
|
372
|
+
row_tints = []
|
|
373
|
+
tint_stack = []
|
|
374
|
+
for row in rows:
|
|
375
|
+
while tint_stack and tint_stack[-1][0] >= row.depth:
|
|
376
|
+
tint_stack.pop()
|
|
377
|
+
parent_tint = tint_stack[-1][1] if tint_stack else group_tint
|
|
378
|
+
scope = collapsible and row.line in model.scope_keys
|
|
379
|
+
tint = row.tint or ((row.block_tint or parent_tint) if scope else None)
|
|
380
|
+
row_tints.append(tint)
|
|
381
|
+
tint_stack.append((row.depth, tint or parent_tint))
|
|
382
|
+
if group_tint is not None:
|
|
383
|
+
draw_list.add_rect_filled(x0, y0, x0 + width, y0 + total_h,
|
|
384
|
+
_mix(group_tint, Toggles.CodeEditor.compare_file_bg_value), rounding=4.0)
|
|
385
|
+
|
|
386
|
+
# ── group background: a tinted scope's rows float in its wash ──
|
|
387
|
+
for i, row in enumerate(rows):
|
|
388
|
+
d = depths[i]
|
|
389
|
+
j = i + 1
|
|
390
|
+
while j < n and depths[j] > d:
|
|
391
|
+
j += 1
|
|
392
|
+
if j == i + 1 or row_tints[i] is None:
|
|
393
|
+
continue
|
|
394
|
+
gy0, gy1 = row_y[i], row_y[j - 1] + row_height
|
|
395
|
+
if gy1 < band_top or gy0 > band_bot:
|
|
396
|
+
continue
|
|
397
|
+
gx0 = content_left + d * TREE_INDENT + GROUP_INSET
|
|
398
|
+
add_shadow((gx0, gy0, x0 + width - gx0, gy1 - gy0),
|
|
399
|
+
offset=2.0 + 2.0 * d, corner_radius=4.0)
|
|
400
|
+
draw_list.add_rect_filled(gx0, gy0, x0 + width, gy1,
|
|
401
|
+
_mix(row_tints[i], group_bg_value + d * group_bg_step),
|
|
402
|
+
rounding=4.0)
|
|
403
|
+
|
|
404
|
+
picked = None
|
|
405
|
+
folded = False
|
|
406
|
+
# The tint a row's background ALREADY wears: its own, else the
|
|
407
|
+
# nearest tinted ancestor's (its group block or under the row).
|
|
408
|
+
# A code line's block wash and symbol washes in that same colour
|
|
409
|
+
# would just double it — they are dropped below (Lukas 09-04).
|
|
410
|
+
painted_stack = [] # (depth, painted tint)
|
|
411
|
+
for i, row in enumerate(rows):
|
|
412
|
+
ry = row_y[i]
|
|
413
|
+
while painted_stack and painted_stack[-1][0] >= depths[i]:
|
|
414
|
+
painted_stack.pop()
|
|
415
|
+
row_tint = row_tints[i]
|
|
416
|
+
painted = row_tint if row_tint is not None else (
|
|
417
|
+
painted_stack[-1][1] if painted_stack else group_tint)
|
|
418
|
+
painted_stack.append((depths[i], painted))
|
|
419
|
+
if ry + row_height < band_top or ry > band_bot:
|
|
420
|
+
continue
|
|
421
|
+
ind = depths[i] * TREE_INDENT
|
|
422
|
+
hot = (i == model.index) if model.kbd_mode else (i == hover)
|
|
423
|
+
own = row_tint is not None
|
|
424
|
+
is_file = row.kind == "file"
|
|
425
|
+
rx = content_left + ind + (ICON_COL if is_file else GROUP_INSET)
|
|
426
|
+
# Offset the row's shadow by depth depth so nested rows stack.
|
|
427
|
+
if hot and not own:
|
|
428
|
+
draw_list.add_rect_filled(rx, ry, x0 + width, ry + row_height,
|
|
429
|
+
hot_bg_untinted, rounding=4.0)
|
|
430
|
+
elif hot or own:
|
|
431
|
+
if own and not is_file:
|
|
432
|
+
add_shadow((rx, ry, x0 + width - rx, row_height),
|
|
433
|
+
offset=2.0 + 2.0 * depths[i], corner_radius=4.0)
|
|
434
|
+
draw_list.add_rect_filled(rx, ry, x0 + width, ry + row_height,
|
|
435
|
+
_mix(row_tint, row_bg_hot if hot else row_bg_value),
|
|
436
|
+
rounding=4.0)
|
|
437
|
+
text_y = ry + (row_height - line_h) * 0.5
|
|
438
|
+
text_x = rx + (4.0 if collapsible else 8.0)
|
|
439
|
+
foldable = collapsible and row.line in model.scope_keys
|
|
440
|
+
if foldable:
|
|
441
|
+
if not line_numbers_left or (over and hover == i):
|
|
442
|
+
draw_list.add_text(content_left + ind + 3.0, text_y, suffix_color,
|
|
443
|
+
collapsed_icon if model.is_collapsed(row) else expanded_icon)
|
|
444
|
+
if draw_state.on_action(
|
|
445
|
+
"left_mouse_down", view_id=f"{event_prefix}_fold_{row.line}",
|
|
446
|
+
rect=(content_left + ind, ry, rx, ry + row_height),
|
|
447
|
+
priority_delta=5) is not None:
|
|
448
|
+
model.toggle(row)
|
|
449
|
+
folded = True
|
|
450
|
+
marker_width = 0.0
|
|
451
|
+
change_kind = (change_kinds or {}).get(row.line)
|
|
452
|
+
scope_counts = model.scope_counts.get(row.line) if collapsible else None
|
|
453
|
+
if line_numbers_left and scope_counts is not None:
|
|
454
|
+
added_label, removed_label = f"+{scope_counts[0]}", f"−{scope_counts[1]}"
|
|
455
|
+
marker_width = imgui.calc_text_size(added_label + " " + removed_label).x
|
|
456
|
+
marker_left = x0 + width - 2.0 - marker_width
|
|
457
|
+
draw_list.add_text(marker_left, text_y,
|
|
458
|
+
pack_color(*Tint.change_count(added=True), 0.95), added_label)
|
|
459
|
+
draw_list.add_text(marker_left + imgui.calc_text_size(added_label + " ").x,
|
|
460
|
+
text_y, pack_color(*Tint.change_count(added=False), 0.95), removed_label)
|
|
461
|
+
elif change_kind:
|
|
462
|
+
if change_kind == "add":
|
|
463
|
+
marker, color = "+", Tint.change_count(added=True)
|
|
464
|
+
elif change_kind == "delete":
|
|
465
|
+
marker, color = "−", Tint.change_count(added=False)
|
|
466
|
+
else:
|
|
467
|
+
marker, color = "~", Tint.dd_text((0.06, 0.24, 0.45))
|
|
468
|
+
packed = pack_color(*color, 0.95)
|
|
469
|
+
if line_numbers_left:
|
|
470
|
+
marker = row.label.partition(" @ ")[0] if row.label else marker
|
|
471
|
+
marker_width = imgui.calc_text_size(marker).x
|
|
472
|
+
draw_list.add_text(x0 + width - 2.0 - marker_width, text_y, packed, marker)
|
|
473
|
+
else:
|
|
474
|
+
draw_list.add_rect_filled(rx, ry + 2, rx + 2, ry + row_height - 2, packed)
|
|
475
|
+
draw_list.add_text(text_x, text_y, packed, marker)
|
|
476
|
+
text_x += char_w + 3.0
|
|
477
|
+
if row.kind == "more":
|
|
478
|
+
# The "+ N more" row: Code-tab style, selectable like any row.
|
|
479
|
+
if hot:
|
|
480
|
+
draw_list.add_rect_filled(rx, ry, x0 + width, ry + row_height,
|
|
481
|
+
hot_bg_untinted, rounding=4.0)
|
|
482
|
+
draw_list.add_text(x0 + ICON_COL + 8.0, text_y,
|
|
483
|
+
_mix(more_tint, more_text_hot if hot else more_text_value),
|
|
484
|
+
row.text)
|
|
485
|
+
elif is_file:
|
|
486
|
+
tv = file_text_hot if hot else file_text_value
|
|
487
|
+
draw_list.add_text(x0 + ICON_X + ind, text_y,
|
|
488
|
+
_mix(row.tint, tv, sat=text_saturation, alpha=icon_alpha),
|
|
489
|
+
file_icon)
|
|
490
|
+
draw_list.add_text(text_x, text_y, _mix(row.tint, tv, sat=text_saturation),
|
|
491
|
+
row.text)
|
|
492
|
+
else:
|
|
493
|
+
suffix = row.label or str(row.line)
|
|
494
|
+
suffix_w = imgui.calc_text_size(suffix)[0]
|
|
495
|
+
if line_numbers_left:
|
|
496
|
+
number = str(row.line)
|
|
497
|
+
number_x = content_left - 5.0 - imgui.calc_text_size(number).x
|
|
498
|
+
number_color = Tint.line_number_tint(group_tint)
|
|
499
|
+
draw_list.add_text(number_x, text_y,
|
|
500
|
+
pack_color(*number_color[:3], 1.0), number)
|
|
501
|
+
code_w = max(0.0, x0 + width - 8.0 - marker_width - text_x)
|
|
502
|
+
else:
|
|
503
|
+
code_w = max(0.0, x0 + width - 8.0 - suffix_w - 12.0 - text_x)
|
|
504
|
+
if line_numbers_left:
|
|
505
|
+
indent_width = getattr(row, "indent", 0.0) * tree_indent
|
|
506
|
+
text_x += indent_width
|
|
507
|
+
code_w = max(0.0, code_w - indent_width)
|
|
508
|
+
block = None if _same_tint(row.block_tint, painted) else row.block_tint
|
|
509
|
+
spans = [sp for sp in row.spans
|
|
510
|
+
if not (_same_tint(sp[2], painted) or _same_tint(sp[2], block))]
|
|
511
|
+
# The focus symbol pops: full brightness on it, the rest of
|
|
512
|
+
# the line (and whole context rows) faded like the diff
|
|
513
|
+
# collapse preview; the hot line reads in full.
|
|
514
|
+
draw_code_line_fast(draw_list, text_x, text_y, row.text, char_w, line_h,
|
|
515
|
+
max_width=code_w, block_tint=block, spans=spans,
|
|
516
|
+
emphasis=None if hot else row.emphasis)
|
|
517
|
+
if not line_numbers_left:
|
|
518
|
+
draw_list.add_text(x0 + width - 8.0 - suffix_w, text_y, suffix_color, suffix)
|
|
519
|
+
if i == model.index and (not line_numbers_left or model.kbd_mode):
|
|
520
|
+
draw_list.add_rect(rx, ry, x0 + width, ry + row_height, sel_color,
|
|
521
|
+
rounding=4.0, thickness=sel_thickness)
|
|
522
|
+
# Click to pick: on_action keeps the honest z-order (a window in
|
|
523
|
+
# front blocks clicks) and replays on multiple hits.
|
|
524
|
+
if draw_state.on_action("left_mouse_down", view_id=f"{event_prefix}_row_{i}",
|
|
525
|
+
rect=(rx if foldable else x0, ry, x0 + width, ry + row_height),
|
|
526
|
+
priority_delta=4) is not None:
|
|
527
|
+
picked = row
|
|
528
|
+
finally:
|
|
529
|
+
pop_code_font(pushed)
|
|
530
|
+
imgui.set_cursor_screen_pos((x0, y0))
|
|
531
|
+
imgui.dummy(width, total_h)
|
|
532
|
+
if folded:
|
|
533
|
+
return True, model
|
|
534
|
+
if picked is not None:
|
|
535
|
+
model.picked = picked
|
|
536
|
+
model.index = rows.index(picked)
|
|
537
|
+
return True, model
|
|
538
|
+
return False, model
|
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Example: InputHandler with various backends.
|
|
3
|
+
"""
|
|
4
|
+
from meltygui.core.input.input_handler import InputHandler
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def demo_basic():
|
|
8
|
+
"""Core API demo without any backend dependencies."""
|
|
9
|
+
handler = InputHandler()
|
|
10
|
+
|
|
11
|
+
print("=== Click Demo ===")
|
|
12
|
+
handler.clear_pending()
|
|
13
|
+
handler.register_hovered("button", 0, ["left_mouse_clicked", "left_mouse_double_clicked"])
|
|
14
|
+
handler.register_hovered("panel", 1, ["left_mouse_down"])
|
|
15
|
+
|
|
16
|
+
handler.feed_down("left_mouse", 100, 100)
|
|
17
|
+
handler.feed_up("left_mouse", 100, 100)
|
|
18
|
+
|
|
19
|
+
for view_id, evts in handler.process_frame().items():
|
|
20
|
+
for e in evts:
|
|
21
|
+
print(f" {view_id}: {e.input_id}:{e.action}")
|
|
22
|
+
|
|
23
|
+
print("\n=== Drag Demo ===")
|
|
24
|
+
handler.clear_pending()
|
|
25
|
+
handler.register_hovered("panel", 0, ["left_mouse_dragged", "left_mouse_up"])
|
|
26
|
+
|
|
27
|
+
handler.feed_down("left_mouse", 100, 100)
|
|
28
|
+
handler.feed_move(120, 110)
|
|
29
|
+
handler.feed_move(140, 120)
|
|
30
|
+
handler.feed_up("left_mouse", 140, 120)
|
|
31
|
+
|
|
32
|
+
for view_id, evts in handler.process_frame().items():
|
|
33
|
+
for e in evts:
|
|
34
|
+
print(f" {view_id}: {e.input_id}:{e.action} dx={e.dx:.0f} dy={e.dy:.0f}")
|
|
35
|
+
|
|
36
|
+
print("\n=== Keyboard Demo ===")
|
|
37
|
+
handler.clear_pending()
|
|
38
|
+
handler.register_hovered("text", 0, ["a_down", "space_down", "enter_down"])
|
|
39
|
+
|
|
40
|
+
handler.feed_down("a")
|
|
41
|
+
handler.feed_down("space")
|
|
42
|
+
handler.feed_down("enter")
|
|
43
|
+
|
|
44
|
+
for view_id, evts in handler.process_frame().items():
|
|
45
|
+
for e in evts:
|
|
46
|
+
print(f" {view_id}: {e.input_id}:{e.action}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def demo_json_backend():
|
|
50
|
+
"""Demo using JsonBackend for toolkit integration."""
|
|
51
|
+
from meltygui.core.input.pynput_backend import JsonBackend
|
|
52
|
+
|
|
53
|
+
handler = InputHandler()
|
|
54
|
+
backend = JsonBackend(handler)
|
|
55
|
+
|
|
56
|
+
print("\n=== JsonBackend Demo ===")
|
|
57
|
+
handler.clear_pending()
|
|
58
|
+
handler.register_hovered("panel", 0, [
|
|
59
|
+
"left_mouse_down", "left_mouse_up", "left_mouse_dragged", "cursor_moved"
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
# Simulate drag
|
|
63
|
+
backend.push_down("left_mouse", 100, 100)
|
|
64
|
+
backend.push_move(110, 105)
|
|
65
|
+
backend.push_move(120, 110)
|
|
66
|
+
backend.push_up("left_mouse", 120, 110)
|
|
67
|
+
|
|
68
|
+
backend.pump()
|
|
69
|
+
|
|
70
|
+
for view_id, evts in handler.process_frame().items():
|
|
71
|
+
for e in evts:
|
|
72
|
+
print(f" {view_id}: {e.input_id}:{e.action}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def demo_pynput():
|
|
76
|
+
"""Demo with real hardware (requires pynput)."""
|
|
77
|
+
try:
|
|
78
|
+
from meltygui.core.input.pynput_backend import PynputBackend, HAS_PYNPUT
|
|
79
|
+
if not HAS_PYNPUT:
|
|
80
|
+
raise ImportError()
|
|
81
|
+
except ImportError:
|
|
82
|
+
print("\n=== Pynput Demo ===")
|
|
83
|
+
print(" Skipped (pip install pynput)")
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
print("\n=== Pynput Demo ===")
|
|
87
|
+
print(" Move mouse and click for 2 seconds...")
|
|
88
|
+
|
|
89
|
+
handler = InputHandler()
|
|
90
|
+
backend = PynputBackend(handler)
|
|
91
|
+
backend.start()
|
|
92
|
+
|
|
93
|
+
import time
|
|
94
|
+
start = time.time()
|
|
95
|
+
|
|
96
|
+
while time.time() - start < 2.0:
|
|
97
|
+
handler.clear_pending()
|
|
98
|
+
handler.register_hovered("root", 0, [
|
|
99
|
+
"left_mouse_down", "left_mouse_up", "left_mouse_clicked",
|
|
100
|
+
"cursor_moved", "left_mouse_dragged"
|
|
101
|
+
])
|
|
102
|
+
|
|
103
|
+
backend.pump()
|
|
104
|
+
events = handler.process_frame()
|
|
105
|
+
|
|
106
|
+
for evts in events.values():
|
|
107
|
+
for e in evts:
|
|
108
|
+
print(f" {e.input_id}:{e.action} @ ({e.x:.0f}, {e.y:.0f})")
|
|
109
|
+
|
|
110
|
+
time.sleep(0.016)
|
|
111
|
+
|
|
112
|
+
backend.stop()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
# demo_basic()
|
|
117
|
+
# demo_json_backend()
|
|
118
|
+
demo_pynput()
|
|
File without changes
|