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,393 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OS-level three-finger click/drag for Linux touchpads (macOS-style).
|
|
3
|
+
|
|
4
|
+
Architecture — permanent grab + clone forwarding:
|
|
5
|
+
|
|
6
|
+
real touchpad ──(EVIOCGRAB, ours exclusively)──► this daemon
|
|
7
|
+
│ not latched: every event forwarded verbatim ──► virtual clone
|
|
8
|
+
│ (libinput drives the cursor from the clone as normal)
|
|
9
|
+
└─ latched (3-finger): events consumed; finger motion becomes
|
|
10
|
+
REL_X/REL_Y + BTN_MIDDLE on a separate virtual pointer
|
|
11
|
+
|
|
12
|
+
libinput only ever sees the clone, whose state we keep consistent: when the
|
|
13
|
+
latch begins we synthesize a clean "all fingers lifted" frame on the clone,
|
|
14
|
+
so there is never a mid-gesture grab/ungrab that strands libinput with
|
|
15
|
+
phantom fingers (which froze pointer input in the grab-on-demand version).
|
|
16
|
+
|
|
17
|
+
Safety — the system must NEVER lose the mouse:
|
|
18
|
+
- The kernel releases EVIOCGRAB automatically when our fd closes, for any
|
|
19
|
+
exit including SIGKILL. A crash always returns the real touchpad.
|
|
20
|
+
- Finger count comes from the hardware's own BTN_TOOL_* / BTN_TOUCH keys
|
|
21
|
+
(authoritative), not our MT-slot bookkeeping, so the latch cannot stick
|
|
22
|
+
on a miscount; BTN_TOUCH==0 force-releases regardless of other state.
|
|
23
|
+
- A physical clickpad button press while latched is an emergency release
|
|
24
|
+
(and the click passes through to the virtual pointer, so it still works).
|
|
25
|
+
- A watchdog force-releases the latch (and, on repeated trouble, shuts the
|
|
26
|
+
whole daemon down, closing fds → grab released) if the device goes
|
|
27
|
+
silent mid-latch for WATCHDOG_S seconds.
|
|
28
|
+
- Any exception path closes all fds in `finally`.
|
|
29
|
+
|
|
30
|
+
Gesture semantics (macOS "three finger drag"): middle press immediately on
|
|
31
|
+
3-finger contact (a quick tap is a middle click); lifting down to one finger
|
|
32
|
+
keeps the latch; releasing all fingers releases the button.
|
|
33
|
+
|
|
34
|
+
The virtual pointer declares BTN_LEFT/BTN_RIGHT alongside BTN_MIDDLE —
|
|
35
|
+
libinput refuses to treat a device without BTN_LEFT as a pointer, which is
|
|
36
|
+
why the first version's middle clicks never reached the compositor.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
import threading
|
|
41
|
+
import time
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import evdev
|
|
45
|
+
from evdev import InputDevice, UInput, ecodes as e
|
|
46
|
+
HAS_EVDEV = True
|
|
47
|
+
except ImportError:
|
|
48
|
+
HAS_EVDEV = False
|
|
49
|
+
|
|
50
|
+
# Touchpad units → cursor pixels (pad ~20 units/mm).
|
|
51
|
+
SENSITIVITY = 0.55
|
|
52
|
+
ACCEL_MAX = 2.5
|
|
53
|
+
ACCEL_REF = 30.0 # units/s where acceleration saturates
|
|
54
|
+
WATCHDOG_S = 5.0 # latched + zero events for this long → force release
|
|
55
|
+
# Fingers land staggered (1→2→3 over a few frames). Events from touch-begin
|
|
56
|
+
# are held back: reach 3 fingers → latch and the clone never sees the touch;
|
|
57
|
+
# otherwise the buffer flushes and the touch proceeds normally. Before this
|
|
58
|
+
# the clone saw a brief 2-finger touch + our synthesized lift, which libinput
|
|
59
|
+
# read as a 2-finger tap → spurious RIGHT CLICKS.
|
|
60
|
+
# The flush trigger is MOTION, not time: a natural 3-finger touch often
|
|
61
|
+
# starts from 1–2 resting fingers with the last finger landing 200ms+ later,
|
|
62
|
+
# so any fixed window either turns 3-finger drags into 2-finger SCROLLS
|
|
63
|
+
# (flushed too early) or makes every scroll start sluggish (flushed too
|
|
64
|
+
# late). Stationary fingers carry no intent - hold them back indefinitely;
|
|
65
|
+
# actual travel (or a physical button press, or the touch ending as a tap)
|
|
66
|
+
# reveals the intent and flushes immediately.
|
|
67
|
+
MOVE_FLUSH = 15 # device units (~0.75mm) of travel that flushes the hold
|
|
68
|
+
DEBUG = False
|
|
69
|
+
|
|
70
|
+
_TOOL_COUNT = {}
|
|
71
|
+
if HAS_EVDEV:
|
|
72
|
+
_TOOL_COUNT = {
|
|
73
|
+
e.BTN_TOOL_FINGER: 1, e.BTN_TOOL_DOUBLETAP: 2,
|
|
74
|
+
e.BTN_TOOL_TRIPLETAP: 3, e.BTN_TOOL_QUADTAP: 4,
|
|
75
|
+
e.BTN_TOOL_QUINTTAP: 5,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
CLONE_TAG = "(meltygui)"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def find_touchpad():
|
|
83
|
+
for path in evdev.list_devices():
|
|
84
|
+
try:
|
|
85
|
+
dev = InputDevice(path)
|
|
86
|
+
except OSError:
|
|
87
|
+
continue
|
|
88
|
+
# NEVER pick one of our own virtual devices: a stale clone from
|
|
89
|
+
# another still-running daemon (e.g. the model server's) matches the
|
|
90
|
+
# MT capability check and only ever carries the events that daemon
|
|
91
|
+
# forwards - grabbing it permanently breaks gesture detection.
|
|
92
|
+
if CLONE_TAG in dev.name or "latent-descent" in dev.name:
|
|
93
|
+
dev.close()
|
|
94
|
+
continue
|
|
95
|
+
caps = dev.capabilities()
|
|
96
|
+
abs_codes = {c for c, _ in caps.get(e.EV_ABS, [])}
|
|
97
|
+
if e.ABS_MT_SLOT in abs_codes and e.BTN_TOOL_FINGER in caps.get(e.EV_KEY, []):
|
|
98
|
+
return dev
|
|
99
|
+
dev.close()
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def existing_clone_present():
|
|
104
|
+
"""A live clone means another process' daemon already owns the touchpad."""
|
|
105
|
+
for path in evdev.list_devices():
|
|
106
|
+
try:
|
|
107
|
+
dev = InputDevice(path)
|
|
108
|
+
except OSError:
|
|
109
|
+
continue
|
|
110
|
+
name = dev.name
|
|
111
|
+
dev.close()
|
|
112
|
+
if CLONE_TAG in name:
|
|
113
|
+
return True
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ThreeFingerDrag:
|
|
118
|
+
def __init__(self):
|
|
119
|
+
self.dev: InputDevice | None = None
|
|
120
|
+
self.clone: UInput | None = None
|
|
121
|
+
self.pointer: UInput | None = None
|
|
122
|
+
self._thread: threading.Thread | None = None
|
|
123
|
+
self._stop = threading.Event()
|
|
124
|
+
self._latched = False
|
|
125
|
+
self._last_event_t = 0.0
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------- lifecycle
|
|
128
|
+
|
|
129
|
+
def start(self) -> bool:
|
|
130
|
+
if not HAS_EVDEV:
|
|
131
|
+
print("touchpad_backend: evdev not installed, 3-finger drag disabled")
|
|
132
|
+
return False
|
|
133
|
+
if existing_clone_present():
|
|
134
|
+
print("touchpad_backend: another 3-finger-drag daemon is already "
|
|
135
|
+
"running (clone device present) — not starting a second one")
|
|
136
|
+
return False
|
|
137
|
+
self.dev = find_touchpad()
|
|
138
|
+
if self.dev is None:
|
|
139
|
+
print("touchpad_backend: no multitouch touchpad found")
|
|
140
|
+
return False
|
|
141
|
+
try:
|
|
142
|
+
# BTN_LEFT is mandatory for libinput to classify this as a pointer.
|
|
143
|
+
self.pointer = UInput(
|
|
144
|
+
{e.EV_REL: [e.REL_X, e.REL_Y],
|
|
145
|
+
e.EV_KEY: [e.BTN_LEFT, e.BTN_RIGHT, e.BTN_MIDDLE]},
|
|
146
|
+
name="latent-descent virtual pointer")
|
|
147
|
+
# Clone of the touchpad - libinput's view of the pad from now on.
|
|
148
|
+
self.clone = UInput.from_device(self.dev,
|
|
149
|
+
name=f"{self.dev.name} (meltygui)")
|
|
150
|
+
# Give udev/libinput a moment to pick the clone up BEFORE we grab
|
|
151
|
+
# the real device, so there is no window with no working touchpad.
|
|
152
|
+
time.sleep(0.5)
|
|
153
|
+
self.dev.grab()
|
|
154
|
+
except Exception as ex:
|
|
155
|
+
print(f"touchpad_backend: setup failed ({ex}), 3-finger drag disabled")
|
|
156
|
+
self._close_all()
|
|
157
|
+
return False
|
|
158
|
+
self._thread = threading.Thread(target=self._run, name="touchpad-3fd",
|
|
159
|
+
daemon=True)
|
|
160
|
+
self._thread.start()
|
|
161
|
+
threading.Thread(target=self._watchdog, name="touchpad-3fd-watchdog",
|
|
162
|
+
daemon=True).start()
|
|
163
|
+
print(f"touchpad_backend: 3-finger drag active on {self.dev.name}")
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
def stop(self):
|
|
167
|
+
self._stop.set()
|
|
168
|
+
self._close_all() # closing dev releases the grab and unblocks reading
|
|
169
|
+
|
|
170
|
+
def _close_all(self):
|
|
171
|
+
for attr in ("dev", "clone", "pointer"):
|
|
172
|
+
obj = getattr(self, attr)
|
|
173
|
+
if obj is not None:
|
|
174
|
+
try:
|
|
175
|
+
obj.close()
|
|
176
|
+
except Exception:
|
|
177
|
+
pass
|
|
178
|
+
setattr(self, attr, None)
|
|
179
|
+
|
|
180
|
+
def _watchdog(self):
|
|
181
|
+
"""Absolute backstop: a latch with a silent device is force-released;
|
|
182
|
+
if the release itself can't be delivered, kill the daemon entirely —
|
|
183
|
+
closing the fds drops the grab and hands the real pad back to libinput."""
|
|
184
|
+
while not self._stop.is_set():
|
|
185
|
+
time.sleep(1.0)
|
|
186
|
+
if self._latched and time.monotonic() - self._last_event_t > WATCHDOG_S:
|
|
187
|
+
print("touchpad_backend: watchdog releasing stale 3-finger latch")
|
|
188
|
+
try:
|
|
189
|
+
self._release_latch()
|
|
190
|
+
except Exception:
|
|
191
|
+
print("touchpad_backend: watchdog release failed — "
|
|
192
|
+
"shutting down, touchpad returns to system")
|
|
193
|
+
self.stop()
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# ------------------------------------------------------------- latch ops
|
|
197
|
+
|
|
198
|
+
def _release_latch(self):
|
|
199
|
+
if self._latched:
|
|
200
|
+
self.pointer.write(e.EV_KEY, e.BTN_MIDDLE, 0)
|
|
201
|
+
self.pointer.syn()
|
|
202
|
+
self._latched = False
|
|
203
|
+
|
|
204
|
+
def _lift_fingers_on_clone(self, touches, active_tool):
|
|
205
|
+
"""Tell libinput (via the clone) that all fingers left the pad, so the
|
|
206
|
+
clone's state stays consistent while we consume the real events."""
|
|
207
|
+
c = self.clone
|
|
208
|
+
for slot in list(touches):
|
|
209
|
+
c.write(e.EV_ABS, e.ABS_MT_SLOT, slot)
|
|
210
|
+
c.write(e.EV_ABS, e.ABS_MT_TRACKING_ID, -1)
|
|
211
|
+
if active_tool is not None:
|
|
212
|
+
c.write(e.EV_KEY, active_tool, 0)
|
|
213
|
+
c.write(e.EV_KEY, e.BTN_TOUCH, 0)
|
|
214
|
+
c.syn()
|
|
215
|
+
|
|
216
|
+
# ------------------------------------------------------------- main loop
|
|
217
|
+
|
|
218
|
+
def _run(self):
|
|
219
|
+
dev, clone, pointer = self.dev, self.clone, self.pointer
|
|
220
|
+
touches: dict[int, list] = {} # slot -> [x, y]
|
|
221
|
+
slot = 0
|
|
222
|
+
fingers = 0 # from BTN_TOOL_* (authoritative)
|
|
223
|
+
touching = False # BTN_TOUCH level
|
|
224
|
+
active_tool = None
|
|
225
|
+
frame: list = [] # raw events of the current frame
|
|
226
|
+
primary: tuple | None = None # (slot, x, y) the drag follows
|
|
227
|
+
rx = ry = 0.0
|
|
228
|
+
# Touch-begin hold: frames withheld from the clone while we wait to
|
|
229
|
+
# see if this touch becomes 3-finger (see MOVE_FLUSH).
|
|
230
|
+
holding = False
|
|
231
|
+
hold_buf: list = [] # withheld raw events (frames + SYNs)
|
|
232
|
+
hold_origin: dict[int, tuple] = {} # slot -> first (x, y) seen in hold
|
|
233
|
+
hold_flush_now = False # physical button seen mid-hold
|
|
234
|
+
clone_touching = False # does the clone believe fingers are down?
|
|
235
|
+
|
|
236
|
+
def flush_hold():
|
|
237
|
+
nonlocal holding, clone_touching
|
|
238
|
+
for hev in hold_buf:
|
|
239
|
+
clone.write_event(hev)
|
|
240
|
+
if hold_buf:
|
|
241
|
+
clone.syn()
|
|
242
|
+
clone_touching = touching
|
|
243
|
+
hold_buf.clear()
|
|
244
|
+
holding = False
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
for ev in dev.read_loop():
|
|
248
|
+
if self._stop.is_set():
|
|
249
|
+
break
|
|
250
|
+
self._last_event_t = time.monotonic()
|
|
251
|
+
|
|
252
|
+
if ev.type == e.EV_ABS:
|
|
253
|
+
if ev.code == e.ABS_MT_SLOT:
|
|
254
|
+
slot = ev.value
|
|
255
|
+
elif ev.code == e.ABS_MT_TRACKING_ID:
|
|
256
|
+
if ev.value == -1:
|
|
257
|
+
touches.pop(slot, None)
|
|
258
|
+
else:
|
|
259
|
+
touches[slot] = [None, None]
|
|
260
|
+
elif ev.code == e.ABS_MT_POSITION_X and slot in touches:
|
|
261
|
+
touches[slot][0] = ev.value
|
|
262
|
+
elif ev.code == e.ABS_MT_POSITION_Y and slot in touches:
|
|
263
|
+
touches[slot][1] = ev.value
|
|
264
|
+
elif ev.type == e.EV_KEY:
|
|
265
|
+
if ev.code in _TOOL_COUNT:
|
|
266
|
+
if ev.value:
|
|
267
|
+
fingers = _TOOL_COUNT[ev.code]
|
|
268
|
+
active_tool = ev.code
|
|
269
|
+
if DEBUG:
|
|
270
|
+
print(f"3fd: fingers={fingers}")
|
|
271
|
+
elif ev.code == active_tool:
|
|
272
|
+
active_tool = None
|
|
273
|
+
fingers = 0
|
|
274
|
+
elif ev.code == e.BTN_TOUCH:
|
|
275
|
+
if ev.value and not touching:
|
|
276
|
+
# Touch begin → start withholding from the clone.
|
|
277
|
+
holding = True
|
|
278
|
+
hold_buf.clear()
|
|
279
|
+
hold_origin.clear()
|
|
280
|
+
hold_flush_now = False
|
|
281
|
+
touching = bool(ev.value)
|
|
282
|
+
elif ev.code in (e.BTN_LEFT, e.BTN_RIGHT):
|
|
283
|
+
if self._latched:
|
|
284
|
+
# Physical clickpad press mid-latch: emergency
|
|
285
|
+
# release; the clickpad works via the pointer.
|
|
286
|
+
self._release_latch()
|
|
287
|
+
pointer.write(e.EV_KEY, ev.code, ev.value)
|
|
288
|
+
pointer.syn()
|
|
289
|
+
elif holding:
|
|
290
|
+
# A physical click must never be missed - flush
|
|
291
|
+
# the held touch so libinput sees it immediately.
|
|
292
|
+
hold_flush_now = True
|
|
293
|
+
|
|
294
|
+
if not (ev.type == e.EV_SYN and ev.code == e.SYN_REPORT):
|
|
295
|
+
frame.append(ev)
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
# ---- end of frame: handle latch / hold / forward / consume ----
|
|
299
|
+
if not self._latched and fingers >= 3 and touching:
|
|
300
|
+
if clone_touching:
|
|
301
|
+
# Touch was already flushed to the clone (3rd finger
|
|
302
|
+
# arrived after we began) - retract it cleanly.
|
|
303
|
+
self._lift_fingers_on_clone(touches, active_tool)
|
|
304
|
+
clone_touching = False
|
|
305
|
+
holding = False
|
|
306
|
+
hold_buf.clear() # clone never sees the held prefix
|
|
307
|
+
pointer.write(e.EV_KEY, e.BTN_MIDDLE, 1)
|
|
308
|
+
pointer.syn()
|
|
309
|
+
self._latched = True
|
|
310
|
+
primary = None
|
|
311
|
+
rx = ry = 0.0
|
|
312
|
+
if DEBUG:
|
|
313
|
+
print("3fd: LATCH")
|
|
314
|
+
elif self._latched and (not touching or not touches):
|
|
315
|
+
self._release_latch()
|
|
316
|
+
if DEBUG:
|
|
317
|
+
print("3fd: RELEASE")
|
|
318
|
+
frame = [] # clone already thinks fingers are up
|
|
319
|
+
continue
|
|
320
|
+
|
|
321
|
+
if self._latched:
|
|
322
|
+
primary, rx, ry = self._track(pointer, touches, primary, rx, ry)
|
|
323
|
+
elif holding:
|
|
324
|
+
hold_buf.extend(frame)
|
|
325
|
+
hold_buf.append(ev) # keep the SYN so frames stay framed
|
|
326
|
+
# Flush when the touch reveals a non-3-finger intent: it
|
|
327
|
+
# ended (a tap), a physical button was pressed, or the
|
|
328
|
+
# fingers have traveled (cursor move / scroll).
|
|
329
|
+
# Stationary fingers stay held - the 3rd finger may still
|
|
330
|
+
# be on the way, however late.
|
|
331
|
+
moved = 0
|
|
332
|
+
for s, pos in touches.items():
|
|
333
|
+
if pos[0] is None or pos[1] is None:
|
|
334
|
+
continue
|
|
335
|
+
if s not in hold_origin:
|
|
336
|
+
hold_origin[s] = (pos[0], pos[1])
|
|
337
|
+
ox, oy = hold_origin[s]
|
|
338
|
+
moved = max(moved, abs(pos[0] - ox) + abs(pos[1] - oy))
|
|
339
|
+
if not touching or hold_flush_now or moved > MOVE_FLUSH:
|
|
340
|
+
flush_hold()
|
|
341
|
+
else:
|
|
342
|
+
for fev in frame:
|
|
343
|
+
clone.write_event(fev)
|
|
344
|
+
clone.syn()
|
|
345
|
+
clone_touching = touching
|
|
346
|
+
frame = []
|
|
347
|
+
except OSError:
|
|
348
|
+
pass # device closed / unplugged / suspend - exit cleanly
|
|
349
|
+
finally:
|
|
350
|
+
try:
|
|
351
|
+
self._release_latch()
|
|
352
|
+
except Exception:
|
|
353
|
+
pass
|
|
354
|
+
self._close_all() # closing dev releases the kernel grab
|
|
355
|
+
|
|
356
|
+
@staticmethod
|
|
357
|
+
def _track(pointer, touches, primary, rx, ry):
|
|
358
|
+
"""Follow one finger; re-anchor without a jump when it changes."""
|
|
359
|
+
live = {s: p for s, p in touches.items()
|
|
360
|
+
if p[0] is not None and p[1] is not None}
|
|
361
|
+
if not live:
|
|
362
|
+
return primary, rx, ry
|
|
363
|
+
cur = min(live)
|
|
364
|
+
x, y = live[cur]
|
|
365
|
+
if primary is None or primary[0] != cur:
|
|
366
|
+
return (cur, x, y), 0.0, 0.0
|
|
367
|
+
dx, dy = x - primary[1], y - primary[2]
|
|
368
|
+
if dx or dy:
|
|
369
|
+
speed = max(abs(dx), abs(dy))
|
|
370
|
+
scale = SENSITIVITY * min(ACCEL_MAX, 1.0 + speed / ACCEL_REF)
|
|
371
|
+
rx += dx * scale
|
|
372
|
+
ry += dy * scale
|
|
373
|
+
ix, iy = int(rx), int(ry)
|
|
374
|
+
rx -= ix
|
|
375
|
+
ry -= iy
|
|
376
|
+
if ix or iy:
|
|
377
|
+
if ix:
|
|
378
|
+
pointer.write(e.EV_REL, e.REL_X, ix)
|
|
379
|
+
if iy:
|
|
380
|
+
pointer.write(e.EV_REL, e.REL_Y, iy)
|
|
381
|
+
pointer.syn()
|
|
382
|
+
return (cur, x, y), rx, ry
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def start_three_finger_drag():
|
|
386
|
+
"""Idempotent module-level starter (safe across hotswap re-exec)."""
|
|
387
|
+
inst = globals().get("_instance")
|
|
388
|
+
if inst is not None and inst._thread is not None and inst._thread.is_alive():
|
|
389
|
+
return inst
|
|
390
|
+
inst = ThreeFingerDrag()
|
|
391
|
+
inst.start()
|
|
392
|
+
globals()["_instance"] = inst
|
|
393
|
+
return inst
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Render-function references and the caller's editable callee projection."""
|
|
2
|
+
import inspect
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from meltygui.core.rendering.core_decoration import Core
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def resolve_view_func(value):
|
|
9
|
+
"""Resolve registered references without evaluating comment expressions."""
|
|
10
|
+
if value is None:
|
|
11
|
+
return None
|
|
12
|
+
if inspect.ismethod(value):
|
|
13
|
+
value = value.__func__
|
|
14
|
+
if callable(value):
|
|
15
|
+
name = getattr(value, "__name__", None)
|
|
16
|
+
registered = Core.melty.render_funcs_by_name.get(name)
|
|
17
|
+
if registered is not None and inspect.unwrap(registered) is inspect.unwrap(value):
|
|
18
|
+
return registered
|
|
19
|
+
from meltygui.core.rendering.render_funcs import _LazyRenderFunc
|
|
20
|
+
if isinstance(value, _LazyRenderFunc):
|
|
21
|
+
return value._resolve()
|
|
22
|
+
if hasattr(value, "__render_func__"):
|
|
23
|
+
return value
|
|
24
|
+
raise ValueError(f"Not a registered render function: {name or value!r}")
|
|
25
|
+
if isinstance(value, str):
|
|
26
|
+
reference = str(value)
|
|
27
|
+
prefix = "__import__('meltygui.core.rendering.render_funcs', fromlist=['RenderFuncs']).RenderFuncs."
|
|
28
|
+
if reference.startswith(prefix):
|
|
29
|
+
reference = reference[len(prefix):]
|
|
30
|
+
parts = reference.split(".")
|
|
31
|
+
if all(part.isidentifier() for part in parts):
|
|
32
|
+
candidate = Core.melty.render_funcs_by_name.get(parts[-1])
|
|
33
|
+
if candidate is not None:
|
|
34
|
+
module = getattr(inspect.unwrap(candidate), "__module__", "")
|
|
35
|
+
if len(parts) == 1 or ".".join(parts[:-1]) in ("RenderFuncs", "meltygui", module):
|
|
36
|
+
return candidate
|
|
37
|
+
raise ValueError(f"Unknown render function: {value}")
|
|
38
|
+
raise ValueError(f"Invalid render function reference: {value!r}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def view_func_name(value):
|
|
42
|
+
return getattr(value, "__name__", str(value)) if value is not None else "Default"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def view_reference_code(value, filename=None):
|
|
46
|
+
"""Use an existing binding, else an import expression valid in any module."""
|
|
47
|
+
from meltygui.code.libcst_conversion import CodeLine
|
|
48
|
+
function = resolve_view_func(value)
|
|
49
|
+
if function is None:
|
|
50
|
+
return None
|
|
51
|
+
from meltygui.core.rendering.render_funcs import RenderFuncs
|
|
52
|
+
for module in tuple(sys.modules.values()):
|
|
53
|
+
if module is None or not filename or getattr(module, "__file__", None) != str(filename):
|
|
54
|
+
continue
|
|
55
|
+
for name, binding in tuple(vars(module).items()):
|
|
56
|
+
if not name.isidentifier():
|
|
57
|
+
continue
|
|
58
|
+
if binding is function or binding is inspect.unwrap(function):
|
|
59
|
+
return CodeLine(name)
|
|
60
|
+
for name, binding in tuple(vars(module).items()):
|
|
61
|
+
if name.isidentifier() and binding is RenderFuncs:
|
|
62
|
+
return CodeLine(f"{name}.{function.__name__}")
|
|
63
|
+
return CodeLine("__import__('meltygui.core.rendering.render_funcs', "
|
|
64
|
+
f"fromlist=['RenderFuncs']).RenderFuncs.{function.__name__}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def comment_view_func(input_value, kwargs):
|
|
68
|
+
"""Own comment wins over the enclosing slot, just like the kwargs splat."""
|
|
69
|
+
selected = None
|
|
70
|
+
collection = kwargs.get("collection")
|
|
71
|
+
if isinstance(collection, dict) and "key" in kwargs:
|
|
72
|
+
overrides = collection.get("__overrides__", {})
|
|
73
|
+
if isinstance(overrides, dict):
|
|
74
|
+
entry = overrides.get(f"__{kwargs['key']}__", {})
|
|
75
|
+
if isinstance(entry, dict):
|
|
76
|
+
selected = entry.get("view_func")
|
|
77
|
+
if isinstance(input_value, dict):
|
|
78
|
+
overrides = input_value.get("__overrides__", {})
|
|
79
|
+
if isinstance(overrides, dict):
|
|
80
|
+
selected = overrides.get("view_func", selected)
|
|
81
|
+
return selected
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def configured_view_func(input_value, kwargs, decoration=None, state_view=None):
|
|
85
|
+
"""Resolve selection before entering a wrapper's layout/cache scopes."""
|
|
86
|
+
defaults = Core.melty.default_kwargs_by_type[kwargs.get("real_type", type(input_value))]
|
|
87
|
+
from meltygui.core.core_render import _codec_render_kwargs
|
|
88
|
+
selected = _codec_render_kwargs(type(input_value)).get("view_func")
|
|
89
|
+
if not isinstance(input_value, (dict, type)):
|
|
90
|
+
selected = getattr(input_value, "view_func", selected)
|
|
91
|
+
selected = (decoration or {}).get("view_func", selected)
|
|
92
|
+
selected = defaults.get("view_func", selected)
|
|
93
|
+
if isinstance(input_value, dict):
|
|
94
|
+
decorators = input_value.get("decorators", {})
|
|
95
|
+
parsed_defaults = decorators.get("defaults", {}) if isinstance(decorators, dict) else {}
|
|
96
|
+
if isinstance(parsed_defaults, dict):
|
|
97
|
+
selected = parsed_defaults.get("view_func", selected)
|
|
98
|
+
collection = kwargs.get("collection")
|
|
99
|
+
attributes = Core.melty.default_kwargs_by_attrib_type[type(collection)]
|
|
100
|
+
selected = attributes.get(kwargs.get("key"), {}).get("view_func", selected)
|
|
101
|
+
if state_view is not None:
|
|
102
|
+
selected = state_view
|
|
103
|
+
selected = kwargs.get("view_func") or selected
|
|
104
|
+
modes = kwargs.get("mode")
|
|
105
|
+
for mode in modes if isinstance(modes, tuple) else (modes,):
|
|
106
|
+
if mode is not None:
|
|
107
|
+
config = mode.get_config_for(input_value)
|
|
108
|
+
if config is not None and config.kwargs:
|
|
109
|
+
selected = config.kwargs.get("view_func", selected)
|
|
110
|
+
selected = comment_view_func(input_value, kwargs) or selected
|
|
111
|
+
return resolve_view_func(selected)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class CallerViewSource(dict):
|
|
115
|
+
"""The call's arguments plus a virtual view_func backed by its callee."""
|
|
116
|
+
def __init__(self, parsed, filename=None, allow_direct=True):
|
|
117
|
+
super().__init__(parsed)
|
|
118
|
+
self.parsed = parsed
|
|
119
|
+
self.filename = filename
|
|
120
|
+
self.direct = False
|
|
121
|
+
if allow_direct and "view_func" not in parsed:
|
|
122
|
+
try:
|
|
123
|
+
function = resolve_view_func(parsed.get("__callee__", getattr(parsed, "func_name", None)))
|
|
124
|
+
except ValueError:
|
|
125
|
+
function = None
|
|
126
|
+
if function is not None:
|
|
127
|
+
self.direct = True
|
|
128
|
+
dict.__setitem__(self, "view_func", function)
|
|
129
|
+
|
|
130
|
+
def __setitem__(self, key, value):
|
|
131
|
+
if key == "view_func" and self.direct:
|
|
132
|
+
if value is None:
|
|
133
|
+
raise ValueError("A direct call needs a render function")
|
|
134
|
+
self.parsed["__callee__"] = view_reference_code(value, self.filename)
|
|
135
|
+
from meltygui.core.conversion.bubbling import _notify
|
|
136
|
+
_notify(self.parsed)
|
|
137
|
+
else:
|
|
138
|
+
self.parsed[key] = value
|
|
139
|
+
super().__setitem__(key, value)
|
|
140
|
+
|
|
141
|
+
def __delitem__(self, key):
|
|
142
|
+
self.pop(key)
|
|
143
|
+
|
|
144
|
+
def pop(self, key, default=None):
|
|
145
|
+
if key == "view_func" and self.direct:
|
|
146
|
+
raise ValueError("A direct call needs a render function")
|
|
147
|
+
self.parsed.pop(key, None)
|
|
148
|
+
return super().pop(key, default)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class WindowViewSource(CallerViewSource):
|
|
152
|
+
"""Window decorator kwargs, with the decorated renderer as their default.
|
|
153
|
+
|
|
154
|
+
The implicit view_func exists only in this projection. Parameter edits
|
|
155
|
+
write through to the decorator parse; choosing another renderer creates
|
|
156
|
+
an explicit view_func kwarg through the same path.
|
|
157
|
+
"""
|
|
158
|
+
def __init__(self, parsed, function, filename=None):
|
|
159
|
+
super().__init__(parsed, filename, allow_direct=False)
|
|
160
|
+
self.function = function
|
|
161
|
+
self.implicit_view_func = self.get("view_func") is None
|
|
162
|
+
if self.implicit_view_func:
|
|
163
|
+
dict.__setitem__(self, "view_func", function)
|
|
164
|
+
|
|
165
|
+
def __setitem__(self, key, value):
|
|
166
|
+
super().__setitem__(key, value)
|
|
167
|
+
if key == "view_func":
|
|
168
|
+
self.implicit_view_func = value is None
|
|
169
|
+
if self.implicit_view_func:
|
|
170
|
+
dict.__setitem__(self, key, self.function)
|
|
171
|
+
|
|
172
|
+
def pop(self, key, default=None):
|
|
173
|
+
value = super().pop(key, default)
|
|
174
|
+
if key == "view_func":
|
|
175
|
+
self.implicit_view_func = True
|
|
176
|
+
dict.__setitem__(self, key, self.function)
|
|
177
|
+
return value
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Shared framework layout machinery."""
|