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.
Files changed (372) hide show
  1. meltygui/__init__.py +107 -0
  2. meltygui/accounts/__init__.py +0 -0
  3. meltygui/accounts/internet_accounts.py +1355 -0
  4. meltygui/chat/__init__.py +91 -0
  5. meltygui/chat/activity.py +75 -0
  6. meltygui/chat/backends.py +36 -0
  7. meltygui/chat/chat_interface.py +732 -0
  8. meltygui/chat/chat_proxy.py +352 -0
  9. meltygui/chat/codex_proxy.py +592 -0
  10. meltygui/chat/codex_settings.py +100 -0
  11. meltygui/chat/codex_transport.py +60 -0
  12. meltygui/chat/command_parser.py +204 -0
  13. meltygui/chat/images.py +227 -0
  14. meltygui/chat/messages.py +417 -0
  15. meltygui/chat/metadata.py +139 -0
  16. meltygui/chat/writer_locks.py +64 -0
  17. meltygui/code/__init__.py +0 -0
  18. meltygui/code/basic_converters.py +533 -0
  19. meltygui/code/chain_converters.py +2111 -0
  20. meltygui/code/code_checks.py +2209 -0
  21. meltygui/code/core_syntax.py +1430 -0
  22. meltygui/code/file_converters.py +1933 -0
  23. meltygui/code/fileref.py +702 -0
  24. meltygui/code/hotswap_guard.py +144 -0
  25. meltygui/code/libcst_conversion.py +9724 -0
  26. meltygui/code/live_instrument.py +392 -0
  27. meltygui/code/live_view.py +2490 -0
  28. meltygui/code/melty_scan.py +2684 -0
  29. meltygui/code/new_codecs.py +1255 -0
  30. meltygui/code/new_converters.py +3017 -0
  31. meltygui/code/project_code.py +278 -0
  32. meltygui/code/source_context.py +63 -0
  33. meltygui/code/symbol_roster.py +1588 -0
  34. meltygui/code/syntax_check.py +34 -0
  35. meltygui/code/syntax_check_worker.py +114 -0
  36. meltygui/completion/__init__.py +0 -0
  37. meltygui/completion/fim.py +1232 -0
  38. meltygui/completion/fim_context.py +481 -0
  39. meltygui/completion/providers/__init__.py +0 -0
  40. meltygui/completion/providers/anthropic_oauth.py +446 -0
  41. meltygui/completion/providers/anthropic_requests.py +69 -0
  42. meltygui/completion/providers/claude.py +203 -0
  43. meltygui/completion/providers/claude_usage.py +499 -0
  44. meltygui/completion/providers/codex_accounts.py +180 -0
  45. meltygui/completion/providers/copilot.py +617 -0
  46. meltygui/completion/providers/oauth_popup.py +220 -0
  47. meltygui/completion/providers/ollama.py +320 -0
  48. meltygui/completion/providers/profiles.py +23 -0
  49. meltygui/core/README.md +88 -0
  50. meltygui/core/__init__.py +1 -0
  51. meltygui/core/automation/__init__.py +1 -0
  52. meltygui/core/automation/action_core.py +153 -0
  53. meltygui/core/automation/collection_action.py +45 -0
  54. meltygui/core/automation/mcp_eval.py +167 -0
  55. meltygui/core/automation/mcp_hotswap.py +107 -0
  56. meltygui/core/automation/mcp_query.py +552 -0
  57. meltygui/core/automation/mcp_server.py +657 -0
  58. meltygui/core/automation/orchestration_core.py +2636 -0
  59. meltygui/core/automation/query_core.py +124 -0
  60. meltygui/core/automation/search_core.py +26 -0
  61. meltygui/core/automation/selector_core.py +340 -0
  62. meltygui/core/automation/value_core.py +1752 -0
  63. meltygui/core/cache/__init__.py +1 -0
  64. meltygui/core/cache/cache_diagnostics.py +0 -0
  65. meltygui/core/cache/invalidation_decoration.py +153 -0
  66. meltygui/core/cache/invalidation_tracker.py +43 -0
  67. meltygui/core/cache/tile_cache.py +5968 -0
  68. meltygui/core/conversion/__init__.py +1 -0
  69. meltygui/core/conversion/bubbling.py +599 -0
  70. meltygui/core/conversion/cache_tree.py +188 -0
  71. meltygui/core/conversion/chain.py +113 -0
  72. meltygui/core/conversion/converter_register.py +145 -0
  73. meltygui/core/conversion/data_decoration.py +67 -0
  74. meltygui/core/conversion/dict_conversion.py +1815 -0
  75. meltygui/core/conversion/dict_conversion_util.py +177 -0
  76. meltygui/core/conversion/dynamic_obj.py +89 -0
  77. meltygui/core/conversion/graph_compare.py +183 -0
  78. meltygui/core/conversion/load_save_v2.py +1032 -0
  79. meltygui/core/conversion/missing_saved_class.py +39 -0
  80. meltygui/core/conversion/path_finder.py +606 -0
  81. meltygui/core/conversion/render_host.py +1083 -0
  82. meltygui/core/core_render.py +6537 -0
  83. meltygui/core/definition_hotswap.py +231 -0
  84. meltygui/core/diagnostics/__init__.py +1 -0
  85. meltygui/core/diagnostics/attribute_churn.py +38 -0
  86. meltygui/core/diagnostics/fps_counter.py +39 -0
  87. meltygui/core/diagnostics/gpu_frame_timer.py +103 -0
  88. meltygui/core/diagnostics/inspection_core.py +169 -0
  89. meltygui/core/diagnostics/monitor_core.py +105 -0
  90. meltygui/core/diagnostics/notifications.py +706 -0
  91. meltygui/core/diagnostics/perf_trace.py +281 -0
  92. meltygui/core/diagnostics/profile_decoration.py +88 -0
  93. meltygui/core/diagnostics/resize_trace.py +62 -0
  94. meltygui/core/diagnostics/screenshot_core.py +247 -0
  95. meltygui/core/diagnostics/session_status.py +98 -0
  96. meltygui/core/diagnostics/trace_core.py +445 -0
  97. meltygui/core/files/__init__.py +1 -0
  98. meltygui/core/files/file_core.py +208 -0
  99. meltygui/core/files/file_explorer_core.py +104 -0
  100. meltygui/core/files/file_tree_core.py +198 -0
  101. meltygui/core/files/file_watch_core.py +43 -0
  102. meltygui/core/files/import_graph_core.py +43 -0
  103. meltygui/core/files/metadata_core.py +51 -0
  104. meltygui/core/graphics/__init__.py +1 -0
  105. meltygui/core/graphics/cuda_context_core.py +166 -0
  106. meltygui/core/graphics/cuda_interop_core.py +136 -0
  107. meltygui/core/graphics/cuda_kernel_core.py +91 -0
  108. meltygui/core/graphics/framebuffer_recorder.py +337 -0
  109. meltygui/core/graphics/gl_state.py +658 -0
  110. meltygui/core/graphics/lut_core.py +52 -0
  111. meltygui/core/graphics/overlay_renderer.py +984 -0
  112. meltygui/core/graphics/scene_target.py +180 -0
  113. meltygui/core/graphics/screenshot.py +439 -0
  114. meltygui/core/graphics/shader_func.py +478 -0
  115. meltygui/core/graphics/tensor_core.py +45 -0
  116. meltygui/core/graphics/text_texture.py +329 -0
  117. meltygui/core/graphics/wayland_color.py +635 -0
  118. meltygui/core/input/__init__.py +1 -0
  119. meltygui/core/input/collision.py +165 -0
  120. meltygui/core/input/drag_drop_core.py +1525 -0
  121. meltygui/core/input/hypr_left_drag.py +323 -0
  122. meltygui/core/input/input_core.py +245 -0
  123. meltygui/core/input/input_handler.py +1101 -0
  124. meltygui/core/input/mouse_cursor.py +355 -0
  125. meltygui/core/input/pynput_backend.py +1054 -0
  126. meltygui/core/input/space_mouse.py +338 -0
  127. meltygui/core/input/touchpad_backend.py +393 -0
  128. meltygui/core/input/view_selection.py +177 -0
  129. meltygui/core/layout/__init__.py +1 -0
  130. meltygui/core/layout/column_core.py +2153 -0
  131. meltygui/core/layout/cursor_core.py +161 -0
  132. meltygui/core/layout/dropdown_core.py +278 -0
  133. meltygui/core/layout/edge_constraints.py +155 -0
  134. meltygui/core/layout/grid_core.py +117 -0
  135. meltygui/core/layout/header_core.py +23 -0
  136. meltygui/core/layout/header_runtime.py +67 -0
  137. meltygui/core/layout/layout_core.py +87 -0
  138. meltygui/core/layout/tile_manager_core.py +591 -0
  139. meltygui/core/melty.py +6726 -0
  140. meltygui/core/module_map.json +896 -0
  141. meltygui/core/module_names.py +19 -0
  142. meltygui/core/rendering/__init__.py +1 -0
  143. meltygui/core/rendering/core_decoration.py +431 -0
  144. meltygui/core/rendering/core_render_helpers.py +328 -0
  145. meltygui/core/rendering/func_metadata.py +398 -0
  146. meltygui/core/rendering/mode.py +818 -0
  147. meltygui/core/rendering/mode_defaults.py +41 -0
  148. meltygui/core/rendering/modes.py +136 -0
  149. meltygui/core/rendering/parameter_core.py +1665 -0
  150. meltygui/core/rendering/render_dispatch.py +1891 -0
  151. meltygui/core/rendering/render_funcs.py +273 -0
  152. meltygui/core/rendering/shaped.py +312 -0
  153. meltygui/core/rendering/window_decoration.py +25 -0
  154. meltygui/core/runtime/__init__.py +1 -0
  155. meltygui/core/runtime/app.py +735 -0
  156. meltygui/core/runtime/app_session.py +140 -0
  157. meltygui/core/runtime/background.py +564 -0
  158. meltygui/core/runtime/extensions.py +53 -0
  159. meltygui/core/runtime/gc_manager.py +1163 -0
  160. meltygui/core/runtime/lifecycle.py +21 -0
  161. meltygui/core/runtime/paths.py +27 -0
  162. meltygui/core/runtime/settings.py +14 -0
  163. meltygui/core/runtime/singleton.py +16 -0
  164. meltygui/core/runtime/thread_safe_bool.py +24 -0
  165. meltygui/core/runtime/thread_signal.py +30 -0
  166. meltygui/core/runtime/toggles.py +3115 -0
  167. meltygui/core/services/__init__.py +1 -0
  168. meltygui/core/services/account_core.py +10 -0
  169. meltygui/core/services/chat_core.py +19 -0
  170. meltygui/core/services/claude_terminal_core.py +346 -0
  171. meltygui/core/services/terminal_core.py +458 -0
  172. meltygui/core/services/terminal_runtime.py +78 -0
  173. meltygui/core/styling/__init__.py +1 -0
  174. meltygui/core/styling/color_core.py +46 -0
  175. meltygui/core/styling/fonts.py +639 -0
  176. meltygui/core/styling/global_style.py +338 -0
  177. meltygui/core/styling/style.py +198 -0
  178. meltygui/core/styling/style_core.py +522 -0
  179. meltygui/core/styling/warm_start.py +149 -0
  180. meltygui/core/windowing/__init__.py +1 -0
  181. meltygui/core/windowing/backends/PYIMGUI_LICENSE +28 -0
  182. meltygui/core/windowing/backends/__init__.py +1 -0
  183. meltygui/core/windowing/backends/imgui_renderer.py +138 -0
  184. meltygui/core/windowing/backends/native_wayland.py +850 -0
  185. meltygui/core/windowing/backends/protocols/xdg-decoration-unstable-v1.xml +156 -0
  186. meltygui/core/windowing/backends/protocols/xdg-shell.xml +1420 -0
  187. meltygui/core/windowing/backends/wayland_protocol.py +101 -0
  188. meltygui/core/windowing/dock_core.py +182 -0
  189. meltygui/core/windowing/frame_geometry.py +42 -0
  190. meltygui/core/windowing/geometry_feed.py +864 -0
  191. meltygui/core/windowing/glfw_utils.py +1343 -0
  192. meltygui/core/windowing/os_frame.py +1552 -0
  193. meltygui/core/windowing/surface.py +643 -0
  194. meltygui/core/windowing/titlebar.py +1560 -0
  195. meltygui/core/windowing/titlebar_buttons.py +281 -0
  196. meltygui/core/windowing/wayland_move.py +932 -0
  197. meltygui/core/windowing/window_api.py +62 -0
  198. meltygui/core/windowing/window_constants.py +339 -0
  199. meltygui/core/windowing/window_visibility.py +162 -0
  200. meltygui/debug/__init__.py +0 -0
  201. meltygui/debug/app_view_utils.py +9 -0
  202. meltygui/editor/__init__.py +0 -0
  203. meltygui/editor/bash_syntax.py +30 -0
  204. meltygui/editor/code_line_fast.py +152 -0
  205. meltygui/editor/diff.py +139 -0
  206. meltygui/editor/external_changes.py +159 -0
  207. meltygui/editor/file_header.py +41 -0
  208. meltygui/editor/live_usage.py +169 -0
  209. meltygui/editor/live_view_views.py +1803 -0
  210. meltygui/editor/pending_save.py +1351 -0
  211. meltygui/editor/roster_tints.py +547 -0
  212. meltygui/editor/source_preview.py +16 -0
  213. meltygui/editor/source_tools.py +10 -0
  214. meltygui/editor/source_ui.py +70 -0
  215. meltygui/editor/spell_check.py +77 -0
  216. meltygui/editor/text_editor.py +9076 -0
  217. meltygui/editor/usage_picker.py +538 -0
  218. meltygui/events/__init__.py +0 -0
  219. meltygui/events/example.py +118 -0
  220. meltygui/examples/__init__.py +0 -0
  221. meltygui/examples/columns_demo.py +33 -0
  222. meltygui/examples/columns_window_demo.py +60 -0
  223. meltygui/examples/context_menu_demo.py +24 -0
  224. meltygui/examples/context_menu_window_demo.py +49 -0
  225. meltygui/examples/gui_playground.py +127 -0
  226. meltygui/examples/live_view_playground.py +256 -0
  227. meltygui/examples/lora.py +43 -0
  228. meltygui/examples/lora_data.py +59 -0
  229. meltygui/examples/lora_policies.py +67 -0
  230. meltygui/examples/mode_demo.py +49 -0
  231. meltygui/examples/modifies_demo.py +119 -0
  232. meltygui/examples/scalar_policies.py +60 -0
  233. meltygui/examples/style_layouts.py +149 -0
  234. meltygui/examples/tile_manager_demo.py +74 -0
  235. meltygui/examples/tint_demo.py +160 -0
  236. meltygui/examples/tint_functions.py +56 -0
  237. meltygui/examples/trace_demo.py +88 -0
  238. meltygui/examples/two_windows.py +36 -0
  239. meltygui/files/__init__.py +0 -0
  240. meltygui/files/fast_file_explorer.py +439 -0
  241. meltygui/gnome_extension/lsd-window-geometry@latent-descent/extension.js +237 -0
  242. meltygui/gnome_extension/lsd-window-geometry@latent-descent/lsd-window-geometry@latent-descent.iml +9 -0
  243. meltygui/gnome_extension/lsd-window-geometry@latent-descent/metadata.json +7 -0
  244. meltygui/graphics/__init__.py +6 -0
  245. meltygui/graphics/base.py +85 -0
  246. meltygui/graphics/examples.py +507 -0
  247. meltygui/graphics/executor.py +520 -0
  248. meltygui/graphics/filter.py +667 -0
  249. meltygui/graphics/filter.pyi +856 -0
  250. meltygui/graphics/generate_stubs.py +22 -0
  251. meltygui/graphics/registry.py +203 -0
  252. meltygui/graphics/shader_compiler.py +155 -0
  253. meltygui/graphics/shaders.py +1302 -0
  254. meltygui/graphics/stub_generator.py +250 -0
  255. meltygui/graphics/texture_manager.py +170 -0
  256. meltygui/graphics/texture_min_max.py +295 -0
  257. meltygui/hdr_color.py +757 -0
  258. meltygui/image_load.py +308 -0
  259. meltygui/model/__init__.py +1 -0
  260. meltygui/model/account_model.py +142 -0
  261. meltygui/model/camera_model.py +159 -0
  262. meltygui/model/chat_model.py +86 -0
  263. meltygui/model/code_model.py +62 -0
  264. meltygui/model/code_proxy_model.py +876 -0
  265. meltygui/model/collection_model.py +41 -0
  266. meltygui/model/color_model.py +127 -0
  267. meltygui/model/cuda_tensor_model.py +36 -0
  268. meltygui/model/cuda_texture_model.py +149 -0
  269. meltygui/model/dropdown_model.py +137 -0
  270. meltygui/model/file_metadata_model.py +73 -0
  271. meltygui/model/file_model.py +289 -0
  272. meltygui/model/format_model.py +390 -0
  273. meltygui/model/graph_model.py +98 -0
  274. meltygui/model/icon_model.py +1024 -0
  275. meltygui/model/import_graph_model.py +468 -0
  276. meltygui/model/layout_model.py +15 -0
  277. meltygui/model/lut_model.py +266 -0
  278. meltygui/model/search_model.py +173 -0
  279. meltygui/model/tensor_model.py +402 -0
  280. meltygui/model/terminal_model.py +329 -0
  281. meltygui/model/texture_model.py +146 -0
  282. meltygui/model/tile_model.py +24 -0
  283. meltygui/model/trace_model.py +198 -0
  284. meltygui/model/trace_report_model.py +146 -0
  285. meltygui/models/__init__.py +0 -0
  286. meltygui/models/file_meta.py +613 -0
  287. meltygui/models/function_console.py +184 -0
  288. meltygui/models/orchestration.py +48 -0
  289. meltygui/pbr.py +1576 -0
  290. meltygui/png_unfilter.c +49 -0
  291. meltygui/resources/JetBrainsMono-Regular.ttf +0 -0
  292. meltygui/resources/THIRD_PARTY_NOTICES.md +21 -0
  293. meltygui/resources/dejavu/DejaVuSans-Bold.ttf +0 -0
  294. meltygui/resources/dejavu/DejaVuSans-ExtraLight.ttf +0 -0
  295. meltygui/resources/dejavu/DejaVuSans.ttf +0 -0
  296. meltygui/resources/dejavu/LICENSE.txt +78 -0
  297. meltygui/resources/fontawesome-LICENSE.txt +121 -0
  298. meltygui/resources/fontawesome-webfont.ttf +0 -0
  299. meltygui/resources/hdri/studio_small_09_1k.hdr +0 -0
  300. meltygui/resources/jetbrains-weights/JetBrainsMono-Bold.ttf +0 -0
  301. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraBold.ttf +0 -0
  302. meltygui/resources/jetbrains-weights/JetBrainsMono-ExtraLight.ttf +0 -0
  303. meltygui/resources/jetbrains-weights/JetBrainsMono-Light.ttf +0 -0
  304. meltygui/resources/jetbrains-weights/JetBrainsMono-Medium.ttf +0 -0
  305. meltygui/resources/jetbrains-weights/JetBrainsMono-SemiBold.ttf +0 -0
  306. meltygui/resources/jetbrains-weights/JetBrainsMono-Thin.ttf +0 -0
  307. meltygui/resources/jetbrains-weights/OFL.txt +93 -0
  308. meltygui/resources/jetbrains-weights/README.md +2 -0
  309. meltygui/state/__init__.py +0 -0
  310. meltygui/state/account_state.py +22 -0
  311. meltygui/state/animation_state.py +97 -0
  312. meltygui/state/annotation_state.py +22 -0
  313. meltygui/state/chat_state.py +36 -0
  314. meltygui/state/code_state.py +10 -0
  315. meltygui/state/core_enums.py +59 -0
  316. meltygui/state/core_markers.py +115 -0
  317. meltygui/state/core_undo.py +1075 -0
  318. meltygui/state/file_state.py +75 -0
  319. meltygui/state/graph_state.py +26 -0
  320. meltygui/state/inspection_state.py +47 -0
  321. meltygui/state/menu_state.py +10 -0
  322. meltygui/state/model_enums.py +11 -0
  323. meltygui/state/new_core_model.py +2394 -0
  324. meltygui/state/orchestration_state.py +14 -0
  325. meltygui/state/query_state.py +24 -0
  326. meltygui/state/tensor_state.py +10 -0
  327. meltygui/state/terminal_state.py +23 -0
  328. meltygui/state/trace_state.py +32 -0
  329. meltygui/state/voxel_state.py +12 -0
  330. meltygui/text_index.py +816 -0
  331. meltygui/utils/__init__.py +0 -0
  332. meltygui/utils/jump_to_code.py +344 -0
  333. meltygui/utils/pkl_inspect.py +90 -0
  334. meltygui/utils/render_utils.py +1419 -0
  335. meltygui/view/__init__.py +1 -0
  336. meltygui/view/account_view.py +524 -0
  337. meltygui/view/action_view.py +73 -0
  338. meltygui/view/chat_decoration_view.py +112 -0
  339. meltygui/view/chat_view.py +1703 -0
  340. meltygui/view/code_view.py +2677 -0
  341. meltygui/view/collection_view.py +1185 -0
  342. meltygui/view/color_view.py +824 -0
  343. meltygui/view/control_view.py +559 -0
  344. meltygui/view/decoration_view.py +439 -0
  345. meltygui/view/diagnostic_view.py +177 -0
  346. meltygui/view/dropdown_view.py +1082 -0
  347. meltygui/view/file_view.py +1599 -0
  348. meltygui/view/graph_cuda_view.py +105 -0
  349. meltygui/view/graph_view.py +882 -0
  350. meltygui/view/header_view.py +848 -0
  351. meltygui/view/input_view.py +238 -0
  352. meltygui/view/inspection_view.py +1783 -0
  353. meltygui/view/layout_view.py +596 -0
  354. meltygui/view/lut_view.py +45 -0
  355. meltygui/view/menu_view.py +212 -0
  356. meltygui/view/orchestration_view.py +658 -0
  357. meltygui/view/query_view.py +118 -0
  358. meltygui/view/search_view.py +342 -0
  359. meltygui/view/tab_view.py +258 -0
  360. meltygui/view/tensor_view.py +430 -0
  361. meltygui/view/terminal_view.py +355 -0
  362. meltygui/view/text_view.py +8031 -0
  363. meltygui/view/texture_view.py +480 -0
  364. meltygui/view/tile_view.py +38 -0
  365. meltygui/view/trace_view.py +923 -0
  366. meltygui/view/voxel_cuda_view.py +824 -0
  367. meltygui/view/voxel_view.py +1860 -0
  368. meltygui/view/window_view.py +111 -0
  369. meltygui-0.1.0.dist-info/METADATA +143 -0
  370. meltygui-0.1.0.dist-info/RECORD +372 -0
  371. meltygui-0.1.0.dist-info/WHEEL +4 -0
  372. meltygui-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,538 @@
