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,355 @@
|
|
|
1
|
+
"""Native mouse-cursor shapes for Melty views (I-beam over text, resize
|
|
2
|
+
arrows over column dividers / window edges / the corner handle, ...).
|
|
3
|
+
|
|
4
|
+
Two ways a view says which shape the pointer should show:
|
|
5
|
+
|
|
6
|
+
* **Subscription-level (preferred).** Pass ``cursor=`` to
|
|
7
|
+
``draw_state.on_action(...)`` / ``InputHandler.register_hovered``. The
|
|
8
|
+
shape rides on the hover subscription, so it obeys exactly the z-order and
|
|
9
|
+
blocker rules the events do — a closable window in front drops it, and a
|
|
10
|
+
child's subscription beats its parent's. ``InputHandler.process_frame``
|
|
11
|
+
resolves it topmost-first and it STICKS for the whole of a captured drag:
|
|
12
|
+
the shape shown at the press stays until release however far the pointer
|
|
13
|
+
strays from the grab rect, and hover shapes are suppressed meanwhile (no
|
|
14
|
+
I-beam while a window is dragged across an editor). A cursor-only
|
|
15
|
+
subscription is ``on_action([], view_id=..., rect=..., cursor=...)`` — no
|
|
16
|
+
events, just the shape over that rect. Views the wrapper registers for
|
|
17
|
+
events can declare a whole-content shape with ``@render_func(mouse_cursor=…)``.
|
|
18
|
+
``cursor_gate="left_mouse_dragged"`` ties the shape to a DRAG HANDLE: it
|
|
19
|
+
shows only where this registration is the subscriber that would capture
|
|
20
|
+
that event (the topmost ``left_mouse_dragged`` subscriber under the
|
|
21
|
+
pointer), so a window's move handle shows ``MOVE`` on its bare areas and
|
|
22
|
+
nothing over a child that takes the drag itself (a slider, a text
|
|
23
|
+
selection) — the shape tracks where the drag would actually land.
|
|
24
|
+
* **Immediate.** ``imgui.set_mouse_cursor(shape)`` inside code that runs
|
|
25
|
+
THIS frame (imgui resets it every ``new_frame``) — for gestures whose owner
|
|
26
|
+
is not a hover subscription: the right-drag corner resize block in
|
|
27
|
+
core_render, the OS-window edges in titlebar.py, imgui's own widgets. A
|
|
28
|
+
non-arrow imgui shape wins over the subscription shape.
|
|
29
|
+
|
|
30
|
+
``apply(window)`` runs once per frame at the render tail (``Melty.render``,
|
|
31
|
+
GLFW's event thread) and pushes the resolved shape to GLFW, on change only.
|
|
32
|
+
It steps aside while another owner (the region-screenshot tool) has set its
|
|
33
|
+
own cursor image — that owner reports through ``note_external_cursor``.
|
|
34
|
+
"""
|
|
35
|
+
import os
|
|
36
|
+
import struct
|
|
37
|
+
|
|
38
|
+
import meltygui.core.windowing.window_api as glfw
|
|
39
|
+
import meltygui_imgui as imgui
|
|
40
|
+
|
|
41
|
+
ARROW = imgui.MOUSE_CURSOR_ARROW
|
|
42
|
+
TEXT = imgui.MOUSE_CURSOR_TEXT_INPUT
|
|
43
|
+
RESIZE_ALL = imgui.MOUSE_CURSOR_RESIZE_ALL
|
|
44
|
+
RESIZE_NS = imgui.MOUSE_CURSOR_RESIZE_NS
|
|
45
|
+
RESIZE_EW = imgui.MOUSE_CURSOR_RESIZE_EW
|
|
46
|
+
RESIZE_NESW = imgui.MOUSE_CURSOR_RESIZE_NESW
|
|
47
|
+
RESIZE_NWSE = imgui.MOUSE_CURSOR_RESIZE_NWSE
|
|
48
|
+
HAND = imgui.MOUSE_CURSOR_HAND
|
|
49
|
+
NOT_ALLOWED = imgui.MOUSE_CURSOR_NOT_ALLOWED
|
|
50
|
+
|
|
51
|
+
# Directional shapes - one arrow, pointing the way the edge/corner moves:
|
|
52
|
+
# right_side = |>, left_side = <|, bottom_right_corner = ↘ in the L of the
|
|
53
|
+
# corner, ... They sit past imgui's enum (NOT_ALLOWED = 8 is its last), so
|
|
54
|
+
# imgui.set_mouse_cursor won't carry them: use `request(shape)` for an
|
|
55
|
+
# immediate shape, `cursor=` on a subscription. GLFW has no standard cursor
|
|
56
|
+
# for them either - on Wayland they come from the theme image
|
|
57
|
+
# (load_theme_cursor), elsewhere _GLFW_SHAPE maps them to the double arrows.
|
|
58
|
+
RESIZE_E = 101 # |> right_side
|
|
59
|
+
RESIZE_W = 102 # <| left edge
|
|
60
|
+
RESIZE_N = 103 # top edge
|
|
61
|
+
RESIZE_S = 104 # bottom edge
|
|
62
|
+
RESIZE_NW = 105 # top-left corner
|
|
63
|
+
RESIZE_SE = 106 # bottom-right corner
|
|
64
|
+
RESIZE_NE = 107 # top-right corner
|
|
65
|
+
RESIZE_SW = 108 # bottom-left corner
|
|
66
|
+
# The window-move shape: shown where a left drag would move a window - a
|
|
67
|
+
# meltygui window's move handle (core_render's `window_move` on_action) and
|
|
68
|
+
# the OS window's drag strip / drag-anywhere background (titlebar.py) -
|
|
69
|
+
# gated by `cursor_gate="left_mouse_dragged"` so it only appears where that
|
|
70
|
+
# handle would actually CAPTURE the drag. It is on screen most of the time,
|
|
71
|
+
# so it is NOT the four-way fleur: it is the theme's own arrow (the default
|
|
72
|
+
# pointer, same hotspot) recoloured DARK GREY (`_RECOLORED`,
|
|
73
|
+
# load_theme_cursor) - a quiet "this drags the window" hint.
|
|
74
|
+
MOVE = 109
|
|
75
|
+
|
|
76
|
+
_GLFW_SHAPE = {
|
|
77
|
+
TEXT: glfw.IBEAM_CURSOR,
|
|
78
|
+
RESIZE_ALL: glfw.RESIZE_ALL_CURSOR,
|
|
79
|
+
RESIZE_NS: glfw.RESIZE_NS_CURSOR,
|
|
80
|
+
RESIZE_EW: glfw.RESIZE_EW_CURSOR,
|
|
81
|
+
RESIZE_NESW: glfw.RESIZE_NESW_CURSOR,
|
|
82
|
+
RESIZE_NWSE: glfw.RESIZE_NWSE_CURSOR,
|
|
83
|
+
HAND: glfw.POINTING_HAND_CURSOR,
|
|
84
|
+
NOT_ALLOWED: glfw.NOT_ALLOWED_CURSOR,
|
|
85
|
+
# Directional shapes: the nearest double arrow when no theme image loads.
|
|
86
|
+
RESIZE_E: glfw.RESIZE_EW_CURSOR, RESIZE_W: glfw.RESIZE_EW_CURSOR,
|
|
87
|
+
RESIZE_N: glfw.RESIZE_NS_CURSOR, RESIZE_S: glfw.RESIZE_NS_CURSOR,
|
|
88
|
+
RESIZE_NW: glfw.RESIZE_NWSE_CURSOR, RESIZE_SE: glfw.RESIZE_NWSE_CURSOR,
|
|
89
|
+
RESIZE_NE: glfw.RESIZE_NESW_CURSOR, RESIZE_SW: glfw.RESIZE_NESW_CURSOR,
|
|
90
|
+
MOVE: glfw.ARROW_CURSOR, # no badge without a theme image: the plain arrow
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
# Theme search NAMES per shape - the XDG name first, then the legacy X name,
|
|
94
|
+
# the same pairs GLFW's own standard-cursor lookup tries.
|
|
95
|
+
_SHAPE_NAMES = {
|
|
96
|
+
TEXT: ("text", "xterm"),
|
|
97
|
+
RESIZE_ALL: ("all-scroll", "fleur"),
|
|
98
|
+
RESIZE_NS: ("ns-resize", "sb_v_double_arrow"),
|
|
99
|
+
RESIZE_EW: ("ew-resize", "sb_h_double_arrow"),
|
|
100
|
+
RESIZE_NESW: ("nesw-resize", "fd_double_arrow"),
|
|
101
|
+
RESIZE_NWSE: ("nwse-resize", "bd_double_arrow"),
|
|
102
|
+
HAND: ("pointer", "hand2"),
|
|
103
|
+
NOT_ALLOWED: ("not-allowed", "crossed_circle"),
|
|
104
|
+
RESIZE_E: ("e-resize", "right_side"),
|
|
105
|
+
RESIZE_W: ("w-resize", "left_side"),
|
|
106
|
+
RESIZE_N: ("n-resize", "top_side"),
|
|
107
|
+
RESIZE_S: ("s-resize", "bottom_side"),
|
|
108
|
+
RESIZE_NW: ("nw-resize", "top_left_corner"),
|
|
109
|
+
RESIZE_SE: ("se-resize", "bottom_right_corner"),
|
|
110
|
+
RESIZE_NE: ("ne-resize", "top_right_corner"),
|
|
111
|
+
RESIZE_SW: ("sw-resize", "bottom_left_corner"),
|
|
112
|
+
MOVE: ("default", "left_ptr"), # the regular arrow, recoloured below
|
|
113
|
+
}
|
|
114
|
+
# Recoloured shapes: `shape -> (body grey, outline grey)`. The theme image's
|
|
115
|
+
# dark pixels (the body) go to the body grey, its bright pixels (the outline)
|
|
116
|
+
# to the outline grey, by luminance - so a black-on-white arrow keeps its
|
|
117
|
+
# white outline but reads on light AND dark. How to tune: raise the body
|
|
118
|
+
# grey for a lighter pointer, lower the outline grey to darken the rim.
|
|
119
|
+
_RECOLORED = {
|
|
120
|
+
MOVE: (70, 255),
|
|
121
|
+
}
|
|
122
|
+
# libXcursor's default search path (XCURSOR_PATH overrides it).
|
|
123
|
+
_XCURSOR_DEFAULT_PATH = "~/.local/share/icons:~/.icons:/usr/share/icons:/usr/share/pixmaps"
|
|
124
|
+
_XCURSOR_IMAGE_TYPE = 0xFFFD0002
|
|
125
|
+
_GLFW_DEFAULT_CURSOR_SIZE = 16 # wl_init.c loadCursorTheme, XCURSOR_SIZE unset
|
|
126
|
+
|
|
127
|
+
_cursors = {} # (shape, theme names, greys) -> GLFWcursor | (None = the platform default arrow)
|
|
128
|
+
_applied = ARROW # shape last pushed to GLFW; None = unknown, re-push
|
|
129
|
+
_applied_immediate = False # the last push came from an immediate shape
|
|
130
|
+
_requested = None # request(): immediate shape for THIS frame, consumed at the tail
|
|
131
|
+
_external = False # another owner has set its own cursor image
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _theme_dirs():
|
|
135
|
+
path = os.environ.get("XCURSOR_PATH") or _XCURSOR_DEFAULT_PATH
|
|
136
|
+
return [os.path.expanduser(d) for d in path.split(":") if d]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _find_cursor_file(theme, names, _seen=None):
|
|
140
|
+
"""`<dir>/<theme>/cursors/<name>` across the search path, trying every
|
|
141
|
+
name in `names` per theme before following the theme's index.theme
|
|
142
|
+
`Inherits=` chain — libXcursor's resolution order."""
|
|
143
|
+
_seen = _seen if _seen is not None else set()
|
|
144
|
+
if theme in _seen:
|
|
145
|
+
return None
|
|
146
|
+
_seen.add(theme)
|
|
147
|
+
inherits = []
|
|
148
|
+
for directory in _theme_dirs():
|
|
149
|
+
base = os.path.join(directory, theme)
|
|
150
|
+
for name in names:
|
|
151
|
+
candidate = os.path.join(base, "cursors", name)
|
|
152
|
+
if os.path.isfile(candidate):
|
|
153
|
+
return candidate
|
|
154
|
+
index = os.path.join(base, "index.theme")
|
|
155
|
+
if os.path.isfile(index):
|
|
156
|
+
try:
|
|
157
|
+
with open(index, encoding="utf-8", errors="replace") as f:
|
|
158
|
+
for line in f:
|
|
159
|
+
key, sep, value = line.partition("=")
|
|
160
|
+
if sep and key.strip().lower() == "inherits":
|
|
161
|
+
inherits += [t.strip() for t in value.replace(";", ",").split(",") if t.strip()]
|
|
162
|
+
except OSError:
|
|
163
|
+
pass
|
|
164
|
+
for parent in inherits:
|
|
165
|
+
found = _find_cursor_file(parent, names, _seen)
|
|
166
|
+
if found:
|
|
167
|
+
return found
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def load_theme_cursor(shape, size, theme=None):
|
|
172
|
+
"""The theme's image for `shape` at the nominal `size` nearest to what
|
|
173
|
+
the theme ships (Xcursor's rule, ties to the larger), as
|
|
174
|
+
(PIL RGBA image, xhot, yhot) ready for glfw.create_cursor. Frame 0 only.
|
|
175
|
+
A `_RECOLORED` shape comes back recoloured. None when the theme has no
|
|
176
|
+
such shape."""
|
|
177
|
+
theme = theme or os.environ.get("XCURSOR_THEME") or "default"
|
|
178
|
+
loaded = _load_theme_image(theme, _SHAPE_NAMES.get(shape), size)
|
|
179
|
+
greys = _RECOLORED.get(shape)
|
|
180
|
+
if loaded is None or greys is None:
|
|
181
|
+
return loaded
|
|
182
|
+
image, xhot, yhot = loaded
|
|
183
|
+
return _recolor(image, *greys), xhot, yhot
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _recolor(image, body_grey, outline_grey):
|
|
187
|
+
"""`image` with every pixel's RGB replaced by a grey between `body_grey`
|
|
188
|
+
(where the source is black) and `outline_grey` (where it is white),
|
|
189
|
+
picked by the source pixel's luminance; alpha untouched."""
|
|
190
|
+
import numpy as np
|
|
191
|
+
from PIL import Image
|
|
192
|
+
pixels = np.asarray(image).astype(np.float32)
|
|
193
|
+
luminance = pixels[..., :3].mean(axis=-1, keepdims=True) / 255.0
|
|
194
|
+
pixels[..., :3] = body_grey + (outline_grey - body_grey) * luminance
|
|
195
|
+
return Image.fromarray(pixels.round().astype(np.uint8), "RGBA")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _load_theme_image(theme, names, size):
|
|
199
|
+
"""One theme cursor file (`names` tried in order, then the `default`
|
|
200
|
+
theme) decoded to (PIL RGBA image, xhot, yhot). Xcursor pixels are
|
|
201
|
+
premultiplied ARGB; GLFW wants straight RGBA, so the alpha is divided
|
|
202
|
+
back out. None when the theme has no such shape."""
|
|
203
|
+
if not names:
|
|
204
|
+
return None
|
|
205
|
+
path = _find_cursor_file(theme, names)
|
|
206
|
+
if path is None and theme != "default":
|
|
207
|
+
path = _find_cursor_file("default", names)
|
|
208
|
+
if path is None:
|
|
209
|
+
return None
|
|
210
|
+
with open(path, "rb") as f:
|
|
211
|
+
data = f.read()
|
|
212
|
+
magic, header_size, version, toc_count = struct.unpack_from("<4sIII", data, 0)
|
|
213
|
+
if magic != b"Xcur":
|
|
214
|
+
return None
|
|
215
|
+
images = [] # (nominal-size, position) in file order = frame order
|
|
216
|
+
for i in range(toc_count):
|
|
217
|
+
entry_type, subtype, position = struct.unpack_from("<III", data, header_size + 12 * i)
|
|
218
|
+
if entry_type == _XCURSOR_IMAGE_TYPE:
|
|
219
|
+
images.append((subtype, position))
|
|
220
|
+
if not images:
|
|
221
|
+
return None
|
|
222
|
+
best = min(images, key=lambda e: (abs(e[0] - size), -e[0]))[0]
|
|
223
|
+
position = next(pos for nominal, pos in images if nominal == best)
|
|
224
|
+
(_chunk_size, _type, _subtype, _version,
|
|
225
|
+
width, height, xhot, yhot, _delay) = struct.unpack_from("<9I", data, position)
|
|
226
|
+
import numpy as np
|
|
227
|
+
from PIL import Image
|
|
228
|
+
argb = np.frombuffer(data, dtype="<u4", count=width * height, offset=position + 36).reshape(height, width)
|
|
229
|
+
alpha = (argb >> 24).astype(np.uint32)
|
|
230
|
+
channels = [(argb >> 16) & 255, (argb >> 8) & 255, argb & 255]
|
|
231
|
+
safe_alpha = np.where(alpha == 0, 1, alpha)
|
|
232
|
+
straight = [np.minimum(255, (c * 255 + safe_alpha // 2) // safe_alpha) for c in channels]
|
|
233
|
+
rgba = np.stack(straight + [alpha], axis=-1).astype(np.uint8)
|
|
234
|
+
return Image.fromarray(rgba, "RGBA"), int(xhot), int(yhot)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _wayland():
|
|
238
|
+
try:
|
|
239
|
+
return glfw.get_platform() == glfw.PLATFORM_WAYLAND
|
|
240
|
+
except Exception:
|
|
241
|
+
return bool(os.environ.get("WAYLAND_DISPLAY"))
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _cursor_size():
|
|
245
|
+
"""The nominal size GLFW itself loaded the theme at (its default arrow
|
|
246
|
+
stays GLFW's), so our images match it: XCURSOR_SIZE — exported from
|
|
247
|
+
GNOME at boot by glfw_utils.export_desktop_cursor_env — else 16."""
|
|
248
|
+
try:
|
|
249
|
+
return int(os.environ.get("XCURSOR_SIZE", "")) or _GLFW_DEFAULT_CURSOR_SIZE
|
|
250
|
+
except ValueError:
|
|
251
|
+
return _GLFW_DEFAULT_CURSOR_SIZE
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _glfw_cursor(shape):
|
|
255
|
+
"""The GLFW cursor object for `shape`, created once.
|
|
256
|
+
|
|
257
|
+
On Wayland the theme's image is loaded HERE and handed to GLFW as an
|
|
258
|
+
image cursor (glfw.create_cursor), not requested as a standard cursor:
|
|
259
|
+
a standard cursor is a wl_cursor, and GLFW arms its animation timer
|
|
260
|
+
from the frame's `delay` without checking the frame count — Bibata
|
|
261
|
+
(and other clickgen themes) stamp delay=13 on every STATIC shape — so
|
|
262
|
+
the same buffer was re-attached and committed every 13 ms, a light
|
|
263
|
+
irregular flicker of exactly the I-beam / resize shapes (the default
|
|
264
|
+
arrow is a stack-local cursor GLFW never re-sets). An image cursor has
|
|
265
|
+
no wl_cursor, so no timer. Image cursors are buffer scale 1: right-sized
|
|
266
|
+
on scale-1 monitors, blurry (not wrong-sized) on scale-2 ones. Any
|
|
267
|
+
failure falls back to the standard cursor; a shape the theme can't
|
|
268
|
+
provide at all falls back to the arrow and is not retried."""
|
|
269
|
+
# Keyed by the theme names and recolour greys too: a hotswap that edits
|
|
270
|
+
# them (this module's registries to re-exec) gets the new image at once.
|
|
271
|
+
key = (shape, _SHAPE_NAMES.get(shape), _RECOLORED.get(shape))
|
|
272
|
+
if key in _cursors:
|
|
273
|
+
return _cursors[key]
|
|
274
|
+
std = _GLFW_SHAPE.get(shape)
|
|
275
|
+
cur = None
|
|
276
|
+
if std is not None and _wayland():
|
|
277
|
+
try:
|
|
278
|
+
loaded = load_theme_cursor(shape, _cursor_size())
|
|
279
|
+
if loaded is not None:
|
|
280
|
+
image, xhot, yhot = loaded
|
|
281
|
+
cur = glfw.create_cursor(image, xhot, yhot)
|
|
282
|
+
except Exception as e:
|
|
283
|
+
print(f"mouse_cursor: theme image for shape {shape} failed, using GLFW's: {e}")
|
|
284
|
+
cur = None
|
|
285
|
+
if std is not None and cur is None:
|
|
286
|
+
try:
|
|
287
|
+
cur = glfw.create_standard_cursor(std)
|
|
288
|
+
except Exception as e:
|
|
289
|
+
print(f"mouse_cursor: no native cursor for shape {shape}: {e}")
|
|
290
|
+
cur = None
|
|
291
|
+
_cursors[key] = cur
|
|
292
|
+
return cur
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def request(shape):
|
|
296
|
+
"""Immediate shape for this frame — the module-side twin of
|
|
297
|
+
imgui.set_mouse_cursor for shapes imgui's enum can't carry (the
|
|
298
|
+
directional ones). Same contract: call it from code that runs THIS
|
|
299
|
+
frame; the render-tail apply consumes it, so it must be re-asserted
|
|
300
|
+
every frame it applies. Wins over subscription shapes like imgui's."""
|
|
301
|
+
global _requested
|
|
302
|
+
_requested = shape
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def note_external_cursor(on):
|
|
306
|
+
"""Another owner (region_screenshot's crosshair) has set (`on`) or
|
|
307
|
+
released the window cursor. While on, `apply` leaves the cursor alone;
|
|
308
|
+
on release the next frame re-pushes ours (GLFW is at the default then)."""
|
|
309
|
+
global _external, _applied, _applied_immediate
|
|
310
|
+
_external = bool(on)
|
|
311
|
+
if not on:
|
|
312
|
+
_applied = None
|
|
313
|
+
_applied_immediate = False
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def apply(window, early=False):
|
|
317
|
+
"""Push the resolved cursor shape to GLFW. Render thread only.
|
|
318
|
+
|
|
319
|
+
Called TWICE per frame. `early=True` from Melty.begin_frame right after
|
|
320
|
+
the input handler resolved `cursor_shape` against the freshest pointer
|
|
321
|
+
position — BEFORE the draw pass, so a slow frame (draw_text re-rendering)
|
|
322
|
+
can't hold an I-beam the pointer has already left; the shape leads the
|
|
323
|
+
frame instead of trailing it. `early=False` from the render tail, where
|
|
324
|
+
imgui's immediate shapes (titlebar edges, corner resize) are known. An
|
|
325
|
+
immediate shape is re-asserted every frame it applies, so while the tail
|
|
326
|
+
is showing one the early push leaves it alone — otherwise the two would
|
|
327
|
+
alternate. (What this can't fix: the render thread is also the GLFW
|
|
328
|
+
event thread, so nothing moves the shape while a frame is mid-draw.)"""
|
|
329
|
+
global _applied, _applied_immediate, _requested
|
|
330
|
+
if _external:
|
|
331
|
+
return
|
|
332
|
+
if imgui.get_io().config_flags & imgui.CONFIG_NO_MOUSE_CURSOR_CHANGE:
|
|
333
|
+
return
|
|
334
|
+
shape = imgui.get_mouse_cursor()
|
|
335
|
+
immediate = shape != ARROW and shape != imgui.MOUSE_CURSOR_NONE
|
|
336
|
+
if not immediate and _requested is not None:
|
|
337
|
+
shape, immediate = _requested, True
|
|
338
|
+
if not early:
|
|
339
|
+
_requested = None # consumed; re-asserted per frame by its owner
|
|
340
|
+
if not immediate:
|
|
341
|
+
from meltygui.core.melty import Melty
|
|
342
|
+
shape = Melty.event_handler.cursor_shape
|
|
343
|
+
if shape is None:
|
|
344
|
+
shape = ARROW
|
|
345
|
+
if early and _applied_immediate:
|
|
346
|
+
return
|
|
347
|
+
if shape == _applied:
|
|
348
|
+
_applied_immediate = immediate
|
|
349
|
+
return
|
|
350
|
+
try:
|
|
351
|
+
glfw.set_cursor(window, _glfw_cursor(shape))
|
|
352
|
+
except Exception as e:
|
|
353
|
+
print(f"mouse_cursor: set_cursor failed for shape {shape}: {e}")
|
|
354
|
+
_applied = shape
|
|
355
|
+
_applied_immediate = immediate
|