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,735 @@
|
|
|
1
|
+
"""meltygui apps: ``@glfw_window`` turns a draw function into an OS window.
|
|
2
|
+
|
|
3
|
+
from meltygui import glfw_window, draw_text, pressed
|
|
4
|
+
|
|
5
|
+
@glfw_window
|
|
6
|
+
def editor():
|
|
7
|
+
changed, new = draw_text(text)
|
|
8
|
+
...
|
|
9
|
+
|
|
10
|
+
The first decoration boots meltygui: the start-up shortcuts (warm_start.py),
|
|
11
|
+
glfw.init and a hidden owner window (the GL share group's root and the imgui
|
|
12
|
+
context that owns the font atlas) on the calling thread, and meltygui's heavy
|
|
13
|
+
imports on a background thread — the two overlap, as hdr-viewer measured.
|
|
14
|
+
Every decoration registers its function; the loop starts when the main
|
|
15
|
+
module's top level finishes (a trace hook on that frame's return — atexit
|
|
16
|
+
is too late: threading is already shut down), or explicitly with ``run()``.
|
|
17
|
+
|
|
18
|
+
Each window is a Surface (surface.py): frameless with meltygui's own title bar,
|
|
19
|
+
window controls, corner cut and shadow unless Toggles.Melty.wayland_show_frame
|
|
20
|
+
asks for the compositor's frame. Windows are peers: closing one closes it
|
|
21
|
+
alone, the loop ends when the last is gone. Child windows come from
|
|
22
|
+
``draw_something(glfw_window=True)`` inside a body (the render wrapper) and
|
|
23
|
+
follow their parent like nested meltygui windows.
|
|
24
|
+
|
|
25
|
+
Every launch appends a phase timing table to ~/.cache/<app_id>/startup.log;
|
|
26
|
+
MELTY_BENCH=1 also prints it and exits after the first frame.
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import inspect
|
|
31
|
+
import os
|
|
32
|
+
import pathlib
|
|
33
|
+
import sys
|
|
34
|
+
import threading
|
|
35
|
+
import traceback
|
|
36
|
+
import time
|
|
37
|
+
|
|
38
|
+
_T0 = float(os.environ.get('MELTY_T0') or time.time())
|
|
39
|
+
_MARKS = [('launcher exec', _T0), ('interpreter + stdlib', time.time())]
|
|
40
|
+
_ROOTS: list = [] # (fn, kwargs) in decoration order
|
|
41
|
+
_state = dict(booted=False, ran=False, app_id=None, cache=None, imports=None,
|
|
42
|
+
import_error=None, switch_interval=None, failed=False)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def mark(label):
|
|
46
|
+
_MARKS.append((label, time.time()))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _write_startup_log(app_id, subject):
|
|
50
|
+
lines = [f"{time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(_T0))} {subject} (pid {os.getpid()})",
|
|
51
|
+
' +ms dms phase']
|
|
52
|
+
prev = _T0
|
|
53
|
+
for label, t in sorted(_MARKS, key=lambda m: m[1]):
|
|
54
|
+
lines.append(f' {(t - _T0) * 1000:6.0f} {(t - prev) * 1000:6.0f} {label}')
|
|
55
|
+
prev = t
|
|
56
|
+
lines.append(f' total {(_MARKS[-1][1] - _T0) * 1000:.0f} ms')
|
|
57
|
+
text = '\n'.join(lines) + '\n\n'
|
|
58
|
+
path = _state['cache'] / 'startup.log'
|
|
59
|
+
try:
|
|
60
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
with open(path, 'a') as f:
|
|
62
|
+
f.write(text)
|
|
63
|
+
except OSError as e:
|
|
64
|
+
print(f'{app_id}: cannot write {path}: {e}', file=sys.stderr)
|
|
65
|
+
if os.environ.get('MELTY_BENCH'):
|
|
66
|
+
print(text, end='', file=sys.stderr, flush=True)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# --- boot -------------------------------------------------------------------------
|
|
70
|
+
def _default_app_id():
|
|
71
|
+
main = sys.modules.get('__main__')
|
|
72
|
+
path = getattr(main, '__file__', None)
|
|
73
|
+
return pathlib.Path(path).stem.replace('_', '-') if path else 'meltygui-app'
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _utf8_output():
|
|
77
|
+
"""Trace groups and stack reports draw bars with box characters. A stream
|
|
78
|
+
that encodes with a legacy code page (Windows redirects stdout as cp1252)
|
|
79
|
+
raises UnicodeEncodeError on them in the middle of a frame, which leaves
|
|
80
|
+
the imgui ID stack unbalanced; such a stream writes UTF-8 instead."""
|
|
81
|
+
for stream in (sys.stdout, sys.stderr):
|
|
82
|
+
encoding = (getattr(stream, 'encoding', None) or '').lower().replace('-', '')
|
|
83
|
+
if encoding != 'utf8' and hasattr(stream, 'reconfigure'):
|
|
84
|
+
stream.reconfigure(encoding='utf-8', errors='replace')
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def boot(app_id=None):
|
|
88
|
+
"""Start meltygui: shortcuts, glfw, the owner window, the import thread.
|
|
89
|
+
Idempotent; the first @glfw_window calls it."""
|
|
90
|
+
if _state['booted']:
|
|
91
|
+
if app_id and app_id != _state['app_id']:
|
|
92
|
+
print(f"meltygui: app_id {app_id!r} ignored — already booted as {_state['app_id']!r} "
|
|
93
|
+
f"(the first boot names the session and cache directories)", file=sys.stderr)
|
|
94
|
+
return
|
|
95
|
+
_state['booted'] = True
|
|
96
|
+
_utf8_output()
|
|
97
|
+
_state['app_id'] = app_id or _default_app_id()
|
|
98
|
+
cache = pathlib.Path(os.environ.get('XDG_CACHE_HOME') or pathlib.Path.home() / '.cache') / _state['app_id']
|
|
99
|
+
_state['cache'] = cache
|
|
100
|
+
_register_editable(getattr(sys.modules.get('__main__'), '__file__', None))
|
|
101
|
+
if sys.platform.startswith('linux'):
|
|
102
|
+
os.environ.setdefault('GDK_BACKEND', 'wayland')
|
|
103
|
+
import meltygui.core.styling.warm_start as warm_start
|
|
104
|
+
warm_start.prepare(cache)
|
|
105
|
+
import meltygui.core.windowing.window_api as glfw
|
|
106
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
107
|
+
backend = glfw.select_backend(Toggles.windows.native_os_windows)
|
|
108
|
+
if backend == 'wayland':
|
|
109
|
+
sys._lsd_wayland_libdecor_disabled = True
|
|
110
|
+
# While the import thread holds the GIL, each of pyGLFW's Python-side
|
|
111
|
+
# steps waits up to a switch interval for it (5 ms default: window
|
|
112
|
+
# creation went 80 -> 150 ms). Shorten it until the imports are done.
|
|
113
|
+
_state['switch_interval'] = sys.getswitchinterval()
|
|
114
|
+
sys.setswitchinterval(0.0002)
|
|
115
|
+
thread = threading.Thread(target=_run_imports, name='meltygui-imports', daemon=True)
|
|
116
|
+
thread.start()
|
|
117
|
+
_state['imports'] = thread
|
|
118
|
+
mark(f'{backend} window API ready')
|
|
119
|
+
# libdecor loads its GTK plugin at init (~60 ms) and the frameless window
|
|
120
|
+
# never shows it: meltygui's own hint (Toggles.Melty.wayland_native_frame)
|
|
121
|
+
# disables it and records that for titlebar.py, whose chrome only runs
|
|
122
|
+
# on the native frame. MELTY_LIBDECOR=1 keeps libdecor (compositors
|
|
123
|
+
# without xdg-decoration), which also means the compositor's frame.
|
|
124
|
+
if backend == 'glfw' and os.environ.get('MELTY_LIBDECOR'):
|
|
125
|
+
sys._lsd_wayland_libdecor_disabled = False
|
|
126
|
+
from meltygui.core.windowing.glfw_utils import apply_wayland_frame_hint
|
|
127
|
+
apply_wayland_frame_hint()
|
|
128
|
+
if not glfw.init():
|
|
129
|
+
raise SystemExit('glfw.init failed')
|
|
130
|
+
mark(f'{backend}.init')
|
|
131
|
+
if backend == 'glfw':
|
|
132
|
+
warm_start.remember_glfw_library(cache)
|
|
133
|
+
glfw.window_hint(glfw.VISIBLE, False)
|
|
134
|
+
glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
|
|
135
|
+
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
|
|
136
|
+
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)
|
|
137
|
+
owner = glfw.create_window(1, 1, 'meltygui owner', None, None)
|
|
138
|
+
if not owner:
|
|
139
|
+
raise SystemExit('glfw.create_window (owner) failed')
|
|
140
|
+
glfw.default_window_hints()
|
|
141
|
+
_state['owner'] = owner
|
|
142
|
+
mark('owner window created')
|
|
143
|
+
# The first eglMakeCurrent loads driver state (~30 ms on NVIDIA). Do it
|
|
144
|
+
# while the import worker is still running, before joining the worker.
|
|
145
|
+
glfw.make_context_current(owner)
|
|
146
|
+
mark('owner context current')
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _run_imports():
|
|
150
|
+
try:
|
|
151
|
+
# PyOpenGL only imports numpy during the first renderer call,
|
|
152
|
+
# after the driver work could have overlapped it. No GL calls here.
|
|
153
|
+
from OpenGL.arrays import numpymodule # noqa: F401
|
|
154
|
+
import meltygui_imgui as imgui # noqa: F401
|
|
155
|
+
import OpenGL.GL # noqa: F401
|
|
156
|
+
mark('imgui/numpy/GL imported (bg)')
|
|
157
|
+
import meltygui.core.melty as runtime # noqa: F401
|
|
158
|
+
import meltygui.core.windowing.surface as surface # noqa: F401
|
|
159
|
+
import meltygui.editor.text_editor as text_editor
|
|
160
|
+
import meltygui.view.texture_view as texture_view # noqa: F401
|
|
161
|
+
mark('meltygui imported (bg)')
|
|
162
|
+
except BaseException as e: # re-raised on the main thread
|
|
163
|
+
_state['import_error'] = e
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _wait_imports():
|
|
167
|
+
thread = _state['imports']
|
|
168
|
+
if thread is not None:
|
|
169
|
+
thread.join()
|
|
170
|
+
_state['imports'] = None
|
|
171
|
+
if _state['switch_interval'] is not None:
|
|
172
|
+
sys.setswitchinterval(_state['switch_interval'])
|
|
173
|
+
if _state['import_error'] is not None:
|
|
174
|
+
raise _state['import_error']
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _init_melty():
|
|
178
|
+
"""Once, after the imports: the owner imgui context with the font atlas,
|
|
179
|
+
the global style manager, meltygui's flags."""
|
|
180
|
+
import meltygui.core.windowing.window_api as glfw
|
|
181
|
+
import meltygui_imgui as imgui
|
|
182
|
+
from meltygui.core.melty import Melty
|
|
183
|
+
from meltygui.core.styling.fonts import FontManager
|
|
184
|
+
from meltygui.core.windowing.surface import Surface
|
|
185
|
+
from meltygui.core.runtime.toggles import Toggles
|
|
186
|
+
from meltygui.core.styling.style_core import ImGuiStyleManager
|
|
187
|
+
import meltygui.core.styling.warm_start as warm_start
|
|
188
|
+
owner = _state['owner']
|
|
189
|
+
glfw.make_context_current(owner)
|
|
190
|
+
Surface.owner_window = owner
|
|
191
|
+
Surface.owner_context = imgui.create_context()
|
|
192
|
+
imgui.get_io().ini_file_name = None
|
|
193
|
+
imgui.get_io().display_size = (1.0, 1.0) # the atlas hinting pass frames on it
|
|
194
|
+
Melty.glfw_window = owner
|
|
195
|
+
Melty.font_mgr = FontManager(imgui.get_io(), Melty.resolve_ui_scale())
|
|
196
|
+
Melty.font_mgr.prewarm()
|
|
197
|
+
warm_start.cache_hinted_atlas(Melty.font_mgr, _state['cache'])
|
|
198
|
+
mark('fonts loaded')
|
|
199
|
+
Melty.style_manager = ImGuiStyleManager()
|
|
200
|
+
Melty.global_attrs['style_manager'] = Melty.style_manager
|
|
201
|
+
# The app's persisted draw states (app_session.py): loaded on the
|
|
202
|
+
# first Surface so every surface's `Melty.vis.root` is the session and
|
|
203
|
+
# get_draw_state / note_window_seen / the window z-order read and write
|
|
204
|
+
# the stores that run() saves on exit.
|
|
205
|
+
session = _load_session()
|
|
206
|
+
_register_projects()
|
|
207
|
+
Melty.draw_state_registry = session.draw_state_registry
|
|
208
|
+
Melty.adopt_registered_windows(session)
|
|
209
|
+
Surface.session = session
|
|
210
|
+
mark('session loaded')
|
|
211
|
+
# meltygui boots in "annotation mode" (view calls return carriers, nothing
|
|
212
|
+
# renders) until the studio's Melty.init() clears it. That init also
|
|
213
|
+
# starts file watchers and a jedi worker we do not need.
|
|
214
|
+
Melty.annotation_mode = False
|
|
215
|
+
Toggles.show_fps = False
|
|
216
|
+
if os.environ.get('MELTY_NO_OS_FRAME'):
|
|
217
|
+
Toggles.Melty.push_os_window_edges = False
|
|
218
|
+
Surface.app_id = _state['app_id']
|
|
219
|
+
import meltygui.core.windowing.geometry_feed as geometry_feed
|
|
220
|
+
geometry_feed.start() # for rects: the size fit, child placement, os_frame
|
|
221
|
+
mark('meltygui configured')
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _load_session():
|
|
225
|
+
"""The app's AppSession (app_session.load), read once: by the first
|
|
226
|
+
`persisted` call or by _init_melty, whichever comes first. Needs the
|
|
227
|
+
meltygui imports (the pickled classes), so it waits for them."""
|
|
228
|
+
session = _state.get('session')
|
|
229
|
+
if session is None:
|
|
230
|
+
_wait_imports()
|
|
231
|
+
import meltygui.core.runtime.app_session as app_session
|
|
232
|
+
session = app_session.load(_state['app_id'])
|
|
233
|
+
_state['session'] = session
|
|
234
|
+
mark('session loaded')
|
|
235
|
+
return session
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def persisted(name, factory, *, app_id=None):
|
|
239
|
+
"""An object the app keeps between runs: last run's saved `name`, or a
|
|
240
|
+
fresh `factory()` when there is none (or the saved one is not an
|
|
241
|
+
instance of `factory`, a class). It rides the app's session
|
|
242
|
+
(app_session.AppSession.app_state) and is saved with the draw states
|
|
243
|
+
when the loop exits, so it must be a DictConversion — its public,
|
|
244
|
+
non-@no_save fields persist, exactly as a studio model field does.
|
|
245
|
+
|
|
246
|
+
open_files = meltygui.persisted('open_files', OpenFiles, app_id='meltygui-code-editor')
|
|
247
|
+
|
|
248
|
+
`app_id` names the session file when this runs before the first
|
|
249
|
+
`@glfw_window` (the usual place — the object feeds the window's body);
|
|
250
|
+
the decorator's later `app_id` must match it."""
|
|
251
|
+
boot(app_id)
|
|
252
|
+
session = _load_session()
|
|
253
|
+
saved = session.app_state.get(name)
|
|
254
|
+
if saved is None or (isinstance(factory, type) and not isinstance(saved, factory)):
|
|
255
|
+
saved = factory()
|
|
256
|
+
session.app_state[name] = saved
|
|
257
|
+
return saved
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# --- the decorator ------------------------------------------------------------------
|
|
261
|
+
def _register_editable(file):
|
|
262
|
+
"""Make the project holding `file` editable source (address.add_editable_root):
|
|
263
|
+
the app's own code loads into the studio's code hosts, so its decorators
|
|
264
|
+
are input sources (the inputs tab, the header's tint chip, `locate_<param>`)
|
|
265
|
+
and its files hotswap, exactly like the checkout's."""
|
|
266
|
+
if not file:
|
|
267
|
+
return
|
|
268
|
+
from meltygui.code.fileref import add_editable_root
|
|
269
|
+
add_editable_root(file)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _register_projects():
|
|
273
|
+
"""Every folder marked as a project in the shared file-meta store
|
|
274
|
+
(file_meta.mark_project — machine-wide, so a project marked in the
|
|
275
|
+
studio or another app counts here) is editable source too, like the
|
|
276
|
+
app's own tree. The store is a small pickle; read once at init."""
|
|
277
|
+
try:
|
|
278
|
+
from meltygui.core.runtime.extensions import source_folders as project_roots
|
|
279
|
+
from meltygui.code.fileref import add_editable_root
|
|
280
|
+
for root in project_roots():
|
|
281
|
+
add_editable_root(root)
|
|
282
|
+
except Exception:
|
|
283
|
+
traceback.print_exc()
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def glfw_window(fn=None, *, name=None, width=1280, height=800, app_id=None, on_close=None, **view_kwargs):
|
|
287
|
+
"""Register ``fn`` as an OS window. ``fn()`` draws the window's content
|
|
288
|
+
each frame; views it draws at root level fill the window.
|
|
289
|
+
|
|
290
|
+
``name`` (default: the function's name) is the window's name AND its
|
|
291
|
+
OS title — one per window; ``width`` / ``height`` its content size.
|
|
292
|
+
``on_close(surface)`` is asked when the window is told to close (the
|
|
293
|
+
title bar's ×, the compositor, `glfw.set_window_should_close`): return
|
|
294
|
+
False to keep it (hide it, say — a chat app with a turn streaming).
|
|
295
|
+
These are the universal meltygui names (`@window`, every view's kwargs),
|
|
296
|
+
never `title=` or `size=` (Lukas 09-12).
|
|
297
|
+
|
|
298
|
+
Every other keyword argument is the root VIEW's, exactly as `@window`'s
|
|
299
|
+
are the studio window's (`tint=`, `disable_scroll=`, `value=`,
|
|
300
|
+
`with_header=`, `show_name=`, ...): a render-func body is drawn with
|
|
301
|
+
them as the window's root view; a plain-function body runs under the
|
|
302
|
+
`tint` (the style tint its filling view colours from). Like `@window`
|
|
303
|
+
the decorator is an INPUT SOURCE of the view (`@glfw_window(<fn>)` in
|
|
304
|
+
the inputs tab, read and written by `locate_<param>`): its file's
|
|
305
|
+
project becomes editable source, and a hotswap that re-runs the
|
|
306
|
+
decorator (an edited kwarg lands as a recompile of the def) updates
|
|
307
|
+
the registered window's config IN PLACE — same name, same window,
|
|
308
|
+
the new kwargs on the next frame — instead of registering a second
|
|
309
|
+
root."""
|
|
310
|
+
def wrap(fn):
|
|
311
|
+
boot(app_id)
|
|
312
|
+
try:
|
|
313
|
+
source = inspect.getsourcefile(inspect.unwrap(fn))
|
|
314
|
+
except TypeError:
|
|
315
|
+
source = None
|
|
316
|
+
_register_editable(source)
|
|
317
|
+
config = dict(name=name or fn.__name__, width=int(width), height=int(height), on_close=on_close,
|
|
318
|
+
view_kwargs=view_kwargs)
|
|
319
|
+
for index, (registered, existing) in enumerate(_ROOTS):
|
|
320
|
+
if existing['name'] == config['name']:
|
|
321
|
+
existing.update(config) # the live config object: the body reads it
|
|
322
|
+
if not _state['ran']:
|
|
323
|
+
_ROOTS[index] = (fn, existing)
|
|
324
|
+
break
|
|
325
|
+
else:
|
|
326
|
+
_ROOTS.append((fn, config))
|
|
327
|
+
if not _state.get('hooked'):
|
|
328
|
+
_state['hooked'] = True
|
|
329
|
+
_hook_main_return()
|
|
330
|
+
return fn
|
|
331
|
+
return wrap(fn) if fn is not None else wrap
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _root_body(fn, name, view_kwargs=None, config=None):
|
|
335
|
+
"""The window's body. A plain function draws inline into the surface
|
|
336
|
+
root (its filling view sizes itself to the window). A RENDER FUNC (the
|
|
337
|
+
@window playgrounds: `@glfw_window` over `@render_func`, the direct
|
|
338
|
+
swap) is drawn as the window's root view the way the studio draws a
|
|
339
|
+
@window: a meltygui view filling the window, with the background and
|
|
340
|
+
layout context its children (draw_rows, draw_any, fields) expect —
|
|
341
|
+
minus the closable chrome, which the OS window provides.
|
|
342
|
+
|
|
343
|
+
``config`` is the root's LIVE registration (the dict in _ROOTS): a
|
|
344
|
+
render-func body reads its `view_kwargs` on every frame, so a
|
|
345
|
+
re-decoration (a hotswapped `@glfw_window(tint=...)` edit) reaches the
|
|
346
|
+
open window; without it `view_kwargs` is fixed."""
|
|
347
|
+
def current_kwargs():
|
|
348
|
+
source = config.get('view_kwargs') if config is not None else view_kwargs
|
|
349
|
+
return dict(source or {})
|
|
350
|
+
if hasattr(fn, '__render_func__'):
|
|
351
|
+
return lambda surface: _draw_root(fn, name, **current_kwargs())
|
|
352
|
+
if current_kwargs().get('tint') is None and config is None:
|
|
353
|
+
return lambda surface: fn()
|
|
354
|
+
|
|
355
|
+
def tinted(surface):
|
|
356
|
+
# The body runs under the decorator's tint (what draw_bg and the
|
|
357
|
+
# style colours read), and after - the same push/restore the
|
|
358
|
+
# wrapper does around a tinted view. Read per frame: a live tint
|
|
359
|
+
# edit (anywhere.live_apply) or a re-decoration changes the config.
|
|
360
|
+
tint = current_kwargs().get('tint')
|
|
361
|
+
if tint is None:
|
|
362
|
+
fn()
|
|
363
|
+
return
|
|
364
|
+
from meltygui.core.melty import Melty
|
|
365
|
+
previous = Melty.style_manager.get_tint()
|
|
366
|
+
Melty.style_manager.set_imgui_tint(*tint[:4])
|
|
367
|
+
try:
|
|
368
|
+
fn()
|
|
369
|
+
finally:
|
|
370
|
+
Melty.style_manager.set_imgui_tint(*previous)
|
|
371
|
+
return tinted
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _searchable_body(body):
|
|
375
|
+
"""The root body plus the app's global search (app_search.draw: a no-op
|
|
376
|
+
unless meltygui.global_search enabled it and this is its window), drawn
|
|
377
|
+
AFTER the body so the search window floats above the root."""
|
|
378
|
+
def searchable(surface):
|
|
379
|
+
body(surface)
|
|
380
|
+
from meltygui.core.runtime.extensions import call
|
|
381
|
+
from meltygui.view.code_view import draw_pending_preview
|
|
382
|
+
draw_pending_preview()
|
|
383
|
+
call('root_draw', surface)
|
|
384
|
+
return searchable
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def _draw_root(fn, name, value=None, **kwargs):
|
|
388
|
+
"""Draw the render func ``fn`` as the window's root view, filling it:
|
|
389
|
+
what `@glfw_window` over `@render_func` does each frame. ``value`` is
|
|
390
|
+
the view's input value (None: the view owns its state); ``kwargs`` are
|
|
391
|
+
the decorator's view kwargs. ``with_header=draw_header`` puts the meltygui
|
|
392
|
+
header in the chrome row beside the window controls
|
|
393
|
+
(surface.root_view_kwargs)."""
|
|
394
|
+
from meltygui.core.windowing.surface import root_view_kwargs
|
|
395
|
+
# A closable meltygui window (the studio's Mode.MODE_WINDOW), pinned to
|
|
396
|
+
# the surface: layouts (draw_rows / draw_columns) register their
|
|
397
|
+
# children on the enclosing view, so the root must be one.
|
|
398
|
+
return fn(value, **root_view_kwargs(name or fn.__name__, **kwargs))
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _hook_main_return():
|
|
402
|
+
"""Start the loop when the main module's top level returns, so a script
|
|
403
|
+
is just decorated functions. A local trace function on that frame sees
|
|
404
|
+
its 'return' event (tracing has to be enabled globally for local trace
|
|
405
|
+
functions to fire; the global one declines every other frame, and line
|
|
406
|
+
events are off, so the cost is one call per function call until the
|
|
407
|
+
module ends). Declines when a debugger already traces; ``run()`` then
|
|
408
|
+
has to be called explicitly."""
|
|
409
|
+
if sys.gettrace() is not None:
|
|
410
|
+
return
|
|
411
|
+
frame = sys._getframe(1)
|
|
412
|
+
while frame is not None and frame.f_globals.get('__name__') != '__main__':
|
|
413
|
+
frame = frame.f_back
|
|
414
|
+
if frame is None or frame.f_code.co_name != '<module>':
|
|
415
|
+
return
|
|
416
|
+
state = {'failed': False}
|
|
417
|
+
|
|
418
|
+
def local(fr, event, arg):
|
|
419
|
+
if event == 'exception':
|
|
420
|
+
state['failed'] = True
|
|
421
|
+
elif event == 'return':
|
|
422
|
+
sys.settrace(None)
|
|
423
|
+
fr.f_trace = None
|
|
424
|
+
_state['hooked'] = False
|
|
425
|
+
if not state['failed']:
|
|
426
|
+
run()
|
|
427
|
+
return local
|
|
428
|
+
|
|
429
|
+
_state['tracer'] = lambda fr, event, arg: None
|
|
430
|
+
sys.settrace(_state['tracer'])
|
|
431
|
+
frame.f_trace_lines = False
|
|
432
|
+
frame.f_trace = local
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
# --- the loop -----------------------------------------------------------------------
|
|
436
|
+
def _unhook_main_return():
|
|
437
|
+
"""Drop the implicit-start trace hook. It needs GLOBAL tracing on to
|
|
438
|
+
fire its local 'return' hook, and global tracing costs a callback per
|
|
439
|
+
Python call — fine for the moment between the decorators and the
|
|
440
|
+
module's return, ruinous for a whole session: a script that calls
|
|
441
|
+
run() itself never returns from its module while the loop runs, and
|
|
442
|
+
every frame ran under the tracer (draw_text 26 ms for an empty line,
|
|
443
|
+
the chat window at 7 fps, 09-12)."""
|
|
444
|
+
if not _state.get('hooked'):
|
|
445
|
+
return
|
|
446
|
+
if sys.gettrace() is not _state.get('tracer'):
|
|
447
|
+
return # a debugger's tracer, not ours: leave it
|
|
448
|
+
sys.settrace(None)
|
|
449
|
+
frame = sys._getframe(1)
|
|
450
|
+
while frame is not None:
|
|
451
|
+
if frame.f_globals.get('__name__') == '__main__' and frame.f_code.co_name == '<module>':
|
|
452
|
+
frame.f_trace = None
|
|
453
|
+
frame = frame.f_back
|
|
454
|
+
_state['hooked'] = False
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def run():
|
|
458
|
+
"""Open every registered window and run until the last one closes."""
|
|
459
|
+
if _state['ran']:
|
|
460
|
+
return
|
|
461
|
+
_unhook_main_return()
|
|
462
|
+
_state['ran'] = True
|
|
463
|
+
if not _state['booted']:
|
|
464
|
+
boot()
|
|
465
|
+
import meltygui.core.windowing.window_api as glfw
|
|
466
|
+
_wait_imports()
|
|
467
|
+
_init_melty()
|
|
468
|
+
from meltygui.core.melty import Melty
|
|
469
|
+
from meltygui.core.windowing.surface import Surface
|
|
470
|
+
from meltygui.core.runtime.extensions import call
|
|
471
|
+
if _ROOTS:
|
|
472
|
+
call('root_ready', _ROOTS[0][1]['name'])
|
|
473
|
+
for fn, kw in _ROOTS:
|
|
474
|
+
view_kwargs = kw.get('view_kwargs') or {}
|
|
475
|
+
# A plain-function body draws straight onto the surface, so its
|
|
476
|
+
# decorator tint is the surface's; a render-func body gets its
|
|
477
|
+
# kwargs from _draw_root and sits on the default ground.
|
|
478
|
+
ground_tint = None if hasattr(fn, '__render_func__') else view_kwargs.get('tint')
|
|
479
|
+
Surface(kw['name'], _searchable_body(_root_body(fn, kw['name'], view_kwargs, config=kw)),
|
|
480
|
+
width=kw['width'], height=kw['height'], tint=ground_tint, on_close=kw.get('on_close'))
|
|
481
|
+
mark(f'{len(Surface.all)} window(s) created')
|
|
482
|
+
import meltygui.core.windowing.glfw_utils as glfw_utils
|
|
483
|
+
bench = os.environ.get('MELTY_BENCH')
|
|
484
|
+
first = True
|
|
485
|
+
frames = 0
|
|
486
|
+
frametime = [] if os.environ.get('MELTY_FRAMETIME') else None
|
|
487
|
+
glfw_utils.request_render()
|
|
488
|
+
try:
|
|
489
|
+
while Surface.all:
|
|
490
|
+
glfw.poll_events()
|
|
491
|
+
_open_requested_children()
|
|
492
|
+
# A frame only on a request (request_render: the input layer's
|
|
493
|
+
# callbacks, Surface's focus/resize/close hooks, animations via
|
|
494
|
+
# frames_left, post-render rendering, a window / white flag), as in
|
|
495
|
+
# the studio's loop; an idle app blocks in wait_events. The flag
|
|
496
|
+
# is cleared BEFORE the frame so a request made mid-frame
|
|
497
|
+
# remains for the next iteration. app_tick advances only with a
|
|
498
|
+
# frame: a child not drawn in a TICK closes (_close_stale_children).
|
|
499
|
+
if glfw_utils._needs_render.is_set():
|
|
500
|
+
glfw_utils._needs_render.clear()
|
|
501
|
+
Melty.app_tick += 1
|
|
502
|
+
frames += 1
|
|
503
|
+
started = time.perf_counter() if frametime is not None else 0.0
|
|
504
|
+
for surface in list(Surface.all):
|
|
505
|
+
try:
|
|
506
|
+
surface.frame()
|
|
507
|
+
except Exception:
|
|
508
|
+
_state['failed'] = True
|
|
509
|
+
raise
|
|
510
|
+
if frametime is not None:
|
|
511
|
+
# MELTY_FRAMETIME=1 prints a line per frame with the render
|
|
512
|
+
# thread's time for it (the budget for 120 fps is 8.3 ms).
|
|
513
|
+
spent = (time.perf_counter() - started) * 1000
|
|
514
|
+
print(f'meltygui: frame {frames} {spent:.1f} ms', flush=True)
|
|
515
|
+
_close_stale_children()
|
|
516
|
+
for surface in list(Surface.all):
|
|
517
|
+
_present_children(surface)
|
|
518
|
+
for surface in list(Surface.all):
|
|
519
|
+
if surface.closed:
|
|
520
|
+
_note_closed(surface)
|
|
521
|
+
surface.destroy()
|
|
522
|
+
if first and Surface.all:
|
|
523
|
+
first = False
|
|
524
|
+
mark('first frame presented')
|
|
525
|
+
_write_startup_log(_state['app_id'], ' '.join(s.name for s in Surface.all))
|
|
526
|
+
if bench:
|
|
527
|
+
break
|
|
528
|
+
if glfw_utils._needs_render.is_set():
|
|
529
|
+
continue
|
|
530
|
+
# glfw.wait_events also returns for events not asked for (on
|
|
531
|
+
# Hyprland the NVIDIA EGL driver's per-swap wl_buffer teardown is
|
|
532
|
+
# posted on the event queue), hence the gate above. Children
|
|
533
|
+
# follow their parent through the geometry feed, a child that
|
|
534
|
+
# requests nothing: poll while any exist. The idle wait is
|
|
535
|
+
# bounded so a signal (Ctrl+C) gets a turn: Python runs its
|
|
536
|
+
# handler between bytecodes, never inside a blocked OS call.
|
|
537
|
+
glfw.wait_events_timeout(1 / 60 if any(s.children for s in Surface.all) else 1.0)
|
|
538
|
+
finally:
|
|
539
|
+
_debug(f'{frames} frames rendered')
|
|
540
|
+
if not _state['failed']:
|
|
541
|
+
_flush_pending_saves()
|
|
542
|
+
_save_session()
|
|
543
|
+
for surface in list(Surface.all):
|
|
544
|
+
surface.destroy()
|
|
545
|
+
glfw.terminate()
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
def _flush_pending_saves():
|
|
549
|
+
"""Write the edits the file hosts hold. A code_file_io host's save is
|
|
550
|
+
the studio's deferred model: each edit queues into PendingSave (in
|
|
551
|
+
memory) and the disk write happens at apply_all_saves, which the studio
|
|
552
|
+
runs from Melty.shutdown. An app that draws such hosts (the code editor)
|
|
553
|
+
exits through here, so flush here too — before the surfaces go, while
|
|
554
|
+
the imgui context the codecs' notifications expect is still alive. A
|
|
555
|
+
failed frame skips it: nothing written from a broken state."""
|
|
556
|
+
from meltygui.editor.pending_save import PendingSave
|
|
557
|
+
if not PendingSave.pending_saves:
|
|
558
|
+
return
|
|
559
|
+
_debug(f'flushing {len(PendingSave.pending_saves)} pending save(s)')
|
|
560
|
+
PendingSave.apply_all_saves()
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
def _save_session():
|
|
564
|
+
"""Persist the draw states (app_session.save) — the studio's exit save,
|
|
565
|
+
for an app. Skipped after a failed frame like the pending saves: a
|
|
566
|
+
broken state must not replace the last good session."""
|
|
567
|
+
session = _state.get('session')
|
|
568
|
+
if session is None:
|
|
569
|
+
return
|
|
570
|
+
import meltygui.core.runtime.app_session as app_session
|
|
571
|
+
path = app_session.save(session, _state['app_id'])
|
|
572
|
+
_debug(f'session saved to {path}' if path else 'session save failed')
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _open_requested_children():
|
|
576
|
+
"""Child surfaces the render wrapper asked for (glfw_window=True) since
|
|
577
|
+
the last tick: Melty.surface_requests, filled by surface_window_request."""
|
|
578
|
+
from meltygui.core.melty import Melty
|
|
579
|
+
from meltygui.core.windowing.surface import Surface
|
|
580
|
+
requests = Melty.surface_requests
|
|
581
|
+
while requests:
|
|
582
|
+
req = requests.pop(0)
|
|
583
|
+
parent = req.parent_surface
|
|
584
|
+
if parent is None or parent not in Surface.all or req.surface is not None or req.closed:
|
|
585
|
+
continue
|
|
586
|
+
ds = req.draw_state
|
|
587
|
+
size = req.window_size
|
|
588
|
+
child = Surface(req.name, _child_body(req), width=size[0], height=size[1],
|
|
589
|
+
parent=parent, draw_state=ds)
|
|
590
|
+
child.request = req
|
|
591
|
+
req.surface = child
|
|
592
|
+
_debug(f'child surface {child.title!r} of {parent.title!r} size={size}')
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _child_body(req):
|
|
596
|
+
def body(surface):
|
|
597
|
+
from meltygui.core.melty import Melty
|
|
598
|
+
Melty.draw_surface_root(req, surface)
|
|
599
|
+
return body
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
def _close_stale_children():
|
|
603
|
+
"""Immediate mode: a child whose glfw_window=True call was not made
|
|
604
|
+
this tick closes (its parent stopped drawing it); the next call
|
|
605
|
+
reopens it."""
|
|
606
|
+
from meltygui.core.melty import Melty
|
|
607
|
+
for req in list(Melty.surface_windows.values()):
|
|
608
|
+
child = req.surface
|
|
609
|
+
if child is not None and req.tick != Melty.app_tick:
|
|
610
|
+
child.closed = True
|
|
611
|
+
child.stale = True
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def _note_closed(surface, *, user_closed=True):
|
|
615
|
+
"""A surface on its way out. An OS close of a child (the title bar's
|
|
616
|
+
X, the compositor) leaves its request CLOSED: the parent's calls
|
|
617
|
+
return (False, None) from then on, as a closed meltygui window's do. A
|
|
618
|
+
stale child (not drawn this tick) just drops its surface."""
|
|
619
|
+
req = surface.request
|
|
620
|
+
if req is not None:
|
|
621
|
+
req.surface = None
|
|
622
|
+
if not getattr(surface, 'stale', False):
|
|
623
|
+
was_closed = req.closed
|
|
624
|
+
req.closed = True
|
|
625
|
+
if req.draw_state is not None:
|
|
626
|
+
from meltygui.core.windowing.window_visibility import native_user_window_closed
|
|
627
|
+
if user_closed and not was_closed:
|
|
628
|
+
native_user_window_closed(req.draw_state, True)
|
|
629
|
+
req.draw_state.closed = True
|
|
630
|
+
for child in list(surface.children):
|
|
631
|
+
_note_closed(child, user_closed=False)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
ACK_TIMEOUT_S = 0.5
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _debug(msg):
|
|
638
|
+
if os.environ.get('MELTY_DEBUG'):
|
|
639
|
+
print(f'[app] {msg}', flush=True)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def _present_children(parent):
|
|
643
|
+
"""Children follow their parent exactly as nested meltygui windows do:
|
|
644
|
+
the request's window_pos is the parent-relative offset. Each tick the
|
|
645
|
+
child's target rect = the parent's screen rect (geometry feed) + the
|
|
646
|
+
offset, sent to the compositor when it differs from what was last
|
|
647
|
+
sent (Hyprland). A child the USER dragged — its feed rect moved while
|
|
648
|
+
the parent's did not, once our own last placement was acknowledged —
|
|
649
|
+
adopts the new offset, unless window_pos= pinned it. GNOME: rects are
|
|
650
|
+
read but never sent."""
|
|
651
|
+
if not parent.children:
|
|
652
|
+
return
|
|
653
|
+
import meltygui.core.windowing.window_api as glfw
|
|
654
|
+
import meltygui.core.windowing.geometry_feed as geometry_feed
|
|
655
|
+
import meltygui.core.windowing.titlebar as titlebar
|
|
656
|
+
import meltygui.core.windowing.wayland_move as wayland_move
|
|
657
|
+
prect = geometry_feed.surface_rect(parent.title)
|
|
658
|
+
if prect is None:
|
|
659
|
+
return
|
|
660
|
+
now = time.monotonic()
|
|
661
|
+
parent_moved = parent.seen_rect is not None and tuple(prect[:2]) != tuple(parent.seen_rect[:2])
|
|
662
|
+
parent.seen_rect = prect
|
|
663
|
+
for child in list(parent.children):
|
|
664
|
+
req = child.request
|
|
665
|
+
if req is None or child.window is None:
|
|
666
|
+
continue
|
|
667
|
+
# Retry a missing relationship, including surfaces created before
|
|
668
|
+
# the set_parent fix was hotswapped into the running app.
|
|
669
|
+
if (getattr(child, 'toplevel', None) and parent.toplevel
|
|
670
|
+
and not getattr(child, 'parent_linked', False)):
|
|
671
|
+
child.parent_linked = wayland_move.set_parent(child.toplevel, parent.toplevel)
|
|
672
|
+
crect = geometry_feed.surface_rect(child.title)
|
|
673
|
+
child_moved = (crect is not None and child.seen_rect is not None
|
|
674
|
+
and tuple(crect[:2]) != tuple(child.seen_rect[:2]))
|
|
675
|
+
child.seen_rect = crect
|
|
676
|
+
if child.await_ack and child.last_sent_rect is not None:
|
|
677
|
+
if (crect is not None and tuple(crect[:2]) == tuple(child.last_sent_rect[:2])) \
|
|
678
|
+
or now - child.sent_at > ACK_TIMEOUT_S:
|
|
679
|
+
child.await_ack = False
|
|
680
|
+
child_moved = False # that was our placement landing
|
|
681
|
+
if (child_moved and not parent_moved and not req.pinned and not child.await_ack
|
|
682
|
+
and child.last_sent_rect is not None):
|
|
683
|
+
req.window_pos = (crect[0] - prect[0], crect[1] - prect[1])
|
|
684
|
+
from meltygui.core.windowing.window_visibility import native_user_window_position
|
|
685
|
+
native_user_window_position(req.draw_state, req.window_pos)
|
|
686
|
+
_debug(f'child {child.title!r} dragged: offset now {req.window_pos}')
|
|
687
|
+
pos = req.window_pos
|
|
688
|
+
width, height = glfw.get_window_size(child.window)
|
|
689
|
+
if geometry_feed.hypr_honors_geometry():
|
|
690
|
+
# The box is the CONTENT: the surface less the shadow inset.
|
|
691
|
+
child.activate()
|
|
692
|
+
inset = int(titlebar.window_inset())
|
|
693
|
+
width, height = max(1, width - 2 * inset), max(1, height - 2 * inset)
|
|
694
|
+
target = (int(prect[0] + pos[0]), int(prect[1] + pos[1]), int(width), int(height))
|
|
695
|
+
# Following a parent's POSITION only. Reissuing GLFW's size
|
|
696
|
+
# through the compositor races pending edge-solver commits and
|
|
697
|
+
# turns an ordinary resize into a second, foreign resize.
|
|
698
|
+
if child.last_sent_rect is None or target[:2] != child.last_sent_rect[:2]:
|
|
699
|
+
if crect is not None and tuple(crect[:2]) == target[:2]:
|
|
700
|
+
child.last_sent_rect = target # already there
|
|
701
|
+
elif geometry_feed.place_window(child.title, target, resize=False):
|
|
702
|
+
_debug(f'place {child.title!r} at {target} (parent {prect[:2]} + {pos})')
|
|
703
|
+
child.last_sent_rect = target
|
|
704
|
+
child.await_ack = True
|
|
705
|
+
child.sent_at = now
|
|
706
|
+
|
|
707
|
+
|
|
708
|
+
# Window input -----------------------------------------------------------------------------
|
|
709
|
+
_MODS = {'ctrl': 2, 'control': 2, 'shift': 1, 'alt': 4, 'super': 8, 'meta': 8}
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
def pressed(combo):
|
|
713
|
+
"""Edge-triggered ``'ctrl+s'``-style check against this frame's key
|
|
714
|
+
events of the active window (GLFW press + repeat, so a held chord
|
|
715
|
+
repeats). Modifiers must match exactly."""
|
|
716
|
+
import meltygui.core.windowing.window_api as glfw
|
|
717
|
+
from meltygui.core.melty import Melty
|
|
718
|
+
parts = [p.strip().lower() for p in combo.split('+') if p.strip()]
|
|
719
|
+
mods = 0
|
|
720
|
+
key = None
|
|
721
|
+
for p in parts:
|
|
722
|
+
if p in _MODS:
|
|
723
|
+
mods |= _MODS[p]
|
|
724
|
+
else:
|
|
725
|
+
key = getattr(glfw, 'KEY_' + p.upper(), None)
|
|
726
|
+
if key is None:
|
|
727
|
+
raise ValueError(f'unknown key in {combo!r}: {p}')
|
|
728
|
+
mask = 0xF
|
|
729
|
+
return any(k == key and (m & mask) == mods for k, m in Melty.frame_key_events)
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def content_size():
|
|
733
|
+
"""The (width, height) a root-level view fills in the active window."""
|
|
734
|
+
from meltygui.core.melty import Melty
|
|
735
|
+
return Melty.root_fill
|