1
+ """The Ctrl+B usage-jump picker — GlobalSearch's Code tab, as a popover.
2
+
3
+ Rows are a TREE, exactly the Code tab's: the file (depth 0, icon + name,
4
+ FileMeta tint), then every enclosing class / def of a hit as its own row
5
+ (the def line, painted like the editor paints it), then the usage line
6
+ itself. EVERY row is selectable — a scope row jumps to that definition, the
7
+ file row opens the file — and the keyboard cursor lands on the BEST match
8
+ (the first target the resolver returned) rather than row 0, so Enter still
9
+ does what the flat list did.
10
+
11
+ Rows are built ONCE when the picker opens (`build_usage_rows`: roster
12
+ tables for the scope chains, `CodeLineTints` for the washes) and painted
13
+ every frame with `draw_code_line_fast` — a few draw-list calls per row, no
14
+ per-row editor body. The window is `draw_usage_picker`, a latched popover
15
+ with the same shell as `draw_dd_menu` (called every frame with `closed=`
16
+ toggled by draw_text, which owns the keys and the focus gate).
17
+ """
18
+ import os
19
+ import re
20
+ from pathlib import Path
21
+
22
+ import meltygui_imgui as imgui
23
+ from meltygui.hdr_color import pack_color
24
+
25
+ from meltygui.core.melty import Melty
26
+ from meltygui.core.runtime.toggles import Tint
27
+ from meltygui.core.runtime.toggles import Toggles
28
+ from meltygui.core.styling.fonts import Font
29
+ from meltygui.code.libcst_conversion import UsageRef
30
+ from meltygui.core.cache.tile_cache import add_shadow
31
+ from meltygui.editor.code_line_fast import CodeLineTints
32
+ from meltygui.view.code_view import draw_code_line_fast
33
+ from meltygui.editor.code_line_fast import pop_code_font
34
+ from meltygui.editor.code_line_fast import push_code_font
35
+ from meltygui.editor.code_line_fast import shift_spans
36
+ from meltygui.core.core_render import render_func
37
+
38
+ # Row pitch shared with the scroll-into-view helper — the Code tab's 24 + 2.
39
+ ROW_H = Toggles.UsagePicker.row_height
40
+ ROW_GAP = Toggles.UsagePicker.row_gap
41
+ ROW_PITCH = ROW_H + ROW_GAP
42
+
43
+
44
+ class UsageRow:
45
+ """One picker row. `kind` is "file" / "scope" / "site"; `ref` is the
46
+ UsageRef a pick jumps to (None for the file row — it opens the file),
47
+ `token` the symbol spelling the caret lands on; `text` is the display
48
+ line (stripped), `block_tint` / `spans` its washes in display columns;
49
+ `tint` the ROW's own tint (file: FileMeta, scope: the definition's)."""
50
+ __slots__ = ("kind", "depth", "path", "line", "text", "tint", "ref", "token",
51
+ "block_tint", "spans", "label", "emphasis", "indent")
52
+
53
+ def __init__(self, kind, depth, path, line, text, tint=None, ref=None,
54
+ token=None, block_tint=None, spans=(), label="", emphasis=None):
55
+ self.kind = kind
56
+ self.indent = 0.0
57
+ self.depth = depth
58
+ self.path = path
59
+ self.line = line
60
+ self.text = text
61
+ self.tint = tint
62
+ self.ref = ref
63
+ self.token = token
64
+ self.block_tint = block_tint
65
+ self.spans = spans
66
+ self.label = label
67
+ # Display columns of the searched symbol on a site row - painted at
68
+ # full brightness while the rest of the line is dimmed; () dims the
69
+ # whole line (a def scope row), None dims nothing.
70
+ self.emphasis = emphasis
71
+
72
+
73
+ from meltygui.model.code_model import UsagePickerModel
74
+
75
+
76
+ # ── row builder ─────────────────────────────────────────────────────────────
77
+
78
+ class _Node:
79
+ __slots__ = ("entry", "children", "sites")
80
+
81
+ def __init__(self, entry):
82
+ self.entry = entry
83
+ self.children = {} # qualname -> _Node, first-appearance order
84
+ self.sites = [] # UsageRefs, first-appearance order
85
+
86
+
87
+ def _display_name(path):
88
+ """The file's name relative to the project's src tree ("core_views/
89
+ text_editor.py"), the bare name when the path isn't under it."""
90
+ p = str(path)
91
+ marker = f"{os.sep}src{os.sep}"
92
+ i = p.rfind(marker)
93
+ return p[i + len(marker):] if i != -1 else os.path.basename(p)
94
+
95
+
96
+ def _symbol_columns(disp, token, column=None):
97
+ """Column spans of `token` (an identifier) in the display line `disp`:
98
+ the occurrence at `column` when one starts there, else every whole-word
99
+ occurrence. Empty when the token isn't on the line."""
100
+ if not token:
101
+ return []
102
+ if column is not None and 0 <= column < len(disp) and disp.startswith(token, column):
103
+ before = disp[column - 1] if column > 0 else ""
104
+ after = disp[column + len(token)] if column + len(token) < len(disp) else ""
105
+ if not (before.isalnum() or before == "_") and not (after.isalnum() or after == "_"):
106
+ return [(column, column + len(token))]
107
+ pat = re.compile(r"(?<![\w])" + re.escape(token) + r"(?![\w])")
108
+ return [(m.start(), m.end()) for m in pat.finditer(disp)]
109
+
110
+
111
+ def build_usage_rows(targets, names, tints=None, *, texts=None):
112
+ """Rows for `targets` (UsageRefs, best first) grouped file → scope chain
113
+ → usage line, in first-appearance order. `names` maps ref → the symbol
114
+ spelling for the caret. Returns (rows, best_index) — best_index is the
115
+ row of targets[0]. Optional `texts` supplies exact buffer snapshots,
116
+ keyed by path, so other code lists can share this tree and painter."""
117
+ import meltygui.code.symbol_roster as roster
118
+ from meltygui.editor.pending_save import PendingSave
119
+ from meltygui.editor.text_editor import _uj_file_tint
120
+ tints = tints if tints is not None else CodeLineTints()
121
+ files = {} # normalized path -> (path_str, text, lines, table, root _Node)
122
+ for ref in targets:
123
+ p = getattr(ref, "path", None)
124
+ if p is None:
125
+ continue
126
+ key = roster._norm(str(p))
127
+ ent = files.get(key)
128
+ if ent is None:
129
+ try:
130
+ text = (texts[str(p)] if texts is not None
131
+ else PendingSave.current_file_text(Path(str(p))))
132
+ except Exception:
133
+ text = None
134
+ text = text if isinstance(text, str) else ""
135
+ try:
136
+ table = (roster.detached_table(key, text) if texts is not None
137
+ else roster.table_for(key))
138
+ except Exception:
139
+ table = None
140
+ ent = files[key] = (str(p), text, text.split("\n"), table, _Node(None))
141
+ _path, _text, _lines, table, root = ent
142
+ node = root
143
+ if table is not None:
144
+ chain = []
145
+ scope = table.scope_at(ref.line)
146
+ while scope is not None:
147
+ chain.append(scope)
148
+ scope = (table.by_qualname.get(scope.parent)
149
+ if scope.parent else None)
150
+ for entry in reversed(chain):
151
+ child = node.children.get(entry.qualname)
152
+ if child is None:
153
+ child = node.children[entry.qualname] = _Node(entry)
154
+ node = child
155
+ node.sites.append(ref)
156
+
157
+ rows, best = [], 0
158
+ first = targets[0] if targets else None
159
+
160
+ def line_row(kind, depth, path, text, lines, line, ref, token, tint=None):
161
+ raw = lines[line - 1] if 0 < line <= len(lines) else ""
162
+ disp = raw.strip()
163
+ indent = len(raw) - len(raw.lstrip())
164
+ block, spans = tints.tints(path, text, line)
165
+ spans = shift_spans(spans, indent)
166
+ # A site row lights the caret symbol; a scope row is context.
167
+ if kind == "site":
168
+ col = getattr(ref, "column", None)
169
+ emphasis = _symbol_columns(disp, token,
170
+ None if col is None else col - indent)
171
+ else:
172
+ emphasis = ()
173
+ return UsageRow(kind, depth, path, line, disp, tint=tint, ref=ref,
174
+ token=token, block_tint=block, spans=spans, emphasis=emphasis)
175
+
176
+ def emit(node, depth, path, text, lines):
177
+ nonlocal best
178
+ entry = node.entry
179
+ site_lines = {getattr(r, "line", None) for r in node.sites}
180
+ if entry is not None:
181
+ # A target ON the def line IS this scope: one row, the site's ref.
182
+ own = next((r for r in node.sites if r.line == entry.line), None)
183
+ ref = own or UsageRef(Path(path), entry.line, entry.col,
184
+ scope=entry.parent or "<module>")
185
+ token = names.get(own) if own is not None else entry.name
186
+ if own is not None and own is first:
187
+ best = len(rows)
188
+ rows.append(line_row("site" if own is not None else "scope", depth,
189
+ path, text, lines, entry.line, ref,
190
+ token or entry.name, tint=entry.tint))
191
+ depth += 1
192
+ for ref in node.sites:
193
+ if entry is not None and ref.line == entry.line:
194
+ continue
195
+ if ref is first:
196
+ best = len(rows)
197
+ rows.append(line_row("site", depth, path, text, lines, ref.line,
198
+ ref, names.get(ref)))
199
+ for child in node.children.values():
200
+ emit(child, depth, path, text, lines)
201
+
202
+ for _key, (path, text, lines, _table, root) in files.items():
203
+ rows.append(UsageRow("file", 0, path, 1, _display_name(path),
204
+ tint=_uj_file_tint(path), label=path))
205
+ emit(root, 1, path, text, lines)
206
+ return rows, best
207
+
208
+
209
+ # ── picker window ───────────────────────────────────────────────────────────────
210
+
211
+ def _same_tint(a, b):
212
+ """True when two rgb tints are the same colour (None never matches)."""
213
+ if a is None or b is None:
214
+ return False
215
+ return all(abs(float(x) - float(y)) < 0.005 for x, y in zip(a[:3], b[:3]))
216
+
217
+
218
+ # Popover size: fitted to the rows ONCE, on open (width = the remembered
219
+ # drag width, TextEditorState.usage_picker_width, when there is one); after
220
+ # that the handle resizes it freely, constrained only by the content height
221
+ # (every row + the "+ N more" row) measured in draw_text's call.
222
+ PICKER_MIN_W = Toggles.UsagePicker.min_width
223
+ PICKER_MIN_H = Toggles.UsagePicker.min_height
224
+
225
+
226
+ def _fresh_rect(menu_ds, model):
227
+ """The wrapper's content rect, only when the body measured it THIS frame
228
+ — a latched popover keeps the rect of its last open (a longer list
229
+ sized a 4-row picker to 500 px, 09-04). A fresh rect also teaches the
230
+ model its chrome height."""
231
+ if menu_ds is None or model is None or model.measured is None:
232
+ return None
233
+ frame, rows_h = model.measured
234
+ if frame != Melty.frame_count:
235
+ return None
236
+ rect = getattr(menu_ds, "_content_rect", None)
237
+ if not rect or rect[0] <= 0 or rect[1] <= 0:
238
+ return None
239
+ if rect[1] >= rows_h:
240
+ model.chrome_h = rect[1] - rows_h
241
+ return rect
242
+
243
+
244
+ def picker_content_height(menu_ds, model):
245
+ """The popover's whole content: every row (the "+ N more" row included)
246
+ plus the window chrome — the ceiling of its height."""
247
+ rect = _fresh_rect(menu_ds, model)
248
+ if rect is not None:
249
+ return max(PICKER_MIN_H, rect[1])
250
+ n = len(model.rows) if model is not None else 0
251
+ chrome = model.chrome_h if model is not None else 8.0
252
+ return max(PICKER_MIN_H, n * ROW_PITCH + chrome)
253
+
254
+
255
+ def picker_fit(menu_ds, model):
256
+ """The popover size that fits its rows, display-clamped: the fresh
257
+ content rect's width (else the minimum) and the content height."""
258
+ from meltygui.core.cache.tile_cache import snap_int
259
+ display_w, display_h = imgui.get_io().display_size
260
+ rect = _fresh_rect(menu_ds, model)
261
+ fit_w = snap_int(max(min(rect[0] if rect else PICKER_MIN_W, display_w), PICKER_MIN_W))
262
+ fit_h = snap_int(max(min(picker_content_height(menu_ds, model), display_h),
263
+ PICKER_MIN_H))
264
+ return (fit_w, fit_h)
265
+
266
+
267
+ def scroll_row_into_view(menu_ds, row_index):
268
+ """Minimal scroll of the picker window so `row_index` is fully visible
269
+ (the dd-menu helper at this module's row pitch)."""
270
+ from meltygui.core.layout.dropdown_core import _dd_scroll_cursor_into_view
271
+ _dd_scroll_cursor_into_view(menu_ds, row_index, pitch=ROW_PITCH)
272
+
273
+
274
+
275
+
276
+
277
+ def paint_usage_rows(input_value, draw_state, *, width=None,
278
+ row_height=ROW_H, row_gap=ROW_GAP, tree_indent=16.0,
279
+ code_font=Font.FONTAWESOME_MONO_19, collapsible=False,
280
+ group_tint=None, change_kinds=None, event_prefix="uj",
281
+ line_numbers_left=False):
282
+ """Paint into the caller's draw list and tile, like the usage search tree.
283
+
284
+ Background groups and code glyphs must share a capture owner. An
285
+ embedded list uses its column's draw_state for events and clipping;
286
+ the standalone picker supplies its own draw_state above.
287
+ """
288
+ from meltygui.view.dropdown_view import _dd_row_width
289
+ # [tint=(0.9, 0.6, 0.2)] layout knobs — the Code tab's numbers
290
+ ICON_COL = 22.0
291
+ ICON_X = 4.0
292
+ TREE_INDENT = tree_indent
293
+ GROUP_INSET = ICON_COL - 4.0
294
+ file_icon = f""
295
+ collapsed_icon, expanded_icon = "", ""
296
+ row_pitch = row_height + row_gap
297
+ icon_alpha = 0.55
298
+ suffix_alpha = 0.55
299
+ row_bg_value, row_bg_hot = 0.045, 0.10
300
+ group_bg_value, group_bg_step = 0.028, 0.012
301
+ file_text_value, file_text_hot = 0.9, 1.5
302
+ more_tint = (0.55, 0.72, 0.85) # the Code tab's default blue
303
+ more_text_value, more_text_hot = 0.5, 1.0
304
+ text_saturation = 0.8
305
+ sel_color = pack_color(0.88, 0.93, 1.0, 1.0)
306
+ sel_thickness = 1.5
307
+ hot_bg_untinted = pack_color(1.0, 1.0, 1.0, 0.07)
308
+ suffix_color = pack_color(0.52, 0.55, 0.6, suffix_alpha)
309
+
310
+ model = input_value
311
+ rows = model.rows if model is not None else []
312
+ sm = Melty.style_manager
313
+
314
+ def _mix(tint, value, factor=0.8, sat=1.0, alpha=1.0):
315
+ c = tint if (isinstance(tint, tuple) and len(tint) >= 3) else (0.5, 0.5, 0.5)
316
+ col = sm.make_color_rgb(c[0], c[1], c[2], value=value, factor=factor,
317
+ saturation_scale=sat)
318
+ return pack_color(col[0], col[1], col[2], alpha)
319
+
320
+ draw_list = imgui.get_window_draw_list()
321
+ origin = imgui.get_cursor_screen_pos()
322
+ x0, y0 = origin[0], origin[1]
323
+ width = _dd_row_width(draw_state) if width is None else width
324
+ n = len(rows)
325
+ total_h = n * row_pitch
326
+ if model is not None:
327
+ model.measured = (Melty.frame_count, total_h if n else row_height)
328
+ if n == 0:
329
+ imgui.dummy(width, row_height)
330
+ return False, model
331
+
332
+ # Visible band of the (scrolling) window - rows outside it keep their
333
+ # geometry and skip their paint.
334
+ band_top = draw_state._abs_top()
335
+ band_bot = band_top + (draw_state.height or 0)
336
+
337
+ # ── hover vs keyboard highlight ──
338
+ mouse = imgui.get_mouse_pos()
339
+ over = (getattr(draw_state, "_bounding_hovered", False)
340
+ and x0 <= mouse[0] < x0 + width and band_top <= mouse[1] < band_bot)
341
+ last = model._last_mouse
342
+ moved = last is not None and (abs(mouse[0] - last[0]) > 0.5
343
+ or abs(mouse[1] - last[1]) > 0.5)
344
+ model._last_mouse = (mouse[0], mouse[1])
345
+ hover = None
346
+ if over:
347
+ hi = int((mouse[1] - y0) // row_pitch)
348
+ if 0 <= hi < n and (mouse[1] - y0) - hi * row_pitch < row_height:
349
+ hover = hi
350
+ if line_numbers_left:
351
+ # Embedded lists share a column: its bbox may still be hovered
352
+ # while the pointer has moved into a new file's usage list.
353
+ if hover is None and not model.kbd_mode:
354
+ model.index = -1
355
+ if moved:
356
+ model.kbd_mode = False
357
+ elif not over:
358
+ model.kbd_mode = True
359
+ elif moved and hover is not None:
360
+ model.kbd_mode = False
361
+ model.hover = hover if not model.kbd_mode else None
362
+ if not model.kbd_mode and hover is not None:
363
+ model.index = hover # Enter switches from the hovered row
364
+
365
+ pushed, char_w, line_h = push_code_font(code_font)
366
+ try:
367
+ gutter_width = (max(len(str(row.line)) for row in rows) * char_w + 8.0
368
+ if line_numbers_left else 0.0)
369
+ content_left = x0 + gutter_width
370
+ row_y = [y0 + i * row_pitch for i in range(n)]
371
+ depths = [r.depth for r in rows]
372
+ row_tints = []
373
+ tint_stack = []
374
+ for row in rows:
375
+ while tint_stack and tint_stack[-1][0] >= row.depth:
376
+ tint_stack.pop()
377
+ parent_tint = tint_stack[-1][1] if tint_stack else group_tint
378
+ scope = collapsible and row.line in model.scope_keys
379
+ tint = row.tint or ((row.block_tint or parent_tint) if scope else None)
380
+ row_tints.append(tint)
381
+ tint_stack.append((row.depth, tint or parent_tint))
382
+ if group_tint is not None:
383
+ draw_list.add_rect_filled(x0, y0, x0 + width, y0 + total_h,
384
+ _mix(group_tint, Toggles.CodeEditor.compare_file_bg_value), rounding=4.0)
385
+
386
+ # ── group background: a tinted scope's rows float in its wash ──
387
+ for i, row in enumerate(rows):
388
+ d = depths[i]
389
+ j = i + 1
390
+ while j < n and depths[j] > d:
391
+ j += 1
392
+ if j == i + 1 or row_tints[i] is None:
393
+ continue
394
+ gy0, gy1 = row_y[i], row_y[j - 1] + row_height
395
+ if gy1 < band_top or gy0 > band_bot:
396
+ continue
397
+ gx0 = content_left + d * TREE_INDENT + GROUP_INSET
398
+ add_shadow((gx0, gy0, x0 + width - gx0, gy1 - gy0),
399
+ offset=2.0 + 2.0 * d, corner_radius=4.0)
400
+ draw_list.add_rect_filled(gx0, gy0, x0 + width, gy1,
401
+ _mix(row_tints[i], group_bg_value + d * group_bg_step),
402
+ rounding=4.0)
403
+
404
+ picked = None
405
+ folded = False
406
+ # The tint a row's background ALREADY wears: its own, else the
407
+ # nearest tinted ancestor's (its group block or under the row).
408
+ # A code line's block wash and symbol washes in that same colour
409
+ # would just double it — they are dropped below (Lukas 09-04).
410
+ painted_stack = [] # (depth, painted tint)
411
+ for i, row in enumerate(rows):
412
+ ry = row_y[i]
413
+ while painted_stack and painted_stack[-1][0] >= depths[i]:
414
+ painted_stack.pop()
415
+ row_tint = row_tints[i]
416
+ painted = row_tint if row_tint is not None else (
417
+ painted_stack[-1][1] if painted_stack else group_tint)
418
+ painted_stack.append((depths[i], painted))
419
+ if ry + row_height < band_top or ry > band_bot:
420
+ continue
421
+ ind = depths[i] * TREE_INDENT
422
+ hot = (i == model.index) if model.kbd_mode else (i == hover)
423
+ own = row_tint is not None
424
+ is_file = row.kind == "file"
425
+ rx = content_left + ind + (ICON_COL if is_file else GROUP_INSET)
426
+ # Offset the row's shadow by depth depth so nested rows stack.
427
+ if hot and not own:
428
+ draw_list.add_rect_filled(rx, ry, x0 + width, ry + row_height,
429
+ hot_bg_untinted, rounding=4.0)
430
+ elif hot or own:
431
+ if own and not is_file:
432
+ add_shadow((rx, ry, x0 + width - rx, row_height),
433
+ offset=2.0 + 2.0 * depths[i], corner_radius=4.0)
434
+ draw_list.add_rect_filled(rx, ry, x0 + width, ry + row_height,
435
+ _mix(row_tint, row_bg_hot if hot else row_bg_value),
436
+ rounding=4.0)
437
+ text_y = ry + (row_height - line_h) * 0.5
438
+ text_x = rx + (4.0 if collapsible else 8.0)
439
+ foldable = collapsible and row.line in model.scope_keys
440
+ if foldable:
441
+ if not line_numbers_left or (over and hover == i):
442
+ draw_list.add_text(content_left + ind + 3.0, text_y, suffix_color,
443
+ collapsed_icon if model.is_collapsed(row) else expanded_icon)
444
+ if draw_state.on_action(
445
+ "left_mouse_down", view_id=f"{event_prefix}_fold_{row.line}",
446
+ rect=(content_left + ind, ry, rx, ry + row_height),
447
+ priority_delta=5) is not None:
448
+ model.toggle(row)
449
+ folded = True
450
+ marker_width = 0.0
451
+ change_kind = (change_kinds or {}).get(row.line)
452
+ scope_counts = model.scope_counts.get(row.line) if collapsible else None
453
+ if line_numbers_left and scope_counts is not None:
454
+ added_label, removed_label = f"+{scope_counts[0]}", f"−{scope_counts[1]}"
455
+ marker_width = imgui.calc_text_size(added_label + " " + removed_label).x
456
+ marker_left = x0 + width - 2.0 - marker_width
457
+ draw_list.add_text(marker_left, text_y,
458
+ pack_color(*Tint.change_count(added=True), 0.95), added_label)
459
+ draw_list.add_text(marker_left + imgui.calc_text_size(added_label + " ").x,
460
+ text_y, pack_color(*Tint.change_count(added=False), 0.95), removed_label)
461
+ elif change_kind:
462
+ if change_kind == "add":
463
+ marker, color = "+", Tint.change_count(added=True)
464
+ elif change_kind == "delete":
465
+ marker, color = "−", Tint.change_count(added=False)
466
+ else:
467
+ marker, color = "~", Tint.dd_text((0.06, 0.24, 0.45))
468
+ packed = pack_color(*color, 0.95)
469
+ if line_numbers_left:
470
+ marker = row.label.partition(" @ ")[0] if row.label else marker
471
+ marker_width = imgui.calc_text_size(marker).x
472
+ draw_list.add_text(x0 + width - 2.0 - marker_width, text_y, packed, marker)
473
+ else:
474
+ draw_list.add_rect_filled(rx, ry + 2, rx + 2, ry + row_height - 2, packed)
475
+ draw_list.add_text(text_x, text_y, packed, marker)
476
+ text_x += char_w + 3.0
477
+ if row.kind == "more":
478
+ # The "+ N more" row: Code-tab style, selectable like any row.
479
+ if hot:
480
+ draw_list.add_rect_filled(rx, ry, x0 + width, ry + row_height,
481
+ hot_bg_untinted, rounding=4.0)
482
+ draw_list.add_text(x0 + ICON_COL + 8.0, text_y,
483
+ _mix(more_tint, more_text_hot if hot else more_text_value),
484
+ row.text)
485
+ elif is_file:
486
+ tv = file_text_hot if hot else file_text_value
487
+ draw_list.add_text(x0 + ICON_X + ind, text_y,
488
+ _mix(row.tint, tv, sat=text_saturation, alpha=icon_alpha),
489
+ file_icon)
490
+ draw_list.add_text(text_x, text_y, _mix(row.tint, tv, sat=text_saturation),
491
+ row.text)
492
+ else:
493
+ suffix = row.label or str(row.line)
494
+ suffix_w = imgui.calc_text_size(suffix)[0]
495
+ if line_numbers_left:
496
+ number = str(row.line)
497
+ number_x = content_left - 5.0 - imgui.calc_text_size(number).x
498
+ number_color = Tint.line_number_tint(group_tint)
499
+ draw_list.add_text(number_x, text_y,
500
+ pack_color(*number_color[:3], 1.0), number)
501
+ code_w = max(0.0, x0 + width - 8.0 - marker_width - text_x)
502
+ else:
503
+ code_w = max(0.0, x0 + width - 8.0 - suffix_w - 12.0 - text_x)
504
+ if line_numbers_left:
505
+ indent_width = getattr(row, "indent", 0.0) * tree_indent
506
+ text_x += indent_width
507
+ code_w = max(0.0, code_w - indent_width)
508
+ block = None if _same_tint(row.block_tint, painted) else row.block_tint
509
+ spans = [sp for sp in row.spans
510
+ if not (_same_tint(sp[2], painted) or _same_tint(sp[2], block))]
511
+ # The focus symbol pops: full brightness on it, the rest of
512
+ # the line (and whole context rows) faded like the diff
513
+ # collapse preview; the hot line reads in full.
514
+ draw_code_line_fast(draw_list, text_x, text_y, row.text, char_w, line_h,
515
+ max_width=code_w, block_tint=block, spans=spans,
516
+ emphasis=None if hot else row.emphasis)
517
+ if not line_numbers_left:
518
+ draw_list.add_text(x0 + width - 8.0 - suffix_w, text_y, suffix_color, suffix)
519
+ if i == model.index and (not line_numbers_left or model.kbd_mode):
520
+ draw_list.add_rect(rx, ry, x0 + width, ry + row_height, sel_color,
521
+ rounding=4.0, thickness=sel_thickness)
522
+ # Click to pick: on_action keeps the honest z-order (a window in
523
+ # front blocks clicks) and replays on multiple hits.
524
+ if draw_state.on_action("left_mouse_down", view_id=f"{event_prefix}_row_{i}",
525
+ rect=(rx if foldable else x0, ry, x0 + width, ry + row_height),
526
+ priority_delta=4) is not None:
527
+ picked = row
528
+ finally:
529
+ pop_code_font(pushed)
530
+ imgui.set_cursor_screen_pos((x0, y0))
531
+ imgui.dummy(width, total_h)
532
+ if folded:
533
+ return True, model
534
+ if picked is not None:
535
+ model.picked = picked
536
+ model.index = rows.index(picked)
537
+ return True, model
538
+ return False, model
File without changes
@@ -0,0 +1,118 @@
1
+ """
2
+ Example: InputHandler with various backends.
3
+ """
4
+ from meltygui.core.input.input_handler import InputHandler
5
+
6
+
7
+ def demo_basic():
8
+ """Core API demo without any backend dependencies."""
9
+ handler = InputHandler()
10
+
11
+ print("=== Click Demo ===")
12
+ handler.clear_pending()
13
+ handler.register_hovered("button", 0, ["left_mouse_clicked", "left_mouse_double_clicked"])
14
+ handler.register_hovered("panel", 1, ["left_mouse_down"])
15
+
16
+ handler.feed_down("left_mouse", 100, 100)
17
+ handler.feed_up("left_mouse", 100, 100)
18
+
19
+ for view_id, evts in handler.process_frame().items():
20
+ for e in evts:
21
+ print(f" {view_id}: {e.input_id}:{e.action}")
22
+
23
+ print("\n=== Drag Demo ===")
24
+ handler.clear_pending()
25
+ handler.register_hovered("panel", 0, ["left_mouse_dragged", "left_mouse_up"])
26
+
27
+ handler.feed_down("left_mouse", 100, 100)
28
+ handler.feed_move(120, 110)
29
+ handler.feed_move(140, 120)
30
+ handler.feed_up("left_mouse", 140, 120)
31
+
32
+ for view_id, evts in handler.process_frame().items():
33
+ for e in evts:
34
+ print(f" {view_id}: {e.input_id}:{e.action} dx={e.dx:.0f} dy={e.dy:.0f}")
35
+
36
+ print("\n=== Keyboard Demo ===")
37
+ handler.clear_pending()
38
+ handler.register_hovered("text", 0, ["a_down", "space_down", "enter_down"])
39
+
40
+ handler.feed_down("a")
41
+ handler.feed_down("space")
42
+ handler.feed_down("enter")
43
+
44
+ for view_id, evts in handler.process_frame().items():
45
+ for e in evts:
46
+ print(f" {view_id}: {e.input_id}:{e.action}")
47
+
48
+
49
+ def demo_json_backend():
50
+ """Demo using JsonBackend for toolkit integration."""
51
+ from meltygui.core.input.pynput_backend import JsonBackend
52
+
53
+ handler = InputHandler()
54
+ backend = JsonBackend(handler)
55
+
56
+ print("\n=== JsonBackend Demo ===")
57
+ handler.clear_pending()
58
+ handler.register_hovered("panel", 0, [
59
+ "left_mouse_down", "left_mouse_up", "left_mouse_dragged", "cursor_moved"
60
+ ])
61
+
62
+ # Simulate drag
63
+ backend.push_down("left_mouse", 100, 100)
64
+ backend.push_move(110, 105)
65
+ backend.push_move(120, 110)
66
+ backend.push_up("left_mouse", 120, 110)
67
+
68
+ backend.pump()
69
+
70
+ for view_id, evts in handler.process_frame().items():
71
+ for e in evts:
72
+ print(f" {view_id}: {e.input_id}:{e.action}")
73
+
74
+
75
+ def demo_pynput():
76
+ """Demo with real hardware (requires pynput)."""
77
+ try:
78
+ from meltygui.core.input.pynput_backend import PynputBackend, HAS_PYNPUT
79
+ if not HAS_PYNPUT:
80
+ raise ImportError()
81
+ except ImportError:
82
+ print("\n=== Pynput Demo ===")
83
+ print(" Skipped (pip install pynput)")
84
+ return
85
+
86
+ print("\n=== Pynput Demo ===")
87
+ print(" Move mouse and click for 2 seconds...")
88
+
89
+ handler = InputHandler()
90
+ backend = PynputBackend(handler)
91
+ backend.start()
92
+
93
+ import time
94
+ start = time.time()
95
+
96
+ while time.time() - start < 2.0:
97
+ handler.clear_pending()
98
+ handler.register_hovered("root", 0, [
99
+ "left_mouse_down", "left_mouse_up", "left_mouse_clicked",
100
+ "cursor_moved", "left_mouse_dragged"
101
+ ])
102
+
103
+ backend.pump()
104
+ events = handler.process_frame()
105
+
106
+ for evts in events.values():
107
+ for e in evts:
108
+ print(f" {e.input_id}:{e.action} @ ({e.x:.0f}, {e.y:.0f})")
109
+
110
+ time.sleep(0.016)
111
+
112
+ backend.stop()
113
+
114
+
115
+ if __name__ == "__main__":
116
+ # demo_basic()
117
+ # demo_json_backend()
118
+ demo_pynput()
File without changes